Functions - A Proposal

Graphical representation of the RFC, through a mix of text blocks and mockups drawings.

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…

Endless possibilities!!


I could talk for hours about how awesome functions are, and all that you could do with them but I already wrote too much :sweat_smile: 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:

Cookie 🍪

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 :yellow_heart:

12 Likes

Hi! I read it all hoping for cookies, and wasn’t disappointed.

Unfortunately I may not be able to make useful comments. Out of GDevelop algorithm I know nothing about programming, so I cannot make comment for the developer side. What’s more I haven’t so far used a lot P2P and these “lifecycle” events. Finally I haven’t used a lot objects and their behaviorus like enemys, etc.

I mainly tried to program scene that choose and prepare all datas before a game starts. So mainly using one scene and scene variables.
I use a lot External Events, that’s why I read your proposition and can leave a comment.

First I hesitated between using external events or functions. Since I wanted to be able at any time to work on the function events and not to have to export it as a function everytime a change is made, I chose to use External Events. For each one of them I clearly set what variables will be in entry and what variables will go out of it.

So then, in my scene, I can use these external events knowing perfectly what variable set before the link event. For example I made an external event “pathfinding”, and I know that, before this “external event link”, I have to set variables “pf.start” and “pf.arrival” according to the path I want the character to go.

This works perfect for me but there are 2 limitations.

1- external events are not a dynamical call during runtime. It’s more like a normal group that helps user to work (and that’s great this way). What I mean is, it doesn’t allow loop-nested external events, since they are all writen before the game starts (I presume). With these external events A, B and C, this doesn’t work :
A contains B contains C contains A
Yet I needed something like that, so I created a copy of A that doesnt contains B, lets call it AA.
So I ended up doing:
A contains B contains C contains AA
So that works.

This is only a small limitation for me and I don’t request anything to be done about it.
I just wonder about your functions : will they allow these loops (dynamic functions execution during runtime) or will they work as External Events ?

2- External Events are within a project. I have different games that will use the same External Events or functions. For example different projects that will use the same Pathfinding external event. So what I did is to have a project only for my External Events. I would modify them only in this project. Then I would copy them in the game projects that need to use them.

I understand that in many cases, with object function for example, you want the function to be only in this project, or in this scene, but not in all your projects like for extensions. However in some cases, you might want to have a place to develop your function, and that this function can be used in any project that needs it.

So my general questions about your functions are (sorry if I didn’t get it by reading your detailed explanations) :
1- Where the functions will be made, and modified? If I understood well, it will be inside the game project that will use them?
2- Functions will be made and modified by user exactly like an external event, with GDevelop “events” (conditions, actions), right?
3- Will it be necessary to “export the function”, or simply like External Events, you can directly use them, according to their name?
4- Will they allow nested-loops (A contains B and B contains A) or will they work as External Events ?

Once more, my comments are more questions and I’m not in a situation to see how benefical it would be with these new functions. However it looks great and, I get, more or less, the huge advantage it can make for global objects for example (it’s just a shame I cannot get the whole advantages).

Hopefully others will understand things better and give usefull comments!

1 Like

I like the idea of being able to make functions specific to the game they were created in. In my early days of using GDevelop I got excited when I read about functions when I saw that it looked like just a one click thing - ‘Extract events to a function’. But then I didn’t know what to do with the screen that came up so just left it. Some time later I managed to make an extension but the process made me less likely to want to use in game functions because the whole process isn’t ‘local’ to the game.

And as for FSM, I tried to implememt that in my current project which is a utility rather than a game but found it too difficult to maintain crossover funtionality between different external events and converted everything back again. Right now I’m at the point of getting things working in separate projects to avoid the clutter of other things interfering with the testing process. And I’ve thought again about functions but doubt my abilities to integrate them.

PS.
Thank you very much for the Object Picking Tools extension, it’s made things a lot easier for me :cookie:

1 Like

Making functions more accissible by moving them outside of extensions has been on my wishlist for a long time. I also wanted to write a similar “propsal”, but never had enough stuff to share.

About the other thing you mentioned at the beginning, I don’t think I can say I understand what you are talking about, but you probably know what you said so sure!

What is the most important to me is that making a new function shouldn’t take too much time. Currently, I’m not really using functions just because of how much time it takes to set them up (like you mentioned. An extension, extension name, new event name, description, the third thing).

