Jump to content

How do you organize your game code?


Chimera
 Share

Recommended Posts

I know there are a few threads and tutorials on this already, but I cannot get my head around some of these concepts. I am just working on some small games, actually decided to create my own version of Lessmilks coin grabber since it has some addicting gameplay. What I want to do now is create classes for my player, enemies, and level to organize the code, but I am not sure how to organize/nest the classes in Phaser and call them from some sort of working class to pull it all together. Can anyone take a look and recommend a tutorial or provide some insight?

 

Current work flow

Each step is a separate file and state, and I am switching states to load the next class

Phaser --> Boot --> Load --> Menu --> Play (game) --> gameover --> menu (repeat)

 

What I want is some way to separate the different components of the game and call them from some external class. I have never done this kind of programming so it is kinda of hard for me to follow. I was reusing a lot of code with the enemies because I was creating different types of enemies which all have the same properties. I wanted to create a base enemy class and create new instances of that enemy, but using my current method I cant find a way for it to work.

Phaser

Player, Enemy, Level

Worker --> player/enemy/level

 

Challenges, 

Using this new abstract method in my head, I am not sure how to change game states since the classes which contain player, enemy, and level are all required by each state. Would I have to manage the states myself and create some sort of destroy function in each class that is called when moving from one level to another?

 

 

Link to comment
Share on other sites

There are lots of different schools of organizing game code, and none of them are objectively right or wrong. Over the years I've come with my own framework that is basically multi-agent system that can be built upon any other framework or engine (and Phaser too).

 

It consists of a two basic classes: GameObject and GameSystem, and a mediating environment for event flow: Event. In terms of phaser GameObject extends Phaser.Group and is completely proactive, which means it completely manages its state, creates images and sprites inside itself, updates itself, adds itself to the world and removes from it. GameSystem extends Phaser.Plugin and is proactive too, but it's not a display object, but rather a static class with a collection of methods. All the game objects in the game like players, weapons, monsters and such are extended from the GameObject, and all the managers like Physics, Sound, etc. are extended from GameSystem. Custom Event class is basically a global class to manage even flow throughout the entire object pool: you can use its static methods like Event.register (E.SomethingHappened, this.onSomethingHappened, this) and Event.dispatch (E.SomethingHappened, {x:2, txt: 'hehe', b:true}) inside your objects and systems to setup effective communication between them. E is just a class with predefined events with float values assigned to them.

 

The key feature of this framework is unprecedented encapsulation, which I find very pleasing for my workflow. Objects and systems know practically nothing about each other - all they do is:

1) subscribe to some events with internal callbacks, usually at initialization state

2) when some event fires, callback changes something in this object or system

3) when something important happens in the object, it dispatches an event along with data for others to receive

4) event is dispatched to the 'outside', and this object/system doesn't know what objects/systems have received it

 

Other than that, I'm using all major game pieces as static classes, and utilize factories inside them, so I could always do things like Player.current.doSomething(), Bullet.instance(n).destroy(), Missile.createNew (), etc, without storing private variables of class A inside class B. That's practically it. 

Link to comment
Share on other sites

I'll be honest, I don't fully grasp your method. That is not because you haven't explained it well, just my lack of programming experience I would say. I understand aspects of it, but I have trouble visualizing how I would be able to apply it to my own project. Your intention was likely just to show me one method of how the code can be organized, not necessarily to do it your way, which is appreciated, just wish some of these concepts 'clicked' a bit fast :P

 

Using your example, where would you store a tween animation for your player or level? Is the event class declared outside of the phaser groups to make it global? 

Link to comment
Share on other sites

When I looked at the code from end3r's Monster Wants Candy it changed how I did things. http://www.html5gamedevs.com/topic/6893-monster-wants-candy/

 

I'm not a Javascript super wizard, this is my first large undertaking with Phaser, I don't 100% understand .prototype and why it does what it does but using the same organizational methods end3r uses has really made it quite easy to keep things clean and modular. He has it on GitHub so I suggest you take a look.

 

