Chapter #13 Combat
Game Prototype Library


Combat Dynamic Menu & HUD

/**  
 * File Name: game.js  
 * Bonus File Content URL: https://leanpub.com/LoRD  
 * Description: monolithic File for controlling and  
 * displaying game scenes; managing global variables  
 * throughout game state.  
 * Author: Stephen Gose  
 * Version: 0.0.0.8 (Chapter 2 complete examples game-cmbtDynamicMenuHUDtiled-AI)  
 * Author URL: http://www.stephen-gose.com/  
 * Support: support@pbmcube.com  
 *  
 * Copyright © 1974-2017 Stephen Gose LLC. All rights reserved.  
 *   
 * Do not sell! Do not distribute!  
 * This is a licensed permission file. Please refer to Terms of Use  
 *   and End Users License Agreement (EULA).  
 * Redistribution of part or whole of this file and  
 * the accompanying files is strictly prohibited.  
 *  
 */  
// =========================================================  
// -------------------------------------------  
// game namespace section  
// -------------------------------------------  
//Typical namespace creation found in all current online Phaser tutorials  
//Create game namespace away from the window global  
 var GAMEAPP = GAMEAPP || {};  
// =========================================================  
//Create game namespace away from the window global  
 var GAMEAPP = {  
	//US Copr. or Copyright;UTF8 circled c is \u00A9 equal to ©  
	Copr: "Copyright © \u00A9 1974-2016, Stephen Gose. All rights reserved.\n",  
	// Here we have some global level vars that persist regardless of State.   
	score: 0,  
	clicks: 0,  
	// If the music in your game, and it needs to play through-out a few State  
	//	swaps, then you could reference it below.  
		//music: null,  
	//Toggle background music theme on or off; starts in "on/true" state  
		//musicToggle: true,  
	//Your game can check MYGAMEAPP.orientated in the game loops  
	//	to know if it should pause or not.  
		//orientated: false  
  
	_audioMgr: function(mode, game) {  
		switch(mode) {  
		case 'init': {  
			GAMEAPP.Storage.initUnset('GAMEAPP.audio', true);  
			GAMEAPP._audioStatus = GAMEAPP.Storage.get('GAMEAPP.audio');  
			// GAMEAPP._soundClick = game.add.audio('audio-click');  
			GAMEAPP._sound = [];  
			GAMEAPP._sound['click'] = game.add.audio('audio-click');  
			if(!GAMEAPP._soundMusic) {  
				GAMEAPP._soundMusic = game.add.audio('audio-theme',1,true);  
				GAMEAPP._soundMusic.volume = 0.5;  
			}  
			break;  
		}  
		case 'on': {  
			GAMEAPP._audioStatus = true;  
			break;  
		}  
		case 'off': {  
			GAMEAPP._audioStatus = false;  
			break;  
		}  
		case 'switch': {  
			GAMEAPP._audioStatus =! GAMEAPP._audioStatus;  
			break;  
		}  
		default: {}  
		}  
		if(GAMEAPP._audioStatus) {  
			GAMEAPP._audioOffset = 0;  
			if(GAMEAPP._soundMusic) {  
				if(!GAMEAPP._soundMusic.isPlaying) {  
					GAMEAPP._soundMusic.play('',0,1,true);  
				}  
			}  
		}  
		else {  
			GAMEAPP._audioOffset = 4;  
			if(GAMEAPP._soundMusic) {  
				GAMEAPP._soundMusic.stop();  
			}  
		}  
		GAMEAPP.Storage.set('GAMEAPP.audio',GAMEAPP._audioStatus);  
		game.buttonAudio.setFrames(GAMEAPP._audioOffset+1,  
			GAMEAPP._audioOffset+0, GAMEAPP._audioOffset+2);  
	},  
	_playAudio: function(sound) {  
		if(GAMEAPP._audioStatus) {  
			if(GAMEAPP._sound && GAMEAPP._sound[sound]) {  
				GAMEAPP._sound[sound].play();  
			}  
		}  
	}  
};  
   
// =========================================================  
//Create game namespace away from the window global  
//Create game boot state  
GAMEAPP.Boot = function (game) { };  

