Compare commits
2 Commits
057b17e6cc
...
dea7fd39ec
| Author | SHA1 | Date | |
|---|---|---|---|
| dea7fd39ec | |||
| 282e4b29c4 |
@@ -334,7 +334,8 @@ public class NetworkedComponent : NetworkBehaviour
|
|||||||
|
|
||||||
- For Unity work, prefer Unity MCP for active scene inspection, runtime verification, prefab checks, and console review when it is available in the session.
|
- For Unity work, prefer Unity MCP for active scene inspection, runtime verification, prefab checks, and console review when it is available in the session.
|
||||||
- Never edit code, scenes, prefabs, components, or Unity asset settings while the Unity Editor is in play mode. Stop play mode first, then edit.
|
- Never edit code, scenes, prefabs, components, or Unity asset settings while the Unity Editor is in play mode. Stop play mode first, then edit.
|
||||||
- After Unity-related edits, refresh or compile as needed and check the Unity console before proceeding.
|
- **CRITICAL**: After any code change (edit, create, delete), always perform a force refresh with compile request and wait for ready before entering play mode. Failing to do so causes mid-play compilation which can leave network ports occupied on the next run. Use `refresh_unity(mode="force", compile="request", wait_for_ready=true)`.
|
||||||
|
- After Unity-related edits, check the Unity console for errors before proceeding.
|
||||||
- For networked play tests, prefer a temporary non-conflicting test port when needed and restore the default port after validation.
|
- For networked play tests, prefer a temporary non-conflicting test port when needed and restore the default port after validation.
|
||||||
- The user has a strong project preference that play mode must be stopped before edits because network ports can remain occupied otherwise.
|
- The user has a strong project preference that play mode must be stopped before edits because network ports can remain occupied otherwise.
|
||||||
- Commit messages should follow the recent project history style: use a type prefix such as `feat:`, `fix:`, or `chore:` and write the subject in Korean so the gameplay/UI/system change is clear from the log alone.
|
- Commit messages should follow the recent project history style: use a type prefix such as `feat:`, `fix:`, or `chore:` and write the subject in Korean so the gameplay/UI/system change is clear from the log alone.
|
||||||
|
|||||||
@@ -4324,6 +4324,51 @@ PrefabInstance:
|
|||||||
m_AddedGameObjects: []
|
m_AddedGameObjects: []
|
||||||
m_AddedComponents: []
|
m_AddedComponents: []
|
||||||
m_SourcePrefab: {fileID: 100100000, guid: bf62c127a7934334d9b955503391f574, type: 3}
|
m_SourcePrefab: {fileID: 100100000, guid: bf62c127a7934334d9b955503391f574, type: 3}
|
||||||
|
--- !u!1 &808329368
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 808329370}
|
||||||
|
- component: {fileID: 808329369}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: SpawnPoint (2)
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &808329369
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 808329368}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: aba9a2eb2571da14d901ed51c8866f47, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Network.PlayerSpawnPoint
|
||||||
|
useRotation: 1
|
||||||
|
--- !u!4 &808329370
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 808329368}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: -5, y: 3, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!4 &817798737 stripped
|
--- !u!4 &817798737 stripped
|
||||||
Transform:
|
Transform:
|
||||||
m_CorrespondingSourceObject: {fileID: 1824081129582740912, guid: bf62c127a7934334d9b955503391f574, type: 3}
|
m_CorrespondingSourceObject: {fileID: 1824081129582740912, guid: bf62c127a7934334d9b955503391f574, type: 3}
|
||||||
@@ -13610,3 +13655,4 @@ SceneRoots:
|
|||||||
- {fileID: 1108135668}
|
- {fileID: 1108135668}
|
||||||
- {fileID: 6925510005455365094}
|
- {fileID: 6925510005455365094}
|
||||||
- {fileID: 1793508636}
|
- {fileID: 1793508636}
|
||||||
|
- {fileID: 808329370}
|
||||||
|
|||||||
37
Assets/_Game/Data/Skills/Data_Skill_Player_부활.asset
Normal file
37
Assets/_Game/Data/Skills/Data_Skill_Player_부활.asset
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
|
||||||
|
m_Name: "Data_Skill_Player_\uBD80\uD65C"
|
||||||
|
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
|
||||||
|
skillName: "\uBD80\uD65C"
|
||||||
|
description: "\uBE48\uC0AC \uC0C1\uD0DC\uC778 \uC544\uAD70\uC744 \uBD80\uD65C\uC2DC\uD0B5\uB2C8\uB2E4."
|
||||||
|
icon: {fileID: 0}
|
||||||
|
skillRole: 1
|
||||||
|
activationType: 1
|
||||||
|
baseTypes: 0
|
||||||
|
skillClip: {fileID: -8689311932429934276, guid: 4450ee0d92144ade9f63dd601432d3bf, type: 3}
|
||||||
|
endClip: {fileID: 0}
|
||||||
|
animationSpeed: 1
|
||||||
|
useRootMotion: 0
|
||||||
|
ignoreRootMotionY: 1
|
||||||
|
jumpToTarget: 0
|
||||||
|
blockMovementWhileCasting: 1
|
||||||
|
blockJumpWhileCasting: 1
|
||||||
|
blockOtherSkillsWhileCasting: 1
|
||||||
|
cooldown: 10
|
||||||
|
manaCost: 0
|
||||||
|
maxGemSlotCount: 2
|
||||||
|
castStartEffects: []
|
||||||
|
effects:
|
||||||
|
- {fileID: 11400000, guid: 4242aba9acf45a545a3aa0201125a3ae, type: 2}
|
||||||
|
targetTeam: 1
|
||||||
|
targetType: 0
|
||||||
8
Assets/_Game/Data/Skills/Data_Skill_Player_부활.asset.meta
Normal file
8
Assets/_Game/Data/Skills/Data_Skill_Player_부활.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c677bf79cbc6af04bae23d11670f82fe
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 848cbb76281c68842a4d00329110b769, type: 3}
|
||||||
|
m_Name: "Data_SkillEffect_Player_\uBD80\uD65C"
|
||||||
|
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.Effects.ReviveEffect
|
||||||
|
targetType: 1
|
||||||
|
targetTeam: 1
|
||||||
|
areaCenter: 0
|
||||||
|
areaShape: 0
|
||||||
|
targetLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
includeCasterInArea: 0
|
||||||
|
areaRadius: 3
|
||||||
|
fanOriginDistance: 1
|
||||||
|
fanRadius: 3
|
||||||
|
fanHalfAngle: 45
|
||||||
|
healthPercent: 0.3
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4242aba9acf45a545a3aa0201125a3ae
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -356,6 +356,7 @@ namespace Colosseum
|
|||||||
#region Player Death Tracking
|
#region Player Death Tracking
|
||||||
|
|
||||||
private List<PlayerNetworkController> alivePlayers = new List<PlayerNetworkController>();
|
private List<PlayerNetworkController> alivePlayers = new List<PlayerNetworkController>();
|
||||||
|
private HashSet<PlayerNetworkController> subscribedPlayers = new HashSet<PlayerNetworkController>();
|
||||||
|
|
||||||
private IEnumerator WaitForPlayersAndSubscribe()
|
private IEnumerator WaitForPlayersAndSubscribe()
|
||||||
{
|
{
|
||||||
@@ -373,16 +374,27 @@ namespace Colosseum
|
|||||||
var players = FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
|
var players = FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
player.OnDeath += HandlePlayerDeath;
|
SubscribeSinglePlayer(player);
|
||||||
if (!player.IsDead)
|
|
||||||
{
|
|
||||||
alivePlayers.Add(player);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debugMode)
|
if (debugMode)
|
||||||
{
|
{
|
||||||
Debug.Log($"[GameManager] Subscribed to {players.Length} players, {alivePlayers.Count} alive");
|
Debug.Log($"[GameManager] Subscribed to {subscribedPlayers.Count} players, {alivePlayers.Count} alive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeSinglePlayer(PlayerNetworkController player)
|
||||||
|
{
|
||||||
|
if (player == null || subscribedPlayers.Contains(player))
|
||||||
|
return;
|
||||||
|
|
||||||
|
player.OnDeath += HandlePlayerDeath;
|
||||||
|
player.OnRevived += HandlePlayerRevived;
|
||||||
|
subscribedPlayers.Add(player);
|
||||||
|
|
||||||
|
if (!player.IsDead)
|
||||||
|
{
|
||||||
|
alivePlayers.Add(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,8 +404,25 @@ namespace Colosseum
|
|||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
player.OnDeath -= HandlePlayerDeath;
|
player.OnDeath -= HandlePlayerDeath;
|
||||||
|
player.OnRevived -= HandlePlayerRevived;
|
||||||
}
|
}
|
||||||
alivePlayers.Clear();
|
alivePlayers.Clear();
|
||||||
|
subscribedPlayers.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MPP 등으로 나중에 스폰된 플레이어를 동적으로 감지하여 구독합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!IsServer || currentState.Value != GameState.Playing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var players = FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
|
||||||
|
for (int i = 0; i < players.Length; i++)
|
||||||
|
{
|
||||||
|
SubscribeSinglePlayer(players[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandlePlayerDeath(PlayerNetworkController player)
|
private void HandlePlayerDeath(PlayerNetworkController player)
|
||||||
@@ -412,6 +441,19 @@ namespace Colosseum
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandlePlayerRevived(PlayerNetworkController player)
|
||||||
|
{
|
||||||
|
if (alivePlayers.Contains(player))
|
||||||
|
return;
|
||||||
|
|
||||||
|
alivePlayers.Add(player);
|
||||||
|
|
||||||
|
if (debugMode)
|
||||||
|
{
|
||||||
|
Debug.Log($"[GameManager] Player revived. Alive: {alivePlayers.Count}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Boss Death Tracking
|
#region Boss Death Tracking
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ namespace Colosseum.Editor
|
|||||||
CastOwnedPlayerSkillAsServer(1, 5);
|
CastOwnedPlayerSkillAsServer(1, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MenuItem("Tools/Colosseum/Debug/Cast Client1 Skill 5")]
|
||||||
|
private static void CastClient1Skill5()
|
||||||
|
{
|
||||||
|
CastOwnedPlayerSkillAsServer(1, 6);
|
||||||
|
}
|
||||||
|
|
||||||
[MenuItem("Tools/Colosseum/Debug/Cast Local Heal")]
|
[MenuItem("Tools/Colosseum/Debug/Cast Local Heal")]
|
||||||
private static void CastLocalHeal()
|
private static void CastLocalHeal()
|
||||||
{
|
{
|
||||||
@@ -167,6 +173,129 @@ namespace Colosseum.Editor
|
|||||||
localNetworkController.TakeDamageRpc(30f);
|
localNetworkController.TakeDamageRpc(30f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MenuItem("Tools/Colosseum/Debug/Kill Local Player")]
|
||||||
|
private static void KillLocalPlayer()
|
||||||
|
{
|
||||||
|
if (!EditorApplication.isPlaying)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerNetworkController localNetworkController = FindLocalNetworkController();
|
||||||
|
if (localNetworkController == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[Debug] 로컬 PlayerNetworkController를 찾지 못했습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localNetworkController.TakeDamageRpc(localNetworkController.Health + 999f);
|
||||||
|
Debug.Log($"[Debug] 로컬 플레이어 즉사 | HP={localNetworkController.Health:F1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[MenuItem("Tools/Colosseum/Debug/Revive Local Player")]
|
||||||
|
private static void ReviveLocalPlayer()
|
||||||
|
{
|
||||||
|
if (!EditorApplication.isPlaying)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerNetworkController localNetworkController = FindLocalNetworkController();
|
||||||
|
if (localNetworkController == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[Debug] 로컬 PlayerNetworkController를 찾지 못했습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localNetworkController.IsDead)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[Debug] 로컬 플레이어가 사망 상태가 아닙니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localNetworkController.Revive(0.3f);
|
||||||
|
Debug.Log($"[Debug] 로컬 플레이어 부활 | HP={localNetworkController.Health:F1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[MenuItem("Tools/Colosseum/Debug/Respawn Local Player")]
|
||||||
|
private static void RespawnLocalPlayer()
|
||||||
|
{
|
||||||
|
if (!EditorApplication.isPlaying)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerNetworkController localNetworkController = FindLocalNetworkController();
|
||||||
|
if (localNetworkController == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[Debug] 로컬 PlayerNetworkController를 찾지 못했습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localNetworkController.Respawn();
|
||||||
|
Debug.Log($"[Debug] 로컬 플레이어 리스폰 | HP={localNetworkController.Health:F1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[MenuItem("Tools/Colosseum/Debug/Spawn Players")]
|
||||||
|
private static void SpawnPlayers()
|
||||||
|
{
|
||||||
|
if (!EditorApplication.isPlaying)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[Debug] 플레이 모드에서만 사용할 수 있습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var networkManager = Unity.Netcode.NetworkManager.Singleton;
|
||||||
|
if (networkManager == null || !networkManager.IsServer)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[Debug] NetworkManager가 없거나 서버가 아닙니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string playerPrefabPath = "Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab";
|
||||||
|
GameObject playerPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(playerPrefabPath);
|
||||||
|
if (playerPrefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[Debug] 플레이어 프리팹을 찾지 못했습니다: {playerPrefabPath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int spawnedCount = 0;
|
||||||
|
foreach (ulong clientId in networkManager.ConnectedClientsIds)
|
||||||
|
{
|
||||||
|
bool alreadyExists = false;
|
||||||
|
var allPlayers = Object.FindObjectsByType<PlayerNetworkController>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||||
|
for (int i = 0; i < allPlayers.Length; i++)
|
||||||
|
{
|
||||||
|
if (allPlayers[i] != null && allPlayers[i].OwnerClientId == clientId)
|
||||||
|
{
|
||||||
|
alreadyExists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alreadyExists)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var go = Object.Instantiate(playerPrefab);
|
||||||
|
var no = go.GetComponent<Unity.Netcode.NetworkObject>();
|
||||||
|
if (no != null)
|
||||||
|
{
|
||||||
|
no.SpawnAsPlayerObject(clientId, true);
|
||||||
|
spawnedCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Object.Destroy(go);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[Debug] 플레이어 스폰 완료 | {spawnedCount}명 스폰 (총 연결: {networkManager.ConnectedClientsIds.Count})");
|
||||||
|
}
|
||||||
|
|
||||||
[MenuItem("Tools/Colosseum/Debug/Log Local Player Status")]
|
[MenuItem("Tools/Colosseum/Debug/Log Local Player Status")]
|
||||||
private static void LogLocalPlayerStatus()
|
private static void LogLocalPlayerStatus()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ namespace Colosseum.Network
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 플레이어 스폰 위치 마커
|
/// 플레이어 스폰 위치 마커
|
||||||
/// 씬에 배치하여 플레이어가 스폰될 위치를 지정
|
/// 씬에 배치하여 플레이어가 스폰될 위치를 지정
|
||||||
|
/// Round-Robin 순차 할당으로 중복 스폰 방지
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PlayerSpawnPoint : MonoBehaviour
|
public class PlayerSpawnPoint : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("Spawn Settings")]
|
[Header("Spawn Settings")]
|
||||||
[SerializeField] private bool useRotation = true;
|
[SerializeField] private bool useRotation = true;
|
||||||
|
|
||||||
private static System.Collections.Generic.List<PlayerSpawnPoint> spawnPoints = new System.Collections.Generic.List<PlayerSpawnPoint>();
|
private static readonly System.Collections.Generic.List<PlayerSpawnPoint> spawnPoints = new System.Collections.Generic.List<PlayerSpawnPoint>();
|
||||||
|
private static int nextSpawnIndex;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
@@ -24,17 +26,23 @@ namespace Colosseum.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 사용 가능한 스폰 포인트 중 하나를 반환
|
/// Round-Robin 순차 할당 — 접속 순서대로 순환하며 중복 없이 반환
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Transform GetRandomSpawnPoint()
|
public static Transform GetNextSpawnPoint()
|
||||||
{
|
{
|
||||||
if (spawnPoints.Count == 0)
|
if (spawnPoints.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
int index = Random.Range(0, spawnPoints.Count);
|
int index = nextSpawnIndex % spawnPoints.Count;
|
||||||
|
nextSpawnIndex++;
|
||||||
return spawnPoints[index].transform;
|
return spawnPoints[index].transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 사용 가능한 스폰 포인트 중 하나를 반환 (기존 호환용, 내부적으로 Round-Robin 사용)
|
||||||
|
/// </summary>
|
||||||
|
public static Transform GetRandomSpawnPoint() => GetNextSpawnPoint();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 스폰 포인트 개수 반환
|
/// 스폰 포인트 개수 반환
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -131,6 +131,11 @@ namespace Colosseum.Player
|
|||||||
{
|
{
|
||||||
RequestRespawnRpc();
|
RequestRespawnRpc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("부활", GUILayout.Height(24f)))
|
||||||
|
{
|
||||||
|
RequestReviveRpc();
|
||||||
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
GUILayout.Space(6f);
|
GUILayout.Space(6f);
|
||||||
@@ -409,6 +414,21 @@ namespace Colosseum.Player
|
|||||||
networkController.Respawn();
|
networkController.Respawn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.Server)]
|
||||||
|
private void RequestReviveRpc()
|
||||||
|
{
|
||||||
|
if (networkController == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!networkController.IsDead)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[AbnormalityDebugHUD] 부활: 사망 상태가 아닙니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkController.Revive(0.3f);
|
||||||
|
}
|
||||||
|
|
||||||
private bool ShouldEnableDebugHud()
|
private bool ShouldEnableDebugHud()
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ namespace Colosseum.Player
|
|||||||
public event Action<PlayerNetworkController> OnDeath;
|
public event Action<PlayerNetworkController> OnDeath;
|
||||||
public event Action<bool> OnDeathStateChanged;
|
public event Action<bool> OnDeathStateChanged;
|
||||||
public event Action<PlayerNetworkController> OnRespawned;
|
public event Action<PlayerNetworkController> OnRespawned;
|
||||||
|
public event Action<PlayerNetworkController> OnRevived;
|
||||||
public event Action OnPassiveSelectionChanged;
|
public event Action OnPassiveSelectionChanged;
|
||||||
|
|
||||||
public float CurrentHealth => currentHealth.Value;
|
public float CurrentHealth => currentHealth.Value;
|
||||||
@@ -249,7 +250,8 @@ namespace Colosseum.Player
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 사망 처리 (서버에서만 실행)
|
/// 사망(빈사) 처리 (서버에서만 실행).
|
||||||
|
/// HP가 0 이하가 되면 호출되며, 부활 스킬로 복귀 가능한 빈사 상태로 전환합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void HandleDeath()
|
private void HandleDeath()
|
||||||
{
|
{
|
||||||
@@ -362,6 +364,36 @@ namespace Colosseum.Player
|
|||||||
Debug.Log($"[Player] Player {OwnerClientId} respawned!");
|
Debug.Log($"[Player] Player {OwnerClientId} respawned!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 빈사 상태에서 부활 (서버에서만 실행)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="healthPercent">부활 시 체력 비율 (0~1)</param>
|
||||||
|
public void Revive(float healthPercent = 0.3f)
|
||||||
|
{
|
||||||
|
if (!IsServer || !isDead.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
isDead.Value = false;
|
||||||
|
float revivedHealth = Mathf.Max(1f, MaxHealth * Mathf.Clamp01(healthPercent));
|
||||||
|
currentHealth.Value = revivedHealth;
|
||||||
|
|
||||||
|
PlayerMovement movement = GetComponent<PlayerMovement>();
|
||||||
|
if (movement != null)
|
||||||
|
{
|
||||||
|
movement.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerSkillInput skillInput = GetComponent<PlayerSkillInput>();
|
||||||
|
if (skillInput != null)
|
||||||
|
{
|
||||||
|
skillInput.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnRevived?.Invoke(this);
|
||||||
|
|
||||||
|
Debug.Log($"[Player] Player {OwnerClientId} revived! HP={revivedHealth:F0}");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 서버 기준으로 패시브 프리셋을 적용합니다.
|
/// 서버 기준으로 패시브 프리셋을 적용합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace Colosseum.Player
|
|||||||
"Assets/_Game/Data/Skills/Data_Skill_Player_보호막.asset",
|
"Assets/_Game/Data/Skills/Data_Skill_Player_보호막.asset",
|
||||||
"Assets/_Game/Data/Skills/Data_Skill_Player_방어태세.asset",
|
"Assets/_Game/Data/Skills/Data_Skill_Player_방어태세.asset",
|
||||||
"Assets/_Game/Data/Skills/Data_Skill_Player_투사체.asset",
|
"Assets/_Game/Data/Skills/Data_Skill_Player_투사체.asset",
|
||||||
"Assets/_Game/Data/Skills/Data_Skill_Player_구르기.asset",
|
"Assets/_Game/Data/Skills/Data_Skill_Player_부활.asset",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly string[] DpsLoadoutPaths =
|
private static readonly string[] DpsLoadoutPaths =
|
||||||
@@ -566,27 +566,48 @@ namespace Colosseum.Player
|
|||||||
public bool DebugExecuteSkillAsServer(int slotIndex)
|
public bool DebugExecuteSkillAsServer(int slotIndex)
|
||||||
{
|
{
|
||||||
if (!IsServer)
|
if (!IsServer)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: 서버가 아님 (IsServer=false)");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
EnsureRuntimeReferences();
|
EnsureRuntimeReferences();
|
||||||
|
|
||||||
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: 슬롯 인덱스 범위 초과 (slotIndex={slotIndex}, length={skillSlots.Length})");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
SkillLoadoutEntry loadoutEntry = GetSkillLoadout(slotIndex);
|
SkillLoadoutEntry loadoutEntry = GetSkillLoadout(slotIndex);
|
||||||
SkillData skill = loadoutEntry != null ? loadoutEntry.BaseSkill : null;
|
SkillData skill = loadoutEntry != null ? loadoutEntry.BaseSkill : null;
|
||||||
if (skill == null)
|
if (skill == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: 슬롯 {slotIndex}이 비어있음");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (actionState != null && !actionState.CanStartSkill(skill))
|
if (actionState != null && !actionState.CanStartSkill(skill))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: CanStartSkill=false (IsDead={actionState.IsDead}, CanUseSkills={actionState.CanUseSkills})");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (skillController == null || skillController.IsExecutingSkill || skillController.IsOnCooldown(skill))
|
if (skillController == null || skillController.IsExecutingSkill || skillController.IsOnCooldown(skill))
|
||||||
|
{
|
||||||
|
string reason = skillController == null ? "skillController=null" :
|
||||||
|
skillController.IsExecutingSkill ? "스킬 실행 중" :
|
||||||
|
skillController.IsOnCooldown(skill) ? "쿨다운 중" : "";
|
||||||
|
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: {reason}");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
float actualManaCost = GetActualManaCost(loadoutEntry);
|
float actualManaCost = GetActualManaCost(loadoutEntry);
|
||||||
if (networkController != null && networkController.Mana < actualManaCost)
|
if (networkController != null && networkController.Mana < actualManaCost)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: MP 부족 (need={actualManaCost:F1}, have={networkController.Mana:F1})");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (networkController != null && actualManaCost > 0f)
|
if (networkController != null && actualManaCost > 0f)
|
||||||
{
|
{
|
||||||
@@ -594,6 +615,7 @@ namespace Colosseum.Player
|
|||||||
}
|
}
|
||||||
|
|
||||||
BroadcastSkillExecutionRpc(slotIndex);
|
BroadcastSkillExecutionRpc(slotIndex);
|
||||||
|
Debug.Log($"[Debug] DebugExecuteSkillAsServer 성공: Slot={slotIndex}, Skill={skill.SkillName}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
Assets/_Game/Scripts/Skills/Effects/ReviveEffect.cs
Normal file
49
Assets/_Game/Scripts/Skills/Effects/ReviveEffect.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
using Colosseum.Player;
|
||||||
|
|
||||||
|
namespace Colosseum.Skills.Effects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 빈사 상태인 아군을 부활시키는 스킬 효과입니다.
|
||||||
|
/// </summary>
|
||||||
|
[CreateAssetMenu(fileName = "ReviveEffect", menuName = "Colosseum/Skills/Effects/Revive")]
|
||||||
|
public class ReviveEffect : SkillEffect
|
||||||
|
{
|
||||||
|
[Header("Revive Settings")]
|
||||||
|
[Tooltip("부활 시 복구할 체력 비율 (0~1)")]
|
||||||
|
[Range(0f, 1f)] [SerializeField] private float healthPercent = 0.3f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 부활 체력 비율
|
||||||
|
/// </summary>
|
||||||
|
public float HealthPercent => healthPercent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 부활 효과를 적용합니다.
|
||||||
|
/// </summary>
|
||||||
|
protected override void ApplyEffect(GameObject caster, GameObject target)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[ReviveEffect] Target is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerNetworkController networkController = target.GetComponent<PlayerNetworkController>();
|
||||||
|
if (networkController == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[ReviveEffect] PlayerNetworkController not found on target: {target.name}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!networkController.IsDead)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[ReviveEffect] Target is not dead: {target.name}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkController.Revive(healthPercent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ namespace Colosseum.Skills
|
|||||||
Stun,
|
Stun,
|
||||||
HitReaction,
|
HitReaction,
|
||||||
Respawn,
|
Respawn,
|
||||||
|
Revive,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user