Jump to content

How do you separate your game logic?


ecv
 Share

Recommended Posts

I'm tempted to put everything in "update" and check there for any conditions. But then again, maybe there are cleaner ways to go about this. 

What about stages/levels? Do you just check for the current stage in "update" and act accordingly?

Maybe there's some sort of event driven scheme to do all this? I don't know, I'm just mindlessly conjecturing... :P

Ah, I read someone over here recommending to use delta time instead of fixed increments for each update cycle. It makes sense, but is this a common practice? This would make the game run steadily (well, as far as the device can handle the game code) despite Phaser's performance across different devices, right?

Thanks

Link to comment
Share on other sites

I don't think there's an easy answer. There aren't hard and fast rules. You fix the problem you have and keep your code manageable for you, even if you think someone else would call it bad design. Worrying too much about what is "correct" doesn't get you anywhere good.

In one game I made I had a plugin that watched for when the killable enemies were dead. When it detected that it was responsible for spawning more and displaying the "Wave 3" indicator (or wave 4, or 5, etc).

In another I drove everything off of tilemaps. If the player reached a door and went through it, the current map and everything created by it was discarded, the new map loaded in, all the objects created, and the game continued. For more scripted levels I made a map property that instructed my map loader to load additional code to run those events.

For sprites, I've used a behavior framework so certain behaviors could be turned off or on wholesale (double jumps, transform abilities, whatever).

Link to comment
Share on other sites

