diff --git a/build/img/explosion.png b/build/img/explosion.png index 6f0bdca..2c93971 100644 Binary files a/build/img/explosion.png and b/build/img/explosion.png differ diff --git a/build/img/spark.png b/build/img/spark.png new file mode 100644 index 0000000..dc838f0 Binary files /dev/null and b/build/img/spark.png differ diff --git a/constants.ts b/constants.ts index 05c6d34..91fd478 100644 --- a/constants.ts +++ b/constants.ts @@ -11,7 +11,7 @@ const CONST = { EXPLOSION_MIN_RADIUS: 64, //radius in pixels from within the maximum explosion force is applied EXPLOSION_MAX_RADIUS: 640, //radius in pixels from without explosions are completely ignored PLAYER_GROUND_COLLISION: -46, //distance in pixels between player center of mass and feet - PLAYER_ACCEL_GROUND: 2000, // player acceleration on ground + PLAYER_ACCEL_GROUND: 0, // player acceleration on ground PLAYER_FRICTION_GROUND: 0.999, // fraction of speed the player loses per second while grounded PLAYER_ACCEL_AIR: 1000, // player acceleration while airborne PLAYER_FRICTION_AIR: 0.5, // fraction of speed the player loses per second while airborne diff --git a/entity.ts b/entity.ts index a2e4c4c..592f0f8 100644 --- a/entity.ts +++ b/entity.ts @@ -95,7 +95,7 @@ class FlyForwardBehavior extends Behavior { private velocity: Point; - constructor(entity: Entity, pos: Point, direction: Point, initialVelocity: Point) { + constructor(entity: Entity, direction: Point, velocity: number, initialVelocity: Point) { super() let distx = direction.x @@ -103,8 +103,8 @@ class FlyForwardBehavior extends Behavior { let dist = Math.sqrt(distx * distx + disty * disty) let directionNormalized = { x: distx / dist, y: disty / dist } - this.velocity = { x: directionNormalized.x * CONST.ROCKET_VELOCITY + initialVelocity.x, - y: directionNormalized.y * CONST.ROCKET_VELOCITY + initialVelocity.y } + this.velocity = { x: directionNormalized.x * velocity + initialVelocity.x, + y: directionNormalized.y * velocity + initialVelocity.y } entity.sprite.rotation = Math.atan(directionNormalized.y / directionNormalized.x) if (directionNormalized.x < 0) entity.sprite.rotation += Math.PI } @@ -152,20 +152,24 @@ class ExplodeOnImpactBehavior extends Behavior { state.addEntity(new Particle(entity.pos, explosionSprite, - CONST.EXPLOSION_FX_LIFETIME)); + CONST.EXPLOSION_FX_LIFETIME, + false, + 0)); state.player.explosion(entity.pos) } } class SelfDestructBehavior extends Behavior { - private initialLifetime: number; - private lifetimeLeft: number; + private initialLifetime: number + private lifetimeLeft: number + private fade: boolean - constructor(lifetime: number) { + constructor(lifetime: number, fade: boolean) { super() this.lifetimeLeft = lifetime this.initialLifetime = lifetime + this.fade = fade } update(entity:Entity, deltaTime: number, state: State) : void { @@ -173,5 +177,22 @@ class SelfDestructBehavior extends Behavior { if (this.lifetimeLeft < 0) { state.removeEntity(entity) } + if (this.fade) { + entity.sprite.alpha = this.lifetimeLeft / this.initialLifetime + } } -} \ No newline at end of file +} + +let SelfDestructIfOffscreen = new class extends Behavior { + + update(entity:Entity, deltaTime: number, state: State) : void { + if (entity.pos.x - state.camera.pos.x > CONST.SCREEN_WIDTH || + entity.pos.x - state.camera.pos.x < -CONST.SCREEN_WIDTH || + entity.pos.y - state.camera.pos.y > CONST.SCREEN_HEIGHT || + entity.pos.y - state.camera.pos.y < -CONST.SCREEN_HEIGHT + ) + { + state.removeEntity(entity) + } + } +} diff --git a/particle.ts b/particle.ts index e6a2bc1..48932ad 100644 --- a/particle.ts +++ b/particle.ts @@ -1,16 +1,49 @@ class Particle extends Entity { - - constructor(pos: Point, sprite: Sprite, lifetime: number) { + constructor(pos: Point, sprite: Sprite, lifetime: number, fade: boolean, velocity:number) { super(pos) this.sprite = sprite this.sprite.drawOrder = CONST.LAYER_FX - this.behaviors.push(new SelfDestructBehavior(lifetime)) + this.behaviors.push(new SelfDestructBehavior(lifetime, fade)) if ("setProgress" in this.sprite) { this.behaviors.push(new AnimatedBehavior(lifetime, false)) } - + if (velocity > 0) { + let angle = Math.random() * Math.PI * 2 + this.behaviors.push(new FlyForwardBehavior(this, { x: Math.sin(angle), y:Math.cos(angle)}, velocity, { x:0, y:0})) + } } - } + +class ParticleEmitterBehavior extends Behavior { + + private time: number = 0 + private particleLifetime: number + private particleSpread: number + private particleSprite: ImageResource + public frequency: number + + constructor(particleLifetime: number, particleSprite: ImageResource, frequency: number, spread:number) { + super() + this.particleLifetime = particleLifetime + this.particleSprite = particleSprite + this.particleSpread = spread + this.frequency = frequency + } + + update(entity:Entity, deltaTime: number, state: State) : void { + + this.time += deltaTime + while (this.time > this.frequency) { + state.addEntity(new Particle( + entity.pos, + new SimpleSprite(this.particleSprite), + this.particleLifetime, + true, + Math.random() * Math.random() * this.particleSpread) + ) + this.time -= this.frequency + } + } +} \ No newline at end of file diff --git a/player.ts b/player.ts index fc5b66d..012d202 100644 --- a/player.ts +++ b/player.ts @@ -1,7 +1,5 @@ class Player extends Entity { - private isGrounded = false - private timeSinceLastRocket = 0 public velocity: Point = {x: 0, y:0} public airtime = 0 private rocketlauncher: Rocketlauncher @@ -12,78 +10,83 @@ class Player extends Entity { this.sprite.drawOrder = CONST.LAYER_PLAYER_FG this.sprite.renderPivot = {x:30, y:50} this.rocketlauncher = rocketlauncher - } - - update(deltaTime: number, state: State) : void { - // apply gravity - this.velocity.y += CONST.GRAVITY * deltaTime - - // apply input - let controlForce = deltaTime * ( this.isGrounded ? CONST.PLAYER_ACCEL_GROUND : CONST.PLAYER_ACCEL_AIR ) - if(Input.keyDown("KeyA") || Input.keyDown("ArrowLeft")) { - this.velocity.x -= controlForce - this.sprite.flipped = true - } - if(Input.keyDown("KeyD") || Input.keyDown("ArrowRight")) { - this.velocity.x += controlForce - this.sprite.flipped = false - } - // apply friction - let frictionFraction: number; - if (this.isGrounded) { - frictionFraction = Math.pow((1 - CONST.PLAYER_FRICTION_GROUND), deltaTime ) - } else { - frictionFraction = Math.pow((1 - CONST.PLAYER_FRICTION_AIR), deltaTime ) - } - this.velocity.x *= frictionFraction - this.velocity.y *= frictionFraction - - // apply velocity - this.pos.x += this.velocity.x * deltaTime - this.pos.y += this.velocity.y * deltaTime - - // ground collision checking - if (this.pos.y > CONST.PLAYER_GROUND_COLLISION) { - this.pos.y = CONST.PLAYER_GROUND_COLLISION; - if (!this.isGrounded) { - this.velocity.x *= CONST.PLAYER_GROUND_BOUNCE - this.velocity.y *= -CONST.PLAYER_GROUND_BOUNCE - } else { - this.velocity.y = 0 + this.behaviors.push(new class extends Behavior { + private isGrounded = false + private timeSinceLastRocket = 0 + + update(entity: Entity, deltaTime: number, state: State) : void { + // apply gravity + let player = entity as Player + player.velocity.y += CONST.GRAVITY * deltaTime + + // apply input + let controlForce = deltaTime * ( this.isGrounded ? CONST.PLAYER_ACCEL_GROUND : CONST.PLAYER_ACCEL_AIR ) + if(Input.keyDown("KeyA") || Input.keyDown("ArrowLeft")) { + player.velocity.x -= controlForce + player.sprite.flipped = true + } + if(Input.keyDown("KeyD") || Input.keyDown("ArrowRight")) { + player.velocity.x += controlForce + player.sprite.flipped = false + } + + // apply friction + let frictionFraction: number; + if (this.isGrounded) { + frictionFraction = Math.pow((1 - CONST.PLAYER_FRICTION_GROUND), deltaTime ) + } else { + frictionFraction = Math.pow((1 - CONST.PLAYER_FRICTION_AIR), deltaTime ) + } + player.velocity.x *= frictionFraction + player.velocity.y *= frictionFraction + + // apply velocity + player.pos.x += player.velocity.x * deltaTime + player.pos.y += player.velocity.y * deltaTime + + // ground collision checking + if (player.pos.y > CONST.PLAYER_GROUND_COLLISION) { + player.pos.y = CONST.PLAYER_GROUND_COLLISION; + if (!this.isGrounded) { + player.velocity.x *= CONST.PLAYER_GROUND_BOUNCE + player.velocity.y *= -CONST.PLAYER_GROUND_BOUNCE + } else { + player.velocity.y = 0 + } + this.isGrounded = true + player.airtime = 0 + } else { + this.isGrounded = false + player.airtime += deltaTime + } + + + // make sure the rocket launcher follows + player.rocketlauncher.pos.x = player.sprite.flipped ? player.pos.x + 8 : player.pos.x - 8 + player.rocketlauncher.pos.y = player.pos.y - CONST.ROCKET_START_HEIGHT + player.rocketlauncher.sprite.flipped = player.sprite.flipped + + // set direction of rocket launcher + let direction = { x: state.cursor.pos.x - player.rocketlauncher.pos.x, + y: state.cursor.pos.y - player.rocketlauncher.pos.y } + player.rocketlauncher.sprite.rotation = Math.atan(direction.y / direction.x) + if (direction.x > 0 === player.rocketlauncher.sprite.flipped) { + player.rocketlauncher.sprite.rotation += Math.PI + } + + // shooting + this.timeSinceLastRocket += deltaTime + if(Input.mouse.click && this.timeSinceLastRocket >= CONST.ROCKET_SHOOTING_COOLDOWN) { + this.timeSinceLastRocket = 0 + state.addEntity(new Rocket( + player.rocketlauncher.pos, + direction, + player.velocity + )) + } } - this.isGrounded = true - this.airtime = 0 - } else { - this.isGrounded = false - this.airtime += deltaTime - } - - - // make sure the rocket launcher follows - this.rocketlauncher.pos.x = this.sprite.flipped ? this.pos.x + 8 : this.pos.x - 8 - this.rocketlauncher.pos.y = this.pos.y - CONST.ROCKET_START_HEIGHT - this.rocketlauncher.sprite.flipped = this.sprite.flipped - - // set direction of rocket launcher - let direction = { x: state.cursor.pos.x - this.rocketlauncher.pos.x, - y: state.cursor.pos.y - this.rocketlauncher.pos.y } - this.rocketlauncher.sprite.rotation = Math.atan(direction.y / direction.x) - if (direction.x > 0 === this.rocketlauncher.sprite.flipped) { - this.rocketlauncher.sprite.rotation += Math.PI - } - - // shooting - this.timeSinceLastRocket += deltaTime - if(Input.mouse.click && this.timeSinceLastRocket >= CONST.ROCKET_SHOOTING_COOLDOWN) { - this.timeSinceLastRocket = 0 - state.addEntity(new Rocket( - this.rocketlauncher.pos, - direction, - this.velocity - )) - } - + }) } explosion(point: Point) { @@ -106,8 +109,8 @@ class Player extends Entity { } } -class Rocketlauncher extends Entity { +class Rocketlauncher extends Entity { constructor() { super( {x:0, y:0} ) @@ -115,7 +118,4 @@ class Rocketlauncher extends Entity { this.sprite.renderPivot = {x:30, y:8} this.sprite.drawOrder = CONST.LAYER_PLAYER_BG } - - update(deltaTime: number, state: State) : void { - } } \ No newline at end of file diff --git a/renderer.ts b/renderer.ts index d99765c..0b14143 100644 --- a/renderer.ts +++ b/renderer.ts @@ -41,6 +41,7 @@ class Renderer { } this.ctx.translate( -r.sprite.renderPivot.x, -r.sprite.renderPivot.y) + this.ctx.globalAlpha = r.sprite.alpha r.sprite.draw(this.ctx) this.ctx.restore() diff --git a/resources.ts b/resources.ts index 1861630..bbed64e 100644 --- a/resources.ts +++ b/resources.ts @@ -7,7 +7,9 @@ enum ImageResource { UFO = "img/ufo.png", CURSOR = "img/cursor.png", ROCKET = "img/rocket.png", - EXPLOSION = "img/explosion.png" + EXPLOSION = "img/explosion.png", + SPARK = "img/spark.png", + PUFF = "img/puff.png" } class Preloader { diff --git a/rocket.ts b/rocket.ts index 33dc3e4..498d1b6 100644 --- a/rocket.ts +++ b/rocket.ts @@ -1,5 +1,6 @@ class Rocket extends Entity { + static PARTICLE_SPRITE: Sprite constructor(pos: Point, direction: Point, initialVelocity: Point) { super(pos) @@ -7,8 +8,9 @@ class Rocket extends Entity { this.sprite = new SimpleSprite(ImageResource.ROCKET) this.sprite.renderPivot = {x: 19, y: 9} this.sprite.drawOrder = CONST.LAYER_MOBS - this.behaviors.push(new FlyForwardBehavior(this, pos, direction, initialVelocity)) + this.behaviors.push(new FlyForwardBehavior(this, direction, CONST.ROCKET_VELOCITY, initialVelocity)) this.behaviors.push(new ExplodeOnImpactBehavior) + this.behaviors.push(new ParticleEmitterBehavior(0.25, ImageResource.SPARK, 0.01, 3)) + this.behaviors.push(SelfDestructIfOffscreen) } - } \ No newline at end of file diff --git a/sprite.ts b/sprite.ts index 9afe2e2..a65d991 100644 --- a/sprite.ts +++ b/sprite.ts @@ -3,14 +3,16 @@ abstract class Sprite { public renderPivot: Point = { x:0, y:0 } public isLoaded: boolean; public flipped: boolean = false; + public alpha: number = 1.0 public drawOrder: number; + public image: CanvasImageSource; + public abstract draw(ctx: CanvasRenderingContext2D): void } class SimpleSprite extends Sprite { - private image: CanvasImageSource; public constructor(src: ImageResource) { super() @@ -32,9 +34,7 @@ class SimpleSprite extends Sprite { } class AnimatedSprite extends Sprite { - - private image: CanvasImageSource; private framesize: Point; private currentFrame = 0;