- Notifications
You must be signed in to change notification settings - Fork14
A dictionary for people moving from GM:S to Godot
License
coppolaemilio/gamemaker-godot-dictionary
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This document is for game maker devs like me that are moving their games or engine from GM:S to Godot. The first section gives a brief overview of the framework differences. The rest gives an API comparison for specific GML functions and their GDScript equivalent. You can use your browser's search functionality to find particular GML functions.
- Framework
- Events
- Scripts
- Globals
- Grouping objects
- Instance functions
- Drawing functions
- Image variables
- Direction functions
- Strings
- Random functions
- Math functions
- Game functions
- Room functions
- Window functions
- Other functions
- Data Structures
- Collision functions
Below is a list of basic framework differences between the two engines. Understanding these will help you more quickly adapt to Godot's workflow.
In Game Maker, you create aroom
filled with a list ofobjects
that each may hold asprite
and a series ofevents
which in turn trigger a series ofactions
.Your scripted behavior exists in theactions
portion. To reproduce this behavior multiple times between objects, you create ascript
that can be executed as a singleaction
.
In Godot, you create ascene
filled with a hierarchy of specialObjects
calledNodes
. Rather than having you specify logic attached to a generic object though, Godot providesa variety of customized objects for you. When you create aScript
, you are in factextending an existingObject
type with custom features.
Objects in Godot can have any combination of...
- properties (like defining a variable during the Create Event)
- constants (same, but they cannot be changed)
- methods (like an action or series of actions)
- Note that some methods are "notifications", triggered by the engine automatically. These are often preceded with an underscore '_'.
- signals (like custom events that other objects can react to)
Many of the code comparisons you see will illustrate a method, a.k.a. function, in place of an event's logic. Rather than having several scripts that each supply the logic for a single event's action(s), Godot has you define a single script per object with multiple functions defined for each "event" (perhaps even one for multiple).
Game Maker's rooms provide a flat list of the different types of assets that exist in the room. In order to have multiple sprites or physical objects come together to form the same object, you have to manually position them all during a step event by globally accessing them from within the room.
Godot's scenes are more small scale and self-contained. Your scenes can effectively be an object unto themselves as you add all of the necessary Sprites, PhysicsBody2Ds, and other Nodes to your scene's hierarchy to build up your object. That scene can then beinstanced
within a larger scene, similar to creating an object in a room. What's more, the nodes lower in the hierarchy will automatically move relative to their parent, so there is no need to manually position them.
In Game Maker, there is a concept of having a "parent" object. Child objects then inherit properties and functions from the parent. However, in Godot there are two distinct concepts: Inheritance and Ownership.
When you construct a scene, you are creating a hierarchy of nodes with parent-child relationships. The node at the top, i.e. the "root of the scene", provides a single point of contact between this scene and others. However, the child nodes do not inherit its functionality. The parent merely "owns" each child node and may pass on information to it that it can use.
This is why a child Node2D will move relative to its parent. The parent tells the child where the parent exists, and the Node2D object will take that into account when positioning itself.
To implement inheritance, you create a script and place it on a "base" object. You may then define custom features for this new type. These scripts will know the properties, constants, methods, and signals of the base object they are extending.
In Game Maker, when you need to code logic inside an object, you use Events. There are many kinds of events but the most used ones are:create
,step
,draw
.
When you need to declare variables for an object in GMS you do it inside theCreate Event. The equivalent in Godot is a function called_ready()
. The main difference here is that on GMS, theCreate Event declares variables that are accessible from everywhere. In Godot if you want your variables to be exposed to other functions or Nodes (objects) you need to declare them outside of the_ready()
function at the top of the document.
Imagine that we want to set the variableplayer_speed
to10
but if there are monsters present, you want it to be5
.In Game Maker you can code all this inside theCreate Event:
GMLCreate Event
player_speed =10;monsters = instance_number(obj_monster);if (monsters) { player_speed =5;}
In Godot you will have to code it like this:
GDScript_ready()
extendsNodevarplayer_speed=10func_ready():varmonsters=get_tree().get_nodes_in_group("MONSTERS").size();ifmonsters:player_speed=5
SimpleStep Event function for moving an object.
GMLStep Event
x += player_speed;
GDScript_process()
func_process(delta):position.x+=player_speed
SimpleDraw Event function for drawing a rectangle.
GMLDraw Event
draw_rectangle(100,120,132,152,false);
GDScript_draw()
func_draw():draw_rect(Rect2,Color,boolfilled=true)
Godot does not provide an equivalent notification function for theDestroy Event but it can be accessed through the actual notification callback.
GDScript_notification(what)
func_notification(what):matchp_what:NOTIFICATION_PREDELETE:# execute logic before deleting the object.
Another option is to easily code up your own in GDScript. Instead of destroying that node with the usualqueue_free()
you create a function calleddestroy()
and execute some code before self deleting.
GDScriptdestroy()
funcdestroy():# Here you write whatever you want to# run before removing the nodequeue_free()
In game maker you can create scripts that you can call from any object in your project. In Godot, the so called "scripts" are called functions and you can declare all the customfunctions
that you want inside any node.
To compare between the two you can see this simple "script" that will add two numbers:
GML: Create new script called add_numbers
return argument0 + argument1;
GDScript: Inside a node's code
funcadd_numbers(argument0,argument1):returnargument0+argument1
In Godot instead of using the namesargumentX
it is recommended that you use a more descriptive name. Every argument named when you declare the function will create a variable that you can use from inside of it.
funcadd_numbers(number_one,number_two):returnnumber_one+number_two
In Game Maker you can declare globals very easy by just addingglobal.
at the start of a variable. In Godot you can create a similar kind of variables via theSingletons (AutoLoad) feature.
I recommend you to read the entry of the Godot documentation but to get a quick equivalent you can do the following:
First of all, create a
global.gd
script.Then, Select Project > Project Settings from the menu, switch to the AutoLoad tab
Add a new entry with name “global” that points to this file:
Now, whenever you run any of your scenes, the script is always loaded. The variables declared insideglobal.gd
can be accesed or modified the same way you would do in GML:global.variable_name
.
The cool thing about godot is that you can also declare functions inside theglobal.gd
file that you can use from any other instance inside your game.
In Game Maker objects are automatically grouped by object_index. This process is not automatic for Godot, so you will need to manually add your nodes to a group to be able to refer to them and perform group actions on them as conveniently as you would in Game Maker. You can add nodes to groups either through code or the editor IDE. Generally you will only want to add your scene's root-node to the group, not all the attached Sprites and CollisionShapes. When the node is deleted it is automatically removed from groups.
Create a new scene in the editor called Tree.tscn, then attach a script to the scene's root-node with the following code:
func_ready():add_to_group("Tree")Now every Tree scene you place will be a part of that grouping, which has a variety of uses.
GML
if object_index = obj_monster {// Object is obj_monster}
GDScript
ifis_in_group("obj_monster"):# Node is obj_monster
GML
with object { y +=1;}
GDScript
foriinget_tree().get_nodes_in_group("groupname"):i.position.y+=1
Alternatively, you can run a function that's inside all members of the group:
get_tree().call_group("groupname","function",argument0,argument1,etc)
GML
instance_number(obj);
GDScript
get_tree().get_nodes_in_group("obj").size()
GML
instance_create(x, y, obj);
GDScript
varscene=load("res://scenefilename.tscn")varid=scene.instance()add_child(id)id.position=Vector2(x,y)
GML
instance_destroy();
GDScript
queue_free()# for Nodes, waits until next frame to delete all queued Nodes at oncefree()# for all Objects, deletes immediately
Note that deleting a Node will also delete all of its attached children automatically.
On game maker you can only use the drawing functions inside the draw event of an instance. On Godot you have to call the drawing functions inside afunc _draw()
of aCanvasItem.
Generally in Godot, you don't really need to call draws all the time since you can use pre-built scenes with everything for your UI. Things like text, panels, buttons and so on are already built-in and ready for you to use without any need to code the entire logic from scratch like you would have to do in Game Maker. Please search for any "Control Nodes" tutorials online and you will find how good this part of Godot is.
This guide will get you started with UI design:Design interfaces with the Control nodes
If you still want to torture yourself with individual draw calls, here you have some references:
GML
// These values are taken as being between 0 and 255make_colour_rgb(red, green, blue);
And you also have the colors likec_white
,c_blue
, etc..
GDScript
# Constructs a color from an RGB profile using values between 0 and 1 (float)Color(0.2,1.0,.7)# You can also set the color alpha by adding an aditional valueColor(0.2,1.0,.7,0.5)
You can also create a color from standardised color names withColorN
. See the full listhere.
GML
draw_rectangle(x1, y1, x2, y2, outline);
GDScript
draw_rect(Rect2,Color,boolfilled=true)
To draw the same rectangle on both engines:
GML
draw_set_color(c_red);draw_rectangle(100,120,132,152,false);GDScript
draw_rect(Rect2(Vector2(100,120),Vector2(32,32)),Color("red"),true)
Drawing text is a bit more tricky in Godot. Make sure you declare the font resource outside of the_draw()
function.
GML
draw_text(x, y, string);
GDScript
draw_string(font,Vector2(x,y),string,color,separation)
To draw the same rectangle on both engines:
GML
draw_set_font(fn_bitter);draw_set_font(make_color_rgb(0,0,0));draw_text(140,100,"Hello world");GDScript
varfont=load('res://fonts/Bitter.tres')func_draw():draw_string(Bitter,Vector2(140,100),"Hello world",Color(0,0,0,1),-1)
GML
image_blend = c_red;
GDScript
modulate=Color.red
Godot rotates clockwise while Game Maker rotates counter-clockwise.
GML
image_angle =90;
GDScript
rotation_degrees=-90
GML
visible =true;visible =false;
GDScript
show()hide()visible=truevisible=falseset_visibility(true)set_visibility(false)
All rotation functions in Godot will rotate clockwise as the variable increases (GameMaker rotates counter-clockwise) and most functions take radians and output radians, not degrees. It's best to get used to these differences, thoughdeg2rad()
andrad2deg()
can help out. If you want to adjust a radian by degrees you can simply do:+deg2rad(180)
.
GML
degrees = point_direction(x1, y1, x2, y2)
GDScript
varradians=Vector2.angle_to_point(Vector2)
point_direction
returns degrees, whileangle_to_point
returns radians. Another difference is the order the coordinates are taken, if a backwards angle is returned you may want to swap the Vector2s.
GML
move_x = lengthdir_x(len, dir);move_y = lengthdir_y(len, dir);
lengthdir_x/y
returns individual X or Y vector components, while the Godot equivalent will store both of those floats in a Vector2.
GDScript
varmove_xy=Vector2(1,0).rotated(radians_var)*length
Alternative:
varmove_xy=Vector2(cos(radians_var),sin(radians_var))*length
In place ofradians_var
we can use our earlierangle_to_point
output variable (which is in radians), or maybe writedeg2rad()
for something else.
String functions are a little bit different because in game maker everything is a function but in Godot they are methods. You can also treat Strings in godot like arrays.
GML
string_length(string);
GDScript
string.length()
GML
string_char_at(string, index);
GDScript
string[index]
GML
string_upper(string);string_lower(string);
GDScript
string.to_upper()string.to_lower()
GML
string_delete(string, index, count);
GDScript
string.erase(index,count)
GML
var value = choose(1,2,3);
In order to achieve something similar in Godot you have to first create an array with all the options and then get a random value from that array.
GDScript
varoptions= [1,2,3]varvalue=options[randi()%options.size()])
These functions take in a single float and return the arc sin
GML
arcsin();
GDScript
asin()
These functions take in a single float and return the arc cos
GML
arccos();
GDScript
acos()
These functions take in a single float and return the arc tan
GML
arctan();
GDScript
atan()
With this function you can quit the game.
GML
game_end();
GDScript
get_tree().quit()
GML
room_goto(room_name)
GDScript
get_tree().change_scene("res://nameofthescene.tscn")
GML
room_restart();
GDScript
get_tree().reload_current_scene()
GML
window_set_caption(string);
GDScript
OS.set_window_title(string)
This will open the specified URL on the browser.
GML
url_open('http://yoyogames.com' );
GDScript
OS.shell_open('http://godotengine.org/')
This is a similar way of calculating the distance from an instance to another. On gdscript all the variables have to be Vector2. Also, be mindful that not all nodes have theposition
variable.
distance_to_object(obj_Player)
position.distance_to(Player.position)
Godot's Array doubles as the stack and queue data structure. Unfortunately, Godot has no Lists for the scripting API (although the engine itself has them).
GMLglobal Stack functions
stack = ds_stack_create();// ...do various insertionsifds_stack_empty(stack) {// is empty}elseifds_stack_size(stack) == 3 {// there are 3 items on the stack stack2 =ds_stack_create();ds_stack_copy(stack2, stack); top =ds_stack_top(stack2); /top is the item at index2ds_stack_pop(stack2);//top's value is no longer in stack2ds_stack_push(stack2,5);//top's previous index now has value 5 serialized_data =ds_stack_write(stack2);//converts stack2 into a data string stack3 =ds_stack_read(serialized_data);//deserializes the data string into another stack object.}ds_stack_clear(stack);ds_stack_destroy(stack);
GDScriptArray type as "stack"
vararr= []ifarr.empty():# if not arr: also workspasselseifarr.size():# else if arr: also worksvararr2=arr.duplicate()vartop=arr2.back()arr2.pop_back()arr2.push_back(5)# a stringified version of the array, but not actually serialized datavararr_str=str(arr2)# all serialized data is stored on Resource objects which automatically serializes all properties of any kind.varres_script=GDScript.new()res_script.source_code="extends Resource\n"res_script.source_code+="var array"res_script.reload()# ^ you could also just create an actual script and load it, e.g.res_script=load("res://res_script.gd")varres=res_script.new()res.array=arr2ResourceSaver.save("res://my_array.tres",res)# serializes all properties on res for youvarres2=load("res://my_array.tres")arr.clear()# because Arrays are allocated with a reference counter, they automatically delete themselves when no references to them exist anymore.
In addition topush_back()
andpop_back()
to simulate a stack, Godot's arrays also havepush_front()
andpop_front()
. The combination ofpush_front()
andpop_back()
gives you a queue's features.
Godot's Dictionary object is the equivalent of GML's DS Map. They can be inlined into GDScript code just like Arrays by using curly brackets.
Unlike DS Maps however, Arrays, Dictionaries, and other data (even custom Objects!) can be made into a keyor value in a Dictionary (or a value in an Array). This is contrary to what the Yoyo Games docs have to say about DS Maps:
NOTE: While these functions permit you to add lists and maps within a map, they are useless for anything other than JSON, and nested maps and lists will not be read correctly if written to disk or accessed in any other way.
GMLglobal Map functions
map = ds_map_create();ds_map_add(map,"level",100);ds_map_add(map,5.2,"hello");map[?"super"] ="awesome";ds_map_add(map,"super","goodbye");// fails because key "super" already existsifds_map_exists("super") {// "super" is a keyds_map_replace(map,"super","goodbye");//succeeds because this is a "replace" operationds_map_delete(map,"super");// now key "super" is gone}ifds_map_empty(map) {// it is empty}elseifds_map_size(map) == 2 {// has 3 key-value pairs}// iterationvar size, key, i;size = ds_map_size(inventory);key = ds_map_find_first(inventory);for (i =0; i < size; i++;) {if key !="level" { key =ds_map_find_next(inventory, key) }else {break; }}map2 = ds_map_copy(map);// map2 is now a copy of map3serialized_data = ds_map_write(map);//converts map into a data stringmap3 = ds_map_read(serialized_data);//deserializes the data string into another map object.ds_map_clear(map);// it is now emptyds_map_destroy(map);// memory is freed
GDScriptDictionary type
vardict= {"hello":"world"}# initialization, just like an array, could be empty with '{}'dict["level"]=100dict[self]= {3.4:"PI", [1,2,3]:GDScript.new(),}ifdict.has(self):dict[self]="testing"# just deleted all of those objects because their references disappeared by overwriting the valuedict.erase(self)# the key-value pair is now gone.ifdict.empty():pass# it is emptyifnotdict:pass# sameifdict.size():# if dict: also workspass# the Dictionary is non-emptyifdict.size()==2:pass# there are 2 key-value pairs that exist# iterationfora_keyindict:print(dict[a_key])# Iterate over first 3 key-value pairs.# Safely exit early if it isn't that largevari=0fora_keyindict:varvalue=dict[a_key]i+=1ifi==3:break# string keys can be directly accessed as if properties on an objectvarvalue=dict.level# copy the Dictionaryvardict2=dict.duplicate()# serialization is the samevarres_script=load("res://res_script.gd")varres=res_script.new()# res_script.gd needs to define a property, in this case called 'foo'res.foo=dict2ResourceSaver.save("res://res.tres",res)varres2=load("res://res.tres")# these print the same thingprint(dict2)print(res2.foo)dict.clear()# going out of scope means that the local variables 'dict', 'dict2', 'res_script', 'res', etc. disappear and all references to them end.# Because they either an Array, a Dictionary, or a Reference object, they are freed automatically when all references to them vanish.
For JSON manipulation, you can also freely translate a combination of Arrays, Dictionaries, and other data from GDScript into a JSON string and back by using the JSON global.
JSONparse(string)
varp=JSON.parse('{"hello": "world"}')ifpandp.error==OK:iftypeof(p.result)==TYPE_DICTIONARY:print(p.result["hello"])# prints "world"iftypeof(p>result)==TYPE_ARRAY:pass# could be possible, with a different JSON stringelse:print("parse error")
Godot does not provide its own Priority Queue object. Instead, to accomplish its functionality, you must either implement your own special Node class that handles its own sub-hierarchy of nodes, or you must use an Array in which you sort it after every mutable operation. Array comes with a built-insort
function for simple types and the option to specify your own sorting algorithm with asort_custom
method.
Arraysort_custom(script, static_function_name)
classMyCustomSorter:staticfuncsort(a,b):ifa[0]<b[0]:returntruereturnfalsevarmy_items= [[5,"Potato"], [9,"Rice"], [4,"Tomato"]]my_items.sort_custom(MyCustomSorter,"sort")
The "min" and "max" are then always located atfront()
andback()
, respectively.
Godot does not provide its own grid class (the TileMap and GridMap nodes are purely for visualization purposes). To create a 2D array, you need to create an array of arrays:
GDScriptArray of Arrays
vararr= [ [0,1,2], [3,4,5], [6,7,8],]print(arr[1][2])# prints 5
There are no special utilities methods to assist in 2D array operations. You will have to code those manually, perhaps with a script extendingReference
.
# grid.gdextendsReferencevardata= []func_init(width=0,height=0):resize(width,height)funcget_width():varmax=0forarrindata:ifarr.size()>max:max=arr.size()returnmaxfuncget_height():returndata.size()funcresize(width,height):data.resize(height)forarrindata:arr.resize(width)# ...etc.# node.gdextendsNodeconstGrid=preload("res://grid.gd")func_ready():vargrid=Grid.new(3,5)# initializes Grid with width=3, height=5# end of scope, local variable 'grid' exits, no references to the Grid object. It inherits Reference, therefore it is freed.
Note that there are many ways to detect collisions in Godot such as:is_on_floor()
,is_on_wall()
,is_on_ceiling()
,move_and_slide()
, RayCast2D and signals. In many cases those may be preferable to the methods shown below.
As a reminder, in GM this function checks a single point position and then returns the instance ID of the colliding object.
GML
instance_id = instance_position(x, y, obj);
GDScript
Feed this function a local position Vector2 and group name string like so:instance_position(position + Vector2(32,16), "obj")
Setroom_node
to the node you want the coordinates of this check to be relative to, which is typically the "level" node that you're spawning all your scenes under. (The reason we useroom_node.global_position
is if we have any parents or grand-parents that have offset positions then the checked position won't be correct.)
onreadyvarroom_node=$'..'funcinstance_position(pos,group):varspace=get_world_2d().direct_space_stateforiinspace.intersect_point(room_node.global_transform.translated(pos).get_origin(),32, [],0x7FFFFFFF,true,true):ifi["collider"].is_in_group(group):returni["collider"]returnnull
In GM this function checks a single point position and then returns true or false depending on whether there was a collision.
GML
boolean = position_meeting(x, y, obj);
GDScript
Same code as Instance Position but replace the last two lines withreturn true
andreturn false
.
In GM this function uses the collision mask of the instance that runs the code to check a position for a collision, then returns true or false depending on whether there was a collision.
GML
boolean = place_meeting(x+32, y+16, obj);
GDScript
test_move
works a little differently thanplace_meeting
, it involves the X and Y position of the current node by default and will return true if it finds anything along the entire vector, while GM's function will check one specific position only. (This function is exclusive to KinematicBody and KinematicBody2D.)
varboolean=test_move(global_transform,Vector2(32,16))
The line below will check only one specific position like GM's function, without checking along the entire vector:
varboolean=test_move(global_transform.translated(Vector2(32,16)),Vector2(0,0))
The line below will check only one specific position like GM's and without involving the current node's X and Y position. This is equivalent toplace_meeting(100, 200, obj)
. Setroom_node
to the node you want the coordinates of this check to be relative to:
varboolean=test_move(room_node.global_transform.translated(Vector2(100,200)),Vector2(0,0))
In GM this function uses the collision mask of the instance that runs the code to check a position for a collision, then returns the instance ID of the colliding object.
GML
instance_id = instance_place(x+32, y+16, obj);
GDScript
If we setmove_and_collide
's last argumenttest_only
to true, then it will behave exactly liketest_move
except it will return collision data and not just a boolean:
varcollision_data=move_and_collide(Vector2(32,16),true,true,true)varid=collision_data.collider
If we want to avoid checking along the entire Vector and check only one specific position like GM's functions, then we'll need to useintersect_shape
. Setroom_node
to the node you want the coordinates of this check to be relative to. Setshape
to whatever CollisionShape2D node you want to use:
onreadyvarroom_node=$'..'onreadyvarcolshape_node=shape_owner_get_owner(0)# Returns the first child node which contains a collision shapefuncinstance_place(pos,group):varspace=get_world_2d().direct_space_statevarquery=Physics2DShapeQueryParameters.new()query.shape_rid=colshape_node.shapequery.transform=room_node.global_transform.translated(pos)*colshape_node.transformforiinspace.intersect_shape(query):ifi["collider"].is_in_group(group):returni["collider"]returnnull
Written byEmilio Coppola andAdditional contributors
Special thanks to a lot of poor souls that helped me answering questions on Discord, Reddit and Twitter.
About
A dictionary for people moving from GM:S to Godot
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.