Files
zeling_v2/Assets/Scripts/Quest/ChallengeRoomManager.cs
2026-05-12 15:34:08 +08:00

139 lines
4.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Enemies;
using BaseGames.Player;
namespace BaseGames.Challenge
{
/// <summary>
/// 挑战房间流程管理器(架构 22_QuestChallengeModule §12
/// 挂在挑战房间场景的 [ChallengeManager] GameObject 上,场景加载时自动启动挑战。
/// </summary>
public class ChallengeRoomManager : MonoBehaviour
{
[SerializeField] private ChallengeRoomSO _challengeData;
[SerializeField] private PlayerStats _player; // 由场景 Inspector 绑定
[Header("Event Channels")]
[SerializeField] private StringEventChannelSO _onChallengeCompleted; // EVT_ChallengeCompleted
[SerializeField] private StringEventChannelSO _onChallengeFailed; // EVT_ChallengeFailed
private int _currentEncounterIndex;
private int _remainingEnemies;
private float _elapsedTime;
private bool _isRunning;
private bool _noHitViolated; // 架构 §12requireNoHit 挑战是否被破坏
private void OnEnable()
{
if (_player != null) _player.OnDamaged += OnPlayerDamaged;
}
private void OnDisable()
{
if (_player != null) _player.OnDamaged -= OnPlayerDamaged;
}
private void OnPlayerDamaged() => _noHitViolated = true;
private void Start() => StartChallenge();
private void Update()
{
if (!_isRunning) return;
_elapsedTime += Time.deltaTime;
if (_challengeData.timeLimit > 0 && _elapsedTime >= _challengeData.timeLimit)
FailChallenge();
}
private void StartChallenge()
{
// 自动快速存档(失败后读档返回挑战入口)
ServiceLocator.GetOrDefault<ISaveService>()?.QuickSave();
_isRunning = true;
_currentEncounterIndex = 0;
_elapsedTime = 0f;
_noHitViolated = false;
SpawnWave(0);
}
private void SpawnWave(int index)
{
if (_challengeData.encounters == null || index >= _challengeData.encounters.Length)
{
CompleteChallenge();
return;
}
var enc = _challengeData.encounters[index];
_remainingEnemies = 0;
foreach (var entry in enc.enemies)
{
for (int i = 0; i < entry.count; i++)
{
_remainingEnemies++;
Vector3 pos = entry.spawnPoint != null ? entry.spawnPoint.position : Vector3.zero;
Addressables.InstantiateAsync(entry.enemyAddressKey, pos, Quaternion.identity)
.Completed += handle =>
{
if (handle.Result != null &&
handle.Result.TryGetComponent<EnemyBase>(out var enemy))
{
enemy.OnDied += OnEnemyDefeated;
}
};
}
}
}
private void OnEnemyDefeated()
{
_remainingEnemies = Mathf.Max(0, _remainingEnemies - 1);
if (_remainingEnemies > 0) return;
_currentEncounterIndex++;
if (_currentEncounterIndex >= _challengeData.encounters.Length)
CompleteChallenge();
else
StartCoroutine(DelayedNextWave(_challengeData.encounters[_currentEncounterIndex].waveDelay));
}
private IEnumerator DelayedNextWave(float delay)
{
yield return new WaitForSeconds(delay);
SpawnWave(_currentEncounterIndex);
}
private void CompleteChallenge()
{
_isRunning = false;
// requireNoHit 挑战:受到伤害则判定失败(架构 §12
if (_challengeData.requireNoHit && _noHitViolated)
{
FailChallenge();
return;
}
var reward = ServiceLocator.GetOrDefault<ISaveService>() is { } sm && sm.IsFirstClear(_challengeData.challengeId)
? _challengeData.firstClearReward
: _challengeData.repeatedReward;
reward?.Apply(_player);
_onChallengeCompleted?.Raise(_challengeData.challengeId);
}
private void FailChallenge()
{
_isRunning = false;
_onChallengeFailed?.Raise(_challengeData.challengeId);
ServiceLocator.GetOrDefault<ISaveService>()?.QuickLoad();
}
}
}