I'm not doing much tonight. I figured I'd refactor the weapon code to use a state machine to reduce bugs with state switching. It’s a little extra work but it’s absolutely worth the stability you get when switching states. And since this is a simple state machine I’m just doing it directly in code. At some point I’ll need one complex enough to warrant making a state machine design tool, but today is not that day.
It may not be necessary to do this. I’m fairly certain that it is currently working but free. But I’ll be even more certain once states are implemented.
This is what the code looked like beforehand. At first, when I fired after waiting a few seconds, all projectiles “saved up” were fired in rapid succession. This is because I didn’t reset _timeTillCycle. When I fixed that I found out I could rapid fire slow weapons by clicking really fast, so I had to reset _timeTillCycle. But I had to do it in the right way and at the right time so the cycle time didn’t persist between clicks: You had to hold the mouse button for the entire duration of _cycelTime before the gun would fire again. Time spent with the mouse button up would not count towards resetting the gun.
This is the new state code. It’s strewn across several more functions now, but the value comes in the simplicity of the logic. Putting all the logic to manage state into one function makes it more difficult to follow and execute bug free. The old code has to check the timer and the trigger before spawning a projectile. It then has to properly manage the timer while being sure to not fire any more. Also worth noting is the input is handled in the space ship class now rather than the weapon module. I’ll end up moving it from there too, but not right away.
Separating the code into their logical states means some checks are implicit and need not be executed. When the weapon is in the Idle State, it is ready to fire, so all it needs to do is check for the trigger. Once the trigger is pulled and the weapon fired it enters in the Reset State. EnterResetState() sets up the timer and sets the state for the next frame. Since this code isn’t run in a loop there is no need to write any convoluted checks to make sure it doesn’t execute more than it should. That sort of logic is a common source of bugs in monolithic update loops that manage object state. Now, since the weapon is in the reset state, it cannot fire. This is another instance of the implicit logic enforced by a state machine: ResetState() does not need to check the trigger. It’s main purpose is to count down. However once the timer is up I do check for the trigger however so I can short circuit back to Fire if the trigger remains held. This is so that small differences between frame times don’t cause too much of a discrepancy in the fire rate of the weapon while the trigger is held down. When the trigger is released, EnterIdleState() resets the timer completely so the gun doesn’t fire a minuscule fraction of a second faster the next time.
Did you like this post? Tell us
Leave a comment
Log in with your itch.io account to leave a comment.