From 5a2ba57115741f735c5f1f2ccd9c4a3e19f82fbf Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sat, 11 Jan 2020 00:54:37 +0200 Subject: [PATCH] simple inventory and item system --- assets/item_dirt.png | Bin 0 -> 681 bytes assets/item_grass.png | Bin 0 -> 478 bytes assets/item_stone.png | Bin 0 -> 510 bytes src/debug.js | 4 +- src/index.js | 114 ++++++++++++++++++++++++++++-------------- src/inventory.js | 89 +++++++++++++++++++++++++++++++++ src/items.js | 87 ++++++++++++++++++++++++++++++++ src/player.js | 10 ++-- src/tiles.js | 113 ++++++++++++++++++++++++++++++----------- 9 files changed, 344 insertions(+), 73 deletions(-) create mode 100644 assets/item_dirt.png create mode 100644 assets/item_grass.png create mode 100644 assets/item_stone.png create mode 100644 src/inventory.js create mode 100644 src/items.js diff --git a/assets/item_dirt.png b/assets/item_dirt.png new file mode 100644 index 0000000000000000000000000000000000000000..8f809cebb3d410bb3c35d378ddfa7ba97d1eacfb GIT binary patch literal 681 zcmV;a0#^NrP)Px#24YJ`L;y?xF#r`TG-Z1L000SaNLh0L04^f{04^f|c%?sf00007bV*G`2jl?? z6*45)LhIWA0013nR9JLFZ*6U5Zgc_CX>@2HM@dakWG-a~00062 zNkl}T#Q_upaQ_ARA6$DtCNe7!gQB&M$HVER7%~e=xjNvQYQ#aRNO&DW{#rUiT=;2 zwW7l?0AL0&OsZy%+2p2}5p*Jq#dh=At#P?r0w4mpIw>kC2-POgB8ExLoD_rx+>o#BaQ_HvRKNELH zFD@>KG46}ZfEa`nlua>GP-2iB`Od=^vi*1pFpeWJM(#d(Dcg3|{lNLE=W-Z1U-dik z^?Q${){2NQ3idqOj4_XBAf*tk;M!QddeWoSh3W;3@FH6;_2(k{)CZB)JQntzuy(0kZfj1M2T~L zZfYpV;TM91qGKcDFZej%0M;af(#UGS1rSyRZ7fv4dBGix+WN2+k1>>3tyM zd64tT!9qLj6N?}J`;z-Q;gij;uYcFi^yOS7uqT0SRa@5MWno9R@1C|*i79K_eP+QG zlLVWUs}wUgYjVpqEOGnd#M7t6%W+0r^`d#{Y}48c+rFjU*`BY)P?+!N4&(tl_*!5xm5plt-laD38 Rab61wSx;9#mvv4FO#r6iywd;x literal 0 HcmV?d00001 diff --git a/assets/item_stone.png b/assets/item_stone.png new file mode 100644 index 0000000000000000000000000000000000000000..ae33ce67c347ec8f939a34b8a9a6d275aff79979 GIT binary patch literal 510 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^M{A z3>idqOj4_XBAf*tk;M!QddeWoSh3W;3@FH6;_2(k{)CZB)W}F@T3i=UNH#MhqQp5r zH#aq}1juDza4t$sEJ;mKD9TWd~Xp>gnPbVsU!u8Cj|lZzEfAbAH4FbPBXi>YQlw!qPtl2+y40M`Cq61Kl{gz&x-$9 z)|@?TU~oUpiKj*2gk|ym`<60%(jEVD?!7J3Io(v{8fs`fYuUl-^9DT9Q-9fBER}6- z>QLZVDl%(X;w&|WGdCh&fCv&zpY@HEw=!}4%6UUa^=thRmHj{%^G3>XSQ(T(W zGcm}Eb+)8s`k5XH< MAX_STACK_SIZE) { + let m = addedCount - MAX_STACK_SIZE + let n = itm.copy() + n.count = m + itm.count = MAX_STACK_SIZE + if (this.items.length >= this.size) { + leftover = n + addedTo = true + } else { + continue + } + } else { + itm.count += i.count + addedTo = true + } + } + } + + if (!addedTo) { + if (this.items.length >= this.size) { + return i + } + this.items.push(i) + } + + return leftover + } + + getItem (slot) { + if (this.isEmpty(slot)) return null + return this.items[slot] + } + + takeItem (slot, count) { + if (this.isEmpty(slot)) return null + let i = this.items[slot] + if (!count || count > i.count) return i + i.count -= count + let copied = i.copy() + copied.count = count + return copied + } + + isEmpty (slot) { + if (!this.items[slot] || this.items[slot].isEmpty()) return true + return false + } + + draw () { + for (let i = 0; i < this.size; i++) { + let stack = this.items[i] + let x = canvas.width / 2 + i * (SLOT_SIZE + 8) - this.size / 2 * SLOT_SIZE + ctx.fillStyle = (this.selected === i) ? '#f00' : '#ddd' + ctx.fillRect(x, 16, SLOT_SIZE, SLOT_SIZE) + if (!stack || stack.isEmpty()) continue + ctx.drawImage(stack.item.image, x, 16, SLOT_SIZE, SLOT_SIZE) + ctx.font = '16px sans' + let measure = ctx.measureText(stack.count) + ctx.fillStyle = '#000' + ctx.fillText(stack.count, x + SLOT_SIZE / 2 - measure.width / 2, 8 + SLOT_SIZE) + ctx.fillStyle = '#fff' + ctx.fillText(stack.count, x + SLOT_SIZE / 2 - measure.width / 2 + 1, 8 + SLOT_SIZE + 1) + } + } +} + +export { Inventory } diff --git a/src/items.js b/src/items.js new file mode 100644 index 0000000..1dda81c --- /dev/null +++ b/src/items.js @@ -0,0 +1,87 @@ +import RES from './resource' + +const MAX_STACK_SIZE = 999 + +const ItemRegistry = new (class ItemRegistry { + constructor () { + this.items = {} + } + + register (name, item) { + this.items[name] = item + } + + get (name) { + return this.items[name] + } +})() + +class Item { + constructor (name, img, description) { + this.name = name + this._img = img + this.description = description + ItemRegistry.register(name, this) + } + + get image () { + return RES.loadImage(this._img, true) + } +} + +class ItemPlaceable extends Item { + constructor (tile, name, img, description) { + super(name, img, description) + this.placeable = tile + } +} + +class ItemStack { + static fromIString (str) { + if (typeof str !== 'string') return + let strpl = str.split(' ') + let iname = strpl[0] + let count = strpl[1] + let item = ItemRegistry.get(iname) + let istack = new ItemStack() + istack.item = item + istack.count = count || 1 + return istack + } + + static new (itemdef, count = 1, metadata) { + if (itemdef instanceof ItemStack) return itemdef.copy() + if (typeof itemdef === 'string') return ItemStack.fromIString(itemdef) + if (!(itemdef instanceof Item)) throw new Error('Invalid Item Definition!') + let istack = new ItemStack() + istack.item = itemdef + istack.count = count + istack.metadata = metadata + return istack + } + + copy () { + return ItemStack.new(this.item, this.count, this.metadata) + } + + get name () { + return this.item ? this.item.name : '' + } + + isEmpty () { + return this.item === null || this.count === 0 + } + + takeItem (c) { + let a = this.copy() + if (c > this.count) { + this.count = 0 + return a + } + this.count -= c + a.count = c + return a + } +} + +export { Item, ItemPlaceable, ItemStack, ItemRegistry, MAX_STACK_SIZE } diff --git a/src/player.js b/src/player.js index 4fea33b..fd41de2 100644 --- a/src/player.js +++ b/src/player.js @@ -14,7 +14,7 @@ class Player { this.grounded = false - this.speed = 5 + this.speed = 8 this.gravity = 1 this.jumpPower = 20 } @@ -35,8 +35,12 @@ class Player { let oldX = this.x this.x += this.mX if (oldX !== this.x && collider.collide(this)) { - this.x = oldX - this.mX = 0 + this.mX = this.mX < 0 ? -1 : 1 + this.x = oldX + this.mX + if (collider.collide(this)) { + this.x = oldX + this.mX = 0 + } } } diff --git a/src/tiles.js b/src/tiles.js index ffc2d59..40d27b4 100644 --- a/src/tiles.js +++ b/src/tiles.js @@ -6,11 +6,20 @@ import Debug from './debug' const cacheFactory = new ResourceCacheFactory() const UPDATE_RADIUS = 6 +class Tile { + constructor (name, index, solid = true, item) { + this.name = name + this.id = index + this.solid = solid + this.item = item + } +} + class TileMap { constructor (image, rows) { this._src = image this.rows = rows - this.defs = {} + this.tiles = [] } get height () { @@ -33,16 +42,41 @@ class TileMap { return { x: (i % this.rows) * this.tile, y: Math.floor(i / this.rows) * this.tile } } - define (tile, index) { - if (typeof tile === 'object') { - this.defs = Object.assign(this.defs, tile) + register (tile) { + if (typeof tile === 'object' && tile.length) { + for (let i in tile) { + this.register(tile[i]) + } return } - this.defs[tile] = index + if (!(tile instanceof Tile)) return + this.tiles.push(tile) + } + + getTileByName (tile) { + for (let i in this.tiles) { + let t = this.tiles[i] + if (t.name === tile) return t + } + return null + } + + getTileByID (id) { + for (let i in this.tiles) { + let t = this.tiles[i] + if (t.id === id) return t + } + return null + } + + isSolid (id) { + let t = this.getTileByID(id) + return t ? t.solid : false } indexOf (tile) { - return this.defs[tile] || null + let t = this.getTileByName(tile) + return t ? t.id : null } positionOf (tile) { @@ -51,7 +85,8 @@ class TileMap { } class TileLayer { - constructor (name, size = 16, tileSize = 16) { + constructor (map, name, size = 16, tileSize = 16) { + this.map = map this.name = name this.size = size this.tile = tileSize @@ -70,11 +105,16 @@ class TileLayer { return true } + isSolid (i) { + return this.map.isSolid(i) + } + tileAt (i) { return this.tiles[i] } tileAtXY (x, y) { + if (x < 0 || x >= this.size || y < 0 || y >= this.size) return null return this.tileAt(x + this.size * y) } @@ -82,13 +122,13 @@ class TileLayer { return { x: i % this.size, y: Math.floor(i / this.size) } } - draw (ctx, view, map) { + draw (ctx, view) { for (let i in this.tiles) { let tilei = this.tiles[i] if (tilei === -1) continue let coords = this.toXY(parseInt(i)) - let tileCoords = map.tileAt(tilei) - ctx.drawImage(map.image, tileCoords.x, tileCoords.y, map.tile, map.tile, + let tileCoords = this.map.tileAt(tilei) + ctx.drawImage(this.map.image, tileCoords.x, tileCoords.y, this.map.tile, this.map.tile, coords.x * this.tile, coords.y * this.tile, this.tile, this.tile) } @@ -113,7 +153,7 @@ class TileLayer { class TilePhysicsLayer extends TileLayer { constructor (size = 16, tileSize = 16) { - super('col', size, tileSize) + super(null, 'col', size, tileSize) this.empty = false } @@ -126,12 +166,15 @@ class TilePhysicsLayer extends TileLayer { this.empty = true for (let i in tiles.tiles) { let t = tiles.tiles[i] - let p = tiles.toXY(parseInt(i)) - if (t === -1) { + // let p = tiles.toXY(parseInt(i)) + if (t === -1 || !tiles.isSolid(t)) { this.tiles[i] = 0 continue } - + this.empty = false + this.tiles[i] = 1 + /* + // Surface tiles only // 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) @@ -145,6 +188,7 @@ class TilePhysicsLayer extends TileLayer { this.empty = false this.tiles[i] = 1 + */ } } @@ -155,10 +199,10 @@ class TilePhysicsLayer extends TileLayer { 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 + let minX = p.x * chunk.tile + absPos.x + 1 + let minY = p.y * chunk.tile + absPos.y + 1 + let maxX = minX + chunk.tile - 2 + let maxY = minY + chunk.tile - 2 // Intersection check if (minX > obj.x + obj.width || maxX < obj.x || @@ -185,8 +229,8 @@ class Chunk { generateMap (tileMap, heightMap) { this.layers = [] - let bgLayer = new TileLayer('bg', this.size, this.tile) - let fgLayer = new TileLayer('fg', this.size, this.tile) + let bgLayer = new TileLayer(tileMap, 'bg', this.size, this.tile) + let fgLayer = new TileLayer(tileMap, '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) @@ -214,7 +258,6 @@ class Chunk { fgLayer.tiles.push(tileMap.indexOf('STONE')) bgLayer.tiles.push(tileMap.indexOf('STONE')) } - clLayer.generateFromTiles(fgLayer) this.layers.push(bgLayer) this.layers.push(fgLayer) @@ -230,8 +273,18 @@ class Chunk { return null } + getTile (layer, x, y) { + if (typeof x === 'object') { + y = x.y + x = x.x + } + let l = this.getLayer(layer) + if (!l) return null + return l.tileAtXY(x, y) + } + setTile (layer, x, y, tile) { - if (!tile && typeof x !== 'object') { + if (!tile && typeof x === 'object') { tile = y y = x.y x = x.x @@ -260,7 +313,7 @@ class Chunk { return this.size * this.tile } - draw (view, map) { + draw (view) { if (this.img) { // Draw the cached image let p = this.absPos @@ -273,19 +326,19 @@ class Chunk { // Draw all layers for (let i in this.layers) { let layer = this.layers[i] - layer.draw(cacheFactory.ctx, view, map) + layer.draw(cacheFactory.ctx, view) } + // Update collision + let cl = this.getLayer('col') + if (cl) cl.generateFromTiles(this.getLayer('fg')) + // Draw a debug grid when enabled Debug.chunkGrid(cacheFactory.ctx, this, view) // 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 @@ -392,7 +445,7 @@ class World { if (absPos.x > vp.x + vp.width + this.tileSize || absPos.x + chunk.fullSize < vp.x - this.tileSize || absPos.y > vp.y + vp.height + this.tileSize || absPos.y + chunk.fullSize < vp.y - this.tileSize) continue chunk._updated = false - chunk.draw(vp, this.tileMaps.GROUND) + chunk.draw(vp) if (chunk._updated) this._lastUpdateCount++ this._lastDrawCount++ } @@ -408,4 +461,4 @@ class World { } } -export { TileMap, Chunk, World } +export { Tile, TileMap, Chunk, World }