//End of namespace creation found in all current online Phaser tutorials  
// =========================================================  
;	//Closes any previous scripts  
// =========================================================  
// -------------------------------------------  
// Main game Handler methods - Chapter 1  
// -------------------------------------------  
// Chapter 1 uses separate JS Objects for game states  
//    This methods is still used in Phaser v3.x.x and  
//    called: "States from Objects"  
// =========================================================  
//Example: 1.2 Starting the Game.js begins  
;//Closes any previous scripts  
//(function() {  
var gWidth = 800;	//Using Golden Ration is important.  
var gHeight = 500;	//Using Golden Ration is important.  
//Example 2.2: Grid-ed Combat begins  
//Grid Tile-Map configurations  
var tileSize =  64; //twice avatar icon size? or same size?  
var numRows = 4; //adjustable for your game  
var numCols = 4; //adjustable for your game  
var tileSpacing = 2;  //adjustable for your game  
var map;	//tile map as background  
var layer;	//tile map layer  
//var tilesArray = []; //one way; thousand more to choose  
//Example 2.2: Grid-ed Combat ends  
var game = new Phaser.Game(gWidth, gHeight, Phaser.AUTO, "gContent");  
//Example: 1.2 ends  
// =========================================================  
//Example 1.3 to 1.19: Creating States in Game.js begins  
//Step 3) new game state additions (similar to Phaser v3   
//	state from objects):  
//new game state additions:  
var mainState = {  
	init: function(){  
		//stuff to generate in this function  
	},  
	  
	create: function() {  
		console.log("mainState Ready!");  
		//Set a neutral background color  
		game.stage.backgroundColor = "#369";  
		//Set game to ARCADE physics systemLanguage  
		game.physics.startSystem(Phaser.Physics.ARCADE);  
		game.renderer.renderSession.roundPixels = true;  
		game.world.enableBody = true;  
		  
		//create a character avatar  
		this.player =  
			game.add.sprite(32,32,box({length:32,width:32,color:'#00F'}));  
		this.cursor = game.input.keyboard.createCursorKeys();  
		this.player.body.collideWorldBounds = true;  
		  
		//create an opponet  
		this.enemy = game.add.sprite(200,32,  
			box({length:32,width:32,color:'#0F0'}));  
		this.enemy.body.collideWorldBounds = true;  
		this.enemy.enableBody = true;  
		this.enemy.body.immovable = true;  
		game.physics.enable(this.enemy, Phaser.Physics.ARCADE);  
		  
		//Create Room Walls on stage  
		this.Room = game.add.group();  
		this.Room.enableBody = true;  
		game.physics.enable(this.Room, Phaser.Physics.ARCADE);  
		  
		var NorthWall = game.add.sprite(0,0,  
			box({length:game.world.width,width:16,color:'#999'}));  
		NorthWall.enableBody = true;  
		NorthWall.body.immovable = true;  
		this.Room.add(NorthWall);  
		  
		var SouthWall = game.add.sprite(0,game.world.height-16,  
			box({length:game.world.width,width:16,color:'#999'}));  
		SouthWall.body.immovable = true;  
		this.Room.add(SouthWall);  
		  
		var WestWall = game.add.sprite(0,16,  
			box({length:16,width:game.world.height-32,color:'#999'}));  
		WestWall.body.immovable = true;  
		this.Room.add(WestWall);  
		  
		var EastWall = game.add.sprite(game.world.width-16,16,  
			box({length:16,width:game.world.height-32,color:'#999'}));  
		EastWall.body.immovable = true;  
		this.Room.add(EastWall);  
		  
		var interior1Wall = game.add.sprite(game.world.width/6,16,  
			box({length:16,width:game.world.width/4,color:'#AAA'}));  
		interior1Wall.body.immovable = true;  
		this.Room.add(interior1Wall);  
		
		var interior2Wall = game.add.sprite(16,game.world.height/2,  
			box({length:game.world.width/2,width:16,color:'#AAA'}));  
		interior2Wall.body.immovable = true;  
		this.Room.add(interior2Wall);  
		  
	},	//the comma is very important.  
	  
	update: function() {  
		//frame refresh and display updates  
		var speed = 250;  
		this.player.body.velocity.x = 0;  
		this.player.body.velocity.y = 0;  
  
		//monitor player's movement input  
		if (this.cursor.up.isDown){  
			this.player.body.velocity.y -= speed;  
		}else if (this.cursor.down.isDown){  
			this.player.body.velocity.y += speed;  
		}else if (this.cursor.right.isDown){  
			this.player.body.velocity.x += speed;  
		}else if (this.cursor.left.isDown){  
			this.player.body.velocity.x -= speed;  
		}  
  
		game.physics.arcade.collide(this.player, this.Room);  
		  
		game.physics.arcade.overlap  
			(this.player,this.enemy,combatEncounter,null,this);  
	}  
}; //the semi-colon is very important.  
// =========================================================  
//Example 1.17: New Game Over State begins  
var gameOverState = {  
	create: function(){  
		label = game.add.text(game.world.width/2,  
			game.world.height/2,  
			"Game Over \n Press the SPACE bar to start again",  
			{font: "22px Arial", fill: "#FFF", align:"center"});  
		label.anchor.setTo(0.5,0.5);  
		  
		this.spacebar = this.game.input.keyboard.addKey
			(Phaser.Keyboard.SPACEBAR);  
	  
	}, //comma very important here  
	update: function(){  
		if(this.spacebar.isDown){  
			game.state.start('main');  
		}  
	}  
};  
//Example 1.17: ends  
// =========================================================  
//Process conflict; refer to book  
var combat = {  
	preload: function () {  
		  
		this.load.crossOrigin = 'anonymous';  
		  
		//game background;static title and copyright  
		//Example 2.1: Dynamic Combat Menus begins  
		this.load.image('background', 'assets/images/menubkgrnd.jpg');  
		this.load.atlas('fireButton',  
			'assets/spriteSheets/mmog-sprites-silver.png',
			'assets/spriteSheets/mmog-sprites.json');  
		this.load.atlas('attackButton',  
			'assets/spriteSheets/mmog-sprites-silver.png',  
			'assets/spriteSheets/mmog-sprites.json');  
		this.load.spritesheet('button',  
			'assets/spriteSheets/mmog-sprites-silver.png', 129, 30);  
		//Example 2.1 ends  
	},  
  
	create: function(){   
		//Set a neutral background color  
		game.stage.backgroundColor = "#300";  
		//Set game to ARCADE physics systemLanguage  
		game.physics.startSystem(Phaser.Physics.ARCADE);  
		game.renderer.renderSession.roundPixels = true;  
		game.world.enableBody = true;  
		  
		label = game.add.text(game.world.width/2,  
			game.world.height-64,"(Grid-ed Tiled) Combat Encouter,  
			Dynamic Menu, HUD, & AI \n Press the SPACE bar to return",  
			{font: "22px Arial",fill: "#FFF", align:"center"});  
		label.anchor.setTo(0.5,0.5);  
		  
		this.spacebar = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);  
		  
		//Example 2.3: Hexagonal Grid-ed Combat begins  
		//Creates a new blank layer and sets the map dimensions.  
		//In this case the map is 40x30 tiles in size and  
		//	the tiles are 32x32 pixels in size.  
		//New Combat Grid - simplistic hexagon grid tiles using squares  
		this.HXTilesFloor = game.add.group();  
		var hxOffSetY = 0; // odd columns are pushed down half a square  
		var spacingX = tileSize * 0.75;  
		for(j=0;j <numRows;j++){  
			for(i=0;i <numCols;i++){  
				if ((i % 2) == 1){  
					hxOffSetY = tileSize * 0.5;  
				}else{  
					hxOffSetY = 0;  
				}  
				gameX = tileSize * i + 32 + tileSpacing ;  
				gameY = tileSize * j + 32  + tileSpacing + hxOffSetY;  
				var tileGridHx =  
				game.add.sprite(gameX+(game.world.width/2),gameY,  
					box({length:60,width:60,color:'#333'}));  
				this.HXTilesFloor.add(tileGridHx);  
			}  
		}  
		//Example 2.3: Hexagonal Grid-ed Combat ends  
		//Example 2.3: Grid-ed Combat Squares begins  
		//New Combat Grid - generic square tiles  
		this.SQTilesFloor = game.add.group();  
		for(j=0;j < numRows;j++){  
			for(i=0;i <numCols;i++){  
				gameX = tileSize * i + 32 + tileSpacing;  
				gameY = tileSize * j + 32  + tileSpacing;  
				var tileGridSQ =  
				game.add.sprite(gameX,gameY,  
					box({length:60,width:60,color:'#333'}));  
				this.SQTilesFloor.add(tileGridSQ);  
			}  
		}  
		//Example 2.3: Grid-ed Combat Squares ends  
		//Create New Dynamic menu & HUD  
		//Example 2.1: Dynamic Combat Menus deployed begins  
		var style = { font: "24px Arial", fill: "#033", align: "center" };  
		var attacktxt = this.add.text(0, 0, "Attack" , style);  
		var firetxt = this.add.text(0, 0, "Fire" , style);  
		// Attack button deployed  off screen  
		this.attackButton = this.add.button(this.world.centerX-800,  
			game.world.height-160,  
			'button', this.MeleeStrike, this, 2, 1, 0);  
		this.attackButton.anchor.set(0.5,0);  
		this.attackButton.addChild(attacktxt).anchor.set(0.5,0);  
		// Fire button deployed  
		this.fireButton = this.add.button(this.world.centerX+80,  
			game.world.height-160,  
			'button', this.MissileFire, this, 2, 1, 0);  
		this.fireButton.anchor.set(0.5,0);  
		this.fireButton.addChild(firetxt).anchor.set(0.5,0);  
		//Example 2.1: Dynamic Combat Menus deployed ends  
		  
		//create a character avatar  
		this.player =  
		game.add.sprite(32,32,box({length:32,width:32,color:'#00F'}));  
		this.cursor = game.input.keyboard.createCursorKeys();  
		this.player.body.collideWorldBounds = true;  
		this.SQTilesFloor.add(this.player);  
		  
		//create an opponent  
		this.enemy = game.add.sprite((numCols*tileSize),  
			(numRows*tileSize),box({length:32,width:32,color:'#0F0'}));  
		this.enemy.body.collideWorldBounds = true;  
		this.enemy.enableBody = true;  
		this.enemy.body.immovable = true;  
		game.physics.enable(this.enemy, Phaser.Physics.ARCADE);  
		this.SQTilesFloor.add(this.enemy);  
			  
		//Create Room Walls on stage  
		this.Room = game.add.group();  
		this.Room.enableBody = true;  
		game.physics.enable(this.Room, Phaser.Physics.ARCADE);  
		  
		var NorthWall = game.add.sprite(0,0,  
			box({length:game.world.width,width:16,color:'#999'}));  
		NorthWall.enableBody = true;  
		NorthWall.body.immovable = true;  
		this.Room.add(NorthWall);  
		  
		var SouthWall = game.add.sprite(0,game.world.height-16,  
			box({length:game.world.width,width:16,color:'#999'}));  
		SouthWall.body.immovable = true;  
		this.Room.add(SouthWall);  
		  
		var WestWall = game.add.sprite(0,16,  
			box({length:16,width:game.world.height-32,color:'#999'}));  
		WestWall.body.immovable = true;  
		this.Room.add(WestWall);  
		  
		var EastWall = game.add.sprite(game.world.width-16,16,  
			box({length:16,width:game.world.height-32,color:'#999'}));  
		EastWall.body.immovable = true;  
		this.Room.add(EastWall);  
		  
	  
	}, //comma very important here  
	update: function(){  
		if(this.spacebar.isDown){  
			//game.state.start('main');  
			window.open("http://adventurers-of-renown.com/book/ch2/index.html", "_blank");  
		}  
		//frame refresh and display updates  
		var speed = 250;  
		this.player.body.velocity.x = 0;  
		this.player.body.velocity.y = 0;  
		this.enemy.body.velocity.x = 0;  
		this.enemy.body.velocity.y = 0;  
  
		//monitor player's movement input  
		//Example 2.7 Enemy AI mirrored movement  
		if (this.cursor.up.isDown){  
			this.player.body.velocity.y -= speed;  
			this.enemy.body.velocity.y += speed;  
		}else if (this.cursor.down.isDown){  
			this.player.body.velocity.y += speed;  
			this.enemy.body.velocity.y -= speed;  
		}else if (this.cursor.right.isDown){  
			this.player.body.velocity.x += speed;  
			this.enemy.body.velocity.x -= speed;  
		}else if (this.cursor.left.isDown){  
			this.player.body.velocity.x -= speed;  
			this.enemy.body.velocity.x += speed;  
		}  
		//Example 2.1: Dynamic Combat Menus buttons swapped begins  
		//Not engaged in melee  
		this.attackButton.x = this.world.centerX-800;  
		this.fireButton.x = this.world.centerX+80;  
		//Example 2.1: Dynamic Combat Menus buttons swapped ends  
		game.physics.arcade.collide(this.player, this.Room);  
		  
		game.physics.arcade.overlap  
		(this.player,this.enemy,meleeCombat,null,this);  
	}  
};  
// =========================================================  
// -------------------------------------------  
// Supporting game Function & Classes  
// -------------------------------------------  
// =========================================================  
//Example 2.1a: Dynamic Combat Menus support begins  
//melee combat: Dynamic Menu; engaged in melee  
var meleeCombat = function(player,enemy){  
	this.attackButton.x = this.world.centerX-80;  
	this.fireButton.x = this.world.centerX+800;  
	  
};  
  
