forked from noxious/client
214 lines
6.6 KiB
Vue
214 lines
6.6 KiB
Vue
<template>
|
|
<!-- Chat bubble -->
|
|
<Container ref="charChatContainer" :depth="999" :x="currentX" :y="currentY">
|
|
<RoundRectangle @create="createChatBubble" :origin-x="0.5" :origin-y="7.5" :fillColor="0xffffff" :width="194" :height="21" :radius="20" />
|
|
<Text @create="createChatText" :style="{ fontSize: 13, fontFamily: 'Arial', color: '#000' }" />
|
|
</Container>
|
|
<!-- Character name and health -->
|
|
<Container :depth="999" :x="currentX" :y="currentY">
|
|
<Text @create="createNicknameText" :text="character.name" />
|
|
<RoundRectangle :origin-x="0.5" :origin-y="18.5" :fillColor="0xffffff" :width="74" :height="6" :radius="5" />
|
|
<RoundRectangle :origin-x="0.5" :origin-y="36.4" :fillColor="0x00b3b3" :width="70" :height="3" :radius="5" />
|
|
</Container>
|
|
<!-- Character sprite -->
|
|
<Container ref="charContainer" :depth="isometricDepth" :x="currentX" :y="currentY">
|
|
<Sprite ref="charSprite" :origin-y="1" :flipX="isFlippedX" :flipY="false" />
|
|
</Container>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import config from '@/config'
|
|
import { type ExtendedCharacter, type Sprite as SpriteT } from '@/types'
|
|
import { useGameStore } from '@/stores/gameStore'
|
|
import { useZoneStore } from '@/stores/zoneStore'
|
|
import { watch, computed, ref, onMounted, onUnmounted } from 'vue'
|
|
import { Container, refObj, RoundRectangle, Sprite, Text, useGame, useScene } from 'phavuer'
|
|
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
|
import { loadSpriteTextures } from '@/composables/gameComposable'
|
|
|
|
enum Direction {
|
|
POSITIVE,
|
|
NEGATIVE,
|
|
UNCHANGED
|
|
}
|
|
|
|
const props = defineProps<{
|
|
layer: Phaser.Tilemaps.TilemapLayer
|
|
character: ExtendedCharacter
|
|
}>()
|
|
|
|
const charChatContainer = refObj<Phaser.GameObjects.Container>()
|
|
const charContainer = refObj<Phaser.GameObjects.Container>()
|
|
const charSprite = refObj<Phaser.GameObjects.Sprite>()
|
|
|
|
const game = useGame()
|
|
const gameStore = useGameStore()
|
|
const zoneStore = useZoneStore()
|
|
const scene = useScene()
|
|
|
|
const currentX = ref(0)
|
|
const currentY = ref(0)
|
|
const isometricDepth = ref(1)
|
|
const isInitialPosition = ref(true)
|
|
const tween = ref<Phaser.Tweens.Tween | null>(null)
|
|
|
|
const updateIsometricDepth = (x: number, y: number) => {
|
|
isometricDepth.value = calculateIsometricDepth(x, y, 28, 94, true)
|
|
}
|
|
|
|
const updatePosition = (x: number, y: number, direction: Direction) => {
|
|
const targetX = tileToWorldX(props.layer, x, y)
|
|
const targetY = tileToWorldY(props.layer, x, y)
|
|
|
|
if (isInitialPosition.value) {
|
|
currentX.value = targetX
|
|
currentY.value = targetY
|
|
isInitialPosition.value = false
|
|
return
|
|
}
|
|
|
|
if (tween.value?.isPlaying()) {
|
|
tween.value.stop()
|
|
}
|
|
|
|
const distance = Math.sqrt(Math.pow(targetX - currentX.value, 2) + Math.pow(targetY - currentY.value, 2))
|
|
|
|
if (distance >= config.tile_size.x / 1.1) {
|
|
currentX.value = targetX
|
|
currentY.value = targetY
|
|
return
|
|
}
|
|
|
|
const duration = distance * 6
|
|
|
|
tween.value = props.layer.scene.tweens.add({
|
|
targets: { x: currentX.value, y: currentY.value },
|
|
x: targetX,
|
|
y: targetY,
|
|
duration,
|
|
ease: 'Linear',
|
|
onStart: () => {
|
|
if (direction === Direction.POSITIVE) {
|
|
updateIsometricDepth(x, y)
|
|
}
|
|
},
|
|
onUpdate: (tween) => {
|
|
currentX.value = tween.targets[0].x
|
|
currentY.value = tween.targets[0].y
|
|
},
|
|
onComplete: () => {
|
|
if (direction === Direction.NEGATIVE) {
|
|
updateIsometricDepth(x, y)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const calcDirection = (oldX: number, oldY: number, newX: number, newY: number): Direction => {
|
|
if (newY < oldY || newX < oldX) return Direction.NEGATIVE
|
|
if (newX > oldX || newY > oldY) return Direction.POSITIVE
|
|
return Direction.UNCHANGED
|
|
}
|
|
|
|
const isFlippedX = computed(() => [6, 4].includes(props.character.rotation ?? 0))
|
|
|
|
const charTexture = computed(() => {
|
|
const { rotation, characterType, isMoving } = props.character
|
|
const spriteId = characterType?.sprite?.id ?? 'idle_right_down'
|
|
const action = isMoving ? 'walk' : 'idle'
|
|
const direction = [0, 6].includes(rotation) ? 'left_up' : 'right_down'
|
|
|
|
return `${spriteId}-${action}_${direction}`
|
|
})
|
|
|
|
const updateSprite = () => {
|
|
if (props.character.isMoving) {
|
|
charSprite.value!.anims.play(charTexture.value, true)
|
|
return
|
|
}
|
|
|
|
charSprite.value!.anims.stop()
|
|
charSprite.value!.setFrame(0)
|
|
charSprite.value!.setTexture(charTexture.value)
|
|
}
|
|
|
|
const createChatBubble = (container: Phaser.GameObjects.Container) => {
|
|
container.setName(`${props.character.name}_chatBubble`)
|
|
}
|
|
|
|
const createChatText = (text: Phaser.GameObjects.Text) => {
|
|
text.setName(`${props.character.name}_chatText`)
|
|
text.setFontSize(13)
|
|
text.setFontFamily('Arial')
|
|
text.setOrigin(0.5, 10.9)
|
|
|
|
// Fix text alignment on Windows and Android
|
|
if (game.device.os.windows || game.device.os.android) {
|
|
text.setOrigin(0.5, 9.75)
|
|
|
|
if (game.device.browser.firefox) {
|
|
text.setOrigin(0.5, 10.9)
|
|
}
|
|
}
|
|
}
|
|
|
|
const createNicknameText = (text: Phaser.GameObjects.Text) => {
|
|
text.setFontSize(13)
|
|
text.setFontFamily('Arial')
|
|
text.setOrigin(0.5, 9)
|
|
|
|
// Fix text alignment on Windows and Android
|
|
if (game.device.os.windows || game.device.os.android) {
|
|
text.setOrigin(0.5, 8)
|
|
|
|
if (game.device.browser.firefox) {
|
|
text.setOrigin(0.5, 9)
|
|
}
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => props.character,
|
|
(newChar, oldChar) => {
|
|
if (!newChar) return
|
|
|
|
if (!oldChar || newChar.positionX !== oldChar.positionX || newChar.positionY !== oldChar.positionY) {
|
|
const direction = !oldChar ? Direction.POSITIVE : calcDirection(oldChar.positionX, oldChar.positionY, newChar.positionX, newChar.positionY)
|
|
updatePosition(newChar.positionX, newChar.positionY, direction)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(() => props.character.isMoving, updateSprite)
|
|
watch(() => props.character.rotation, updateSprite)
|
|
|
|
loadSpriteTextures(scene, props.character.characterType?.sprite as SpriteT)
|
|
.then(() => {
|
|
charSprite.value!.setTexture(charTexture.value)
|
|
charSprite.value!.setFlipX(isFlippedX.value)
|
|
})
|
|
.catch((error) => {
|
|
console.error('Error loading texture:', error)
|
|
})
|
|
|
|
onMounted(() => {
|
|
charChatContainer.value!.setName(`${props.character!.name}_chatContainer`)
|
|
charChatContainer.value!.setVisible(false)
|
|
charContainer.value!.setName(props.character!.name)
|
|
|
|
if (props.character.id === gameStore.character!.id) {
|
|
zoneStore.setCharacterLoaded(true)
|
|
|
|
// #146 : Set camera position to character, need to be improved still
|
|
scene.cameras.main.startFollow(charContainer.value as Phaser.GameObjects.Container)
|
|
scene.cameras.main.stopFollow()
|
|
}
|
|
|
|
updatePosition(props.character.positionX, props.character.positionY, props.character.rotation)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
tween.value?.stop()
|
|
})
|
|
</script>
|