Skip to content

Commit 33558fc

Browse files
committed
fix level rendering in the my levels page
1 parent b74be2e commit 33558fc

2 files changed

Lines changed: 185 additions & 37 deletions

File tree

docs/tileset.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,3 @@ The player spritesheet is a 10x1 strip of tiles.
105105
# Submitting your own tileset
106106

107107
To submit your own tileset, make a pull request with all the player files and the json file. Have fun!
108-
109-
**One final note:**
110-
111-
Currently switching tilesets just uses whatever tileId that the previous had, and if it doesn't have one for that id, there's no block in the level. I'm currently working on a function to automatically switch between the tilesets regardless of id, but for the time being make sure the `"id"` property of each tile matches up with the existing tilesets.

frontend/javascript/my-levels.js

Lines changed: 185 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -211,47 +211,199 @@ function calculateAdjacency(tileIdx, tileId, tiles, tileset, w, h) {
211211
const tilesetMap = new Map()
212212
const imgMap = new Map()
213213

214-
async function loadTileset(tilesetPath) {
215-
if (tilesetMap.has(tilesetPath)) return tilesetMap.get(tilesetPath)
216-
const res = await fetch(tilesetPath)
217-
const rawJson = await res.json()
218-
const tilesetJson = rawJson.tiles
219-
const tileset = {}
220-
const path = rawJson.path
221-
222-
const promises = tilesetJson.map(async (def) => {
214+
async function loadSpriteSheetTileset(manifest) {
215+
const tileset = []
216+
const raw = await fetch(manifest.spritesheetPath)
217+
const blob = await raw.blob()
218+
219+
const spriteSheet = await new Promise((resolve, reject) => {
223220
const img = new Image()
224-
img.src = path + def.file
225-
await new Promise(resolve => {
226-
img.onload = resolve
227-
img.onerror = resolve
228-
})
221+
img.onload = () => resolve(img)
222+
img.onerror = reject
223+
img.src = manifest.spritesheetPath
224+
})
229225

230-
tileset[def.id] = { ...def, triggerAdjacency: def.triggerAdjacency, image: img, images: [] }
231-
232-
if (def.type == "adjacency" || def.type == "rotation") {
233-
const w = img.naturalHeight
234-
if (w > 0) {
235-
const count = Math.floor(img.naturalWidth / w)
236-
for (let i = 0; i < count; i++) {
237-
const canvas = document.createElement('canvas')
238-
canvas.width = w
239-
canvas.height = w
240-
const ctx = canvas.getContext('2d')
241-
ctx.drawImage(img, i * w, 0, w, w, 0, 0, w, w)
242-
243-
const sliceImg = new Image()
244-
sliceImg.src = canvas.toDataURL()
245-
tileset[def.id].images[i] = sliceImg
226+
const tileWidth = manifest.tileWidth
227+
const width = spriteSheet.naturalWidth / tileWidth
228+
console.log(`width: ${width}`)
229+
for (const tile of manifest.tiles) {
230+
const dpr = window.devicePixelRatio
231+
const canvas = document.createElement("canvas")
232+
const ctx = canvas.getContext('2d')
233+
canvas.width = width
234+
canvas.height = width
235+
ctx.imageSmoothingEnabled = false
236+
canvas.style.imageRendering = 'pixelated'
237+
238+
ctx.drawImage(spriteSheet, tile.x * width, tile.y * width, width, width, 0, 0, width, width)
239+
240+
const images = []
241+
242+
if (tile.type === "adjacency") {
243+
for (let i = 0; i < 16; i++) {
244+
const subCanvas = document.createElement("canvas")
245+
const subCtx = subCanvas.getContext("2d")
246+
subCtx.setTransform(dpr, 0, 0, dpr, 0, 0)
247+
subCanvas.width = width
248+
subCanvas.height = width
249+
subCtx.imageSmoothingEnabled = false
250+
subCanvas.style.imageRendering = 'pixelated'
251+
252+
let x = tile.x + i
253+
let y = tile.y
254+
255+
if (x >= tileWidth) {
256+
y = Math.floor(x / tileWidth) + y
257+
x = x % tileWidth
246258
}
259+
subCtx.drawImage(spriteSheet, x * width, y * width, width, width, 0, 0, width, width)
260+
images.push(subCanvas)
261+
}
262+
} else if (tile.type === "rotation") {
263+
for (let i = 0; i < 4; i++) {
264+
const subCanvas = document.createElement("canvas")
265+
const subCtx = subCanvas.getContext("2d")
266+
subCtx.setTransform(dpr, 0, 0, dpr, 0, 0)
267+
subCanvas.width = width
268+
subCanvas.height = width
269+
subCtx.imageSmoothingEnabled = false
270+
subCanvas.style.imageRendering = 'pixelated'
271+
272+
let x = tile.x + i
273+
let y = tile.y
274+
275+
if (x >= tileWidth) {
276+
y = Math.floor(x / tileWidth) + y
277+
x = x % tileWidth
278+
}
279+
subCtx.drawImage(spriteSheet, x * width, y * width, width, width, 0, 0, width, width)
280+
images.push(subCanvas)
247281
}
248282
}
249-
})
250-
await Promise.all(promises)
251-
tilesetMap.set(tilesetPath, tileset)
283+
284+
let minimapColor = 'rgba(0, 0, 0, 0)'
285+
try {
286+
const imgData = ctx.getImageData(0, 0, width, width).data
287+
const colorCounts = {}
288+
let maxCount = 0
289+
for (let i = 0; i < imgData.length; i += 4) {
290+
const r = imgData[i]
291+
const g = imgData[i + 1]
292+
const b = imgData[i + 2]
293+
const a = imgData[i + 3]
294+
295+
if (a < 128) continue
296+
const rgb = `rgb(${r}, ${g}, ${b})`
297+
colorCounts[rgb] = (colorCounts[rgb] || 0) + 1
298+
299+
if (colorCounts[rgb] > maxCount) {
300+
maxCount = colorCounts[rgb]
301+
minimapColor = rgb
302+
}
303+
}
304+
} catch (e) {
305+
console.warn("could not calculate minimap color", e)
306+
}
307+
308+
const tileObject = {
309+
...tile,
310+
minimapColor: minimapColor,
311+
image: canvas,
312+
}
313+
if (images.length > 0) {
314+
tileObject.images = images
315+
}
316+
tileset.push(tileObject)
317+
}
318+
console.log(tileset)
252319
return tileset
253320
}
254321

322+
export async function loadTileset(manifestPath) {
323+
if (manifestPath === "/assets/medium-spritesheet.json") manifestPath = "/assets/medium.json"
324+
if (tilesetMap.has(manifestPath)) {
325+
const tileset = tilesetMap.get(manifestPath)
326+
return tileset
327+
}
328+
329+
const fetchPromise = fetch(manifestPath)
330+
.then(response => response.json())
331+
.then(async (manifest) => {
332+
if (manifest.type == "spritesheet") {
333+
const tileset = await loadSpriteSheetTileset(manifest)
334+
335+
const characterImage = await new Promise((resolve) => {
336+
const img = new Image()
337+
const prefix = manifest.path + "/"
338+
img.onload = () => resolve(img)
339+
img.onerror = (e) => {
340+
console.error(`failed to load character image from: ${srcPath}`, e)
341+
resolve(null)
342+
}
343+
img.src = prefix + manifest.characterFile
344+
})
345+
346+
tilesetMap.set(manifestPath, tileset)
347+
console.log(characterImage)
348+
return tileset
349+
}
350+
351+
let loadedCount = 0
352+
const totalCount = manifest.tiles.length + 1
353+
354+
function updateProgress() {
355+
loadedCount++
356+
window.dispatchEvent(new CustomEvent('loading:progress', {
357+
detail: { loaded: loadedCount, total: totalCount }
358+
}))
359+
}
360+
361+
const promises = manifest.tiles.map(tileData => {
362+
363+
if (!tileData.file) {
364+
updateProgress()
365+
return Promise.resolve(tileData)
366+
}
367+
return new Promise((resolve, reject) => {
368+
const img = new Image()
369+
img.src = manifest.path + tileData.file
370+
img.onload = () => {
371+
const canvas = document.createElement('canvas')
372+
const ctx = canvas.getContext('2d')
373+
canvas.width = img.height || 1
374+
canvas.height = img.height || 1
375+
ctx.drawImage(img, 0, 0)
376+
377+
updateProgress()
378+
resolve({ ...tileData, image: img })
379+
}
380+
img.onerror = (e) => {
381+
updateProgress()
382+
reject(e)
383+
}
384+
})
385+
})
386+
387+
return Promise.all(promises)
388+
.then((items) => {
389+
const tileset = []
390+
items.forEach(item => {
391+
tileset[item.id] = item
392+
})
393+
394+
tilesetMap.set(manifestPath, tileset)
395+
return tileset
396+
})
397+
398+
})
399+
400+
tilesetMap.set(manifestPath, fetchPromise)
401+
402+
return fetchPromise
403+
404+
}
405+
406+
255407
export async function renderLevelPreview(canvas, levelData) {
256408
const tilesetPath = levelData.data ? levelData.data.tilesetPath : "/assets/medium.json"
257409
let tileset = await loadTileset(tilesetPath)

0 commit comments

Comments
 (0)