import 'package:asciineverdie/src/shared/animation/ascii_animation_type.dart'; import 'package:asciineverdie/src/shared/animation/canvas/ascii_cell.dart'; import 'package:asciineverdie/src/shared/animation/canvas/ascii_layer.dart'; /// Canvas용 특수 이벤트 애니메이션 합성기 /// /// 레벨업, 퀘스트 완료, Act 완료, 부활 등 class CanvasSpecialComposer { const CanvasSpecialComposer(); /// 프레임 상수 static const int frameWidth = 60; static const int frameHeight = 8; /// 레이어 기반 프레임 생성 List composeLayers( AsciiAnimationType type, int frameIndex, int globalTick, ) { return switch (type) { AsciiAnimationType.levelUp => _composeLevelUp(frameIndex, globalTick), AsciiAnimationType.questComplete => _composeQuestComplete( frameIndex, globalTick, ), AsciiAnimationType.actComplete => _composeActComplete( frameIndex, globalTick, ), AsciiAnimationType.resurrection => _composeResurrection( frameIndex, globalTick, ), _ => [AsciiLayer.empty()], }; } /// 레벨업 애니메이션 List _composeLevelUp(int frameIndex, int globalTick) { final layers = [ _createEffectBackground(globalTick, '*'), _createCenteredSprite(_levelUpFrames[frameIndex % _levelUpFrames.length]), ]; return layers; } /// 퀘스트 완료 애니메이션 List _composeQuestComplete(int frameIndex, int globalTick) { final layers = [ _createEffectBackground(globalTick, '+'), _createCenteredSprite( _questCompleteFrames[frameIndex % _questCompleteFrames.length], ), ]; return layers; } /// Act 완료 애니메이션 List _composeActComplete(int frameIndex, int globalTick) { final layers = [ _createEffectBackground(globalTick, '~'), _createCenteredSprite( _actCompleteFrames[frameIndex % _actCompleteFrames.length], ), ]; return layers; } /// 부활 애니메이션 List _composeResurrection(int frameIndex, int globalTick) { final layers = [ _createEffectBackground(globalTick, '.'), _createCenteredSprite( _resurrectionFrames[frameIndex % _resurrectionFrames.length], ), ]; return layers; } /// 이펙트 배경 레이어 생성 (z=0) AsciiLayer _createEffectBackground(int globalTick, String effectChar) { final cells = List.generate( frameHeight, (_) => List.filled(frameWidth, AsciiCell.empty), ); // 반짝이는 이펙트 for (var y = 0; y < frameHeight; y++) { for (var x = 0; x < frameWidth; x++) { final offset = (x + y + globalTick) % 8; if (offset == 0) { cells[y][x] = AsciiCell.fromChar(effectChar); } } } return AsciiLayer(cells: cells, zIndex: 0); } /// 중앙 정렬 스프라이트 레이어 생성 (z=1) AsciiLayer _createCenteredSprite(List lines) { final cells = _spriteToCells(lines); // 중앙 정렬 final spriteWidth = lines.isEmpty ? 0 : lines[0].length; final offsetX = (frameWidth - spriteWidth) ~/ 2; final offsetY = (frameHeight - cells.length) ~/ 2; return AsciiLayer( cells: cells, zIndex: 1, offsetX: offsetX, offsetY: offsetY, ); } /// 문자열 스프라이트를 AsciiCell 2D 배열로 변환 List> _spriteToCells(List lines) { return lines.map((line) { return line.split('').map(AsciiCell.fromChar).toList(); }).toList(); } } // ============================================================================ // 레벨업 프레임 (5프레임) // ============================================================================ const _levelUpFrames = [ [r' * ', r' \|/ ', r' o ', r' /|\ ', r' / \ '], [r' * * ', r' \|/ ', r' O ', r' ', r' / \ '], [r' * * * ', r' \|/ ', r' O ', r' <\|/> ', r' / \ '], [r' * * * * ', r' LEVEL ', r' UP! ', r' \O/ ', r' / \ '], [r'* * * * *', r' LEVEL ', r' UP! ', r' \O/ ', r' | | '], ]; // ============================================================================ // 퀘스트 완료 프레임 (4프레임) // ============================================================================ const _questCompleteFrames = [ [r' [?] ', r' | ', r' o ', r' /|\ ', r' / \ '], [r' [???] ', r' | ', r' o! ', r' /|\ ', r' / \ '], [r' [DONE] ', r' ! ', r' \o/ ', r' | ', r' / \ '], [r' +[DONE]+', r' \!/ ', r' \o/ ', r' | ', r' / \ '], ]; // ============================================================================ // Act 완료 프레임 (4프레임) // ============================================================================ const _actCompleteFrames = [ [r'=========', r' ACT ', r' CLEAR ', r' o ', r' /|\ '], [r'~~~~~~~~~', r' ACT ', r' CLEAR! ', r' \o/ ', r' | '], [r'*~*~*~*~*', r' ACT ', r' CLEAR!! ', r' \O/ ', r' / \ '], [r'*********', r' ACT ', r' CLEAR!! ', r' \O/ ', r' | | '], ]; // ============================================================================ // 부활 프레임 (5프레임) // ============================================================================ const _resurrectionFrames = [ // 프레임 1: R.I.P 묘비 [r' ___ ', r' |RIP| ', r' | | ', r'__|___|__'], // 프레임 2: 빛 내림 [r' \|/ ', r' -|R|- ', r' | | ', r'__|___|__'], // 프레임 3: 일어남 [r' \o/ ', r' --|-- ', r' | | ', r'__|___|__'], // 프레임 4: 서있음 [r' o ', r' /|\ ', r' / \ ', r'_________'], // 프레임 5: 부활 완료 [r' REVIVED ', r' \o/ ', r' | ', r'___/ \___'], ];