WIP: updated zone manager to match with zone logic (dynamic asset/texture loading)

This commit is contained in:
Dennis Postma 2024-10-27 21:04:08 +01:00
parent d091aabeb3
commit dc2b6b9851
9 changed files with 124 additions and 96 deletions

View File

@ -1,7 +1,7 @@
<template> <template>
<Tiles @tilemap:create="tileMap = $event" /> <ZoneTiles @tilemap:create="tileMap = $event" />
<Objects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" /> <ZoneObjects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
<EventTiles v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" /> <ZoneEventTiles v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
<Toolbar @save="save" /> <Toolbar @save="save" />
@ -26,9 +26,9 @@ import ObjectList from '@/components/gameMaster/zoneEditor/partials/ObjectList.v
import ZoneSettings from '@/components/gameMaster/zoneEditor/partials/ZoneSettings.vue' import ZoneSettings from '@/components/gameMaster/zoneEditor/partials/ZoneSettings.vue'
import ZoneList from '@/components/gameMaster/zoneEditor/partials/ZoneList.vue' import ZoneList from '@/components/gameMaster/zoneEditor/partials/ZoneList.vue'
import TeleportModal from '@/components/gameMaster/zoneEditor/partials/TeleportModal.vue' import TeleportModal from '@/components/gameMaster/zoneEditor/partials/TeleportModal.vue'
import Tiles from '@/components/gameMaster/zoneEditor/Tiles.vue' import ZoneTiles from '@/components/gameMaster/zoneEditor/ZoneTiles.vue'
import Objects from '@/components/gameMaster/zoneEditor/Objects.vue' import ZoneObjects from '@/components/gameMaster/zoneEditor/ZoneObjects.vue'
import EventTiles from '@/components/gameMaster/zoneEditor/EventTiles.vue' import ZoneEventTiles from '@/components/gameMaster/zoneEditor/ZoneEventTiles.vue'
const gameStore = useGameStore() const gameStore = useGameStore()
const zoneEditorStore = useZoneEditorStore() const zoneEditorStore = useZoneEditorStore()

View File

