feat: substituir sprite SVG por animação de frames PNG do personagem
Cria pasta assets/sprites/character/ com 5 frames de idle e reescreve CharacterSprite.vue para ciclar entre eles via setInterval. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -1,80 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg
|
<img
|
||||||
:width="cols * scale"
|
:src="currentFrame"
|
||||||
:height="rows * scale"
|
:width="displaySize"
|
||||||
:viewBox="`0 0 ${cols * scale} ${rows * scale}`"
|
:height="displaySize"
|
||||||
shape-rendering="crispEdges"
|
|
||||||
class="fc-sprite"
|
class="fc-sprite"
|
||||||
:class="{ 'fc-sprite--bob': bob && !celebrate, 'fc-sprite--celebrate': celebrate }"
|
:class="{ 'fc-sprite--celebrate': celebrate }"
|
||||||
aria-label="Personagem pixel art"
|
aria-label="Personagem pixel art"
|
||||||
>
|
|
||||||
<rect
|
|
||||||
v-for="(px, i) in pixels"
|
|
||||||
:key="i"
|
|
||||||
:x="px.x * scale"
|
|
||||||
:y="px.y * scale"
|
|
||||||
:width="scale"
|
|
||||||
:height="scale"
|
|
||||||
:fill="colors[px.k]"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { ref, computed, watch, onUnmounted } from 'vue'
|
||||||
import spriteData from '@/assets/sprite-data.json'
|
|
||||||
|
import frame1 from '@/assets/sprites/character/nivel_1_idle_frame1.png'
|
||||||
|
import frame2 from '@/assets/sprites/character/nivel_1_idle_frame2.png'
|
||||||
|
import frame3 from '@/assets/sprites/character/nivel_1_idle_frame3.png'
|
||||||
|
import frame4 from '@/assets/sprites/character/nivel_1_idle_frame4.png'
|
||||||
|
import frame5 from '@/assets/sprites/character/nivel_1_idle_frame5.png'
|
||||||
|
|
||||||
|
const IDLE_FRAMES = [frame1, frame2, frame3, frame4, frame5]
|
||||||
|
// Base unit in px; displaySize = UNIT * scale
|
||||||
|
const UNIT = 16
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
|
/** Zoom factor. 3 = widget (home), 4 = default, 6 = hero (character view). */
|
||||||
scale?: number
|
scale?: number
|
||||||
|
/** Color overrides — kept for API compatibility, unused with PNG sprites. */
|
||||||
theme?: Partial<Record<string, string>>
|
theme?: Partial<Record<string, string>>
|
||||||
|
/** Enable idle frame animation. */
|
||||||
bob?: boolean
|
bob?: boolean
|
||||||
|
/** Celebration pose — faster animation + bounce CSS. */
|
||||||
celebrate?: boolean
|
celebrate?: boolean
|
||||||
}>(), { scale: 4, bob: true })
|
}>(), { scale: 4, bob: true })
|
||||||
|
|
||||||
const rows = spriteData.rows
|
const frameIndex = ref(0)
|
||||||
const cols = spriteData.cols
|
const displaySize = computed(() => UNIT * props.scale)
|
||||||
|
const currentFrame = computed(() => IDLE_FRAMES[frameIndex.value])
|
||||||
|
|
||||||
const defaultColors: Record<string, string> = {
|
let timer: ReturnType<typeof setInterval> | null = null
|
||||||
L: 'var(--fc-sprite-outline, #10131c)',
|
|
||||||
s: 'var(--fc-sprite-skin, #f3c79b)',
|
function startAnim() {
|
||||||
S: 'var(--fc-sprite-skin-shade, #c98863)',
|
stopAnim()
|
||||||
e: 'var(--fc-sprite-eye, #10131c)',
|
const ms = props.celebrate ? 100 : 200
|
||||||
m: 'var(--fc-sprite-mouth, #80484b)',
|
timer = setInterval(() => {
|
||||||
h: 'var(--fc-sprite-hat, #ffd84d)',
|
frameIndex.value = (frameIndex.value + 1) % IDLE_FRAMES.length
|
||||||
w: 'var(--fc-sprite-band, #fde68a)',
|
}, ms)
|
||||||
c: 'var(--fc-sprite-shirt, #00f0ff)',
|
|
||||||
C: 'var(--fc-sprite-shirt-shade, #0369a1)',
|
|
||||||
p: 'var(--fc-sprite-pants, #3a1f6e)',
|
|
||||||
P: 'var(--fc-sprite-pants-shade, #2d1857)',
|
|
||||||
b: 'var(--fc-sprite-boots, #0f172a)',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const colors = computed(() => ({ ...defaultColors, ...(props.theme ?? {}) }))
|
function stopAnim() {
|
||||||
|
if (timer) { clearInterval(timer); timer = null }
|
||||||
const pixels = computed(() => {
|
|
||||||
const out: { x: number; y: number; k: string }[] = []
|
|
||||||
spriteData.grid.forEach((row: string, y: number) => {
|
|
||||||
for (let x = 0; x < row.length; x++) {
|
|
||||||
const k = row[x]
|
|
||||||
if (k === '.' || !colors.value[k]) continue
|
|
||||||
out.push({ x, y, k })
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
return out
|
watch([() => props.bob, () => props.celebrate], () => {
|
||||||
})
|
if (props.bob || props.celebrate) startAnim()
|
||||||
|
else { stopAnim(); frameIndex.value = 0 }
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
onUnmounted(stopAnim)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-sprite { image-rendering: pixelated; display: inline-block; line-height: 0; }
|
.fc-sprite {
|
||||||
@keyframes fc-sprite-bob {
|
image-rendering: pixelated;
|
||||||
0%, 100% { transform: translateY(0); }
|
display: inline-block;
|
||||||
50% { transform: translateY(-3px); }
|
|
||||||
}
|
}
|
||||||
.fc-sprite--bob { animation: fc-sprite-bob 2.2s steps(2) infinite; }
|
|
||||||
@keyframes fc-sprite-celebrate {
|
@keyframes fc-sprite-celebrate {
|
||||||
0%, 100% { transform: translateY(0) rotate(0deg); }
|
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||||||
25% { transform: translateY(-6px) rotate(-3deg); }
|
25% { transform: translateY(-6px) rotate(-3deg); }
|
||||||
75% { transform: translateY(-6px) rotate(3deg); }
|
75% { transform: translateY(-6px) rotate(3deg); }
|
||||||
}
|
}
|
||||||
.fc-sprite--celebrate { animation: fc-sprite-celebrate 1s ease-in-out infinite; }
|
.fc-sprite--celebrate { animation: fc-sprite-celebrate 0.5s ease-in-out infinite; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
Reference in New Issue
Block a user