New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add script API / make it scriptable #949

Open
bjorn opened this Issue May 15, 2015 · 8 comments

Comments

Projects
None yet
5 participants
@bjorn
Owner

bjorn commented May 15, 2015

Most people are not into C++ / Qt and compiling Tiled themselves, and as such small additions that would benefit their productivity are often not even started on. There should be an easy way to write scripts to perform actions in Tiled. Examples of things that should be possible:

  • Running some script explicitly that performs some action on the map
  • Adding a custom tool
  • Hooking up to events to perform certain actions automatically
  • Adding a custom map reader / writer

At the moment I think JavaScript would be the best choice of scripting language, because a JavaScript engine is provided as part of Qt and because having a JavaScript Tiled API should help with the development of a QtQuick based version of Tiled.

Examples of where scripting would have been useful (also see issues referencing this issue below):

@kkworden

This comment has been minimized.

Show comment
Hide comment
@kkworden

kkworden May 21, 2015

Did you have any idea as to how the feature would be implemented? Were you thinking a JS console? Macros and custom key binds? I'd be interested in helping add this feature.

kkworden commented May 21, 2015

Did you have any idea as to how the feature would be implemented? Were you thinking a JS console? Macros and custom key binds? I'd be interested in helping add this feature.

@bjorn

This comment has been minimized.

Show comment
Hide comment
@bjorn

bjorn May 21, 2015

Owner

@kkworden Great to hear you'd like to help out with this! All your suggestions would be nice things to have eventually. My approach would be the following:

  • First of all we need to instantiate the scripting engine. Since QtScript is deprecated, we should look at how this is expected to be done with QtQml. I think it's alright to depend on Qt 5 at this point. Maintaining compatibility with Qt 4 would be somewhat cumbersome here.
  • Then, we need to look into how we can effectively expose Tiled's data types to the script. In Qt, the bindings to the scripting engine are commonly done via QObject derived classes or alternatively with classes marked with Q_GADGET. On the wip/qtquick branch, I've derived the Tiled::Object from QObject, which allows exposing properties and methods of the basic data types to the scripting engine.
  • I think we should have a standard directory that Tiled scans for JS files, executing each script on startup. An API call should be available through which the script can add actions to the menu.
  • Eventually we need to add more high-level API to access Tiled itself, allowing opening and saving maps, changing the current map, adding a custom tool, etc.

Since I'd like the scripting support to extend to a QtQuick based version of Tiled later, I think it may be good to do this in the wip/qtquick branch.

Owner

bjorn commented May 21, 2015

@kkworden Great to hear you'd like to help out with this! All your suggestions would be nice things to have eventually. My approach would be the following:

  • First of all we need to instantiate the scripting engine. Since QtScript is deprecated, we should look at how this is expected to be done with QtQml. I think it's alright to depend on Qt 5 at this point. Maintaining compatibility with Qt 4 would be somewhat cumbersome here.
  • Then, we need to look into how we can effectively expose Tiled's data types to the script. In Qt, the bindings to the scripting engine are commonly done via QObject derived classes or alternatively with classes marked with Q_GADGET. On the wip/qtquick branch, I've derived the Tiled::Object from QObject, which allows exposing properties and methods of the basic data types to the scripting engine.
  • I think we should have a standard directory that Tiled scans for JS files, executing each script on startup. An API call should be available through which the script can add actions to the menu.
  • Eventually we need to add more high-level API to access Tiled itself, allowing opening and saving maps, changing the current map, adding a custom tool, etc.

Since I'd like the scripting support to extend to a QtQuick based version of Tiled later, I think it may be good to do this in the wip/qtquick branch.

@tpetry

This comment has been minimized.

Show comment
Hide comment
@tpetry

tpetry Aug 5, 2015

Sounds interesting and something i would need at the moment. So i have to stick to exporting the map, modifying it with scripting and then reopening it.
But i personally would not prefer a solution allowing tiled to scan some folders for javascript-files (too much magic). Instead (like adding tilesets) the scripts should be added manually, because maybe you want to add a script to one map but not to another and don't want to seperate them to different folders.

I would suggest the PostgreSQL approach of developing such a feature: Start with an easy implementation and make it more powerful over time. The first iteration could simply be the automappig feature implemented for scripting. So whenever the map is modified the script will be called with the changed element and the script can modify the map. This would enable:

  • a more powerfull automapping feature
  • running scripts (by placing "special" tiles on the map which the script would detect and could remove after running)
    UI-Integrations, Export-Scripts etc. could be added later, having something up and running and waiting for user feedback is much more powerfull than adding an overly complex solution at first. Custom Export-Scripts e.g. can be trivially implemented by writing a small script in any language by reading the XML output and converting it.

tpetry commented Aug 5, 2015