var MeleeStrike = function(){  
	console.log('attacked');  
	  
};  
  
var MissileFire = function(){  
	console.log('fired');  
	  
};  
//Example 2.1a: Dynamic Combat Menus support ends  
// =========================================================  
//Example 1.16: Collision Results Determination begins  
//character's death  
var handlePlayerDeath = function(player,enemy){  
	player.kill();  //kill off the avatar  
	game.state.start("gameOver"); //change to Game Over scene  
};  
//Example 1.16: ends  
// =========================================================  
//character's initiates combat  
var combatEncounter = function(player,enemy){  
	  
	game.state.start("combat"); //change to Game Over scene  
};  
// =========================================================  
//Example 1.7: Prototyping Graphics begins  
//create a box Image (pseudo graphics) for the HTML5 canvas.  
var box = function(options) {  
	var bxImg = this.game.add.bitmapData(options.length,options.width);  
	bxImg.ctx.beginPath();  
	bxImg.ctx.rect(0,0,options.length,options.width);  
	bxImg.ctx.fillStyle = options.color;  
	bxImg.ctx.strokeStyle = "#FFF";
	bxImg.ctx.fill();  
	return bxImg;  
};  
// =========================================================  
//Step 1) Let’s tell Phaser about our new scenes  
//Phaser uses our code and gives it a name of ‘main’.  
//Let’s tell Phaser about our new scenes  
//Phaser uses our code and gives it a name of ‘main’.  
game.state.add("main", mainState);  
game.state.add("gameOver", gameOverState);  
game.state.add("combat", combat);  
//tells Phase to start using it.  
game.state.start("main"); //tells Phase to start using it.  
//Example 1.7: ends  
// =========================================================  

