simple player and collisions
This commit is contained in:
parent
4eed971735
commit
d04cfc425f
@ -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)
|
||||
|
33
src/index.js
33
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)
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ const specialKeyMap = {
|
||||
'pausebreak': 19,
|
||||
'capslock': 20,
|
||||
'escape': 27,
|
||||
'space': 32,
|
||||
'pgup': 33,
|
||||
'pgdown': 34,
|
||||
'end': 35,
|
||||
|
234
src/player.js
234
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
|
||||
}
|
||||
|
||||
draw () {
|
||||
// Draw player
|
||||
super.draw()
|
||||
|
||||
// Draw hook
|
||||
this.hook.draw()
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
update (level) {
|
||||
if (!level) return
|
||||
this.hook.update(level)
|
||||
// x collision
|
||||
let oldX = this.x
|
||||
this.x += this.mX
|
||||
if (oldX !== this.x && collider.collide(this)) {
|
||||
this.x = oldX
|
||||
this.mX = 0
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
90
src/tiles.js
90
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 }
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user