Introducing: GMLive

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二03 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

Introducing: GMLive

For a little while now, I was working on a new thing - a program that would allow to test GameMaker code right in your browser. It is now complete, published, and is pretty cool.

You can eithercheck it out right now or continue reading for development details and a bunch of demonstrations of it in action.

Technical, in short

So, in short, this works as following:

I wrote a GML->javascript compiler that functions closely enough to that of GM itself. I found a way to resolve names of built-in functions in the compiled game and used this to make dynamically generated JS code call the built-in functions directly. I wrote several Ace editor plugins to provide syntax highlighting and auto-completion on par to what GameMaker itself does. Technical: Transpiling GML

When I mention "wrote a GML->JS compiler", that, of course, is a bit of simplification.

While GML is a relatively non-complex language (on par with JS), writing a complete parser and transpiler for it, let alone one that behaves closely enough to the official one, is a challenge.

Making it work right in browser - even more so.

But, not to worry - as you may already know, by this time I wrote quite a number of parsers, lexers, and compilers for all sorts of formats and languages, meaning that I generally know what I'm doing.

As far as actual compiler goes, it is written in Haxe, which remains one of the best options for this kind of thing. Code is converted into " tokens "; tokens are converted into an abstract syntax tree , syntax tere is checked and modified a little to ensure that all is well and to carry out required structure modifications. Finally, structure is converted to actual JavaScript code, and inserted into a separate small page running a special blank-but-not-quite game.

GML being akin to JavaScript, many structures translate pretty well. Some don't.

For example, GML spots a with-statement, which on a glance may look similar to what Object Pascal and JS have, but is in fact a loop - passing an object type instead of a instance reference will have it pick through all of them. Therefore,

with (obj_some) show_debug_message(id);

must compile to something like