See Examples here


Example Guitar Hero Combat

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8" />
	<title>Phaser Game Prototyping - (Your Game Title Here)</title>
	<meta name="description" content="Phaser Game Prototyping Template" />
	<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
	<style>
		body{margin:0;padding:0;}
		canvas {margin: 0 auto;}
	</style>
   <script src="https://cdn.jsdelivr.net/phaser/2.6.2/phaser.min.js"></script>
</head>
<body>
    <div id="game"></div>
    <script type="text/javascript">
      
      
      //Canvas dimensions: world and viewports' Height and Width
//**TODO** adjust for your game deployment
var game = new Phaser.Game(880, 550, Phaser.AUTO, 'game');

var NUMBER_OF_ATTACKS = 8;	//maximum for each attack blade
var difficulty =  1;		//Level of difficulty

// Our Missile class template for ranged attacks 
// This uses an image Sprite with our secret sauce properties 
// It launches the Weapon class plugin manager

//Step 4) Dedicated functions for missile objects 
var Bullet = function (game, key) {

	Phaser.Sprite.call(this, game, 0, 0, key);

	//tell Pixi to use nearest neighbor scaling. 
	//When a bullet is scaled from its default size 
	//it won't be automatically 'smoothed' and will retain its pixel crispness. 
	this.texture.baseTexture.scaleMode = PIXI.scaleModes.NEAREST;
	this.anchor.set(0.5);

	this.inputEnabled = true;
	
	//check if the bullet is inside the world boundaries and 
	//if not kill it, return it to bullet pool. 
	this.checkWorldBounds = true;
	this.outOfBoundsKill = true;
	this.exists = false;

	//Re-align the Bullet toward the direction it is traveling. 
	this.tracking = false;
	
	//how fast the bullet should grow in size as it travels 
	this.scaleSpeed = 0;

};

//See Phaser Plugin Appendix for style debate
//this method required when accessing classes.
Bullet.prototype = Object.create(Phaser.Sprite.prototype);
Bullet.prototype.constructor = Bullet;

Bullet.prototype.fire = function (x, y, angle, speed, gx, gy) {

	gx = gx || 0;
	gy = gy || 0;

	this.reset(x, y);
	this.scale.set(1);
	//important to maintain correct facing
	this.rotation = this.body.rotation;
	
	this.game.physics.arcade.velocityFromAngle(angle, speed, this.body.velocity);
	
	//don't use in Guitar-Hero style games.
	//this.angle = angle;

	this.body.gravity.set(gx, gy);

};

Bullet.prototype.update = function () {
	//console.log(this.deltaY);
	if (this.tracking)
	{
		this.rotation = Math.atan2(this.body.velocity.y, this.body.velocity.x);
	}

	if (this.scaleSpeed > 0)
	{
		this.scale.x += this.scaleSpeed;
		this.scale.y += this.scaleSpeed;
	}

};

//Step 4) Dedicated functions for missile objects.  
// add the following to the end of Example 3.2  
var Weapon = {};  
/** My concept:  
Set movie at 30fps; therefore 15fps = .5 second  
Level 0 monsters are slower; so sTempo should be a  
	variable number between 16 - 20  
Level 1 monster are slightly faster; so sTempo is 13 - 15 fps  
Level 2 monsters are quick; set sTempo at 10 - 14 fps  
Level 3 monsters are very fast; 6 - 11 fps  
Set sTempo at a "Base" rate and then modify with a variable roll  
 
Phaser.bulletSpeedVariance is:  
var SpeedVar = Math.round(Math.random()*6);  
Phaser.fireRate is: 
var sTempo = 14 + (SpeedVar - GAMEAPP.difficulty);  
var ReadiedWeapon = 1;	//should come from avatar inventory?  
  
*/
///////////////////////////////////////////////////////////////
//   launch a single missile from avatar's facing direction  //
///////////////////////////////////////////////////////////////

