Skip to content

Commit fffdc29

Browse files
authored
release: 2.5.7 (#483)
2 parents cb4f86c + 8e88156 commit fffdc29

22 files changed

Lines changed: 2031 additions & 16 deletions

File tree

Binary file not shown.
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
# ํŽซ์— Emotion ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ถ”๊ฐ€ํ•˜๊ธฐ
2+
3+
## ๊ฐœ์š”
4+
5+
ํŽซ์— emotion ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๋ฉด, `loadSvg` ํ˜ธ์ถœ ์‹œ ๋žœ๋คํ•œ ํƒ€์ด๋ฐ์— emotion์ด ์žฌ์ƒ๋ฉ๋‹ˆ๋‹ค.
6+
emotion์ด ์žฌ์ƒ๋˜๋Š” ๋™์•ˆ base ํŽซ์€ ์ˆจ๊ฒจ์ง€๊ณ , emotion SVG๊ฐ€ ๋Œ€์‹  ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
7+
์ด๋™(act)๊ณผ ๋ฐฉํ–ฅ์ „ํ™˜(flip)์€ emotion ์ค‘์—๋„ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
8+
9+
## ํŒŒ์ผ ๊ตฌ์กฐ
10+
11+
```
12+
src/main/resources/persona/animal/
13+
โ”œโ”€โ”€ {pet-name}.svg # base ํŽซ SVG ํ…œํ”Œ๋ฆฟ
14+
โ””โ”€โ”€ emotion/{pet-short-name}/ # emotion SVG ๋””๋ ‰ํ† ๋ฆฌ
15+
โ”œโ”€โ”€ error.svg
16+
โ”œโ”€โ”€ happy.svg
17+
โ”œโ”€โ”€ idle-follow.svg
18+
โ”œโ”€โ”€ notification.svg
19+
โ”œโ”€โ”€ thinking.svg
20+
โ””โ”€โ”€ typing.svg
21+
22+
src/main/kotlin/org/gitanimals/core/
23+
โ”œโ”€โ”€ PersonaType.kt # ํŽซ enum (loadSvg, act, addEmotions)
24+
โ”œโ”€โ”€ PersonaEmotionType.kt # emotion ํƒ€์ž… enum (ERROR, HAPPY, IDLE_FOLLOW, NOTIFICATION, THINKING, TYPING)
25+
โ””โ”€โ”€ Svgs.kt # SVG ํŒŒ์ผ ๋กœ๋”ฉ
26+
```
27+
28+
## ๋‹จ๊ณ„๋ณ„ ๊ตฌํ˜„
29+
30+
### 1. Emotion SVG ํŒŒ์ผ ์ž‘์„ฑ
31+
32+
`src/main/resources/persona/animal/emotion/{pet-short-name}/` ๋””๋ ‰ํ† ๋ฆฌ์— 6๊ฐœ์˜ emotion SVG๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
33+
34+
**ํ•„์ˆ˜ ๊ทœ์น™:**
35+
36+
- viewBox: ํŽซ ๋ณธ์ฒด + props(๋А๋‚Œํ‘œ, ์ŠคํŒŒํด, ๋งํ’์„  ๋“ฑ)๊ฐ€ ๋ชจ๋‘ ํฌํ•จ๋˜๋„๋ก ์„ค์ •
37+
- ID ํŒจํ„ด: `{pet-name}-*{id}-{emotion}-{part}` (์˜ˆ: `dessert-fox-*{id}-error-shadow`)
38+
- `*{id}`๋Š” ๋Ÿฐํƒ€์ž„์— `animationId`๋กœ ์น˜ํ™˜๋ฉ๋‹ˆ๋‹ค
39+
- ๊ฐ SVG๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ์™„์ „ํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (style + ๊ตฌ์กฐ ํฌํ•จ)
40+
41+
**SVG ๋‚ด๋ถ€ ๊ตฌ์กฐ:**
42+
43+
```xml
44+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-15 -25 45 45">
45+
<style>
46+
@keyframes {pet-name}-*{id}-{emotion}-shadow { ... }
47+
@keyframes {pet-name}-*{id}-{emotion}-obj { ... }
48+
/* ๊ธฐํƒ€ keyframes */
49+
50+
#{pet-name}-*{id}-{emotion}-shadow { animation: ... }
51+
#{pet-name}-*{id}-{emotion}-obj { animation: ... }
52+
</style>
53+
54+
<!-- Shadow -->
55+
<g id="{pet-name}-*{id}-{emotion}-shadow" transform="translate(3, 7)">...</g>
56+
57+
<!-- obj: legs + body + head -->
58+
<g id="{pet-name}-*{id}-{emotion}-obj">
59+
<!-- Legs -->
60+
<g id="{pet-name}-*{id}-{emotion}-leg-left" transform="translate(5, 5)">...</g>
61+
<g id="{pet-name}-*{id}-{emotion}-leg-right" transform="translate(8, 5)">...</g>
62+
<!-- Body -->
63+
<g transform="translate(-2, 2)">...</g>
64+
<!-- Head -->
65+
<g transform="translate(0, -5)">
66+
<g id="{pet-name}-*{id}-{emotion}-head-nod">...</g>
67+
</g>
68+
</g>
69+
70+
<!-- Props (!, sparkles ๋“ฑ โ€” obj ๋ฐ”๊นฅ์— ๋ฐฐ์น˜) -->
71+
<g id="{pet-name}-*{id}-{emotion}-exclam" transform="translate(15, -13)">...</g>
72+
</svg>
73+
```
74+
75+
**emotion๋ณ„ ํŠน์ง•:**
76+
77+
| emotion | ํฌ์ฆˆ | ํŠน์ˆ˜ ํšจ๊ณผ |
78+
|---------|------|-----------|
79+
| error | standing | X X ๋ˆˆ, ์ขŒ์šฐ ํ”๋“ค๋ฆผ, ๋นจ๊ฐ„ ! ๊นœ๋นก์ž„ |
80+
| happy | standing | ^^ ๋ˆˆ, ์ ํ”„ ๋ฐ”์šด์Šค, ์ŠคํŒŒํด |
81+
| idle-follow | standing | ์ผ๋ฐ˜ ๋ˆˆ, ๋ถ€๋“œ๋Ÿฌ์šด ๋ฐ”์šด์Šค |
82+
| notification | standing | ์ผ๋ฐ˜ ๋ˆˆ, ๋†€๋žŒ ์ ํ”„, ๋…ธ๋ž€ ! ํŒ์ธ |
83+
| thinking | standing | ์ผ๋ฐ˜ ๋ˆˆ, ๋งํ’์„  + ๋กœ๋”ฉ dots |
84+
| typing | **sitting** | ์ผ๋ฐ˜ ๋ˆˆ, ๋…ธํŠธ๋ถ prop, ๊ณ ๊ฐœ ๋„๋•์ž„ |
85+
86+
### 2. Svgs.kt์— SVG ๋กœ๋”ฉ ์ถ”๊ฐ€
87+
88+
```kotlin
89+
// src/main/kotlin/org/gitanimals/core/Svgs.kt
90+
91+
val {petName}ErrorEmotionSvg: String = ClassPathResource("persona/animal/emotion/{pet-short-name}/error.svg")
92+
.getContentAsString(Charset.defaultCharset())
93+
94+
val {petName}HappyEmotionSvg: String = ClassPathResource("persona/animal/emotion/{pet-short-name}/happy.svg")
95+
.getContentAsString(Charset.defaultCharset())
96+
97+
val {petName}IdleFollowEmotionSvg: String = ClassPathResource("persona/animal/emotion/{pet-short-name}/idle-follow.svg")
98+
.getContentAsString(Charset.defaultCharset())
99+
100+
val {petName}NotificationEmotionSvg: String = ClassPathResource("persona/animal/emotion/{pet-short-name}/notification.svg")
101+
.getContentAsString(Charset.defaultCharset())
102+
103+
val {petName}ThinkingEmotionSvg: String = ClassPathResource("persona/animal/emotion/{pet-short-name}/thinking.svg")
104+
.getContentAsString(Charset.defaultCharset())
105+
106+
val {petName}TypingEmotionSvg: String = ClassPathResource("persona/animal/emotion/{pet-short-name}/typing.svg")
107+
.getContentAsString(Charset.defaultCharset())
108+
```
109+
110+
### 3. Base ํŽซ SVG ํ…œํ”Œ๋ฆฟ ์ˆ˜์ •
111+
112+
base SVG์— 3๊ฐ€์ง€ ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
113+
114+
**3-1. `*{emotion-style}` ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ถ”๊ฐ€**
115+
116+
`<style>` ๋ธ”๋ก ์•ˆ์— `*{emotion-style}`์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:
117+
118+
```xml
119+
<style>
120+
*{act}
121+
*{emotion-style}
122+
123+
@keyframes {pet-name}-*{id}-leg-left-move { ... }
124+
...
125+
</style>
126+
```
127+
128+
**3-2. base ํŽซ ์ฝ˜ํ…์ธ ๋ฅผ `<g id="{pet-name}-*{id}-base">`๋กœ ๊ฐ์‹ธ๊ธฐ**
129+
130+
emotion ์žฌ์ƒ ์‹œ base๋ฅผ ์ˆจ๊ธฐ๊ธฐ ์œ„ํ•ด wrapper ๊ทธ๋ฃน์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:
131+
132+
```xml
133+
<svg width="600" height="300" viewBox="0 0 200 100" fill="none" overflow="visible">
134+
<g id="{pet-name}-*{id}-base">
135+
<!-- ๊ธฐ์กด shadow, obj, body, head ๋“ฑ ๋ชจ๋“  base ์ฝ˜ํ…์ธ  -->
136+
</g>
137+
*{emotions}
138+
</svg>
139+
```
140+
141+
**3-3. `*{emotions}` ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ถ”๊ฐ€**
142+
143+
base ์ฝ˜ํ…์ธ  `</g>` ๋‹ซํž˜ ํƒœ๊ทธ ๋ฐ”๋กœ ๋’ค, `</svg>` ์•ž์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
144+
145+
### 4. PersonaType.kt์—์„œ loadSvg ๊ตฌํ˜„
146+
147+
`buildEmotionAnimation()` ๊ณตํ†ต ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
148+
149+
```kotlin
150+
{PET_NAME}(weight) {
151+
override fun loadSvg(name: String, animationId: Long, level: Long, mode: Mode): String {
152+
val emotion = buildEmotionAnimation(
153+
idPrefix = "{pet-name}", // base SVG์˜ ID prefix์™€ ๋™์ผํ•ด์•ผ ํ•จ
154+
animationId = animationId,
155+
totalDuration = 180.0, // ์ „์ฒด ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‚ฌ์ดํด (์ดˆ)
156+
emotionDuration = 3.0, // ๊ฐ emotion ์žฌ์ƒ ์‹œ๊ฐ„ (์ดˆ)
157+
emotionSvgs = listOf(
158+
{petName}ErrorEmotionSvg, // index 0
159+
{petName}HappyEmotionSvg, // index 1
160+
{petName}IdleFollowEmotionSvg, // index 2
161+
{petName}NotificationEmotionSvg, // index 3
162+
{petName}ThinkingEmotionSvg, // index 4
163+
{petName}TypingEmotionSvg, // index 5
164+
),
165+
emotionYOffsets = listOf(
166+
5.0, // error (standing)
167+
5.0, // happy (standing)
168+
5.0, // idle (standing)
169+
5.0, // notif (standing)
170+
5.0, // thinking (standing)
171+
2.5, // typing (sitting โ€” ์•‰์•„์žˆ์œผ๋ฏ€๋กœ Y ๋ณด์ •์ด ๋‹ค๋ฆ„)
172+
),
173+
minGap = 5.0, // emotion ์‚ฌ์ด ์ตœ์†Œ ๊ฐ„๊ฒฉ (์ดˆ)
174+
maxGap = 30.0, // emotion ์‚ฌ์ด ์ตœ๋Œ€ ๊ฐ„๊ฒฉ (์ดˆ)
175+
)
176+
177+
return {petName}Svg
178+
.replace("*{act}", act(animationId))
179+
.replace("*{emotion-style}", emotion.css)
180+
.replace("*{emotions}", emotion.content)
181+
.replace("*{id}", animationId.toString())
182+
.replace("*{level}", level.toSvg(14.0, 2.0))
183+
.replace("*{levelx}", (-3 + (-1 * (level.toString().length))).toString())
184+
.replace("*{username}", name.toSvg(14.0, 25.0))
185+
.replace("*{usernamex}", (23 + (-3 * name.length)).toString())
186+
}
187+
188+
override fun addEmotions(emotionType: PersonaEmotionType): String {
189+
return when (emotionType) {
190+
ERROR -> {petName}ErrorEmotionSvg
191+
HAPPY -> {petName}HappyEmotionSvg
192+
IDLE_FOLLOW -> {petName}IdleFollowEmotionSvg
193+
NOTIFICATION -> {petName}NotificationEmotionSvg
194+
THINKING -> {petName}ThinkingEmotionSvg
195+
TYPING -> {petName}TypingEmotionSvg
196+
}
197+
}
198+
}
199+
```
200+
201+
### 5. emotionYOffsets ๊ฒฐ์ •ํ•˜๊ธฐ
202+
203+
`emotionYOffsets`๋Š” emotion SVG๋ฅผ base ํŽซ ์œ„์น˜์— ์ •๋ ฌํ•˜๊ธฐ ์œ„ํ•œ Y์ถ• ๋ณด์ •๊ฐ’์ž…๋‹ˆ๋‹ค.
204+
base SVG์™€ emotion SVG์˜ ์ขŒํ‘œ๊ณ„๊ฐ€ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•˜๋ฉฐ, ์ •ํ™•ํ•œ ๊ฐ’์€ ์„œ๋ฒ„๋ฅผ ์ง์ ‘ ๋„์›Œ์„œ ํ™•์ธํ•˜๋ฉฐ ์กฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
205+
Agent๊ฐ€ ์•„๋ž˜ ์š”๊ฑด์— ๋”ฐ๋ผ์„œ ์Šค์Šค๋กœ ์„œ๋ฒ„๋ฅผ ๋„์šฐ๊ณ  ํ…Œ์ŠคํŠธํ•˜์—ฌ ์ •ํ™•ํ•œ ๊ฐ’์„ ์กฐ์ •ํ•˜์„ธ์š”.
206+
207+
**์„œ๋ฒ„ ๋„์šฐ๊ธฐ:**
208+
209+
```bash
210+
./gradlew bootRun --args="--spring.profiles.active=local"
211+
```
212+
213+
**ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์„ค์ •:**
214+
215+
local MySQL์— ์ ‘์†ํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•  ํŽซ ํƒ€์ž…์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค:
216+
217+
```sql
218+
USE gitanimalsrender;
219+
UPDATE persona SET type = '{PET_NAME}';
220+
```
221+
222+
**ํ™•์ธ:**
223+
224+
๋ธŒ๋ผ์šฐ์ €์—์„œ `localhost:8080/lines/{username}` ์œผ๋กœ ์กฐํšŒํ•˜๋ฉด SVG๊ฐ€ ์‘๋‹ต๋ฉ๋‹ˆ๋‹ค.
225+
emotion์ด base ํŽซ๊ณผ ๋™์ผํ•œ ์œ„์น˜์— ๋‚˜ํƒ€๋‚˜๋„๋ก `emotionYOffsets` ๊ฐ’์„ ์กฐ์ •ํ•˜์„ธ์š”.
226+
227+
- ๋””๋ฒ„๊น… ์‹œ `minGap`๊ณผ `maxGap`์„ `1.0`์œผ๋กœ ์„ค์ •ํ•˜๋ฉด 1์ดˆ๋งˆ๋‹ค emotion์ด ๋‚˜ํƒ€๋‚˜์„œ ์œ„์น˜ ํ™•์ธ์ด ์‰ฌ์›€
228+
- standing ํฌ์ฆˆ์™€ sitting ํฌ์ฆˆ๋Š” ๋ณด์ •๊ฐ’์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ (DESSERT_FOX ๊ธฐ์ค€: standing `5.0`, sitting `2.5`)
229+
230+
### 6. PersonaEmotionAssets ๊ตฌํ˜„ํ•˜๊ธฐ
231+
232+
`src/main/kotlin/org/gitanimals/render/app/PersonaEmotionAssets.kt` ํŒŒ์ผ์˜ `PersonaEmotionAssets` sealed interface์— ์ƒˆ๋กœ์šด ํŽซ์˜ ๊ตฌํ˜„์ฒด๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” ํŽซ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์™€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋‹ค์šด๋กœ๋“œ URL ์ •๋ณด๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
233+
234+
**๊ตฌํ˜„ ์˜ˆ์‹œ (DESSERT_FOX ์ฐธ๊ณ ):**
235+
236+
```kotlin
237+
data object {PetClassName} : PersonaEmotionAssets {
238+
override val personaType: PersonaType = PersonaType.{PET_ENUM_NAME}
239+
override val name: String = "{Display Name}"
240+
override val author: String = "{Author Name}"
241+
override val description: String = "{Detailed Description}"
242+
243+
// SVG ์ขŒํ‘œ๊ณ„ ์„ค์ • (๋ณดํ†ต -15 -25 45 45 ์‚ฌ์šฉ)
244+
override val viewBox = ViewBox(x = -15, y = -25, width = 45, height = 45)
245+
246+
// ๋ ˆ์ด์•„์›ƒ ์„ค์ •
247+
override val layout = Layout(
248+
contentBox = Box(x = -2, y = -2, width = 20, height = 18),
249+
centerX = 7.5,
250+
baselineY = 15.0,
251+
visibleHeightRatio = 0.58,
252+
baselineBottomRatio = 0.05
253+
)
254+
255+
// ๋ˆˆ ์ถ”์ (Eye Tracking) ์„ค์ • (๋ฏธ์ง€์› ์‹œ enabled = false)
256+
override val eyeTracking = EyeTracking(
257+
enabled = false,
258+
states = listOf("idle"),
259+
eyeRatioX = 0.52,
260+
eyeRatioY = 0.45,
261+
maxOffset = 1.5,
262+
bodyScale = 0.25,
263+
shadowStretch = 0.15,
264+
shadowShift = 0.3,
265+
ids = EyeTrackingIds(eyes = "eyes-js", body = "body-js", shadow = "shadow-js"),
266+
shadowOrigin = "7.5px 14px"
267+
)
268+
269+
// ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ด๋ฐ ์„ค์ • (ms ๋‹จ์œ„)
270+
override val timings = Timings(
271+
minDisplay = mapOf(
272+
"attention" to 4000,
273+
"error" to 5000,
274+
"notification" to 2500,
275+
"working" to 1000,
276+
"thinking" to 1000
277+
),
278+
autoReturn = mapOf(
279+
"attention" to 4000,
280+
"error" to 5000,
281+
"notification" to 2500
282+
),
283+
mouseIdleTimeout = 20000,
284+
mouseSleepTimeout = 60000,
285+
wakeDuration = 5000
286+
)
287+
288+
// ํžˆํŠธ๋ฐ•์Šค ์„ค์ •
289+
override val hitBoxes = HitBoxes(
290+
default = Box(x = -3, y = -8, width = 22, height = 20),
291+
sleeping = Box(x = -3, y = -5, width = 22, height = 18)
292+
)
293+
294+
override val sleepingHitboxFiles = listOf("sleeping.svg")
295+
override val miniMode = MiniMode(supported = false)
296+
297+
// ์˜ค๋ธŒ์ ํŠธ ์Šค์ผ€์ผ ๋ณด์ •
298+
override val objectScale = ObjectScale(
299+
widthRatio = 1.9,
300+
heightRatio = 1.3,
301+
offsetX = -0.45,
302+
offsetY = -0.25
303+
)
304+
305+
// ์‹ค์ œ SVG ์ปจํ…์ธ  ๋ฐ˜ํ™˜ ๋กœ์ง ๊ตฌํ˜„
306+
override fun getAsset(emotion: String): String {
307+
return when (emotion) {
308+
"error" -> {petName}ErrorEmotionSvg
309+
"happy" -> {petName}HappyEmotionSvg
310+
"idleFollow" -> {petName}IdleFollowEmotionSvg
311+
"notification" -> {petName}NotificationEmotionSvg
312+
"thinking" -> {petName}ThinkingEmotionSvg
313+
"typing" -> {petName}TypingEmotionSvg
314+
else -> throw IllegalArgumentException("Invalid emotion: $emotion")
315+
}
316+
}
317+
}
318+
```
319+
320+
**์ฐธ๊ณ  ์‚ฌํ•ญ:**
321+
- `error`, `happy`, `idleFollow` ๋“ฑ์˜ ํ”„๋กœํผํ‹ฐ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„(`get()` ์ ‘๊ทผ์ž)์„ ํ†ตํ•ด ์ž๋™์œผ๋กœ ๋‹ค์šด๋กœ๋“œ URL (`/assets/images?...`)์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
322+
- `getAsset` ๋ฉ”์„œ๋“œ๋Š” `AnimationController`์˜ `/assets/images` API์—์„œ ์‹ค์ œ SVG ํŒŒ์ผ์„ ์‘๋‹ตํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
323+
- ์ƒˆ๋กœ์šด ํŽซ ๊ตฌํ˜„์ฒด๋ฅผ ์ถ”๊ฐ€ํ•œ ํ›„ `companion object`์˜ `from` ๋ฉ”์„œ๋“œ์—์„œ ํ•ด๋‹น ํŽซ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์กฐํšŒ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š” (Sealed interface์˜ `sealedSubclasses`๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ž๋™์œผ๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค).
324+
325+
## buildEmotionAnimation ํŒŒ๋ผ๋ฏธํ„ฐ
326+
327+
| ํŒŒ๋ผ๋ฏธํ„ฐ | ํƒ€์ž… | ์„ค๋ช… |
328+
|----------|------|------|
329+
| `idPrefix` | String | CSS ID prefix. base SVG์˜ `*{id}` ์•ž ๋ถ€๋ถ„๊ณผ ๋™์ผํ•ด์•ผ ํ•จ |
330+
| `animationId` | Long | ํŽซ ์ธ์Šคํ„ด์Šค ๊ณ ์œ  ID. ID ์ถฉ๋Œ ๋ฐฉ์ง€์šฉ |
331+
| `totalDuration` | Double | ์ „์ฒด ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‚ฌ์ดํด ๊ธธ์ด (์ดˆ) |
332+
| `emotionDuration` | Double | ๊ฐ emotion์ด ํ‘œ์‹œ๋˜๋Š” ์‹œ๊ฐ„ (์ดˆ) |
333+
| `emotionSvgs` | List\<String\> | emotion SVG ๋ฌธ์ž์—ด ๋ฆฌ์ŠคํŠธ |
334+
| `emotionYOffsets` | List\<Double\> | ๊ฐ emotion์˜ Y์ถ• ๋ณด์ •๊ฐ’. emotionSvgs์™€ ๊ฐ™์€ ์ˆœ์„œ |
335+
| `minGap` | Double | emotion ์‚ฌ์ด ์ตœ์†Œ ๊ฐ„๊ฒฉ (์ดˆ). ๊ธฐ๋ณธ๊ฐ’ 5.0 |
336+
| `maxGap` | Double | emotion ์‚ฌ์ด ์ตœ๋Œ€ ๊ฐ„๊ฒฉ (์ดˆ). ๊ธฐ๋ณธ๊ฐ’ 30.0 |
337+
338+
## ๋™์ž‘ ์›๋ฆฌ
339+
340+
1. `Random(animationId)` ์‹œ๋“œ๋กœ ๊ฒฐ์ •๋ก ์  ์Šค์ผ€์ค„ ์ƒ์„ฑ
341+
2. base ํŽซ์— `opacity` ํ† ๊ธ€ CSS ์ ์šฉ โ†’ emotion ๊ตฌ๊ฐ„์—์„œ base ์ˆจ๊น€
342+
3. ๊ฐ emotion์—๋„ `opacity` ํ† ๊ธ€ CSS ์ ์šฉ โ†’ ํ•ด๋‹น ๊ตฌ๊ฐ„์—์„œ๋งŒ ํ‘œ์‹œ
343+
4. `steps(1, end)` timing function์œผ๋กœ ์ฆ‰์‹œ ์ „ํ™˜ (ํŽ˜์ด๋“œ ์—†์Œ)
344+
5. emotion SVG ๋‚ด๋ถ€์˜ ์ž์ฒด ์• ๋‹ˆ๋ฉ”์ด์…˜ (๋ฐ”์šด์Šค, ํ”๋“ค๋ฆผ ๋“ฑ)์€ ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘
345+
6. `*{id}`๋Š” ์ตœ์ข… `.replace("*{id}", animationId.toString())`์—์„œ ์ผ๊ด„ ์น˜ํ™˜
346+
347+
## ๋””๋ฒ„๊น… ํŒ
348+
349+
- `minGap`๊ณผ `maxGap`์„ `1.0`์œผ๋กœ ์„ค์ •ํ•˜๋ฉด 1์ดˆ๋งˆ๋‹ค emotion์ด ๋‚˜ํƒ€๋‚จ
350+
- `localhost:8080/lines/{username}`์œผ๋กœ ๊ฒฐ๊ณผ ํ™•์ธ
351+
- emotion์ด ์•ˆ ๋ณด์ด๋ฉด: CSS `animation` shorthand ๋Œ€์‹  longhand ์‚ฌ์šฉ ํ™•์ธ
352+
- ์œ„์น˜๊ฐ€ ๋งž์ง€ ์•Š์œผ๋ฉด: `emotionYOffsets` ๊ฐ’ ์กฐ์ •
353+
354+
## ์ฐธ๊ณ  ๊ตฌํ˜„
355+
356+
DESSERT_FOX ๊ตฌํ˜„์„ ์ฐธ๊ณ ํ•˜์„ธ์š”:
357+
- base SVG: `src/main/resources/persona/animal/dessert-fox.svg`
358+
- emotion SVGs: `src/main/resources/persona/animal/emotion/fox/`
359+
- PersonaType: `PersonaType.kt` DESSERT_FOX ํ•ญ๋ชฉ
360+
- SVG ๋กœ๋”ฉ: `Svgs.kt` dessertFox*EmotionSvg ๋ณ€์ˆ˜๋“ค

0 commit comments

Comments
ย (0)