fix: 스킬 슬롯 아이템 변경 불가 문제 수정 (slot/loadoutEntry 양방향 의존성 제거)
- SyncLegacySkillsToLoadoutEntries를 양방향에서 슬롯→엔트리 단방향으로 변경 (skillSlots를 SSOT로 지정, loadoutEntry는 항상 슬롯을 따름) - GetSkill/GetSkillLoadout에서 불필요한 sync 호출 제거로 읽기 시 역동기화 방지 - SetSkillGem에서 skillSlots 역동기화 제거 - SetSkillLoadout/SetSkillLoadouts에서 슬롯 우선 갱신 후 젬만 복사하도록 개선
This commit is contained in:
@@ -92,9 +92,25 @@ namespace Colosseum.Player
|
|||||||
[Tooltip("시야 차단 확인용 레이어 (벽, 바닥 등)")]
|
[Tooltip("시야 차단 확인용 레이어 (벽, 바닥 등)")]
|
||||||
[SerializeField] private LayerMask lineOfSightBlockLayers;
|
[SerializeField] private LayerMask lineOfSightBlockLayers;
|
||||||
|
|
||||||
|
[Header("지면 타겟팅 설정 (Ground Target)")]
|
||||||
|
[Tooltip("지면 레이캐스트용 레이어 (바닥, 지형 등)")]
|
||||||
|
[SerializeField] private LayerMask groundTargetLayers;
|
||||||
|
[Tooltip("지면 타겟팅 최대 사거리")]
|
||||||
|
[Min(1f)] [SerializeField] private float groundTargetMaxRange = 20f;
|
||||||
|
|
||||||
private InputSystem_Actions inputActions;
|
private InputSystem_Actions inputActions;
|
||||||
private bool gameplayInputEnabled = true;
|
private bool gameplayInputEnabled = true;
|
||||||
|
|
||||||
|
// Ground Target 타겟팅 모드
|
||||||
|
private enum TargetingMode
|
||||||
|
{
|
||||||
|
None, // 일반 모드
|
||||||
|
GroundTarget // 지면 타겟팅 모드 (커서로 위치 선택 중)
|
||||||
|
}
|
||||||
|
|
||||||
|
private TargetingMode currentTargetingMode = TargetingMode.None;
|
||||||
|
private int pendingGroundTargetSlotIndex = -1;
|
||||||
|
|
||||||
public SkillData[] SkillSlots => skillSlots;
|
public SkillData[] SkillSlots => skillSlots;
|
||||||
public SkillLoadoutEntry[] SkillLoadoutEntries => skillLoadoutEntries;
|
public SkillLoadoutEntry[] SkillLoadoutEntries => skillLoadoutEntries;
|
||||||
|
|
||||||
@@ -160,6 +176,10 @@ namespace Colosseum.Player
|
|||||||
EnsureSkillLoadoutCapacity();
|
EnsureSkillLoadoutCapacity();
|
||||||
SyncLegacySkillsToLoadoutEntries();
|
SyncLegacySkillsToLoadoutEntries();
|
||||||
AutoRegisterPlayerSkills();
|
AutoRegisterPlayerSkills();
|
||||||
|
|
||||||
|
// Ground 레이어가 설정되지 않은 경우 기본값 적용 (Layer 7 = Ground)
|
||||||
|
if (groundTargetLayers.value == 0)
|
||||||
|
groundTargetLayers = 1 << 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
@@ -186,6 +206,7 @@ namespace Colosseum.Player
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CancelGroundTargetMode();
|
||||||
inputActions.Player.Disable();
|
inputActions.Player.Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +351,8 @@ namespace Colosseum.Player
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 기존 SkillData 직렬화와 새 로드아웃 엔트리 구조를 동기화합니다.
|
/// skillSlots를 출처(SSOT)로 삼아 로드아웃 엔트리의 BaseSkill을 동기화합니다.
|
||||||
|
/// 슬롯 변경은 항상 skillSlots를 거쳐야 하며, 엔트리는 이를 따릅니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SyncLegacySkillsToLoadoutEntries()
|
private void SyncLegacySkillsToLoadoutEntries()
|
||||||
{
|
{
|
||||||
@@ -339,20 +361,12 @@ namespace Colosseum.Player
|
|||||||
|
|
||||||
for (int i = 0; i < skillSlots.Length; i++)
|
for (int i = 0; i < skillSlots.Length; i++)
|
||||||
{
|
{
|
||||||
|
SkillData slotSkill = skillSlots[i];
|
||||||
SkillLoadoutEntry entry = skillLoadoutEntries[i];
|
SkillLoadoutEntry entry = skillLoadoutEntries[i];
|
||||||
SkillData legacySkill = skillSlots[i];
|
|
||||||
|
|
||||||
if (entry.BaseSkill == null && legacySkill != null)
|
if (entry.BaseSkill != slotSkill)
|
||||||
{
|
{
|
||||||
entry.SetBaseSkill(legacySkill);
|
entry.SetBaseSkill(slotSkill);
|
||||||
}
|
|
||||||
else if (legacySkill == null && entry.BaseSkill != null)
|
|
||||||
{
|
|
||||||
skillSlots[i] = entry.BaseSkill;
|
|
||||||
}
|
|
||||||
else if (entry.BaseSkill != legacySkill)
|
|
||||||
{
|
|
||||||
skillSlots[i] = entry.BaseSkill;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,6 +444,13 @@ namespace Colosseum.Player
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ground Target 스킬인 경우 타겟팅 모드 진입
|
||||||
|
if (RequiresGroundTarget(loadoutEntry))
|
||||||
|
{
|
||||||
|
EnterGroundTargetMode(slotIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
RequestSkillExecutionRpc(slotIndex, targetNetworkObjectId);
|
RequestSkillExecutionRpc(slotIndex, targetNetworkObjectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,7 +458,7 @@ namespace Colosseum.Player
|
|||||||
/// 서버에 스킬 실행 요청
|
/// 서버에 스킬 실행 요청
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Rpc(SendTo.Server)]
|
[Rpc(SendTo.Server)]
|
||||||
private void RequestSkillExecutionRpc(int slotIndex, ulong targetNetworkObjectId = 0)
|
private void RequestSkillExecutionRpc(int slotIndex, ulong targetNetworkObjectId = 0, Vector3 groundTargetPosition = default)
|
||||||
{
|
{
|
||||||
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
||||||
return;
|
return;
|
||||||
@@ -469,6 +490,17 @@ namespace Colosseum.Player
|
|||||||
targetNetworkObjectId = 0;
|
targetNetworkObjectId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 지면 타겟 사거리 검증
|
||||||
|
if (groundTargetPosition != default)
|
||||||
|
{
|
||||||
|
float distance = Vector3.Distance(transform.position, groundTargetPosition);
|
||||||
|
if (distance > groundTargetMaxRange)
|
||||||
|
{
|
||||||
|
Debug.Log($"[GroundTarget] 사거리 초과: {distance:F1}m (max={groundTargetMaxRange}m)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 마나 소모 (무기 배율 적용)
|
// 마나 소모 (무기 배율 적용)
|
||||||
if (networkController != null && actualManaCost > 0)
|
if (networkController != null && actualManaCost > 0)
|
||||||
{
|
{
|
||||||
@@ -476,14 +508,14 @@ namespace Colosseum.Player
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 모든 클라이언트에 스킬 실행 전파
|
// 모든 클라이언트에 스킬 실행 전파
|
||||||
BroadcastSkillExecutionRpc(slotIndex, targetNetworkObjectId);
|
BroadcastSkillExecutionRpc(slotIndex, targetNetworkObjectId, groundTargetPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 모든 클라이언트에 스킬 실행 전파
|
/// 모든 클라이언트에 스킬 실행 전파
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Rpc(SendTo.ClientsAndHost)]
|
[Rpc(SendTo.ClientsAndHost)]
|
||||||
private void BroadcastSkillExecutionRpc(int slotIndex, ulong targetNetworkObjectId = 0)
|
private void BroadcastSkillExecutionRpc(int slotIndex, ulong targetNetworkObjectId = 0, Vector3 groundTargetPosition = default)
|
||||||
{
|
{
|
||||||
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
||||||
return;
|
return;
|
||||||
@@ -496,7 +528,14 @@ namespace Colosseum.Player
|
|||||||
GameObject targetOverride = ResolveTargetFromNetworkId(targetNetworkObjectId);
|
GameObject targetOverride = ResolveTargetFromNetworkId(targetNetworkObjectId);
|
||||||
|
|
||||||
// 모든 클라이언트에서 스킬 실행 (애니메이션 포함)
|
// 모든 클라이언트에서 스킬 실행 (애니메이션 포함)
|
||||||
skillController.ExecuteSkill(loadoutEntry, targetOverride);
|
if (groundTargetPosition != default)
|
||||||
|
{
|
||||||
|
skillController.ExecuteSkill(loadoutEntry, targetOverride, groundTargetPosition);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
skillController.ExecuteSkill(loadoutEntry, targetOverride);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -518,13 +557,9 @@ namespace Colosseum.Player
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public SkillData GetSkill(int slotIndex)
|
public SkillData GetSkill(int slotIndex)
|
||||||
{
|
{
|
||||||
EnsureSkillSlotCapacity();
|
|
||||||
EnsureSkillLoadoutCapacity();
|
|
||||||
SyncLegacySkillsToLoadoutEntries();
|
|
||||||
|
|
||||||
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
||||||
return null;
|
return null;
|
||||||
return skillLoadoutEntries[slotIndex] != null ? skillLoadoutEntries[slotIndex].BaseSkill : skillSlots[slotIndex];
|
return skillSlots[slotIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -532,10 +567,6 @@ namespace Colosseum.Player
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public SkillLoadoutEntry GetSkillLoadout(int slotIndex)
|
public SkillLoadoutEntry GetSkillLoadout(int slotIndex)
|
||||||
{
|
{
|
||||||
EnsureSkillSlotCapacity();
|
|
||||||
EnsureSkillLoadoutCapacity();
|
|
||||||
SyncLegacySkillsToLoadoutEntries();
|
|
||||||
|
|
||||||
if (slotIndex < 0 || slotIndex >= skillLoadoutEntries.Length)
|
if (slotIndex < 0 || slotIndex >= skillLoadoutEntries.Length)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -585,19 +616,32 @@ namespace Colosseum.Player
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 슬롯 엔트리를 직접 설정합니다.
|
/// 슬롯 엔트리를 직접 설정합니다.
|
||||||
|
/// skillSlots를 우선 갱신한 뒤 엔트리에 젬 정보를 복사합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetSkillLoadout(int slotIndex, SkillLoadoutEntry loadoutEntry)
|
public void SetSkillLoadout(int slotIndex, SkillLoadoutEntry loadoutEntry)
|
||||||
{
|
{
|
||||||
EnsureSkillSlotCapacity();
|
EnsureSkillSlotCapacity();
|
||||||
EnsureSkillLoadoutCapacity();
|
EnsureSkillLoadoutCapacity();
|
||||||
|
|
||||||
if (slotIndex < 0 || slotIndex >= skillLoadoutEntries.Length)
|
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
skillLoadoutEntries[slotIndex] = loadoutEntry != null ? loadoutEntry.CreateCopy() : new SkillLoadoutEntry();
|
// skillSlots를 SSOT로 먼저 갱신
|
||||||
skillLoadoutEntries[slotIndex].EnsureGemSlotCapacity();
|
SkillData skillFromEntry = loadoutEntry != null ? loadoutEntry.BaseSkill : null;
|
||||||
skillLoadoutEntries[slotIndex].SanitizeInvalidGems(true);
|
skillSlots[slotIndex] = skillFromEntry;
|
||||||
skillSlots[slotIndex] = skillLoadoutEntries[slotIndex].BaseSkill;
|
|
||||||
|
// 엔트리는 슬롯의 스킬 + 전달받은 젬을 보관
|
||||||
|
SkillLoadoutEntry targetEntry = skillLoadoutEntries[slotIndex];
|
||||||
|
targetEntry.SetBaseSkill(skillFromEntry);
|
||||||
|
|
||||||
|
if (loadoutEntry != null)
|
||||||
|
{
|
||||||
|
for (int g = 0; g < loadoutEntry.SocketedGems.Count; g++)
|
||||||
|
{
|
||||||
|
targetEntry.SetGem(g, loadoutEntry.SocketedGems[g]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OnSkillSlotsChanged?.Invoke();
|
OnSkillSlotsChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,12 +657,12 @@ namespace Colosseum.Player
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
skillLoadoutEntries[slotIndex].SetGem(gemSlotIndex, gem);
|
skillLoadoutEntries[slotIndex].SetGem(gemSlotIndex, gem);
|
||||||
skillSlots[slotIndex] = skillLoadoutEntries[slotIndex].BaseSkill;
|
|
||||||
OnSkillSlotsChanged?.Invoke();
|
OnSkillSlotsChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 슬롯 엔트리 전체를 한 번에 갱신합니다.
|
/// 슬롯 엔트리 전체를 한 번에 갱신합니다.
|
||||||
|
/// skillSlots를 SSOT로 먼저 갱신한 뒤 엔트리에 젬 정보를 복사합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetSkillLoadouts(IReadOnlyList<SkillLoadoutEntry> loadouts)
|
public void SetSkillLoadouts(IReadOnlyList<SkillLoadoutEntry> loadouts)
|
||||||
{
|
{
|
||||||
@@ -628,20 +672,28 @@ namespace Colosseum.Player
|
|||||||
if (loadouts == null)
|
if (loadouts == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int count = Mathf.Min(skillLoadoutEntries.Length, loadouts.Count);
|
int count = Mathf.Min(skillSlots.Length, loadouts.Count);
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
skillLoadoutEntries[i] = loadouts[i] != null ? loadouts[i].CreateCopy() : new SkillLoadoutEntry();
|
SkillData skillFromEntry = loadouts[i] != null ? loadouts[i].BaseSkill : null;
|
||||||
skillLoadoutEntries[i].EnsureGemSlotCapacity();
|
skillSlots[i] = skillFromEntry;
|
||||||
skillLoadoutEntries[i].SanitizeInvalidGems(true);
|
|
||||||
skillSlots[i] = skillLoadoutEntries[i].BaseSkill;
|
SkillLoadoutEntry targetEntry = skillLoadoutEntries[i];
|
||||||
|
targetEntry.SetBaseSkill(skillFromEntry);
|
||||||
|
|
||||||
|
if (loadouts[i] != null)
|
||||||
|
{
|
||||||
|
for (int g = 0; g < loadouts[i].SocketedGems.Count; g++)
|
||||||
|
{
|
||||||
|
targetEntry.SetGem(g, loadouts[i].SocketedGems[g]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = count; i < skillLoadoutEntries.Length; i++)
|
for (int i = count; i < skillSlots.Length; i++)
|
||||||
{
|
{
|
||||||
skillLoadoutEntries[i] = new SkillLoadoutEntry();
|
|
||||||
skillLoadoutEntries[i].EnsureGemSlotCapacity();
|
|
||||||
skillSlots[i] = null;
|
skillSlots[i] = null;
|
||||||
|
skillLoadoutEntries[i].SetBaseSkill(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
OnSkillSlotsChanged?.Invoke();
|
OnSkillSlotsChanged?.Invoke();
|
||||||
@@ -819,6 +871,143 @@ namespace Colosseum.Player
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region 지면 타게팅 (Ground Target)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 해당 로드아웃 엔트리에 GroundPoint AreaCenter를 사용하는 Area 효과가 있는지 확인합니다.
|
||||||
|
/// </summary>
|
||||||
|
private bool RequiresGroundTarget(SkillLoadoutEntry loadoutEntry)
|
||||||
|
{
|
||||||
|
if (loadoutEntry == null) return false;
|
||||||
|
|
||||||
|
var castStartEffects = new List<SkillEffect>();
|
||||||
|
loadoutEntry.CollectCastStartEffects(castStartEffects);
|
||||||
|
for (int i = 0; i < castStartEffects.Count; i++)
|
||||||
|
{
|
||||||
|
if (castStartEffects[i] != null
|
||||||
|
&& castStartEffects[i].TargetType == TargetType.Area
|
||||||
|
&& castStartEffects[i].AreaCenter == AreaCenterType.GroundPoint)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkillData skill = loadoutEntry.BaseSkill;
|
||||||
|
if (skill != null)
|
||||||
|
{
|
||||||
|
foreach (var effect in skill.Effects)
|
||||||
|
{
|
||||||
|
if (effect != null
|
||||||
|
&& effect.TargetType == TargetType.Area
|
||||||
|
&& effect.AreaCenter == AreaCenterType.GroundPoint)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 지면 타겟팅 모드로 진입합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void EnterGroundTargetMode(int slotIndex)
|
||||||
|
{
|
||||||
|
currentTargetingMode = TargetingMode.GroundTarget;
|
||||||
|
pendingGroundTargetSlotIndex = slotIndex;
|
||||||
|
Debug.Log($"[GroundTarget] 타겟팅 모드 진입: 슬롯 {slotIndex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 지면 타겟팅 모드를 취소하고 일반 모드로 복귀합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void CancelGroundTargetMode()
|
||||||
|
{
|
||||||
|
if (currentTargetingMode != TargetingMode.GroundTarget)
|
||||||
|
return;
|
||||||
|
|
||||||
|
currentTargetingMode = TargetingMode.None;
|
||||||
|
pendingGroundTargetSlotIndex = -1;
|
||||||
|
Debug.Log("[GroundTarget] 타겟팅 모드 취소");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 카메라 화면 중앙에서 지면 방향으로 레이캐스트하여 지면 위치를 구합니다.
|
||||||
|
/// </summary>
|
||||||
|
private bool RaycastForGroundPosition(out Vector3 groundPosition)
|
||||||
|
{
|
||||||
|
groundPosition = default;
|
||||||
|
|
||||||
|
Camera mainCamera = Camera.main;
|
||||||
|
if (mainCamera == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[GroundTarget] Camera.main을 찾을 수 없습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groundTargetLayers.value == 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[GroundTarget] groundTargetLayers가 설정되지 않았습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ray ray = mainCamera.ScreenPointToRay(new Vector3(Screen.width * 0.5f, Screen.height * 0.5f, 0f));
|
||||||
|
|
||||||
|
if (!Physics.Raycast(ray, out RaycastHit hit, groundTargetMaxRange, groundTargetLayers))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
groundPosition = hit.point;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 지면 타겟팅 위치를 확정하고 스킬을 시전합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void ConfirmGroundTarget()
|
||||||
|
{
|
||||||
|
if (!RaycastForGroundPosition(out Vector3 groundPosition))
|
||||||
|
{
|
||||||
|
Debug.Log("[GroundTarget] 지면 위치를 탐지하지 못했습니다. 취소합니다.");
|
||||||
|
CancelGroundTargetMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int slotIndex = pendingGroundTargetSlotIndex;
|
||||||
|
|
||||||
|
// 타겟팅 모드 종료
|
||||||
|
currentTargetingMode = TargetingMode.None;
|
||||||
|
pendingGroundTargetSlotIndex = -1;
|
||||||
|
|
||||||
|
// 캐릭터를 타겟 방향으로 회전
|
||||||
|
Vector3 flatTargetPos = new Vector3(groundPosition.x, transform.position.y, groundPosition.z);
|
||||||
|
if ((flatTargetPos - transform.position).sqrMagnitude > 0.01f)
|
||||||
|
{
|
||||||
|
transform.rotation = Quaternion.LookRotation(flatTargetPos - transform.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 서버에 스킬 실행 요청
|
||||||
|
RequestSkillExecutionRpc(slotIndex, 0, groundPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (currentTargetingMode != TargetingMode.GroundTarget)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 좌클릭: 지면 타겟 확정
|
||||||
|
if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)
|
||||||
|
{
|
||||||
|
ConfirmGroundTarget();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 우클릭 또는 ESC: 타겟팅 취소
|
||||||
|
if ((Mouse.current != null && Mouse.current.rightButton.wasPressedThisFrame)
|
||||||
|
|| (Keyboard.current != null && Keyboard.current.escapeKey.wasPressedThisFrame))
|
||||||
|
{
|
||||||
|
CancelGroundTargetMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 아군 타게팅
|
#region 아군 타게팅
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user