As general advice (not Phaser specific), putting conditionals in the update tick is robust however often the most performance demanding approach.  Therefore rationalise each condition, return as early as possible, and skip a beat where appropriate for the conditional (not everything is equal, and most things don't need to be checked at 60fps).  Likewise using deltaTime over fixedIntervals has pros and cons and different sub systems may require different choices - for example fixed is often essential to serialise input states, whereas delta is sensible for smooth animation.  As @drhayes, what is usually more important is whether it makes sense to you and your team today, it's ok to refactor another day!

Link to comment
Share on other sites

Thank you both for the input.

3 hours ago, drhayes said:

[...]Worrying too much about what is "correct" doesn't get you anywhere good.[...]

I think you nailed it. Also, interesting different takes on this. I think I could use these as reference points on what to do in a future. Thanks.

1 hour ago, b10b said:

As general advice (not Phaser specific), putting conditionals in the update tick is robust however often the most performance demanding approach.  Therefore rationalise each condition, return as early as possible, and skip a beat where appropriate for the conditional (not everything is equal, and most things don't need to be checked at 60fps).  Likewise using deltaTime over fixedIntervals has pros and cons and different sub systems may require different choices - for example fixed is often essential to serialise input states, whereas delta is sensible for smooth animation.  As @drhayes, what is usually more important is whether it makes sense to you and your team today, it's ok to refactor another day!

I see... and I guess this is what you mean by skipping a beat. I guess that by rationalizing you mean I should be prioritizing my conditions from the most crucial ones at the top, to the lesser important ones, inside the bodies of the greater ones, in a hierarchy fashion, and to pay attention not to recheck for conditions, as well as to grouping conditions in a smart way.

And yes, I definitely I agree with you both that I should worry less and get more things done. This is actually a personal unresolved issue of mine, I'm afraid, which feels like a curse as far as I'm concerned :ph34r:. I wish I could be a little bit more forgiving to myself :wacko:

Thanks again, much appreciated.

Link to comment
Share on other sites

Do you mean the game logic only inside update or as a whole? I usually just separate them out with functions

update(){

monsterCollisions()

HandlePlayerMovement()

//etc

}

I usually put physics related stuff in there, but to handle other objects like, but not limited to: objects on the floors (coins, hp orbs), item loot, clickable objects I usually have those run on a secondary timer outside of update. 

Link to comment
Share on other sites

I like to do what Wombat does, I separate and organize my code a lot, instead of having large functions I like to split them up into smaller ones. So instead of having a huge function with nested if statements for my player movement I'll separate them into a more organized format, it makes it easier to read and debug. As other posters have mentioned though as long as everything is readable to you or if you have a team then them as well, then don't worry too much, just remember to have fun. A few examples, I did not like the fact that I had to enter in a filepath for each asset I loaded so I made static classes to handle them.

// Preload Functions - With pre-set directory
Game.ImageManager.loadPicture = function(refrence, filename){
  Game.Phaser.load.image(refrence, 'img/pictures/' + filename);
};
Game.ImageManager.loadSystem = function(refrence, filename){
  Game.Phaser.load.image(refrence, 'img/system/' + filename);
};
Game.ImageManager.loadCharacter = function(refrence, filename){
  Game.Phaser.load.image(refrence, 'img/characters/' + filename);
};

It saves time, and if I ever change the directory of my assets I can simply change the one function. Also another example, for my update function for my player, because why not, examples are good. Below is a more readable to me, I can go directly to the function that says updateJump and change whatever in there, whereas some people will throw everything in their update function and it's a total mess to read and find things.

Game.Sprite_Player.prototype.update = function() {
  this.updateMovement();
  //----DEBUG---------
  this.game.debug.text(this.game.time.fps, 2, 14, "#00ff00");
  this.game.debug.bodyInfo(this, 32, 32);
  this.game.debug.body(this);
};

Game.Sprite_Player.prototype.updateMovement = function() {
  this.body.velocity.x = 0 ;
  this.updateDirection();
  this.updateJump();
  this.updateRun();

};
Game.Sprite_Player.prototype.updateRun = function() {
  if (this.shiftButton.isDown && this.body.onFloor()) {
    this.animations.play('run', 25, true);
    this.walkSpeed = this.runSpeed;
    this.isRunning = true;
  } else {
    this.walkSpeed = this.initialSpeed;
    this.isRunning = false;
  }
};

Game.Sprite_Player.prototype.updateJump = function() {
  if (this.jumpButton.isDown && this.body.onFloor() && this.game.time.now > this.jumpTimer) {
    if(this.isRunning) {
      this.body.velocity.y = this.jumpSpeed - this.runSpeed /2;
    } else {
      this.body.velocity.y = this.jumpSpeed;
    }
    this.isJumping = true;
    this.jumpTimer = this.game.time.now + 750;
  } else if(!this.body.onFloor()) {
    this.playJumpAnimation();
  } else {
    this.isJumping = false;
  }
};

Game.Sprite_Player.prototype.updateDirection = function() {
  if (this.cursor.left.isDown) {
    this.playWalkAnimation();
    this.body.velocity.x = -this.walkSpeed;
    this.scale.x = -0.5;
    //Right
  } else if (this.cursor.right.isDown) {
    this.playWalkAnimation();
    this.body.velocity.x = this.walkSpeed;
    this.scale.x = 0.5;
  } else if(this.body.onFloor()) {
    //======Idle========
    this.animations.stop('walk');
    this.animations.play('idle', 15, true);
  }
};
Game.Sprite_Player.prototype.playWalkAnimation = function() {
  if(!this.isRunning && !this.isJumping){
    this.animations.play('walk', 25, true);
  }
};
Game.Sprite_Player.prototype.playJumpAnimation = function() {
  if(this.isJumping) {
    this.animations.play('jump', 7, false);
  }
};

 

Link to comment
Share on other sites

Thank you @WombatTurkey, @LTNGames. Using functions does improve readability. But I also think these might lower performance, like that would translate to two extra JMPs or CALL&RET, and I guess since it's an interpreted language a bit more than that. But, no, I didn't lose my mind, I guess the impact on performance MUST BE negligible. So it's definitely a good trade-off. Good piece of advice. Definitely a lot more manageable and readable than a lot of packed IFs in the "update" function.

@WombatTurkey: It hadn't occurred to me the use of a secondary timer for less important stuff. I like it. This will definitely improve performance as it'll rid the "update" function of some IFs.

 

This is turning out to be quite an enlightening thread. You guys gave me a lot of good ideas which will no doubt help me code my first HTML5 game on a firmer base and with greater confidence. I'm super happy I asked this question.

 

Cheers,

Eneko

Link to comment
Share on other sites

4 hours ago, ecv said:

Thank you @WombatTurkey, @LTNGames. Using functions does improve readability. But I also think these might lower performance, like that would translate to two extra JMPs or CALL&RET, and I guess since it's an interpreted language a bit more than that. But, no, I didn't lose my mind, I guess the impact on performance MUST BE negligible. So it's definitely a good trade-off. Good piece of advice. Definitely a lot more manageable and readable than a lot of packed IFs in the "update" function.

@WombatTurkey: It hadn't occurred to me the use of a secondary timer for less important stuff. I like it. This will definitely improve performance as it'll rid the "update" function of some IFs.

 

This is turning out to be quite an enlightening thread. You guys gave me a lot of good ideas which will no doubt help me code my first HTML5 game on a firmer base and with greater confidence. I'm super happy I asked this question.

 

Cheers,

Eneko

Haha, I wouldn't worry too much about performance. V8 is pretty fast :) But it's good to always have a chip on your shoulder about optimization. This will help you a lot when you start doing multiplayer (node) later. Just don't get too caught up in it imo :)

Link to comment
Share on other sites

A function call in a hot loop in a JS VM will probably be optimized away by the VM. But, really, you don't know what's going on until you measure by profiling. If you're not doing anything egregious like 400 function calls per sprite when you have 40,000 sprites -- actually, even then. If you're looking at performance you need to run the profiler to figure out what's really going on.

Link to comment
Share on other sites

Thank you. Yes, I've been thinking this for a while that when I have a semi-working game I'll open a new thread asking about profiling. I don't want to ask or research yet as it'll probably won't make much sense to me if I don't have something to test on. Anxiously looking forward to it but first things first, I gotta get some work done :lol:

Link to comment
Share on other sites

Lean on functions as much as possible. A complex nest of if loops, infact, even a sequence of if loops, quickly becomes nigh-on impossible to read or manage, particularly if the closure formed by the if clause contains code (rather than a function invocation).

Divide and conquer, keep your routing logic (all those ifs, although there are other control patterns...) in one place and the actual mutative code (the stuff that actually does something) somewhere else, somewhere that makes sense for whatever that function does.

A nice rule of thumb is to keep functions quite small and refactor where you can to keep them that way. If you're writing basically the same piece of code in 2 or more places then that is a sure-fire candidate for refactoring and reusing that new function in all those places (DRY).

If functions are small they are easy to reason about, if your project is non-trivial you might revisit a function after a few weeks (or longer), the quicker you can understand exactly what that function does the quicker you can make your changes and get on with the next job (and not screw something else up in the meantime).

The best functions are referentially transparent, or pure, this means they take inputs, produce outputs and produce no side effects. In a class based architecture you can not use solely pure functions but it is often beneficial to try to make your functions pure. They become far more robust, increase the robustness of your whole application (side effects are a killer), they are testable, they are reusable, and they have very very few downsides, the only downside is that sometimes it might take you a few minutes more to work out how to make a function pure.

I agree with the advice given though, do what makes sense for you and your team, if you get to the end of the project and decide that those decisions did not work so well they try something else next time, pick out the good bits, ditch the bad stuff.

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

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