Efficient pause methods? (AS3)

Subscribe to Efficient pause methods? (AS3) 24 posts

Sign in to reply


 
avatar for Aghannor Aghannor 125 posts
Flag Post

I’m implementing a pause function/pause menu for a game, and I’m trying to see if there’s a more efficient way to do it.

As it is, I have a boolean in my document class called isPaused. For all functions called by an ENTER_FRAME event listener, the function checks if isPaused is false before doing anything. This prevents any per-frame actions from being processed while the game is paused.

All classes with additional functionality, e.g. a timer or other event listener, have disable and enable functions which stop and start (respectively) timers and listeners. When pausing, the game calls all the disable functions and sets isPaused to true; when unpausing, the game calls all enable functions and sets isPaused to false.

This works just fine, I’m just wondering if anyone has found a more efficient or more effective way to do this.

 
avatar for bLasTamos bLasTamos 545 posts
Flag Post

Make one TimeControl class listen for enter_frame, then make all the time dependant classes have a function that is called by TimeControl, like tick(time:int). Whenever you pause, only TimeControl will check if isPaused is true, if so, it doesnt call the tick() functions on time dependent objects.

Edit: it also works if you want higher gamespeed, just pass a different value on the time parameter

 
avatar for Aghannor Aghannor 125 posts
Flag Post

Hm… I’m trying to do something like that now, but with events instead. So basically I have a GameClock class which “ticks” every frame. I’ve rewritten all my time-dependent classes to respond to a tick event rather than a ENTER_FRAME event. The code looks like this:

package {
	import flash.events.*
	import flash.display.Stage;
	
	public class GameClock {

		public static const tickEvent:Event = new Event(Game.TICK);
		
		public static function initialize(stage:Stage)
		{
			Game.main.stage.addEventListener(Event.ENTER_FRAME, onFrame);
			Game.main.stage.addEventListener(Game.TICK, tickFrame);
		}
		
		public static function pause()
		{
			Game.main.stage.removeEventListener(Event.ENTER_FRAME, onFrame);
		}
		
		public static function unpause()
		{
			Game.main.stage.addEventListener(Event.ENTER_FRAME, onFrame);
		}
		
		private static function onFrame(e:Event)
		{
			Game.main.stage.dispatchEvent(tickEvent);
		}
		
		private static function tickFrame(e:Event)
		{
			trace("Tick");
		}
	}
}

Now everywhere else in my code I’ve replaced:

addEventListener(Event.ENTER_FRAME, onFrame)

with

addEventListener(Game.TICK, onFrame)

and nothing happens. The trace triggered by a tick event is firing off like crazy, but every external class doesn’t seem to register the tick event. Ideas?

 
avatar for Moonkey Moonkey 1007 posts
Flag Post

with
addEventListener(Game.TICK, onFrame)

Your stage is dispatching the event, so that’s what everything will need to attach their listeners to

stage.addEventListener(Game.TICK, onFrame)
 
avatar for Aghannor Aghannor 125 posts
Flag Post

Hm, you’re right. After looking through the Event and EventDispatcher classes at first, I was under the impression that dispatching an event to a display object will also cause that event to be dispatched to all its children, but now I see that isn’t true. Specifically, Actionscript 3 Reference has this to say about the enterFrame event in DisplayObject:

“Dispatched when the playhead is entering a new frame. If the playhead is not moving, or if there is only one frame, this event is dispatched continuously in conjunction with the frame rate. This event is dispatched simultaneously to all display objects listenting for this event.”

That makes it quite obvious that the event has to be dispatched to each particular DisplayObject… and as far as I can tell, there’s no quick and dirty way to make that happen. Is there?

The only options I can think of from here would be:

(1) Iterate through all the loaded display objects and dispatch a Tick to them. This now seems pretty inefficient since an enterFrame event is already being dispatched to all of these objects and I’m just doing the same work over again. Also, some of the displayObjects may not have a Tick listener. But as long as I don’t have a ridiculous number of objects loaded, the overhead would be trivial.
(2) Generate an array of objects that are time-sensitive and only dispatch a Tick to those objects. This is probably a bad idea, since there shouldn’t be very many displayObjects that don’t tick, so I’m cutting out a trivial number of cases while necessitating extra logic to keep track of the array (particularly for removing objects that are no longer active)
(3) Use bLasTamos’s suggestion and implement this instead using tick() functions. I don’t think this would really be any more efficient than option (1), and this requires more rewiring within each individual class.
(4) Dispatch the event to one object, like the top-level Game object, and just have all the children listen for the event there. This will work just fine, but I’m strangely hesitant to do it. Still, I can’t think of anything that might go wrong. Unattached children with no references to them by a parent are not garbage collected if there’s an enter frame event listener OR if there’s an event listener on an outside object pointing to a function in the child; so the garbage collection behavior should be the same.

The only problem I ran into with that approach (briefly) was setting the event listener in constructors for objects that are loaded at the beginning of the game. If Game is the document class, and Game.main = this (in the constructor for Game), then you can make sure the event listener is added to Game.main for all dynamically created children. For static children, though, those constructors are apparently run before the document class constructor is run, so Game.main is not yet defined. I dodged this by just adding the event listener to those objects’ parent, e.g., parent.addEventListener(…). Since they’re added at the very top level of the animation, their parent is the Game object.

Is there a more direct way to access the actual document-level instance? I guess my solution would be a little less readable if you had a parent with multiple levels of children that were all statically created and needed an enterFrame listener. (parent.parent.parent…parent.addEventListener? Probably works, but looks ugly.)

 
avatar for bLasTamos bLasTamos 545 posts
Flag Post

Instead of making your time dependent classes listen for the event, make the GameClock class hold an array of the dependent objects, then make each time dependent class call something like GameClock.register(this) when you want to register the class. That function should add the registering class to the array and when the ENTER_FRAME comes you just iterate the array and call an update function for all objects. To remove it just make GameClock.unregister(this) when you don’t need the object anymore OR when the tick() function is called make it return 0 when you want to keep it on the list and -1 when you want it removed; say your object decreases health by 1 each frame, keep returning 0 and when the HP reaches 0 you return -1, then the GameClock class should remove the item from the array.
The hard thing maybe would be to find the GameClock object, I would suggest you to make it a singleton.

 
avatar for Moonkey Moonkey 1007 posts
Flag Post

The way you have the GameClock class set up with all the static methods is a bit unusual. Forcing an outside object to dispatch an event isn’t the normal way things are done. Usually objects dispatch their own events, and whatever needs to listen for those events can do.

I think option 4 is the best of those, but I would take a slightly different approach.

I’d make GameClock extend EventDispatcher, and hold a static instance of it in Game. Then GameClock can dispatch its own TICK events, and whatever needs to listen for that can use Game.gameClock.addEventListener(Game.TICK, onFrame);

 
avatar for Moonkey Moonkey 1007 posts
Flag Post

Instead of making your time dependent classes listen for the event, make the GameClock class hold an array of the dependent objects, then make each time dependent class call something like GameClock.register(this) when you want to register the class.

That’s basically just rewriting the event system. Why not use the one already in place?

 
avatar for bLasTamos bLasTamos 545 posts
Flag Post

As you can see it’s a bit harder to get to know something already built than build your own. :x
But yeah i’d advise you to use the existing system

 
avatar for Aghannor Aghannor 125 posts
Flag Post

Yeah, I had settled on option 4, but I think I’ll reorganize it as you suggested Moonkey. Makes it more conceptually coherent to have an instantiated clock that ticks and events that listen to the clock rather than objects and methods being assigned haphazardly.

 
avatar for Gamepages Gamepages 75 posts
Flag Post

How about just using a static class called GameState to control the game’s timers.
public class GameState {
private static paused:boolean;
public static pause():void;
public static run():void;
public static paused():boolean;
}

For added security and continuity you could add an interface called TimeSensitive for all Time Sensitive objects. You could also take it 1 step further and make a class called GameStateControl that you use via composition in all TimeSensitive functions.

 
avatar for Goofyguy763 Goofyguy763 1 post
Flag Post

Well, if you want to truly remove timer based pause detection all together, while having the quickest pause detection, you could create a PausedModelEvent class that hold PRE_PAUSED_CHANGED & POST_PAUSED_CHANGED static properties. That way, you’ll be notified before and after the property changes. It’s a little more code, but the advantages are dramatic.

Then you would just use a getter / setter for “isPaused”. Obviously there is a way to abstract / strict type the code, but I’ve written it out so you can get the basics of what is happening.
////////////////
private const _gsVars : Object = {isPaused:false};

public function get isPaused() : Boolean{
return _gsVars.isPaused;
}
public function set isPaused(value : Boolean) : void{
if(value == _gsVars.isPaused){return;} // Prevent unnecessary event firing.

gameModel.dispatchEvent(new PausedModelEvent(PausedModelEvent.PRE_PAUSEDCHANGED, _gsVars.isPaused); _gsVars.isPaused = value; gameModel.dispatchEvent(new PausedModelEvent(PausedModelEvent.POST_PAUSEDCHANGED, _gsVars.isPaused);

}

 
avatar for parallactic_acid parallactic_... 547 posts
Flag Post

can’t be bothered reading all that, but here’s my 2 cents…

enterFrame listener in your main or game class. This calls everything else’s enterFrame method. When you pause remove this. when you unpause reintroduce it.

the Perfect solution.

(and you can call the enterFrame method on all creatures because you are using arrays/vectors to store them)

 
avatar for HensHouse HensHouse 93 posts
Flag Post

remove the listener, then add it back

 
avatar for umairazfar umairazfar 2 posts
Flag Post

first of all, I declared a timer:

var pauseTimer:Timer = new Timer(10);
pauseTimer.addEventListener(TimerEvent.TIMER, onPause);

then in the function that runs when every frame is entered I did this

function onEnter(e:Event):void
{
if (Key.isDown(Key.P) && gamePaused == false)
{
gamePaused = true;
stage.removeEventListener(Event.ENTER_FRAME,onEnter);
pauseTimer.start();
Mouse.show();
}
.
.
.
}

The timer starts and keeps checking for input every 10 milliseconds

now in the onPause function, i did this:

function onPause(e:Event):void
{
if(Key.isDown(Key.U) && gamePaused)
{
gamePaused = false;
stage.addEventListener(Event.ENTER_FRAME,onEnter);
pauseTimer.stop();
}
}

so I remove the EventListener and then add it right back.

Problems:

  1. Using the same key for Pausing and un-pausing was causing problems for me, hence I used P for pausing and U for resuming. I could not come up with a better solution.
  2. the movie just stops at a frame… if you have more MCs running in the same clip, they will show moving at one place. My solution would be to unhide a screen on top of the game and then hide it as you unpause.
 
avatar for EpicLLama EpicLLama 7 posts
Flag Post

if the key is the problem…. i think its pausing and unpausing because it detects the key more than 1 time…

heres a practical solution

if(key.isDown(Key.U) && gamPaused && !key_u_is_down)
{
key_u_is_down = true;
PAUSE GAME
}

if(key.isDown(Key.U) && !gamPaused && !key_u_is_down)
{
key_u_is_down = true;
UNPAUSE GAME
}

if(!key.isDown(Key.U) )
{
key_u_is_down = false; → this prevents the pause unpause at the same time.

}

 
avatar for NMSUCC NMSUCC 100 posts
Flag Post
Originally posted by Moonkey:

I’d make GameClock extend EventDispatcher, and hold a static instance of it in Game. Then GameClock can dispatch its own TICK events, and whatever needs to listen for that can use Game.gameClock.addEventListener(Game.TICK, onFrame);

This.

The key is to have different event dispatchers for your different contexts. Your game’s clock is different than the Flash runtime clock, because sometimes, you want the game clock to not fire events. Pulling it out as a separate event dispatcher is a good strategy for dealing with this.

Likewise, you could have your game clock dispatch tick events for its paused state, so that you could have elements that animate differently when the game is paused, such as screens fading to black, rolling “continue” buttons onto the screen, etc.

 
avatar for Johnwater Johnwater 21 posts
Flag Post

Hmm. If your game is ENTER_FRAME based, you could simply set the frame rate to zero.
public function pauseGame(e:KeyboardEvent):void
{
if (e.keyCode == 27)
{
if(currentFrame > 118)
{
if(!ispaused)
{
pauseScreen.x = 0;
pauseScreen.y = 0;
stage.frameRate = 0;
//saves a reference to current point in background music
pausePosition = musicChannel.position;
//pauses the background music
musicChannel.stop();
}

else

{
stage.frameRate = 24;
removeChild(pauseScreen);
//continues the music
musicChannel = playtrack.play(pausePosition);
}
}
}
}
Erm, ignore the bad indents

 
avatar for alecz127 alecz127 480 posts
Flag Post

How’s this post been going for 4 years? hasn’t he figured it out yet? x D

 
avatar for player_03 player_03 904 posts
Flag Post
Originally posted by alecz127:

How’s this post been going for 4 years? hasn’t he figured it out yet? x D

Aghannor hasn’t been the one reviving the thread…

It seems that people have been showing up with their own system and posting it in this thread because they want the world to know the “right” way to do it.
Or perhaps they just don’t look at the dates. I didn’t until you pointed it out.

 
avatar for Aesica Aesica 501 posts
Flag Post
Originally posted by player_03:
Or perhaps they just don’t look at the dates. I didn’t until you pointed it out.

Same here. D: I try to read every post before I respond, which is good because in this case, it saved me from adding more feedback to a problem which has likely been solved by the OP years ago.

 
avatar for skyboy skyboy 5926 posts
Flag Post
Originally posted by player_03:

Or perhaps they just don’t look at the dates. I didn’t until you pointed it out.

i looked when i went to reply, promptly scrolled down to see who was bumping it only to be surprised that it’s been constantly bumped for 3 years.

i’ll just flag the top post and request a lock to prevent it being bumped again

 
avatar for ehaugw ehaugw 499 posts
Flag Post

My way to do pause is to remove all the event listeners, and add one event listener that adds them all again when you press “p” or whatever you want it to be.

 
avatar for Draco18s Draco18s 5884 posts
Flag Post
Originally posted by ehaugw:

My way to do pause is to remove all the event listeners, and add one event listener that adds them all again when you press “p” or whatever you want it to be.

Congrats on not paying attention.

Oct 30, 2008

Sign in to reply