Compare commits
3 Commits
df876cae6d
...
d71f065745
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d71f065745 | ||
|
|
929b8a7f96 | ||
|
|
38b9955b73 |
BIN
assets/audio/bgm/act_boss.mp3
Normal file
BIN
assets/audio/bgm/act_boss.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/act_cinemetic.mp3
Normal file
BIN
assets/audio/bgm/act_cinemetic.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/battle_act4.mp3
Normal file
BIN
assets/audio/bgm/battle_act4.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/battle_act5.mp3
Normal file
BIN
assets/audio/bgm/battle_act5.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/death.mp3
Normal file
BIN
assets/audio/bgm/death.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/elite.mp3
Normal file
BIN
assets/audio/bgm/elite.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/ending.mp3
Normal file
BIN
assets/audio/bgm/ending.mp3
Normal file
Binary file not shown.
@@ -443,8 +443,15 @@ enum BgmType {
|
||||
title,
|
||||
town,
|
||||
battle,
|
||||
battleAct4, // Act IV 전용 전투 BGM
|
||||
battleAct5, // Act V 전용 전투 BGM
|
||||
boss,
|
||||
actBoss, // Act 보스 전용 BGM
|
||||
elite, // 엘리트 몬스터 전투 BGM
|
||||
victory,
|
||||
death, // 사망 BGM
|
||||
actCinematic, // Act 전환 시네마틱 BGM
|
||||
ending, // 엔딩 BGM
|
||||
}
|
||||
|
||||
/// SFX 타입 열거형
|
||||
@@ -463,7 +470,20 @@ enum SfxType {
|
||||
|
||||
/// BgmType을 파일명으로 변환
|
||||
extension BgmTypeExtension on BgmType {
|
||||
String get fileName => name;
|
||||
String get fileName => switch (this) {
|
||||
BgmType.title => 'title',
|
||||
BgmType.town => 'town',
|
||||
BgmType.battle => 'battle',
|
||||
BgmType.battleAct4 => 'battle_act4',
|
||||
BgmType.battleAct5 => 'battle_act5',
|
||||
BgmType.boss => 'boss',
|
||||
BgmType.actBoss => 'act_boss',
|
||||
BgmType.elite => 'elite',
|
||||
BgmType.victory => 'victory',
|
||||
BgmType.death => 'death',
|
||||
BgmType.actCinematic => 'act_cinemetic', // 파일명 오타 유지
|
||||
BgmType.ending => 'ending',
|
||||
};
|
||||
}
|
||||
|
||||
/// SfxType을 파일명으로 변환
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:asciineverdie/src/core/engine/story_service.dart';
|
||||
import 'package:asciineverdie/src/core/model/combat_event.dart';
|
||||
import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart';
|
||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||
import 'package:asciineverdie/src/core/model/monster_grade.dart';
|
||||
import 'package:asciineverdie/src/core/model/skill.dart';
|
||||
import 'package:asciineverdie/src/core/notification/notification_service.dart';
|
||||
import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic;
|
||||
@@ -99,6 +100,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
// 오디오 상태 추적 (TaskType 기반)
|
||||
bool _wasInBattleTask = false;
|
||||
|
||||
// 사망/엔딩 상태 추적 (BGM 전환용)
|
||||
bool _wasDead = false;
|
||||
bool _wasComplete = false;
|
||||
|
||||
// 사운드 볼륨 상태 (모바일 설정 UI용)
|
||||
double _bgmVolume = 0.7;
|
||||
double _sfxVolume = 0.8;
|
||||
@@ -177,6 +182,38 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
_resetSpecialAnimationAfterFrame();
|
||||
}
|
||||
_lastPlotStageCount = state.progress.plotStageCount;
|
||||
|
||||
// 사망/엔딩 BGM 전환 (Death/Ending BGM Transition)
|
||||
_updateDeathEndingBgm(state);
|
||||
}
|
||||
|
||||
/// 사망/엔딩 BGM 전환 처리
|
||||
void _updateDeathEndingBgm(GameState state) {
|
||||
final audio = widget.audioService;
|
||||
if (audio == null) return;
|
||||
|
||||
final isDead = state.isDead;
|
||||
final isComplete = widget.controller.isComplete;
|
||||
|
||||
// 엔딩 BGM (게임 클리어 시)
|
||||
if (isComplete && !_wasComplete) {
|
||||
audio.playBgm('ending');
|
||||
_wasComplete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 사망 BGM (isDead 상태 진입 시)
|
||||
if (isDead && !_wasDead) {
|
||||
audio.playBgm('death');
|
||||
_wasDead = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 부활 시 사망 상태 리셋 (다음 사망 감지 가능하도록)
|
||||
if (!isDead && _wasDead) {
|
||||
_wasDead = false;
|
||||
// 부활 후 BGM은 _updateBgmForTaskType에서 처리됨
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 8: 전투 로그 추가 (Add Combat Log Entry)
|
||||
@@ -227,16 +264,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
final isInBattleTask = taskType == TaskType.kill;
|
||||
|
||||
if (isInBattleTask) {
|
||||
// 전투 태스크: 보스 여부에 따라 BGM 선택
|
||||
final monsterLevel = state.progress.currentTask.monsterLevel ?? 0;
|
||||
final playerLevel = state.traits.level;
|
||||
final isBoss = monsterLevel >= playerLevel + 5;
|
||||
|
||||
if (isBoss) {
|
||||
audio.playBgm('boss');
|
||||
} else {
|
||||
audio.playBgm('battle');
|
||||
}
|
||||
audio.playBgm(_getBattleBgm(state));
|
||||
} else {
|
||||
// 비전투 태스크: 마을 BGM
|
||||
audio.playBgm('town');
|
||||
@@ -245,6 +273,48 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
_wasInBattleTask = isInBattleTask;
|
||||
}
|
||||
|
||||
/// 전투 BGM 결정 (몬스터 등급 + 레벨 + Act 고려)
|
||||
///
|
||||
/// 우선순위:
|
||||
/// 1. MonsterGrade.boss → 'act_boss'
|
||||
/// 2. 레벨 기반 보스 (monsterLevel >= playerLevel + 5) → 'boss'
|
||||
/// 3. MonsterGrade.elite → 'elite'
|
||||
/// 4. Act별 일반 전투 → 'battle', 'battle_act4', 'battle_act5'
|
||||
String _getBattleBgm(GameState state) {
|
||||
final task = state.progress.currentTask;
|
||||
final monsterGrade = task.monsterGrade;
|
||||
final monsterLevel = task.monsterLevel ?? 0;
|
||||
final playerLevel = state.traits.level;
|
||||
|
||||
// 1. 등급 보스 (3% 확률로 등장하는 특수 보스)
|
||||
if (monsterGrade == MonsterGrade.boss) {
|
||||
return 'act_boss';
|
||||
}
|
||||
|
||||
// 2. 레벨 기반 보스 (강적)
|
||||
if (monsterLevel >= playerLevel + 5) {
|
||||
return 'boss';
|
||||
}
|
||||
|
||||
// 3. 엘리트 몬스터 (12% 확률)
|
||||
if (monsterGrade == MonsterGrade.elite) {
|
||||
return 'elite';
|
||||
}
|
||||
|
||||
// 4. 일반 전투 (Act별 분기)
|
||||
return _getBattleBgmForLevel(playerLevel);
|
||||
}
|
||||
|
||||
/// 레벨에 따른 전투 BGM 파일명 반환 (Act별 분기)
|
||||
String _getBattleBgmForLevel(int playerLevel) {
|
||||
final act = getActForLevel(playerLevel);
|
||||
return switch (act) {
|
||||
StoryAct.act4 => 'battle_act4',
|
||||
StoryAct.act5 => 'battle_act5',
|
||||
_ => 'battle',
|
||||
};
|
||||
}
|
||||
|
||||
/// TaskType 기반 BGM 전환 (애니메이션과 동기화)
|
||||
///
|
||||
/// 애니메이션은 TaskType으로 결정되므로, BGM도 동일한 기준 사용
|
||||
@@ -258,11 +328,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
// 전투 태스크 상태 결정
|
||||
if (isInBattleTask) {
|
||||
// 전투 태스크: 보스 여부에 따라 BGM 선택
|
||||
final monsterLevel = state.progress.currentTask.monsterLevel ?? 0;
|
||||
final playerLevel = state.traits.level;
|
||||
final isBoss = monsterLevel >= playerLevel + 5;
|
||||
final expectedBgm = isBoss ? 'boss' : 'battle';
|
||||
final expectedBgm = _getBattleBgm(state);
|
||||
|
||||
// 전환 시점이거나 현재 BGM이 일치하지 않으면 재생
|
||||
if (!_wasInBattleTask || audio.currentBgm != expectedBgm) {
|
||||
@@ -408,11 +474,14 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
// 게임 일시 정지
|
||||
await widget.controller.pause(saveOnStop: false);
|
||||
|
||||
// 시네마틱 BGM 재생
|
||||
widget.audioService?.playBgm('act_cinemetic');
|
||||
|
||||
if (mounted) {
|
||||
await showActCinematic(context, act);
|
||||
}
|
||||
|
||||
// 게임 재개
|
||||
// 게임 재개 (BGM은 _updateBgmForTaskType에서 복원됨)
|
||||
if (mounted) {
|
||||
await widget.controller.resume();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user