https://github.com/EnclaveGames/Monster-Wants-Candy

Link to comment
Share on other sites

Using your example, where would you store a tween animation for your player or level? Is the event class declared outside of the phaser groups to make it global? 

 

Phaser.Game already has a tweens variable that is intended for that. You just pass a game variable to every new object or system you create and then use all phaser goods internally, when reacting to events, like this.game.sound.volume = 0.5, this.game.time.now, this.game.world.add (this). etc. I'm using TypeScript so on top of that I extend Phaser.Game as Game and Phaser.State as LoaderState, MenuState, PlayState, etc. In JS you can do it with extending prototype chain. All high-order game logics always happens inside states anyway, even when not using my framework.

Link to comment
Share on other sites

Alright, so I have been trying to rework my code into separate classes that make sense and I am still getting stuck on how to make the code better, more object oriented. Can someone take a look at the code below, this is the single class I am trying to separate up. 

var playState = {	create: function() {     // Create player sprite/object    this.player = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY, 'player');    // Set player animations from spritesheet    this.player.animations.add('static', [0], 1, true);    this.player.animations.add('left', [1], 1, true);    this.player.animations.add('right', [2], 1, true);    this.player.animations.add('jump', [3], 1, true);    this.player.animations.add('down', [4], 1, true);    // Set anchor point    this.player.anchor.setTo(0.5, 0.5);    // Enable physics for player object    this.game.physics.arcade.enable(this.player);    // Set gravity to player object    this.player.body.gravity.y = 500;    // Initialize game sounds    this.bgmLoop = this.game.add.audio('bgmLoop');    this.enemyDie = this.game.add.audio('enemyDie');    this.uniqueEnemySpawn = this.game.add.audio('uniqueEnemySpawn');    this.pickupCoin = this.game.add.audio('pickupCoin');    this.playerDie = this.game.add.audio('playerDie');    this.playerJump = this.game.add.audio('playerJump');    // Start bgm loop    this.bgmLoop.loop = true;    this.bgmLoop.play();    // Creating an emitter for pixel partical effect    this.emitter = this.game.add.emitter(0, 0, 15);    this.emitter.makeParticles('pixel');    this.emitter.setYSpeed(-150, 150);    this.emitter.setXSpeed(-150, 150);    this.emitter.gravity = 0;    // Setup controller object    this.cursor = this.game.input.keyboard.createCursorKeys();    // Assigns these keys to the game object to prevent the browser from utilizing them    this.game.input.keyboard.addKeyCapture([Phaser.Keyboard.UP, Phaser.Keyboard.DOWN, Phaser.Keyboard.LEFT, Phaser.Keyboard.RIGHT]);    // Setup WASD as alternate keys    this.wasd = {      up: this.game.input.keyboard.addKey(Phaser.Keyboard.W),      left: this.game.input.keyboard.addKey(Phaser.Keyboard.A),      right: this.game.input.keyboard.addKey(Phaser.Keyboard.D),      down: this.game.input.keyboard.addKey(Phaser.Keyboard.S),    };    // Create level    this.createWorld();    // Create coins    this.coin = this.game.add.sprite(60, 140, 'coin');    this.game.physics.arcade.enable(this.coin);    this.coin.anchor.setTo(0.5, 0.5);    // Add score text    this.scoreLabel = this.game.add.text(30, 30, 'score: 0',       { font: '18px Arial', fill: '#ffffff' });    this.score = 0;    // Add Enemy    this.enemies = this.game.add.group();    this.enemies.enableBody = true;    this.enemies.createMultiple(10, 'enemy');    this.game.time.events.loop(2200, this.addEnemy, this);	},  createWorld: function() {    // Create our wall group with Arcade physics    this.walls = this.game.add.group();    this.walls.enableBody = true;    // Create the 10 walls    this.game.add.sprite(0, 0, 'wallV', 0, this.walls); // Left    this.game.add.sprite(480, 0, 'wallV', 0, this.walls); // Right    this.game.add.sprite(0, 0, 'wallH', 0, this.walls); // Top left    this.game.add.sprite(300, 0, 'wallH', 0, this.walls); // Top right    this.game.add.sprite(0, 330, 'wallH', 0, this.walls); // Bottom left    this.game.add.sprite(300, 330, 'wallH', 0, this.walls); // Bottom right    this.game.add.sprite(-100, 160, 'wallH', 0, this.walls); // Middle left    this.game.add.sprite(400, 160, 'wallH', 0, this.walls); // Middle right    var middleTop = this.game.add.sprite(100, 80, 'wallH', 0, this.walls);    middleTop.scale.setTo(1.5, 1);    var middleBottom = this.game.add.sprite(100, 240, 'wallH', 0, this.walls);    middleBottom.scale.setTo(1.5, 1);    // Set all the walls to be immovable    this.walls.setAll('body.immovable', true);  },  movePlayer: function() {    // Left movement    if (this.cursor.left.isDown || this.wasd.left.isDown) {      this.player.body.velocity.x = -200;      this.player.animations.play('left');    }    // Right movement    else if (this.cursor.right.isDown || this.wasd.right.isDown) {      this.player.body.velocity.x = 200;      this.player.animations.play('right');    }    // Velocity set to 0 if no key pressed    else {      this.player.body.velocity.x = 0;      this.player.animations.play('static');    }    // Jump if up key is pressed    if (this.cursor.up.isDown || this.wasd.up.isDown) {      this.player.animations.play('jump');      if (this.player.body.touching.down)      {        this.player.body.velocity.y = -320;        this.playerJump.play();      }    }    else if (this.cursor.down.isDown || this.wasd.down.isDown) {      this.player.animations.play('down');    }  },  playerDeath: function() {    this.game.global.score = this.score;    this.player.kill();    this.playerDie.play();    this.emitter.x = this.player.x;    this.emitter.y = this.player.y;    this.emitter.start(true, 1000, null, 20);    this.game.time.events.add(1000, this.gameOver, this);  },  gameOver: function() {    this.game.state.start('gameover');    this.bgmLoop.stop();  },  updateCoinPosition: function() {    // Store all the possible coin positions in an array    var coinPosition = [      {x: 140, y: 60}, {x: 360, y: 60}, // Top row      {x: 60, y: 140}, {x: 440, y: 140}, // Middle row      {x: 130, y: 310}, {x: 370, y: 310} // Bottom row    ];    // Remove the current coin position from the array    // Otherwise the coin could appear at the same spot twice in a row    for (var i = 0; i < coinPosition.length; i++) {      if (coinPosition[i].x === this.coin.x) {        coinPosition.splice(i, 1);      }    }    // Randomly select a position from the array    var newPosition = coinPosition[      this.game.rnd.integerInRange(0, coinPosition.length-1)];    // Set the new position of the coin    this.coin.reset(newPosition.x, newPosition.y);      },  takeCoin: function(player, coin) {    // Play coin sound    this.pickupCoin.play();    // Update score by 5 points    this.score += 5;    // Update score label on screen    this.scoreLabel.text = 'score: ' + this.score;    // Change coin position    this.updateCoinPosition();  },  addEnemy: function() {    // Create new enemy if one exists    var enemy = this.enemies.getFirstDead();    // If no enemies exists execute a return ending the function    if(!enemy) {      return;    }    // If the function has not ended then proceed to create new enemy with below properties    enemy.anchor.setTo(0.5, 1);    enemy.reset(this.game.world.centerX, 0);    enemy.body.gravity.y = 500;    enemy.body.velocity.x = 100 * Phaser.Math.randomSign();    enemy.body.bounce.x = 1;    enemy.body.bounce.y = 0;    enemy.scale.setTo(1, 1);    enemy.checkWorldBounds = true;    enemy.outOfBoundsKill = true;    var num = this.game.rnd.integerInRange(0, 100);    if (this.score > 40 && num < 15) {      enemy.body.velocity.x = 60 * Phaser.Math.randomSign();      enemy.body.gravity.y = 250;      enemy.scale.setTo(1.5, 1.5);      console.log('+++ Big Boy +++');      this.uniqueEnemySpawn.play();    }    else if (this.score > 120 && num > 15 && num < 30) {      enemy.body.velocity.x = 200 * Phaser.Math.randomSign();      enemy.scale.setTo(1, 1);      console.log('+++ Speedy +++');      this.uniqueEnemySpawn.play();    }    else if (this.score > 80 && num > 30 && num < 45) {      enemy.body.velocity.x = 100 * Phaser.Math.randomSign();      enemy.scale.setTo(.8, .8);      enemy.body.bounce.y = .70;      enemy.body.gravity.y = 500;      console.log('+++ Bouncy +++');      this.uniqueEnemySpawn.play();    }    console.log('Enemy Integer: ' + num);    console.log('Enemy Velocity: ' + enemy.body.velocity.x);    console.log('Enemy BounceX: ' + enemy.body.bounce.x);    console.log('Enemy BounceY: ' + enemy.body.bounce.y);    console.log('Enemy Gravity: ' + enemy.body.gravity.y);    console.log('Enemy ScaleX: ' + enemy.scale.x);    console.log('Enemy ScaleY: ' + enemy.scale.y);    console.log('\n');  },	update: function() {    // Adds collision between player and the level ground/walls    this.game.physics.arcade.collide(this.player, this.walls);    // Calls the movePlayer() function every frame checking if any keys have been pressed    this.movePlayer();        // Checks if player is outside of the world boundry and calls playerDeath() function if true    if (!this.player.inWorld) {      this.playerDeath();    }        // Checks if the player and coin objects are overlapping and calls takeCoin if true    this.game.physics.arcade.overlap(this.player, this.coin, this.takeCoin, null, this);        // Make enemies collide with ground/walls    this.game.physics.arcade.collide(this.enemies, this.walls);        // Call the playerDeath() function when the player and an enemy overlap    this.game.physics.arcade.overlap(this.player, this.enemies, this.playerDeath, null, this);	},};
Link to comment
Share on other sites

I can't really write the class for you at the moment, but I'm working on something very similar at the moment. You can look at it on my github. I currently have something similar to what you're after with a main class, plus a player and enemy class. It's very basic at the moment and can/will be refactored over time (hopefully :P). It might not be particularly great code, but it works and does what you're trying to do.

 

Also look at the best answer for this thread here. I found it very useful when building my other classes.

Link to comment
Share on other sites

Thank you Danny for the reply, it looks like the github link does not exist though, is there an alternate link? 

 

Due to some people who took the source code of the game, rip off the links and logos and was selling it on CodeCanyon, also put adverts and was selling it across various app stores and portals and destroying my business I had to take the full sources of some of the games down from GitHub.

You can still see the commented source code of the demo on GitHub with the structure and basic functions, you can also read the long tutorial about it on Tuts+ Game Development website.

Link to comment
Share on other sites

Thank you Signalsin and End3r, I will take a look at the links and see if that helps. I started building empty classes to help define them a bit better, and that seems to be helping as well. Easier to visualize what each class will be doing when you don't clutter it up with the actual code :P. I figure I can create a player, enemy, and maybe a level class, then create a worker class which actually has the logic and game update method etc. I will take a look at your links before going further to see if that changes how I approach writing this, and will probably post the results after for critique :).

Link to comment
Share on other sites

Due to some people who took the source code of the game, rip off the links and logos and was selling it on CodeCanyon, also put adverts and was selling it across various app stores and portals and destroying my business I had to take the full sources of some of the games down from GitHub.

You can still see the commented source code of the demo on GitHub with the structure and basic functions, you can also read the long tutorial about it on Tuts+ Game Development website.

 

Wow end3r that is crazy. Sorry to hear it. I suppose you have to be careful here.

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

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