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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										64
									
								
								src/game.js
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								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) { | ||||
|     this.actors.forEach(actor => actor.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) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										93
									
								
								src/room.js
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								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,26 +27,73 @@ export default class Room { | ||||
|   } | ||||
| 
 | ||||
|   draw(ctx) { | ||||
|     this.json.layers.forEach(layer => { | ||||
|       for (let y = 0; y < layer.height; y++) { | ||||
| 	for (let x = 0; x < layer.width; x++) { | ||||
| 	  const index = x + (y * layer.width) | ||||
| 	  const tileIndex = layer.data[index] - 1 | ||||
| 	  const tileset = this.tilesets[0] | ||||
| 	  const [sx, sy] = tileset.tileOffset(tileIndex) | ||||
| 	  ctx.drawImage( | ||||
| 	    tileset.image, | ||||
| 	    sx, | ||||
| 	    sy, | ||||
| 	    tileset.tileWidth, | ||||
| 	    tileset.tileHeight, | ||||
| 	    x * this.json.tilewidth, | ||||
| 	    y * this.json.tileheight, | ||||
| 	    this.json.tilewidth, | ||||
| 	    this.json.tileheight | ||||
|     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) | ||||
| 	const tileIndex = layer.data[index] - 1 | ||||
| 	const tileset = this.tilesets[0] | ||||
| 	const [sx, sy] = tileset.tileOffset(tileIndex) | ||||
| 	ctx.drawImage( | ||||
| 	  tileset.image, | ||||
| 	  sx, | ||||
| 	  sy, | ||||
| 	  tileset.tileWidth, | ||||
| 	  tileset.tileHeight, | ||||
| 	  x * this.json.tilewidth, | ||||
| 	  y * this.json.tileheight, | ||||
| 	  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