Map editor tiles are now fully loaded from cache

This commit is contained in:
Dennis Postma 2025-01-13 10:52:40 +01:00
parent 367d536c52
commit 8f07cf5093
6 changed files with 107 additions and 44 deletions

View File

@ -1,6 +1,6 @@
<template> <template>
<MapTiles @tileMap:create="tileMap = $event" /> <MapTiles @tileMap:create="tileMap = $event" />
<MapObjects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" /> <!-- <MapObjects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />-->
<MapEventTiles v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" /> <MapEventTiles v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
<Toolbar @save="save" @clear="clear" /> <Toolbar @save="save" @clear="clear" />
@ -24,12 +24,15 @@ import MapSettings from '@/components/gameMaster/mapEditor/partials/MapSettings.
import TeleportModal from '@/components/gameMaster/mapEditor/partials/TeleportModal.vue' import TeleportModal from '@/components/gameMaster/mapEditor/partials/TeleportModal.vue'
import TileList from '@/components/gameMaster/mapEditor/partials/TileList.vue' import TileList from '@/components/gameMaster/mapEditor/partials/TileList.vue'
import Toolbar from '@/components/gameMaster/mapEditor/partials/Toolbar.vue' import Toolbar from '@/components/gameMaster/mapEditor/partials/Toolbar.vue'
import { loadAllTilesIntoScene } from '@/composables/mapComposable'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorStore } from '@/stores/mapEditorStore'
import { onUnmounted, shallowRef } from 'vue' import { useScene } from 'phavuer'
import { onBeforeMount, onUnmounted, shallowRef } from 'vue'
const gameStore = useGameStore() const gameStore = useGameStore()
const mapEditorStore = useMapEditorStore() const mapEditorStore = useMapEditorStore()
const scene = useScene()
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>() const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()

View File

