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:
2026-05-27 21:26:15 -03:00
co-authored by Claude Sonnet 4.6
parent acbce4edc0
commit 3db4c66d01
7 changed files with 47 additions and 52 deletions
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

+46 -52
View File
@@ -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(() => { watch([() => props.bob, () => props.celebrate], () => {
const out: { x: number; y: number; k: string }[] = [] if (props.bob || props.celebrate) startAnim()
spriteData.grid.forEach((row: string, y: number) => { else { stopAnim(); frameIndex.value = 0 }
for (let x = 0; x < row.length; x++) { }, { immediate: true })
const k = row[x]
if (k === '.' || !colors.value[k]) continue onUnmounted(stopAnim)
out.push({ x, y, k })
}
})
return out
})
</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>
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />