"Hard" Pause (Tech Demo 2)
by Charlie Patterson · 04/30/2012 (1:44 pm) · 6 comments
Hey everyone,
I thought I'd document a few of my tech successes updating the Torque2D engine. This one is about a "hard" pause.
Here's an example scenario: you have a game with an enemy, named baddie, that flies around above your head. It has an onUpdate() which it uses to correct course to follow you on every frame. You use setLinearVelocity() to set its direction chasing you, and every 5 seconds it fires a laser at you. The laser is fired using a schedule() call -- something like baddie.schedule(5000, "fireLaser"). This callback will create a laser scene object and send it in your direction. Then the fireLaser() routine will set up the next call to itself in 5 seconds. Finally, if you click on baddie, it will explode, so you set it to receive mouse clicks.
Almost every video game needs a pause feature, so the player can get up and, erm, answer the phone. Well, I can think of one or two that don't have it, such as the Binding of Isaac, but the rule is, a pause is just expected.
While Torque2D has a pause for scene objects, it is not a "full" video game pause. Torque's pause keeps object from "ticking," in Torque parlance. This means they no longer receive physics updates. For instance, if you were moving an object via setLinearVelocity(), like in our enemy example, it will stop moving. So far so good. It will also no longer receive onCollision() callbacks. Finally, your object will not receive onUpdate() calls which is also useful in our enemy scenario.
However... the enemy's fireLaser() will still be called (and will still run!) every five seconds. Torque does not stop the schedule queue for this object. The callback will run, laser objects will get created, and the routine will be rescheduled. Also, the enemy can still be destroyed by clicking it with the mouse. Not very pause-like.
One final (advanced) thing if you've ever played much with T2D pausing: Many onUpdate() loops compare the current "sim" time to some starting time to see how much time has passed, like this:
So the battle is half won with the Torque pause, but not quite. As well as what T2d does, a paused object should
Torque has a useMouseEvents, but I added a useActionMapEvents (where an action map is how the key pressed and joystick are handled). In both cases, a hard pause "eats" any of these events during pause.
So, without further philosophising, here is a quick tech demo of pause in action! The demo is of a tower defense, and specifically a Plants vs Zombies style prototype. The game is paused and flying bullets and bombs pause in the classic way. The caveat here is that this demo is graphics-free. :) It's basically just "squares versus circles". Hope you enjoy it!
I thought I'd document a few of my tech successes updating the Torque2D engine. This one is about a "hard" pause.
background
Here's an example scenario: you have a game with an enemy, named baddie, that flies around above your head. It has an onUpdate() which it uses to correct course to follow you on every frame. You use setLinearVelocity() to set its direction chasing you, and every 5 seconds it fires a laser at you. The laser is fired using a schedule() call -- something like baddie.schedule(5000, "fireLaser"). This callback will create a laser scene object and send it in your direction. Then the fireLaser() routine will set up the next call to itself in 5 seconds. Finally, if you click on baddie, it will explode, so you set it to receive mouse clicks.
Almost every video game needs a pause feature, so the player can get up and, erm, answer the phone. Well, I can think of one or two that don't have it, such as the Binding of Isaac, but the rule is, a pause is just expected.
While Torque2D has a pause for scene objects, it is not a "full" video game pause. Torque's pause keeps object from "ticking," in Torque parlance. This means they no longer receive physics updates. For instance, if you were moving an object via setLinearVelocity(), like in our enemy example, it will stop moving. So far so good. It will also no longer receive onCollision() callbacks. Finally, your object will not receive onUpdate() calls which is also useful in our enemy scenario.
However... the enemy's fireLaser() will still be called (and will still run!) every five seconds. Torque does not stop the schedule queue for this object. The callback will run, laser objects will get created, and the routine will be rescheduled. Also, the enemy can still be destroyed by clicking it with the mouse. Not very pause-like.
One final (advanced) thing if you've ever played much with T2D pausing: Many onUpdate() loops compare the current "sim" time to some starting time to see how much time has passed, like this:
%t = getSimTime() - %this.startTime;The sim time flows forward no matter what, so this code is actually a bit unsafe if pausing is possible. If your onUpdate() callback was, say, calculating a trajectory, those gravity calculations will not realize the game has been paused. If it's been 10 seconds or 1 hour, your routine will show a time %t of 10 seconds or one hour and the object will fall to the ground instantly.
So the battle is half won with the Torque pause, but not quite. As well as what T2d does, a paused object should
- deflect any mouse input
- deflect any keyboard input
- suspend any scheduling!
- think that no time has passed when it calls getSimTime()
"hard" pause
My update has a new method called setHardPause() which works on scene objects and even sim objects. It handles all the issues above. Here is what it is doing more specifically:- The regular pause() routine is called so that physics, onUpdate and onCollision are stopped.
- The object's scheduled callbacks are suspended. I'm particularly proud of this code. When the object is unpaused, the items move back into the queue but they are adjusted so that the time paused is forgotten. That is, if an event were to go off in 4 seconds, but then the object is paused for 10 seconds, once unpaused, the event still has 4 seconds until it fires. A nice side-effect is some code to ask about your events: how many, what function will be called etc.
- Objects can now call %this.getSimTime() and get a personal lie about the current time which pretends that time during pause did not pass.
- the onUpdate() routine now gives your code an elapsedTime parameter. This may hold 32ms or 1000ms or whatever the engine deemed had passed. However it will not show any time that accumlated during pause. Most onUpdate routines can now use
%totalElapsed += %elapsedTime;which is a bit more natural.
Demo
So, without further philosophising, here is a quick tech demo of pause in action! The demo is of a tower defense, and specifically a Plants vs Zombies style prototype. The game is paused and flying bullets and bombs pause in the classic way. The caveat here is that this demo is graphics-free. :) It's basically just "squares versus circles". Hope you enjoy it!
#2
I went through lots of engine stuff and I hope I wrote up what I found in a way others can learn from.
05/01/2012 (1:15 pm)
Thanks @Frank! That kinda comment makes it worthwhile!I went through lots of engine stuff and I hope I wrote up what I found in a way others can learn from.
#3
05/03/2012 (12:04 am)
I have been setting $timescale = 0; to simulate a pause. It seems to stop the passing of time, which pauses scheduled events as well.
#4
a) I'd like to pause individual objects so that any dialog I bring up still gets time updates for showing any animations, etc. Also for upcoming "tutorial mode" I want to pause some things and leave others for you to click on and watch idle and stuff. If I had a %this.timeScale per object that would help... but this would require more engine mods than just setting a var.
a) I have *some* schedules I'd like to run, which requires time moving for objects that aren't paused.
c) I'd like to block the mouse and keyboard as well. Easy enough with some more calls alongside $timeScale=0, though.
05/03/2012 (10:50 am)
Thanks for the suggestion, @Simon. I played $timeScale=0 first and wasn't satisfied but that was months ago. Hmm. Not sure why anymore. But a couple of justifications come to mind:a) I'd like to pause individual objects so that any dialog I bring up still gets time updates for showing any animations, etc. Also for upcoming "tutorial mode" I want to pause some things and leave others for you to click on and watch idle and stuff. If I had a %this.timeScale per object that would help... but this would require more engine mods than just setting a var.
a) I have *some* schedules I'd like to run, which requires time moving for objects that aren't paused.
c) I'd like to block the mouse and keyboard as well. Easy enough with some more calls alongside $timeScale=0, though.
#5
Can you do 'a' (first 'a' that is) by hiding the actual object that is paused and inserting an object that looks identical and is animated, but does not listen to time scale. Like a dummy object. Then when you are done you can destroy the fake object and redisplay the actual object. Would something like that work?
05/03/2012 (1:10 pm)
@Charlie,Can you do 'a' (first 'a' that is) by hiding the actual object that is paused and inserting an object that looks identical and is animated, but does not listen to time scale. Like a dummy object. Then when you are done you can destroy the fake object and redisplay the actual object. Would something like that work?
#6
05/03/2012 (6:52 pm)
@Frank, actually I don't think you can! The animation won't work while the game time scale is set to 0.
Torque Owner Demolishun
DemolishunConsulting Rocks!