Sounds interesting and something i would need at the moment. So i have to stick to exporting the map, modifying it with scripting and then reopening it.
But i personally would not prefer a solution allowing tiled to scan some folders for javascript-files (too much magic). Instead (like adding tilesets) the scripts should be added manually, because maybe you want to add a script to one map but not to another and don't want to seperate them to different folders.

I would suggest the PostgreSQL approach of developing such a feature: Start with an easy implementation and make it more powerful over time. The first iteration could simply be the automappig feature implemented for scripting. So whenever the map is modified the script will be called with the changed element and the script can modify the map. This would enable:

  • a more powerfull automapping feature
  • running scripts (by placing "special" tiles on the map which the script would detect and could remove after running)
    UI-Integrations, Export-Scripts etc. could be added later, having something up and running and waiting for user feedback is much more powerfull than adding an overly complex solution at first. Custom Export-Scripts e.g. can be trivially implemented by writing a small script in any language by reading the XML output and converting it.
@genericptr

This comment has been minimized.

Show comment
Hide comment
@genericptr

genericptr Apr 4, 2016

I wanted to suggest the using Lua is the easiest way to implement this after just replacing a huge mess of AI code in a game to Lua. It's was really easy and had a very small learning curve.

Here's a possible implementation idea:

  1. Select a group of tiles in the editor which represent the "palette"
  2. On the brush tool assign a script to use
  3. When the user clicks to add a tile call the function "brush" on the Lua instance you created for the brush and pass the x,y coords of the cursor and the palette which is an array of tile ID's
  4. In the Lua script below implement "brush" and 2 functions get_tile() and set_tile():
function brush(x, y, palette)
    if get_tile(x, y) == 1 then -- if the tile under the brush is id 1
        set_tile(x, y, palette[1])  -- set the tile under the brush to tile #1 from the palette
    end
end
  1. get_tile() will return the tile ID at the x,y coords passed in by calling back to a C function in Tiled which will return that number.
  2. set_tile() will add a tile to the layer at the x,y coords and tile ID. Likewise this will call a C function which basically performs the same function as the brush normally would.

Here's the basic outline of how the C bindings work but my pseudo code is Pascal:

// create a new lua instance
// you'll want to keep track of this unless the user changes scripts on the brush
l := luaL_newstate();
luaL_openlibs(l);

// bind 2 c functions (LuaGetTile, LuaSetTile) to Lua
lua_pushcfunction(l, @LuaGetTile);
lua_setglobal(l, 'get_tile');
lua_pushcfunction(l, @LuaSetTile);
lua_setglobal(l, 'set_tile');

// run the lua script from a string you loaded early
luaL_dostring(l, scriptText);

// when the user uses the brush tool retrieve the lua instance you created early and call:
lua_getglobal(l, 'brush');
lua_pushnumber(L, 1);
// ??? we need an array of tile IDs passed in! I'll provide that code also since passing arrays to lua is confusing imo.
lua_pcall(L, 1, 0, 0);

// when get_tile() is called from lua it will run this C function (mine being Pascal, sorry)
function LuaGetTile (l: Plua_State): integer; cdecl;
var
    x, y: integer;
    tileID: string;
begin
        // get the x and y coords that were passed from lua
    x := luaL_checkinteger(L, 1);
        y := luaL_checkinteger(L, 2);
        // find the tile ID at x,y and return the value back to lua
       tileID := FindTileID(x, y)
    lua_pushstring(L, tileID);
    result := 1;
end;

This is trivially easy to implement and would mean the user could have precise control over what tile the brush will insert and what tiles are around the cursor. I'm happy to help answering any questions.

genericptr commented Apr 4, 2016

I wanted to suggest the using Lua is the easiest way to implement this after just replacing a huge mess of AI code in a game to Lua. It's was really easy and had a very small learning curve.

Here's a possible implementation idea:

  1. Select a group of tiles in the editor which represent the "palette"
  2. On the brush tool assign a script to use
  3. When the user clicks to add a tile call the function "brush" on the Lua instance you created for the brush and pass the x,y coords of the cursor and the palette which is an array of tile ID's
  4. In the Lua script below implement "brush" and 2 functions get_tile() and set_tile():
function brush(x, y, palette)
    if get_tile(x, y) == 1 then -- if the tile under the brush is id 1
        set_tile(x, y, palette[1])  -- set the tile under the brush to tile #1 from the palette
    end
end
  1. get_tile() will return the tile ID at the x,y coords passed in by calling back to a C function in Tiled which will return that number.
  2. set_tile() will add a tile to the layer at the x,y coords and tile ID. Likewise this will call a C function which basically performs the same function as the brush normally would.

Here's the basic outline of how the C bindings work but my pseudo code is Pascal:

// create a new lua instance
// you'll want to keep track of this unless the user changes scripts on the brush
l := luaL_newstate();
luaL_openlibs(l);

