From d04cfc425f234885e150e7beda085a9affa7ee13 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Fri, 10 Jan 2020 20:46:14 +0200 Subject: [PATCH] simple player and collisions --- src/debug.js | 9 ++ src/index.js | 33 ++----- src/input.js | 1 + src/player.js | 226 ++++++++++++------------------------------------ src/tiles.js | 90 ++++++++++++++++++- src/viewport.js | 15 ++++ 6 files changed, 171 insertions(+), 203 deletions(-) diff --git a/src/debug.js b/src/debug.js index e2db9cb..5e0124f 100644 --- a/src/debug.js +++ b/src/debug.js @@ -110,6 +110,15 @@ class Debugging { ctx.closePath() ctx.stroke() + // Draw colliders + ctx.fillStyle = '#00aaff' + let collider = chunk.getLayer('col') + for (let i in collider.tiles) { + let p = collider.toXY(parseInt(i)) + if (collider.tiles[i] === 0) continue + ctx.fillRect(p.x * chunk.tile, p.y * chunk.tile, chunk.tile, chunk.tile) + } + // Chunk index ctx.fillStyle = '#fff' ctx.fillText(chunk.x + ';' + chunk.y, 5, 16) diff --git a/src/index.js b/src/index.js index abb7d8b..cb3ebcf 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ import { canvas, ctx } from './canvas' import { TileMap, World } from './tiles' import { HeightMap } from './heightmap' import Debug from './debug' +import Player from './player' import Input from './input' import Viewport from './viewport' import RES from './resource' @@ -12,13 +13,14 @@ let frameTime = 0 let frameCount = 0 let fps = 0 -let vp = new Viewport(1111, 900) +let vp = new Viewport(0, 0) +let p = new Player(800, 1200, 32, 64) let height = new HeightMap(0, 32, 16, 0) let map = new TileMap('assets/ground.png', 32) const chunkSize = 32 -const tileSize = 12 +const tileSize = 16 // Define dirt tiles map.define({ @@ -63,30 +65,8 @@ let world = new World(height, { GROUND: map }, chunkSize, tileSize, 32, 64) function update (dt) { world.update(dt, vp) - if (Input.isDown('w')) { - vp.y -= 15 - } else if (Input.isDown('s')) { - vp.y += 15 - } - - if (Input.isDown('a')) { - vp.x -= 15 - } else if (Input.isDown('d')) { - vp.x += 15 - } - - let full = world.chunkSize * world.tileSize - if (vp.x < 0) { - vp.x = 0 - } else if (vp.x + vp.width > world.width * full) { - vp.x = (full * world.width) - vp.width - } - - if (vp.y < 0) { - vp.y = 0 - } else if (vp.y + vp.height > world.height * full) { - vp.y = (full * world.height) - vp.height - } + p.update(dt, vp, world) + vp.update(dt, world) if (Input.mouse['btn0']) { let mpin = world.pickMouse(vp, Input.mouse.pos) @@ -103,6 +83,7 @@ function update (dt) { function draw () { world.draw(vp) + p.draw(vp) Debug.draw(vp, world, fps) } diff --git a/src/input.js b/src/input.js index 79d2d7f..cac4c94 100644 --- a/src/input.js +++ b/src/input.js @@ -10,6 +10,7 @@ const specialKeyMap = { 'pausebreak': 19, 'capslock': 20, 'escape': 27, + 'space': 32, 'pgup': 33, 'pgdown': 34, 'end': 35, diff --git a/src/player.js b/src/player.js index 4157654..4fea33b 100644 --- a/src/player.js +++ b/src/player.js @@ -1,189 +1,69 @@ import { ctx } from './canvas' -import { GameObject } from './level' -import { deg2rad, rad2vec, intersectRect } from './utils' -import RES from './resource' +import Input from './input' -const FULL_ROTATION = -85 -const FULL_ROTATION_EDGE = -FULL_ROTATION - -class Hook extends GameObject { - constructor (player, x, y, w, h, len) { - super(x, y, w, h) - this.player = player - - // Return position - this.rx = x - this.ry = y - - // Hook rotation - // Hook rotation direction - this.r = 0 - this.rd = 1 - - // Distance from center - // Moving direction - this.d = 0 - this.md = -1 - - // Travel length - this.len = len - - // Attached object - this.obj = null - } - - draw () { - if (this.md !== -1) { - // Draw line - ctx.beginPath() - ctx.moveTo(ctx.oX + this.rx, ctx.oY + this.ry) - ctx.lineTo(ctx.oX + this.x, ctx.oY + this.y) - ctx.stroke() - ctx.closePath() - } - - let hookr = deg2rad(360 - this.r) - ctx.save() - ctx.translate(ctx.oX + this.x, ctx.oY + this.y) - ctx.rotate(hookr) - ctx.drawImage(RES.loadImage('static/hook_open.png', true), -this.w / 2, -this.h / 2, this.w, this.h) - ctx.restore() - } - - clear () { - this.obj = null - this.d = 0 - this.md = -1 - this.x = this.rx - this.y = this.ry - } - - update (level) { - if (this.d === 0 && this.r < FULL_ROTATION_EDGE && this.rd === 1) { - this.r += 1 - } - - if (this.d === 0 && this.r > FULL_ROTATION && this.rd === 0) { - this.r -= 1 - } - - if (this.r >= FULL_ROTATION_EDGE && this.rd === 1) { - this.r = FULL_ROTATION_EDGE - this.rd = 0 - } - - if (this.r <= FULL_ROTATION && this.rd === 0) { - this.r = FULL_ROTATION - this.rd = 1 - } - - if (ctx.mouse.down['btn0'] && this.d === 0 && !this.obj) { - this.d = 0 - this.md = 1 - } - - if (this.md > -1) { - if (this.d > this.len && this.md === 1) { - this.md = 0 - } - - if (this.d <= 2 && this.md === 0) { - this.d = 0 - this.md = -1 - this.x = this.rx - this.y = this.ry - - // Score - if (this.obj) { - this.player.score(this.obj) - this.obj.destroy() - this.obj = null - } - return - } - - let dir = rad2vec(deg2rad(90 - this.r)) - dir.x *= this.d - dir.y *= this.d - - this.x = this.rx + dir.x - this.y = this.ry + dir.y - - if (this.obj) { - this.obj.x = this.x - this.obj.w / 2 - this.obj.y = this.y - } - - // Detect intersection - if (this.md === 1) { - if (!this.obj) { - let firstIntersect - for (let i in level.objects) { - let obj = level.objects[i] - if (obj.physical && intersectRect(obj, this)) { - firstIntersect = obj - break - } - } - - if (firstIntersect) { - if (firstIntersect.explode) { - let obj = firstIntersect.explode(level) - this.obj = obj - this.md = 0 - return - } - - this.obj = firstIntersect - this.md = 0 - return - } - } - - if (this.player.superStrength) { - this.d += 10 - } else { - this.d += 5 - } - } else { - if (this.player.superStrength) { - this.d -= 10 - } else { - this.d -= this.obj ? 5 * (1 - this.obj.weight) : 10 - } - } - } - } -} - -export class Player extends GameObject { - constructor (x, y, mh) { - super(x, y, 60, 55, '#4d4b4f') +class Player { + constructor (x, y, w, h) { this.x = x this.y = y - this.hook = new Hook(this, this.x + this.w / 2, this.y + this.h, 20, 20, mh) - this.hook.r = FULL_ROTATION + this.width = w + this.height = h - this.superStrength = false + this.mX = 0 + this.mY = 0 + + this.grounded = false + + this.speed = 5 + this.gravity = 1 + this.jumpPower = 20 } - score (object) { - console.log('Scored', object) + moveAndSlide (collider) { + // y collision + let oldY = this.y + this.y += this.mY + if (oldY !== this.y && collider.collide(this)) { + if (this.y > oldY) this.grounded = true + this.y = oldY + this.mY = 0 + } else { + this.grounded = false + } + + // x collision + let oldX = this.x + this.x += this.mX + if (oldX !== this.x && collider.collide(this)) { + this.x = oldX + this.mX = 0 + } } - draw () { - // Draw player - super.draw() + update (dt, vp, world) { + this.mY += this.gravity + if (Input.isDown('a')) { + this.mX = -this.speed + } else if (Input.isDown('d')) { + this.mX = this.speed + } else { + this.mX = 0 + } - // Draw hook - this.hook.draw() + if (this.grounded && (Input.isDown('w') || Input.isDown('space'))) { + this.mY = -this.jumpPower + } - // + this.moveAndSlide(world) + + vp.x = parseInt(this.x - vp.width / 2) + vp.y = parseInt(this.y - vp.height / 2) } - update (level) { - if (!level) return - this.hook.update(level) + draw (vp) { + ctx.fillStyle = '#f00' + ctx.fillRect(this.x - vp.x, this.y - vp.y, this.width, this.height) } } + +export default Player diff --git a/src/tiles.js b/src/tiles.js index 3b351ad..074a35e 100644 --- a/src/tiles.js +++ b/src/tiles.js @@ -51,9 +51,8 @@ class TileMap { } class TileLayer { - constructor (name, collider = false, size = 16, tileSize = 16) { + constructor (name, size = 16, tileSize = 16) { this.name = name - this.collide = collider this.size = size this.tile = tileSize this.tiles = [] @@ -99,6 +98,61 @@ class TileLayer { } } +class TilePhysicsLayer extends TileLayer { + constructor (size = 16, tileSize = 16) { + super('col', size, tileSize) + } + + draw () {} + + update (dt) {} + + generateFromTiles (tiles) { + this.tiles = [] + for (let i in tiles.tiles) { + let t = tiles.tiles[i] + let p = tiles.toXY(parseInt(i)) + if (t === -1) { + this.tiles[i] = 0 + continue + } + + // If this tile has neighbors that are air but its not itself air, it has a collider + let l = tiles.tileAtXY(p.x - 1, p.y) + let r = tiles.tileAtXY(p.x + 1, p.y) + let u = tiles.tileAtXY(p.x, p.y - 1) + let d = tiles.tileAtXY(p.x, p.y + 1) + if ((l == null || l !== -1) && (r == null || r !== -1) && + (u == null || u !== -1) && (d == null || d !== -1)) { + this.tiles[i] = 0 + continue + } + + this.tiles[i] = 1 + } + } + + collide (chunk, obj) { + let absPos = chunk.absPos + for (let i in this.tiles) { + let t = this.tiles[i] + if (t === 0) continue + let p = this.toXY(parseInt(i)) + let minX = p.x * chunk.tile + absPos.x + let minY = p.y * chunk.tile + absPos.y + let maxX = minX + chunk.tile + let maxY = minY + chunk.tile + + // Intersection check + if (minX > obj.x + obj.width || maxX < obj.x || + minY > obj.y + obj.height || maxY < obj.y) continue + + return { chunk, tile: i } + } + return false + } +} + class Chunk { constructor (ix, iy, size = 16, tileSize = 16) { this.x = ix @@ -114,7 +168,9 @@ class Chunk { generateMap (tileMap, heightMap) { this.layers = [] - let fgLayer = new TileLayer('fg', true, this.size, this.tile) + let bgLayer = new TileLayer('bg', this.size, this.tile) + let fgLayer = new TileLayer('fg', this.size, this.tile) + let clLayer = new TilePhysicsLayer(this.size, this.tile) for (let i = 0; i < this.size * this.size; i++) { let tileCoords = fgLayer.toXY(i) let tileAbs = this.toAbs(tileCoords) @@ -137,7 +193,11 @@ class Chunk { } fgLayer.tiles.push(tileMap.indexOf('STONE')) } + + clLayer.generateFromTiles(fgLayer) + this.layers.push(bgLayer) this.layers.push(fgLayer) + this.layers.push(clLayer) this.dirty = true } @@ -201,6 +261,10 @@ class Chunk { // Create cached image this.img = cacheFactory.capture() + // Update collision + let cl = this.getLayer('col') + if (cl) cl.generateFromTiles(this.getLayer('fg')) + // Don't update again next tick this.dirty = false this._updated = true @@ -212,6 +276,12 @@ class Chunk { this.layers[i].update(dt) } } + + collide (obj) { + let cl = this.getLayer('col') + if (!cl) return null + return cl.collide(this, obj) + } } class World { @@ -231,6 +301,7 @@ class World { this._unloadTick = 0 this._lastDrawCount = 0 this._lastUpdateCount = 0 + this._collide = [] } getChunk (x, y) { @@ -242,6 +313,7 @@ class World { } update (dt, vp) { + this._collide = [] let posPoint = vp.chunkIn(this.chunkSize * this.tileSize) for (let x = posPoint.x - 4; x < posPoint.x + 5; x++) { for (let y = posPoint.y - 4; y < posPoint.y + 5; y++) { @@ -251,8 +323,9 @@ class World { let n = new Chunk(x, y, this.chunkSize, this.tileSize) n.generateMap(this.tileMaps.GROUND, this.heightMap) this.chunks.push(n) - break + continue } + this._collide.push(exists) } } @@ -303,6 +376,15 @@ class World { this._lastDrawCount++ } } + + collide (obj) { + if (!this._collide.length) return null + for (let i in this._collide) { + let c = this._collide[i] + let collide = c.collide(obj) + if (collide) return collide + } + } } export { TileMap, Chunk, World } diff --git a/src/viewport.js b/src/viewport.js index c64fa0f..4d67589 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -23,6 +23,21 @@ class Viewport { let adj = this.adjustCentered return { x: Math.floor(adj.x / size), y: Math.floor(adj.y / size) } } + + update (dt, world) { + let full = world.chunkSize * world.tileSize + if (this.x < 0) { + this.x = 0 + } else if (this.x + this.width > world.width * full) { + this.x = (full * world.width) - this.width + } + + if (this.y < 0) { + this.y = 0 + } else if (this.y + this.height > world.height * full) { + this.y = (full * world.height) - this.height + } + } } export default Viewport