@ -1,40 +1,27 @@
<template> <template>
<SelectedZoneObject v-if="selectedZoneObject" :zoneObject="selectedZoneObject" @move="moveZoneObject" @rotate="rotateZoneObject" @delete="deleteZoneObject" /> <SelectedZoneObject v-if="selectedZoneObject" :zoneObject="selectedZoneObject" @move="moveZoneObject" @rotate="rotateZoneObject" @delete="deleteZoneObject" />
<Image v-for="object in zoneEditorStore.zone?.zoneObjects" v-bind="getImageProps(object)" @pointerup="() => (selectedZoneObject = object)" /> <ZoneObject v-for="zoneObject in zoneEditorStore.zone?.zoneObjects" :tilemap="tilemap" :zoneObject @pointerup="() => (selectedZoneObject = zoneObject)" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { uuidv4 } from '@/utilities' import { uuidv4 } from '@/utilities'
import { calculateIsometricDepth, getTile, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable' import { getTile } from '@/composables/zoneComposable'
import { Image, useScene } from 'phavuer' import { useScene } from 'phavuer'
import { useZoneEditorStore } from '@/stores/zoneEditorStore' import { useZoneEditorStore } from '@/stores/zoneEditorStore'
import type { ZoneObject } from '@/types'
import SelectedZoneObject from '@/components/gameMaster/zoneEditor/partials/SelectedZoneObject.vue' import SelectedZoneObject from '@/components/gameMaster/zoneEditor/partials/SelectedZoneObject.vue'
import { onMounted, onUnmounted, ref, watch } from 'vue' import { onMounted, onUnmounted, ref, watch } from 'vue'
import ZoneObject from '@/components/gameMaster/zoneEditor/zonePartials/ZoneObject.vue'
import type { ZoneObject as ZoneObjectT } from '@/types'
const scene = useScene() const scene = useScene()
const zoneEditorStore = useZoneEditorStore() const zoneEditorStore = useZoneEditorStore()
const selectedZoneObject = ref<ZoneObject | null>(null) const selectedZoneObject = ref<ZoneObjectT | null>(null)
const movingZoneObject = ref<ZoneObject | null>(null) const movingZoneObject = ref<ZoneObjectT | null>(null)
const props = defineProps<{ const props = defineProps<{
tilemap: Phaser.Tilemaps.Tilemap tilemap: Phaser.Tilemaps.Tilemap
}>() }>()
function getImageProps(zoneObject: ZoneObject) {
return {
alpha: zoneObject.id === movingZoneObject.value?.id ? 0.5 : 1,
depth: calculateIsometricDepth(zoneObject.positionX, zoneObject.positionY, zoneObject.object.frameWidth, zoneObject.object.frameHeight),
tint: selectedZoneObject.value?.id === zoneObject.id ? 0x00ff00 : 0xffffff,
x: tileToWorldX(props.tilemap, zoneObject.positionX, zoneObject.positionY),
y: tileToWorldY(props.tilemap, zoneObject.positionX, zoneObject.positionY),
flipX: zoneObject.isRotated,
texture: zoneObject.object.id,
originY: Number(zoneObject.object.originX),
originX: Number(zoneObject.object.originY)
}
}
function pencil(pointer: Phaser.Input.Pointer) { function pencil(pointer: Phaser.Input.Pointer) {
// Check if zone is set // Check if zone is set
if (!zoneEditorStore.zone) return if (!zoneEditorStore.zone) return

View File

@ -5,21 +5,24 @@
<script setup lang="ts"> <script setup lang="ts">
import config from '@/config' import config from '@/config'
import { useScene } from 'phavuer' import { useScene } from 'phavuer'
import { useGameStore } from '@/stores/gameStore'
import { useZoneEditorStore } from '@/stores/zoneEditorStore' import { useZoneEditorStore } from '@/stores/zoneEditorStore'
import { onMounted, onUnmounted } from 'vue' import { onMounted, onUnmounted } from 'vue'
import { createTileArray, getTile, placeTile, setLayerTiles } from '@/composables/zoneComposable' import { createTileArray, FlattenZoneArray, getTile, placeTile, setLayerTiles } from '@/composables/zoneComposable'
import Controls from '@/components/utilities/Controls.vue' import Controls from '@/components/utilities/Controls.vue'
import { unduplicateArray } from '@/utilities'
const emit = defineEmits(['tilemap:create']) const emit = defineEmits(['tilemap:create'])
const scene = useScene() const scene = useScene()
const gameStore = useGameStore()
const zoneEditorStore = useZoneEditorStore() const zoneEditorStore = useZoneEditorStore()
const zoneTilemap = createTilemap() const zoneTilemap = createTilemap()
const tiles = createTileLayer() const tiles = createTileLayer()
/**
* A Tilemap is a container for Tilemap data.
* This isn't a display object, rather, it holds data about the map and allows you to add tilesets and tilemap layers to it.
* A map can have one or more tilemap layers, which are the display objects that actually render the tiles.
*/
function createTilemap() { function createTilemap() {
const zoneData = new Phaser.Tilemaps.MapData({ const zoneData = new Phaser.Tilemaps.MapData({
width: zoneEditorStore.zone?.width, width: zoneEditorStore.zone?.width,
@ -34,11 +37,19 @@ function createTilemap() {
return tilemap return tilemap
} }
/**
* A Tileset is a combination of a single image containing the tiles and a container for data about each tile.
*/
function createTileLayer() { function createTileLayer() {
const tilesetImages = gameStore.assets.filter((asset) => asset.group === 'tiles').map((asset, index) => zoneTilemap.addTilesetImage(asset.key, asset.key, config.tile_size.x, config.tile_size.y, 1, 2, index + 1, { x: 0, y: -config.tile_size.y })) const tilesArray = unduplicateArray(FlattenZoneArray(zoneEditorStore.zone?.tiles ?? []))
tilesetImages.push(zoneTilemap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.x, config.tile_size.y, 1, 2, 0, { x: 0, y: -config.tile_size.y }))
const layer = zoneTilemap.createBlankLayer('tiles', tilesetImages as any, 0, config.tile_size.y) as Phaser.Tilemaps.TilemapLayer const tilesetImages = Array.from(tilesArray).map((tile: any, index: number) => {
return zoneTilemap.addTilesetImage(tile, tile, config.tile_size.x, config.tile_size.y, 1, 2, index + 1, { x: 0, y: -config.tile_size.y })
}) as any
// Add blank tile
tilesetImages.push(zoneTilemap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.x, config.tile_size.y, 1, 2, 0, { x: 0, y: -config.tile_size.y }))
const layer = zoneTilemap.createBlankLayer('tiles', tilesetImages, 0, config.tile_size.y) as Phaser.Tilemaps.TilemapLayer
layer.setDepth(0) layer.setDepth(0)
layer.setCullPadding(2, 2) layer.setCullPadding(2, 2)

View File

@ -0,0 +1,36 @@
<template>
<Image v-if="isTextureLoaded" v-bind="imageProps" />
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { Image, useScene } from 'phavuer'
import { calculateIsometricDepth, loadZoneObjectTexture, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
import type { ZoneObject } from '@/types'
const props = defineProps<{
tilemap: Phaser.Tilemaps.Tilemap
zoneObject: ZoneObject
}>()
const scene = useScene()
const isTextureLoaded = ref(false)
const imageProps = computed(() => ({
depth: calculateIsometricDepth(props.zoneObject.positionX, props.zoneObject.positionY, props.zoneObject.object.frameWidth, props.zoneObject.object.frameHeight),
x: tileToWorldX(props.tilemap, props.zoneObject.positionX, props.zoneObject.positionY),
y: tileToWorldY(props.tilemap, props.zoneObject.positionX, props.zoneObject.positionY),
flipX: props.zoneObject.isRotated,
texture: props.zoneObject.object.id,
originY: Number(props.zoneObject.object.originX),
originX: Number(props.zoneObject.object.originY)
}))
loadZoneObjectTexture(scene, props.zoneObject.object.id, props.zoneObject.object.updatedAt)
.then((loaded) => {
isTextureLoaded.value = loaded
})
.catch((error) => {
console.error('Error loading texture:', error)
})
</script>

View File

@ -5,15 +5,15 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import { useScene } from 'phavuer' import { useScene } from 'phavuer'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useZoneStore } from '@/stores/zoneStore' import { useZoneStore } from '@/stores/zoneStore'
import { ref, onUnmounted, onMounted } from 'vue' import { loadZoneTilesIntoScene } from '@/composables/zoneComposable'
import type { Character as CharacterT, Zone as ZoneT, ExtendedCharacter as ExtendedCharacterT } from '@/types' import type { Character as CharacterT, Zone as ZoneT, ExtendedCharacter as ExtendedCharacterT } from '@/types'
import ZoneTiles from '@/components/zone/ZoneTiles.vue' import ZoneTiles from '@/components/zone/ZoneTiles.vue'
import ZoneObjects from '@/components/zone/ZoneObjects.vue' import ZoneObjects from '@/components/zone/ZoneObjects.vue'
import Characters from '@/components/zone/Characters.vue' import Characters from '@/components/zone/Characters.vue'
import { loadZoneTiles } from '@/composables/zoneComposable'
const scene = useScene() const scene = useScene()
const gameStore = useGameStore() const gameStore = useGameStore()
@ -36,7 +36,7 @@ gameStore.connection!.on('zone:character:teleport', async (data: zoneLoadData) =
zoneId: data.zone.id zoneId: data.zone.id
}) })
await loadZoneTiles(data.zone, scene) await loadZoneTilesIntoScene(data.zone, scene)
zoneStore.setZone(data.zone) zoneStore.setZone(data.zone)
zoneStore.setCharacters(data.characters) zoneStore.setCharacters(data.characters)
}) })
@ -57,7 +57,7 @@ gameStore.connection!.on('character:move', (data: ExtendedCharacterT) => {
// Emit zone:character:join event to server and wait for response, then set zone and characters // Emit zone:character:join event to server and wait for response, then set zone and characters
gameStore!.connection!.emit('zone:character:join', async (response: zoneLoadData) => { gameStore!.connection!.emit('zone:character:join', async (response: zoneLoadData) => {
await loadZoneTiles(response.zone, scene) await loadZoneTilesIntoScene(response.zone, scene)
zoneStore.setZone(response.zone) zoneStore.setZone(response.zone)
zoneStore.setCharacters(response.characters) zoneStore.setCharacters(response.characters)
}) })

View File

@ -7,13 +7,14 @@ import config from '@/config'
import { useScene } from 'phavuer' import { useScene } from 'phavuer'
import { useZoneStore } from '@/stores/zoneStore' import { useZoneStore } from '@/stores/zoneStore'
import { onBeforeUnmount } from 'vue' import { onBeforeUnmount } from 'vue'
import { setLayerTiles } from '@/composables/zoneComposable' import { FlattenZoneArray, setLayerTiles } from '@/composables/zoneComposable'
import Controls from '@/components/utilities/Controls.vue' import Controls from '@/components/utilities/Controls.vue'
import { unduplicateArray } from '@/utilities'
const emit = defineEmits(['tilemap:create']) const emit = defineEmits(['tilemap:create'])
const zoneStore = useZoneStore()
const scene = useScene() const scene = useScene()
const zoneStore = useZoneStore()
const zoneTilemap = createTilemap() const zoneTilemap = createTilemap()
const tiles = createTileLayer() const tiles = createTileLayer()
@ -40,10 +41,9 @@ function createTilemap() {
* A Tileset is a combination of a single image containing the tiles and a container for data about each tile. * A Tileset is a combination of a single image containing the tiles and a container for data about each tile.
*/ */
function createTileLayer() { function createTileLayer() {
const tilesFromZone = zoneStore.zone?.tiles || [] const tilesArray = unduplicateArray(FlattenZoneArray(zoneStore.zone?.tiles ?? []))
const uniqueTiles = new Set(tilesFromZone.flat().filter(Boolean))
const tilesetImages = Array.from(uniqueTiles).map((tile: any, index: number) => { const tilesetImages = Array.from(tilesArray).map((tile: any, index: number) => {
return zoneTilemap.addTilesetImage(tile, tile, config.tile_size.x, config.tile_size.y, 1, 2, index + 1, { x: 0, y: -config.tile_size.y }) return zoneTilemap.addTilesetImage(tile, tile, config.tile_size.x, config.tile_size.y, 1, 2, index + 1, { x: 0, y: -config.tile_size.y })
}) as any }) as any

View File

@ -85,7 +85,7 @@ export function FlattenZoneArray(tiles: string[][]) {
return normalArray return normalArray
} }
export async function loadZoneTiles(zone: ZoneT, scene: Phaser.Scene) { export async function loadZoneTilesIntoScene(zone: ZoneT, scene: Phaser.Scene) {
const tileArray = unduplicateArray(FlattenZoneArray(zone.tiles)) const tileArray = unduplicateArray(FlattenZoneArray(zone.tiles))
for (const tile of tileArray) { for (const tile of tileArray) {
await loadZoneTileTexture(scene, tile, new Date()) await loadZoneTileTexture(scene, tile, new Date())

View File

@ -2,7 +2,7 @@
<div class="flex justify-center items-center h-dvh relative"> <div class="flex justify-center items-center h-dvh relative">
<Game :config="gameConfig" @create="createGame"> <Game :config="gameConfig" @create="createGame">
<Scene name="main" @preload="preloadScene" @create="createScene"> <Scene name="main" @preload="preloadScene" @create="createScene">
<ZoneEditor v-if="isLoaded" :key="JSON.stringify(`${zoneEditorStore.zone?.id}_${zoneEditorStore.zone?.createdAt}_${zoneEditorStore.zone?.updatedAt}`)" /> <ZoneEditor :key="JSON.stringify(`${zoneEditorStore.zone?.id}_${zoneEditorStore.zone?.createdAt}_${zoneEditorStore.zone?.updatedAt}`)" />
</Scene> </Scene>
</Game> </Game>
</div> </div>
@ -16,17 +16,29 @@ import { Game, Scene } from 'phavuer'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useZoneEditorStore } from '@/stores/zoneEditorStore' import { useZoneEditorStore } from '@/stores/zoneEditorStore'
import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue' import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue'
import AwaitLoaderPlugin from 'phaser3-rex-plugins/plugins/awaitloader-plugin'
import { useAssetManager } from '@/managers/assetManager'
const gameStore = useGameStore() const gameStore = useGameStore()
const zoneEditorStore = useZoneEditorStore() const zoneEditorStore = useZoneEditorStore()
const isLoaded = ref(false) const isLoaded = ref(false)
const assetManager = useAssetManager
const gameConfig = { const gameConfig = {
name: config.name, name: config.name,
width: window.innerWidth, width: window.innerWidth,
height: window.innerHeight, height: window.innerHeight,
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
resolution: 5 resolution: 5,
plugins: {
global: [
{
key: 'rexAwaitLoader',
plugin: AwaitLoaderPlugin,
start: true
}
]
}
} }
const createGame = (game: Phaser.Game) => { const createGame = (game: Phaser.Game) => {
@ -48,43 +60,6 @@ const createGame = (game: Phaser.Game) => {
} }
const preloadScene = async (scene: Phaser.Scene) => { const preloadScene = async (scene: Phaser.Scene) => {
isLoaded.value = false
/**
* Create loading bar
*/
const width = scene.cameras.main.width
const height = scene.cameras.main.height
const progressBox = scene.add.graphics()
const progressBar = scene.add.graphics()
progressBox.fillStyle(0x222222, 0.8)
progressBox.fillRect(width / 2 - 180, height / 2, 320, 50)
const loadingText = scene.make.text({
x: width / 2,
y: height / 2 - 50,
text: 'Loading...',
style: {
font: '20px monospace',
fill: '#ffffff'
}
})
loadingText.setOrigin(0.5, 0.5)
scene.load.on(Phaser.Loader.Events.PROGRESS, function (value: any) {
progressBar.clear()
progressBar.fillStyle(0x368f8b, 1)
progressBar.fillRect(width / 2 - 180 + 10, height / 2 + 10, 300 * value, 30)
})
scene.load.on(Phaser.Loader.Events.COMPLETE, function () {
progressBar.destroy()
progressBox.destroy()
loadingText.destroy()
isLoaded.value = true
})
/** /**
* Load the base assets into the Phaser scene * Load the base assets into the Phaser scene
*/ */
@ -92,6 +67,29 @@ const preloadScene = async (scene: Phaser.Scene) => {
scene.load.image('TELEPORT', '/assets/zone/tp_tile.png') scene.load.image('TELEPORT', '/assets/zone/tp_tile.png')
scene.load.image('blank_tile', '/assets/zone/blank_tile.png') scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
scene.load.image('waypoint', '/assets/waypoint.png') scene.load.image('waypoint', '/assets/waypoint.png')
/**
* We're using rex-await-loader to load assets asynchronously
* Phaser does not support this out of the box, so we're using this plugin
*/
// scene.load.rexAwait(async function (successCallback) {
// await assetManager.getAssetsByGroup('tiles').then((assets) => {
// assets.forEach((asset) => {
// if (scene.load.textureManager.exists(asset.key)) return
// scene.textures.addBase64(asset.key, asset.data)
// })
// })
//
// // Load objects
// await assetManager.getAssetsByGroup('objects').then((assets) => {
// assets.forEach((asset) => {
// if (scene.load.textureManager.exists(asset.key)) return
// scene.textures.addBase64(asset.key, asset.data)
// })
// })
//
// successCallback()
// })
} }
const createScene = async (scene: Phaser.Scene) => { const createScene = async (scene: Phaser.Scene) => {
@ -99,19 +97,15 @@ const createScene = async (scene: Phaser.Scene) => {
* Create sprite animations * Create sprite animations
* This is done here because phaser forces us to * This is done here because phaser forces us to
*/ */
gameStore.assets.forEach((asset) => { // assetManager.getAssetsByGroup('sprite_animations').then((assets) => {
if (asset.group !== 'sprite_animations') return // assets.forEach((asset) => {
// scene.anims.create({
scene.anims.create({ // key: asset.key,
key: asset.key, // frameRate: 7,
frameRate: 7, // frames: scene.anims.generateFrameNumbers(asset.key, { start: 0, end: asset.frameCount! - 1 }),
frames: scene.anims.generateFrameNumbers(asset.key, { start: 0, end: asset.frameCount! - 1 }), // repeat: -1
repeat: -1 // })
}) // })
}) // })
} }
onBeforeUnmount(() => {
isLoaded.value = false
})
</script> </script>