Compare commits
14 Commits
b2624eb989
...
94b2c6b140
Author | SHA1 | Date | |
---|---|---|---|
94b2c6b140 | |||
d65f6af7f0 | |||
9ea7564ac8 | |||
499814553f | |||
64a21c91a3 | |||
d14864c604 | |||
8537af9f08 | |||
56e6a728c3 | |||
60e8054ce8 | |||
452969c7be | |||
299f1dfcca | |||
f99494c42b | |||
9fb09c1244 | |||
b7e86ce8ac |
127
rooms/sample.tmj
127
rooms/sample.tmj
@ -33,13 +33,134 @@
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"draworder":"topdown",
|
||||
"id":3,
|
||||
"name":"Object Layer 1",
|
||||
"objects":[
|
||||
{
|
||||
"height":64,
|
||||
"id":1,
|
||||
"name":"hi_box",
|
||||
"properties":[
|
||||
{
|
||||
"name":"color",
|
||||
"type":"string",
|
||||
"value":"red"
|
||||
},
|
||||
{
|
||||
"name":"event",
|
||||
"type":"string",
|
||||
"value":"change_color"
|
||||
}],
|
||||
"rotation":0,
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":64,
|
||||
"x":128,
|
||||
"y":256
|
||||
},
|
||||
{
|
||||
"height":64,
|
||||
"id":4,
|
||||
"name":"low_box",
|
||||
"properties":[
|
||||
{
|
||||
"name":"color",
|
||||
"type":"string",
|
||||
"value":"green"
|
||||
},
|
||||
{
|
||||
"name":"event",
|
||||
"type":"string",
|
||||
"value":"change_color"
|
||||
},
|
||||
{
|
||||
"name":"interactEvent",
|
||||
"type":"string",
|
||||
"value":"log_test"
|
||||
}],
|
||||
"rotation":0,
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":64,
|
||||
"x":128,
|
||||
"y":384
|
||||
},
|
||||
{
|
||||
"height":46.3794477161778,
|
||||
"id":5,
|
||||
"name":"sign_crate",
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
},
|
||||
{
|
||||
"name":"interactEvent",
|
||||
"type":"string",
|
||||
"value":"show_message"
|
||||
},
|
||||
{
|
||||
"name":"messageText",
|
||||
"type":"string",
|
||||
"value":"I'm just a humble box!\nIt's possible to say more than one thing you know."
|
||||
}],
|
||||
"rotation":0,
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":41.2588439219327,
|
||||
"x":331.477887674811,
|
||||
"y":401.217123575356
|
||||
},
|
||||
{
|
||||
"height":37.6264667354163,
|
||||
"id":6,
|
||||
"name":"",
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}],
|
||||
"rotation":0,
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":23.6907383148917,
|
||||
"x":403.02126592157,
|
||||
"y":408.316842721369
|
||||
},
|
||||
{
|
||||
"height":35.6754647565428,
|
||||
"id":7,
|
||||
"name":"",
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}],
|
||||
"rotation":0,
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":30.3798879567435,
|
||||
"x":656.930237743527,
|
||||
"y":411.661417542295
|
||||
}],
|
||||
"opacity":1,
|
||||
"type":"objectgroup",
|
||||
"visible":true,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 182, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
182, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
182, 0, 0, 0, 0, 188, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 189, 229, 0, 0, 0, 190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
@ -64,8 +185,8 @@
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":3,
|
||||
"nextobjectid":1,
|
||||
"nextlayerid":4,
|
||||
"nextobjectid":8,
|
||||
"orientation":"orthogonal",
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"1.11.2",
|
||||
|
@ -12,13 +12,12 @@ const ROOM_ASSETS = {
|
||||
}
|
||||
|
||||
export default class Assets {
|
||||
constructor() {
|
||||
constructor(game) {
|
||||
this.game = game
|
||||
this.assetMap = {}
|
||||
}
|
||||
|
||||
get(assetName) {
|
||||
console.log("getting", assetName)
|
||||
console.log("from", this.assetMap)
|
||||
return this.assetMap[assetName]
|
||||
}
|
||||
|
||||
@ -44,7 +43,6 @@ export default class Assets {
|
||||
}
|
||||
|
||||
loadImage(name, path) {
|
||||
console.log(name, path)
|
||||
return new Promise(resolve => {
|
||||
const img = new Image()
|
||||
this.assetMap[name] = img
|
||||
@ -55,13 +53,13 @@ export default class Assets {
|
||||
|
||||
loadTileset(name, path) {
|
||||
return fetch(path).then(rsp => rsp.json()).then(json => {
|
||||
return this.assetMap[name] = new Tileset(json, name)
|
||||
return this.assetMap[name] = new Tileset(this.game, json, name)
|
||||
}).then(tileset => this.loadImages(tileset.imagesToLoad))
|
||||
}
|
||||
|
||||
loadRoom(name, path) {
|
||||
return fetch(path).then(rsp => rsp.json()).then(json => {
|
||||
return this.assetMap[name] = new Room(json, name)
|
||||
return this.assetMap[name] = new Room(this.game, json, name)
|
||||
}).then(room => this.loadTilesets(room.tilesetsToLoad))
|
||||
}
|
||||
}
|
||||
|
21
src/event.js
Normal file
21
src/event.js
Normal file
@ -0,0 +1,21 @@
|
||||
export default class Event {
|
||||
constructor(name, action) {
|
||||
this.name = name
|
||||
this.action = action
|
||||
this.triggered = false
|
||||
this.triggeredThisFrame = false
|
||||
}
|
||||
|
||||
trigger(object) {
|
||||
this.triggeredThisFrame = true
|
||||
if (!this.triggered) {
|
||||
this.triggered = true
|
||||
this.action(object)
|
||||
}
|
||||
}
|
||||
|
||||
nextFrame() {
|
||||
this.triggered = this.triggeredThisFrame
|
||||
this.triggeredThisFrame = false
|
||||
}
|
||||
}
|
62
src/game.js
62
src/game.js
@ -1,40 +1,90 @@
|
||||
import Player from "./player.js"
|
||||
import Input from "./input.js"
|
||||
import Event from "./event.js"
|
||||
import Message from "./message.js"
|
||||
|
||||
import { average } from "./util.js"
|
||||
|
||||
export default class Game {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas
|
||||
this.ctx = canvas.getContext("2d")
|
||||
this.timestamp = 0
|
||||
this.actors = []
|
||||
this.actors.push(new Player(this, 200, 200))
|
||||
this.player = new Player(this, 200, 200)
|
||||
this.actors = [this.player]
|
||||
|
||||
this.input = new Input().initialize()
|
||||
|
||||
this.currentRoom = null
|
||||
this.events = {
|
||||
"log_test": new Event("log_test", () => console.log("Log events work!")),
|
||||
"change_color": new Event("change_color", object => object.setProperty("color", "blue")),
|
||||
"show_message": new Event("show_message", object => this.message = new Message(this, object.getProperty("messageText")))
|
||||
}
|
||||
|
||||
this.message = null
|
||||
|
||||
this.fpsBuffer = [60, 60, 60, 60, 60, 60, 60, 60, 60, 60]
|
||||
}
|
||||
|
||||
triggerEvent(eventName, object) {
|
||||
const event = this.events[eventName]
|
||||
if (event) event.trigger(object)
|
||||
else console.error("Unknown event " + eventName)
|
||||
}
|
||||
|
||||
start() {
|
||||
this.currentRoom = this.assets.get("sampleRoom")
|
||||
this.loadRoom(this.assets.get("sampleRoom"))
|
||||
requestAnimationFrame(this.loop.bind(this))
|
||||
}
|
||||
|
||||
loadRoom(room) {
|
||||
this.currentRoom = room
|
||||
this.currentRoom.objects.forEach(roomObject => this.actors.push(roomObject))
|
||||
}
|
||||
|
||||
closeMessage(message) {
|
||||
this.message = null
|
||||
}
|
||||
|
||||
loop(timestamp) {
|
||||
const dt = timestamp - this.timestamp
|
||||
this.timestamp = timestamp
|
||||
this.tick(dt)
|
||||
this.dt = timestamp - this.timestamp
|
||||
const fps = 1000 / this.dt
|
||||
this.fpsBuffer.pop()
|
||||
this.fpsBuffer.unshift(fps)
|
||||
this.timestamp= timestamp
|
||||
this.tick(this.dt)
|
||||
this.draw()
|
||||
|
||||
requestAnimationFrame(this.loop.bind(this))
|
||||
}
|
||||
|
||||
tick(dt) {
|
||||
if (this.message) {
|
||||
this.message?.tick(dt)
|
||||
} else {
|
||||
this.actors.forEach(actor => actor.tick(dt))
|
||||
Object.values(this.events).forEach(e => e.nextFrame())
|
||||
}
|
||||
this.input.tick()
|
||||
}
|
||||
|
||||
draw() {
|
||||
const { canvas, ctx } = this
|
||||
this.currentRoom.draw(ctx)
|
||||
this.actors.forEach(actor => actor.draw(ctx))
|
||||
this.message?.draw(ctx)
|
||||
this.drawFps(ctx)
|
||||
}
|
||||
|
||||
drawFps(ctx) {
|
||||
ctx.fillStyle = "white"
|
||||
ctx.fillRect(ctx.canvas.width, 0, -25, 20)
|
||||
ctx.strokeStyle = "black"
|
||||
ctx.textBaseline = "top"
|
||||
ctx.textAlign = "right"
|
||||
ctx.font = "bold 20px serif"
|
||||
ctx.fillText(Math.round(average(this.fpsBuffer)), ctx.canvas.width, 0)
|
||||
ctx.strokeText(Math.round(average(this.fpsBuffer)), ctx.canvas.width, 0)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ document.addEventListener("DOMContentLoaded", async e => {
|
||||
const canvas = document.getElementById("game-canvas")
|
||||
const game = new Game(canvas)
|
||||
|
||||
game.assets = new Assets()
|
||||
game.assets = new Assets(game)
|
||||
await game.assets.load()
|
||||
|
||||
game.start()
|
||||
|
18
src/input.js
18
src/input.js
@ -12,11 +12,17 @@ export default class Input {
|
||||
this.inputsToKeys = Object.fromEntries(Object.keys(this.keysToInputs).map(key => [this.keysToInputs[key], key]))
|
||||
|
||||
this.inputPressed = Object.fromEntries(Object.keys(this.inputsToKeys).map(key => [key, false]))
|
||||
this.inputJustPressed = Object.fromEntries(Object.keys(this.inputsToKeys).map(key => [key, false]))
|
||||
}
|
||||
|
||||
initialize() {
|
||||
window.addEventListener("keydown", key => this.inputPressed[this.keyFromInput(key.key)] = true)
|
||||
window.addEventListener("keyup", key => this.inputPressed[this.keyFromInput(key.key)] = false)
|
||||
window.addEventListener("keydown", key => {
|
||||
this.inputJustPressed[this.keyFromInput(key.key)] = true
|
||||
this.inputPressed[this.keyFromInput(key.key)] = true
|
||||
})
|
||||
window.addEventListener("keyup", key => {
|
||||
this.inputPressed[this.keyFromInput(key.key)] = false
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
@ -24,6 +30,14 @@ export default class Input {
|
||||
return !!this.inputPressed[inputName]
|
||||
}
|
||||
|
||||
isInputJustPressed(inputName) {
|
||||
return !!this.inputJustPressed[inputName]
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.inputJustPressed = Object.fromEntries(Object.keys(this.inputsToKeys).map(key => [key, false]))
|
||||
}
|
||||
|
||||
keyFromInput(input) {
|
||||
return this.keysToInputs[input]
|
||||
}
|
||||
|
65
src/message.js
Normal file
65
src/message.js
Normal file
@ -0,0 +1,65 @@
|
||||
export default class Message {
|
||||
constructor(game, text) {
|
||||
this.game = game
|
||||
this.text = text.split("\n")
|
||||
this.currentText = this.text.shift()
|
||||
this.textColor = "white"
|
||||
this.backgroundColor = "black"
|
||||
this.textIndex = 0
|
||||
this.textProgress = 0.0
|
||||
this.textSpeed = 0.08 // seconds per character
|
||||
this.backgroundHeight = 40
|
||||
this.textSize = 30
|
||||
}
|
||||
|
||||
tick(dt) {
|
||||
this.textProgress += (dt / 1000.0) / this.textSpeed
|
||||
this.textIndex = Math.floor(this.textProgress)
|
||||
|
||||
const ijp = this.game.input.isInputJustPressed("interact")
|
||||
if (ijp) {
|
||||
if (this.messageComplete()) {
|
||||
this.game.closeMessage(this)
|
||||
} else if (this.lineComplete()) {
|
||||
this.currentText = this.text.shift()
|
||||
this.textProgress = 0.0
|
||||
this.textIndex = 0
|
||||
} else {
|
||||
this.textProgress = this.currentText.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
ctx.fillStyle = this.backgroundColor
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, this.backgroundHeight)
|
||||
ctx.font = `bold ${this.textSize}px sans-serif`
|
||||
ctx.textBaseline = "top"
|
||||
ctx.textAlign = "left"
|
||||
ctx.fillStyle = this.textColor
|
||||
ctx.fillText(this.currentText.substring(0, this.textIndex), 5, 5)
|
||||
if (this.messageComplete()) {
|
||||
ctx.fillRect(
|
||||
ctx.canvas.width - 20,
|
||||
this.backgroundHeight - 20,
|
||||
10, 10
|
||||
)
|
||||
} else if (this.lineComplete()) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(ctx.canvas.width - 20, this.backgroundHeight - 20)
|
||||
ctx.lineTo(ctx.canvas.width - 20, this.backgroundHeight - 10)
|
||||
ctx.lineTo(ctx.canvas.width - 15, this.backgroundHeight - 15)
|
||||
ctx.lineTo(ctx.canvas.width - 20, this.backgroundHeight - 20)
|
||||
ctx.fill()
|
||||
ctx.closePath()
|
||||
}
|
||||
}
|
||||
|
||||
lineComplete() {
|
||||
return this.textIndex >= this.currentText.length + 3
|
||||
}
|
||||
|
||||
messageComplete() {
|
||||
return this.lineComplete() && !this.text.length
|
||||
}
|
||||
}
|
@ -7,11 +7,12 @@ export default class Player extends Actor {
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.width = 32
|
||||
this.height = 64
|
||||
this.height = 32
|
||||
this.xVel = 0
|
||||
this.yVel = 0
|
||||
this.color = "#56E"
|
||||
this.playerDirection = { x: 0, y: 1 }
|
||||
this.interactHitbox = null
|
||||
}
|
||||
|
||||
tick(dt) {
|
||||
@ -26,16 +27,51 @@ export default class Player extends Actor {
|
||||
this.x += this.xVel
|
||||
this.y += this.yVel
|
||||
|
||||
if (this.collidesWithAbsolutelyAnything()) {
|
||||
this.x -= this.xVel
|
||||
this.y -= this.yVel
|
||||
}
|
||||
|
||||
if (!isZeroVector(dir)) this.playerDirection = dir
|
||||
|
||||
if (this.isInputPressed("interact")) this.createInteractHitbox()
|
||||
else this.interactHitbox = null
|
||||
}
|
||||
|
||||
collidesWithAbsolutelyAnything() {
|
||||
return this.collidesWithTiles() || this.collidesWithObjects()
|
||||
}
|
||||
|
||||
collidesWithObjects() {
|
||||
const objects = this.game.currentRoom.objectsUnderRectangle(this)
|
||||
return !!objects.find(object => object.collides())
|
||||
}
|
||||
|
||||
collidesWithTiles() {
|
||||
const tur = this.game.currentRoom.tilesUnderRectangle(this).filter(x => x)
|
||||
const colliders = tur.filter(tile => tile.properties.find(prop => prop.name == "collides" && prop.value))
|
||||
return !!colliders.length
|
||||
}
|
||||
|
||||
createInteractHitbox() {
|
||||
this.interactHitbox = {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
x: this.x + (this.playerDirection.x * this.width / 2),
|
||||
y: this.y + (this.playerDirection.y * this.height / 2),
|
||||
}
|
||||
}
|
||||
|
||||
isInputPressed(action) {
|
||||
return this.game.input.isInputPressed.call(this.game.input, action)
|
||||
}
|
||||
|
||||
inputDirection() {
|
||||
const isInputPressed = this.game.input.isInputPressed.bind(this.game.input)
|
||||
const dir = { x: 0, y: 0 }
|
||||
if (isInputPressed("up")) dir.y -= 1
|
||||
if (isInputPressed("down")) dir.y += 1
|
||||
if (isInputPressed("left")) dir.x -= 1
|
||||
if (isInputPressed("right")) dir.x += 1
|
||||
if (this.isInputPressed("up")) dir.y -= 1
|
||||
if (this.isInputPressed("down")) dir.y += 1
|
||||
if (this.isInputPressed("left")) dir.x -= 1
|
||||
if (this.isInputPressed("right")) dir.x += 1
|
||||
|
||||
if (Math.abs(dir.x, dir.y) == 2) {
|
||||
dir.x *= SQRT_OF_TWO
|
||||
@ -47,10 +83,11 @@ export default class Player extends Actor {
|
||||
|
||||
draw(ctx) {
|
||||
this.color = `rgb(128 ${(this.playerDirection.x * 128) + 128} ${(this.playerDirection.y * 128) + 128}`
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = this.color
|
||||
ctx.rect(this.x, this.y, this.width, this.height)
|
||||
ctx.fill()
|
||||
ctx.closePath()
|
||||
ctx.fillRect(this.x, this.y, this.width, this.height)
|
||||
if (this.interactHitbox) {
|
||||
ctx.fillStyle = "#FF000088"
|
||||
ctx.fillRect(this.interactHitbox.x, this.interactHitbox.y, this.interactHitbox.width, this.interactHitbox.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
61
src/room.js
61
src/room.js
@ -1,8 +1,13 @@
|
||||
import RoomObject from "./roomObject.js"
|
||||
import { doRectanglesOverlap } from "./util.js"
|
||||
|
||||
export default class Room {
|
||||
constructor(json, name) {
|
||||
constructor(game, json, name) {
|
||||
this.game = game
|
||||
this.json = json
|
||||
this.name = name
|
||||
console.log(json)
|
||||
const objectJson = this.objectLayers.map(layer => layer.objects).flat()
|
||||
this.objects = objectJson.map(RoomObject.fromJson.bind(null, this.game))
|
||||
}
|
||||
|
||||
get tilesetsToLoad() {
|
||||
@ -14,7 +19,6 @@ export default class Room {
|
||||
}
|
||||
|
||||
populateTilesets(assets) {
|
||||
console.log(this.json)
|
||||
this.tilesets = this.json.tilesets.map((tileset, index) => {
|
||||
const ts = assets.get(`${this.name}-${index}`)
|
||||
ts.populateImage(assets)
|
||||
@ -23,7 +27,46 @@ export default class Room {
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
this.json.layers.forEach(layer => {
|
||||
this.tileLayers.forEach(this.drawTileLayer.bind(this, ctx))
|
||||
}
|
||||
|
||||
drawLayer(ctx, layer) {
|
||||
if (layer.type == "tilelayer") this.drawTileLayer(ctx, layer)
|
||||
}
|
||||
|
||||
get tileLayers() {
|
||||
return this.json.layers.filter(layer => layer.type == "tilelayer")
|
||||
}
|
||||
|
||||
get objectLayers() {
|
||||
return this.json.layers.filter(layer => layer.type == "objectgroup")
|
||||
}
|
||||
|
||||
objectsUnderRectangle(rect) {
|
||||
return this.objects.filter(object => doRectanglesOverlap(object, rect))
|
||||
}
|
||||
|
||||
tilesUnderRectangle(rect) {
|
||||
return this.tileLayers.map(layer => this.tilesUnderRectangleInLayer(layer, rect)).flat()
|
||||
}
|
||||
|
||||
tilesUnderRectangleInLayer(layer, rect) {
|
||||
return [{ x: rect.x, y: rect.y },
|
||||
{ x: rect.x + rect.width, y: rect.y },
|
||||
{ x: rect.x, y: rect.y + rect.height },
|
||||
{ x: rect.x + rect.width, y: rect.y + rect.height }
|
||||
].map(point => {
|
||||
const tileset = this.tilesets[0]
|
||||
const { x, y } = point
|
||||
const tileX = Math.floor(x / tileset.tileWidth)
|
||||
const tileY = Math.floor(y / tileset.tileHeight)
|
||||
const index = tileX + (tileY * layer.width)
|
||||
const tileIndex = layer.data[index] - 1
|
||||
return tileset.tileAt(tileIndex)
|
||||
})
|
||||
}
|
||||
|
||||
drawTileLayer(ctx, layer) {
|
||||
for (let y = 0; y < layer.height; y++) {
|
||||
for (let x = 0; x < layer.width; x++) {
|
||||
const index = x + (y * layer.width)
|
||||
@ -41,8 +84,16 @@ export default class Room {
|
||||
this.json.tilewidth,
|
||||
this.json.tileheight
|
||||
)
|
||||
if (tileset.collides(tileIndex)) {
|
||||
ctx.fillStyle = "#aa660088"
|
||||
ctx.fillRect(
|
||||
x * this.json.tilewidth,
|
||||
y * this.json.tileheight,
|
||||
this.json.tilewidth,
|
||||
this.json.tileheight
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
52
src/roomObject.js
Normal file
52
src/roomObject.js
Normal file
@ -0,0 +1,52 @@
|
||||
import { doRectanglesOverlap } from "./util.js"
|
||||
|
||||
export default class RoomObject {
|
||||
constructor(game) {
|
||||
this.game = game
|
||||
}
|
||||
|
||||
static fromJson(game, json) {
|
||||
const roomObject = new RoomObject(game)
|
||||
Object.entries(json).forEach(([key, value]) => roomObject[key] = value)
|
||||
return roomObject
|
||||
}
|
||||
|
||||
getProperty(name) {
|
||||
const property = this.properties.find(p => p.name == name)
|
||||
if (!property) {
|
||||
// console.error(`Unknown property ${name} on ${this.name}`)
|
||||
return null
|
||||
}
|
||||
return property.value
|
||||
}
|
||||
|
||||
setProperty(name, value) {
|
||||
const p = this.properties.find(p => p.name == name)
|
||||
if (p) {
|
||||
p.value = value
|
||||
} else {
|
||||
this.properties[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
tick(dt) {
|
||||
const { player } = this.game
|
||||
if (doRectanglesOverlap(player, this)) {
|
||||
const eventName = this.getProperty.call(this, "event")
|
||||
if (eventName) this.game.triggerEvent(eventName, this)
|
||||
}
|
||||
if (player.interactHitbox && doRectanglesOverlap(player.interactHitbox, this)) {
|
||||
const eventName = this.getProperty("interactEvent")
|
||||
if (eventName) this.game.triggerEvent(eventName, this)
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
ctx.fillStyle = this.getProperty("color") || "#00000000"
|
||||
ctx.fillRect(this.x, this.y, this.width, this.height)
|
||||
}
|
||||
|
||||
collides() {
|
||||
return this.getProperty("collides") || false
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
export default class Tileset {
|
||||
constructor(json, name) {
|
||||
constructor(game, json, name) {
|
||||
this.game = game
|
||||
this.json = json
|
||||
this.name = name
|
||||
this.tiles = json.tiles
|
||||
}
|
||||
|
||||
get imagesToLoad() {
|
||||
@ -32,4 +34,15 @@ export default class Tileset {
|
||||
(Math.floor(index / this.columns) * this.tileHeight)
|
||||
]
|
||||
}
|
||||
|
||||
tileAt(index) {
|
||||
return this.tiles.find(tile => tile.id == index)
|
||||
}
|
||||
|
||||
collides(index) {
|
||||
const tile = this.tileAt(index)
|
||||
if (!tile) return
|
||||
|
||||
return tile.properties.find(prop => prop.name == "collides").value == "true"
|
||||
}
|
||||
}
|
||||
|
24
src/util.js
24
src/util.js
@ -2,7 +2,29 @@ const SQRT_OF_TWO = Math.sqrt(2)
|
||||
|
||||
const isZeroVector = vector => !vector.x && !vector.y
|
||||
|
||||
const doRectanglesOverlap = (rect1, rect2) => {
|
||||
return (
|
||||
doLengthsOverlap({ x: rect1.x, width: rect1.width }, { x: rect2.x, width: rect2.width })
|
||||
&&
|
||||
doLengthsOverlap({ x: rect1.y, width: rect1.height }, { x: rect2.y, width: rect2.height })
|
||||
)
|
||||
}
|
||||
|
||||
const doLengthsOverlap = (l1, l2) => {
|
||||
return !((l1.x + l1.width < l2.x) || (l2.x + l2.width) < l1.x)
|
||||
}
|
||||
|
||||
function sum(array) {
|
||||
return array.reduce((a, b) => a + b, 0)
|
||||
}
|
||||
|
||||
function average(array) {
|
||||
return sum(array) / array.length
|
||||
}
|
||||
|
||||
export {
|
||||
SQRT_OF_TWO,
|
||||
isZeroVector
|
||||
isZeroVector,
|
||||
doRectanglesOverlap,
|
||||
sum, average
|
||||
}
|
||||
|
@ -8,6 +8,358 @@
|
||||
"tilecount":260,
|
||||
"tiledversion":"1.11.2",
|
||||
"tileheight":64,
|
||||
"tiles":[
|
||||
{
|
||||
"id":60,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":61,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":62,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":63,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":64,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":65,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":66,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":80,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":81,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":82,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":83,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":84,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":85,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":86,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":100,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":101,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":102,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":103,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":144,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":145,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":146,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":164,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":165,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":166,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":180,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":182,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":184,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":200,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":201,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":202,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":203,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":204,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":205,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":220,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":221,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":222,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":223,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":224,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":225,
|
||||
"properties":[
|
||||
{
|
||||
"name":"collides",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}]
|
||||
}],
|
||||
"tilewidth":64,
|
||||
"type":"tileset",
|
||||
"version":"1.10"
|
||||
|
Loading…
Reference in New Issue
Block a user