Open the mockup drawing in full resolution
What are functions anyways?
GDevelop has a very awesome, yet relatively hidden feature: Events functions extensions. You can create an extension from your project manager, and create new actions, conditions, expressions and behaviors with events. Each condition, action and expression is a function - an event sheet that takes a few parameter values in, executes it’s events using those parameters, and potentially gives back a response (trigger or not trigger for conditions, for example).
There are also special lifecycle functions, that allow reacting to specific types of events: a scene switch, the game starting, an object being deleted…
Functions are awesome for many reasons:
Encapsulation & Separation of concerns
Groups events are pretty good at separating different systems of your game, but we can do better. With functions, you can package code in neat little readable event sheets without the clutter of a ton of group events!
Separation of concerns
Let’s say you have an FSM, and have many events outside of it change the state of that FSM. But you may have states that have special rules: when in the “falling” state, never switch to another state unless the ground is touched, never allow switch to “holding_sword” state if the sword is not in invenotry…
Of course, before changing the object’s variable containing the state of the FSM, you can simply do these checks, but this comes with some issues:
- What if you add a new state with new rules and forget to update all the places where states are switched to, and cause an illegal state state switch?
- What if you forget to add a check when doing a state switch?
- What if another person who helps on the project does not know of some of those rules and does an illegal state switch?
- You need to edit all the locations where state is switched if the rules change
This takes a lot of mental capacity, time & efforts, coordination with other people working on the game, and adds the risk of putting the game in a bad, buggy state.
The core of this issue is a lack of separation of concern: Knowing the rules of the FSM and switching states while respecting those is “not the job” of any event that want to change the state of the FSM, it’s up to the FSM to know all the rules and enforce them. The code for the FSM, as a concern of it’s own, should be isolated from events handling other concerns.
Functions can help there: simply make a function to request a state change! That function can then codify all the state switching rules of the FSM in a single event sheet. The rules can be changeed there, as a part of the FSM code, and all switching request will be forced to comply with rules at all time that prevent the FSM to get to an unintended invalid bad state.
Avoiding repetition
Let’s say you have a trampoline object that makes a player jump extra high when you touch it. You start making your events, but then realize you want a special animation for your player when doing this extra jump. You add the animation switch, and then you add an extra high jump sound, and then you prevent it from jumping when in a cutscene… That begins to be a pretty big event! And then you realize you want to have this high jump on other occasions, and copy paste it in a lot of places…
That’s annoying for many reasons:
- If you make a change you need to do it in many places
- If you make a mistake while copying or changing those events you can have a different behavior for the same high-jump
- The event being spread out may not be known by other people helping with the project and only modify a single copy of the events
- The location of the events becomes unclear
- etc…
Instead, you could make a “Do a high-jump” action function with all of those events, and call it in the relevant places! You can thereby have the code in one easy to find place, and change it once to change it everywhere.
Instantly executed
My proposal
Ok so functions are awesome, but you may still be wondering what my proposal is all about. Well, as I said, functions are pretty hidden right now, and most of all really unpractical. I think a few changes are needed.
1. Reacting to events outside of the game state
Calling events in GDevelop “events” is, in my opinion, kind of a misnomer. The word “event” implies that the actions are executed the moment the conditions are met, but they are not. A more befiitting name (although I am not suggesting changing the term) would be “queries” in my opinion: They are regularly querying the currrent state of the game, and if the query is met, execute actions. This is all cool and good, until we introduce “real events” into the mix.
Some events are not part of the game state, and are simply, well, events that may happen at multiple moments. You may have encountered those while using for example P2P (message receiving), Multitouch (touch has started/ended), Firebase (watching a change in a document), etc already. Each of those cannot just run your actions when they happpen, they need to store the event’s data in some kind of game state to be queried later.
I won’t get into too much detail, but that makes it really tricky to ensure that the condition is executed whithout delay, at every event that use it, and without missing an event. P2P has two different queue systems switchable with the “dataloss” option each with disadvantages, Multitouch a list system where you to write a ton of events to go through the whole list of touches when the condition is met to not miss one, Firebase queries changes a value in a scene variable…
All of this makes it not only harder for creators to make their game, because those conditions become complex to understand and use, but also for the developers who have to implement those complex and weird queues systems! Instead, we could simply use a function, and just run it whenever the event for it is triggered. Functions have parameters, so this would also allow passing in additional data from the event (for example, extra data sent over P2P).
So, let’s make it possible for extensions to define lifecycle functions that they may call when they please, and for people to create those lifecycle functions to be called when the extension triggers the event.
2. Make functions more accessible
Functions are super hard to find and use. You need to create an extension and all, and then it can become a cluttered mess of different functions and behaviors, and you need to go through it all to find the functions that interest you… And those extensions are made to be usable from any project, so you are forced to pass in any value, object, behavior, etc that you may want to use in it. See where I’m getting at?
I would suggest to make functions more accessible, by simply adding them outside of extensions:
Global functions (in the project manager)
Global functions would be callable from anywhere in your project, and have access to global objects. You could use them to add utility functions, use a lifetime events function to run global initialisation code when the game has launched (connect to p2p broker, enable analytics, load saves, etc.), or use a global “popup” object to create popups from any scene when someone in your friendlist has come online or when you got logged out…
Scene functions (in the scene events sheet)
Those functions would only be accessible from the scene it was defined in, and would have access to all scene variables and objects in this scene. This can be used to implement functions in this scene. It allows to make a function for a single scene without the hassle of passing all objects and behaviors through. It would also allow to have lifecycle functions to save when switching away from a scene for example. Or a leaderboard scene could use a firebase event function to update what it displays when the data on the leaderboards changed.
Objects functions (Bound to an object)
Functions bound to objects would be so insanely cool, as that allows to create another layer of separation of concerns, but would get even more interesting when combined with global objects or objects groups. You would be able to add events for a global object that just work, without linking external events on all scenes. With objects groups, you could make let’s say an “Enemies” group, that all share the health code. If you want to make individual death sounds and particles, you could create a function that plays the sound you want and create the right particle for each enemy type, and call it on that object group to make each object play the right animation and sound on death.
You could also use lifecycle functions to do something when the object is created or deleted…
I could talk for hours about how awesome functions are, and all that you could do with them but I already wrote too much If you read all the way until here, first of all, thank you so much! Take this viirtual cookie as a token of my gratitude:
At this point, I already spent countless hours writing this, and even more creating the attached mockup at the top. This proposal is an RFC - a Request For Comment. Please be so kind as to take 10 minutes of your time to share your opinion with this proposal, criticism is as welcome as supporting messages. Thank you to everyone who do take the time to read this and respond, you all get a second virtual thank you cookie