simple player and collisions

This commit is contained in:
Evert Prants 2020-01-10 20:46:14 +02:00
parent 4eed971735
commit d04cfc425f
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
6 changed files with 171 additions and 203 deletions

View File

@ -110,6 +110,15 @@ class Debugging {
ctx.closePath() ctx.closePath()
ctx.stroke() 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 // Chunk index
ctx.fillStyle = '#fff' ctx.fillStyle = '#fff'
ctx.fillText(chunk.x + ';' + chunk.y, 5, 16) ctx.fillText(chunk.x + ';' + chunk.y, 5, 16)

View File

@ -3,6 +3,7 @@ import { canvas, ctx } from './canvas'
import { TileMap, World } from './tiles' import { TileMap, World } from './tiles'
import { HeightMap } from './heightmap' import { HeightMap } from './heightmap'
import Debug from './debug' import Debug from './debug'
import Player from './player'
import Input from './input' import Input from './input'
import Viewport from './viewport' import Viewport from './viewport'
import RES from './resource' import RES from './resource'
@ -12,13 +13,14 @@ let frameTime = 0
let frameCount = 0 let frameCount = 0
let fps = 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 height = new HeightMap(0, 32, 16, 0)
let map = new TileMap('assets/ground.png', 32) let map = new TileMap('assets/ground.png', 32)
const chunkSize = 32 const chunkSize = 32
const tileSize = 12 const tileSize = 16
// Define dirt tiles // Define dirt tiles
map.define({ map.define({
@ -63,30 +65,8 @@ let world = new World(height, { GROUND: map }, chunkSize, tileSize, 32, 64)
function update (dt) { function update (dt) {
world.update(dt, vp) world.update(dt, vp)
if (Input.isDown('w')) { p.update(dt, vp, world)
vp.y -= 15 vp.update(dt, world)
} 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
}
if (Input.mouse['btn0']) { if (Input.mouse['btn0']) {
let mpin = world.pickMouse(vp, Input.mouse.pos) let mpin = world.pickMouse(vp, Input.mouse.pos)
@ -103,6 +83,7 @@ function update (dt) {
function draw () { function draw () {
world.draw(vp) world.draw(vp)
p.draw(vp)
Debug.draw(vp, world, fps) Debug.draw(vp, world, fps)
} }

View File

@ -10,6 +10,7 @@ const specialKeyMap = {
'pausebreak': 19, 'pausebreak': 19,
'capslock': 20, 'capslock': 20,
'escape': 27, 'escape': 27,
'space': 32,
'pgup': 33, 'pgup': 33,
'pgdown': 34, 'pgdown': 34,
'end': 35, 'end': 35,

View File

@ -1,189 +1,69 @@
import { ctx } from './canvas' import { ctx } from './canvas'
import { GameObject } from './level' import Input from './input'
import { deg2rad, rad2vec, intersectRect } from './utils'
import RES from './resource'
const FULL_ROTATION = -85 class Player {
const FULL_ROTATION_EDGE = -FULL_ROTATION constructor (x, y, w, h) {
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')
this.x = x this.x = x
this.y = y this.y = y
this.hook = new Hook(this, this.x + this.w / 2, this.y + this.h, 20, 20, mh) this.width = w
this.hook.r = FULL_ROTATION 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) { moveAndSlide (collider) {
console.log('Scored', object) // 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
} }
draw () { // x collision
// Draw player let oldX = this.x
super.draw() this.x += this.mX
if (oldX !== this.x && collider.collide(this)) {
// Draw hook this.x = oldX
this.hook.draw() this.mX = 0
}
//
} }
update (level) { update (dt, vp, world) {
if (!level) return this.mY += this.gravity
this.hook.update(level) if (Input.isDown('a')) {
this.mX = -this.speed
} else if (Input.isDown('d')) {
this.mX = this.speed
} else {
this.mX = 0
}
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)
}
draw (vp) {
ctx.fillStyle = '#f00'
ctx.fillRect(this.x - vp.x, this.y - vp.y, this.width, this.height)
} }
} }
export default Player

View File

@ -51,9 +51,8 @@ class TileMap {
} }
class TileLayer { class TileLayer {
constructor (name, collider = false, size = 16, tileSize = 16) { constructor (name, size = 16, tileSize = 16) {
this.name = name this.name = name
this.collide = collider
this.size = size this.size = size
this.tile = tileSize this.tile = tileSize
this.tiles = [] 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 { class Chunk {
constructor (ix, iy, size = 16, tileSize = 16) { constructor (ix, iy, size = 16, tileSize = 16) {
this.x = ix this.x = ix
@ -114,7 +168,9 @@ class Chunk {
generateMap (tileMap, heightMap) { generateMap (tileMap, heightMap) {
this.layers = [] 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++) { for (let i = 0; i < this.size * this.size; i++) {
let tileCoords = fgLayer.toXY(i) let tileCoords = fgLayer.toXY(i)
let tileAbs = this.toAbs(tileCoords) let tileAbs = this.toAbs(tileCoords)
@ -137,7 +193,11 @@ class Chunk {
} }
fgLayer.tiles.push(tileMap.indexOf('STONE')) fgLayer.tiles.push(tileMap.indexOf('STONE'))
} }
clLayer.generateFromTiles(fgLayer)
this.layers.push(bgLayer)
this.layers.push(fgLayer) this.layers.push(fgLayer)
this.layers.push(clLayer)
this.dirty = true this.dirty = true
} }
@ -201,6 +261,10 @@ class Chunk {
// Create cached image // Create cached image
this.img = cacheFactory.capture() this.img = cacheFactory.capture()
// Update collision
let cl = this.getLayer('col')
if (cl) cl.generateFromTiles(this.getLayer('fg'))
// Don't update again next tick // Don't update again next tick
this.dirty = false this.dirty = false
this._updated = true this._updated = true
@ -212,6 +276,12 @@ class Chunk {
this.layers[i].update(dt) this.layers[i].update(dt)
} }
} }
collide (obj) {
let cl = this.getLayer('col')
if (!cl) return null
return cl.collide(this, obj)
}
} }
class World { class World {
@ -231,6 +301,7 @@ class World {
this._unloadTick = 0 this._unloadTick = 0
this._lastDrawCount = 0 this._lastDrawCount = 0
this._lastUpdateCount = 0 this._lastUpdateCount = 0
this._collide = []
} }
getChunk (x, y) { getChunk (x, y) {
@ -242,6 +313,7 @@ class World {
} }
update (dt, vp) { update (dt, vp) {
this._collide = []
let posPoint = vp.chunkIn(this.chunkSize * this.tileSize) let posPoint = vp.chunkIn(this.chunkSize * this.tileSize)
for (let x = posPoint.x - 4; x < posPoint.x + 5; x++) { for (let x = posPoint.x - 4; x < posPoint.x + 5; x++) {
for (let y = posPoint.y - 4; y < posPoint.y + 5; y++) { 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) let n = new Chunk(x, y, this.chunkSize, this.tileSize)
n.generateMap(this.tileMaps.GROUND, this.heightMap) n.generateMap(this.tileMaps.GROUND, this.heightMap)
this.chunks.push(n) this.chunks.push(n)
break continue
} }
this._collide.push(exists)
} }
} }
@ -303,6 +376,15 @@ class World {
this._lastDrawCount++ 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 } export { TileMap, Chunk, World }

View File

@ -23,6 +23,21 @@ class Viewport {
let adj = this.adjustCentered let adj = this.adjustCentered
return { x: Math.floor(adj.x / size), y: Math.floor(adj.y / size) } 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 export default Viewport