feat(hall-of-fame): 명예의 전당 대폭 개선 및 장비/아이템 직렬화

- HallOfFameEntry에 finalEquipmentDetails 추가 (상세 장비 정보)
- EquipmentItem/ItemStats에 toJson/fromJson 직렬화 추가
- 명예의 전당 상세 다이얼로그 UI 대폭 개선
- Canvas 타운/워킹 애니메이션 컴포저 개선
- 캐릭터 생성 화면 UI 개선
- 게임 텍스트 다국어 지원 확장
This commit is contained in:
JiWoong Sul
2025-12-24 18:34:00 +09:00
parent d82bf05978
commit dd83923ddf
9 changed files with 730 additions and 153 deletions

View File

@@ -1,11 +1,16 @@
import 'package:askiineverdie/src/core/animation/canvas/ascii_cell.dart';
import 'package:askiineverdie/src/core/animation/canvas/ascii_layer.dart';
import 'package:askiineverdie/src/core/animation/race_character_frames.dart';
/// Canvas용 마을/상점 애니메이션 합성기
///
/// 마을 배경 + 상점 건물 + 캐릭터
/// Phase 4: 종족별 캐릭터 프레임 지원
class CanvasTownComposer {
const CanvasTownComposer();
const CanvasTownComposer({this.raceId});
/// 종족 ID (종족별 캐릭터 프레임 선택용)
final String? raceId;
/// 프레임 상수
static const int frameWidth = 60;
@@ -67,9 +72,24 @@ class CanvasTownComposer {
}
/// 캐릭터 레이어 생성 (z=2)
/// Phase 4: 종족별 프레임 지원
AsciiLayer _createCharacterLayer(int globalTick) {
final frameIndex = globalTick % _shopIdleFrames.length;
final charFrame = _shopIdleFrames[frameIndex];
final frameIndex = globalTick % 4; // 4프레임 루프
List<String> charFrame;
// 종족별 프레임 사용 시도
if (raceId != null && raceId!.isNotEmpty) {
final raceData = RaceCharacterFrames.get(raceId!);
if (raceData != null) {
// idle 프레임 직접 사용 (마을에서는 서있는 자세)
final idleFrame = raceData.idle[frameIndex % raceData.idle.length];
charFrame = idleFrame.lines;
} else {
charFrame = _shopIdleFrames[frameIndex];
}
} else {
charFrame = _shopIdleFrames[frameIndex];
}
final cells = _spriteToCells(charFrame);

View File

@@ -2,12 +2,17 @@ import 'package:askiineverdie/src/core/animation/background_data.dart';
import 'package:askiineverdie/src/core/animation/background_layer.dart';
import 'package:askiineverdie/src/core/animation/canvas/ascii_cell.dart';
import 'package:askiineverdie/src/core/animation/canvas/ascii_layer.dart';
import 'package:askiineverdie/src/core/animation/race_character_frames.dart';
/// Canvas용 걷기 애니메이션 합성기
///
/// 배경 스크롤 + 걷는 캐릭터
/// Phase 4: 종족별 캐릭터 프레임 지원
class CanvasWalkingComposer {
const CanvasWalkingComposer();
const CanvasWalkingComposer({this.raceId});
/// 종족 ID (종족별 캐릭터 프레임 선택용)
final String? raceId;
/// 프레임 상수
static const int frameWidth = 60;
@@ -54,9 +59,24 @@ class CanvasWalkingComposer {
}
/// 걷는 캐릭터 레이어 생성 (z=1)
/// Phase 4: 종족별 프레임 지원
AsciiLayer _createCharacterLayer(int globalTick) {
final frameIndex = globalTick % _walkingFrames.length;
final charFrame = _walkingFrames[frameIndex];
final frameIndex = globalTick % 4; // 4프레임 루프
List<String> charFrame;
// 종족별 프레임 사용 시도
if (raceId != null && raceId!.isNotEmpty) {
final raceData = RaceCharacterFrames.get(raceId!);
if (raceData != null) {
// idle 프레임을 기반으로 걷기 애니메이션 생성
final idleFrame = raceData.idle[frameIndex % raceData.idle.length];
charFrame = _animateWalking(idleFrame.lines, frameIndex);
} else {
charFrame = _walkingFrames[frameIndex];
}
} else {
charFrame = _walkingFrames[frameIndex];
}
final cells = _spriteToCells(charFrame);
@@ -68,6 +88,22 @@ class CanvasWalkingComposer {
return AsciiLayer(cells: cells, zIndex: 1, offsetX: charX, offsetY: charY);
}
/// idle 프레임 기반 걷기 애니메이션 생성
/// 머리와 몸통은 유지, 다리만 걷는 동작으로 변경
List<String> _animateWalking(List<String> idleLines, int frameIndex) {
if (idleLines.length < 3) return idleLines;
// 머리(0)와 몸통(1)은 그대로 유지
final head = idleLines[0];
final body = idleLines[1];
// 다리 애니메이션 (4프레임) - 걷기 동작
const legFrames = [' /| ', ' |\\ ', ' /| ', ' |\\ '];
final legs = legFrames[frameIndex % legFrames.length];
return [head, body, legs];
}
/// 문자열 스프라이트를 AsciiCell 2D 배열로 변환
List<List<AsciiCell>> _spriteToCells(List<String> lines) {
return lines.map((line) {