feat(animation): 종족별 캐릭터 애니메이션 시스템 추가
- 21개 종족별 고유 ASCII 캐릭터 프레임 데이터 추가 - 각 종족당 5가지 상태 애니메이션: idle, prepare, attack, hit, recover - 종족 특성에 맞는 시각적 차별화 (마법사 ~, 기사 ♦, 언데드 ☠ 등) - 캐릭터 생성 화면 종족 미리보기 위젯 추가 - 프론트 화면 Hero vs Boss 애니메이션 개선 - 게임 플레이 화면 애니메이션 패널 연동 강화
This commit is contained in:
@@ -5,6 +5,7 @@ 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/character_frames.dart';
|
||||
import 'package:askiineverdie/src/core/animation/monster_size.dart';
|
||||
import 'package:askiineverdie/src/core/animation/race_character_frames.dart';
|
||||
import 'package:askiineverdie/src/core/animation/weapon_category.dart';
|
||||
import 'package:askiineverdie/src/core/animation/weapon_effects.dart';
|
||||
|
||||
@@ -18,6 +19,7 @@ class CanvasBattleComposer {
|
||||
required this.hasShield,
|
||||
required this.monsterCategory,
|
||||
required this.monsterSize,
|
||||
this.raceId,
|
||||
});
|
||||
|
||||
final WeaponCategory weaponCategory;
|
||||
@@ -25,6 +27,9 @@ class CanvasBattleComposer {
|
||||
final MonsterCategory monsterCategory;
|
||||
final MonsterSize monsterSize;
|
||||
|
||||
/// 종족 ID (Phase 4: 종족별 캐릭터 애니메이션)
|
||||
final String? raceId;
|
||||
|
||||
/// 프레임 상수
|
||||
static const int frameWidth = 60;
|
||||
static const int frameHeight = 8;
|
||||
@@ -94,8 +99,25 @@ class CanvasBattleComposer {
|
||||
}
|
||||
|
||||
/// 캐릭터 레이어 생성 (z=1)
|
||||
///
|
||||
/// Phase 4: 종족별 캐릭터 프레임 지원
|
||||
AsciiLayer _createCharacterLayer(BattlePhase phase, int subFrame) {
|
||||
var charFrame = getCharacterFrame(phase, subFrame);
|
||||
CharacterFrame charFrame;
|
||||
|
||||
// 종족 ID가 있으면 종족별 프레임 사용
|
||||
if (raceId != null && raceId!.isNotEmpty) {
|
||||
final raceData = RaceCharacterFrames.get(raceId!);
|
||||
if (raceData != null) {
|
||||
final frames = raceData.getFrames(phase);
|
||||
charFrame = frames[subFrame % frames.length];
|
||||
} else {
|
||||
// 종족 데이터 없으면 기본 프레임 사용
|
||||
charFrame = getCharacterFrame(phase, subFrame);
|
||||
}
|
||||
} else {
|
||||
charFrame = getCharacterFrame(phase, subFrame);
|
||||
}
|
||||
|
||||
if (hasShield) {
|
||||
charFrame = charFrame.withShield();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// 프론트 화면용 Hero vs Glitch God ASCII 애니메이션 데이터
|
||||
// 작은 용사가 거대한 Glitch God에 맞서는 장면을 표현
|
||||
// 작은 용사(3줄)가 거대한 Glitch God(10줄)에 맞서는 장면
|
||||
// 캐릭터 해부학: 머리(1개), 양팔(2개), 양다리(2개)
|
||||
|
||||
/// 애니메이션 프레임 (10줄, 6프레임 루프)
|
||||
/// 애니메이션 프레임 (10줄, 12프레임 루프)
|
||||
const frontScreenAnimationFrames = [
|
||||
// 프레임 0: 대치 상태 (방패 들고 대기)
|
||||
// ========================================================================
|
||||
// 프레임 0: 대치 상태 1 (숨쉬기 - 들숨)
|
||||
// ========================================================================
|
||||
'''
|
||||
░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░
|
||||
░▓▓ G L I T C H ▓▓░
|
||||
@@ -11,12 +14,44 @@ const frontScreenAnimationFrames = [
|
||||
░▓▓▓ ◈◈ ◈◈ ▓▓▓░
|
||||
░▓▓▓▓ ▼▼▼ ▓▓▓▓░
|
||||
░▓▓▓▓▓ ████████ ▓▓▓▓▓░
|
||||
o ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
[]|- ░▓▓▓ G O D ▓▓▓░
|
||||
/ \\ ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
o ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
/|\\ ░▓▓▓ G O D ▓▓▓░
|
||||
/ \\ ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// 프레임 1: 용사 전진 (방패 앞으로)
|
||||
// ========================================================================
|
||||
// 프레임 1: 대치 상태 2 (숨쉬기 - 날숨)
|
||||
// ========================================================================
|
||||
'''
|
||||
░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░
|
||||
░▓▓ G L I T C H ▓▓░
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
░▓▓▓ ◉◉ ◉◉ ▓▓▓░
|
||||
░▓▓▓▓ ~~~ ▓▓▓▓░
|
||||
░▓▓▓▓▓ ████████ ▓▓▓▓▓░
|
||||
O ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
/|\\ ░▓▓▓ G O D ▓▓▓░
|
||||
| | ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// ========================================================================
|
||||
// 프레임 2: 용사 전진 1
|
||||
// ========================================================================
|
||||
'''
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓ G L I T C H ▓▓
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓▓▓ ◈◈ ◈◈ ▓▓▓▓
|
||||
▓▓▓▓▓ ▼▼▼ ▓▓▓▓▓
|
||||
o ▓▓▓▓▓▓ ████████ ▓▓▓▓▓▓
|
||||
/|\\ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
/ \\ ▓▓▓▓ G O D ▓▓▓▓
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// ========================================================================
|
||||
// 프레임 3: 용사 전진 2
|
||||
// ========================================================================
|
||||
'''
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓ G L I T C H ▓▓
|
||||
@@ -24,66 +59,134 @@ const frontScreenAnimationFrames = [
|
||||
▓▓▓▓ ◉◉ ◉◉ ▓▓▓▓
|
||||
▓▓▓▓▓ ~~~ ▓▓▓▓▓
|
||||
o ▓▓▓▓▓▓ ████████ ▓▓▓▓▓▓
|
||||
[]|> ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
/ \\ ▓▓▓▓ G O D ▓▓▓▓
|
||||
/|\\ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
| | ▓▓▓▓ G O D ▓▓▓▓
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// 프레임 2: 용사 공격 준비 (방패 방어 자세)
|
||||
// ========================================================================
|
||||
// 프레임 4: 공격 준비 1 (무기 들기)
|
||||
// ========================================================================
|
||||
'''
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
▒▓ G L I T C H ▓▒
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
▒▓▓▓ ◈◈ ◈◈ ▓▓▓▒
|
||||
▒▓▓▓▓ ▼▼▼ ▓▓▓▓▒
|
||||
o\\ ▒▓▓▓▓▓ ████████ ▓▓▓▓▓▒
|
||||
[]=|== ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
o ▒▓▓▓▓▓ ████████ ▓▓▓▓▓▒
|
||||
\\| ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
/ \\ ▒▓▓▓ G O D ▓▓▓▒
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// 프레임 3: 용사 공격 (글리치 갓 데미지)
|
||||
'''
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
░▓ G#L@I*T&C!H ▓░
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
░▓▓▓ X X X X ▓▓▓░
|
||||
o\\ ░▓▓▓▓ !!! ▓▓▓▓░
|
||||
[]=|===> ░▓▓▓▓▓ ████████ ▓▓▓▓▓░
|
||||
/ \\ ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
░▓▓▓ G O D ▓▓▓░
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// 프레임 4: 글리치 갓 반격 준비 (방패로 방어)
|
||||
'''
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓ G L I T C H ▓▓
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓▓▓ @@ @@ ▓▓▓▓
|
||||
▓▓▓▓▓ <=== ▓▓▓▓▓
|
||||
[]o ▓▓▓▓▓▓ ████████ ▓▓▓▓▓▓
|
||||
|\\ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
/ \\ ▓▓▓▓ G O D ▓▓▓▓
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// 프레임 5: 글리치 갓 공격 (용사 방패로 막기)
|
||||
// ========================================================================
|
||||
// 프레임 5: 공격 준비 2 (무기 뒤로)
|
||||
// ========================================================================
|
||||
'''
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
▒▓ G L I T C H ▓▒
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
▒▓▓▓ ◉◉ ◉◉ ▓▓▓▒
|
||||
▒▓▓▓▓ <====== ▓▓▓▓▒
|
||||
[]o * ▒▓▓▓▓▓ ████████ ▓▓▓▓▓▒
|
||||
|/ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
|\\ ▒▓▓▓ G O D ▓▓▓▒
|
||||
▒▓▓▓▓ ~~~ ▓▓▓▓▒
|
||||
o ▒▓▓▓▓▓ ████████ ▓▓▓▓▓▒
|
||||
\\|/ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
/ \\ ▒▓▓▓ G O D ▓▓▓▒
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// ========================================================================
|
||||
// 프레임 6: 공격 1 (휘두르기)
|
||||
// ========================================================================
|
||||
'''
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
░▓ G L I T C H ▓░
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
░▓▓▓ ◈◈ ◈◈ ▓▓▓░
|
||||
░▓▓▓▓ ▼▼▼ ▓▓▓▓░
|
||||
o ░▓▓▓▓▓ ████████ ▓▓▓▓▓░
|
||||
\\|- ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
/ \\ ░▓▓▓ G O D ▓▓▓░
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// ========================================================================
|
||||
// 프레임 7: 공격 2 (이펙트 + 히트)
|
||||
// ========================================================================
|
||||
'''
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
░▓ G#L@I*T&C!H ▓░
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
░▓▓▓ X X X X ▓▓▓░
|
||||
░▓▓▓▓ !!! ▓▓▓▓░
|
||||
o ░▓▓▓▓▓ ████████ ▓▓▓▓▓░
|
||||
\\|-=>* ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
/ \\ ░▓▓▓ G O D ▓▓▓░
|
||||
░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// ========================================================================
|
||||
// 프레임 8: 보스 반격 준비 1
|
||||
// ========================================================================
|
||||
'''
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓ G L I T C H ▓▓
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓▓▓ @@ @@ ▓▓▓▓
|
||||
▓▓▓▓▓ <=== ▓▓▓▓▓
|
||||
o ▓▓▓▓▓▓ ████████ ▓▓▓▓▓▓
|
||||
/|\\ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
/ \\ ▓▓▓▓ G O D ▓▓▓▓
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// ========================================================================
|
||||
// 프레임 9: 보스 반격 2 (글리치 증가)
|
||||
// ========================================================================
|
||||
'''
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
▒▓ G L I T C H ▓▒
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
▒▓▓▓ ◉◉ ◉◉ ▓▓▓▒
|
||||
▒▓▓▓▓ <====== ▓▓▓▓▒
|
||||
o ▒▓▓▓▓▓ ████████ ▓▓▓▓▓▒
|
||||
/|\\ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
/ \\ ▒▓▓▓ G O D ▓▓▓▒
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// ========================================================================
|
||||
// 프레임 10: 방어 1 (방패 들기)
|
||||
// ========================================================================
|
||||
'''
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓ G L I T C H ▓▓
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
▓▓▓▓ @@ @@ ▓▓▓▓
|
||||
▓▓▓▓▓ <==== ▓▓▓▓▓
|
||||
o[] ▓▓▓▓▓▓ ████████ ▓▓▓▓▓▓
|
||||
/| ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
/ \\ ▓▓▓▓ G O D ▓▓▓▓
|
||||
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
|
||||
// ========================================================================
|
||||
// 프레임 11: 방어 2 (충격파 막기)
|
||||
// ========================================================================
|
||||
'''
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
▒▓ G L I T C H ▓▒
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
▒▓▓▓ ◉◉ ◉◉ ▓▓▓▒
|
||||
▒▓▓▓▓ <===== ▓▓▓▓▒
|
||||
o[]* ▒▓▓▓▓▓ ████████ ▓▓▓▓▓▒
|
||||
/| ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
/ \\ ▒▓▓▓ G O D ▓▓▓▒
|
||||
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒
|
||||
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~''',
|
||||
];
|
||||
|
||||
/// 애니메이션 프레임 간격 (밀리초)
|
||||
const frontScreenAnimationIntervalMs = 400;
|
||||
const frontScreenAnimationIntervalMs = 350;
|
||||
|
||||
/// 애니메이션 총 프레임 수
|
||||
const frontScreenAnimationFrameCount = 6;
|
||||
const frontScreenAnimationFrameCount = 12;
|
||||
|
||||
754
lib/src/core/animation/race_character_frames.dart
Normal file
754
lib/src/core/animation/race_character_frames.dart
Normal file
@@ -0,0 +1,754 @@
|
||||
// 종족별 ASCII 캐릭터 프레임 데이터
|
||||
// 모든 캐릭터는 3줄 × 6자 폭으로 통일 (보스 10줄과 대비)
|
||||
|
||||
import 'package:askiineverdie/src/core/animation/character_frames.dart';
|
||||
|
||||
/// 종족별 캐릭터 프레임 저장소
|
||||
class RaceCharacterFrames {
|
||||
RaceCharacterFrames._();
|
||||
|
||||
/// 종족 ID로 프레임 데이터 조회
|
||||
static RaceFrameData? get(String raceId) => _raceFrames[raceId];
|
||||
|
||||
/// 기본 프레임 (종족 미지정 시)
|
||||
static RaceFrameData get defaultFrames => _raceFrames['byte_human']!;
|
||||
|
||||
/// 모든 종족 ID 목록
|
||||
static List<String> get allRaceIds => _raceFrames.keys.toList();
|
||||
}
|
||||
|
||||
/// 종족별 프레임 데이터
|
||||
class RaceFrameData {
|
||||
const RaceFrameData({
|
||||
required this.raceId,
|
||||
required this.idle,
|
||||
required this.prepare,
|
||||
required this.attack,
|
||||
required this.hit,
|
||||
required this.recover,
|
||||
});
|
||||
|
||||
final String raceId;
|
||||
final List<CharacterFrame> idle; // 4 프레임
|
||||
final List<CharacterFrame> prepare; // 3 프레임
|
||||
final List<CharacterFrame> attack; // 5 프레임
|
||||
final List<CharacterFrame> hit; // 3 프레임
|
||||
final List<CharacterFrame> recover; // 3 프레임
|
||||
|
||||
/// BattlePhase로 프레임 목록 조회
|
||||
List<CharacterFrame> getFrames(BattlePhase phase) {
|
||||
return switch (phase) {
|
||||
BattlePhase.idle => idle,
|
||||
BattlePhase.prepare => prepare,
|
||||
BattlePhase.attack => attack,
|
||||
BattlePhase.hit => hit,
|
||||
BattlePhase.recover => recover,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 종족별 프레임 데이터 (21개 종족)
|
||||
// 구조: [머리, 몸통+팔, 다리] - 모두 6자 폭
|
||||
// ============================================================================
|
||||
|
||||
const _raceFrames = <String, RaceFrameData>{
|
||||
// --------------------------------------------------------------------------
|
||||
// 기본형 (Byte Human, Buffer Dwarf)
|
||||
// --------------------------------------------------------------------------
|
||||
'byte_human': RaceFrameData(
|
||||
raceId: 'byte_human',
|
||||
idle: [
|
||||
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' | | ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' O ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' o ', r' \|\ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' \|/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
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' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' o ', r'/|\* ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r'/|\ *', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' o ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'buffer_dwarf': RaceFrameData(
|
||||
raceId: 'buffer_dwarf',
|
||||
idle: [
|
||||
CharacterFrame([r' o ', r' /█\ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' /█\ ', r' | | ']),
|
||||
CharacterFrame([r' o ', r' /█\ ', r' / \ ']),
|
||||
CharacterFrame([r' O ', r' /█\ ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' o ', r' \█\ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' \█ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' \█/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
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' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' o ', r'/█\* ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r'/█\ *', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' /█\ ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' o ', r' /█\ ', r' | ']),
|
||||
CharacterFrame([r' o ', r' /█\ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' /█\ ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 대형 (Kernel Giant, Heap Troll)
|
||||
// --------------------------------------------------------------------------
|
||||
'kernel_giant': RaceFrameData(
|
||||
raceId: 'kernel_giant',
|
||||
idle: [
|
||||
CharacterFrame([r' O ', r' /█\ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' O ', r' /█\ ', r' | | ']),
|
||||
CharacterFrame([r' O ', r' /█\ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' O ', r' /█\ ', r' ┘ └ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' O ', r' \█\ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' O ', r' \█ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' O ', r' \█/ ', r' ┘ └ ']),
|
||||
],
|
||||
attack: [
|
||||
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' ┘ └ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' O ', r'/█\* ', r' ┘ └ ']),
|
||||
CharacterFrame([r' O ', r'/█\ *', r' ┘ └ ']),
|
||||
CharacterFrame([r' O ', r' /█\ ', r' ┘ └ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' O ', r' /█\ ', r' || ']),
|
||||
CharacterFrame([r' O ', r' /█\ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' O ', r' /█\ ', r' ┘ └ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'heap_troll': RaceFrameData(
|
||||
raceId: 'heap_troll',
|
||||
idle: [
|
||||
CharacterFrame([r' Ö ', r' /▓\ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r' /▓\ ', r' | | ']),
|
||||
CharacterFrame([r' Ö ', r' /▓\ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r' /▓\ ', r' ┘ └ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' Ö ', r' \▓\ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r' \▓ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r' \▓/ ', r' ┘ └ ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' Ö ', r' \▓ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r' \▓- ', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r' \▓-- ', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r'\▓-=>', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r' /▓\ ', r' ┘ └ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' Ö ', r'/▓\* ', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r'/▓\ *', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r' /▓\ ', r' ┘ └ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' Ö ', r' /▓\ ', r' || ']),
|
||||
CharacterFrame([r' Ö ', r' /▓\ ', r' ┘ └ ']),
|
||||
CharacterFrame([r' Ö ', r' /▓\ ', r' ┘ └ ']),
|
||||
],
|
||||
),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 전사형 (Protocol Paladin, Flag Knight, Array Orc)
|
||||
// --------------------------------------------------------------------------
|
||||
'protocol_paladin': RaceFrameData(
|
||||
raceId: 'protocol_paladin',
|
||||
idle: [
|
||||
CharacterFrame([r' o ', r' /|+ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' /|+ ', r' | | ']),
|
||||
CharacterFrame([r' o ', r' /|+ ', r' / \ ']),
|
||||
CharacterFrame([r' O ', r' /|+ ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' o ', r' \|+ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' \|/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
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' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' o ', r'/|+* ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r'/|+ *', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' /|+ ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' o ', r' /|+ ', r' | ']),
|
||||
CharacterFrame([r' o ', r' /|+ ', r' / \ ']),
|
||||
CharacterFrame([r' o ', r' /|+ ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'flag_knight': RaceFrameData(
|
||||
raceId: 'flag_knight',
|
||||
idle: [
|
||||
CharacterFrame([r' ♦ ', r' /|] ', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r' /|] ', r' | | ']),
|
||||
CharacterFrame([r' ♦ ', r' /|] ', r' / \ ']),
|
||||
CharacterFrame([r' ◆ ', r' /|] ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' ♦ ', r' \|] ', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r' \|/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' ♦ ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r' \|- ', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r' \|-- ', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r'\|-->', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r' /|] ', r' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' ♦ ', r'/|]* ', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r'/|] *', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r' /|] ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' ♦ ', r' /|] ', r' | ']),
|
||||
CharacterFrame([r' ♦ ', r' /|] ', r' / \ ']),
|
||||
CharacterFrame([r' ♦ ', r' /|] ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'array_orc': RaceFrameData(
|
||||
raceId: 'array_orc',
|
||||
idle: [
|
||||
CharacterFrame([r' ö ', r' /▒\ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' /▒\ ', r' | | ']),
|
||||
CharacterFrame([r' ö ', r' /▒\ ', r' / \ ']),
|
||||
CharacterFrame([r' Ö ', r' /▒\ ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' ö ', r' \▒\ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' \▒ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' \▒/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' ö ', r' \▒ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' \▒- ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' \▒-- ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r'\▒-=>', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' /▒\ ', r' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' ö ', r'/▒\* ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r'/▒\ *', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' /▒\ ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' ö ', r' /▒\ ', r' | ']),
|
||||
CharacterFrame([r' ö ', r' /▒\ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' /▒\ ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 마법형 (Loop Wizard, Lambda Druid, Recursive Sage)
|
||||
// --------------------------------------------------------------------------
|
||||
'loop_wizard': RaceFrameData(
|
||||
raceId: 'loop_wizard',
|
||||
idle: [
|
||||
CharacterFrame([r' o~ ', r' /|) ', r' / \ ']),
|
||||
CharacterFrame([r' o~ ', r' /|) ', r' | | ']),
|
||||
CharacterFrame([r' o~ ', r' /|) ', r' / \ ']),
|
||||
CharacterFrame([r' O~ ', r' /|) ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' o~ ', r' \|) ', r' / \ ']),
|
||||
CharacterFrame([r' o~ ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' o~ ', r' \|/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
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' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' o~ ', r'/|)* ', r' / \ ']),
|
||||
CharacterFrame([r' o~ ', r'/|) *', r' / \ ']),
|
||||
CharacterFrame([r' o~ ', r' /|) ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' o~ ', r' /|) ', r' | ']),
|
||||
CharacterFrame([r' o~ ', r' /|) ', r' / \ ']),
|
||||
CharacterFrame([r' o~ ', r' /|) ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'lambda_druid': RaceFrameData(
|
||||
raceId: 'lambda_druid',
|
||||
idle: [
|
||||
CharacterFrame([r' o♣ ', r' /|) ', r' / \ ']),
|
||||
CharacterFrame([r' o♣ ', r' /|) ', r' | | ']),
|
||||
CharacterFrame([r' o♣ ', r' /|) ', r' / \ ']),
|
||||
CharacterFrame([r' O♣ ', r' /|) ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' o♣ ', r' \|) ', r' / \ ']),
|
||||
CharacterFrame([r' o♣ ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' o♣ ', r' \|/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
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' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' o♣ ', r'/|)* ', r' / \ ']),
|
||||
CharacterFrame([r' o♣ ', r'/|) *', r' / \ ']),
|
||||
CharacterFrame([r' o♣ ', r' /|) ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' o♣ ', r' /|) ', r' | ']),
|
||||
CharacterFrame([r' o♣ ', r' /|) ', r' / \ ']),
|
||||
CharacterFrame([r' o♣ ', r' /|) ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'recursive_sage': RaceFrameData(
|
||||
raceId: 'recursive_sage',
|
||||
idle: [
|
||||
CharacterFrame([r' o∞ ', r' /|) ', r' / \ ']),
|
||||
CharacterFrame([r' o∞ ', r' /|) ', r' | | ']),
|
||||
CharacterFrame([r' o∞ ', r' /|) ', r' / \ ']),
|
||||
CharacterFrame([r' O∞ ', r' /|) ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' o∞ ', r' \|) ', r' / \ ']),
|
||||
CharacterFrame([r' o∞ ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' o∞ ', r' \|/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
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' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' o∞ ', r'/|)* ', r' / \ ']),
|
||||
CharacterFrame([r' o∞ ', r'/|) *', r' / \ ']),
|
||||
CharacterFrame([r' o∞ ', r' /|) ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' o∞ ', r' /|) ', r' | ']),
|
||||
CharacterFrame([r' o∞ ', r' /|) ', r' / \ ']),
|
||||
CharacterFrame([r' o∞ ', r' /|) ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 언데드/엘프 (Coredump Undead, Null Elf, Callback Priest)
|
||||
// --------------------------------------------------------------------------
|
||||
'coredump_undead': RaceFrameData(
|
||||
raceId: 'coredump_undead',
|
||||
idle: [
|
||||
CharacterFrame([r' ☠ ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r' /|\ ', r' | | ']),
|
||||
CharacterFrame([r' ☠ ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' ☠ ', r' \|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r' \|/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' ☠ ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r' \|- ', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r' \|-- ', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r'\|-~>', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' ☠ ', r'/|\~ ', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r'/|\ ~', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' ☠ ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' ☠ ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ☠ ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'null_elf': RaceFrameData(
|
||||
raceId: 'null_elf',
|
||||
idle: [
|
||||
CharacterFrame([r' ö ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' /|\ ', r' | | ']),
|
||||
CharacterFrame([r' ö ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' Ö ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' ö ', r' \|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' \|/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' ö ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' \|~ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' \|~~ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r'\|~~*', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' ö ', r'/|\* ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r'/|\ *', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' ö ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' ö ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ö ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'callback_priest': RaceFrameData(
|
||||
raceId: 'callback_priest',
|
||||
idle: [
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' | | ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' Ô ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' ô ', r' \|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r' \|/ ', r' / \ ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' ô ', r' \| ', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r' \|+ ', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r' \|++ ', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r'\|++*', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' ô ', r'/|\+ ', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r'/|\ +', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' / \ ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' / \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 소형 (Bit Halfling, Stack Goblin, Cache Imp)
|
||||
// --------------------------------------------------------------------------
|
||||
'bit_halfling': RaceFrameData(
|
||||
raceId: 'bit_halfling',
|
||||
idle: [
|
||||
CharacterFrame([r' o ', r' /|\ ', r' Y ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' Y ']),
|
||||
CharacterFrame([r' O ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' o ', r' \|\ ', r' Y ']),
|
||||
CharacterFrame([r' o ', r' \| ', r' Y ']),
|
||||
CharacterFrame([r' o ', r' \|/ ', r' Y ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' o ', r' \| ', r' Y ']),
|
||||
CharacterFrame([r' o ', r' \|- ', r' Y ']),
|
||||
CharacterFrame([r' o ', r' \|-- ', r' Y ']),
|
||||
CharacterFrame([r' o ', r'\|-=>', r' Y ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' o ', r'/|\* ', r' Y ']),
|
||||
CharacterFrame([r' o ', r'/|\ *', r' Y ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' o ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' Y ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
),
|
||||
|
||||
'stack_goblin': RaceFrameData(
|
||||
raceId: 'stack_goblin',
|
||||
idle: [
|
||||
CharacterFrame([r' ◦ ', r' /|\ ', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' ◦ ', r' /|\ ', r' Y ']),
|
||||
CharacterFrame([r' ● ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' ◦ ', r' \|\ ', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r' \| ', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r' \|/ ', r' Y ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' ◦ ', r' \| ', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r' \|- ', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r' \|-- ', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r'\|-=>', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' ◦ ', r'/|\* ', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r'/|\ *', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' ◦ ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' ◦ ', r' /|\ ', r' Y ']),
|
||||
CharacterFrame([r' ◦ ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
),
|
||||
|
||||
'cache_imp': RaceFrameData(
|
||||
raceId: 'cache_imp',
|
||||
idle: [
|
||||
CharacterFrame([r' ^ ', r' /|\ ', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' ^ ', r' /|\ ', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' ^ ', r' \|\ ', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r' \| ', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r' \|/ ', r' Y ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' ^ ', r' \| ', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r' \|- ', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r' \|-- ', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r'\|-->', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' ^ ', r'/|\* ', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r'/|\ *', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' ^ ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' ^ ', r' /|\ ', r' Y ']),
|
||||
CharacterFrame([r' ^ ', r' /|\ ', r' Y ']),
|
||||
],
|
||||
),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 영체 (Thread Spirit, Pointer Fairy)
|
||||
// --------------------------------------------------------------------------
|
||||
'thread_spirit': RaceFrameData(
|
||||
raceId: 'thread_spirit',
|
||||
idle: [
|
||||
CharacterFrame([r' ·o· ', r' | ', r' ~~~ ']),
|
||||
CharacterFrame([r' ·o· ', r' | ', r' ~~ ']),
|
||||
CharacterFrame([r' ·o· ', r' | ', r' ~~~ ']),
|
||||
CharacterFrame([r' ·O· ', r' | ', r' ~~~ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' ·o· ', r' \| ', r' ~~~ ']),
|
||||
CharacterFrame([r' ·o· ', r' \ ', r' ~~~ ']),
|
||||
CharacterFrame([r' ·o· ', r' \/ ', r' ~~~ ']),
|
||||
],
|
||||
attack: [
|
||||
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' ~~~ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' ·o· ', r' |* ', r' ~~~ ']),
|
||||
CharacterFrame([r' ·o· ', r' | * ', r' ~~~ ']),
|
||||
CharacterFrame([r' ·o· ', r' | ', r' ~~~ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' ·o· ', r' | ', r' ~~ ']),
|
||||
CharacterFrame([r' ·o· ', r' | ', r' ~~~ ']),
|
||||
CharacterFrame([r' ·o· ', r' | ', r' ~~~ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'pointer_fairy': RaceFrameData(
|
||||
raceId: 'pointer_fairy',
|
||||
idle: [
|
||||
CharacterFrame([r' *o* ', r' | ', r' ~~~ ']),
|
||||
CharacterFrame([r' *o* ', r' | ', r' ~~ ']),
|
||||
CharacterFrame([r' *o* ', r' | ', r' ~~~ ']),
|
||||
CharacterFrame([r' *O* ', r' | ', r' ~~~ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' *o* ', r' \| ', r' ~~~ ']),
|
||||
CharacterFrame([r' *o* ', r' \ ', r' ~~~ ']),
|
||||
CharacterFrame([r' *o* ', r' \/ ', r' ~~~ ']),
|
||||
],
|
||||
attack: [
|
||||
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' ~~~ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' *o* ', r' |+ ', r' ~~~ ']),
|
||||
CharacterFrame([r' *o* ', r' | + ', r' ~~~ ']),
|
||||
CharacterFrame([r' *o* ', r' | ', r' ~~~ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' *o* ', r' | ', r' ~~ ']),
|
||||
CharacterFrame([r' *o* ', r' | ', r' ~~~ ']),
|
||||
CharacterFrame([r' *o* ', r' | ', r' ~~~ ']),
|
||||
],
|
||||
),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 레인저형 (Index Ranger, Iterator Rogue)
|
||||
// --------------------------------------------------------------------------
|
||||
'index_ranger': RaceFrameData(
|
||||
raceId: 'index_ranger',
|
||||
idle: [
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' | | ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
CharacterFrame([r' O ', r' /|\ ', r' λ \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' o ', r' \|\ ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' \| ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' \|/ ', r' λ \ ']),
|
||||
],
|
||||
attack: [
|
||||
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' λ \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' o ', r'/|\* ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r'/|\ *', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' o ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
'iterator_rogue': RaceFrameData(
|
||||
raceId: 'iterator_rogue',
|
||||
idle: [
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' | | ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' o ', r' \|\ ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' \| ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' \|/ ', r' λ \ ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' o ', r' \| ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' \|- ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' \|-- ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r'\|--x', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' o ', r'/|\x ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r'/|\ x', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' o ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
CharacterFrame([r' o ', r' /|\ ', r' λ \ ']),
|
||||
],
|
||||
),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 노움 (Register Gnome)
|
||||
// --------------------------------------------------------------------------
|
||||
'register_gnome': RaceFrameData(
|
||||
raceId: 'register_gnome',
|
||||
idle: [
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' ∧ ']),
|
||||
CharacterFrame([r' Ô ', r' /|\ ', r' ∧ ']),
|
||||
],
|
||||
prepare: [
|
||||
CharacterFrame([r' ô ', r' \|\ ', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r' \| ', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r' \|/ ', r' ∧ ']),
|
||||
],
|
||||
attack: [
|
||||
CharacterFrame([r' ô ', r' \| ', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r' \|* ', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r' \|** ', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r'\|**>', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' ∧ ']),
|
||||
],
|
||||
hit: [
|
||||
CharacterFrame([r' ô ', r'/|\* ', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r'/|\ *', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' ∧ ']),
|
||||
],
|
||||
recover: [
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' | ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' ∧ ']),
|
||||
CharacterFrame([r' ô ', r' /|\ ', r' ∧ ']),
|
||||
],
|
||||
),
|
||||
};
|
||||
@@ -3,12 +3,13 @@ import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/data/race_data.dart';
|
||||
import 'package:askiineverdie/src/core/animation/front_screen_animation.dart';
|
||||
|
||||
/// 프론트 화면용 Hero vs Glitch God ASCII 애니메이션 위젯
|
||||
///
|
||||
/// 작은 용사가 거대한 Glitch God에 맞서는 루프 애니메이션
|
||||
/// 항상 검은 배경에 흰색 텍스트, 특수 효과만 컬러로 표시
|
||||
/// RichText로 문자별 색상 적용, 랜덤 종족 변경 지원
|
||||
class HeroVsBossAnimation extends StatefulWidget {
|
||||
const HeroVsBossAnimation({super.key});
|
||||
|
||||
@@ -18,23 +19,30 @@ class HeroVsBossAnimation extends StatefulWidget {
|
||||
|
||||
class _HeroVsBossAnimationState extends State<HeroVsBossAnimation> {
|
||||
int _currentFrame = 0;
|
||||
Timer? _timer;
|
||||
Timer? _animationTimer;
|
||||
Timer? _raceChangeTimer;
|
||||
final Random _random = Random();
|
||||
|
||||
// 현재 종족 (랜덤 변경)
|
||||
String _currentRaceId = 'byte_human';
|
||||
int _raceIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_startAnimation();
|
||||
_startRaceChangeTimer();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
_animationTimer?.cancel();
|
||||
_raceChangeTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startAnimation() {
|
||||
_timer = Timer.periodic(
|
||||
_animationTimer = Timer.periodic(
|
||||
const Duration(milliseconds: frontScreenAnimationIntervalMs),
|
||||
(_) {
|
||||
if (mounted) {
|
||||
@@ -47,6 +55,26 @@ class _HeroVsBossAnimationState extends State<HeroVsBossAnimation> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 8초마다 랜덤 종족 변경
|
||||
void _startRaceChangeTimer() {
|
||||
_raceChangeTimer = Timer.periodic(
|
||||
const Duration(seconds: 8),
|
||||
(_) => _changeRace(),
|
||||
);
|
||||
}
|
||||
|
||||
void _changeRace() {
|
||||
if (!mounted) return;
|
||||
|
||||
final allRaces = RaceData.all;
|
||||
if (allRaces.isEmpty) return;
|
||||
|
||||
setState(() {
|
||||
_raceIndex = (_raceIndex + 1) % allRaces.length;
|
||||
_currentRaceId = allRaces[_raceIndex].raceId;
|
||||
});
|
||||
}
|
||||
|
||||
/// 글리치 효과: 랜덤 문자 대체
|
||||
String _applyGlitchEffect(String frame) {
|
||||
// 10% 확률로 글리치 효과 적용
|
||||
@@ -66,12 +94,52 @@ class _HeroVsBossAnimationState extends State<HeroVsBossAnimation> {
|
||||
return chars.join();
|
||||
}
|
||||
|
||||
/// 문자별 색상 결정
|
||||
Color _getCharColor(String char) {
|
||||
// 공격/이펙트 (시안)
|
||||
if ('><=!+'.contains(char)) return Colors.cyan;
|
||||
// 데미지/글리치 (마젠타)
|
||||
if ('*~@#\$%&'.contains(char)) return const Color(0xFFFF00FF);
|
||||
// 보스 눈 (빨강)
|
||||
if ('◈◉X'.contains(char)) return Colors.red;
|
||||
// 보스 타이틀 텍스트 (시안)
|
||||
if ('GLITCH'.contains(char) || 'GOD'.contains(char)) return Colors.cyan;
|
||||
// 기본 (흰색)
|
||||
return Colors.white;
|
||||
}
|
||||
|
||||
/// 문자열을 색상별 TextSpan으로 변환
|
||||
TextSpan _buildColoredTextSpan(String text) {
|
||||
final spans = <TextSpan>[];
|
||||
|
||||
for (final char in text.split('')) {
|
||||
final color = _getCharColor(char);
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: char,
|
||||
style: TextStyle(
|
||||
fontFamily: 'JetBrainsMono',
|
||||
fontSize: 10,
|
||||
height: 1.1,
|
||||
color: color,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return TextSpan(children: spans);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final frame = _applyGlitchEffect(
|
||||
frontScreenAnimationFrames[_currentFrame],
|
||||
);
|
||||
|
||||
// 현재 종족 이름 (UI 표시용)
|
||||
final raceName = RaceData.findById(_currentRaceId)?.name ?? 'Hero';
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -93,23 +161,26 @@ class _HeroVsBossAnimationState extends State<HeroVsBossAnimation> {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// ASCII 애니메이션 (흰색 텍스트)
|
||||
// ASCII 애니메이션 (컬러 적용)
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
frame,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'JetBrainsMono',
|
||||
fontSize: 10,
|
||||
height: 1.1,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
child: RichText(
|
||||
text: _buildColoredTextSpan(frame),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 하단 효과 바 (컬러)
|
||||
_buildEffectBar(),
|
||||
const SizedBox(height: 4),
|
||||
// 현재 종족 표시
|
||||
Text(
|
||||
'♦ $raceName',
|
||||
style: TextStyle(
|
||||
fontFamily: 'JetBrainsMono',
|
||||
fontSize: 9,
|
||||
color: Colors.cyan.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -485,10 +485,24 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
Navigator.pop(context); // 다이얼로그 닫기
|
||||
// 안전한 언어 변경: 전체 화면 재생성
|
||||
final navigator = Navigator.of(this.context);
|
||||
await widget.controller.pause(saveOnStop: true);
|
||||
game_l10n.setGameLocale(locale);
|
||||
setState(() {});
|
||||
if (mounted) {
|
||||
await widget.controller.resume();
|
||||
navigator.pushReplacement(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => GamePlayScreen(
|
||||
controller: widget.controller,
|
||||
currentThemeMode: widget.currentThemeMode,
|
||||
onThemeModeChange: widget.onThemeModeChange,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -573,6 +587,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
notificationService: _notificationService,
|
||||
specialAnimation: _specialAnimation,
|
||||
onLanguageChange: (locale) async {
|
||||
// navigator 참조를 async gap 전에 저장
|
||||
final navigator = Navigator.of(context);
|
||||
// 1. 현재 상태 저장
|
||||
await widget.controller.pause(saveOnStop: true);
|
||||
// 2. 로케일 변경
|
||||
@@ -580,7 +596,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
// 3. 화면 재생성 (전체 UI 재구성)
|
||||
if (context.mounted) {
|
||||
await widget.controller.resume();
|
||||
Navigator.of(context).pushReplacement(
|
||||
navigator.pushReplacement(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => GamePlayScreen(
|
||||
controller: widget.controller,
|
||||
@@ -689,6 +705,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
monsterLevel: state.progress.currentTask.monsterLevel,
|
||||
latestCombatEvent:
|
||||
state.progress.currentCombat?.recentEvents.lastOrNull,
|
||||
raceId: state.traits.raceId,
|
||||
),
|
||||
|
||||
// 메인 3패널 영역
|
||||
|
||||
@@ -405,6 +405,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
monsterLevel: state.progress.currentTask.monsterLevel,
|
||||
latestCombatEvent:
|
||||
state.progress.currentCombat?.recentEvents.lastOrNull,
|
||||
raceId: state.traits.raceId,
|
||||
),
|
||||
|
||||
// 중앙: 캐로셀 (PageView)
|
||||
|
||||
@@ -45,6 +45,7 @@ class AsciiAnimationCard extends StatefulWidget {
|
||||
this.monsterLevel,
|
||||
this.isPaused = false,
|
||||
this.latestCombatEvent,
|
||||
this.raceId,
|
||||
});
|
||||
|
||||
final TaskType taskType;
|
||||
@@ -75,6 +76,9 @@ class AsciiAnimationCard extends StatefulWidget {
|
||||
/// 최근 전투 이벤트 (애니메이션 동기화용)
|
||||
final CombatEvent? latestCombatEvent;
|
||||
|
||||
/// 종족 ID (Phase 4: 종족별 캐릭터 애니메이션)
|
||||
final String? raceId;
|
||||
|
||||
@override
|
||||
State<AsciiAnimationCard> createState() => _AsciiAnimationCardState();
|
||||
}
|
||||
@@ -168,7 +172,8 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
oldWidget.monsterBaseName != widget.monsterBaseName ||
|
||||
oldWidget.weaponName != widget.weaponName ||
|
||||
oldWidget.shieldName != widget.shieldName ||
|
||||
oldWidget.monsterLevel != widget.monsterLevel) {
|
||||
oldWidget.monsterLevel != widget.monsterLevel ||
|
||||
oldWidget.raceId != widget.raceId) {
|
||||
_updateAnimation();
|
||||
}
|
||||
}
|
||||
@@ -391,6 +396,7 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
hasShield: hasShield,
|
||||
monsterCategory: monsterCategory,
|
||||
monsterSize: monsterSize,
|
||||
raceId: widget.raceId,
|
||||
);
|
||||
|
||||
// 환경 타입 추론
|
||||
|
||||
@@ -30,6 +30,7 @@ class EnhancedAnimationPanel extends StatefulWidget {
|
||||
this.characterLevel,
|
||||
this.monsterLevel,
|
||||
this.latestCombatEvent,
|
||||
this.raceId,
|
||||
});
|
||||
|
||||
final ProgressState progress;
|
||||
@@ -46,6 +47,9 @@ class EnhancedAnimationPanel extends StatefulWidget {
|
||||
final int? monsterLevel;
|
||||
final CombatEvent? latestCombatEvent;
|
||||
|
||||
/// 종족 ID (Phase 4: 종족별 캐릭터 애니메이션)
|
||||
final String? raceId;
|
||||
|
||||
@override
|
||||
State<EnhancedAnimationPanel> createState() => _EnhancedAnimationPanelState();
|
||||
}
|
||||
@@ -183,6 +187,7 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
||||
monsterLevel: widget.monsterLevel,
|
||||
isPaused: widget.isPaused,
|
||||
latestCombatEvent: widget.latestCombatEvent,
|
||||
raceId: widget.raceId,
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ class TaskProgressPanel extends StatelessWidget {
|
||||
this.characterLevel,
|
||||
this.monsterLevel,
|
||||
this.latestCombatEvent,
|
||||
this.raceId,
|
||||
});
|
||||
|
||||
final ProgressState progress;
|
||||
@@ -45,6 +46,9 @@ class TaskProgressPanel extends StatelessWidget {
|
||||
/// 최근 전투 이벤트 (애니메이션 동기화용, Phase 5)
|
||||
final CombatEvent? latestCombatEvent;
|
||||
|
||||
/// 종족 ID (Phase 4: 종족별 캐릭터 애니메이션)
|
||||
final String? raceId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@@ -71,6 +75,7 @@ class TaskProgressPanel extends StatelessWidget {
|
||||
monsterLevel: monsterLevel,
|
||||
isPaused: isPaused,
|
||||
latestCombatEvent: latestCombatEvent,
|
||||
raceId: raceId,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:askiineverdie/src/core/model/race_traits.dart';
|
||||
import 'package:askiineverdie/src/core/util/deterministic_random.dart';
|
||||
import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart';
|
||||
import 'package:askiineverdie/src/core/util/pq_logic.dart';
|
||||
import 'package:askiineverdie/src/features/new_character/widgets/race_preview.dart';
|
||||
|
||||
/// 캐릭터 생성 화면 (NewGuy.pas 포팅)
|
||||
class NewCharacterScreen extends StatefulWidget {
|
||||
@@ -264,6 +265,14 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
_buildStatsSection(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 종족 미리보기 (Phase 5: 종족별 캐릭터 애니메이션)
|
||||
Center(
|
||||
child: RacePreview(
|
||||
raceId: _races[_selectedRaceIndex].raceId,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 종족/직업 선택 섹션
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
129
lib/src/features/new_character/widgets/race_preview.dart
Normal file
129
lib/src/features/new_character/widgets/race_preview.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/src/core/animation/character_frames.dart';
|
||||
import 'package:askiineverdie/src/core/animation/race_character_frames.dart';
|
||||
|
||||
/// 종족 미리보기 위젯
|
||||
///
|
||||
/// 새 캐릭터 생성 화면에서 선택한 종족의 idle 애니메이션을 보여줌.
|
||||
/// RichText 기반 색상 적용.
|
||||
class RacePreview extends StatefulWidget {
|
||||
const RacePreview({
|
||||
super.key,
|
||||
required this.raceId,
|
||||
});
|
||||
|
||||
/// 종족 ID (예: "byte_human", "kernel_giant")
|
||||
final String raceId;
|
||||
|
||||
@override
|
||||
State<RacePreview> createState() => _RacePreviewState();
|
||||
}
|
||||
|
||||
class _RacePreviewState extends State<RacePreview> {
|
||||
Timer? _timer;
|
||||
int _currentFrame = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_startAnimation();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(RacePreview oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.raceId != widget.raceId) {
|
||||
_currentFrame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startAnimation() {
|
||||
_timer = Timer.periodic(const Duration(milliseconds: 400), (_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_currentFrame++;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final raceData = RaceCharacterFrames.get(widget.raceId);
|
||||
final frames = raceData?.idle ?? RaceCharacterFrames.defaultFrames.idle;
|
||||
final frame = frames[_currentFrame % frames.length];
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 캐릭터 프레임
|
||||
_buildColoredFrame(frame),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// RichText 기반 색상 적용 프레임
|
||||
Widget _buildColoredFrame(CharacterFrame frame) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: frame.lines.map((line) => _buildColoredLine(line)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 한 줄을 색상 적용하여 렌더링
|
||||
Widget _buildColoredLine(String line) {
|
||||
final spans = <TextSpan>[];
|
||||
|
||||
for (var i = 0; i < line.length; i++) {
|
||||
final char = line[i];
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: char,
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
height: 1.2,
|
||||
color: _getCharColor(char),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(children: spans),
|
||||
);
|
||||
}
|
||||
|
||||
/// 문자별 색상 매핑
|
||||
Color _getCharColor(String char) {
|
||||
// 공격/이펙트 (시안)
|
||||
if ('><=!+~'.contains(char)) return Colors.cyan;
|
||||
// 데미지/글리치 (마젠타)
|
||||
if ('*@#\$%&'.contains(char)) return const Color(0xFFFF00FF);
|
||||
// 특수 문자 (노랑)
|
||||
if ('☠◈◉'.contains(char)) return Colors.yellow;
|
||||
// 대형 문자 (밝은 녹색)
|
||||
if ('█▓░'.contains(char)) return Colors.lightGreen;
|
||||
// 기본 (흰색)
|
||||
return Colors.white;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user