UNPKG

littlejsengine

Version:

LittleJS - Tiny and Fast HTML5 Game Engine

309 lines (268 loc) 11 kB
/* LittleJS Platformer Example - Character - Platform style character - Handles animation and sound effects - Uses special platforming physics system - Characters can jump, shoot, roll, and throw grenades - Characters can climb ladders and break blocks - Characters also have a damage and can die - Has a weapon which can be fired */ 'use strict'; // import LittleJS module import * as LJS from '../../dist/littlejs.esm.js'; import * as GameObjects from './gameObjects.js'; import * as GameEffects from './gameEffects.js'; import * as GameLevel from './gameLevel.js'; import * as Game from './game.js'; const {vec2, hsl, Timer} = LJS; export class Character extends GameObjects.GameObject { constructor(pos) { super(pos, vec2(.6,.95)); this.lastPos = pos; this.groundTimer = new Timer; this.jumpTimer = new Timer; this.pressedJumpTimer = new Timer; this.dodgeTimer = new Timer; this.dodgeRechargeTimer = new Timer; this.deadTimer = new Timer; this.grenadeThrowTimer = new Timer; this.drawSize = vec2(1); this.moveInput = vec2(); this.color = hsl(LJS.rand(),1,.7); this.renderOrder = 10; this.walkCyclePercent = 0; this.health = 1; this.climbingLadder = false; this.setCollision(true,false); // attach weapon this.weapon = new GameObjects.Weapon(pos); this.weapon.renderOrder = this.renderOrder + 1; this.addChild(this.weapon, vec2(this.size.x,0)); } update() { this.gravityScale = 1; // reset to default gravity if (this.isDead()) return super.update(); // get move input const moveInput = this.moveInput.copy(); // jump if (!this.holdingJump) this.pressedJumpTimer.unset(); else if (!this.wasHoldingJump || this.climbingWall) this.pressedJumpTimer.set(.3); this.wasHoldingJump = this.holdingJump; // wall climb this.climbingWall = 0; if (moveInput.x && !this.velocity.x && this.velocity.y < 0) { this.velocity.y *=.8; this.climbingWall = 1; } if (this.dodgeTimer.active()) { // update roll this.angle = this.getMirrorSign()*2*LJS.PI*this.dodgeTimer.getPercent(); if (this.groundObject) this.velocity.x += this.getMirrorSign()*.1; } else { // not rolling this.angle = 0; if (this.pressedDodge && !this.dodgeRechargeTimer.active()) { // start dodge this.dodgeTimer.set(.4); this.dodgeRechargeTimer.set(2); this.jumpTimer.unset(); GameEffects.sound_dodge.play(this.pos); if (!this.groundObject && this.getAliveTime() > .2) this.velocity.y += .2; } if (this.pressingThrow && !this.wasPressingThrow && !this.grenadeThrowTimer.active()) { // throw grenade const grenade = new GameObjects.Grenade(this.pos); const speed = vec2(this.getMirrorSign(),LJS.rand(.8,.7)).normalize(.2+LJS.rand(.02)); grenade.velocity = this.velocity.add(speed); grenade.angleVelocity = this.getMirrorSign() * LJS.rand(.8,.5); GameEffects.sound_jump.play(this.pos); this.grenadeThrowTimer.set(1); } this.wasPressingThrow = this.pressingThrow; } // allow grabbing ladder at head or feet let touchingLadder = 0; for (let y=2;y--;) { const testPos = this.pos.add(vec2(0, y + .1*moveInput.y - this.size.y/2)); const collisionData = LJS.tileCollisionGetData(testPos); touchingLadder ||= collisionData == GameLevel.tileType_ladder; } if (!touchingLadder) this.climbingLadder = false; else if (moveInput.y) this.climbingLadder = true; if (this.weapon) // update weapon trigger this.weapon.triggerIsDown = this.holdingShoot && !this.dodgeTimer.active(); if (this.climbingLadder) { // update ladder this.gravityScale = this.climbingWall = this.groundObject = 0; this.jumpTimer.unset(); this.groundTimer.unset(); this.velocity = this.velocity.multiply(vec2(.85)).add(vec2(0,.02*moveInput.y)); // pull towards ladder const delta = (this.pos.x|0)+.5 - this.pos.x; this.velocity.x += .02*delta*LJS.abs(moveInput.x ? 0:moveInput.y); moveInput.x *= .2; // exit ladder if ground is below this.climbingLadder = moveInput.y >= 0 || LJS.tileCollisionGetData(this.pos.subtract(vec2(0,1))) <= 0; } else { // update jumping and ground check if (this.groundObject || this.climbingWall) { if (!this.groundTimer.isSet()) GameEffects.sound_walk.play(this.pos); // land sound this.groundTimer.set(.1); } if (this.groundTimer.active() && !this.dodgeTimer.active()) { // is on ground if (this.pressedJumpTimer.active() && !this.jumpTimer.active()) { // start jump if (this.climbingWall) this.velocity.y = .25; else { this.velocity.y = .15; this.jumpTimer.set(.2); } GameEffects.sound_jump.play(this.pos); } } if (this.jumpTimer.active() && !this.climbingWall) { // update variable height jump this.groundTimer.unset(); if (this.holdingJump && this.velocity.y > 0 && this.jumpTimer.active()) this.velocity.y += .017; } if (!this.groundObject) { // air control if (LJS.sign(moveInput.x) == LJS.sign(this.velocity.x)) moveInput.x *= .1; // moving with velocity else moveInput.x *= .2; // moving against velocity (stopping) // slight extra gravity when moving down if (this.velocity.y < 0) this.velocity.y += LJS.gravity.y*.2; } } // apply movement acceleration and clamp const maxCharacterSpeed = .2; this.velocity.x = LJS.clamp(this.velocity.x + moveInput.x * .04, -maxCharacterSpeed, maxCharacterSpeed); // track last pos for ladder collision code this.lastPos = this.pos.copy(); // call parent super.update(); // update walk cycle const speed = this.getSpeed(); if (this.climbingLadder || this.groundTimer.active() && !this.dodgeTimer.active()) { this.walkCyclePercent += speed * .5; this.walkCyclePercent = speed > .01 ? LJS.mod(this.walkCyclePercent) : 0; } else this.walkCyclePercent = 0; // update walk sound this.walkSoundTime += speed; if (speed > .001 && ((this.climbingLadder || this.groundTimer.active()) && !this.dodgeTimer.active())) { if (this.walkSoundTime > 1) { this.walkSoundTime = this.walkSoundTime%1; GameEffects.sound_walk.play(this.pos); } } else this.walkSoundTime = 0; // update mirror if (moveInput.x && !this.dodgeTimer.active()) this.mirror = moveInput.x < 0; } render() { // update animation const animationFrame = this.isDead() ? 0 : this.climbingLadder || this.groundTimer.active() ? 2*this.walkCyclePercent|0 : 1; this.tileInfo = Game.spriteAtlas.player.frame(animationFrame); let bodyPos = this.pos; if (!this.isDead()) { // bounce pos with walk cycle bodyPos = bodyPos.add(vec2(0,.05*LJS.sin(this.walkCyclePercent*LJS.PI))); // make bottom flush bodyPos = bodyPos.add(vec2(0,(this.drawSize.y-this.size.y)/2)); } LJS.drawTile(bodyPos, this.drawSize, this.tileInfo, this.color, this.angle, this.mirror); } damage(damage, damagingObject) { if (this.isDead() || this.getAliveTime() < 1 || this.dodgeTimer.active()) return 0; GameEffects.makeBlood(damagingObject ? damagingObject.pos : this.pos); return super.damage(damage, damagingObject); } kill(damagingObject) { if (this.isDead()) return; GameEffects.makeBlood(this.pos, 100); GameEffects.sound_die.play(this.pos); this.health = 0; this.deadTimer.set(); this.size = this.size.scale(.5); const fallDirection = damagingObject ? LJS.sign(damagingObject.velocity.x) : LJS.randSign(); this.angleVelocity = fallDirection*LJS.rand(.22,.14); this.angleDamping = .9; this.renderOrder = -1; // move to back layer if (this.weapon) this.weapon.destroy(); } collideWithTile(data, pos) { if (!data) return false; if (data == GameLevel.tileType_ladder) { // handle ladder collisions if (pos.y + 1 > this.lastPos.y - this.size.y/2) return false; if (LJS.tileCollisionGetData(pos.add(vec2(0,1))) // above && !LJS.tileCollisionGetData(pos.add(vec2(1,0))) // left && !LJS.tileCollisionGetData(pos.add(vec2(1,0)))) // right return false; // don't collide if something above it and nothing to left or right // allow standing on top of ladders return !this.climbingLadder; } const d = pos.y - this.pos.y; if (!this.climbingLadder && this.velocity.y > .1 && d > 0 && d < this.size.y/2) if (GameEffects.destroyTile(pos)) { // break blocks above this.velocity.y = 0; return false; } return true; } }