// bind 2 c functions (LuaGetTile, LuaSetTile) to Lua
lua_pushcfunction(l, @LuaGetTile);
lua_setglobal(l, 'get_tile');
lua_pushcfunction(l, @LuaSetTile);
lua_setglobal(l, 'set_tile');

// run the lua script from a string you loaded early
luaL_dostring(l, scriptText);

// when the user uses the brush tool retrieve the lua instance you created early and call:
lua_getglobal(l, 'brush');
lua_pushnumber(L, 1);
// ??? we need an array of tile IDs passed in! I'll provide that code also since passing arrays to lua is confusing imo.
lua_pcall(L, 1, 0, 0);

// when get_tile() is called from lua it will run this C function (mine being Pascal, sorry)
function LuaGetTile (l: Plua_State): integer; cdecl;
var
    x, y: integer;
    tileID: string;
begin
        // get the x and y coords that were passed from lua
    x := luaL_checkinteger(L, 1);
        y := luaL_checkinteger(L, 2);
        // find the tile ID at x,y and return the value back to lua
       tileID := FindTileID(x, y)
    lua_pushstring(L, tileID);
    result := 1;
end;

This is trivially easy to implement and would mean the user could have precise control over what tile the brush will insert and what tiles are around the cursor. I'm happy to help answering any questions.

@bjorn

This comment has been minimized.

Show comment
Hide comment
@bjorn

bjorn Apr 4, 2016

Owner

@genericptr Thanks for the suggestion. Actually I know Lua inside out and have used it in several C++ hobby projects as well as at work, where I essentially code Lua full time. Yet, I'm not convinced Lua is the best choice here because Qt ships with a JavaScript engine and provides an easy way of exposing existing C++ objects to it. Also, when I finally get time to work on a "Tiled 2", then probably all the high-level stuff will be written with JavaScript (as part of Qt Quick).

I think as languages go, Lua and JavaScript are very similar, though granted Lua is a lot less cryptic in its syntax. On the other hand, JavaScript is much more widely used. In the end though, it's more about what Qt supports and where I want to go with Tiled in the future.

The danger with starting with two simple API functions like in your example, is that there will be no end to requests for more functionality to be exposed. Maybe the tool wants access to tile properties, maybe it wants to base its decisions on data from other layers, etc. So in the end, we need a solution that can provide a full API and does not require extra maintenance.

Owner

bjorn commented Apr 4, 2016

@genericptr Thanks for the suggestion. Actually I know Lua inside out and have used it in several C++ hobby projects as well as at work, where I essentially code Lua full time. Yet, I'm not convinced Lua is the best choice here because Qt ships with a JavaScript engine and provides an easy way of exposing existing C++ objects to it. Also, when I finally get time to work on a "Tiled 2", then probably all the high-level stuff will be written with JavaScript (as part of Qt Quick).

I think as languages go, Lua and JavaScript are very similar, though granted Lua is a lot less cryptic in its syntax. On the other hand, JavaScript is much more widely used. In the end though, it's more about what Qt supports and where I want to go with Tiled in the future.

The danger with starting with two simple API functions like in your example, is that there will be no end to requests for more functionality to be exposed. Maybe the tool wants access to tile properties, maybe it wants to base its decisions on data from other layers, etc. So in the end, we need a solution that can provide a full API and does not require extra maintenance.

@genericptr

This comment has been minimized.

Show comment
Hide comment
@genericptr

genericptr Apr 4, 2016

If it's just as easy with JS than go that route for sure. I'd assume add a larger API eventually but you could implement something really powerful even with those 2 functions and build from there. Thanks.

genericptr commented Apr 4, 2016

If it's just as easy with JS than go that route for sure. I'd assume add a larger API eventually but you could implement something really powerful even with those 2 functions and build from there. Thanks.

@starwing

This comment has been minimized.

Show comment
Hide comment
@starwing

starwing Nov 3, 2016

and there is a Lua module for QtScript: http://www.nongnu.org/libqtlua/

you can use this directly to get same ability as JavaScript in QtScript.

starwing commented Nov 3, 2016

and there is a Lua module for QtScript: http://www.nongnu.org/libqtlua/

you can use this directly to get same ability as JavaScript in QtScript.

@bjorn

This comment has been minimized.

Show comment
Hide comment
@bjorn

bjorn Nov 3, 2016

Owner

@starwing I'm aware of QtLua. It would indeed help creating Lua APIs, but I'd still rather go with JavaScript since it's shipping with Qt and would anyway be the basis of a possible Qt Quick based Tiled.

Owner

bjorn commented Nov 3, 2016

@starwing I'm aware of QtLua. It would indeed help creating Lua APIs, but I'd still rather go with JavaScript since it's shipping with Qt and would anyway be the basis of a possible Qt Quick based Tiled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment