Jump to content

Executing code after tweening completes


Rebecca
 Share

Recommended Posts

I'm building my first Phaser game and stumbled upon a conceptual issue I'm not sure how to solve best.
 
Sometimes I want to wait for a tween to finish before I run the next bit, e.g. in a match 3 game I'd want to remove the matched blocks before dropping the rest and so on. Here's a snippet to illustrate how I've found it done in simple examples.

function removeBlocks() {  var duration = 1000;  game.add.tween(...).to(..., duration, ...).onComplete(...);  ...}function dropBlocks() {  ...}removeBlocks();game.time.events.add(1000, dropBlocks);

This works but there's an implicit dependency between the duration of the tween and the timer and any code run in the onComplete callback will race dropBlocks. The former could be fixed by passing the duration around but that's an implementation detail of removeBlocks that nothing else should depend on.
 
The obvious way to make the dependency explicit is to use the onComplete callback. However, that means either passing dropBlocks to removeBlocks or exposing the tween to add the onComplete outside. Alternatively, one could introduce an onBlocksRemoved event that's triggered in the onComplete callback and add removeBlocks as a one-time listener. That hides the tween completely but feels like abusing the event system if this is the only callback and means writing code in the wrong order:

game.onBlocksRemoved.add(dropBlocks);removeBlocks();

Instead I'm currently returning a promise.

function removeBlocks() {  var tween = game.add.tween(...).to(...);  var promise = new Promise(function (resolve) {    tween.onComplete.add(function () { resolve(); });  });}removeBlocks()  .then(dropBlocks)  .then(...);

I do like this solution a lot more than the alternatives because it hides the tween while making chained events readable but promises are usually defined by resolving to a value which is something I don't have or need. That may be a hint at me misusing promises here.

 

I'd love to hear your opinion on my approach and some open questions / thoughts.

 

1. Is there an obvious solution that I'm missing because I'm still new to the framework?

2. When I'm tweening several things at once I could use Promise.all to continue once all tweens have finished but that might be me overthinking something simple.

Link to comment
Share on other sites

I personally like async


 

async.auto({  removeBlocks:function(callback){     tween.onComplete.add(callback)  },  dropBlocks:['removeBlocks',function(callback){    // some other code  }]},function(err, results){  // end results})

 

or in this case you could also use async series

async.series({  removeBlocks:function(callback){     tween.onComplete.add(callback)  },  dropBlocks:function(callback){    // some other code  }},function(err, results){  // end results})
Link to comment
Share on other sites

That code isn't in the wrong order since removeBlocks is not synchronous. You could write these 2 lines in either order (I think you've missed out an events keyword there though)

game.onBlocksRemoved.add(dropBlocks);

removeBlocks();

I'm still a bit lost why you don't want to just call dropBlocks on the completion of removeBlocks ? Because you have multiple removeBlocks() that need to run first? Or does dropBlocks function independently normally, dropping blocks every second?

Could you describe it in terms of game play and user input rather than technically?

I'm guessing user makes a match, several Tweens run to clear the relevant pieces (either serially or staggered in parallel), then finally new blocks are dropped and input is restored to the user. ?

Link to comment
Share on other sites

I'm still a bit lost why you don't want to just call dropBlocks on the completion of removeBlocks ? Because you have multiple removeBlocks() that need to run first? Or does dropBlocks function independently normally, dropping blocks every second?

Could you describe it in terms of game play and user input rather than technically?

 

It's mostly a technical issue of how I want to organize my code but I'll try.

 

After the user matches blocks in a match 3 game a couple of things need to happen. All steps may involve some mutation of the board which is why they have to be in order. Without animations or other asynchronous calls I just write them in order which is what I usually do when writing the first prototype. At this point I have a high level where you can easily understand how the game works without having to know details like how the board is organized. Those are concerns of functions like removeBlocks which as you've guessed might also be generic and not just used after matching.

 

Once I introduce animations I have to refactor to integrate the asynchronous parts. I can't just keep calling the functions in order because they might change the board asynchrously internally. The possible solutions I see are:

 

use onComplete handlers directly but that's not clean code, i.e. it's unreadable, unmaintainable;

expose the tween to add the handler on the higher level which isn't too dirty but leads to callback hell;

use an async library or promises which currently appears to be the best solution.

 

TL;DR: I'm trying to find a way to add or remove animations anywhere in my code without having to refactor half the game or rendering it unmaintainable.

 

In an ideal world I'd just slap an await in front of functions like removeBlocks and leave the rest to the lower level but we're not quite there yet.

Link to comment
Share on other sites

i don't see any problem with this code... i've been using similar in flash for years!

function doSomething() {    var tween = this.game.add.tween(sprite).to({alpha:0, angle:90}, 500, null, true)        tween.onComplete.add(this.doSomethingComplete, this)}function doSomethingComplete(obj, tween) {         obj.kill()}
Link to comment
Share on other sites

ok i guess you're referring to this... fair enough
http://stackoverflow.com/questions/26382809/how-to-avoid-callback-chains-in-asynchronous-programing

I'm not sure why you refer to "abusing the event system" . Signals are implemented to dispatch events to multiple listeners , would they not help somehow?

 

you can also chain tweens, but that doesn't necessarily directly solve your issue depending on your structure http://phaser.io/examples/v2/tweens/chained-tweens

 

@webcaetano the issue with your code as far as I can see is that you've made dropBlocks and removeBlocks non-generic there. they need to be performing generic functions (going off Rebecca's original description)

Link to comment
Share on other sites

It sounds like your primary objection to using a custom Signal is that it would have one subscriber -- which seems more like a function call than a "true" event?

 

I don't think there's anything wrong with "onBlocksRemoved" as a signal. It's self-documenting and works at a "business logic" level... like, these are the pieces specific to your game instead of Phaser, if that analogy makes any sense. Your game would eventually collect a host of these higher-level concepts (above Sprites and Tweens) that exist in the language of your game. That feels like good design to me, composing higher level bits out of lower level bits.

 

As jmp909 said, since it's async this isn't the "wrong" order:

game.onBlocksRemoved.add(dropBlocks);removeBlocks();

The code would work if these lines were reversed since the duration is 1000ms anyway and everything is async.

 

I'd be wary of making a bunch of Promises in a game. Native Promises aren't super optimized versus e.g. bluebird. Also, creating them will put pressure on the garbage collector since you're making closures every time you call this function.

Link to comment
Share on other sites

what's your criteria for "clean code" Rebecca?... as drhayes mentions you need to consider performance along with this too

 

i made a rough implementation with signals. (don't bother mentioning the mess of my 'OOP'... that's not the point of this demo!)

http://phaser.io/sandbox/FNCucrtN/play

 

(and those I'm only using because we have no chain.onComplete functionality in phaser currently)

Link to comment
Share on other sites

My definition of clean code is the one defined in the linked book. ;) There are a lot of rules in there but eventually they're all meant to improve maintainability especially when working with others. Here I was mostly referring to one function not doing what it's name implies because it's calling the next function internally. Anyone working with the code will have to memorize that.

 

I do have performance in mind but honestly not a lot of knowledge in that area and my current game is too slow for it to matter much. If you do have any pointers to learn more about performance in HTML5 or even Phaser games, I'd be glad.

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...