Moved game components into a new folder, working proof of concept hair customisation

This commit is contained in:
Dennis Postma 2024-11-23 16:47:41 +01:00
parent ee3e1b55cb
commit ab97e27f27
11 changed files with 68 additions and 29 deletions

View File

@ -67,7 +67,7 @@ input {
}
&[type='number']::-webkit-inner-spin-button,
&[type='number']::-webkit-outer-spin-button,
&[type='radio']{
&[type='radio'] {
-webkit-appearance: none;
}
}

View File

@ -12,6 +12,8 @@
</Container>
<!-- Character sprite -->
<Container ref="charContainer" :depth="isometricDepth" :x="currentX" :y="currentY">
<!-- HAIR-->
<Image :origin-y="4.2" :depth="1" :texture="props.zoneCharacter.character.hair?.spriteId + '-front'" />
<Sprite ref="charSprite" :origin-y="1" :flipX="isFlippedX" :flipY="false" />
</Container>
</template>
@ -22,7 +24,7 @@ import { type Sprite as SpriteT, type ZoneCharacter } 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 { Container, Image, refObj, RoundRectangle, Sprite, Text, useGame, useScene } from 'phavuer'
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
import { loadSpriteTextures } from '@/composables/gameComposable'
@ -31,7 +33,6 @@ enum Direction {
NEGATIVE,
UNCHANGED
}
const props = defineProps<{
layer: Phaser.Tilemaps.TilemapLayer
zoneCharacter: ZoneCharacter
@ -181,6 +182,14 @@ watch(
watch(() => props.zoneCharacter, updateSprite)
// Hair demo
loadSpriteTextures(scene, props.zoneCharacter.character.hair?.sprite as SpriteT)
.then(() => {})
.catch((error) => {
console.error('Error loading texture:', error)
})
loadSpriteTextures(scene, props.zoneCharacter.character.characterType?.sprite as SpriteT)
.then(() => {
charSprite.value!.setTexture(charTexture.value)

View File

@ -3,7 +3,7 @@
</template>
<script setup lang="ts">
import Character from '@/components/sprites/Character.vue'
import Character from '@/components/game/character/Character.vue'
import { useZoneStore } from '@/stores/zoneStore'
const zoneStore = useZoneStore()

View File

@ -11,9 +11,9 @@ import { useGameStore } from '@/stores/gameStore'
import { useZoneStore } from '@/stores/zoneStore'
import { loadZoneTilesIntoScene } from '@/composables/zoneComposable'
import type { Zone as ZoneT, ZoneCharacter } from '@/types'
import ZoneTiles from '@/components/zone/ZoneTiles.vue'
import ZoneObjects from '@/components/zone/ZoneObjects.vue'
import Characters from '@/components/zone/Characters.vue'
import ZoneTiles from '@/components/game/zone/ZoneTiles.vue'
import ZoneObjects from '@/components/game/zone/ZoneObjects.vue'
import Characters from '@/components/game/zone/Characters.vue'
const scene = useScene()
const gameStore = useGameStore()

View File

@ -4,7 +4,7 @@
<script setup lang="ts">
import { useZoneStore } from '@/stores/zoneStore'
import ZoneObject from '@/components/zone/partials/ZoneObject.vue'
import ZoneObject from '@/components/game/zone/partials/ZoneObject.vue'
const zoneStore = useZoneStore()

View File

@ -37,11 +37,11 @@ const chats = ref([] as Chat[])
const chatWindow = ref<HTMLElement | null>(null)
const chatInput = ref<HTMLElement | null>(null)
onClickOutside(chatInput, event => unfocusChat(event, chatInput.value as HTMLElement))
onClickOutside(chatInput, (event) => unfocusChat(event, chatInput.value as HTMLElement))
function unfocusChat(event: Event, targetElement: HTMLElement) {
if (!(event.target instanceof Node) || !targetElement.contains(event.target)) {
targetElement.blur();
targetElement.blur()
}
}

View File

@ -16,7 +16,12 @@
<div class="flex w-full h-[400px] border border-solid border-gray-500 rounded-md rounded-tl-none bg-gray">
<div class="w-1/3 h-full bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover bg-center border-0 border-r border-solid border-gray-500 rounded-bl-md relative">
<div class="absolute right-full -top-px flex gap-1 flex-col">
<div v-for="character in characters" :key="character.id" class="character relative rounded-l border border-solid border-gray-500 w-9 h-[50px] bg-[url('/assets/ui-texture.png')] after:absolute after:w-full after:h-px after:bg-gray-500" :class="{ active: selected_character == character.id }">
<div
v-for="character in characters"
:key="character.id"
class="character relative rounded-l border border-solid border-gray-500 w-9 h-[50px] bg-[url('/assets/ui-texture.png')] after:absolute after:w-full after:h-px after:bg-gray-500"
:class="{ active: selected_character == character.id }"
>
<img src="/assets/avatar/default/head.png" class="w-9 h-9 object-contain absolute top-1/2 -translate-y-1/2" alt="Player head" />
<input class="h-full w-full absolute m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0" type="radio" name="character" :value="character.id" v-model="selected_character" />
</div>
@ -39,10 +44,10 @@
<img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 -scale-x-100" alt="Arrow right" />
</button>
</div>
<!-- <div class="flex justify-between w-[190px]">-->
<!-- &lt;!&ndash; TODO: replace with color swatches &ndash;&gt;-->
<!-- <button v-for="n in 9" class="w-4 h-4 rounded-sm bg-white"></button>-->
<!-- </div>-->
<!-- <div class="flex justify-between w-[190px]">-->
<!-- &lt;!&ndash; TODO: replace with color swatches &ndash;&gt;-->
<!-- <button v-for="n in 9" class="w-4 h-4 rounded-sm bg-white"></button>-->
<!-- </div>-->
</div>
<!-- TODO: update gender on (selected) character -->
<div class="flex justify-between w-[190px]">
@ -66,8 +71,18 @@
<button class="bg-gray border border-solid border-gray-500 min-w-9 max-w-9 min-h-9 max-h-9 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">
<img src="/assets/icons/x-button-gray.svg" class="w-4 h-4 m-auto" alt="Male symbol" />
</button>
<!-- TODO: replace with hairstyles -->
<button v-for="n in 30" class="bg-gray border border-solid border-gray-500 min-w-9 max-w-9 min-h-9 max-h-9 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"></button>
<!-- TODO #255: make radio button so we can set a value, do the same with swatches -->
<button
v-for="hair in hairs"
class="border border-solid border-gray-500 min-w-9 max-w-9 min-h-9 max-h-9 p-2 rounded-sm hover:border-gray-400 focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500"
@click="selectedHair = hair"
:class="{
'bg-cyan': hair.id === selectedHair?.id,
'bg-gray-500 hover:bg-gray-500': hair.id !== selectedHair?.id
}"
>
<img class="w-4 h-4 m-auto" :src="config.server_endpoint + '/assets/sprites/' + hair.spriteId + '/front.png'" alt="Male symbol" />
</button>
</div>
</div>
<div class="flex flex-col gap-3 w-full">
@ -103,7 +118,7 @@
<form method="post" @submit.prevent="create" class="h-full flex flex-col justify-between">
<div class="form-field-full">
<label for="name" class="text-white">Nickname</label>
<input class="input-field" v-model="name" name="name" id="name" placeholder="Enter a nickname.." />
<input class="input-field" v-model="name" name="name" id="name" placeholder="Enter a nickname..." />
</div>
<div class="grid grid-flow-col justify-stretch gap-4">
<button type="button" class="btn-empty py-1.5 px-4 inline-block" @click.prevent="isModalOpen = false">Cancel</button>
@ -129,10 +144,11 @@
</template>
<script setup lang="ts">
import config from '@/config'
import { useGameStore } from '@/stores/gameStore'
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { onBeforeUnmount, ref, watch } from 'vue'
import Modal from '@/components/utilities/Modal.vue'
import { type Character as CharacterT } from '@/types'
import { type Character as CharacterT, type CharacterHair } from '@/types'
import ConfirmationModal from '@/components/utilities/ConfirmationModal.vue'
const gameStore = useGameStore()
@ -140,25 +156,39 @@ const isLoading = ref(true)
const characters = ref([] as CharacterT[])
const deletingCharacter = ref(null as CharacterT | null)
const hairs = ref([] as CharacterHair[])
const selectedHair = ref(null as CharacterHair | null)
watch(selectedHair, (hair: CharacterHair | null) => {
console.log(hair)
})
// Fetch characters
gameStore.connection?.on('character:list', (data: any) => {
characters.value = data
isLoading.value = false
// Fetch hairs
// @TODO: This is hacky, we should have a better way to do this
gameStore.connection?.emit('character:hair:list', {}, (data: CharacterHair[]) => {
console.log(data)
hairs.value = data
})
})
onMounted(async () => {
// wait 0.75 sec
setTimeout(() => {
gameStore.connection?.emit('character:list')
isLoading.value = false
}, 750)
})
setTimeout(() => {
gameStore.connection?.emit('character:list')
}, 750)
// Select character logics
const selected_character = ref(null)
function select_character() {
if (!selected_character.value) return
deletingCharacter.value = null
gameStore.connection?.emit('character:connect', { characterId: selected_character.value })
gameStore.connection?.emit('character:connect', {
characterId: selected_character.value,
hairId: selectedHair.value?.id
})
gameStore.connection?.on('character:connect', (data: CharacterT) => gameStore.setCharacter(data))
}

View File

@ -25,7 +25,7 @@ import { useGameStore } from '@/stores/gameStore'
import Menu from '@/components/gui/Menu.vue'
import ExpBar from '@/components/gui/ExpBar.vue'
import Hud from '@/components/gui/Hud.vue'
import Zone from '@/components/zone/Zone.vue'
import Zone from '@/components/game/zone/Zone.vue'
import Hotkeys from '@/components/gui/Hotkeys.vue'
import Chat from '@/components/gui/Chat.vue'
import CharacterProfile from '@/components/gui/CharacterProfile.vue'