[2025_11_19]아이템 효과 플레이어와 연동
2025. 11. 19. 21:57ㆍTIL
아이템 효과 시스템 구현 - 버프 관리와 스탯 복구
구현한 기능
아이템 시스템 구조
Item (충돌 감지)
↓
PlayerController.ApplyItemEffect()
↓
ItemEffectManager.ApplyItem()
↓
버프 활성화 + 스탯 적용
↓
ManagerRoot.Update() → itemEffectManager.Update()
↓
만료 시 원본 스탯 복구
핵심 구현 사항
1. 버프 중복 처리
csharp
private void ApplyBuff(ItemData itemData)
{
// 같은 타입의 버프가 이미 있는지 확인
ActiveItem existing = activeItems.Find(x => x.itemData.itemType == itemData.itemType);
if (existing != null)
{
// 이미 있으면 시간만 갱신
existing.remainingTime = itemData.duration;
existing.itemData = itemData;
}
else
{
// 새로 추가
ActiveItem buff = new ActiveItem { };
activeItems.Add(buff);
}
}
왜 이렇게?
- JumpUp 버프 중복 시 → 중첩 X, 시간만 연장
- 스택처럼 쌓이면 점프력이 너무 높아짐
2. 원본 스탯 저장 & 복구
csharp
// 게임 시작 시 원본 저장
public void SaveOriginalStats(float jumpPower, float maxHeight)
{
originalJumpPower = jumpPower;
originalMaxHeight = maxHeight;
}
// 버프 적용
private void ApplyJumpStats(ItemData itemData)
{
float bonus = GetValue(itemData, 0, 0);
player.SetJumpPower(originalJumpPower + bonus); // 원본 + 보너스
}
// 버프 만료 시 복구
private void RestoreJumpStats()
{
player.SetJumpPower(originalJumpPower); // 원본으로 되돌림
}
중요 포인트:
- 항상 원본 기준으로 계산 → 원본 + 보너스
- 만료 시 원본으로 복구 → 정확한 값 보장
3. 시간 기반 버프 관리
csharp
public void Update()
{
// 역순으로 순회 (RemoveAt 때문에)
for (int i = activeItems.Count - 1; i >= 0; i--)
{
activeItems[i].remainingTime -= Time.deltaTime;
if (activeItems[i].remainingTime <= 0)
{
OnBuffExpired(activeItems[i]); // 스탯 복구
activeItems.RemoveAt(i); // 리스트에서 제거
}
}
}
역순 순회 이유:
csharp
// 정순으로 하면 문제 발생
for (int i = 0; i < list.Count; i++)
{
list.RemoveAt(i); // i번째 제거
// 다음 요소들이 앞으로 당겨짐
// i+1은 이제 원래 i+2 요소
// 한 요소를 건너뜀!
}
// 역순이면 안전
for (int i = list.Count - 1; i >= 0; i--)
{
list.RemoveAt(i); // 뒤에서부터 제거
// 앞 요소들에 영향 없음
}
개선할 점
1. Item.cs - 초기화 순서 문제
현재 코드:
csharp
private void OnEnable()
{
ResetItem(); // meshRenderer 사용
}
private void Start()
{
itemData = ManagerRoot.Instance.dataManager.GetItemData(itemID); // 늦음
}
문제:
- OnEnable이 Start보다 먼저 실행됨
- itemData가 null인 상태로 로직 실행 가능
개선안:
csharp
private void Awake()
{
meshRenderer = GetComponent<MeshRenderer>();
itemCollider = GetComponent<Collider>();
// Awake에서 itemData 로드
itemData = ManagerRoot.Instance.dataManager.GetItemData(itemID);
audioManager = ManagerRoot.Instance.audioManager;
}
private void OnEnable()
{
ResetItem();
}
2. ManagerRoot의 Update 호출 - 현재 구조 분석
현재 코드:
csharp
public class ManagerRoot : Singleton<ManagerRoot>
{
public ItemEffectManager itemEffectManager;
protected override void Init()
{
itemEffectManager = new ItemEffectManager(); // 일반 클래스
}
private void Update()
{
itemEffectManager?.Update(); // 수동 호출
}
}
현재 방식의 장단점:
장점:
- ItemEffectManager가 MonoBehaviour에 의존하지 않음
- 단위 테스트가 쉬움 (GameObject 없이 테스트 가능)
- 생명주기를 ManagerRoot가 완전히 제어
단점:
- ManagerRoot가 여러 매니저의 Update를 호출하면 코드가 늘어남
개선안: 통합 Update 관리
csharp
public class ManagerRoot : Singleton<ManagerRoot>
{
private List<IUpdatable> updatableManagers = new();
protected override void Init()
{
itemEffectManager = new ItemEffectManager();
updatableManagers.Add(itemEffectManager);
// 다른 매니저도 추가 가능
// updatableManagers.Add(buffManager);
// updatableManagers.Add(effectManager);
}
private void Update()
{
foreach (var manager in updatableManagers)
{
manager.Update();
}
}
}
// 인터페이스 정의
public interface IUpdatable
{
void Update();
}
// ItemEffectManager에 적용
public class ItemEffectManager : IUpdatable
{
public void Update()
{
}
}
3. ItemData.value의 의미 불명확
현재:
csharp
public int[] value; // 뭘 의미하는지 알 수 없음!
// 사용할 때도 매직 넘버
float jumpPowerBonus = GetValue(itemData, 0, 0); // 0번째가 점프력?
float maxHeightBonus = GetValue(itemData, 1, 0); // 1번째가 높이?
개선안 : Enum으로 인덱스 명확화
csharp
public class ItemEffectManager
{
private enum JumpUpValueIndex
{
JumpPower = 0,
MaxHeight = 1
}
private enum MagnetValueIndex
{
Range = 0
}
private void ApplyJumpStats(ItemData itemData)
{
float jumpPowerBonus = GetValue(itemData, (int)JumpUpValueIndex.JumpPower, 0);
float maxHeightBonus = GetValue(itemData, (int)JumpUpValueIndex.MaxHeight, 0);
// 훨씬 명확함!
}
}
4. 매니저 초기화 개선
현재 ManagerRoot:
csharp
protected override void Init()
{
dataManager = new DataManager("Data/Item", "Items");
itemEffectManager = new ItemEffectManager();
gameManager = _gameManager;
gameManager?.Init();
dataManager?.Init();
}
개선안:
csharp
protected override void Init()
{
Debug.Log("ManagerRoot 초기화 시작");
try
{
// 1. 매니저 생성
dataManager = new DataManager("Data/Item", "Items");
itemEffectManager = new ItemEffectManager();
gameManager = _gameManager;
// 2. 의존성 순서대로 초기화
InitializeManager(dataManager, "DataManager");
InitializeManager(gameManager, "GameManager");
InitializeManager(scoreManager, "ScoreManager");
InitializeManager(sceneController, "SceneController");
InitializeManager(audioManager, "AudioManager");
Debug.Log("ManagerRoot 초기화 완료");
}
catch (Exception e)
{
Debug.LogError($"ManagerRoot 초기화 실패: {e.Message}");
}
}
private void InitializeManager<T>(T manager, string managerName) where T : class
{
if (manager == null)
{
Debug.LogWarning($"{managerName}가 null입니다.");
return;
}
// IInitializable 인터페이스가 있다면
if (manager is IInitializable initializable)
{
initializable.Init();
Debug.Log($"{managerName} 초기화 완료");
}
}
// 인터페이스 정의
public interface IInitializable
{
void Init();
}
잘한 점
1. 버프 중복 처리
- 같은 버프 여러 개 -> 시간만 갱신
- 스택되지 않아 밸런스 유지
2. 원본 스탯 복구
- 버프 만료 시 정확한 값으로 되돌림
- 다른 버프의 영향 없음
3. 확장 가능한 구조
csharp
// 새 아이템 추가가 쉬움
public enum ItemType
{
Coin,
JumpUp,
Magnet,
Shield, // 새로 추가
SpeedBoost // 새로 추가
}
// switch-case만 추가하면 됨
배운 점
1. List 순회 시 역순 주의
csharp
// RemoveAt 할 때는 반드시 역순!
for (int i = list.Count - 1; i >= 0; i--)
고민할 점
Q: 버프가 100개면 Update가 느리지 않을까?
- A: 아이템은 보통 3~5개 동시 활성화
- 필요하면 코루틴으로 변경
Q: 원본 스탯을 매번 저장할까, 한 번만 저장할까?
- 한 번만: 간단, 업그레이드 시 문제
- 매번: 복잡, 업그레이드 안전→ 버프 최초 적용 시에만 저장하는 방식 추천
->원본 스탯 변경 시 그때 다시 저장하는 방식
Q: value를 배열로 관리 vs 구조체?
- 배열: JSON 연동 쉬움, 의미 불명확
- 구조체: 명확, JSON 파싱 복잡
- ->어떻게 해야 할까...?
Q: ManagerRoot에서 Update를 직접 호출 vs 이벤트?
- 직접 호출: 간단, 명확, 성능 좋음
- 이벤트: 느슨한 결합, 확장성
- -> 매니저가 적으면 직접 호출, 많으면 인터페이스/이벤트
'TIL' 카테고리의 다른 글
| [2025_11_21] 프로젝트 마무리 (0) | 2025.11.21 |
|---|---|
| [2025_11_20]3d에서 캐릭터의 물리 문제 (0) | 2025.11.20 |
| [2025_11_18]ItemScriptableObject생성 및 Json과 연동 (0) | 2025.11.18 |
| [2025_11_17]장애물 충돌 관리(콜라이더 분리) (0) | 2025.11.17 |
| [2025_11_14]3d 러닝 게임 팀프로젝트 시작 (0) | 2025.11.14 |