Skip to content

Commit

Permalink
Added character sprite with rotating launcher
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp Sehmisch committed Sep 20, 2020
1 parent 82dc30f commit 3c8e008
Show file tree
Hide file tree
Showing 16 changed files with 487 additions and 38 deletions.
Binary file modified build/img/player.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added build/img/rocketlauncher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
358 changes: 358 additions & 0 deletions build/img/ufo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 12 additions & 5 deletions camera.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
25 changes: 21 additions & 4 deletions constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
10 changes: 10 additions & 0 deletions credits.txt
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
7 changes: 5 additions & 2 deletions mob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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"))
Expand All @@ -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))
Expand All @@ -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
}))
Expand All @@ -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),
Expand Down
1 change: 1 addition & 0 deletions particle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
65 changes: 46 additions & 19 deletions player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,31 @@ 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

// 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
Expand Down Expand Up @@ -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
))
}

}

Expand All @@ -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 {
}
}
2 changes: 1 addition & 1 deletion renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions resources.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
1 change: 1 addition & 0 deletions rocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions sprite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
32 changes: 27 additions & 5 deletions state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

}

Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -64,4 +85,5 @@ class State {
})
}


}

0 comments on commit 3c8e008

Please sign in to comment.