From 3c8e008f9edd5d9b80096a4e0d5ab87c55572b49 Mon Sep 17 00:00:00 2001 From: Philipp Sehmisch Date: Sun, 20 Sep 2020 22:29:36 +0200 Subject: [PATCH] Added character sprite with rotating launcher --- build/img/player.png | Bin 414 -> 4937 bytes build/img/rocketlauncher.png | Bin 0 -> 1851 bytes build/img/ufo.svg | 358 +++++++++++++++++++++++++++++++++++ camera.ts | 17 +- constants.ts | 25 ++- credits.txt | 10 + cursor.ts | 1 + entity.ts | 4 +- mob.ts | 7 +- particle.ts | 1 + player.ts | 65 +++++-- renderer.ts | 2 +- resources.ts | 1 + rocket.ts | 1 + sprite.ts | 1 + state.ts | 32 +++- 16 files changed, 487 insertions(+), 38 deletions(-) create mode 100644 build/img/rocketlauncher.png create mode 100644 build/img/ufo.svg diff --git a/build/img/player.png b/build/img/player.png index f2f78de61f1ade1fec1fab36ba45ab297b50b8f1..6d74772fdb910c5d47fc3ebba314848d67ea4e32 100644 GIT binary patch literal 4937 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 Hu0mjfq+&7) literal 414 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Y)RhkE(}*0E-+j?!aYX?D8gCb z5n0T@z%2~Ij105pNB{-dOFVsD*`II<3CjosFN|{p3Q3l@MwB?`=jNv7l`uFLr6!i7 zrYMwWmSiZnd-?{1H}Z)yFfg)tx;TbZ+SZT7zn^V7PeRKE7T)R{GIpK~wHoDcNz_b+dpwf%SJYA-7hTBg}V0#ZTjqFMc5b>*;t z8FmI{V8`)a>Pvl9{r&&-J*v8){YdCK-_jD|sHTf~p^1uSh-p0`-U6NgjE{jsT?k-X z8{R7c2N=?H@$+Ptk?tH!+GbiuXo5IYR7(-g#cJ7iWEe~JW8g1s;BiX`O%OiNW0qQ3 zRP}(v|pdz_)+EuzF>Nd5qCQz+#xdsK4{n*M{n%~U) zNhiz+OBe3*kM-HDIC}fM%cJ+D8`=Y@_{rfb*lTR8ToYNXz?tw!6ePPgbzvv8R_FjA z6&xC`@cC8ENqP29GnKZ~+wVrp7(I!)!XGS4j%W1RK>@jyDar|-pZ;r(U7Nbenyqbs z0A?!@AI#n|A+IA=h}O6GHVCC+M8{k$X;=bB6P4}5N70I3x z4m&q=H0@UamM-YA_1n(9I$te$oW-Lqf*fmg6!%fdrmrh zcT@NE@v-@ky`v5rp6(#kv*4$Dt;ridE3(>7@aJ=GQ#_SITXOMAu~2|Fz+PrDb=+ru zEabPJtIdkOhb)TukiwWp_f7S0FE^Q+3HbL+m5qH#6!6MfhoPr3*M$R^Sq;q;4CV~B z4Klm3%+QV$y&J4WwrW8#@j?Bji$#i6> zzK!4M)H4IM30t}l@%~HmfgW&0*Qg4Cu0Tks8s2zfm@%r6+QOaOC}sUMT0$HPz4#$B zz4*xKx;Mq>ewStIOf;i*bCO*;>z>MT`Zr|;)@Kn{k4k6CkCaq@^brSp*O@=c3>cZ4 z-#hk}TateK{Xp`IRwOL>Q6~^5La%;Fm}wm=YcO`ut84FBXVSmXV)p#P5f`|U!iSzC zBlSH^SI-T9DYHm;<+5aMG%QQWFI(lhqFvW$#9==n*Hivek(M&~g z(r5a=B`22Zt9nX%W^hZ&vN{&iJ@Z=0QGt3ESbSl)4VGLy@VYx2)DJzN8G_tElC%5E zb?rif4Lfs`FIANc)enL50=0A1qxPcvav>j5nv5<=$*=B&XgkDDDaB0K;4}cp^jkO| zMmS^jgJotl9{fg57%A}sBPm`Q-jPY^R&9?^F?_JED)RC$Ty!bG1_)clIMSL8`yGt`c7)Ol* zlIk?jgvQuA_3!W1f&?MVjoO_<;1wnDV>Uf1nI^&^OzQNKH>5t+Nd5Vcc9z$PDV|8exdUU#m zO~&7?km}JncfhTVUCrLa?aTKK{E{^!ya)SxW>d+Z~w-V;xaHnNeZ3brk-yA;< pd?t|=@9U;08MfF5^t8jt{{TTC%-8OE1wa4*002ovPDHLkV1iWsj<^5- literal 0 HcmV?d00001 diff --git a/build/img/ufo.svg b/build/img/ufo.svg new file mode 100644 index 0000000..ca35cc4 --- /dev/null +++ b/build/img/ufo.svg @@ -0,0 +1,358 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/camera.ts b/camera.ts index 2c4cc17..de73c57 100644 --- a/camera.ts +++ b/camera.ts @@ -1,15 +1,22 @@ class Camera extends Entity { - pos: Point = { x:0, y: 0} - private tracedEntity: Entity; + private player: Player; + private currentOffset: Point; - constructor(tracedEntity: Entity) { + constructor(player: Player) { super( {x:0, y:0}) - this.tracedEntity = tracedEntity; + this.player = player; + this.currentOffset = { x:0, y: 0 } } update(deltaTime: number, state: State) : void { - this.pos = { x: this.tracedEntity.pos.x, y: this.tracedEntity.pos.y} + let desiredOffset = { x: this.player.velocity.x * CONST.CAMERA_LEAD, + y: this.player.velocity.y * CONST.CAMERA_LEAD} + this.currentOffset.x += (desiredOffset.x - this.currentOffset.x) * deltaTime * CONST.CAMERA_AGILITY + this.currentOffset.y += (desiredOffset.y - this.currentOffset.y) * deltaTime * CONST.CAMERA_AGILITY + + this.pos.x = this.player.pos.x + this.currentOffset.x + this.pos.y = this.player.pos.y + this.currentOffset.y } screenPointToWorld(screenPos: Point) { diff --git a/constants.ts b/constants.ts index f1ddba2..05c6d34 100644 --- a/constants.ts +++ b/constants.ts @@ -4,20 +4,28 @@ const CONST = { SCREEN_WIDTH: 1024, SCREEN_HEIGHT: 768, + + // physics sttings GRAVITY: 1000, // Gravity in pixels per secondĀ² EXPLOSION_FORCE: 7000000, // impulse caused by rocket explosions 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: -32, //distance in pixels between player center of mass and feet + PLAYER_GROUND_COLLISION: -46, //distance in pixels between player center of mass and feet PLAYER_ACCEL_GROUND: 2000, // 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 PLAYER_GROUND_BOUNCE: 0.75, // how much the player bounces when impacting the ground ROCKET_VELOCITY: 1000, // speed of rockets in pixels per second - ROCKET_START_HEIGHT: 0, // hight at the player sprite where rockets are spawned + ROCKET_START_HEIGHT: 25, // hight at the player sprite where rockets are spawned ROCKET_SHOOTING_COOLDOWN: 0.5, // time in seconds between shooting rockets EXPLOSION_FX_LIFETIME: 0.5, // lifetime of explosions in seconds + + // camera settings + CAMERA_LEAD: 0.25, // distance the camera leads ahead in direction of player trajectory + CAMERA_AGILITY: 1.5, // how fast the camera reacts to changes in direction + + // mob properties MOB_BALLOON_RADIUS: 48, // radius of the collision hitcircle MOB_BIRD_RADIUS: 45, // radius of the collision hitcircle MOB_BIRD_ANIMATION_LENGHT: 0.2, // time for one animation cycle of the birds @@ -26,9 +34,18 @@ const CONST = { MOB_UFO_RADIUS: 55, // radius of the collision hitcircle MOB_UFO_ANIMATION_LENGHT: 0.2, // time for one animation cycle of the birds - // now it gets techical + // render order layers + LAYER_BG: -1, + LAYER_MOBS: 0, + LAYER_PLAYER_BG: 1, + LAYER_PLAYER_FG: 2, + LAYER_FX: 3, + LAYER_UI: 4, + LAYER_XHAIR: 5, + + // now it gets technical DEBUG_SHOW_COLLIDERS: false, CHUNK_WIDTH: 500, // width of an area where objects spawn / despawn - CHUNK_NUMBER: 3, // number of chunks + CHUNK_NUMBER: 3, // number of chunks in each direction METER: 40 // pixels per meter } \ No newline at end of file diff --git a/credits.txt b/credits.txt index e69de29..5acfe2f 100644 --- a/credits.txt +++ b/credits.txt @@ -0,0 +1,10 @@ +Explosion: + CC-0 + https://opengameart.org/content/explosion-7 +Bird: + CC-0 + https://opengameart.org/content/bird-cute-bird +Font + Orbitron + Open Font License + https://fonts.google.com/specimen/Orbitron?category=Sans+Serif&vfonly=true#about diff --git a/cursor.ts b/cursor.ts index cc03296..22d1bcb 100644 --- a/cursor.ts +++ b/cursor.ts @@ -4,6 +4,7 @@ class Cursor extends Entity { super( {x:0, y: 0}) this.sprite = new SimpleSprite(ImageResource.CURSOR) this.sprite.renderPivot = {x:7, y:7} + this.sprite.drawOrder = CONST.LAYER_XHAIR } update(deltaTime: number, state: State) : void { diff --git a/entity.ts b/entity.ts index a8d76c1..5205a41 100644 --- a/entity.ts +++ b/entity.ts @@ -125,7 +125,7 @@ class ExplodeOnImpactBehavior extends Behavior { // explode on enemy contact // There might be some optimization potential here. But this is a game jam, so who cares about performance :) - state.entities.forEach(other => { + state.doWithAllEntities(other => { if (other.collisionRadiusSqare > 0) { let distX = entity.pos.x - other.pos.x let distY = entity.pos.y - other.pos.y @@ -146,7 +146,7 @@ class ExplodeOnImpactBehavior extends Behavior { explosionSprite.renderPivot = {x:100, y:200} explosionSprite.rotation = entity.sprite.rotation - Math.PI / 2 - state.entities.push(new Particle(entity.pos, + state.addEntity(new Particle(entity.pos, explosionSprite, CONST.EXPLOSION_FX_LIFETIME)); state.player.explosion(entity.pos) diff --git a/mob.ts b/mob.ts index 404516a..55abe34 100644 --- a/mob.ts +++ b/mob.ts @@ -7,6 +7,7 @@ class Balloon extends Entity { super( pos ) this.sprite = new SimpleSprite(ImageResource.BALLOON) this.sprite.renderPivot = { x: CONST.MOB_BALLOON_RADIUS, y: CONST.MOB_BALLOON_RADIUS} + this.sprite.drawOrder = CONST.LAYER_MOBS this.collisionRadiusSqare = CONST.MOB_BALLOON_RADIUS * CONST.MOB_BALLOON_RADIUS this.behaviors.push(new SineMovementBehavior(this, 10, 2.0, "y")) @@ -20,6 +21,7 @@ class Bird extends Entity { super( pos ) this.sprite = new AnimatedSprite(ImageResource.BIRD, { x: 182, y:117 }) this.sprite.renderPivot = { x: 77, y: 77} + this.sprite.drawOrder = CONST.LAYER_MOBS this.collisionRadiusSqare = CONST.MOB_BIRD_RADIUS * CONST.MOB_BIRD_RADIUS this.behaviors.push(new SineMovementBehavior(this, CONST.MOB_BIRD_MOVE_RANGE, CONST.MOB_BIRD_MOVE_TIME, "x")) @@ -33,6 +35,7 @@ class Ufo extends Entity { super( pos ) this.sprite = new AnimatedSprite(ImageResource.UFO, { x: 131, y:75 }) this.sprite.renderPivot = { x: 65, y: 40} + this.sprite.drawOrder = CONST.LAYER_MOBS this.collisionRadiusSqare = CONST.MOB_UFO_RADIUS * CONST.MOB_UFO_RADIUS this.behaviors.push(new AnimatedBehavior(CONST.MOB_UFO_ANIMATION_LENGHT)) @@ -49,7 +52,7 @@ class MobLayer { create: (pos:Point) => Entity spawn(state:State, start:number) { for (let i = 0; i < this.count; i++) { - state.entities.push(this.create({ + state.addEntity(this.create({ x: Math.random() * CONST.CHUNK_WIDTH + start, y: Math.pow(Math.random(), 2) * -this.range - this.from })) @@ -65,7 +68,7 @@ class MobLayer { // these are the height levels at which the mobs spawn const MOB_LAYERS = [ - new MobLayer((pos) => new Balloon(pos), 5 * CONST.METER, 10 * CONST.METER, 1), + new MobLayer((pos) => new Balloon(pos), 5 * CONST.METER, 15 * CONST.METER, 1), new MobLayer((pos) => new Balloon(pos), 5 * CONST.METER, 100 * CONST.METER, 10), new MobLayer((pos) => new Balloon(pos), 5 * CONST.METER, 300 * CONST.METER, 10), new MobLayer((pos) => new Bird(pos), 100 * CONST.METER, 20 * CONST.METER, 5), diff --git a/particle.ts b/particle.ts index cfff42d..e91b085 100644 --- a/particle.ts +++ b/particle.ts @@ -4,6 +4,7 @@ class Particle extends Entity { constructor(pos: Point, sprite: Sprite, lifetime: number) { super(pos) this.sprite = sprite + this.sprite.drawOrder = CONST.LAYER_FX this.behaviors.push(new SelfDestructBehavior(lifetime)) if ("setProgress" in this.sprite) { diff --git a/player.ts b/player.ts index 2f68b1c..fc5b66d 100644 --- a/player.ts +++ b/player.ts @@ -2,31 +2,19 @@ class Player extends Entity { private isGrounded = false private timeSinceLastRocket = 0 - private velocity: Point = {x: 0, y:0} - public airtime = 0; + public velocity: Point = {x: 0, y:0} + public airtime = 0 + private rocketlauncher: Rocketlauncher - constructor() { + constructor(rocketlauncher: Rocketlauncher) { super( { x: 0, y: -500} ) this.sprite = new SimpleSprite(ImageResource.PLAYER) - this.sprite.renderPivot = {x:32, y:32} + this.sprite.drawOrder = CONST.LAYER_PLAYER_FG + this.sprite.renderPivot = {x:30, y:50} + this.rocketlauncher = rocketlauncher } update(deltaTime: number, state: State) : void { - - // shooting - this.timeSinceLastRocket += deltaTime - if(Input.mouse.click && this.timeSinceLastRocket >= CONST.ROCKET_SHOOTING_COOLDOWN) { - this.timeSinceLastRocket = 0 - let startPos = { x: this.pos.x, - y: this.pos.y - CONST.ROCKET_START_HEIGHT } - state.entities.push(new Rocket( - startPos, - { x: state.cursor.pos.x - startPos.x, - y: state.cursor.pos.y - startPos.y }, - this.velocity - )) - } - // apply gravity this.velocity.y += CONST.GRAVITY * deltaTime @@ -34,9 +22,11 @@ class Player extends Entity { 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 @@ -70,6 +60,29 @@ class Player extends Entity { } + // 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 + )) + } } @@ -91,4 +104,18 @@ class Player extends Entity { } } +} + +class Rocketlauncher extends Entity { + + + constructor() { + super( {x:0, y:0} ) + this.sprite = new SimpleSprite(ImageResource.ROCKETLAUNCHER) + 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 f4813b3..d99765c 100644 --- a/renderer.ts +++ b/renderer.ts @@ -26,7 +26,7 @@ class Renderer { this.ctx.fillRect(0, offset.y, CONST.SCREEN_WIDTH, CONST.SCREEN_HEIGHT) // objects - state.entities.forEach(r => { + state.doWithAllEntities(r => { if (r.sprite && r.sprite.isLoaded) { this.ctx.save() this.ctx.translate( Math.round(r.pos.x + offset.x), diff --git a/resources.ts b/resources.ts index 37b225c..1861630 100644 --- a/resources.ts +++ b/resources.ts @@ -1,6 +1,7 @@ // handler for resources enum ImageResource { PLAYER = "img/player.png", + ROCKETLAUNCHER = "img/rocketlauncher.png", BALLOON = "img/balloon.png", BIRD = "img/bird.png", UFO = "img/ufo.png", diff --git a/rocket.ts b/rocket.ts index b3d1413..33dc3e4 100644 --- a/rocket.ts +++ b/rocket.ts @@ -6,6 +6,7 @@ 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 ExplodeOnImpactBehavior) } diff --git a/sprite.ts b/sprite.ts index 35607ca..9afe2e2 100644 --- a/sprite.ts +++ b/sprite.ts @@ -3,6 +3,7 @@ abstract class Sprite { public renderPivot: Point = { x:0, y:0 } public isLoaded: boolean; public flipped: boolean = false; + public drawOrder: number; public abstract draw(ctx: CanvasRenderingContext2D): void } diff --git a/state.ts b/state.ts index 9d13ee1..476cfc2 100644 --- a/state.ts +++ b/state.ts @@ -2,26 +2,30 @@ class State { public camera: Camera - public entities: Entity[] + private entities: Entity[] public cursor: Cursor public player: Player private lastChunkGenX = 0 + private entitySortRequired = false; constructor() { this.entities = Array() - this.player = new Player() - this.entities.push(this.player) + let rocketlauncher = new Rocketlauncher() + this.addEntity(rocketlauncher) + this.player = new Player(rocketlauncher) + this.addEntity(this.player) this.camera = new Camera(this.player) - this.entities.push(this.camera) + this.addEntity(this.camera) this.cursor = new Cursor() - this.entities.push(this.cursor) + this.addEntity(this.cursor) for (var i = 0; i < CONST.CHUNK_NUMBER * CONST.CHUNK_WIDTH; i+= CONST.CHUNK_WIDTH) { this.generateChunk(i) this.generateChunk(-i - CONST.CHUNK_WIDTH) } + this.entitySortRequired = true; } @@ -32,6 +36,15 @@ class State { } } + addEntity(entity: Entity) { + this.entities.push(entity) + this.entitySortRequired = true + } + + doWithAllEntities(callbackfn: (value: Entity, index: number, array: Entity[]) => void, thisArg?: any) { + this.entities.forEach(callbackfn) + } + update(deltaTime: number) { // check for chunk regeneration @@ -56,6 +69,14 @@ class State { } this.entities.forEach(e => e.update(deltaTime, this)) + + if (this.entitySortRequired){ + this.entities = this.entities.sort((a:Entity, b:Entity) => { + if (a.sprite === undefined || b.sprite === undefined) return 0; + return a.sprite.drawOrder - b.sprite.drawOrder + }); + this.entitySortRequired = false; + } } private generateChunk(start: number) { @@ -64,4 +85,5 @@ class State { }) } + } \ No newline at end of file