This is a very common thing in games programming. You have an if case, that you need to get checked on each frame.
So this if case, the condition get's true for more than one frames - lets say it's true for 60 -80 frames. Whats inside it will get executed 60-80 times. If you are spawning an object - gets spawned 80 times. If you are playing a sound effect - 80 times.
What if you need it to execute only one time - on the frame the condition became true. Very often you do.
Please correct me if I am missing something, but you have to create a new variable - to switch it on and off, and sometimes on again - it's messy.
Now a lot of hardcore programmers out there might get annoyed I am proposing this, but not why have a common method to force the block of code in an if case to trigger only once while true?
It's so common that even python programmers have been looking for it:
http://stackoverflow.com/questions/4103773/efficient-way-of-having-a-function-only-execute-once-in-a-loop
more user friendly competitor game engines already have that - its called 'trigger once' condition. When you add 'trigger once' to a condition - whats in it executes only once:
construct2 has it (clickteam also):
https://www.scirra.com/manual/124/system-conditions
Trigger once while true
Turn an ordinary event (which is tested every tick) in to a trigger. For example, if an event plays a sound when lives equals 0, normally this event runs every tick. This plays about 60 sounds a second and would sound pretty bad. Adding Trigger once while true after the other conditions makes the event run just once when it first becomes true. This makes the previous example only play a sound once the first time your lives reaches 0. It must be the last condition in an event.
This could help gdscripters use less convoluted code to get what they want, create less utility variables and switches.
But what would the syntax be? I think maybe something like:
time= time+delta
if time>500 and trigger_once: get_node("sound").play("hit")
This is a very crude example. Yes I could just as easily put if time=500 instead. But sometimes you cant. sometimes you are forced to use > because it's likely that the frame gets skipped, maybe something else will force you to be very specific and not be able to limit the execution to only one frame in which it will be true. How many times have you had to make arbitrary new variables to force it to trigger once. We can get cleaner code if we had a way to force it like that
Please add to gdscript a trigger_once condition - to force the events of the met condition to execute only once - on the first frame the condition is met.
In my opinion, there might be a few other ways to make clear that a statement is to be triggered once, one could even argue we're adding an entirely new component to GDscript (let's call it the statement flag).
trigger_once(if time > 500):
if time > 500: (trigger_once)
[trigger_once(if time > 500)]:
The benefits of these syntax schemes might be that you can theoretically do them on _any_ statement in the code (though it'd be recommended to flag the if statement itself if you need a lot of logic done just once). As an example...
trigger_once(time+=500)
@Ace-Dragon
I like this syntax too:
trigger_once(if time> 500)
good thinking!
I suggested it to be an option that you append to an if case, because that is how other software with the feature does it. But it could just as well be a new type of a condition block that you put events in in order to force their execution only one time when it becomes true. A trigger!
for example we could nest the events under a trigger, which could be nested under an if case or other type condition check:
if time>500:
trigger_once:
stuffHappens()
otherStuffHappens()
or
if time>500:
trigger_once: stuffHappens()
It is good to note that the trigger once will trigger the event once when a condition is met. But it will also trigger once again if the condition becomes false and then true again. Trigger once forces it to execute one time when met. It is not limited to trigger it only one time in the life cycle of the application. Instead it limits it to execute one time in the sequence of frames in which the condition is true.
We could call it 'the trigger operator' ?
In any case - whoever implements it should decide what the best syntax would be.
While this is cool and I'm not dismissing the possibility of such functionality, I can't see any implementation of this that does not includes a variable to keep track of execution. So at best (unless I'm missing something) this is a syntactic sugar for an underlying boolean flag. It is the way you don't want to do that looks clunky just under the hood, which might be good per se.
You also have to consider in the syntax something reset the trigger upon some condition. If you want to play a sound when hit, you likely want to play the sound once but also in every hit. So when the sound is finished you should reset the trigger so it can be played again. That requires an identifier for the trigger and a way to reset it (such as reset_trigger("my_trigger")
).
You also have to consider in the syntax something reset the trigger upon some condition. If you want to play a sound when hit, you likely want to play the sound once but also in every hit. So when the sound is finished you should reset the trigger so it can be played again. That requires an identifier for the trigger and a way to reset it (such as
reset_trigger("my_trigger")
).
So start the trigger in some place, reset it in another place. Wait, are we talking about checking against a boolean variable? :D
The whole point of it is not to have to start and stop it via booleans - that weight is taken away by this feature.
A trigger means that whatever is inside it triggers one time and switches itself off. But It keeps track of the if case thats you gave to the trigger - whether if is true or false and decides when to trigger once again.
If it keeps track then no - you dont have to make the programmer reset their trigger. The trigger will know when to reset itself because it keeps track when whats parsed to it has become false again.
Ok, got it. It's sort of a special case for while
: It runs the function once when the condition is true
and reset the flag when the condition becomes false
. Rinse and repeat.
In this case, none of this can work unless you make the parser backtrack:
if time>500:
trigger_once: # Won't work
stuffHappens()
otherStuffHappens()
and
if time>500:
trigger_once: stuffHappens() # Won't work
The problem with those is that trigger_once
will be completely bypassed on false
, since the if
block won't be executed, and then it won't know when to reset.
We could add a once
control structure:
once time > 500:
do_stuff()
do_other_stuff_too()
I also have some problem with the semantics of the word "once". It seems to me that it will happen everytime once the condition is true. I guess "trigger" might make more sense for a oneshot thing.
And, of course, never seen this in any programming language that I know. It doesn't mean we can't do it, but the impact of this (in memory, speed, etc.) should be carefully thought.
I made a little schematic of how a trigger operator's life cycle could work:
@vnen I like your syntax best so far:
once time > 500:
do_stuff()
do_other_stuff_too()
Seems to make most sense and requires much less typing.
This would be super useful to have!
So it triggers the events once when the condition is met, waits for the condition to become false and then true again - and if it does - then repeats. Exactly!
Btw my schematic might not be the bestest implementation idea. The important part is that everyone gets the gist of what we can do with it and why it is very useful.
I think the only possible initial confusion from the word might be the suggestion that this will trigger only one time in the life cycle of the application, rather than only one time when a condition is met sequentially for more than one frame.
The truth is that I would never have any use of it - if it triggers only one time in the life cycle of the application and thus will not even assume the function would work that way.
I think the word 'once' is clear as to what it does. But if we delve more into it, i guess we could also look at other possible word to use.
'on', 'at' or 'upon'
To Vnen.
I'm not really sure if we want to go the route of using it in a way where it essentially becomes an alternative version of the if statement (because otherwise it can only be used with 'if' type scenarios and ignores the other statement types in GDscript).
With my suggestions, you can also do something like...
while time > 500: (trigger_once)
and
for hours in timeList: (trigger_once)
and
if time > 500:
#do stuff
else: (trigger_once)
#do this other stuff once
and even
for hours in timeList:
if hours == 3:
break: (trigger_once) #only break at this value on the first iteration
It could even work for more statement types that are yet to be added (should they be added in the future as mentioned in the documentation)
switch(hours): (trigger_once)
@Ace-Dragon not sure if I would ever use it in some of the cases.
The for case for example I cant come up with why I would need a trigger once. We can already trigger once in a for case, even when it continues iterating. Right?
The for case is not like the if case - it runs once, iterates through a list and gets the job done. Doesnt cycle every frame when I use it - that would be expensive. Perhaps I havent used it in other ways. I dont know.
The goal of the trigger is to address the need to limit the execution of events to one time - while their condition is true for more than one frame. That usually happens in simple if cases - something that is continuously checked every frame.
About the syntax, I think that in terms of actually getting this implemented, a trigger once would make more sense in @vnen 's approach. And if you want to use it the way you do, we could still nest it like this:
if time > 500:
#do stuff
else:
once: #do this other stuff once
I am assuming that if the syntax worked the way you want it, more internal parts of gdscript in godot will have to be touched to get it to work.
Actually I am not sure if nesting it would work - now that I think of it. It needs to check whether something is true or false constantly - like an if case. Otherwise How would the trigger know how to reset itself?
It probably wouldn't work like a for case. so in a lot of ways you cant apply it everywhere - or at least I am not sure why or how.
But does it have to? I think it should do one thing and do it well - act like an if case that limits to one execution the events you have given it once true - even if it's true for longer than one frame.
An open source example of triger once condition block can be found in gdevelop:
https://github.com/4ian/GD/search?utf8=%E2%9C%93&q=trigger+once
it is perhaps interesting to see how it is implemented there.
Gdevelop is kind of like a clone of construct2. It has very similar approach to coding.
As I said, this is unpreceded in programming languages that I know, so I'm trying to understand what's being proposed here. Let me comment on the usage suggestions by @Ace-Dragon:
while time > 500: (trigger_once)
How's that supposed to work? Only run the loop once? If so it's not a loop. If it's a loop, it won't run only once. Same thing applies to a for
loop.
if time > 500:
#do stuff
else: (trigger_once)
#do this other stuff once
This is possible with my suggestion, but requires a bit of repetition:
if time > 500:
#do stuff
once not (time > 500):
#do this other stuff once
And how's this supposed to work too:
for hours in timeList:
if hours == 3:
break: (trigger_once) #only break at this value on the first iteration
Do you realize that break
stops the loop? If it stops always in the first iteration, it'll have only one iteration. If it's supposed to stop only if condition is true
in the first iteration, it means that trigger_once
has also to keep track of an iteration counter. Actually, every for
will have to keep track of this and use if there's some trigger_once
inside eventually. For this there are better alternative syntax, such as:
for hours in timeList:
if loop.counter == 1 and hours == 3:
break
This loop counter is much more useful than the OP, but also implies a higher memory allocation and usage (creating an object for every loop).
While I can see a great value in thought processes from non-programmers, you all have to understand that in the end this is all an algorithm. So if we were to add such suggestions, we do have to think of a program that solves the problem in a general fashion and uses the structures provided by the underlying platform. We need to build a basis for the higher-level language using the low-level barebones, which is not always easy, viable or even possible
Also, I want to see real use cases to the proposals, not "someone might sometime need something like this perhaps" (see this section of our FAQ).
@blurymind I can see that the code you linked uses an identifier, so it knows what trigger to check or reset. It indeed does need an identifier, as I said in my first post, even if it's internal. But I personally can't see a way for this to actually work without an explicit identifier, which makes my syntax proposal bogus. Maybe a thing like trigger my_identifier once time > 500:
is more viable, but again this needs to be properly thought. And I'm pretty sure I can solve this with a GDscript singleton although in a clunkier fashion (I will try this later and show my results).
It is unprecedented, but its commonly used and needed in terms of controlling flow in a game. All the stack overflow threads are a proof. Some of the tools in unity for state machines also have it built in.
Its important to understand where it has its use, so as not to expect putting it in flow logic where it would make no sense. @vnen i appreciate that you are looking into this as i strongly believe it will make life easier and gscript code cleaner
Another example is Unreal's blueprint system, which has a Do Once node:
https://docs.unrealengine.com/latest/INT/Engine/Blueprints/UserGuide/FlowControl/#doonce
The DoOnce node - as the name suggests - will fire off an execution pulse just once. From that point forward, it will cease all outgoing execution until a pulse is sent into its Reset input. This node is equivalent to a DoN node where N = 1.
For example, you could have the network for an opening door run through a DoOnce, and that door would open only one time. However, you could tie a trigger event into the Reset, which will cause the door to be accessible once the trigger has been activated.
I think you could look at it's source code if you wish - i believe it's written in C++
It seems to work almost exactly as we think ours should.
If godot is really planning to do a visual scripting in the future, having this operator in gdscript in advance would be useful to have under the hood. Even without a visual scripting system, it is damn useful in terms of controlling flow with less code and creation of new boolean variables all over the place.
A key point of this feature is that the programmer should not have to create a new boolean variable and keep track of it. Instead the trigger would do that for us under the hood.
I suggest a new keyword. Trigger, would that be. Trigger is basically a bool. You set it true or false. But once it's used and it's true, it automatically become false. So,
trigger b = true
if (b == true): #automatically become false
print (b) #will print "false"
Now, due to the dynamic characteristic of GDScript, I don't know how is it going to be recognized:
var b = true #this is a trigger
var c = true #this is a bool
Maybe two new keywords that has the same value of true and false. Truth and lie. It's a pun due to the double meaning of lie. They would be interchangeable. Could be active and idle/inactive if you're more serious.
if (truth == true): #it's true
if (lie == false): #it's also true
Although that doesn't solve the problem that you had of having multiple variables, but I think it's the most optimal and flexible alternative. We could have both alternatives, too, if it's necessary.
@mateusak I am trying to wrap my head around your approach.
So a trigger could be a type of a new variable that is similar to the boolean - one that automatically resets itself?
Please explain more what makes it different than using a boolean?
In which case - on the question of how we create it, I believe that it would be wrong to create it with the same syntax as the boolean.
var c = true #this is a bool
instead maybe:
var c = Trigger()
So you are saying that with this type of variable, it starts as false always, so we dont need to give it a true or false value.
I am trying to not get too confused so far.
I think the problem is that we need to send a 'reset' signal to the trigger (unreal's approach)- or let it do that on its own (construct2,some unity state machines, gdevelop approach) . We need to feed it with conditions and also give it the actions to do one time when the conditions are true. How would that work in your syntax?
If a trigger is a variable type, do you use a method to do those things?
So far @vnen has suggested the cleanest and least confusing way to do a trigger in gdscript imo.
If we do the unreal approach and allow the programmer to manually reset triggers - then I think it shouldnt be mandatory as it will make them harder to use in a non-visual programming environment such as gdscript. In the end it's good to experiment with an implementation of a trigger - try it out in the other engines and think about it.
@blurymind Oh no, no, no. What i'm suggesting isn't something so... fancy. It's simply a variable that once is check in an if automatically becomes false. You can set it true or false manually, of course. Like this:
var k = true #declaring a trigger
if (k == true): #the var k automatically become false, once checked
print (k) #will print false, since k became false after being checked
Your scenario would be like this:
var k = true
if (time > 400 and k):
do stuff
Next frame won't activate it, since 'k' automatically became false after being checked. The verification of a trigger for your scenario would always be on the end, because if it's in the beginning of the if it would become false before the other condition was met.
@mateusak so a trigger in your case is a boolean pretty much, that toggles itself off or on when checked.How do you reset it?
The reason that I thought it should be like a new type of if case is because the reset depends on the condition that is fed to the trigger to become false again.
Without a reset, your trigger is the same as using a boolean like this:
var k = true
if (time > 400 and k):
do stuff
k=false
Which could leave us with situations where you can paint yourself in a corner again.
The other problem with that is of course clearly differenting the new variable type from the boolean
@blurymind You simply set it true again. If you're looking for something to compare, look at Unity's mecanim. It has a trigger variable which does almost the same as what i'm suggesting.
@mateusak isnt that specifically designed for animations? I dont think that unity developers use it to control flow the way we want to here.
I am trying to find more documentation on them:
http://answers.unity3d.com/questions/600268/mecanim-animation-parameter-types-boolean-vs-trigg.html
@blurymind Yes it is. Since they don't have a custom programming language I don't think it will ever be used like this. The way my alternative differentiate from a bool is in more complex situations.
var k = active #my suggestion, as you can see a few posts up, to replace true for triggers
if (time > 400 or k):
do stuff
You may don't see any difference at first, but what happened is this: if time is bigger than 400 then k won't become false, because it was not checked. But if it time isn't bigger than 400 and k is true then k will become false. You can do the same with booleans like this:
var k = true
if (time > 400):
do stuff
elif (k == true):
k = false
copy n paste the above 'do stuff' here
or
var k = true
if (time > 400 or k):
if (time <= 400):
k = false
do stuff
Now, this is a very basic thing you can simplify using triggers. But imagine there are 20 lines of code inside the if (time > 400). You would have to put it again on the elif just to mark k as false. As I said, I think this approach is the most flexible.
@mateusak in your example - you change the boolean, but you dont really use it in the if case where it should force the events to happen once
@blurymind Exactly. My alternative won't be used just to be an 'once' thing. It has multiple uses.
Wait i am starting to understand what you mean,
so in your idea the boolean could have a third state 'trigger'
then it should be
var mytrigger = trigger # this is a boolean, but instead of true or false, it is set to trigger
@blurymind Well, not exactly what I had in mind, but I think that's actually better.
So, the difference between a trigger and a regular bool is; a bool can only be turned false once the if has be successfully called.
var k = true
if (k):
k = false
But in a trigger state, it will be turned to false in the exact moment it was checked
var k = trigger
if (k):
This proportionate scenarios like the one I described above.
@mateusak in your approach we at least have the user create an identifier for the trigger.
If we give the boolean the ability to act like a trigger, then I think resetting it should not be done by setting it to true or false. That would make the boolean act like a normal boolean again.
Instead perhaps to reset it, we set it the same way we created it
var k = trigger #creates a boolean and sets it to be a trigger
k=trigger #resets the trigger
Or perhaps it should reset itself, whenever the conditions that it is pared with become false again. At least that is the expected behavior in gdevelop and construct2
I am still not completely sure the boolean is the best approach, but now we have more fresh ideas
. Lets see what @vnen thinks too :)
What do you guys think on the resetting part? Should it do that on its own, or should the user reset it manually somehow? What would the pros and cons be?
Here's a summary;
I suggest that a boolean can have three states, true, false and trigger. The trigger state behaves exactly the same as the true state. We can say that
if (true == trigger): #it's true
The difference between the true and trigger states are that the trigger state becomes false when checked.
var a = trigger
if (a): #var 'a' now is false
It become false instantly after being checked. This means that
var a = trigger
if (a && a): #it's false, because when 'a' got checked it became false
We could write the above example as
if (true && false):
This allow some complex functionality. A example of this is a monster that can only attack when he sees the player, but can attack only once when not seeing the player.
var a = trigger
if (seePlayer || a):
attack
This could be rewritten in boolean like this:
var a = true
if (seePlayer || a):
if (!seePlayer):
a = false
attack
As I thought, it is very simple to do this with a singleton. Here it is:
extends Node
var triggers = {}
func make_trigger(id, object, method):
if not triggers.has(id):
triggers[id] = {
"valid": true,
"object": object,
"method": method
}
func trigger_once(id, condition):
if triggers.has(id):
if condition:
if triggers[id].valid:
triggers[id].object.call(triggers[id].method)
triggers[id].valid = false
else:
triggers[id].valid = true
Add that as singleton in your project settings. To use it you have to create a trigger first (with make_trigger
) with an object and a method to run and then use it via the identifier you chose. Example (assuming you added as a singleton named trigger
):
extends Node
func _ready():
set_process(true)
trigger.make_trigger("my_trigger", self, "do_stuff")
pass
func _process(delta):
trigger.trigger_once("my_trigger", Input.is_key_pressed(KEY_SPACE))
func do_stuff():
print("Doing stuff...")
This prints "Doing stuff..." when you press the space bar but only once even if you hold it. It resets when you release the space bar.
It's possible to tweak this so you select the method to run on trigger_once
. You would still need an identifier (unless you can infer it from the object-method pair), but then you could remove the need to make a trigger first.
@vnen I am going to give this a try! Very nice :D Godot is so flexible!
On having trigger once as part of godot - is there interest in making it a part of gdscript?
I looked more into this - its a common problem. At some places they call it 'how to avoid a recursive trigger'. So a trigger once is a tool specifically designed to help avoid recursive triggers.
http://amitsalesforce.blogspot.co.uk/2015/03/how-to-stop-recursive-trigger-in.html
On having trigger once as part of godot - is there interest in making it a part of gdscript?
I'm sorry, but I don't think so. I'm not willing to mess with the GDScript parser, at least not without @reduz approval. Though if someone wants to tackle this and make a PR, I'm not against it.
ah fair enough. Thank you for the singleton code. :)
Well... okay. One day people will look back and see that my idea was brilliant. Like Einstein.
@mateusak our idea - I helped shape it :p
Until someone actually puts it in godot and we try it out in practice, nobody will know if its good or bad. Its up to @reduz to decide if gdscript will benefit from a trigger and how a trigger should work. Perhaps someone experienced in c++ would see this thread and laugh a little at some of our crazy syntax, but maybe they will come up with a better way to do it
Btw here the creator of construct2 explains how his trigger events work:
https://www.scirra.com/forum/trigger-once-while-true_t119456
It basically means "Is true this time and was false last time", or in other words, "is the first time this is true".
Well I don't see how you'd even accomplish it with a class or method? You basically just want a boolean for "was true last time". Also in many programming languages you'd actually explicitly fire an event when something happens and allow callers to register for that event, instead of testing a condition regularly and triggering something when the state changes. It's a case of where typical C2 style is different to traditional programming.
In C2's case, it stores a separate boolean per condition, since each condition needs to track its state independently. Actually the specific C2 implementation is based on tick counts, but if you're just learning to program, that's not really important.
@vnen just tested your trigger on my mini rhytm game experiment. It works like a charm. It's a real shame it's not going to be built into gdscript! This is very very nice.
I guess the triggers in other engines will always have the advantage of not having to be created by the programmer at the start for each instance they would be needed. But thats not a big deal, because now at least we get this with fewer lines of code and it's clearer when read. The trigger once logic is easy to distinguish from other code. Cool stuff!
I will use it literally on everything I do in gdscript from now on :+1:
If I knew c++ and was as experienced I would make a pull request for a built in trigger_once method in gdscript that does exactly what vnen's does - but instead of creating it the ready(): process, I would like to not even have to create it manually - the engine could create it when its called for the first time and keep track of it under the hood. Thats how the others do it and its very convenient.
@mateusak ~ Here is some code that closely follows your idea:
#once.gd
var did_run = false
func run_once():
var allow = !did_run
did_run = true
return allow
func reset():
did_run = false
func set_state(state):
did_run = !!state
func get_state():
return did_run
Usage:
const Once = preload("res://once.gd")
var jump = Once.new()
#...
func _process():
if Input.is_action_pressed("jump"):
if jump.run_once(): # Inside, so that the else part works correctly
print("JUMP!")
else:
jump.reset()
jump_available_sprite.set_opacity(!jump.get_state())
Also, if you want to make it into a singleton, here is a quick idea:
extends Node
func new():
return preload("once.gd").new()
@bojidar-bg I dont know why but in my test your approach did not trigger a print.
When I disabled the reset - it triggered one print and then stopped triggering for all the steps after.
In my example the code is supposed to trigger a print once at specific points in time - the time is taken from a json file.
When replaced at the same place with @vnen 's approach - it started working again.
I like your syntax a bit more though. get_state could be useful too!
@blurymind ~ My bad, the else was wrong, I'll edit my comment :smile:
_EDIT_: I missed a colon in the code.... oops.
With this change:
if Input.is_action_pressed("jump")
if jump.run_once(): # Inside, so that the else part works correctly
print("JUMP!")
It still doesnt print anything for some odd reason.
@mateusak have you managed to successfully try and use the example code provided by @bojidar-bg ? What do you think
you know if this
const Once = preload("res://once.gd")
var jump = Once.new()
if Input.is_action_pressed("jump"):
if jump.run_once():
doThing()
else:
jump.reset()
could internally create a new boolean automatically, keep track of it and reset it automatically when the else happens, it will really boil down to a single line of simple code (instead of 5 scattered around) that does what we want:
if Input.is_action_pressed("jump") and run_once:
doThing()
This is the ideal case scenario imo. It's how the others do it. Is that so impossible to achieve by extending gdscript?
Not that it worked or anything. :)
@bojidar-bg can you share a gdscript code example where your approach demonstrates itself with a print? I will try it again on a simpler project - one that tests input like yours
Just for the record: when testing Input, it's best to use the _input(ev)
callback.
Something like
if ev.is_action_pressed("jump") and !ev.is_echo:
doThing()
Will trigger doThing()
only once, as all the following pressed events will be echos.
@vnen in your approach,
trigger.make_trigger("my_trigger", self, "do_stuff")
There are two downsides to it, that are quite serious limitations:
How do you add parameters to it?
@Hinsbart can you provide a more complete example of the is_echo? Is it possible to use it as a trigger for non input events?
The prime need for a trigger is within fixed_process(delta) and process(delta) events.
In this case I want something like is_echo to become available to non-input functions too!
@vnen does that seem like a less extreme proposal to add to gdscript? What does @reduz think?
@blurymind, you can think of ev.is_echo()
as an indicator if the event is happening the for first time. Example:
Frame1 - User pressed key, send event with pressed = true, echo = false
Frame2 - User is still pressing, send event with pressed = true, echo = true
Frame3 - User released key, send event pressed = false, echo = false
And yes, it's only available for objects of type InputEvent
. But it doesn't make sense to make this available anywhere else. How would that even work? :P
@Hinsbart we are trying to figure that out.
You tell me. Me and some other users supplied free and proprietary game engine examples of the feature functioning - without need to write many lines of code at different locations of your script, their source code, made a diagram... What else do you need :p
Now we have a couple of prototypes - one of which works - but is limited.
@vnen suggested the one that works - but does not support function parameters.
@bojidar-bg suggested another - but that does not work at all. It doesnt trigger the event at all.
Today I tried it on a clean scene with a single node2d node and a 'jump' event added to the project's inputmap. in my case the name of the trigger script is once.gd instead of 0nce.gd
the code is as follows:
extends Node2D
const once = preload("res://once.gd")
var jump = once.new()
func _ready():
set_process(true)
func _process(delta):
if Input.is_action_pressed("jump"):
if jump.run_once(): # Inside, so that the else part works correctly
print("JUMP!")
else: jump.reset()
#jump_available_sprite.set_opacity(!jump.get_state())
nothing happens.
Well, that seems like a communications issue.
I was just talking about ev.is_echo
, which really doesn't any make sense outside of _input(ev)
:P
@Hinsbart xD point taken. Thank you for explaining how it works.
@blurymind ~ Lol, seems like I messed up my reset func. I'll fix it, but mind that I was giving example code, not a working solution :smile: .
@bojidar-bg after the changes you made - it works now!!! You approach has the big advantage of being able to be paired with an if case, and thus its also possible to run a function with parameters once (or do anything else).
I will therefore use it instead of @vnen 's
It is awesome! :+1:
Why cant we have something like this built in
I'd do it a little bit differently, as @bojidar-bg's code is not far from a boolean flag.
#once.gd
var did_run = false
func run_once(condition):
if condition:
var allow = !did_run
did_run = true
return allow
else:
self.reset()
return false
func reset():
did_run = false
func set_state(state):
did_run = !!state
func get_state():
return did_run
So you can use it like this, without a need to manual reset:
if jump.run_once(Input.is_action_pressed("jump")):
print("JUMP!")
# No else block needed
@vnen This is better as a quick trigger, as we also dont have to nest two if cases in order to get it to trigger once and reset properly- yours looks cleaner and tidier. is it possible to also do it in a way where we dont even need to create a new variable for each instance we need the quick trigger to do it's thing?
having a reset in @bojidar-bg 's approach allows for the user to reset the trigger once with a logic that is detached from the trigger once condition. That is similar to how Unreal's doOnce blueprint node allows a reset input to come from anywhere, and it can be useful in some cases too:
Reset - This execution input will reset the DoOnce node so that it can be triggered again.
For example, you could have the network for an opening door run through a DoOnce, and that door would open only one time. However, you could tie a trigger event into the Reset, which will cause the door to be accessible once the trigger has been activated.
I guess in an ideal world we would be able to create a trigger in two ways and use it
if run_once(Input.is_action_pressed("jump")):
print("JUMP!")
Godot does the rest under the hood. This could be used for a quick automatic trigger, which does not give the programmer control, but also doesnt ask for more code to work.
think of it as a temporary local variable that is needed only for the event where its used - created automatically by the runtime - under the hood.
pros: no need to create a new trigger variable every single time you need to use a trigger
pros: needs less lines of code to work and does work as expected for most needed cases
cons: if you need a more specific user case where you need to be able to manually reset that trigger to work again, you cant address it directly, nor tell it when to reset.
var jump = run_once.new()
#...
func _process():
if Input.is_action_pressed("jump"):
if jump.run_once(): # Inside, so that the else part works correctly
print("JUMP!")
else:
jump.reset()
jump_available_sprite.set_opacity(!jump.get_state())
think of it as a global variable that other functions and events need access and control to.
pros: You have a bit more control over your trigger - you can reset it with some logic that doesnt have to do with the trigger's condition to trigger once.
cons: You need to write and keep track of more code. Create a variable for each custom trigger once event and manually reset it. If you dont reset it - the event will automatically trigger only one time in the game and will not trigger once the second time it's conditions have become true after being false - until you reset it.
If I have to be honest - I will most probably use the Quick trigger most of the time.
@blurymind ~ The problems with having this in the very core are two:
var did_jump = false
func _process():
if Input.is_action_pressed("jump"):
if not did_jump:
did_jump = true # The only additional line needed.
print("JUMP!")
else:
did_jump = false
Finally, when the asset store / addon library comes in 2.1/2.2, I guess such small-and-nifty libraries would be made every day for whoever wants to use them.
@bojidar-bg I see. Thank you for providing the awesome utility function.
Also much thanks to @vnen who came up with an implementation first.
It just saves a lot of typing and extra code. :) you know, it all adds up when you repeat this many times.
You may not see much value or universal use of it, but I do and I appreciate that you made that script.
I dont think it's a specific bloat that only few can make use of. It is not a request to implement a bitcoin class or something like that.
There is a good reason unreal engine and unity have a trigger once function. it's a fundamental tool for easier control of flow - to avoid recursive triggers. Recursive triggers are a common problem to game development that is obvious to see in search results.
None of the implementations so far are as convenient as whats in other engines - as they still require you to create a variable for each time a trigger is used. But they are nice and they do save time and save repeating same blocks of code. it also helps make code more readable.
I disagree that a feature like that makes code more readable. Imho it makes the code literally unreadable in that it vanishes behind a keyword. You can't read what the code does anymore, you have to know it.
I would really appreciate it if we tried to keep out features that aren't really necessary and are just making something a little more convenient, otherwise we end up in a mess like the one C# is in. Just take this example:
Car b = a;
b.size = 3;
If this is C++-code then I can tell that the first line allocates memory on the stack and fills it with a copy of the data stored in a. The second line changes one value in that block of memory.
If this is C# code I can't tell you whether b is a copy of a or a reference to a. If Car is a struct it's a copy, if it's a class it's a reference. I even can't tell you whether the second line is an assignment or a function call. "size" might be a property and the get-method of that property could actually be quite expensive, it might update the whole Car-object, it might store the new value in a database...
You can use getters and setters in C++, you can create references and copies, the only thing you are "missing" are C#'s features that allow you to save a few characters while sacrificing a lot of information.
Of course if you intend to you can mess up C++ code just as much by overwriting operator= in a weird way, but while that is considered bad style using properties in C# is completely normal.
I have no issue with adding a helper function or object like the ones suggested above, because that way you can still look up the code and read up what happens (be it in GDScript or C++), but please don't use language features in a case like this.
And yes, there is a reason why other engines have functions like that (btw. I couldn't find anything about Unity's trigger once function, does it really have one?), but there's also a reason why Unity or Unreal take Gigabytes of space and a lot longer to install than Godot, why their code base is not as readable and clean etc.. Different engines follow different philosophies, that's the main reason why it makes sense to have more than one engine, so imho asking for a feature because another engine has it doesn't really make sense.
EDIT: I hope I don't come across as too negative and offensive, but as a teacher I regularly see people using unnecessary language features just because they are there without understanding what they actually do. That's why I have a very strong opinion on this matter and why I may come across as more aggressive than I intend to.
Do you have access to the function call stack in GDScript? Or some other identifier of the context? That would allow you to use a singleton that distinguishes each call to run_once(). Removing the need to declare your triggers.
Accessing the stack trace is do-able from c++ on windows , linux, and macOS.
This is a rather dramatic solution to an incredibly simple problem, but it seems more broadly useful/powerful.
EDIT: Here is a stack trace function available only while debugging. I'm not sure why it's only available while debugging. Perhaps because of the storage of names in the stack? The stack itself is information that the program has (needs to have) even when not debugging.
Since GDScript is just for scripting and completely integrated with the engine, likely there's no need for the runtime to keep a call stack _of the script_. It can just call the functions and have the C++ stack as it is, which likely would make little sense for scripting. In runtime this would be unneeded overhead.
Also, for a topic surrounding beginners, analyzing the call stack seems much harder (both to write and to read) than simply using a boolean flag.
It would be useful to come up with a general way of triggering once without keeping track of variables for each trigger
There must be a better way to do this
@blurymind To be fair, I can't remember enough reasons to use "once" triggers, and most cases it can be covered by a slightly more elegant and featureful solution..
once
used to make sure the player jumps only when he can -- doesn't allow for easy addition of double-jumps, reseting the trigger is pain. An is_on_ground
boolean is much simpler, and effective. (yeah, that's not a common case for once
, just mentioning it)once
used to make sure a submit button doesn't get double-clicked -- failure to reset the trigger would result in an unclickable button in case an error occurs, and you certainly don't want this. A more effective solution might be just ignoring clicks when the request is in progress, and clearing the form right after the request is finished (again a bad example, ain't it.. lemme think of a better one)once
used to execute something only the first iteration of a loop -- probably unnecessary, put outside loop (maybe with an if, to check if it can get inside the loop); in some rarer cases where it might be useful, a boolean can be used just as fine too.once
used to ensure an InputEvent
condition happens only once -- there is already event.is_echo
once
used to ensure an Input.is_action_pressed
condition happens only one time -- there is the _new_ is_action_just_pressed
(and `is_action_just_released)once
used to make sure an object is initialized just the first time something runs -- you can probably use null
to signify the object is uninitialized. Even if not done with null, a simple initialized
flag is good to go, and much easier for other parts of the code to check than a trigger.¯\_(ツ)_/¯
:smiley:I continue to assert that every code that requires once
can be rewritten elegantly using something else, usually if
+ a boolean, but sometimes if
+ a number, and rarely if
+ a null. Also, once
has a few problems:
once
trigger, unless we have named onces (and if those are stored in variables, then we just get booleans with syntax sugar, not really what's wanted)once("thing") Input.is_action_pressed("action")
is easier to overlook when looking for the source of the issue than if !was_thing_pressed and Input.is_action_pressed("action")
.To conclude, I think that once
triggers have _no_ place in GDScript-2.2, though they might be cool in VisualScript (and I have to check GDScript-3.0 to decide if it is worth the trouble there).
(Ehh, my 5¢ comments always get too long :( )
The easiest way to solve this would be providing some method to get current stack trace. We then could use caller data as "trigger name", so triggers are named automatically with each call of trigger_once()
. Then the triggers could be automatically cleared if the method isn't called again in the current frame for specific trigger.
I don't think trigger_once()
should be in core functionality, but with stack trace info, making a plugin for it would be pretty straightforward.
Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.
The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.
If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!
Most helpful comment
@blurymind To be fair, I can't remember enough reasons to use "once" triggers, and most cases it can be covered by a slightly more elegant and featureful solution..
once
used to make sure the player jumps only when he can -- doesn't allow for easy addition of double-jumps, reseting the trigger is pain. Anis_on_ground
boolean is much simpler, and effective. (yeah, that's not a common case foronce
, just mentioning it)once
used to make sure a submit button doesn't get double-clicked -- failure to reset the trigger would result in an unclickable button in case an error occurs, and you certainly don't want this. A more effective solution might be just ignoring clicks when the request is in progress, and clearing the form right after the request is finished (again a bad example, ain't it.. lemme think of a better one)once
used to execute something only the first iteration of a loop -- probably unnecessary, put outside loop (maybe with an if, to check if it can get inside the loop); in some rarer cases where it might be useful, a boolean can be used just as fine too.once
used to ensure anInputEvent
condition happens only once -- there is alreadyevent.is_echo
once
used to ensure anInput.is_action_pressed
condition happens only one time -- there is the _new_is_action_just_pressed
(and `is_action_just_released)once
used to make sure an object is initialized just the first time something runs -- you can probably usenull
to signify the object is uninitialized. Even if not done with null, a simpleinitialized
flag is good to go, and much easier for other parts of the code to check than a trigger.¯\_(ツ)_/¯
:smiley:I continue to assert that every code that requires
once
can be rewritten elegantly using something else, usuallyif
+ a boolean, but sometimesif
+ a number, and rarelyif
+ a null. Also,once
has a few problems:once
trigger, unless we have named onces (and if those are stored in variables, then we just get booleans with syntax sugar, not really what's wanted)once("thing") Input.is_action_pressed("action")
is easier to overlook when looking for the source of the issue thanif !was_thing_pressed and Input.is_action_pressed("action")
.To conclude, I think that
once
triggers have _no_ place in GDScript-2.2, though they might be cool in VisualScript (and I have to check GDScript-3.0 to decide if it is worth the trouble there).(Ehh, my 5¢ comments always get too long :( )