Weapon.LtArrowAttack = function (game,key) {
	Phaser.Group.call(this, game, game.world, 'Chopping Attack', 
				false, true, Phaser.Physics.ARCADE);
	this.name = "Chopping Attack";
	this.nextFire = 0;
	//this.bulletSpeed = 600;	//pixels per update; fixed value  
	this.bulletSpeed =  
		100 + Math.round(Math.random() * ((difficulty+1) * 160));  
	if (this.bulletSpeed > 900){this.bulletSpeed = 870;}  
		this.bulletSpeedVariance = 30;  
		console.log("Lt Attack speed: ",this.bulletSpeed);  
	
	//this.fireRate = 100; 	//in milliseconds; fixed value  
	//more difficult levels have a faster fire rate  
		this.fireRate = 1000 - this.bulletSpeed;  
	if (this.fireRate < 100){this.fireRate = 100;}  
		console.log("Lt Attack fire rate: ",this.fireRate);  
	
	for (var i = 0; i < NUMBER_OF_ATTACKS; i++)  
	{  
		this.add(new Bullet(game, 'bullet1'), true);  
	}  

	return this;  

};

//See Phaser Plugin Appendix for style debate
//this method required when accessing classes.
Weapon.LtArrowAttack.prototype = Object.create(Phaser.Group.prototype);  
Weapon.LtArrowAttack.prototype.constructor = Weapon.LtArrowAttack;  

//Step 4) Dedicated functions for missile objects. 
// add the following to the end of Example 3.3
//Launching thrown weapons using `Weapon.fire` 
Weapon.LtArrowAttack.prototype.fire = function (source) {

	if (this.game.time.time < this.nextFire) { return; }

	var x = source.x + 40;
	var y = source.y + 17;

	//270 degrees throws this weapon attack up the scene.
	this.getFirstExists(false).fire(x, y, 270, this.bulletSpeed, 0, 0);

	this.nextFire = this.game.time.time + this.fireRate;

};

//////////////////////////
//  Lunge Attack        //
//////////////////////////

Weapon.UpArrowAttack = function (game,key) {  

	Phaser.Group.call(this, game, game.world, 'Lunge Attack',  
					false, true, Phaser.Physics.ARCADE);
	this.name = "Lunge Attack";
	this.nextFire = 0;
   //this.bulletSpeed = 600;	//pixels per update; fixed value 
	this.bulletSpeed =  
		100 + Math.round(Math.random() * ((difficulty+1) * 160));  
	if (this.bulletSpeed > 900){this.bulletSpeed = 870;}
		this.bulletSpeedVariance = 30;  
		console.log("Up Attack speed: ",this.bulletSpeed);  
	
	//this.fireRate = 100; 	//in milliseconds; fixed value  
	//more difficult levels have a faster fire rate  
		this.fireRate = 1000 - this.bulletSpeed;
	if (this.fireRate < 100){this.fireRate = 100;}
		console.log("Up Attack fire rate: ",this.fireRate);

	for (var i = 0; i < NUMBER_OF_ATTACKS; i++)
	{
		this.add(new Bullet(game, 'bullet2'), true);
	}

	return this;

};

Weapon.UpArrowAttack.prototype = Object.create(Phaser.Group.prototype);
Weapon.UpArrowAttack.prototype.constructor = Weapon.UpArrowAttack;

Weapon.UpArrowAttack.prototype.fire = function (source) {

	if (this.game.time.time < this.nextFire) { return; }

	var x = this.game.world.centerX-36;
	var y = 490;

	this.getFirstExists(false).fire(x, y, 270, this.bulletSpeed, 0, 0);
   
	this.nextFire = this.game.time.time + this.fireRate;

};

///////////////////////////////////////
//  Stabbing Attack (directly above) //
///////////////////////////////////////

Weapon.DnArrowAttack = function (game,key) {

	Phaser.Group.call(this, game, game.world, 'Stabbing Attack',  
					false, true, Phaser.Physics.ARCADE);
	this.name = "Stabbing Attack";
	this.nextFire = 0;
	//this.bulletSpeed = 600;	//pixels per update; fixed value 
	this.bulletSpeed =  
		100 + Math.round(Math.random() * ((difficulty+1) * 160));
	if (this.bulletSpeed > 900){this.bulletSpeed = 870;}
		this.bulletSpeedVariance = 30;
		console.log("Dn Attack speed: ",this.bulletSpeed);
	
	//this.fireRate = 100; 	//in milliseconds; fixed value
	//more difficult levels have a faster fire rate
		this.fireRate = 1000 - this.bulletSpeed;
	if (this.fireRate < 100){this.fireRate = 100;}
		console.log("Dn Attack fire rate: ",this.fireRate);
	
	for (var i = 0; i < NUMBER_OF_ATTACKS; i++)
	{
		this.add(new Bullet(game, 'bullet3'), true);
	}
	
	return this;

};

Weapon.DnArrowAttack.prototype = Object.create(Phaser.Group.prototype);
Weapon.DnArrowAttack.prototype.constructor = Weapon.DnArrowAttack;

Weapon.DnArrowAttack.prototype.fire = function (source) {

if (this.game.time.time < this.nextFire) { return; }

	var x = this.game.world.centerX+36;
	var y = 490;

	this.getFirstExists(false).fire(x, y, 270, this.bulletSpeed, 0, 0);

	this.nextFire = this.game.time.time + this.fireRate;  
};

/////////////////////////////////////////////
//  Slice Attack - Right Arrow             //
/////////////////////////////////////////////

Weapon.RtArrowAttack = function (game,key) {
	Phaser.Group.call(this, game, game.world, 'Slice Attack',  
					false, true, Phaser.Physics.ARCADE);
	this.name = "Slice Attack";
	this.nextFire = 0;
	//this.bulletSpeed = 600;	//pixels per update; fixed value  
	this.bulletSpeed =  
		100 + Math.round(Math.random() * ((difficulty+1) * 160));  
	if (this.bulletSpeed > 900){this.bulletSpeed = 870;}  
		this.bulletSpeedVariance = 30;  
		console.log("Rt Attack speed: ",this.bulletSpeed);  
	
	//this.fireRate = 100; 	//in milliseconds; fixed value
	//more difficult levels have a faster fire rate
	this.fireRate = 1000 - this.bulletSpeed;
	if (this.fireRate < 100){this.fireRate = 100;}
		console.log("Rt Attack fire rate: ",this.fireRate);
	
	for (var i = 0; i < NUMBER_OF_ATTACKS; i++)
	{
		this.add(new Bullet(game, 'bullet4'), true);
	}
	
	return this;  

};