var with_arr = find_instances(1/* obj_some */);
for (var with_key in with_arr) {
if (!with_arr.hasOwnProperty(with_key)) continue;
var with_inst = with_arr[with_key];
// (several check-skips for instance being active)

Other distinct thing is GM's historic handling of arrays. See, in GML you are not required to initialize something as an array, you can do

var arr; arr[0] = 1;

And that will make a 1-element array. But not just that, you could also initialize arr to a non-array value, and that would still work. What is actually going is that GameMaker compiler finds out the first-of-a-batch array modification, and inserts a check before it, like so:

var arr;
if (!(arr instanceof Array)) arr = [];
arr[0] = 1;

Automatically locating the spots for inserting this takes a particularly curious algorithm that looks outwards and backwards of an expression while looking into branches along the way.

Overall, while reasonably challenging, I was able to get compilation to work very closely to original, meaning that any average script pasted into GMLive runs about the same as it would in GMS' html5 target.

Technical: Interfacing with GM:S HTML5 games

This is where the second act of curious things starts: by default, GameMaker minifies all function and variable names (for filesize concerns) in HTML5 builds, meaning that show_message will become something like _ZN1 .

Obviously, setting original+renamed function name equivalents would have been very time consuming (there's over 1000 functions total!) and not future-proof (since adding new scripts/variables causes the rest to change names based on new sorting results).

But, not to forget - this is JavaScript. JavaScript' API has all sorts of features, some so specific that you may not even realize what they can be used for. For instance, you can get any JS function' code as a piece of text. Of course, with all variables and functions renamed, it may not make any immediate sense, but that is fine - it describes exactly what the browser will do upon calling it.

The other bit is what functions GameMaker renames and does not rename. There are three general groups of identifiers that GM will not rename on compilation:

Reserved JavaScript function, variable, and keyword names: Because not every value is produced by the game itself and you do not want any syntax errors. Extension functions and variables: GM does a reasonable thing of not messing with extension code - extensions are output as-is, and calls to their functions are kept with original names. User-defined scripts starting with gmcallback_ . These were originally added for use with clickable_ functions (for producing button-like page elements that could call the game code), but can be used as-is as well (with a few gotchas).

As the result, if you make a script of kind

/// gmcallback_test
ext_func("func1", func1(1));
ext_func("func2", func2(1, 2));

It will be compiled to something like

function gml_Script_gmcallback_test() {
ext_func("func1", AQ1(1));
ext_func("func2", DF3(1, 2));

Which, combined with ability to get function code as a string, allows to very literally detect patterns in resulting JavaScript code to resolve the shortened names of functions.

So the seemingly-blank game spots a small extension which does it's share of trickery to map out every function of interest and store them in regularly-named global variables, which can then be referenced by generated code. This way the JavaScript generator only needs minimal knowledge of game state to output valid code, and the generated code is even pretty readable.

Inserted JavaScript then firstly calls a function that cleans up the results of previous execution, initializes what is needed, and starts the new cycle, calling JS scripts where needed.

All in all this is both easier and weirder than one could expect.

Technical: Editing GML

The next part is on making an appropriate editor.

While GML is not a complex programming language, syntax highlighting and auto-completion play a noticeable role when working with it.

For some of the previous projects I've used CodeMirror . CodeMirror is pretty good and easy to use, but is single-threaded, meaning that it does not cope too nicely with oversized files.

So, for this project, I've decided to use Ace . Ace is specifically designed for large tasks (makes use of Web Workers API for multi-threading) and is used in Cloud 9 IDE .

That said, Ace is very nice. While documentation does not cover a few things, source code is nicely documented, and I was able to find answers to all my subjects of curiosity via debugging or looking at the existing code (mostly of components in Ace itself).

Syntax highlighting was pretty easy to do - Ace's implementation is based on using regular expression groups to control tokenizer state, thus was a matter of using an existing set of highlight rules as reference to finetune GML-specific detection rules.

Introducing: GMLive

Auto-completion was a little trickier but still fine. To display argument list descriptions, these are stored separately and "unpacked" during initialization.

Introducing: GMLive

Status bar is a slightly altered version of built-in status bar plugin, which for mysterious reasons displays columns and rows starting from 0 by default. Function argument list is perhaps the largest highlight here, working by looking through tokens backwards from cursor position to discover whether the cursor is inside a function call.

User-defined scripts are highlighted, including showing arguments for them if they have documentation-comments at the start ( /// script(arg1, arg2) ).

Enums are currently not highlighted, as I am still figuring out the most appropriate method of having the application listen for structure changes (enums declarations being added/removed).

Overall, again, everything comes pretty closely to how GameMaker itself works.


But - enough with technical talk. Let's move onto the interesting bits. Click on any of the images to load up the according demo and click "Run" to run it.

Let's start with basics - displaying a piece of text on screen:

Introducing: GMLive

trace function is much like the regular show_debug_message , but accepts a variable number of arguments. Additionally, both functions now displays a scrolling log of messages over the game view for convenience.

If you'd like to mess with drawing functions, you can - just #define a script called "draw", and it'll be called on every frame (note: rendering loop is disabled unless there's a "draw" script):

Introducing: GMLive

If you'd like interactivity, you can do that too, same as you would in regular GM:

Introducing: GMLive

(one thing to keep in mind is that you'll need to click on the game area before it can receive keyboard inputs)

Next up is 3D. It appears to be a little known fact that GameMaker allows to use majority of d3d_ functions on HTML5 (making use of WebGL). Similarly, the procedure is equivalent to what you would do in GameMaker: Studio itself:

Introducing: GMLive

While there are some quirks with 3d mode in HTML5 still, there's enough to be able to experiment. There's also support for mouselock functions from my extension if you'd like to try making something with first-person view or "mouselook" in general.

Finally, instances. You currently cannot declare new object types (an API for that was deprecated), but you can create instances as per usual, and do what you want with them:

Introducing: GMLive

As with other data structures, instances are cleaned up on program restart/shutdown, so you don't need to do any additional actions to quickly iterate on code.

Future considerations

Depending on tool's reception, popularity, and donations, there are various possible ways of further improving upon it:

Enum highlighting : As I've previously mentioned, it would be nice to provide syntax highlighting and auto-completion for enum fields aside of compile-time error checking. Further editor polish : There are various small details that can be finetuned, such as auto-completion list popping up after 1 character entered (while in GM it pops up after 2) or display style for auto-completion items. Asset UI : It would be more convenient if it was possible to add assets via some sort of user interface. This isn't a particularly critical issue though, since I was able to patch in support for Data URI scheme (e.g. base64) into most loading-related functions. Object definitions : As previously mentioned, it would have been better if it was possible to define new object types. A good approach for dealing with this is not yet found, however. Server-side JavaScript target : My work on GML->JS compiler opens up a theoretical possibility of creating a more complex compiler based on it, which would output code that could be ran on Node.js servers. Unfortunately, this is something that requires substantial time investment (thus funding), and is a bit unlikely to happen too soon. In conclusion

This was not a small nor an easy project, but in the end it worked out, and works well.

There are still some things that can be done, but it perfectly usable.

You can support the development by donating via itch.io if you'd like.

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

主题: JavaJavaScriptHTMLHTML5GMWebGLNode.js
本文标题:Introducing: GMLive

技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(19)