1 Like

Nice analysis

I think it’s important to keep in mind that adding new concepts make the language harder to understand for beginners. Even just declining concepts into new scopes can be overwhelming.
Extensions don’t impose any new concept to extension users, only to extension writers (who already mastered the basics).

(Please take my message as a challenge to continue the discussion, I’m interested in knowing more about your point of view)

About 1, a code that can execute at any time and can have large side effect is not a good mix because it will do not reproducible bugs. The solution can be to use events queues to apply the side effects at the right time which is already how it can be done currently so I don’t see the need for a change.

About 2 - Scene, I sometimes wanted to pass parameters to external events. Though, I wonder if extension functions are already enough because object parameters are still needed to pass picked instances.
I guess this is not a priority because they are not sharable. It may be good to organize code but it doesn’t have much impact on the community.

About 2 - Global, I’m not a big fan of global objects. Actually, I’ve never used one. Maybe, it’s because I don’t make projects big enough. I doubt that some events need to run on every scene, but don’t feet in an extension.

About 2 - Objects, the example might not be the best one because it could be done with a behavior.
With events, behaviors can only use their owner object in life-cycle functions, but it’s not a limitation for built-in behaviors. Maybe the solution would be to give the same possibility to events-based behaviors somehow (give access to objects with a given behavior?).

1 Like

I think I mostly disagree with this statement. With this logic, we should move all elements that are non-essential to getting started, like the instance selector, objects groups, layer effects, etc away into a more advanced editor. Those concepts do not need to be thrown at the faces of beginners: the tabs I gave as example are hidden by default, and functions can also be. By default, you are on your scene events sheet, and can just use that, only getting functions if you open the panel to switch to a function. Thereby, you get the power when you need it, without being overwhelmed at the start.

When I say at any time, I did not mean “randomly between two other events”. I meant it just like you would with the javascript events loop: you know that the events will always be called sequentially in order of triggering in between ticks, not in the middle of your normal code, what i propose is like promises. JavaScript being single threaded, it is not even possible to do otherwise anyways. The only exception could maybe be game lifecycle events, for example we might decide to run the object deleted callbacks the moment the “Delete object” action is called, but that is also a very clear and known time of execution. The game remains deterministic. I don’t see any “events callback function” being a problem, not any more than the on object deleted and pre events callback functions extensions already have.

It’s fair to want to delay certain side effects to a certain time, I believe - but I also think that it’s not the responsibility of the different extensions to implement queues, but of the user. Any generic implementation we can currently do either has a big limitation or expects the user to know and implement stupidly complex extra setup.

Example:

P2P dataloss mode - Does not queue, only keeps latest event. Issues: events are missed if more than one is received in a single frame, which usually is the case for multiplayer.

P2P no-dataloss mode - Pops every frame. Issues: Can only handle one event trigger every frame + expects the user’s events to handle P2P events on every frame.

P2P advanced evens handling mode. Pops at the end of a while loop. Issues: Require the user to write a while loop and add an action to pop from the queue a the end of it (& built on top of P2P no-dataloss, so technically still requires users to handle P2P events every frame, although that is a fixable implementation detail and not a problem bound to the queue system).

Multitouch - Empties every frame. Issues: Requires the user to code a for loop to go through all the touches started between two frames + expects the user to handle touch events every frame.

THNK - Pops every time the condition is called (except the first time of every frame). Issues: Events can only be handled in a single place, as after handling all events the queue is empty, limiting usage in extensions, and expects the user to put the condition in a while event if they want all events to be handled within a single server tick (which 100% of the users want).

Basically, since we cannot handle multiple triggers within a single frame without the user having to implement a loop (and a lot of users don’t even know GDevelop have those or how to use these), nor can we know when all intended consumers of a queue have consumed it for certain.

So if I want all messages in a multiplayer server to be process all queued messages in the order they were received, with the server code not running every frame, the best API I can think of would have to expect users to come up with this insanity:

This goes against one of the core goals of GDevelop: it should be self documenting - by simply looking at the actions and conditions, you should be able to have some understanding of what they do and how to use them without having to look up any documentation. The expectation is also bad because, if the user does not use the events exactly like this, they will fall in all sorts of pitfals and “undefined behavior”. Here, a self-documenting, intuitive, fast to write and generally high UX experience would be to just be able to do this:

…which is what event handling functions would allow to do!
Users are not stupid, and will know that the function will not magically execute at the specific moment they need it to if they really need the effects to bee executed at a specific moment of the events loop. Since they know their requirements, and what & how their queues will be used, they can implement them fairly easily:


Or even simpler depending on the type of event:

Additionally, in some cases, you might want to offload some work out of the main events loop, for example if a game client wants to request the game version before fully connecting, queueing the request on the server, makes little sense: it wastes memory and time in the game loop, and it slows down respond times artificially, where responding directly when the message is received would not have any of those problem and not have any bad side effects.

So, to sum up, the way I see it, queues are a poor take at the problem, that artifically limits the user, provides a terrible UX, add a lot of work for the devs, break the single-responsibility principle, degrade performance, and all of that already excluding the compromises each queue in question have to make that create aa fragmented experience, with always some kind of limit being present.
I think that functions events handlers would have very sparse instances of breaking determinism of the game or of executing side effects at a moment where they should not, and that in those cases, users would be able to recognize it and delay the handling of the event by themselves, while removing all pitfalls and limitations, providing a better UX, being faster to create, and providing overall more control and performance.

Well, many many users use them extensively. Especially in level base games: people will have their players, base enemies, platforms, collectibles, etc as global objects and use different scenes to build different levels with the same objects. Sharing events is already possible via external events, but those are pretty inconvenient: they need a linked scene, they cannot be recursive, they cannot take parameters…

Extensions are inconvenient for many reasons: the creation and usage of extensions is relatively hidden away in the IDE, you need to know that extensions are made with events and creatable from the GDevelop IDE, …

In the eyes of a user that never opened an extension before, when they think “I need to make some functionality accessible globally”, never are they going to think of extensions - for any classic user, extensions are a way to install new features in GDevelop, not for their game mechanics. However, a menu called “Global functions” would be something that’d catch their eye, and they’ll find a cool & good way to share logic across scenes. Game specific logic does not make sense logically to put in extensions, even if they could certainly be used this way - in fact, internally, global functions would probably be implemented as an events based extension, as to avoid code duplication.

I think that organisation is already reason enough, but that is not all I see to it.
Functions are not just external events with parameters, they allow for so much more…
Sharability is the least of most user’s concerns, this is a game engine first and foremost, not a social network. Users want ease of access, which extensions do not provide by being far away (not inside the events sheet), in another tab (one opened, an extension has to stay in a separate tab from the scene event sheet), and in a place most people wouldn’t even think of approaching for their game logic (Extensions in project manager). Users want productivity, which making a lot of parameters, for accessing objects and behaviors that do not need any special picking, goes against.

And of course, assuming we do add “function events handler”, people would want to be able to handle them on a specific scene.

Extensions are unsuitable, not necessarily from a functionality standpoint, but from a usability, a UX standpoint, hence why I suggest we expand functions to multiple other scopes.

Yet again, this is missing my point: You can most likely do many things with object functions that you can do with an events based behavior, but this is about giving a more direct access to the tools.

My thinking was also that object functions would have access to their context (other scene objects, or if it is a global object, other global objects), which woud make them more powerful than custom behaviors (e.g. you could just use a “Collider” object group) for the collision in your object’s event sheet to add collisions to it.


Sorry for taking so long to write an answer, my time is sparse and this was a lot to type :sweat_smile:

To be clear, functions already exist in GDevelop as components of extensions, my proposal is about extending functions to a concept accessible across more parts of the editor and solve existing problems with some conditions.

Currently, you can make them in extensions, my proposal proposes to allow editing them inside the scene, inside objects, and inside the project manager.

Yep!

A function is usuable like any other event - it can be an action, a condition, or an expression, and used as such directly.

Yes! Theor differences with external events are:

  • Can be used for recursive algorithms (aka nested)
  • Can take input parameters
  • Can give an output (yes or no if it’s a conditon, a string if it’s a string expression, etc)
  • Does not require you to link a scene to it
  • When made in an extension, can easily be shared across projects. Although functions made in the context of my proposal may not be usable directly in other projects, I can’t imagine it being hard to make an option to extract your functions into an extension, turning elements of a scene you use directly into a parameter automatically.

Hi,
Once more I’m not in a position to have a useful opinion about this! But still, thanks a lot for your clear explanations. I understand it much better now. :slight_smile:

1 Like