Hi, I've been working on a Punch-Out like boxing game recently, and I've scripted a lot of things. However, I recently ran into a bug I can't seem to fix.

There are objects for the player, the opponent, and a referee. When the opponent gets knocked down, a certain routine happens where certain nodes execute code from each other scripts. First the opponent falls down, then the player's input gets disabled then the ref comes out, then he counts to a (temporarily) random number, then the opponent gets back up, then the ref says fight, then the player's input gets enabled.

The player's input is kept as a boolean, and the function that enables it is just changing it to the opposite. However, this is not happening. I spent a long time using breakpoints and experimenting to no avail trying to figure out what's exactly going on, but I do know that the function that enables the player's input is getting executed twice, and that the opponent getting up script is too. But I don't know why this is happening - especially why it's exactly twice.

Attached are my scripts, any help would be greatly appreciated.

this is going to sound gross, but perhaps the fastest way to figure it out (besides staring at the code for an hour hoping to get lucky) is just put a different print statement anywhere that function that is executed twice can be called.

You can also set a break point in the function (that gets called twice) and play the game and when it's tripped, look at the call stack and see who called it. Then play and when it trips a second time, see why it was called.

I was already using breakpoints and print statements before posting, but I decided to take a deeper look at the code, disabling everything and turning every step into a print statement. After doing that and re-enabling everything one thing at a time, it started working again even though I'm not sure I did anything. Very weird, but I'm not complaining. Thanks for the help!

Btw you have yields inside process() callback. Try to refactor things to eliminate them as they can cause really nasty, hard to trace bugs. Think about it; process() is called incessantly 60 times per second. If you yield in such a function, hundreds of yields may accumulate and all of them will continue executing at some point in the future returning to the local context from x seconds ago.

@xyz I'll take a look at that, thanks for warning me. I was thinking of putting them into separate functions anyways, but now I know I should do that.

Just take care that these functions are not called from process callbacks, because it'd boil down to same thing.

Best to rethink the system so that there is no waiting whatsoever during any process callback execution.

Yeah, basically never use yield. Unless you are writing complex asynchronous code (like in an online multiplayer game, maybe it would be needed, or if the game receives HTTP requests from a server for high scores, etc.). For local, single player, code there is no good reason to ever use yield and it just confuses the flow of logic and introduces hard to catch bugs.

@cybereality said: Yeah, basically never use yield. Unless you are writing complex asynchronous code (like in an online multiplayer game, maybe it would be needed, or if the game receives HTTP requests from a server for high scores, etc.). For local, single player, code there is no good reason to ever use yield and it just confuses the flow of logic and introduces hard to catch bugs.

I've been trying to rewrite to omit the use of yield, but things seem even more hairy than they were stability wise. Since my code is relatively state driven, I've been using yield to make sure an animation plays out before either reverting to a default state, or calling another function. I've been replacing yield with calling functions in the animation editor, but it's not really ideal. I had a whole knockdown sequence that involved multiple functions, but that's been becoming even more functions since I was changing since I basically have to use one animation per function, even for something that has one desired result.

What would be an alternative to yield and possibly what I'm doing now if I have to wait for a function, and call between objects?

Normally you can use Signals for a lot of stuff. Like when an animation or tween ends. Or you can call functions directly from the Animation track (you can provide optional arguments, so you only need one function to handle multiple animations). Or you could use Timers.

What about yielding for a signal? Should that be avoided?

I first learned about coroutines when I was learning Kotlin a couple of years ago, and was skeptical about them. But the documentation argues that coroutines produce code that's more understandable than event handlers.

I mean, it's a tool, that can be useful. I just find that it breaks the flow of code. With events it is very clear when they emit and are handled. Also, if you make a mistake with a signal, the worst case is some event doesn't happen. If you make a mistake with a yield it could freeze your game.

@cybereality said: I mean, it's a tool, that can be useful. I just find that it breaks the flow of code. With events it is very clear when they emit and are handled. Also, if you make a mistake with a signal, the worst case is some event doesn't happen. If you make a mistake with a yield it could freeze your game.

Alright, I was using signals a while ago (up until, say, Thursday). I started using function calls because it seemed neater, but I think I could use signals with the timer node. Thanks everyone for the help

@PowerUpT said: Alright, I was using signals a while ago (up until, say, Thursday). I started using function calls because it seemed neater, but I think I could use signals with the timer node. Thanks everyone for the help

Signals are function calls. When you make a signal connection you basically tell to the system: "call this function (signal handler) when this condition is met." That's all there is to it.

So in your case, instead of yielding for animations you should write signal handlers that run when animations are done. You typically want to minimize time spent in _process(). Making them wait for an unknown time is opposite of that.

Yields are ok in general, but calling them inside _process() will likely make your system fall apart at some point. Better to stay away from yields until you have full understanding of how they work.

What's also problematic in your code is that sibling nodes/scenes are changing each other's state. This may result in chaos down the line. Better to have a parent node that handles the gameplay logic and controls the state of chidlren.

a year later