@ -1,24 +1,18 @@
<template> <template>
<Controls :layer="tileLayer" :depth="0" /> <Controls v-if="tileLayer" :layer="tileLayer" :depth="0" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import config from '@/application/config' import config from '@/application/config'
import type { TextureData } from '@/application/types' import type { TextureData } from '@/application/types'
import Controls from '@/components/utilities/Controls.vue' import Controls from '@/components/utilities/Controls.vue'
import { import { createTileArray, FlattenMapArray, getTile, loadAllTilesIntoScene, loadMapTilesIntoScene, placeTile, setLayerTiles } from '@/composables/mapComposable'
createTileArray, import { TileStorage } from '@/storage/storages'
FlattenMapArray,
getTile,
loadMapTilesIntoScene,
placeTile,
setLayerTiles
} from '@/composables/mapComposable'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorStore } from '@/stores/mapEditorStore'
import { useScene } from 'phavuer' import { useScene } from 'phavuer'
import { onMounted, onUnmounted, shallowRef, watch } from 'vue' import { onBeforeMount, onMounted, onUnmounted, shallowRef, watch } from 'vue'
import { TileStorage } from '@/storage/storages'
import Tileset = Phaser.Tilemaps.Tileset import Tileset = Phaser.Tilemaps.Tileset
const emit = defineEmits(['tileMap:create']) const emit = defineEmits(['tileMap:create'])
@ -50,15 +44,11 @@ async function createTileLayer(currentTileMap: Phaser.Tilemaps.Tilemap) {
const tilesetImages = [] const tilesetImages = []
for (const tile of tiles) { for (const tile of tiles) {
tilesetImages.push( tilesetImages.push(currentTileMap.addTilesetImage(tile.id, tile.id, config.tile_size.width, config.tile_size.height, 1, 2, tilesetImages.length + 1, { x: 0, y: -config.tile_size.height }))
currentTileMap.addTilesetImage(tile.id, tile.id, config.tile_size.width, config.tile_size.height, 1, 2, tilesetImages.length + 1, { x: 0, y: -config.tile_size.height })
)
} }
// Add blank tile // Add blank tile
tilesetImages.push( tilesetImages.push(currentTileMap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.width, config.tile_size.height, 1, 2, 0, { x: 0, y: -config.tile_size.height }))
currentTileMap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.width, config.tile_size.height, 1, 2, 0, { x: 0, y: -config.tile_size.height })
)
const layer = currentTileMap.createBlankLayer('tiles', tilesetImages as Tileset[], 0, config.tile_size.height) as Phaser.Tilemaps.TilemapLayer const layer = currentTileMap.createBlankLayer('tiles', tilesetImages as Tileset[], 0, config.tile_size.height) as Phaser.Tilemaps.TilemapLayer
@ -189,7 +179,9 @@ function tilePicker(pointer: Phaser.Input.Pointer) {
mapEditorStore.setSelectedTile(mapEditorStore.map.tiles[tile.y][tile.x]) mapEditorStore.setSelectedTile(mapEditorStore.map.tiles[tile.y][tile.x])
} }
watch(() => mapEditorStore.shouldClearTiles, (shouldClear) => { watch(
() => mapEditorStore.shouldClearTiles,
(shouldClear) => {
if (shouldClear && mapEditorStore.map && tileMap.value && tileLayer.value) { if (shouldClear && mapEditorStore.map && tileMap.value && tileLayer.value) {
const blankTiles = createTileArray(tileMap.value.width, tileMap.value.height, 'blank_tile') const blankTiles = createTileArray(tileMap.value.width, tileMap.value.height, 'blank_tile')
setLayerTiles(tileMap.value, tileLayer.value, blankTiles) setLayerTiles(tileMap.value, tileLayer.value, blankTiles)
@ -202,8 +194,6 @@ watch(() => mapEditorStore.shouldClearTiles, (shouldClear) => {
onMounted(async () => { onMounted(async () => {
if (!mapEditorStore.map?.tiles) return if (!mapEditorStore.map?.tiles) return
await loadMapTilesIntoScene(mapEditorStore.map.id, scene)
tileMap.value = createTileMap() tileMap.value = createTileMap()
tileLayer.value = await createTileLayer(tileMap.value) tileLayer.value = await createTileLayer(tileMap.value)
@ -228,6 +218,10 @@ onMounted(async () => {
scene.input.on(Phaser.Input.Events.POINTER_DOWN, tilePicker) scene.input.on(Phaser.Input.Events.POINTER_DOWN, tilePicker)
}) })
onBeforeMount(async () => {
await loadAllTilesIntoScene(scene)
})
onUnmounted(() => { onUnmounted(() => {
scene.input.off(Phaser.Input.Events.POINTER_MOVE, pencil) scene.input.off(Phaser.Input.Events.POINTER_MOVE, pencil)
scene.input.off(Phaser.Input.Events.POINTER_MOVE, eraser) scene.input.off(Phaser.Input.Events.POINTER_MOVE, eraser)

View File

@ -12,6 +12,7 @@
import config from '@/application/config' import config from '@/application/config'
import 'phaser' import 'phaser'
import MapEditor from '@/components/gameMaster/mapEditor/MapEditor.vue' import MapEditor from '@/components/gameMaster/mapEditor/MapEditor.vue'
import { loadAllTilesIntoScene } from '@/composables/mapComposable'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorStore } from '@/stores/mapEditorStore'
import { Game, Scene } from 'phavuer' import { Game, Scene } from 'phavuer'

View File

@ -2,7 +2,7 @@ import config from '@/application/config'
import type { HttpResponse, TextureData, UUID } from '@/application/types' import type { HttpResponse, TextureData, UUID } from '@/application/types'
import { unduplicateArray } from '@/application/utilities' import { unduplicateArray } from '@/application/utilities'
import { loadTexture } from '@/composables/gameComposable' import { loadTexture } from '@/composables/gameComposable'
import { MapStorage } from '@/storage/storages' import { MapStorage, TileStorage } from '@/storage/storages'
import Tilemap = Phaser.Tilemaps.Tilemap import Tilemap = Phaser.Tilemaps.Tilemap
import TilemapLayer = Phaser.Tilemaps.TilemapLayer import TilemapLayer = Phaser.Tilemaps.TilemapLayer
@ -88,19 +88,54 @@ export function FlattenMapArray(tiles: string[][]) {
} }
export async function loadMapTilesIntoScene(map_id: UUID, scene: Phaser.Scene) { export async function loadMapTilesIntoScene(map_id: UUID, scene: Phaser.Scene) {
const tileStorage = new TileStorage()
const mapStorage = new MapStorage() const mapStorage = new MapStorage()
const map = await mapStorage.get(map_id) const map = await mapStorage.get(map_id)
if (!map) return if (!map) return
const tileArray = unduplicateArray(FlattenMapArray(map.tiles)) const tileArray = unduplicateArray(FlattenMapArray(map.tiles))
const tiles = await tileStorage.getByIds(tileArray)
// Load each tile into the scene // Load each tile into the scene
for (const tile of tileArray) { for (const tile of tiles) {
const textureData = { const textureData = {
key: tile, key: tile.id,
data: '/textures/tiles/' + tile + '.png', data: '/textures/tiles/' + tile.id + '.png',
group: 'tiles', group: 'tiles',
updatedAt: map.updatedAt // @TODO: Fix this updatedAt: tile.updatedAt
} as TextureData
await loadTexture(scene, textureData)
}
}
export async function loadTilesIntoScene(tileIds: string[], scene: Phaser.Scene) {
const tileStorage = new TileStorage()
const tiles = await tileStorage.getByIds(tileIds)
// Load each tile into the scene
for (const tile of tiles) {
const textureData = {
key: tile.id,
data: '/textures/tiles/' + tile.id + '.png',
group: 'tiles',
updatedAt: tile.updatedAt
} as TextureData
await loadTexture(scene, textureData)
}
}
export async function loadAllTilesIntoScene(scene: Phaser.Scene) {
const tileStorage = new TileStorage()
const tiles = await tileStorage.getAll()
// Load each tile into the scene
for (const tile of tiles) {
const textureData = {
key: tile.id,
data: '/textures/tiles/' + tile.id + '.png',
group: 'tiles',
updatedAt: tile.updatedAt
} as TextureData } as TextureData
await loadTexture(scene, textureData) await loadTexture(scene, textureData)
} }

View File

@ -2,41 +2,57 @@ import config from '@/application/config'
import { getTile, tileToWorldXY } from '@/composables/mapComposable' import { getTile, tileToWorldXY } from '@/composables/mapComposable'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorStore } from '@/stores/mapEditorStore'
import { computed, type Ref } from 'vue' import { computed, ref, type Ref } from 'vue'
export function useMapEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) { export function useMapEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
const gameStore = useGameStore() const gameStore = useGameStore()
const mapEditorStore = useMapEditorStore() const mapEditorStore = useMapEditorStore()
const isMoveTool = computed(() => mapEditorStore.tool === 'move') const isMoveTool = computed(() => mapEditorStore.tool === 'move')
const pointerStartPosition = ref({ x: 0, y: 0 })
const dragThreshold = 5 // pixels
function updateWaypoint(pointer: Phaser.Input.Pointer) { function updateWaypoint(worldX: number, worldY: number) {
const { x: px, y: py } = camera.getWorldPoint(pointer.x, pointer.y) const pointerTile = getTile(layer, worldX, worldY)
const pointerTile = getTile(layer, px, py) if (!pointerTile) {
if (pointerTile) {
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
if (!worldPoint.positionX || !worldPoint.positionY) return
waypoint.value = {
visible: true,
x: worldPoint.positionX,
y: worldPoint.positionY + config.tile_size.height + 15
}
} else {
waypoint.value.visible = false waypoint.value.visible = false
return
}
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
if (!worldPoint.worldPositionX || !worldPoint.worldPositionX) return
waypoint.value = {
visible: true,
x: worldPoint.worldPositionX,
y: worldPoint.worldPositionY + config.tile_size.height + 15
}
}
function handlePointerDown(pointer: Phaser.Input.Pointer) {
pointerStartPosition.value = { x: pointer.x, y: pointer.y }
if (isMoveTool.value || pointer.event.shiftKey) {
gameStore.setPlayerDraggingCamera(true)
} }
} }
function dragMap(pointer: Phaser.Input.Pointer) { function dragMap(pointer: Phaser.Input.Pointer) {
if (!gameStore.game.isPlayerDraggingCamera) return if (!gameStore.game.isPlayerDraggingCamera) return
camera.setScroll(camera.scrollX - (pointer.x - pointer.prevPosition.x) / camera.zoom, scrollY - (pointer.y - pointer.prevPosition.y) / camera.zoom)
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
if (distance <= dragThreshold) return
camera.setScroll(camera.scrollX - (pointer.x - pointer.prevPosition.x) / camera.zoom, camera.scrollY - (pointer.y - pointer.prevPosition.y) / camera.zoom)
} }
function handlePointerMove(pointer: Phaser.Input.Pointer) { function handlePointerMove(pointer: Phaser.Input.Pointer) {
if (isMoveTool.value || pointer.event.shiftKey) { if (isMoveTool.value || pointer.event.shiftKey) {
dragMap(pointer) dragMap(pointer)
} }
updateWaypoint(pointer) updateWaypoint(pointer.worldX, pointer.worldY)
}
function handlePointerUp(pointer: Phaser.Input.Pointer) {
gameStore.setPlayerDraggingCamera(false)
} }
function handleZoom(pointer: Phaser.Input.Pointer) { function handleZoom(pointer: Phaser.Input.Pointer) {
@ -50,12 +66,16 @@ export function useMapEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.T
} }
const setupPointerHandlers = () => { const setupPointerHandlers = () => {
scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove) scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
scene.input.on(Phaser.Input.Events.POINTER_WHEEL, handleZoom) scene.input.on(Phaser.Input.Events.POINTER_WHEEL, handleZoom)
} }
const cleanupPointerHandlers = () => { const cleanupPointerHandlers = () => {
scene.input.off(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove) scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
scene.input.off(Phaser.Input.Events.POINTER_UP, handlePointerUp)
scene.input.off(Phaser.Input.Events.POINTER_WHEEL, handleZoom) scene.input.off(Phaser.Input.Events.POINTER_WHEEL, handleZoom)
} }

View File

@ -33,6 +33,16 @@ export class BaseStorage<T extends { id: string }> {
} }
} }
async getByIds(ids: string[]): Promise<T[]> {
try {
const items = await this.dexie.table(this.tableName).bulkGet(ids)
return items.filter((item) => item !== null)
} catch (error) {
console.error(`Failed to retrieve ${this.tableName} by ids:`, error)
return []
}
}
async getAll(): Promise<T[]> { async getAll(): Promise<T[]> {
try { try {
return await this.dexie.table(this.tableName).toArray() return await this.dexie.table(this.tableName).toArray()