Weapon.RtArrowAttack.prototype = Object.create(Phaser.Group.prototype);
Weapon.RtArrowAttack.prototype.constructor = Weapon.RtArrowAttack;

Weapon.RtArrowAttack.prototype.fire = function (source) {

if (this.game.time.time < this.nextFire) { return; }

var x = this.game.world.centerX+110;
var y = 490;

this.getFirstExists(false).fire(x,y,270,this.bulletSpeed,0,0);  

this.nextFire = this.game.time.time + this.fireRate;

};

//  The core game loop

var RhythmCombat = function () {
	
	this.background = null;
	this.foreground = null;
	
	this.player = null;
	this.cursors = null;
	this.speed = 300;
	
	this.weapons = [];
	this.currentWeapon = 0;
	this.weaponName = null;

};

RhythmCombat.prototype = {

init: function () {
	
	this.game.renderer.renderSession.roundPixels = true;
	
	this.physics.startSystem(Phaser.Physics.ARCADE);
	
	},
	
	preload: function () {
	console.log("loading play state");
	//  We need this because the assets are on Amazon S3
	//  Remove the next line if running locally
	
	this.load.crossOrigin = 'anonymous';
	
	var levelSelected = 0;	//demo - rfs-Phaser.jpg
	this.load.image('play', 'assets/images/rfs-Phaser-play2.jpg');
	this.load.image('menu', 'assets/images/rfs-Phaser.jpg');
	this.load.image('receptor', 'assets/images/receptorTrans.png');
	this.load.image('dnArrow', 'assets/images/down.png');
	this.load.image('ltArrow', 'assets/images/left.png');
	this.load.image('upArrow', 'assets/images/up.png');
	this.load.image('rtArrow', 'assets/images/right.png');
	this.load.image('header', 'assets/gameFrame/silverHeader.jpg');
	this.load.image('footer', 'assets/gameFrame/silverFooter.jpg');
	this.load.atlas('button-continue','assets/spriteSheets/RightArrow-Phaser.png','assets/spriteSheets/arrowR-sprites.json\
');
	this.load.spritesheet('button',  
		'assets/spriteSheets/mmm-sprites.png', 129, 30);

	 for (var i = 1; i <= 4; i++)
	{
		this.load.image('bullet' + i, 'assets/bullet' + i + '.png');  
	}
////////////////////////////////////////////
// required assets for following states here
	
},

create: function () {
	console.log("starting play state");
	var InfoText = "Combat Begins!";
	this.background= this.add.image(0, 0, 'play');
	
	var style = { font: "bold 32px Arial", fill: "#fff", 
			boundsAlignH: "center", boundsAlignV: "middle" };
	this._toolTip = this.game.add.text(this.world.centerX,  
			this.world.centerY,InfoText, style);
	this._toolTip.anchor.set(0.5,0.5);
	this._toolTip.setShadow(3, 3, 'rgba(0,0,0,0.5)', 5);
	this._toolTip.fontWeight = 'bold';
	
	this.weapons.push(new Weapon.LtArrowAttack(this.game));
	this.weapons.push(new Weapon.UpArrowAttack(this.game));
	this.weapons.push(new Weapon.DnArrowAttack(this.game));
	this.weapons.push(new Weapon.RtArrowAttack(this.game));
	
	this.currentWeapon = 0;
	
	//must be last added to shield attacks
	this.player = this.add.sprite(this.world.centerX-260,0,  
						'header');
	//embedded receptor as background image is default
	//un comment for attacks to go under the receptor
	//this.receptor = this.add.sprite(this.world.centerX-146,53,  					'receptor');
	this.UpAttack = this.add.sprite(this.world.centerX-73,490,'upArrow');
	this.DnAttack = this.add.sprite(this.world.centerX,490,'dnArrow');
	this.LtAttack = this.add.sprite(this.world.centerX-146,490,'ltArrow');
	this.RtAttack = this.add.sprite(this.world.centerX+73,490,'rtArrow');
	this.add.image(165, 0, 'header'); //is the player's sprite
	this.add.image(165, 500, 'footer');

	this.physics.arcade.enable(this.player);
	this.player.body.collideWorldBounds = true;
	//  Cursor keys to fly + space to fire
	this.cursors = this.input.keyboard.createCursorKeys();
	this.input.keyboard.addKeyCapture([ Phaser.Keyboard.SPACEBAR ]);
	var changeKey = this.input.keyboard.addKey(Phaser.Keyboard.ENTER);
	changeKey.onDown.add(this.nextWeapon, this);
},

nextWeapon: function () {
	//  Tidy-up the current weapon
	if (this.currentWeapon > 4)
	{
		this.weapons[this.currentWeapon].reset();
	} else {
	  this.weapons[this.currentWeapon].visible = false;
	  this.weapons[this.currentWeapon].callAll('reset',null,0,0);  
		this.weapons[this.currentWeapon].setAll('exists', false);
	}
	//  Activate the new one
	this.currentWeapon++;
	if (this.currentWeapon === this.weapons.length)
	{
		this.currentWeapon = 0;
	}
	this.weapons[this.currentWeapon].visible = true;
	InfoText = this.weapons[this.currentWeapon].name;
	console.log("Current Attack Blade: ",  
		this.weapons[this.currentWeapon].name);
},

update: function () {
	//auto fire:
	//this.currentWeapon = Math.round(Math.random()*4);
	//console.log("Current Attacking blade: ",this.currentWeapon);
	
	if (this.cursors.left.isDown)
	{
		//this.player.body.velocity.x = -this.speed;
		console.log("Current Attack Blade: ", this.weapons[this.currentWeapon].name);
		console.log(" - Y: ",this.weapons[this.currentWeapon].UpArrowAttack.name);
	}
	
	if (this.cursors.right.isDown)
	{
		//this.player.body.velocity.x = this.speed;
	}
	
	if (this.cursors.up.isDown)
	{
		//this.player.body.velocity.y = -this.speed;
	}
	
	if (this.cursors.down.isDown)
	{
		//this.player.body.velocity.y = this.speed;
	}
	
	//debug:  to manually test fire; 
	//debug:  to change attacks
	if (this.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR))
	{
		this.weapons[this.currentWeapon].fire(this.LtAttack);
		this.weapons[this.currentWeapon].fire(this.UpAttack);
		this.weapons[this.currentWeapon].fire(this.DnAttack);
		this.weapons[this.currentWeapon].fire(this.RtAttack);
	}

	},
	
	render: function(){
		//console.log(game.time.now);
	}

};

