ItemManager.cs에 있는 force와 coolTime의 값 때문에 문제가 많았다.
프로퍼티를 잘 몰랐어서 아래 수정하기 전에는 프로퍼티 없이 쓰면서 _player.force하며 하나하나 참조를 해가며 썼었다. 나는 상속만 받는다면 거기서 쓰는 force나 coolTime값이 다 같은 값을 참조하는줄 알고 착각했었기 때문이다.
그걸 몰랐던 나는 문제를 알게 된 이유가 Bounce와 GuidedMissile쪽을 만들어 _player.force값이 필요할 때 접근자 보호 때문에 사용을 할수가 없어서 이상하다고 느끼게 되었다. 분명 상속을 받는데? 하고 생각했는데..
그 이유때문에 튜터님에게 질문하고 프로퍼티에 대해서 듣고 적용해보며 오류가나고 잡다보니 프로퍼티에 대해서 느낌을 알게 되었다!
그냥 내가 값의 기준을 _player.force~coolTime으로 잡지 않고 아래처럼 처름부터 프로퍼티로 하여 값을 참조하고 변경했다면 훨씬 쉽고 더 코드가 간결해졌을텐데 아쉽다.
일단 아래처럼 내가 보기 편하고 관리하기 쉽게 해놨다. 다른 사람이 볼 때는 좀 더러워 보일수 있지만 일단 다른 할게 많기에 이렇게 해두고 차후에 시간이 남으면 코드들을 더 깔끔하게 이어지게 하고싶다.
- ItemManager에서 아이템의 타입(Enum)으로 관리
- force, coolTime같은 참조 및 변경 해야 하는 변수는 프로퍼티로 따로 운용
- 아이템을 먹으면 ItemManager에서 Player의 현재 Bullet상태를 바꿔줌.
public class ItemManager : MonoBehaviour
{
#region ItemTypeEnum
public enum ItemType
{
Normal,
BulletUPItem, // bullet 갯수 증가
PenetrateItem, // 관통
BounceItem, // 튕김
GuidedMissileItem // 유도탄
}
#endregion
#region Global_Variale
public PlayerAttackSystem _player;
// Basic Value
[SerializeField] protected float force = 5f;
[SerializeField] protected float coolTime = 1.0f;
public float Force { get { return force; } set { force = value; } }
public float CoolTime { get { return coolTime; } set { coolTime = value; } }
#endregion
private void Awake()
{
_player = FindObjectOfType<PlayerAttackSystem>();
}
#region Continuation_Item
protected void BulletDelayLower()
{
coolTime -= 0.05f;
}
protected void BulletSpeedUp()
{
force += 0.5f;
}
#endregion
#region OneTime_Item
protected void BulletCountUpItem()
{
BulletStateReset(_player);
_player.currentItem = ItemType.BulletUPItem;
}
protected void BulletPenetrateItem()
{
BulletStateReset(_player);
_player.currentItem = ItemType.PenetrateItem;
}
protected void BulletBounceItem()
{
BulletStateReset(_player);
_player.currentItem = ItemType.BounceItem;
}
protected void BulletGuidedMissileItem()
{
BulletStateReset(_player);
_player.currentItem = ItemType.GuidedMissileItem;
}
#endregion
private void BulletStateReset(PlayerAttackSystem player)
{
player.itemDuration = 0f;
player.itemDuration = 5;
}
}
- 'Space'입력을 받으면 이벤트 발동시 공격 실행
- currentItem상태에 따라 RecallBullet()에서 각각 다른 메서드 실행 ( 다른 종류의 공격 )
- currentItem상태에 따라 찍어내는 Prefab이 다름.
- Update()에서 아이템의 지속시간 설정.
- Coroutine을 이용하여 공격의 쿨타임 설정.
public class PlayerAttackSystem : ItemManager
{
#region Example
//// Coroutine Caching
//private IEnumerator ItemCoolTime;
//private WaitForFixedUpdate fixedUpdate = new WaitForFixedUpdate();
#endregion
#region Global_Variale
TopDownCharacterController _controller;
Rigidbody2D bulletRigid; // bullet Prefab Clone Rigidbody
GameObject bullet; // ( bullet는 player가 발사하는 총알 Prefab )
GameObject PenetrateItemBullet;
GameObject bounceBullet;
GameObject guidedMissileBullet;
private bool coolTimeCheck = true;
// ItemType Reset Value
public ItemType currentItem;
public float itemDuration = 0f;
#endregion
#region Initialization_Settings
private void Awake()
{
_controller = GetComponent<TopDownCharacterController>();
bullet = Resources.Load<GameObject>("Prefabs/Bullet");
PenetrateItemBullet = Resources.Load<GameObject>("Prefabs/PenetrateItemBullet");
bounceBullet = Resources.Load<GameObject>("Prefabs/BounceBullet");
guidedMissileBullet = Resources.Load<GameObject>("Prefabs/GuidedMissileBullet");
}
private void Start()
{
_controller.OnAttackEvent += Attack;
}
#endregion
#region Player_AttackLogic
private void Update()
{
BulletStateCheck();
}
private void Attack()
{
// CoolTime Check
if (coolTimeCheck == true) StartCoroutine(CollTime(CoolTime));
}
private void RecallBullet()
{
switch (currentItem)
{
case ItemType.Normal:
ItemNormalBullet();
break;
case ItemType.BulletUPItem:
ItemBulletCountUp();
break;
case ItemType.PenetrateItem:
ItemBulletPenetrateItem();
break;
case ItemType.BounceItem:
ItemBulletBounce();
break;
case ItemType.GuidedMissileItem:
ItemBulletGuidedMissile();
break;
default:
break;
}
}
private void ApplyAttck(GameObject obj)
{
bulletRigid = obj.GetComponent<Rigidbody2D>();
bulletRigid.AddForce(transform.up * Force, ForceMode2D.Impulse);
}
private IEnumerator CollTime(float time)
{
// CoolTime Setting
coolTimeCheck = false;
RecallBullet();
while (time > 0.0f)
{
time -= Time.deltaTime;
yield return new WaitForFixedUpdate();
}
// CoolTime Reset
coolTimeCheck = true;
}
#endregion
#region BulletState
private void ItemNormalBullet()
{
GameObject playerbullet = Instantiate(bullet);
playerbullet.transform.position = new Vector3(transform.position.x, transform.position.y + 0.4f, transform.position.z);
ApplyAttck(playerbullet);
}
private void ItemBulletCountUp()
{
GameObject playerBullet1 = Instantiate(bullet);
GameObject playerBullet2 = Instantiate(bullet);
playerBullet1.transform.position = new Vector3(transform.position.x - 0.2f, transform.position.y + 0.4f, transform.position.z);
playerBullet2.transform.position = new Vector3(transform.position.x + 0.2f, transform.position.y + 0.4f, transform.position.z);
ApplyAttck(playerBullet1);
ApplyAttck(playerBullet2);
}
private void ItemBulletPenetrateItem()
{
GameObject playerbullet = Instantiate(PenetrateItemBullet);
playerbullet.transform.position = new Vector3(transform.position.x, transform.position.y + 0.4f, transform.position.z);
ApplyAttck(playerbullet);
}
private void ItemBulletBounce()
{
GameObject playerbullet = Instantiate(bounceBullet);
playerbullet.transform.position = new Vector3(transform.position.x, transform.position.y + 0.4f, transform.position.z);
ApplyAttck(playerbullet);
}
private void ItemBulletGuidedMissile()
{
GameObject playerbullet = Instantiate(guidedMissileBullet);
playerbullet.transform.position = new Vector3(transform.position.x, transform.position.y + 0.4f, transform.position.z);
}
#endregion
private void BulletStateCheck()
{
if (currentItem != ItemType.Normal && itemDuration > 0f)
{
itemDuration -= Time.deltaTime;
}
if (itemDuration <= 0f && currentItem != ItemType.Normal)
{
itemDuration = 0f;
currentItem = ItemType.Normal;
}
}
}
- 예시) Bonce Item
- 아이템 스크립트 들은 ItemManager를 상속
- player의 currentItem타입을 바꿔주는 메서드와 파괴 처리만 들어가있다.
public class BulletBounceItem : ItemManager
{
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Player")
{
// Bullet Bounce
BulletBounceItem();
Destroy(gameObject); // 추후 오브젝트 풀링
}
}
}
- 플레이어가 Bullet을 발사하면 각도는 무조건 90도가 고정이다. 그래서 첫번째의 바운스는 랜덤으로 105~ 255도의 값중 한 방향으로 바운스하게 설정.
- 첫번째의 바운스 이후로는 반사각으로 바운스.
- 5번의 바운스 후 오브젝트 삭제.
public class BounceBulletPrefabLogic : ItemManager
{
const float _Radian = 180f;
bool firstWallCheck = true;
Rigidbody2D _rigid;
private int[] arrAngles = { 105, 135, 165, 195, 225, 255 }; // 225 : Right Bounce, 135 : Left
private int bulletLifeCount = 0;
private void Awake()
{
_rigid = GetComponent<Rigidbody2D>();
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.collider.CompareTag("TopWall"))
{
if (firstWallCheck == true)
{
// Random Angle Setting
int randomAngle = Random.Range(0, arrAngles.Length);
Vector3 tmps = transform.eulerAngles;
tmps.z = arrAngles[randomAngle];
transform.eulerAngles = tmps;
_rigid.AddForce(transform.up * Force, ForceMode2D.Impulse);
firstWallCheck = false;
}
else
{
// Bounce Angle Setting
Vector3 tmp = transform.eulerAngles;
tmp.z = _Radian - tmp.z;
transform.eulerAngles = tmp;
// Bounce After AddForce
_rigid.AddForce(transform.up * Force, ForceMode2D.Impulse);
}
}
else if (collision.collider.CompareTag("Wall"))
{
Vector3 tmp = transform.eulerAngles;
tmp.z = (_Radian * 2) - tmp.z;
transform.eulerAngles = tmp;
_rigid.AddForce(transform.up * Force, ForceMode2D.Impulse);
}
bulletLifeCount++;
// bullet Destory Condition ( 오브젝트 풀링 필요 )
if(bulletLifeCount >= 6)
{
Destroy(gameObject);
}
}
}
- Ball의 Tag를 가진 gameObject들을 Balls 리스트에 추가
- 비교값을 위해 Balls[0]과 bullet의 거리를 nearDis에 대입
- Null값을 대비해 LockOnTarget = Balls[0] 으로 설정.
- Balls를 foreach에 돌려 bullet과 거리를 비교
- 가장 가까운 오브젝트를 LockOnTarget에 할당.
- FixedUpdate()에서 MoveTowards를 사용하여 bullet이 LockOnTarget을 따라간다.
public class GuidedMissileBulletPrefabLogic : ItemManager
{
List<GameObject> Balls;
GameObject LockOnTarget;
float nearDis;
// LockOn Setting
private void Awake()
{
Balls = new List<GameObject>(GameObject.FindGameObjectsWithTag("Ball"));
nearDis = Vector3.Distance(gameObject.transform.position, Balls[0].transform.position);
}
// LockOn Search
private void Start()
{
// Null Value Ready
LockOnTarget = Balls[0];
// Search Near Distance Target
foreach (GameObject index in Balls)
{
float distance = Vector3.Distance(gameObject.transform.position, index.transform.position);
if (distance < nearDis)
{
LockOnTarget = index;
}
}
Debug.Log(LockOnTarget);
}
// LockOn Target Attack
private void FixedUpdate()
{
transform.position = Vector3.MoveTowards(transform.position, LockOnTarget.transform.position, Force * Time.deltaTime);
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.collider.CompareTag("Ball"))
{
Destroy(gameObject); // 추후 오브젝트 풀링
}
}
}