Dev Update
Well, I missed the first deadline. When I originally started this jam, it was 19 May. Everything had to be done by that date. But then, at some point I guess, Game Jolt became part of it and the deadline was extended to the whole month of May. So, at least in my mind anyway, I missed that original deadline. And I’m kinda bummed about that.
However, despite dealing with a number of distractions since my last post on this, I’m still mostly on track for getting everything done within in the next 24 hours. All I have left is to add in Gutsman himself and then do more testing. So far, I’ve got a few frames of him drawn, but I haven’t worked out how I want his AI to work yet. That’s probably what I’m going to add in last.
I’m also not really holding to 32×32 pixels rules anymore either. As I noted in my first post, up-scaling images in not easy cross-browser. Even with using a canvas-to-canvas scaling trick, it still didn’t look right to me. So, I’ve basically decided to just release this on my own site when I’m done and not bother with putting it up on Game Jolt as many others have done.
Art + Code
Since I’m finally done with the tile art, I’ve posted it to Open Game Art for people to use under a CC BY 3.0 license. If you want a 8×8 pixel tileset that look like, but is notably dissimilar from, those in the Gutsman stage of Mega Man, you can use those. (Or, do like I do, and use them for inspiration to draw your own.)
This post, I’m also sharing the code that drives the AI behind the Picket Men. Unlike previous posts, though, I’m also sharing the code that drives it from within the game state’s update loop too.
GameState.prototype.create = function() { | |
... | |
this.picketGroup = this.game.add.group(); | |
this.picketGroup.add(new PicketMan(this.game, 116, 9, 'picketMan')); | |
this.picketGroup.add(new PicketMan(this.game, 128, 7, 'picketMan')); | |
this.picketGroup.add(new PicketMan(this.game, 139, 7, 'picketMan')); | |
... | |
}; | |
GameState.prototype.update = function() { | |
... | |
this.game.physics.arcade.collide(this.player, this.picketGroup, | |
function(player) { | |
player.damage(1); | |
player.blink(); | |
}); | |
this.picketGroup.forEach(function(enemy) { | |
if (this.game.physics.arcade.collide(enemy.bulletGroup, this.player, | |
function(player, bullet) { | |
bullet.kill(); | |
player.damage(1); | |
player.blink(); | |
})) { | |
} | |
if (enemy.triggerBox.contains(this.player.x, this.player.y)) { | |
enemy.frame = 0; | |
enemy.shoot(); | |
} else { | |
enemy.frame = 1; | |
} | |
}, this); | |
... | |
}; |
function PicketMan(game, x, y, cacheKey) { | |
// Call the Phaser.Sprite constructor | |
Phaser.Sprite.call(this, game, x, y, cacheKey); | |
// Enable physics | |
this.game.physics.enable(this); | |
// Set the (physics) body to be immovable | |
this.body.immovable = true; | |
// Center the anchor | |
this.anchor.setTo(0.5, 0.5); | |
// Create a group for the bullets | |
this.bulletGroup = this.game.add.group(); | |
// Enable all create items to have (physics) bodies | |
this.bulletGroup.enableBody = true; | |
// Set the type of physics bodies to be ARCADE | |
this.bulletGroup.physicsBodyType = Phaser.Physics.ARCADE; | |
// Create 5 bullets using the 'pickaxe' cache key | |
this.bulletGroup.createMultiple(5, 'pickaxe'); | |
// Check if bullets collide with world bounds | |
this.bulletGroup.setAll('checkWorldBounds', true); | |
// If they do collide with world bounds, they are killed | |
this.bulletGroup.setAll('outOfBoundsKill', true); | |
// Bullets only 'live' for 1600ms | |
this.bulletGroup.setAll('lifespan', 1600); | |
// Set the firing rate (how long between shooting) | |
this.fireRate = 4200; | |
// The time of the next shot | |
this.nextFire = 0; | |
// The velocity of each created or revived bullet | |
this.bulletSpeed = 550; | |
// The initial firing angle | |
this.firingAngle = 245; | |
// The 'triggerBox' | |
// If another sprite is within this rectangle, shoot at them | |
this.triggerBox = new Phaser.Rectangle( | |
x - (this.width * 2), | |
y - (this.height), | |
this.width * 2.5, | |
this.height * 1.5); | |
// The 'explosionGroup' for spawning explosions | |
this.explosionGroup = new ExplosionGroup(this.game); | |
// Set that this sprite is 'alive' | |
this.alive = true; | |
} | |
PicketMan.prototype = Object.create(Phaser.Sprite.prototype); | |
PicketMan.prototype.constructor = PicketMan; | |
PicketMan.prototype.shoot = function() { | |
// Is this sprite alive? | |
if (this.alive) { | |
// It is alive, so check if enough time has past since the last | |
// shot and that there are at least 1 bullets 'dead' | |
if (this.game.time.now > this.nextFire && this.bulletGroup.countDead() > 1) | |
{ | |
// Update the timer | |
this.nextFire = this.game.time.now + this.fireRate; | |
// Get the first 'dead' sprite | |
var bullet = this.bulletGroup.getFirstDead(); | |
// Reset its position to the same as this sprite's position | |
// making some adjustments for where the bullets should | |
// come from for this sprite. | |
bullet.reset(this.body.x + (this.width / 2), this.body.y + (this.height / 2) - 18); | |
// Set the 'anchor' to center (0.5 * width, 0.5 * height) | |
bullet.anchor.setTo(0.5, 0.5); | |
// Set the firing angle | |
bullet.angle = this.firingAngle; | |
// Set the firing trajectory as based on | |
// the angle and set velocity (linear diagonal motion) | |
this.game.physics.arcade.velocityFromAngle(bullet.angle, | |
this.bulletSpeed, | |
bullet.body.velocity); | |
//Unlike other projectiles, this trajectory | |
// needs to be curved. To do that, apply | |
// a high gravity to pull it down as it moves. | |
bullet.body.gravity.y = 895; | |
// Reset the lifespan so that it is kill()'ed after a | |
// certain amount of time | |
bullet.lifespan = 1600; | |
} | |
} | |
}; | |
PicketMan.prototype.update = function() { | |
// Since the hammers need to spin, | |
// iterate through the bulletGroup each update | |
// and add to each sprite's angle | |
this.bulletGroup.forEach(function(pickaxe) { | |
pickaxe.angle += 15; | |
}); | |
}; | |
PicketMan.prototype.kill = function() { | |
// Create an explosion at this position | |
this.explosionGroup.explode(this.x, this.y); | |
// kill() this sprite by calling Phaser.Sprite's | |
// own kill function and passing 'this' | |
Phaser.Sprite.prototype.kill.call(this); | |
}; |
Here is also the code that drives the Bigeye and its update code as well.
function Bigeye(game, x, y, cacheKey) { | |
// Call the Phaser.Sprite constructor | |
Phaser.Sprite.call(this, game, x, y, cacheKey); | |
// Enable physics | |
this.game.physics.enable(this); | |
// Set the (physics) body to be immovable | |
this.body.immovable = true; | |
// Center the anchor | |
this.anchor.setTo(0.5, 0.5); | |
// The 'triggerBox' | |
// If another sprite is within this rectangle, jump at them | |
this.triggerBox = new Phaser.Rectangle( | |
x - (this.width * 5), | |
y - (this.height * 5), | |
this.width * 5.5, | |
this.height * 5.5); | |
// The 'explosionGroup' for spawning explosions | |
this.explosionGroup = new ExplosionGroup(this.game); | |
// Set that this sprite is 'alive' | |
this.alive = true; | |
// Set the gravity | |
this.body.gravity.y = 825; | |
// Set health | |
this.health = 12; | |
// The jumping rate (in milliseconds) | |
this.jumpingRate = 1400; | |
// Time of next jump | |
this.nextJump = 0; | |
// Always moving left | |
this.body.velocity.x = -12; | |
} | |
Bigeye.prototype = Object.create(Phaser.Sprite.prototype); | |
Bigeye.prototype.constructor = Bigeye; | |
Bigeye.prototype.jump = function() { | |
// Every (jumping rate), move up (negative velocity) | |
if (this.game.time.now > this.nextJump) { | |
this.nextJump = this.game.time.now + this.jumpingRate; | |
this.body.velocity.y = -350; | |
} | |
}; | |
Bigeye.prototype.update = function() { | |
// If this is !alive (dead), don't update anything | |
if (this.alive) { | |
// Adjust the triggerBox coordinates | |
this.triggerBox.x = this.x - (this.width * 5); | |
this.triggerBox.y = this.y - (this.height * 5); | |
this.triggerBox.width = this.width * 5.5; | |
this.triggerBox.height = this.height * 5.5; | |
} | |
}; | |
Bigeye.prototype.kill = function() { | |
// Create an explosion at this position | |
this.explosionGroup.explode(this.x, this.y); | |
// kill() this sprite by calling Phaser.Sprite's | |
// own kill function and passing 'this' | |
Phaser.Sprite.prototype.kill.call(this); | |
}; |
GameState.prototype.create = function() { | |
... | |
this.bigeye = new Bigeye(this.game, 167, 64, 'bigeye'); | |
this.game.add.existing(this.bigeye); | |
... | |
}; | |
GameState.prototype.update = function() { | |
... | |
this.game.physics.arcade.collide(this.bigeye, this.layer); | |
this.game.physics.arcade.collide(this.player, this.bigeye, | |
function(player) { | |
player.damage(1); | |
player.blink(); | |
}); | |
... | |
if (this.bigeye.triggerBox.contains(this.player.x, this.player.y)) { | |
this.bigeye.jump(); | |
} | |
... | |
}; |