From e4b9bb4d61e3f7c5d440c92fbc69c9cac7cd487a Mon Sep 17 00:00:00 2001 From: Dennis Postma <dennis@directonline.io> Date: Sat, 1 Feb 2025 15:10:52 +0100 Subject: [PATCH] Refactored Character.vue as preparation for attack anims. --- src/application/enums.ts | 5 + src/components/game/character/Character.vue | 152 +++--------------- .../partials/{Healthbar.vue => HealthBar.vue} | 0 .../useCharacterSpriteComposable.ts | 131 +++++++++++++++ 4 files changed, 157 insertions(+), 131 deletions(-) create mode 100644 src/application/enums.ts rename src/components/game/character/partials/{Healthbar.vue => HealthBar.vue} (100%) create mode 100644 src/composables/useCharacterSpriteComposable.ts diff --git a/src/application/enums.ts b/src/application/enums.ts new file mode 100644 index 0000000..8dd4620 --- /dev/null +++ b/src/application/enums.ts @@ -0,0 +1,5 @@ +export enum Direction { + POSITIVE, + NEGATIVE, + UNCHANGED +} diff --git a/src/components/game/character/Character.vue b/src/components/game/character/Character.vue index 29596bf..f1d2593 100644 --- a/src/components/game/character/Character.vue +++ b/src/components/game/character/Character.vue @@ -1,134 +1,43 @@ <template> <ChatBubble :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" /> - <Healthbar :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" /> + <HealthBar :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" /> <CharacterHair :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" /> <Sprite ref="charSprite" :depth="isometricDepth" :x="currentPositionX" :y="currentPositionY" :origin-y="1" :flipX="isFlippedX" /> </template> <script lang="ts" setup> -import config from '@/application/config' import { type MapCharacter } from '@/application/types' import CharacterHair from '@/components/game/character/partials/CharacterHair.vue' import ChatBubble from '@/components/game/character/partials/ChatBubble.vue' -import Healthbar from '@/components/game/character/partials/Healthbar.vue' -import { loadSpriteTextures } from '@/composables/gameComposable' -import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable' -import { CharacterTypeStorage } from '@/storage/storages' +import HealthBar from '@/components/game/character/partials/HealthBar.vue' +import { useCharacterSprite } from '@/composables/useCharacterSpriteComposable' import { useGameStore } from '@/stores/gameStore' import { useMapStore } from '@/stores/mapStore' -import { refObj, Sprite, useScene } from 'phavuer' -import { computed, onMounted, onUnmounted, ref, watch } from 'vue' - -enum Direction { - POSITIVE, - NEGATIVE, - UNCHANGED -} +import { Sprite, useScene } from 'phavuer' +import { onMounted, onUnmounted, watch } from 'vue' +import { Direction } from '@/application/enums' const props = defineProps<{ tilemap: Phaser.Tilemaps.Tilemap mapCharacter: MapCharacter }>() -const charSprite = refObj<Phaser.GameObjects.Sprite>() -const charSpriteId = ref('') - const gameStore = useGameStore() const mapStore = useMapStore() const scene = useScene() -const currentPositionX = ref(0) -const currentPositionY = ref(0) -const isometricDepth = ref(1) -const isInitialPosition = ref(true) -const tween = ref<Phaser.Tweens.Tween | null>(null) - -const updateIsometricDepth = (positionX: number, positionY: number) => { - isometricDepth.value = calculateIsometricDepth(positionX, positionY, 28, 94, true) -} - -const updatePosition = (positionX: number, positionY: number, direction: Direction) => { - const newPositionX = tileToWorldX(props.tilemap, positionX, positionY) - const newPositionY = tileToWorldY(props.tilemap, positionX, positionY) - - if (isInitialPosition.value) { - currentPositionX.value = newPositionX - currentPositionY.value = newPositionY - isInitialPosition.value = false - return - } - - if (tween.value?.isPlaying()) { - tween.value.stop() - } - - const distance = Math.sqrt(Math.pow(newPositionX - currentPositionX.value, 2) + Math.pow(newPositionY - currentPositionY.value, 2)) - - if (distance >= config.tile_size.width / 1.1) { - currentPositionX.value = newPositionX - currentPositionY.value = newPositionY - return - } - - const duration = distance * 5.7 - - tween.value = props.tilemap.scene.tweens.add({ - targets: { x: currentPositionX.value, y: currentPositionY.value }, - x: newPositionX, - y: newPositionY, - duration, - ease: 'Linear', - onStart: () => { - if (direction === Direction.POSITIVE) { - updateIsometricDepth(positionX, positionY) - } - }, - onUpdate: (tween) => { - // @ts-ignore - currentPositionX.value = tween.targets[0].x - // @ts-ignore - currentPositionY.value = tween.targets[0].y - }, - onComplete: () => { - if (direction === Direction.NEGATIVE) { - updateIsometricDepth(positionX, positionY) - } - } - }) -} - -const calcDirection = (oldPositionX: number, oldPositionY: number, newPositionX: number, newPositionY: number): Direction => { - if (newPositionY < oldPositionY || newPositionX < oldPositionX) return Direction.NEGATIVE - if (newPositionX > oldPositionX || newPositionY > oldPositionY) return Direction.POSITIVE - return Direction.UNCHANGED -} - -const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0)) - -const currentDirection = computed(() => { - return [0, 6].includes(props.mapCharacter.character.rotation ?? 0) ? 'left_up' : 'right_down' -}) - -const currentAction = computed(() => { - return props.mapCharacter.isMoving ? 'walk' : 'idle' -}) - -const charTexture = computed(() => { - const spriteId = charSpriteId.value ?? 'idle_right_down' - return `${spriteId}-${currentAction.value}_${currentDirection.value}` -}) - -const updateSprite = () => { - if (!charSprite.value) return - - if (props.mapCharacter.isMoving) { - charSprite.value.anims.play(charTexture.value, true) - } else { - charSprite.value.anims.stop() - charSprite.value.setFrame(0) - charSprite.value.setTexture(charTexture.value) - } -} +const { + charSprite, + currentPositionX, + currentPositionY, + isometricDepth, + isFlippedX, + updatePosition, + calcDirection, + updateSprite, + initializeSprite, + cleanup +} = useCharacterSprite(scene, props.tilemap, props.mapCharacter) const handlePositionUpdate = (newValues: any, oldValues: any) => { if (!newValues) return @@ -155,34 +64,15 @@ watch( ) onMounted(async () => { - let character = props.mapCharacter.character + await initializeSprite() - const characterTypeStorage = new CharacterTypeStorage() - - const spriteId = await characterTypeStorage.getSpriteId(character.characterType!) - if (!spriteId) return - - charSpriteId.value = spriteId - - await loadSpriteTextures(scene, spriteId) - - if (charSprite.value) { - charSprite.value.setTexture(charTexture.value) - charSprite.value.setFlipX(isFlippedX.value) - charSprite.value.setName(props.mapCharacter.character.name) - } - - if (character.id === gameStore.character!.id) { + if (props.mapCharacter.character.id === gameStore.character!.id) { mapStore.setCharacterLoaded(true) - - // #146 : Set camera position to character, need to be improved still scene.cameras.main.startFollow(charSprite.value as Phaser.GameObjects.Sprite) } - - updatePosition(character.positionX, character.positionY, character.rotation) }) onUnmounted(() => { - tween.value?.stop() + cleanup() }) </script> diff --git a/src/components/game/character/partials/Healthbar.vue b/src/components/game/character/partials/HealthBar.vue similarity index 100% rename from src/components/game/character/partials/Healthbar.vue rename to src/components/game/character/partials/HealthBar.vue diff --git a/src/composables/useCharacterSpriteComposable.ts b/src/composables/useCharacterSpriteComposable.ts new file mode 100644 index 0000000..500d768 --- /dev/null +++ b/src/composables/useCharacterSpriteComposable.ts @@ -0,0 +1,131 @@ +import { type MapCharacter } from '@/application/types' +import { loadSpriteTextures } from '@/composables/gameComposable' +import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable' +import { CharacterTypeStorage } from '@/storage/storages' +import { refObj } from 'phavuer' +import { computed, ref } from 'vue' +import { Direction } from '@/application/enums' + +export function useCharacterSprite(scene: Phaser.Scene, tilemap: Phaser.Tilemaps.Tilemap, mapCharacter: MapCharacter) { + const charSprite = refObj<Phaser.GameObjects.Sprite>() + const charSpriteId = ref('') + const currentPositionX = ref(0) + const currentPositionY = ref(0) + const isometricDepth = ref(1) + const isInitialPosition = ref(true) + const tween = ref<Phaser.Tweens.Tween | null>(null) + + const updateIsometricDepth = (positionX: number, positionY: number) => { + isometricDepth.value = calculateIsometricDepth(positionX, positionY, 28, 94, true) + } + + const updatePosition = (positionX: number, positionY: number, direction: Direction) => { + const newPositionX = tileToWorldX(tilemap, positionX, positionY) + const newPositionY = tileToWorldY(tilemap, positionX, positionY) + + if (isInitialPosition.value) { + currentPositionX.value = newPositionX + currentPositionY.value = newPositionY + isInitialPosition.value = false + return + } + + if (tween.value?.isPlaying()) { + tween.value.stop() + } + + const distance = Math.sqrt(Math.pow(newPositionX - currentPositionX.value, 2) + Math.pow(newPositionY - currentPositionY.value, 2)) + const baseSpeed = 150 // pixels per second + const duration = (distance / baseSpeed) * 1000 // Convert to milliseconds + + tween.value = tilemap.scene.tweens.add({ + targets: charSprite.value, + x: newPositionX, + y: newPositionY, + duration, + ease: 'Linear', + onStart: () => { + if (direction === Direction.POSITIVE) { + updateIsometricDepth(positionX, positionY) + } + }, + onUpdate: () => { + currentPositionX.value = charSprite.value?.x ?? currentPositionX.value + currentPositionY.value = charSprite.value?.y ?? currentPositionY.value + }, + onComplete: () => { + if (direction === Direction.NEGATIVE) { + updateIsometricDepth(positionX, positionY) + } + } + }) + } + + const calcDirection = (oldPositionX: number, oldPositionY: number, newPositionX: number, newPositionY: number): Direction => { + if (newPositionY < oldPositionY || newPositionX < oldPositionX) return Direction.NEGATIVE + if (newPositionX > oldPositionX || newPositionY > oldPositionY) return Direction.POSITIVE + return Direction.UNCHANGED + } + + const isFlippedX = computed(() => [6, 4].includes(mapCharacter.character.rotation ?? 0)) + + const currentDirection = computed(() => { + return [0, 6].includes(mapCharacter.character.rotation ?? 0) ? 'left_up' : 'right_down' + }) + + const currentAction = computed(() => { + return mapCharacter.isMoving ? 'walk' : 'idle' + }) + + const charTexture = computed(() => { + const spriteId = charSpriteId.value ?? 'idle_right_down' + return `${spriteId}-${currentAction.value}_${currentDirection.value}` + }) + + const updateSprite = () => { + if (!charSprite.value) return + + if (mapCharacter.isMoving) { + charSprite.value.anims.play(charTexture.value, true) + } else { + charSprite.value.anims.stop() + charSprite.value.setFrame(0) + charSprite.value.setTexture(charTexture.value) + } + } + + const initializeSprite = async () => { + const characterTypeStorage = new CharacterTypeStorage() + const spriteId = await characterTypeStorage.getSpriteId(mapCharacter.character.characterType!) + if (!spriteId) return + + charSpriteId.value = spriteId + await loadSpriteTextures(scene, spriteId) + + if (charSprite.value) { + charSprite.value.setTexture(charTexture.value) + charSprite.value.setFlipX(isFlippedX.value) + charSprite.value.setName(mapCharacter.character.name) + } + + updatePosition(mapCharacter.character.positionX, mapCharacter.character.positionY, mapCharacter.character.rotation) + } + + const cleanup = () => { + tween.value?.stop() + } + + return { + charSprite, + charSpriteId, + currentPositionX, + currentPositionY, + isometricDepth, + isFlippedX, + updatePosition, + calcDirection, + updateSprite, + initializeSprite, + cleanup + } +} \ No newline at end of file