清理各个 Component 里的判断以及调用规范

This commit is contained in:
2026-09-14 18:35:02 +08:00
parent 5fbeec2476
commit 17ee268416
30 changed files with 769 additions and 1326 deletions
Binary file not shown.
@@ -1,12 +1,15 @@
[
{
"weaponCapacity": 6,
"propMaxCount": -1,
"weaponRecycleRate": 300,
"shopGoodsCount": 4,
"shopPriceRandomPercent": 50,
"shopMinPrice": 0,
"propMaxCount": -1,
"levelUpRefreshPriceBase": 2,
"levelUpRewardCount": 4
"levelUpRewardCount": 4,
"enemyGroupName": "Enemy",
"spawnEnemyMaxCount": 5000,
"spawnDistanceFromPlayer": 20000
}
]
@@ -1,580 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SepCore.Event;
using SepCore.Definition;
using SepCore.Entity;
using SepCore.Components;
using SepCore.CustomUtility;
using SepCore.Simulation;
using SepCore.EnemyManager;
using SepCore.Procedure;
using UnityEngine;
using UnityGameFramework.Runtime;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
public class RuntimeDebugPanelComponent : MonoBehaviour
{
private const float MinSpawnRate = 0.1f;
private const float CornerTapWindow = 0.6f;
private const int RequiredCornerTapCount = 3;
private const int DebugHealAmount = 200;
[Header("Window Content")] [SerializeField]
private bool _showBuffSection = true;
[SerializeField] private bool _showBattleOverview = true;
[SerializeField] private bool _showCollisionStats = true;
[SerializeField] private bool _showSpawnControls = true;
[SerializeField] private bool _showBattleDurationControls = true;
[SerializeField] private bool _showPlayerWeaponControls = true;
[SerializeField] private bool _showPlayerHealthControls = true;
[SerializeField] private bool _showTips = true;
private Rect _windowRect;
private Vector2 _panelScroll;
private GUISkin _panelSkin;
private bool _isPanelVisible;
private int _windowId;
private string _searchText = string.Empty;
private int _selectedIndex;
private int _addCount = 1;
private Vector2 _buffScroll;
private float _spawnRateScaleInput = 1f;
private float _extendDurationSeconds = 30f;
private IReadOnlyList<PropConfig> _allProps;
private IReadOnlyList<PropConfig> _filteredProps;
private string[] _displayNames = Array.Empty<string>();
private int _cornerTapCount;
private float _lastCornerTapTime = -10f;
private bool _lockPlayerHealthToMax;
private void Awake()
{
_windowId = GetInstanceID();
}
private void Update()
{
if (IsTogglePressed())
{
_isPanelVisible = !_isPanelVisible;
}
HandleCornerTapGesture();
if (_lockPlayerHealthToMax)
{
KeepPlayerHealthAtMax();
}
}
private void OnGUI()
{
GUISkin previousSkin = GUI.skin;
Matrix4x4 previousMatrix = GUI.matrix;
if (_panelSkin == null)
{
_panelSkin = Instantiate(previousSkin);
_panelSkin.label.fontSize = 18;
_panelSkin.label.wordWrap = true;
_panelSkin.button.fontSize = 18;
_panelSkin.button.padding = new RectOffset(12, 12, 10, 10);
_panelSkin.button.fixedHeight = 44f;
_panelSkin.textField.fontSize = 20;
_panelSkin.textField.fixedHeight = 44f;
_panelSkin.textField.padding = new RectOffset(10, 10, 10, 10);
_panelSkin.window.padding = new RectOffset(16, 16, 12, 16);
_panelSkin.verticalScrollbar.fixedWidth = 28f;
_panelSkin.verticalScrollbarThumb.fixedWidth = 28f;
}
Rect safeArea = Screen.safeArea;
float scale = Mathf.Max(0.1f, Mathf.Min(safeArea.width, safeArea.height) / 480f);
Rect bounds = new Rect(0f, 0f, safeArea.width / scale, safeArea.height / scale);
try
{
GUI.skin = _panelSkin;
GUI.matrix = Matrix4x4.TRS(
new Vector3(safeArea.x, Screen.height - safeArea.yMax, 0f),
Quaternion.identity, new Vector3(scale, scale, 1f));
DrawToggleButton(bounds);
if (!_isPanelVisible) return;
float width = Mathf.Min(460f, bounds.width - 24f);
_windowRect = new Rect(bounds.width - width - 12f, 72f, width,
Mathf.Min(760f, bounds.height - 84f));
GUI.Window(_windowId, _windowRect, DrawWindow, string.Empty);
}
finally
{
GUI.matrix = previousMatrix;
GUI.skin = previousSkin;
}
}
private void OnDestroy()
{
if (_panelSkin != null) Destroy(_panelSkin);
}
private void DrawToggleButton(Rect bounds)
{
const float width = 120f;
const float height = 48f;
Rect buttonRect = new Rect(bounds.width - width - 12f, 12f, width, height);
if (GUI.Button(buttonRect, "DEBUG"))
{
_isPanelVisible = !_isPanelVisible;
}
}
private void DrawWindow(int windowId)
{
if (_showBuffSection)
{
EnsurePropList();
}
GUILayout.BeginVertical();
GUILayout.BeginHorizontal();
GUILayout.Label("Runtime Debug", GUILayout.ExpandWidth(true));
if (GUILayout.Button("Close", GUILayout.Width(90f))) _isPanelVisible = false;
GUILayout.EndHorizontal();
GUILayout.Space(8f);
_panelScroll = GUILayout.BeginScrollView(_panelScroll, false, true);
bool hasPreviousSection = false;
if (_showBuffSection)
{
DrawBuffSection();
hasPreviousSection = true;
}
if (HasVisibleBattleSection())
{
if (hasPreviousSection)
{
GUILayout.Space(8f);
GUILayout.Label(string.Empty, GUI.skin.horizontalSlider);
GUILayout.Space(8f);
}
DrawBattleSection();
hasPreviousSection = true;
}
if (_showTips)
{
if (hasPreviousSection)
{
GUILayout.Space(8f);
}
GUILayout.Label("Tips: press F8 or tap top-left corner 3 times to toggle.");
}
GUILayout.EndScrollView();
GUILayout.EndVertical();
}
private void DrawBuffSection()
{
GUILayout.Label("Buff Debug");
GUILayout.BeginHorizontal();
GUILayout.Label("Search", GUILayout.Width(78f));
_searchText = GUILayout.TextField(_searchText);
if (GUILayout.Button("Refresh", GUILayout.Width(100f)))
{
EnsurePropList(true);
}
GUILayout.EndHorizontal();
ApplyFilter(_searchText);
if (_displayNames.Length == 0)
{
GUILayout.Label("No Buff data. Enter battle and try again.");
return;
}
_selectedIndex = Mathf.Clamp(_selectedIndex, 0, _displayNames.Length - 1);
_buffScroll = GUILayout.BeginScrollView(_buffScroll, GUILayout.Height(120f));
_selectedIndex = GUILayout.SelectionGrid(_selectedIndex, _displayNames, 1);
GUILayout.EndScrollView();
PropConfig selectedProp = GetSelectedProp();
if (selectedProp == null) return;
GUILayout.Label($"Selected: {selectedProp.Name} ({selectedProp.Rarity})");
GUILayout.Label(ItemDescUtility.CreatePropDescription(selectedProp.Modifiers), GUILayout.Height(40f));
GUILayout.BeginHorizontal();
GUILayout.Label("Count", GUILayout.Width(78f));
string addCountText = GUILayout.TextField(_addCount.ToString(), GUILayout.Width(88f));
if (!int.TryParse(addCountText, out _addCount)) _addCount = 1;
_addCount = Mathf.Clamp(_addCount, 1, 99);
if (GUILayout.Button("Add Buff To Player", GUILayout.Height(44f)))
{
AddSelectedBuffToPlayer(selectedProp, _addCount);
}
GUILayout.EndHorizontal();
}
private void DrawBattleSection()
{
GUILayout.Label("Battle Debug");
ProcedureGame procedure = GameEntry.Procedure.CurrentProcedure as ProcedureGame;
EnemyManagerComponent enemyManager = GameEntry.EnemyManager;
Player player = FindPlayer();
HealthComponent playerHealth = player != null ? player.GetComponent<HealthComponent>() : null;
if (enemyManager == null)
{
GUILayout.Label("EnemyManager unavailable.");
return;
}
if (procedure == null)
{
GUILayout.Label("ProcedureGame unavailable.");
return;
}
if (_showBattleOverview)
{
GUILayout.Label($"Spawn Rate: {enemyManager.SpawnRateScale:F2}");
GUILayout.Label(
$"Battle Time: {enemyManager.ElapsedBattleTime:F1}s / {enemyManager.BattleDuration:F1}s");
GUILayout.Label($"Enemy Count: {enemyManager.CurrentEnemyCount}");
}
SimulationWorld simulationWorld = GameEntry.SimulationWorld;
if (_showCollisionStats && simulationWorld != null)
{
GUILayout.Space(4f);
GUILayout.Label(
$"Collision Queries: total {simulationWorld.LastCollisionQueryCount} (Projectile {simulationWorld.LastProjectileCollisionQueryCount} / Area {simulationWorld.LastAreaCollisionQueryCount})");
GUILayout.Label(
$"Collision Candidates: total {simulationWorld.LastCollisionCandidateCount} (Projectile {simulationWorld.LastProjectileCollisionCandidateCount} / Area {simulationWorld.LastAreaCollisionCandidateCount})");
GUILayout.Label(
$"Area Resolve: hits {simulationWorld.LastResolvedAreaHitCount}");
GUILayout.Label(
$"Broad Phase: cell {simulationWorld.LastCollisionCellSize:F2}, hasEnemyTargets {(simulationWorld.LastCollisionHasEnemyTargets ? "Yes" : "No")}");
if (simulationWorld.LastCollisionCandidateCount != 0)
{
Log.Info($"LastCollisionCandidateCount:{simulationWorld.LastCollisionCandidateCount}");
}
if (simulationWorld.LastResolvedAreaHitCount != 0)
{
Log.Info($"LastResolvedAreaHitCount:{simulationWorld.LastResolvedAreaHitCount}");
}
}
if (_showSpawnControls)
{
GUILayout.Space(12f);
GUILayout.Label("Enemy Spawn Rate");
GUILayout.BeginHorizontal();
GUILayout.Label("Rate", GUILayout.Width(78f));
string rateText = GUILayout.TextField(_spawnRateScaleInput.ToString("F2"), GUILayout.Width(88f));
if (float.TryParse(rateText, out float parsedRate))
{
_spawnRateScaleInput = Mathf.Clamp(parsedRate, MinSpawnRate, 50f);
}
if (GUILayout.Button("Apply", GUILayout.Width(100f)))
{
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("x0.5"))
{
_spawnRateScaleInput = Mathf.Max(MinSpawnRate, enemyManager.SpawnRateScale * 0.5f);
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
}
if (GUILayout.Button("x2"))
{
_spawnRateScaleInput = enemyManager.SpawnRateScale * 2f;
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
}
GUILayout.EndHorizontal();
}
if (_showBattleDurationControls)
{
GUILayout.Space(12f);
GUILayout.Label("Battle Duration");
GUILayout.BeginHorizontal();
GUILayout.Label("Add Sec", GUILayout.Width(78f));
string durationText = GUILayout.TextField(_extendDurationSeconds.ToString("F0"), GUILayout.Width(88f));
if (float.TryParse(durationText, out float parsedDuration))
{
_extendDurationSeconds = Mathf.Clamp(parsedDuration, 1f, 3600f);
}
if (GUILayout.Button("Extend Battle", GUILayout.Height(44f)))
{
if (procedure.CurrentGameState is GameStateBattle gameState)
{
gameState.AddBattleDuration(_extendDurationSeconds);
}
}
GUILayout.EndHorizontal();
}
if (_showPlayerWeaponControls)
{
GUILayout.Space(12f);
GUILayout.Label(
$"Player Weapon: {(player == null ? "Player not found" : (player.WeaponEnabled ? "Enabled" : "Disabled"))}");
GUILayout.BeginHorizontal();
GUI.enabled = player != null;
if (GUILayout.Button("Disable Weapons", GUILayout.Height(44f)))
{
player.SetWeaponEnabled(false);
}
if (GUILayout.Button("Enable Weapons", GUILayout.Height(44f)))
{
player.SetWeaponEnabled(true);
}
GUI.enabled = true;
GUILayout.EndHorizontal();
}
if (_showPlayerHealthControls)
{
GUILayout.Space(4f);
GUILayout.Label(
$"Player HP: {(playerHealth == null ? "Unavailable" : $"{playerHealth.CurrentHealth}/{playerHealth.MaxHealth}")}");
GUILayout.BeginHorizontal();
GUI.enabled = playerHealth != null;
if (GUILayout.Button($"+{DebugHealAmount} HP", GUILayout.Height(44f)))
{
AddPlayerHealth(playerHealth, DebugHealAmount);
}
if (GUILayout.Button(_lockPlayerHealthToMax ? "GodMode: ON" : "GodMode: OFF", GUILayout.Height(44f)))
{
_lockPlayerHealthToMax = !_lockPlayerHealthToMax;
if (_lockPlayerHealthToMax)
{
RestorePlayerHealthToMax(playerHealth);
}
}
GUI.enabled = true;
GUILayout.EndHorizontal();
}
}
private bool HasVisibleBattleSection()
{
return _showBattleOverview ||
_showCollisionStats ||
_showSpawnControls ||
_showBattleDurationControls ||
_showPlayerWeaponControls ||
_showPlayerHealthControls;
}
private void EnsurePropList(bool force = false)
{
if (!force && _allProps != null && _allProps.Count > 0) return;
if (GameEntry.Luban == null)
{
_allProps = Array.Empty<PropConfig>();
_displayNames = Array.Empty<string>();
return;
}
_allProps = GameEntry.Luban.GetTable<PropConfig>();
_selectedIndex = Mathf.Clamp(_selectedIndex, 0, Mathf.Max(0, _allProps.Count - 1));
ApplyFilter(_searchText);
}
private void ApplyFilter(string keyword)
{
if (_allProps == null || _allProps.Count == 0)
{
_displayNames = Array.Empty<string>();
return;
}
if (string.IsNullOrWhiteSpace(keyword))
{
_filteredProps = _allProps;
}
else
{
string search = keyword.Trim();
_filteredProps = _allProps.Where(p =>
p != null &&
(p.Name?.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 ||
p.Id.ToString().Contains(search))).ToArray();
}
_displayNames = _filteredProps.Select(p => $"[{p.Id}] {p.Name} ({p.Rarity})").ToArray();
if (_displayNames.Length == 0) _selectedIndex = 0;
else _selectedIndex = Mathf.Clamp(_selectedIndex, 0, _displayNames.Length - 1);
}
private PropConfig GetSelectedProp()
{
if (_filteredProps == null || _filteredProps.Count == 0) return null;
_selectedIndex = Mathf.Clamp(_selectedIndex, 0, _filteredProps.Count - 1);
return _filteredProps[_selectedIndex];
}
private static Player FindPlayer()
{
return UnityEngine.Object.FindObjectOfType<Player>();
}
private void KeepPlayerHealthAtMax()
{
Player player = FindPlayer();
if (player == null) return;
HealthComponent playerHealth = player.GetComponent<HealthComponent>();
if (playerHealth == null) return;
RestorePlayerHealthToMax(playerHealth);
}
private static void AddPlayerHealth(HealthComponent playerHealth, int amount)
{
if (playerHealth == null || amount <= 0) return;
if (playerHealth.CurrentHealth <= 0) return;
int maxHealth = playerHealth.MaxHealth;
if (maxHealth <= 0) return;
int nextHealth = Mathf.Clamp(playerHealth.CurrentHealth + amount, 0, maxHealth);
if (nextHealth == playerHealth.CurrentHealth) return;
playerHealth.CurrentHealth = nextHealth;
PublishPlayerHealthChanged(playerHealth);
}
private static void RestorePlayerHealthToMax(HealthComponent playerHealth)
{
if (playerHealth == null) return;
if (playerHealth.CurrentHealth <= 0) return;
int maxHealth = playerHealth.MaxHealth;
if (maxHealth <= 0 || playerHealth.CurrentHealth >= maxHealth) return;
playerHealth.CurrentHealth = maxHealth;
PublishPlayerHealthChanged(playerHealth);
}
private static void PublishPlayerHealthChanged(HealthComponent playerHealth)
{
if (playerHealth == null || GameEntry.Event == null) return;
GameEntry.Event.Fire(null,
PlayerHealthChangeEventArgs.Create(0, playerHealth.CurrentHealth, playerHealth.MaxHealth));
}
private static void AddSelectedBuffToPlayer(PropConfig prop, int count)
{
Player player = FindPlayer();
if (player == null || prop == null || prop.Modifiers == null) return;
int applyCount = Mathf.Clamp(count, 1, 99);
for (int i = 0; i < applyCount; i++)
{
player.AddProp(new PropItem(prop));
}
}
private void HandleCornerTapGesture()
{
if (!TryGetTouchReleased(out Vector2 touchPosition)) return;
if (touchPosition.x > 80f || touchPosition.y < Screen.height - 80f) return;
float now = Time.unscaledTime;
if (now - _lastCornerTapTime > CornerTapWindow)
{
_cornerTapCount = 0;
}
_lastCornerTapTime = now;
_cornerTapCount++;
if (_cornerTapCount >= RequiredCornerTapCount)
{
_cornerTapCount = 0;
_isPanelVisible = !_isPanelVisible;
}
}
private static bool IsTogglePressed()
{
#if ENABLE_INPUT_SYSTEM
Keyboard keyboard = Keyboard.current;
if (keyboard == null) return false;
return keyboard.backquoteKey.wasPressedThisFrame || keyboard.f8Key.wasPressedThisFrame;
#else
return Input.GetKeyDown(KeyCode.BackQuote) || Input.GetKeyDown(KeyCode.F8);
#endif
}
private static bool TryGetTouchReleased(out Vector2 touchPosition)
{
#if ENABLE_INPUT_SYSTEM
Touchscreen touchscreen = Touchscreen.current;
if (touchscreen == null)
{
touchPosition = default;
return false;
}
var touch = touchscreen.primaryTouch;
if (!touch.press.wasReleasedThisFrame)
{
touchPosition = default;
return false;
}
touchPosition = touch.position.ReadValue();
return true;
#else
if (Input.touchCount <= 0)
{
touchPosition = default;
return false;
}
Touch touch = Input.GetTouch(0);
if (touch.phase != TouchPhase.Ended)
{
touchPosition = default;
return false;
}
touchPosition = touch.position;
return true;
#endif
}
}
@@ -17,13 +17,16 @@ public sealed partial class GlobalConfig : Luban.BeanBase
public GlobalConfig(ByteBuf _buf)
{
WeaponCapacity = _buf.ReadInt();
PropMaxCount = _buf.ReadInt();
WeaponRecycleRate = _buf.ReadInt();
ShopGoodsCount = _buf.ReadInt();
ShopPriceRandomPercent = _buf.ReadInt();
ShopMinPrice = _buf.ReadInt();
PropMaxCount = _buf.ReadInt();
LevelUpRefreshPriceBase = _buf.ReadInt();
LevelUpRewardCount = _buf.ReadInt();
EnemyGroupName = _buf.ReadString();
SpawnEnemyMaxCount = _buf.ReadInt();
SpawnDistanceFromPlayer = _buf.ReadInt();
}
public static GlobalConfig DeserializeGlobalConfig(ByteBuf _buf)
@@ -36,6 +39,10 @@ public sealed partial class GlobalConfig : Luban.BeanBase
/// </summary>
public readonly int WeaponCapacity;
/// <summary>
/// 最大持有道具数量
/// </summary>
public readonly int PropMaxCount;
/// <summary>
/// 武器回收价比例
/// </summary>
public readonly int WeaponRecycleRate;
@@ -52,10 +59,6 @@ public sealed partial class GlobalConfig : Luban.BeanBase
/// </summary>
public readonly int ShopMinPrice;
/// <summary>
/// 最大持有道具数量
/// </summary>
public readonly int PropMaxCount;
/// <summary>
/// 升级奖励刷新基本价格
/// </summary>
public readonly int LevelUpRefreshPriceBase;
@@ -63,6 +66,18 @@ public sealed partial class GlobalConfig : Luban.BeanBase
/// 升级奖励候选数
/// </summary>
public readonly int LevelUpRewardCount;
/// <summary>
/// 敌人对象池名
/// </summary>
public readonly string EnemyGroupName;
/// <summary>
/// 最大敌人数量
/// </summary>
public readonly int SpawnEnemyMaxCount;
/// <summary>
/// 敌人生成距离
/// </summary>
public readonly int SpawnDistanceFromPlayer;
public const int __ID__ = -958250779;
public override int GetTypeId() => __ID__;
@@ -77,19 +92,25 @@ public sealed partial class GlobalConfig : Luban.BeanBase
}
public override string ToString()
{
return "{ "
+ "weaponCapacity:" + WeaponCapacity + ","
+ "propMaxCount:" + PropMaxCount + ","
+ "weaponRecycleRate:" + WeaponRecycleRate + ","
+ "shopGoodsCount:" + ShopGoodsCount + ","
+ "shopPriceRandomPercent:" + ShopPriceRandomPercent + ","
+ "shopMinPrice:" + ShopMinPrice + ","
+ "propMaxCount:" + PropMaxCount + ","
+ "levelUpRefreshPriceBase:" + LevelUpRefreshPriceBase + ","
+ "levelUpRewardCount:" + LevelUpRewardCount + ","
+ "enemyGroupName:" + EnemyGroupName + ","
+ "spawnEnemyMaxCount:" + SpawnEnemyMaxCount + ","
+ "spawnDistanceFromPlayer:" + SpawnDistanceFromPlayer + ","
+ "}";
}
}
@@ -35,6 +35,10 @@ public partial class TbGlobalConfig
/// </summary>
public int WeaponCapacity => _data.WeaponCapacity;
/// <summary>
/// 最大持有道具数量
/// </summary>
public int PropMaxCount => _data.PropMaxCount;
/// <summary>
/// 武器回收价比例
/// </summary>
public int WeaponRecycleRate => _data.WeaponRecycleRate;
@@ -51,10 +55,6 @@ public partial class TbGlobalConfig
/// </summary>
public int ShopMinPrice => _data.ShopMinPrice;
/// <summary>
/// 最大持有道具数量
/// </summary>
public int PropMaxCount => _data.PropMaxCount;
/// <summary>
/// 升级奖励刷新基本价格
/// </summary>
public int LevelUpRefreshPriceBase => _data.LevelUpRefreshPriceBase;
@@ -62,6 +62,18 @@ public partial class TbGlobalConfig
/// 升级奖励候选数
/// </summary>
public int LevelUpRewardCount => _data.LevelUpRewardCount;
/// <summary>
/// 敌人对象池名
/// </summary>
public string EnemyGroupName => _data.EnemyGroupName;
/// <summary>
/// 最大敌人数量
/// </summary>
public int SpawnEnemyMaxCount => _data.SpawnEnemyMaxCount;
/// <summary>
/// 敌人生成距离
/// </summary>
public int SpawnDistanceFromPlayer => _data.SpawnDistanceFromPlayer;
public void ResolveRef(Tables tables)
{
@@ -0,0 +1,561 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SepCore.Event;
using SepCore.Definition;
using SepCore.Entity;
using SepCore.Components;
using SepCore.CustomUtility;
using SepCore.Simulation;
using SepCore.EnemyManager;
using SepCore.Procedure;
using UnityEngine;
using UnityGameFramework.Runtime;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
namespace SepCore.DebugPanel
{
public class RuntimeDebugPanelComponent : MonoBehaviour
{
private const float MinSpawnRate = 0.1f;
private const float CornerTapWindow = 0.6f;
private const int RequiredCornerTapCount = 3;
private const int DebugHealAmount = 200;
[Header("Window Content")]
[SerializeField] private bool _showBuffSection = true;
[SerializeField] private bool _showBattleOverview = true;
[SerializeField] private bool _showCollisionStats = true;
[SerializeField] private bool _showSpawnControls = true;
[SerializeField] private bool _showBattleDurationControls = true;
[SerializeField] private bool _showPlayerWeaponControls = true;
[SerializeField] private bool _showPlayerHealthControls = true;
[SerializeField] private bool _showTips = true;
private Rect _windowRect;
private Vector2 _panelScroll;
private GUISkin _panelSkin;
private bool _isPanelVisible;
private int _windowId;
private string _searchText = string.Empty;
private int _selectedIndex;
private int _addCount = 1;
private Vector2 _buffScroll;
private float _spawnRateScaleInput = 1f;
private float _extendDurationSeconds = 30f;
private IReadOnlyList<PropConfig> _allProps;
private IReadOnlyList<PropConfig> _filteredProps;
private string[] _displayNames = Array.Empty<string>();
private int _cornerTapCount;
private float _lastCornerTapTime = -10f;
private bool _lockPlayerHealthToMax;
private void Awake()
{
_windowId = GetInstanceID();
}
private void Update()
{
if (IsTogglePressed())
{
_isPanelVisible = !_isPanelVisible;
}
HandleCornerTapGesture();
if (_lockPlayerHealthToMax)
{
KeepPlayerHealthAtMax();
}
}
private void OnGUI()
{
GUISkin previousSkin = GUI.skin;
Matrix4x4 previousMatrix = GUI.matrix;
if (_panelSkin == null)
{
_panelSkin = Instantiate(previousSkin);
_panelSkin.label.fontSize = 18;
_panelSkin.label.wordWrap = true;
_panelSkin.button.fontSize = 18;
_panelSkin.button.padding = new RectOffset(12, 12, 10, 10);
_panelSkin.button.fixedHeight = 44f;
_panelSkin.textField.fontSize = 20;
_panelSkin.textField.fixedHeight = 44f;
_panelSkin.textField.padding = new RectOffset(10, 10, 10, 10);
_panelSkin.window.padding = new RectOffset(16, 16, 12, 16);
_panelSkin.verticalScrollbar.fixedWidth = 28f;
_panelSkin.verticalScrollbarThumb.fixedWidth = 28f;
}
Rect safeArea = Screen.safeArea;
float scale = Mathf.Max(0.1f, Mathf.Min(safeArea.width, safeArea.height) / 480f);
Rect bounds = new Rect(0f, 0f, safeArea.width / scale, safeArea.height / scale);
try
{
GUI.skin = _panelSkin;
GUI.matrix = Matrix4x4.TRS(
new Vector3(safeArea.x, Screen.height - safeArea.yMax, 0f),
Quaternion.identity, new Vector3(scale, scale, 1f));
DrawToggleButton(bounds);
if (!_isPanelVisible) return;
float width = Mathf.Min(460f, bounds.width - 24f);
_windowRect = new Rect(bounds.width - width - 12f, 72f, width,
Mathf.Min(760f, bounds.height - 84f));
GUI.Window(_windowId, _windowRect, DrawWindow, string.Empty);
}
finally
{
GUI.matrix = previousMatrix;
GUI.skin = previousSkin;
}
}
private void OnDestroy()
{
if (_panelSkin != null) Destroy(_panelSkin);
}
private void DrawToggleButton(Rect bounds)
{
const float width = 120f;
const float height = 48f;
Rect buttonRect = new Rect(bounds.width - width - 12f, 12f, width, height);
if (GUI.Button(buttonRect, "DEBUG"))
{
_isPanelVisible = !_isPanelVisible;
}
}
private void DrawWindow(int windowId)
{
if (_showBuffSection)
{
EnsurePropList();
}
GUILayout.BeginVertical();
GUILayout.BeginHorizontal();
GUILayout.Label("Runtime Debug", GUILayout.ExpandWidth(true));
if (GUILayout.Button("Close", GUILayout.Width(90f))) _isPanelVisible = false;
GUILayout.EndHorizontal();
GUILayout.Space(8f);
_panelScroll = GUILayout.BeginScrollView(_panelScroll, false, true);
bool hasPreviousSection = false;
if (_showBuffSection)
{
DrawBuffSection();
hasPreviousSection = true;
}
if (HasVisibleBattleSection())
{
if (hasPreviousSection)
{
GUILayout.Space(8f);
GUILayout.Label(string.Empty, GUI.skin.horizontalSlider);
GUILayout.Space(8f);
}
DrawBattleSection();
hasPreviousSection = true;
}
if (_showTips)
{
if (hasPreviousSection)
{
GUILayout.Space(8f);
}
GUILayout.Label("Tips: press F8 or tap top-left corner 3 times to toggle.");
}
GUILayout.EndScrollView();
GUILayout.EndVertical();
}
private void DrawBuffSection()
{
GUILayout.Label("Buff Debug");
GUILayout.BeginHorizontal();
GUILayout.Label("Search", GUILayout.Width(78f));
_searchText = GUILayout.TextField(_searchText);
if (GUILayout.Button("Refresh", GUILayout.Width(100f)))
{
EnsurePropList(true);
}
GUILayout.EndHorizontal();
ApplyFilter(_searchText);
if (_displayNames.Length == 0)
{
GUILayout.Label("No Buff data. Enter battle and try again.");
return;
}
_selectedIndex = Mathf.Clamp(_selectedIndex, 0, _displayNames.Length - 1);
_buffScroll = GUILayout.BeginScrollView(_buffScroll, GUILayout.Height(120f));
_selectedIndex = GUILayout.SelectionGrid(_selectedIndex, _displayNames, 1);
GUILayout.EndScrollView();
PropConfig selectedProp = GetSelectedProp();
if (selectedProp == null) return;
GUILayout.Label($"Selected: {selectedProp.Name} ({selectedProp.Rarity})");
GUILayout.Label(ItemDescUtility.CreatePropDescription(selectedProp.Modifiers), GUILayout.Height(40f));
GUILayout.BeginHorizontal();
GUILayout.Label("Count", GUILayout.Width(78f));
string addCountText = GUILayout.TextField(_addCount.ToString(), GUILayout.Width(88f));
if (!int.TryParse(addCountText, out _addCount)) _addCount = 1;
_addCount = Mathf.Clamp(_addCount, 1, 99);
if (GUILayout.Button("Add Buff To Player", GUILayout.Height(44f)))
{
AddSelectedBuffToPlayer(selectedProp, _addCount);
}
GUILayout.EndHorizontal();
}
private void DrawBattleSection()
{
GUILayout.Label("Battle Debug");
ProcedureGame procedure = GameEntry.Procedure.CurrentProcedure as ProcedureGame;
Player player = FindPlayer();
HealthComponent playerHealth = player != null ? player.GetComponent<HealthComponent>() : null;
if (procedure == null)
{
GUILayout.Label("ProcedureGame unavailable.");
return;
}
EnemyManagerComponent enemyManager = GameEntry.EnemyManager;
if (_showBattleOverview)
{
GUILayout.Label($"Spawn Rate: {enemyManager.SpawnRateScale:F2}");
GUILayout.Label(
$"Battle Time: {enemyManager.ElapsedBattleTime:F1}s / {enemyManager.BattleDuration:F1}s");
GUILayout.Label($"Enemy Count: {enemyManager.CurrentEnemyCount}");
}
if (_showCollisionStats)
{
SimulationWorld simulationWorld = GameEntry.SimulationWorld;
GUILayout.Space(4f);
GUILayout.Label(
$"Collision Queries: total {simulationWorld.LastCollisionQueryCount} (Projectile {simulationWorld.LastProjectileCollisionQueryCount} / Area {simulationWorld.LastAreaCollisionQueryCount})");
GUILayout.Label(
$"Collision Candidates: total {simulationWorld.LastCollisionCandidateCount} (Projectile {simulationWorld.LastProjectileCollisionCandidateCount} / Area {simulationWorld.LastAreaCollisionCandidateCount})");
GUILayout.Label(
$"Area Resolve: hits {simulationWorld.LastResolvedAreaHitCount}");
GUILayout.Label(
$"Broad Phase: cell {simulationWorld.LastCollisionCellSize:F2}, hasEnemyTargets {(simulationWorld.LastCollisionHasEnemyTargets ? "Yes" : "No")}");
}
if (_showSpawnControls)
{
GUILayout.Space(12f);
GUILayout.Label("Enemy Spawn Rate");
GUILayout.BeginHorizontal();
GUILayout.Label("Rate", GUILayout.Width(78f));
string rateText = GUILayout.TextField(_spawnRateScaleInput.ToString("F2"), GUILayout.Width(88f));
if (float.TryParse(rateText, out float parsedRate))
{
_spawnRateScaleInput = Mathf.Clamp(parsedRate, MinSpawnRate, 50f);
}
if (GUILayout.Button("Apply", GUILayout.Width(100f)))
{
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("x0.5"))
{
_spawnRateScaleInput = Mathf.Max(MinSpawnRate, enemyManager.SpawnRateScale * 0.5f);
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
}
if (GUILayout.Button("x2"))
{
_spawnRateScaleInput = enemyManager.SpawnRateScale * 2f;
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
}
GUILayout.EndHorizontal();
}
if (_showBattleDurationControls)
{
GUILayout.Space(12f);
GUILayout.Label("Battle Duration");
GUILayout.BeginHorizontal();
GUILayout.Label("Add Sec", GUILayout.Width(78f));
string durationText = GUILayout.TextField(_extendDurationSeconds.ToString("F0"), GUILayout.Width(88f));
if (float.TryParse(durationText, out float parsedDuration))
{
_extendDurationSeconds = Mathf.Clamp(parsedDuration, 1f, 3600f);
}
if (GUILayout.Button("Extend Battle", GUILayout.Height(44f)))
{
if (procedure.CurrentGameState is GameStateBattle gameState)
{
gameState.AddBattleDuration(_extendDurationSeconds);
}
}
GUILayout.EndHorizontal();
}
if (_showPlayerWeaponControls)
{
GUILayout.Space(12f);
GUILayout.Label(
$"Player Weapon: {(player == null ? "Player not found" : (player.WeaponEnabled ? "Enabled" : "Disabled"))}");
GUILayout.BeginHorizontal();
GUI.enabled = player != null;
if (GUILayout.Button("Disable Weapons", GUILayout.Height(44f)))
{
player.SetWeaponEnabled(false);
}
if (GUILayout.Button("Enable Weapons", GUILayout.Height(44f)))
{
player.SetWeaponEnabled(true);
}
GUI.enabled = true;
GUILayout.EndHorizontal();
}
if (_showPlayerHealthControls)
{
GUILayout.Space(4f);
GUILayout.Label(
$"Player HP: {(playerHealth == null ? "Unavailable" : $"{playerHealth.CurrentHealth}/{playerHealth.MaxHealth}")}");
GUILayout.BeginHorizontal();
GUI.enabled = playerHealth != null;
if (GUILayout.Button($"+{DebugHealAmount} HP", GUILayout.Height(44f)))
{
AddPlayerHealth(playerHealth, DebugHealAmount);
}
if (GUILayout.Button(_lockPlayerHealthToMax ? "GodMode: ON" : "GodMode: OFF", GUILayout.Height(44f)))
{
_lockPlayerHealthToMax = !_lockPlayerHealthToMax;
if (_lockPlayerHealthToMax)
{
RestorePlayerHealthToMax(playerHealth);
}
}
GUI.enabled = true;
GUILayout.EndHorizontal();
}
}
private bool HasVisibleBattleSection()
{
return _showBattleOverview ||
_showCollisionStats ||
_showSpawnControls ||
_showBattleDurationControls ||
_showPlayerWeaponControls ||
_showPlayerHealthControls;
}
private void EnsurePropList(bool force = false)
{
if (!force && _allProps != null && _allProps.Count > 0) return;
if (!GameEntry.Luban.IsReady)
{
_allProps = Array.Empty<PropConfig>();
_displayNames = Array.Empty<string>();
return;
}
_allProps = GameEntry.Luban.GetTable<PropConfig>();
_selectedIndex = Mathf.Clamp(_selectedIndex, 0, Mathf.Max(0, _allProps.Count - 1));
ApplyFilter(_searchText);
}
private void ApplyFilter(string keyword)
{
if (_allProps == null || _allProps.Count == 0)
{
_displayNames = Array.Empty<string>();
return;
}
if (string.IsNullOrWhiteSpace(keyword))
{
_filteredProps = _allProps;
}
else
{
string search = keyword.Trim();
_filteredProps = _allProps.Where(p =>
p.Name.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 ||
p.Id.ToString().Contains(search)).ToArray();
}
_displayNames = _filteredProps.Select(p => $"[{p.Id}] {p.Name} ({p.Rarity})").ToArray();
if (_displayNames.Length == 0) _selectedIndex = 0;
else _selectedIndex = Mathf.Clamp(_selectedIndex, 0, _displayNames.Length - 1);
}
private PropConfig GetSelectedProp()
{
if (_filteredProps == null || _filteredProps.Count == 0) return null;
_selectedIndex = Mathf.Clamp(_selectedIndex, 0, _filteredProps.Count - 1);
return _filteredProps[_selectedIndex];
}
private static Player FindPlayer()
{
return UnityEngine.Object.FindObjectOfType<Player>();
}
private void KeepPlayerHealthAtMax()
{
Player player = FindPlayer();
if (player == null) return;
HealthComponent playerHealth = player.GetComponent<HealthComponent>();
RestorePlayerHealthToMax(playerHealth);
}
private static void AddPlayerHealth(HealthComponent playerHealth, int amount)
{
if (amount <= 0) return;
if (playerHealth.CurrentHealth <= 0) return;
int maxHealth = playerHealth.MaxHealth;
if (maxHealth <= 0) return;
int nextHealth = Mathf.Clamp(playerHealth.CurrentHealth + amount, 0, maxHealth);
if (nextHealth == playerHealth.CurrentHealth) return;
playerHealth.CurrentHealth = nextHealth;
PublishPlayerHealthChanged(playerHealth);
}
private static void RestorePlayerHealthToMax(HealthComponent playerHealth)
{
if (playerHealth.CurrentHealth <= 0) return;
int maxHealth = playerHealth.MaxHealth;
if (maxHealth <= 0 || playerHealth.CurrentHealth >= maxHealth) return;
playerHealth.CurrentHealth = maxHealth;
PublishPlayerHealthChanged(playerHealth);
}
private static void PublishPlayerHealthChanged(HealthComponent playerHealth)
{
GameEntry.Event.Fire(null,
PlayerHealthChangeEventArgs.Create(0, playerHealth.CurrentHealth, playerHealth.MaxHealth));
}
private static void AddSelectedBuffToPlayer(PropConfig prop, int count)
{
Player player = FindPlayer();
if (player == null) return;
int applyCount = Mathf.Clamp(count, 1, 99);
for (int i = 0; i < applyCount; i++)
{
player.AddProp(new PropItem(prop));
}
}
private void HandleCornerTapGesture()
{
if (!TryGetTouchReleased(out Vector2 touchPosition)) return;
if (touchPosition.x > 80f || touchPosition.y < Screen.height - 80f) return;
float now = Time.unscaledTime;
if (now - _lastCornerTapTime > CornerTapWindow)
{
_cornerTapCount = 0;
}
_lastCornerTapTime = now;
_cornerTapCount++;
if (_cornerTapCount >= RequiredCornerTapCount)
{
_cornerTapCount = 0;
_isPanelVisible = !_isPanelVisible;
}
}
private static bool IsTogglePressed()
{
#if ENABLE_INPUT_SYSTEM
Keyboard keyboard = Keyboard.current;
if (keyboard == null) return false;
return keyboard.backquoteKey.wasPressedThisFrame || keyboard.f8Key.wasPressedThisFrame;
#else
return Input.GetKeyDown(KeyCode.BackQuote) || Input.GetKeyDown(KeyCode.F8);
#endif
}
private static bool TryGetTouchReleased(out Vector2 touchPosition)
{
#if ENABLE_INPUT_SYSTEM
Touchscreen touchscreen = Touchscreen.current;
if (touchscreen == null)
{
touchPosition = default;
return false;
}
var touch = touchscreen.primaryTouch;
if (!touch.press.wasReleasedThisFrame)
{
touchPosition = default;
return false;
}
touchPosition = touch.position.ReadValue();
return true;
#else
if (Input.touchCount <= 0)
{
touchPosition = default;
return false;
}
Touch touch = Input.GetTouch(0);
if (touch.phase != TouchPhase.Ended)
{
touchPosition = default;
return false;
}
touchPosition = touch.position;
return true;
#endif
}
}
}
@@ -49,7 +49,7 @@ namespace SepCore.Procedure
_levelTimerHandle = GameEntry.Timer.ScheduleOnce(levelConfig.Duration, OnLevelTimeUp, this);
_enemyManager.OnInit(levelConfig, Player);
if (Player != null) Player.Enable = true;
Player.Enable = true;
if (Application.isMobilePlatform)
{
@@ -61,12 +61,8 @@ namespace SepCore.Procedure
{
_enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
SimulationWorld simulationWorld = GameEntry.SimulationWorld;
if (simulationWorld != null)
{
Vector3 playerPosition = Player != null ? Player.CachedTransform.position : Vector3.zero;
simulationWorld.Tick(new SimulationTickContext(elapseSeconds, realElapseSeconds, playerPosition));
}
Vector3 playerPosition = Player.CachedTransform.position;
GameEntry.SimulationWorld.Tick(new SimulationTickContext(elapseSeconds, realElapseSeconds, playerPosition));
int timeLeft = Mathf.Max(0, (int)GameEntry.Timer.GetRemainingTime(_levelTimerHandle));
GameEntry.Event.Fire(this, LevelProcessEventArgs.Create(timeLeft));
@@ -8,7 +8,8 @@
"GUID:47a82ffa13c291447ab895cd0bc251cd",
"GUID:436e23dbdc31e7d4fb5c3f804548b2df",
"GUID:0e1d182005e0ae647ab3fa40f5492dbb",
"GUID:3bb549212964b4192b427888ac9382e7"
"GUID:3bb549212964b4192b427888ac9382e7",
"GUID:75469ad4d38634e559750d17036d5f7c"
],
"includePlatforms": [],
"excludePlatforms": [],
@@ -167,7 +167,7 @@ namespace SepCore.AsyncTask
return entityComponent.ShowEntityAsync(
data.Id, enemyType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), "Enemy",
Constant.AssetPriority.EnemyAsset, data, timeout, cancellationToken)
.ContinueWith(entity => entity?.Logic as EnemyBase);
.ContinueWith(entity => entity.Logic as EnemyBase);
}
/// <summary>
@@ -184,7 +184,7 @@ namespace SepCore.AsyncTask
return entityComponent.ShowEntityAsync(
data.Id, weaponType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), "Weapon",
Constant.AssetPriority.WeaponAsset, data, timeout, cancellationToken)
.ContinueWith(entity => entity?.Logic as WeaponBase);
.ContinueWith(entity => entity.Logic as WeaponBase);
}
/// <summary>
@@ -1,6 +1,5 @@
using UnityEngine;
using UnityGameFramework.Runtime;
using Object = UnityEngine.Object;
namespace SepCore.CameraModule
{
@@ -11,178 +10,52 @@ namespace SepCore.CameraModule
[DisallowMultipleComponent]
public class CameraModuleComponent : GameFrameworkComponent
{
[SerializeField] private CinemachineCameraController _cinemachineController;
[SerializeField] private bool _autoSearchController = true;
[SerializeField] private bool _logMissingControllerWarnings = true;
private ICameraController _controller;
private bool _hasLoggedMissingController;
public ICameraController Controller
{
get
{
ResolveController(false);
return IsControllerValid(_controller) ? _controller : null;
}
}
public bool HasController => Controller != null;
public Transform Target => Controller?.Target;
public CameraEffectSettings CurrentSettings => Controller?.CurrentSettings ?? CameraEffectSettings.Default;
protected override void Awake()
{
base.Awake();
ResolveController(false);
}
public void RegisterController(ICameraController controller)
{
if (!IsControllerValid(controller))
{
Log.Warning("CameraModuleComponent.RegisterController() controller is invalid.");
return;
}
_controller = controller;
_hasLoggedMissingController = false;
if (controller is CinemachineCameraController cinemachineController)
{
_cinemachineController = cinemachineController;
}
}
public void UnregisterController(ICameraController controller)
{
if (controller != null && !ReferenceEquals(_controller, controller))
{
return;
}
_controller = null;
_cinemachineController = null;
}
[SerializeField] private CinemachineCameraController _cinemachineController = null;
public void SetTarget(Transform target)
{
if (!EnsureController())
{
return;
}
if (!EnsureController()) return;
_controller.SetTarget(target);
_cinemachineController.SetTarget(target);
}
public void ApplySettings(CameraEffectSettings settings)
{
if (!EnsureController())
{
return;
}
if (!EnsureController()) return;
_controller.ApplySettings(settings);
_cinemachineController.ApplySettings(settings);
}
public void EnableEffect(CameraEffectType effect)
{
if (!EnsureController())
{
return;
}
if (!EnsureController()) return;
_controller.EnableEffect(effect);
_cinemachineController.EnableEffect(effect);
}
public void DisableEffect(CameraEffectType effect)
{
if (!EnsureController())
{
return;
}
if (!EnsureController()) return;
_controller.DisableEffect(effect);
_cinemachineController.DisableEffect(effect);
}
public bool IsEffectEnabled(CameraEffectType effect)
{
return EnsureController() && _controller.IsEffectEnabled(effect);
if (!EnsureController()) return false;
return _cinemachineController.IsEffectEnabled(effect);
}
private bool EnsureController()
{
ResolveController(_logMissingControllerWarnings);
return IsControllerValid(_controller);
}
private void ResolveController(bool logWarning)
{
if (IsControllerValid(_controller))
{
return;
}
if (_cinemachineController != null)
{
_controller = _cinemachineController;
_hasLoggedMissingController = false;
return;
return true;
}
if (!_autoSearchController)
{
LogMissingController(logWarning);
return;
}
_cinemachineController = GetComponentInChildren<CinemachineCameraController>(true);
if (_cinemachineController == null)
{
#if UNITY_2023_1_OR_NEWER
_cinemachineController = FindFirstObjectByType<CinemachineCameraController>(FindObjectsInactive.Include);
#else
_cinemachineController = FindObjectOfType<CinemachineCameraController>(true);
#endif
}
if (_cinemachineController != null)
{
_controller = _cinemachineController;
_hasLoggedMissingController = false;
return;
}
LogMissingController(logWarning);
}
private void LogMissingController(bool logWarning)
{
if (!logWarning || _hasLoggedMissingController)
{
return;
}
_hasLoggedMissingController = true;
Log.Warning(
"CameraModuleComponent requires a CinemachineCameraController. " +
"Assign one in Inspector or place one in the loaded scene.");
}
private static bool IsControllerValid(ICameraController controller)
{
if (controller == null)
{
return false;
}
if (controller is Object unityObject && unityObject == null)
{
return false;
}
return true;
Log.Error("CameraModuleComponent requires a CinemachineCameraController. Assign one in Inspector.");
return false;
}
}
}
@@ -11,11 +11,11 @@ namespace SepCore.DamageText
[SerializeField] private string _poolName = "DamageTextItem";
[SerializeField] private RectTransform _instanceRoot;
[SerializeField] private RectTransform _instanceRoot = null;
[SerializeField] private Canvas _canvas;
[SerializeField] private Canvas _canvas = null;
[SerializeField] private GameObject _damageTextItemPrefab;
[SerializeField] private GameObject _damageTextItemPrefab = null;
private IObjectPool<DamageTextItemObject> _damageTextItemPool;
@@ -30,11 +30,9 @@ namespace SepCore.DamageText
public void ShowDamage(Vector3 worldPosition, int damage)
{
if (damage <= 0 || _damageTextItemPool == null || _canvas == null) return;
if (damage <= 0) return;
DamageTextItem item = CreateDamageTextItem();
if (item == null) return;
item.Show(worldPosition, damage, _canvas, OnItemComplete);
_activeDamageTextItems.Add(item);
}
@@ -62,7 +60,6 @@ namespace SepCore.DamageText
private void OnItemComplete(DamageTextItem item)
{
if (item == null) return;
item.ResetItem();
_activeDamageTextItems.Remove(item);
_damageTextItemPool.Unspawn(item);
@@ -72,7 +69,6 @@ namespace SepCore.DamageText
{
_activeDamageTextItems.Clear();
_damageTextItemPool.Release();
_damageTextItemPool = null;
}
}
}
@@ -13,26 +13,17 @@ namespace SepCore.DamageText
private const float FadeDuration = 0.35f;
private static readonly Color NormalColor = new Color(1f, 0.32f, 0.23f, 1f);
[SerializeField] private RectTransform _cachedTransform;
[SerializeField] private RectTransform _cachedTransform = null;
[SerializeField] private CanvasGroup _canvasGroup;
[SerializeField] private CanvasGroup _canvasGroup = null;
[SerializeField] private TMP_Text _text;
[SerializeField] private TMP_Text _text = null;
private Sequence _sequence;
private Action<DamageTextItem> _onComplete;
private void Awake()
{
if (_cachedTransform == null) _cachedTransform = GetComponent<RectTransform>();
if (_canvasGroup == null) _canvasGroup = GetComponent<CanvasGroup>();
if (_text == null) _text = GetComponent<TMP_Text>();
}
public void Show(Vector3 worldPosition, int damage, Canvas canvas, Action<DamageTextItem> onComplete)
{
if (canvas == null || GameEntry.Scene.MainCamera == null) return;
_onComplete = onComplete;
gameObject.SetActive(true);
KillSequence();
@@ -53,7 +44,7 @@ namespace SepCore.DamageText
.SetEase(Ease.OutQuad));
_sequence.Join(_cachedTransform.DOPunchScale(new Vector3(0.15f, 0.15f, 0f), 0.18f, 6, 0.75f));
_sequence.Join(_canvasGroup.DOFade(0f, FadeDuration).SetDelay(FadeDelay));
_sequence.OnComplete(() => _onComplete?.Invoke(this));
_sequence.OnComplete(() => _onComplete(this));
}
public void ResetItem()
@@ -73,6 +64,7 @@ namespace SepCore.DamageText
private void KillSequence()
{
if (_sequence == null) return;
_sequence.Kill();
_sequence = null;
}
@@ -13,7 +13,7 @@ namespace SepCore.EnemyManager
{
public class EnemyManagerComponent : GameFrameworkComponent
{
private const string EnemyGroupName = "Enemy";
private string EnemyGroupName => GameEntry.Luban.Global.EnemyGroupName;
private EntityComponent _entity;
private EnemyRegistry _enemyRegistry;
@@ -21,8 +21,8 @@ namespace SepCore.EnemyManager
public IReadOnlyCollection<EntityBase> Enemies => _enemyRegistry.Enemies;
[SerializeField] private int _spawnEnemyMaxCount = 5000;
[SerializeField] private int _spawnDistanceFromPlayer = 20;
private int _spawnEnemyMaxCount;
private int _spawnDistanceFromPlayer;
private int _currentSpawnEnemyId;
private int _currentLevel;
@@ -61,12 +61,8 @@ namespace SepCore.EnemyManager
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
_spawnCts?.Dispose();
_spawnCts = null;
_enemyRegistry = null;
_spawnScheduler?.Reset();
_spawnScheduler = null;
_entity = null;
_spawnCts.Dispose();
_spawnScheduler.Reset();
}
public void OnInit(LevelConfig level, Player player)
@@ -78,7 +74,8 @@ namespace SepCore.EnemyManager
}
_isStopped = false;
_spawnEnemyMaxCount = GameEntry.Luban.Global.SpawnEnemyMaxCount;
_spawnDistanceFromPlayer = GameEntry.Luban.Global.SpawnDistanceFromPlayer / 1000;
_player = player.CachedTransform;
_spawnPositionStrategy = new RandomCircleSpawnStrategy(_spawnDistanceFromPlayer);
@@ -138,7 +135,7 @@ namespace SepCore.EnemyManager
var ct = _spawnCts.Token;
var (isCanceled, enemy) =
await _entity.ShowEnemyAsync(enemyData, cancellationToken: ct).SuppressCancellationThrow();
if (isCanceled || ct.IsCancellationRequested || enemy == null || !enemy.Available)
if (isCanceled || ct.IsCancellationRequested)
{
// 取消通常发生在 OnReset 期间,此时实体往往尚未出生,enemy 为 null 无法 hide。
// 真正的兜底在 OnShowEntitySuccess:停战后出生的敌人会被立即 hide。
@@ -150,6 +147,19 @@ namespace SepCore.EnemyManager
return;
}
if (enemy == null)
{
Log.Error("Can not spawn enemy '{0}' with entity id '{1}': entity logic is invalid.", enemyType,
entityId);
return;
}
// 未取消但实体已不可用,说明 OnShowEntitySuccess 已在停战窗口兜底 hide。
if (!enemy.Available)
{
return;
}
// await 恢复时若已停战,说明这是漏网的在途敌人,立即 hide,不注册。
if (_isStopped)
{
@@ -157,11 +167,7 @@ namespace SepCore.EnemyManager
return;
}
if (_player != null)
{
enemy.SetTarget(_player);
}
enemy.SetTarget(_player);
_enemyRegistry.Register(enemy);
}
@@ -20,22 +20,12 @@ namespace SepCore.HPBar
private void Start()
{
if (_hpBarInstanceRoot == null)
{
Log.Error("You must set HP bar instance root first.");
return;
}
_cachedCanvas = _hpBarInstanceRoot.GetComponent<Canvas>();
_hpBarItemObjectPool =
GameEntry.ObjectPool.CreateSingleSpawnObjectPool<HPBarItemObject>("HPBarItem", _instancePoolCapacity);
_activeHPBarItems = new List<HPBarItem>();
}
private void OnDestroy()
{
}
private void Update()
{
for (int i = _activeHPBarItems.Count - 1; i >= 0; i--)
@@ -54,14 +44,14 @@ namespace SepCore.HPBar
{
if (entity == null)
{
Log.Warning("Entity is invalid.");
Log.Error("Entity is invalid.");
return;
}
HPBarItem hpBarItem = GetActiveHPBarItem(entity);
if (hpBarItem == null)
{
hpBarItem = CreateHPBarItem(entity);
hpBarItem = CreateHPBarItem();
_activeHPBarItems.Add(hpBarItem);
}
@@ -77,11 +67,6 @@ namespace SepCore.HPBar
private HPBarItem GetActiveHPBarItem(EntityBase entity)
{
if (entity == null)
{
return null;
}
for (int i = 0; i < _activeHPBarItems.Count; i++)
{
if (_activeHPBarItems[i].Owner == entity)
@@ -93,7 +78,7 @@ namespace SepCore.HPBar
return null;
}
private HPBarItem CreateHPBarItem(EntityBase entity)
private HPBarItem CreateHPBarItem()
{
HPBarItem hpBarItem = null;
HPBarItemObject hpBarItemObject = _hpBarItemObjectPool.Spawn();
@@ -104,7 +89,7 @@ namespace SepCore.HPBar
else
{
hpBarItem = Instantiate(_hpBarItemTemplate);
Transform transform = hpBarItem.GetComponent<Transform>();
Transform transform = hpBarItem.transform;
transform.SetParent(_hpBarInstanceRoot);
transform.localScale = Vector3.one;
_hpBarItemObjectPool.Register(HPBarItemObject.Create(hpBarItem), true);
@@ -14,13 +14,13 @@ namespace SepCore.HPBar
private const float KeepSeconds = 0.4f;
private const float FadeOutSeconds = 0.3f;
[FormerlySerializedAs("m_HPBar")] [SerializeField] private Slider _hPBar = null;
[FormerlySerializedAs("m_HPBar")] [SerializeField] private Slider _hpBar = null;
private Canvas _parentCanvas = null;
private RectTransform _cachedTransform = null;
private CanvasGroup _cachedCanvasGroup = null;
private EntityBase _owner = null;
private int m_OwnerId = 0;
private int _ownerId = 0;
public EntityBase Owner => _owner;
@@ -38,11 +38,11 @@ namespace SepCore.HPBar
StopAllCoroutines();
_cachedCanvasGroup.alpha = 1f;
if (_owner != owner || m_OwnerId != owner.Id)
if (_owner != owner || _ownerId != owner.Id)
{
_hPBar.value = fromHPRatio;
_hpBar.value = fromHPRatio;
_owner = owner;
m_OwnerId = owner.Id;
_ownerId = owner.Id;
}
Refresh();
@@ -57,7 +57,7 @@ namespace SepCore.HPBar
return false;
}
if (_owner != null && Owner.Available && Owner.Id == m_OwnerId)
if (_owner != null && Owner.Available && Owner.Id == _ownerId)
{
Vector3 worldPosition = _owner.CachedTransform.position + Vector3.forward;
Vector3 screenPosition = GameEntry.Scene.MainCamera.WorldToScreenPoint(worldPosition);
@@ -78,7 +78,7 @@ namespace SepCore.HPBar
{
StopAllCoroutines();
_cachedCanvasGroup.alpha = 1f;
_hPBar.value = 1f;
_hpBar.value = 1f;
_owner = null;
gameObject.SetActive(false);
}
@@ -86,23 +86,12 @@ namespace SepCore.HPBar
private void Awake()
{
_cachedTransform = GetComponent<RectTransform>();
if (_cachedTransform == null)
{
Log.Error("RectTransform is invalid.");
return;
}
_cachedCanvasGroup = GetComponent<CanvasGroup>();
if (_cachedCanvasGroup == null)
{
Log.Error("CanvasGroup is invalid.");
return;
}
}
private IEnumerator HPBarCo(float value, float animationDuration, float keepDuration, float fadeOutDuration)
{
yield return _hPBar.SmoothValue(value, animationDuration);
yield return _hpBar.SmoothValue(value, animationDuration);
yield return new WaitForSeconds(keepDuration);
yield return _cachedCanvasGroup.FadeToAlpha(0f, fadeOutDuration);
}
@@ -114,7 +114,7 @@ namespace SepCore.UIRouter
private void RegisterSerializedBindings()
{
if (_controllerBindings == null || _controllerBindings.Count == 0)
if (_controllerBindings.Count == 0)
{
return;
}
@@ -122,28 +122,36 @@ namespace SepCore.UIRouter
HashSet<UIFormType> seen = new();
foreach (ControllerBinding binding in _controllerBindings)
{
if (binding == null || binding.UIFormType == UIFormType.Undefined ||
string.IsNullOrWhiteSpace(binding.ControllerTypeName))
bool hasFormType = binding.UIFormType != UIFormType.Undefined;
bool hasControllerTypeName = !string.IsNullOrWhiteSpace(binding.ControllerTypeName);
if (!hasFormType && !hasControllerTypeName)
{
continue;
}
if (!hasFormType || !hasControllerTypeName)
{
Log.Error("UIRouter binding is incomplete: UIFormType '{0}', controller type name '{1}'.",
binding.UIFormType.ToString(), binding.ControllerTypeName);
continue;
}
if (!seen.Add(binding.UIFormType))
{
Log.Warning("UIRouter duplicate binding for '{0}', later one overrides earlier one.",
Log.Error("UIRouter duplicate binding for '{0}', later one overrides earlier one.",
binding.UIFormType.ToString());
}
Type controllerType = Type.GetType(binding.ControllerTypeName);
if (controllerType == null)
{
Log.Warning("UIRouter binding type not found: {0}", binding.ControllerTypeName);
Log.Error("UIRouter binding type not found: {0}", binding.ControllerTypeName);
continue;
}
if (!typeof(IUIController).IsAssignableFrom(controllerType))
{
Log.Warning("UIRouter binding type '{0}' does not implement IUIFormController.",
Log.Error("UIRouter binding type '{0}' does not implement IUIController.",
binding.ControllerTypeName);
continue;
}
@@ -77,12 +77,9 @@ public abstract class EnemyBase : TargetableObject
CachedTransform.localScale = Vector3.one * Mathf.Max(0.1f, data.Scale);
var renderer = GetComponentInChildren<Renderer>();
if (renderer != null)
{
var block = new MaterialPropertyBlock();
block.SetColor("_BaseColor", data.Color);
renderer.SetPropertyBlock(block);
}
var block = new MaterialPropertyBlock();
block.SetColor("_BaseColor", data.Color);
renderer.SetPropertyBlock(block);
}
}
@@ -27,7 +27,7 @@ namespace SepCore.CustomUtility
WeaponType.Axe => new WeaponSlashData(entityId, config, ownerId, ownerCamp),
WeaponType.Lightning => new WeaponLightningData(entityId, config, ownerId, ownerCamp),
WeaponType.Lance => new WeaponLanceData(entityId, config, ownerId, ownerCamp),
_ => throw new NotImplementedException()
_ => throw new NotImplementedException($"Unsupported weapon type '{weaponType}'.")
};
}