refactor(animation): Canvas 레이어 z-index 정리 및 애니메이션 개선

- 캐릭터 z=2, 몬스터 z=1, 이펙트 z=3으로 레이어 순서 정리
- walking composer 업데이트
- 게임 화면 및 애니메이션 카드 개선
This commit is contained in:
JiWoong Sul
2025-12-22 16:08:10 +09:00
parent 012d4dafd2
commit f606fca063
5 changed files with 44 additions and 40 deletions

View File

@@ -24,6 +24,33 @@ enum AsciiAnimationType {
resurrection, resurrection,
} }
// ============================================================================
// 특수 애니메이션 타이밍 상수
// ============================================================================
/// 특수 애니메이션 프레임 수
const specialAnimationFrameCounts = {
AsciiAnimationType.levelUp: 5,
AsciiAnimationType.questComplete: 4,
AsciiAnimationType.actComplete: 4,
AsciiAnimationType.resurrection: 5,
};
/// 특수 애니메이션 프레임 간격 (밀리초)
const specialAnimationFrameIntervals = {
AsciiAnimationType.levelUp: 300,
AsciiAnimationType.questComplete: 350,
AsciiAnimationType.actComplete: 400,
AsciiAnimationType.resurrection: 600,
};
/// 특수 애니메이션 총 지속 시간 (밀리초)
int getSpecialAnimationDuration(AsciiAnimationType type) {
final frames = specialAnimationFrameCounts[type] ?? 1;
final interval = specialAnimationFrameIntervals[type] ?? 200;
return frames * interval;
}
/// TaskType을 AsciiAnimationType으로 변환 /// TaskType을 AsciiAnimationType으로 변환
AsciiAnimationType taskTypeToAnimation(TaskType taskType) { AsciiAnimationType taskTypeToAnimation(TaskType taskType) {
return switch (taskType) { return switch (taskType) {

View File

@@ -115,13 +115,13 @@ class CanvasBattleComposer {
return AsciiLayer( return AsciiLayer(
cells: cells, cells: cells,
zIndex: 1, zIndex: 2, // 몬스터(z=1) 위에 캐릭터 표시
offsetX: charX, offsetX: charX,
offsetY: charY, offsetY: charY,
); );
} }
/// 몬스터 레이어 생성 (z=1) /// 몬스터 레이어 생성 (z=1, 캐릭터보다 뒤)
AsciiLayer _createMonsterLayer(BattlePhase phase, int subFrame) { AsciiLayer _createMonsterLayer(BattlePhase phase, int subFrame) {
final monsterFrames = _getAnimatedMonsterFrames( final monsterFrames = _getAnimatedMonsterFrames(
monsterCategory, monsterCategory,
@@ -155,7 +155,7 @@ class CanvasBattleComposer {
); );
} }
/// 이펙트 레이어 생성 (z=2) /// 이펙트 레이어 생성 (z=3, 캐릭터/몬스터 위에 표시)
AsciiLayer? _createEffectLayer(BattlePhase phase, int subFrame) { AsciiLayer? _createEffectLayer(BattlePhase phase, int subFrame) {
final effect = getWeaponEffect(weaponCategory); final effect = getWeaponEffect(weaponCategory);
final effectLines = _getEffectLines(effect, phase, subFrame); final effectLines = _getEffectLines(effect, phase, subFrame);
@@ -176,7 +176,7 @@ class CanvasBattleComposer {
return AsciiLayer( return AsciiLayer(
cells: cells, cells: cells,
zIndex: 2, zIndex: 3,
offsetX: effectX, offsetX: effectX,
offsetY: effectY, offsetY: effectY,
); );

View File

@@ -65,12 +65,7 @@ class CanvasWalkingComposer {
// 바닥 레이어(Y=7) 위에 서있도록 // 바닥 레이어(Y=7) 위에 서있도록
final charY = frameHeight - cells.length - 1; final charY = frameHeight - cells.length - 1;
return AsciiLayer( return AsciiLayer(cells: cells, zIndex: 1, offsetX: charX, offsetY: charY);
cells: cells,
zIndex: 1,
offsetX: charX,
offsetY: charY,
);
} }
/// 문자열 스프라이트를 AsciiCell 2D 배열로 변환 /// 문자열 스프라이트를 AsciiCell 2D 배열로 변환
@@ -87,27 +82,11 @@ class CanvasWalkingComposer {
const _walkingFrames = [ const _walkingFrames = [
// 프레임 1: 오른발 앞 // 프레임 1: 오른발 앞
[ [r' o ', r' /|\ ', r' /| '],
r' o ',
r' /|\ ',
r' / | ',
],
// 프레임 2: 모음 // 프레임 2: 모음
[ [r' o ', r' /|\ ', r' |\ '],
r' o ',
r' /|\ ',
r' / \ ',
],
// 프레임 3: 왼발 앞 // 프레임 3: 왼발 앞
[ [r' o ', r' /|\ ', r' /| '],
r' o ',
r' /|\ ',
r' | \ ',
],
// 프레임 4: 모음 // 프레임 4: 모음
[ [r' o ', r' /|\ ', r' |\ '],
r' o ',
r' /|\ ',
r' / \ ',
],
]; ];

View File

@@ -476,8 +476,11 @@ class _GamePlayScreenState extends State<GamePlayScreen>
_specialAnimation = AsciiAnimationType.resurrection; _specialAnimation = AsciiAnimationType.resurrection;
}); });
// 3. 애니메이션 종료 후 게임 재개 (5프레임 × 600ms = 3초) // 3. 애니메이션 종료 후 게임 재개
Future.delayed(const Duration(milliseconds: 3000), () async { final duration = getSpecialAnimationDuration(
AsciiAnimationType.resurrection,
);
Future.delayed(Duration(milliseconds: duration), () async {
if (mounted) { if (mounted) {
setState(() { setState(() {
_specialAnimation = null; _specialAnimation = null;

View File

@@ -118,13 +118,8 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
int? _lastEventTimestamp; int? _lastEventTimestamp;
bool _showCriticalEffect = false; bool _showCriticalEffect = false;
// 특수 애니메이션 프레임 수 // 특수 애니메이션 프레임 수는 ascii_animation_type.dart의
static const _specialAnimationFrameCounts = { // specialAnimationFrameCounts 상수 사용
AsciiAnimationType.levelUp: 5,
AsciiAnimationType.questComplete: 4,
AsciiAnimationType.actComplete: 4,
AsciiAnimationType.resurrection: 5,
};
@override @override
void initState() { void initState() {
@@ -242,7 +237,7 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
if (_animationMode == AnimationMode.special) { if (_animationMode == AnimationMode.special) {
_currentFrame++; _currentFrame++;
final maxFrames = final maxFrames =
_specialAnimationFrameCounts[_currentSpecialAnimation] ?? 5; specialAnimationFrameCounts[_currentSpecialAnimation] ?? 5;
// 마지막 프레임에 도달하면 특수 애니메이션 종료 // 마지막 프레임에 도달하면 특수 애니메이션 종료
if (_currentFrame >= maxFrames) { if (_currentFrame >= maxFrames) {
_currentSpecialAnimation = null; _currentSpecialAnimation = null;