forked from noxious/client
Character hair refactor, enhancements
This commit is contained in:
parent
51e885cfdf
commit
d85bf4846b
6
package-lock.json
generated
6
package-lock.json
generated
@ -4618,9 +4618,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.2",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||||
"integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==",
|
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
@ -210,6 +210,8 @@ export enum CharacterEquipmentSlotType {
|
|||||||
export type Sprite = {
|
export type Sprite = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
width: number | null
|
||||||
|
height: number | null
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
spriteActions: SpriteAction[]
|
spriteActions: SpriteAction[]
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Container ref="characterContainer" :x="currentPositionX" :y="currentPositionY" :depth="isometricDepth">
|
<Container ref="characterContainer" :x="currentPositionX" :y="currentPositionY" :depth="isometricDepth">
|
||||||
<ChatBubble :mapCharacter="props.mapCharacter" />
|
<ChatBubble :mapCharacter="props.mapCharacter" />
|
||||||
<HealthBar :mapCharacter="props.mapCharacter" />
|
<HealthBar :mapCharacter="props.mapCharacter" />
|
||||||
<CharacterHair :mapCharacter="props.mapCharacter" />
|
<CharacterHair :mapCharacter="props.mapCharacter" :flipX="isFlippedX" />
|
||||||
<Sprite ref="characterSprite" :flipX="isFlippedX" />
|
<Sprite ref="characterSprite" :flipX="isFlippedX" />
|
||||||
</Container>
|
</Container>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,49 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<Image v-bind="imageProps" v-if="hairSpriteId && gameStore.isTextureLoaded(texture)" />
|
<Image ref="image" v-if="hairSpriteId" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { MapCharacter, Sprite as SpriteT } from '@/application/types'
|
import type { MapCharacter, Sprite as SpriteT } from '@/application/types'
|
||||||
import { loadSpriteTextures } from '@/services/textureService'
|
import { loadSpriteTextures } from '@/services/textureService'
|
||||||
import { CharacterHairStorage, CharacterTypeStorage, SpriteStorage } from '@/storage/storages'
|
import { CharacterHairStorage, CharacterTypeStorage, SpriteStorage } from '@/storage/storages'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { Image, refObj, useScene } from 'phavuer'
|
||||||
import { Image, useScene } from 'phavuer'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mapCharacter: MapCharacter
|
mapCharacter: MapCharacter
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
const hairSpriteId = ref('')
|
const hairSpriteId = ref('')
|
||||||
const hairSprite = ref<SpriteT | null>(null)
|
const hairSprite = ref<SpriteT | null>(null)
|
||||||
const characterSpriteHeight = ref(0)
|
const characterSpriteHeight = ref(0)
|
||||||
|
const image = refObj<Phaser.GameObjects.Image>()
|
||||||
|
|
||||||
|
const flipX = computed(() => [6, 0].includes(props.mapCharacter.character.rotation ?? 0))
|
||||||
const texture = computed(() => {
|
const texture = computed(() => {
|
||||||
const { rotation } = props.mapCharacter.character
|
const direction = flipX.value ? 'back' : 'front'
|
||||||
const direction = [0, 6].includes(rotation) ? 'back' : 'front'
|
|
||||||
|
|
||||||
return `${hairSpriteId.value}-${direction}`
|
return `${hairSpriteId.value}-${direction}`
|
||||||
})
|
})
|
||||||
|
|
||||||
const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0))
|
watch(
|
||||||
|
() => props.mapCharacter.character,
|
||||||
const imageProps = computed(() => {
|
(newValue) => {
|
||||||
const direction = [0, 6].includes(props.mapCharacter.character.rotation ?? 0) ? 'back' : 'front'
|
if (!image.value) return
|
||||||
const spriteAction = hairSprite.value?.spriteActions?.find((spriteAction) => spriteAction.action === direction)
|
image.value.setTexture(texture.value)
|
||||||
|
},
|
||||||
const hairHeight = (spriteAction?.frameHeight ?? 0) + (spriteAction?.originY ?? 0)
|
{ deep: true }
|
||||||
const originY = characterSpriteHeight.value / hairHeight
|
)
|
||||||
|
|
||||||
return {
|
|
||||||
depth: 9999,
|
|
||||||
originX: 0.5, // This is always center
|
|
||||||
originY: originY, // @TODO #376: See if we can fully calculate this
|
|
||||||
flipX: isFlippedX.value,
|
|
||||||
texture: texture.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!props.mapCharacter.character.characterType || !props.mapCharacter.character.characterHair) return
|
if (!props.mapCharacter.character.characterType || !props.mapCharacter.character.characterHair) return
|
||||||
@ -63,5 +53,11 @@ onMounted(async () => {
|
|||||||
if (!hairSprite.value) return
|
if (!hairSprite.value) return
|
||||||
|
|
||||||
await loadSpriteTextures(scene, hairSpriteId.value)
|
await loadSpriteTextures(scene, hairSpriteId.value)
|
||||||
|
|
||||||
|
if (!image.value) return
|
||||||
|
|
||||||
|
image.value.setOrigin(0.5, 2.15)
|
||||||
|
image.value.setTexture(texture.value)
|
||||||
|
image.value.setSize(30, 40)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -7,6 +7,15 @@
|
|||||||
<input v-model="spriteName" class="input-field" type="text" name="name" placeholder="New sprite" />
|
<input v-model="spriteName" class="input-field" type="text" name="name" placeholder="New sprite" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-field-half">
|
||||||
|
<label class="mb-1.5 font-titles" for="name">Width override</label>
|
||||||
|
<input v-model="spriteWidth" class="input-field" type="number" name="width" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field-half">
|
||||||
|
<label class="mb-1.5 font-titles" for="name">Height override</label>
|
||||||
|
<input v-model="spriteHeight" class="input-field" type="number" name="height" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="w-full flex gap-2 mt-2 pb-4 relative">
|
<div class="w-full flex gap-2 mt-2 pb-4 relative">
|
||||||
<button class="btn-cyan px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
|
<button class="btn-cyan px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
|
||||||
<button class="btn-red px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="deleteSprite">Delete</button>
|
<button class="btn-red px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="deleteSprite">Delete</button>
|
||||||
@ -84,6 +93,8 @@ const assetManagerStore = useAssetManagerStore()
|
|||||||
const selectedSprite = computed(() => assetManagerStore.selectedSprite)
|
const selectedSprite = computed(() => assetManagerStore.selectedSprite)
|
||||||
|
|
||||||
const spriteName = ref('')
|
const spriteName = ref('')
|
||||||
|
const spriteWidth = ref(0)
|
||||||
|
const spriteHeight = ref(0)
|
||||||
const spriteActions = ref<SpriteAction[]>([])
|
const spriteActions = ref<SpriteAction[]>([])
|
||||||
const isModalOpen = ref(false)
|
const isModalOpen = ref(false)
|
||||||
const selectedAction = ref<SpriteAction | null>(null)
|
const selectedAction = ref<SpriteAction | null>(null)
|
||||||
@ -94,6 +105,8 @@ if (!selectedSprite.value) {
|
|||||||
|
|
||||||
if (selectedSprite.value) {
|
if (selectedSprite.value) {
|
||||||
spriteName.value = selectedSprite.value.name
|
spriteName.value = selectedSprite.value.name
|
||||||
|
spriteWidth.value = selectedSprite.value.width
|
||||||
|
spriteHeight.value = selectedSprite.value.height
|
||||||
spriteActions.value = sortSpriteActions(selectedSprite.value.spriteActions)
|
spriteActions.value = sortSpriteActions(selectedSprite.value.spriteActions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +153,8 @@ async function saveSprite() {
|
|||||||
const updatedSprite = {
|
const updatedSprite = {
|
||||||
id: selectedSprite.value.id,
|
id: selectedSprite.value.id,
|
||||||
name: spriteName.value,
|
name: spriteName.value,
|
||||||
|
width: spriteWidth.value,
|
||||||
|
height: spriteHeight.value,
|
||||||
spriteActions:
|
spriteActions:
|
||||||
spriteActions.value?.map((action) => {
|
spriteActions.value?.map((action) => {
|
||||||
return {
|
return {
|
||||||
@ -217,6 +232,8 @@ function handleTempOffsetChange(action: SpriteAction, index: number, offset: { x
|
|||||||
watch(selectedSprite, (sprite: Sprite | null) => {
|
watch(selectedSprite, (sprite: Sprite | null) => {
|
||||||
if (!sprite) return
|
if (!sprite) return
|
||||||
spriteName.value = sprite.name
|
spriteName.value = sprite.name
|
||||||
|
spriteWidth.value = sprite.width
|
||||||
|
spriteHeight.value = sprite.height
|
||||||
spriteActions.value = sortSpriteActions(sprite.spriteActions)
|
spriteActions.value = sortSpriteActions(sprite.spriteActions)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@
|
|||||||
v-for="hair in filteredHairs"
|
v-for="hair in filteredHairs"
|
||||||
class="relative flex justify-center items-center bg-gray default-border w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500 has-[:checked]:bg-cyan has-[:checked]:border-transparent"
|
class="relative flex justify-center items-center bg-gray default-border w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500 has-[:checked]:bg-cyan has-[:checked]:border-transparent"
|
||||||
>
|
>
|
||||||
<img class="h-4 object-contain" :src="config.server_endpoint + '/textures/sprites/' + hair.sprite + '/front.png'" alt="Hair sprite" />
|
<img class="h-16 -m-5 mt-4 object-contain" :src="config.server_endpoint + '/textures/sprites/' + hair.sprite + '/front.png'" alt="Hair sprite" />
|
||||||
<input type="radio" name="hair" :value="hair.id" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
<input type="radio" name="hair" :value="hair.id" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user