//game.stage.setBackgroundColor(0xbfbfbf);
game.state.add('Game', RhythmCombat, true);

      
       </script>

</body>
</html>
      

See Examples here


Dynamic Story Generation

var plotType = [	
    "Here is Your Randomly generated Quest ...",
    "Here's how your new Adventure begins ...",
    "Here's how your new Quest begins ...",
    "Here's your background tale ...",
    "Once upon a time ..."

	];
	
var authorityFigure = [
    "A brilliant yet infamous rogue",  
    "A famous paladin or warrior",  
    "A friendly ranger",  
    "A mighty warrior hero",  
    "A noble of high esteem",  
    "A pious and powerful cleric",
    "A powerful legendary wizard",  
    "A rich guildmaster ",  
    "The chancellor/advisor to the local lord",  
    "The head of the local adventurer's guild", 
    "The local captain of the guard",  
    "The mentor of one of the player characters"  
];  

var crisisEvent = [	
    "arcane lights and unidentified sounds that come at night ",  
    "desecration of a local temple or cemetery",  
    "destruction of property by fire",  
    "increase in activity from a long-standing threat of brigands and assassins",
    "kidnappings of local children",  
    "mysterious rumors of wild magics and powerful summonings in the area",  
    "outbreaks of disease",  
    "rash of major thefts from the rich and powerful",  
    "rise in the local monstrous population",  
    "seemingly random wildfires claiming the local farms and forests",  
    "unexplained deaths among the local nobility",  
    "unexplained murders among the poor"
];  
	
var localAdventure = [	
    "a desecrated temple or shrine",  
    "a major city",
    "a nearby poorly-explored forest",  
    "a small village",  
    "isolated farms and outlying areas",  
    "local swamps and marshes", 
    "the family tombs of rich noblemen",  
    "the graveyard of your home town",  
    "the ruins of a castle",  
    "the ruins of a lost city",  
    "the ruins of an abbey",  
    "the sewers of a major city"
];  
	
var  startLocation= [	
    "the deep forest",  
    "the estate of a local lord",  
    "the frontier wilderness away from most civilization",  
    "the local adventurer's guild",  
    "the local tavern",  
    "the lower mountain passes",  
    "the rich farmlands near a major city",  
    "the streets of a large city",  
    "the streets of a small village",  
    "the trade roads between the major cities",
    "the wilderness around your previous adventure",  
    "your home or rented rooms"
];  
    
var rewardBounty = [	
    "all the supplies and clothing you can carry",  
    "local land and property grants",  
    "the adulation and gratitude of the locals",  
    "the reward of 100s of gold pieces",  
    "the reward of a minor magical item",  
    "whatever the town can gather (2d20 gold pieces each)",  
    "whatever the village can gather (2d20 gold pieces)",  
    "your armor and weapons repaired for free",  
    "your armor improved or upgraded with magical properties",  
    "your armor improved or upgraded with non-magical properties",  
    "your weapons improved or upgraded with magical properties ",
    "your weapons improved or upgraded with non-magical properties "
]; 
    
var miscreant = [	
    "a brilliant and ruthless assassin",  
    "a marauding dragon",  
    "a mighty villainous warrior for hire",  
    "a nobleman of questionable character",  
    "a pious and powerful cleric of an evil faith",
    "a powerful legendary wizard",  
    "a rich guild-master ",  
    "an infamous barbarian warrior or evil knight",  
    "the corrupt commander of the local army",  
    "the corrupt head of the local adventurer's guild",  
    "the nemesis of one of the player characters",  
    "the traitorous former captain of the guard"
];  
  
var Legend = "\n";  
  
function display(id, str) {  
	if ((id == "titlecontainer") || (id == "formatcontainer")) {  
		Legend = Legend+str+"\n";  
	} else {  
		Legend = "\n";  
	}  
}  
function displayPhaser(id, str) {  
	if (id == "titlecontainer") { 
		Legend = this.game.add.text(0, 0, str , GAMEAPP.styleRA);  
		Legend = Legend+str+"\n";  
	}  
	if (id == "formatcontainer") { 
		Legend = this.game.add.text(0, 0, str , GAMEAPP.styleHUD);  
		Legend = Legend+str+"\n";  
	} else {  
		Legend = "\n";  
	}  
}  
function displayhtml0() {  
	var format = "0";  
	var authfig = Math.floor(Math.random()*authorityFigure.length);  
	var conflict = Math.floor(Math.random()*crisisEvent.length);  
	var locadv = Math.floor(Math.random()*localAdventure.length);  
	var locstart = Math.floor(Math.random()*startLocation.length);  
	var bounty = Math.floor(Math.random()*rewardBounty.length);  
	var sinner = Math.floor(Math.random()*miscreant.length);  
	display("NONE", "");  
	display("titlecontainer", "<html><body><B>"+plotType[0]+"</B>");  
	display("formatcontainer", "As you wander through "+startLocation[locstart]+", you soon meet a familiar face, who treats you\
 to a meal in exchange for willing ears. "+authorityFigure[authfig]+" waits until most of the crowds have drawn away from you b\
efore telling you any news beyond small talk. Finally, the tale begins.\n\nRecently, there has been "+crisisEvent[conflict]\
+". This obviously has many locals concerned. Rumors say it is the work of "+miscreant[sinner]+", but none know the truth\
. It's up to you to get to the bottom of the controversy and learn what it means for all the folk and lands around "+localAdventure[locadv]+". In exchange for your aid, you'll gain "+rewardBounty[bounty]+" as well as additional accolades as heroes of "+localAdventure[locadv]+".</body></html>");  
}  
function displayhtml1() {  
	var format = "1";  
	var authfig = Math.floor(Math.random()*authorityFigure.length); 
	var conflict = Math.floor(Math.random()*crisisEvent.length);  
	var locadv = Math.floor(Math.random()*localAdventure.length);  
	var locstart = Math.floor(Math.random()*startLocation.length);  
	var bounty = Math.floor(Math.random()*rewardBounty.length);  
	var villian = Math.floor(Math.random()*miscreant.length);  
	display("NONE", "");  
	display("titlecontainer", "<html><body><B>"+plotType[1]+" </B>");  
	display("formatcontainer", "Once all was right in "+localAdventure[locadv]+" -- until "+miscreant[sinner]+" arrived. Despite the\
 best efforts of "+authorityFigure[authfig]+" and others, none could stop the "+crisisEvent[conflict]+". The only ones left to figh\
t "+miscreant[sinner]+" are you, the intrepid heroes, backed only by the hopes of those from "+startLocation[locstart]+" and t\
heir promised reward of "+rewardBounty[bounty]+".</body></html>");  
}  
function displayhtml2() {  
	var format = "2";  
	var authfig = Math.floor(Math.random()*authorityFigure.length); 
	var conflict = Math.floor(Math.random()*crisisEvent.length);  
	var locadv = Math.floor(Math.random()*localAdventure.length);  
	var locstart = Math.floor(Math.random()*startLocation.length);  
	var bounty = Math.floor(Math.random()*rewardBounty.length);  
	var villian = Math.floor(Math.random()*miscreant.length);  
	display("NONE", "");  
	display("titlecontainer", "<html><body><B>"+plotType[2]+" </B>");  
	display("formatcontainer", "• You begin the adventure in "+startLocation[locstart]+".\n• You meet "+authorityFigure[authfig]+", who \
gives you your mission.<BR>• You have to resolve the "+crisisEvent[conflict]+".<BR>• They've determined it is the work of "\
+miscreant[sinner]+", and it's up to you to stop it.<BR>• You must travel to "+localAdventure[locadv]+" to find "+villian[villian\
]+".<BR>• End the threat in exchange for "+rewardBounty[bounty]+".</font></td></tr></table>");  
}  
  
function displayCanvas0() {  
	var format = "0";  
	var authfig = Math.floor(Math.random()*authorityFigure.length); 
	var conflict = Math.floor(Math.random()*crisisEvent.length);  
	var locadv = Math.floor(Math.random()*localAdventure.length);  
	var locstart = Math.floor(Math.random()*startLocation.length);  
	var bounty = Math.floor(Math.random()*rewardBounty.length);  
	var villian = Math.floor(Math.random()*miscreant.length);  
	displayPhaser("NONE", "");  
	displayPhaser("titlecontainer", " "+plotType[0]+"");  
	displayPhaser("formatcontainer", "As you wander through "+startLocation[locstart]+", you soon meet a familiar face, who trea\
ts you to a meal in exchange for willing ears. "+authorityFigure[authfig]+" waits until most of the crowds have drawn away from\
 you before telling you any news beyond small talk. Finally, the tale begins.\n\nRecently, there has been "+crisisEvent[con \
flict]+". This obviously has many locals concerned. Rumors say it is the work of "+miscreant[sinner]+", but none know the\
 truth. It's up to you to get to the bottom of the controversy and learn what it means for all the folk and lands around\
 "+localAdventure[locadv]+". In exchange for your aid, you'll gain "+rewardBounty[bounty]+" as well as additional accolades as heroes \
of "+localAdventure[locadv]+".");  
}  
function displayCanvas1() {  
	var format = "1";  
	var authfig = Math.floor(Math.random()*authorityFigure.length); 
	var conflict = Math.floor(Math.random()*crisisEvent.length);  
	var locadv = Math.floor(Math.random()*localAdventure.length);  
	var locstart = Math.floor(Math.random()*startLocation.length);  
	var bounty = Math.floor(Math.random()*rewardBounty.length);  
	var villian = Math.floor(Math.random()*miscreant.length);  
	displayPhaser("NONE", "");  
	displayPhaser("titlecontainer", " "+plotType[1]+" ");  
	displayPhaser("formatcontainer", "Once all was right in "+localAdventure[locadv]+" -- until "+miscreant[sinner]+" arrived. Despi\
te the best efforts of "+authorityFigure[authfig]+" and others, none could stop the "+crisisEvent[conflict]+". The only ones left t\
o fight "+miscreant[sinner]+" are you, the intrepid heroes, backed only by the hopes of those from "+startLocation[locstart]+"\
 and their promised reward of "+rewardBounty[bounty]+".");  
}  
function displayCanvas2() {  
	var format = "2";  
	var authfig = Math.floor(Math.random()*authorityFigure.length); 
	var conflict = Math.floor(Math.random()*crisisEvent.length);  
	var locadv = Math.floor(Math.random()*localAdventure.length);  
	var locstart = Math.floor(Math.random()*startLocation.length);  
	var bounty = Math.floor(Math.random()*rewardBounty.length);  
	var villian = Math.floor(Math.random()*miscreant.length);  
	displayPhaser("NONE", "");  
	displayPhaser("titlecontainer", " "+plotType[2]+" ");  
	displayPhaser("formatcontainer", "• You begin the adventure in "+startLocation[locstart]+".\n• You meet "+authorityFigure[authfig]+"\
, who gives you your mission.\n• You have to resolve the "+crisisEvent[conflict]+".\n• They've determined it is the work of\
 "+miscreant[sinner]+", and it's up to you to stop it.\n• You must travel to "+localAdventure[locadv]+" to find "+villian[villian\
]+".\n• End the threat in exchange for "+rewardBounty[bounty]+".");  
}  
      

See Examples here

Copyright © 2017, Stephen Gose LLC.
All rights reserved.