Files
asciinevrdie/lib/src/shared/animation/character_frames.dart
JiWoong Sul 8f351df0b6 refactor(shared): animation, l10n, theme 모듈을 core에서 shared로 이동
- core/animation → shared/animation
- core/l10n → shared/l10n
- core/constants/ascii_colors → shared/theme/ascii_colors
- import 경로 업데이트
2026-02-23 15:49:14 +09:00

129 lines
4.3 KiB
Dart

// 캐릭터 애니메이션 프레임 (8줄 Stone Story RPG 스타일)
// 참조: Stone Story RPG - 상세하고 생동감 있는 ASCII 아트
/// 전투 페이즈
enum BattlePhase {
/// 대치 상태 (기본)
idle,
/// 공격 준비
prepare,
/// 공격 중
attack,
/// 피격 (몬스터가 맞음)
hit,
/// 복귀
recover,
}
/// 공격자 타입 (위치 계산용)
enum AttackerType {
/// 공격 없음 (idle 상태)
none,
/// 플레이어가 공격
player,
/// 몬스터가 공격
monster,
/// 동시 공격 (양쪽 모두 이동)
both,
}
/// 캐릭터 프레임 데이터
class CharacterFrame {
const CharacterFrame(this.lines);
/// 프레임 데이터 (3줄)
final List<String> lines;
/// 방패 오버레이 적용
/// 3줄 캐릭터: [0]=머리, [1]=몸통/팔, [2]=다리
CharacterFrame withShield() {
if (lines.length < 2) return this;
final newLines = List<String>.from(lines);
// 몸통 줄(1번줄, 팔 위치)에 방패 추가
final bodyIdx = 1;
if (newLines[bodyIdx].length >= 2) {
// 첫 두 문자를 방패로 대체
newLines[bodyIdx] = '[]${newLines[bodyIdx].substring(2)}';
} else {
newLines[bodyIdx] = '[]${newLines[bodyIdx]}';
}
return CharacterFrame(newLines);
}
}
/// 특정 페이즈와 서브프레임에 해당하는 캐릭터 프레임 반환
CharacterFrame getCharacterFrame(BattlePhase phase, int subFrame) {
final frames = switch (phase) {
BattlePhase.idle => _idleFrames,
BattlePhase.prepare => _prepareFrames,
BattlePhase.attack => _attackFrames,
BattlePhase.hit => _hitFrames,
BattlePhase.recover => _recoverFrames,
};
final index = subFrame % frames.length;
return frames[index];
}
// ============================================================================
// 대기 프레임 (숨쉬기 애니메이션) - 4프레임, 심플 3줄 스타일, 폭 6자
// 구조: [머리, 몸통+팔, 다리]
// ============================================================================
const _idleFrames = [
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
CharacterFrame([r' o ', r' /|\ ', r' | | ']),
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
CharacterFrame([r' O ', r' /|\ ', r' / \ ']),
];
// ============================================================================
// 준비 프레임 (무기 들기) - 3프레임, 심플 3줄 스타일, 폭 6자
// 구조: [머리, 몸통+팔, 다리]
// ============================================================================
const _prepareFrames = [
CharacterFrame([r' o ', r' \|\ ', r' / \ ']),
CharacterFrame([r' o_ ', r' \| ', r' / \ ']),
CharacterFrame([r' o/ ', r' \| ', r' / \ ']),
];
// ============================================================================
// 공격 프레임 (전진 + 휘두르기) - 5프레임, 심플 3줄 스타일
// 구조: [머리+공격, 몸통+팔, 다리]
// 수정: 공격 이펙트를 머리 줄로 통일 (1칸 위로)
// ============================================================================
const _attackFrames = [
CharacterFrame([r' o\ ', r' /| ', r' / \ ']),
CharacterFrame([r' o- ', r' /| ', r' / \ ']),
CharacterFrame([r' o-- ', r' /| ', r' / \ ']),
CharacterFrame([r' o-=>', r' /| ', r' / \ ']),
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
];
// ============================================================================
// 히트 프레임 (공격 명중) - 3프레임, 심플 3줄 스타일
// 구조: [머리+이펙트, 몸통+팔, 다리]
// 수정: 히트 이펙트를 머리 줄로 통일 (1칸 위로)
// ============================================================================
const _hitFrames = [
CharacterFrame([r' o-* ', r' /| ', r' / \ ']),
CharacterFrame([r' o=* ', r' /| ', r' / \ ']),
CharacterFrame([r' o~* ', r' /| ', r' / \ ']),
];
// ============================================================================
// 복귀 프레임 - 3프레임, 심플 3줄 스타일
// 구조: [머리, 몸통+팔, 다리]
// ============================================================================
const _recoverFrames = [
CharacterFrame([r' o ', r' /|\ ', r' | ']),
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
];