[Unity 강의] 뱀서라이크 강의 - ObjectManager, Controller(충돌, 데미지)
카테고리: Class VamSurLike
태그: C#, Unity, VamSurLike
ObjectManager
✔ 게임에 등장하는 물체들 관리 (Player, Monster, Projectile)
✔ 자료구조 어떤 걸로 오브젝트들을 관리할지
✔ ID를 string, int로 할지
ObjectManager
public class ObjectManager
{
public PlayerController Player { get; private set; }
public HashSet<MonsterController> Monster { get; } = new HashSet<MonsterController>();
public HashSet<ProjectileController> Projectile { get; } = new HashSet<ProjectileController>();
public T Spawn<T>(int templateID =0) where T : BaseController
{
System.Type type = typeof(T);
if (type == typeof(PlayerController))
{
GameObject go = Managers.Resource.Instantiate(PrefabsName.Player, pooling: true);
go.name = "Player";
PlayerController pc = go.GetOrAddComponent<PlayerController>();
Player = pc;
return pc as T;
}
else if(type == typeof(MonsterController))
{
string name = (templateID == 0 ? PrefabsName.Goblin : PrefabsName.Snake);
GameObject go = Managers.Resource.Instantiate(name, pooling : true);
MonsterController mc = go.GetOrAddComponent<MonsterController>();
Monster.Add(mc);
return mc as T;
}
return null;
}
public void Despawn<T>(T obj) where T : BaseController
{
System.Type type = typeof(T);
if (type == typeof(PlayerController))
{
//?
}
else if (type == typeof(MonsterController))
{
Monster.Remove(obj as MonsterController);
Managers.Resource.Destroy(obj.gameObject);
}
else if (type == typeof(ProjectileController))
{
Projectile.Remove(obj as ProjectileController);
Managers.Resource.Destroy(obj.gameObject);
}
}
}
GameScene
public class GameScene : MonoBehaviour
{
void StartLoaded()
{
var player = Managers.Object.Spawn<PlayerController>();
for (int i = 0; i < 10; i++)
{
MonsterController mc = Managers.Object.Spawn<MonsterController>(Random.Range(0,2));
mc.transform.position = new Vector2(Random.Range(-5, 5), Random.Range(-5, 5));
}
var joystick = Managers.Resource.Instantiate(PrefabsName.UI_Joystick);
joystick.name = "@UI_Joystick";
var map = Managers.Resource.Instantiate(PrefabsName.Map);
map.name = "@Map";
Camera.main.GetComponent<CameraController>().Target = player.gameObject;
}
}
충돌, 데미지
✔ 충돌 시 데미지 관련 코드 OnCollisionEnter2D
✔ 몬스터 플레이어 둘 다 공격을 할 수 있기에 CreatureConstroller에서 virtual OnDamage
✔ 데미지관리 코드는 주는 쪽에서 코드로 관리
✔ 상황에 맞게
플레이어가 1초마다 데미지를 받는다? (1초 무적) -> Player에
각 몬스터 별로 데미지를 1초마다 준다? (무적 x) -> Monster에
코루틴을 사용해서 1초 딜레이
프리팹 수정
✔ 프리팹에 collider2D rigidbody 추가, 수치 조정
✔ rigidbody 2d - Material - Physics Material 추가
충돌, 데미지 구조
1. 몬스터 OncollisionEnter2D에서 Player 인지 체크
2. 코루틴 실행 0.1초 간격 데미지, OncollisionExit2D로 멀어졌다면 코루틴 해제
3. Player가 데미지를 받았으면 Attacker에게 반격 10000
4. Monster 데미지 받음 -> Dead -> Managers.Object.Despawn(this);
Creature
public class CreatureController : BaseController
{
protected float _speed = 2.0f;
public int Hp { get; set; } = 100;
public int MaxHp { get; set; } = 100;
public virtual void OnDamaged(BaseController attacker, int damage)
{
Hp -= damage;
if (Hp <= 0)
{
Hp = 0;
OnDead();
}
}
protected virtual void OnDead()
{
}
}
Player
public class PlayerController : CreatureController
{
private void OnCollisionEnter2D(Collision2D collision)
{
MonsterController target = collision.gameObject.GetComponent<MonsterController>();
if (target == null)
return;
}
public override void OnDamaged(BaseController attacker, int damage)
{
base.OnDamaged(attacker, damage);
Debug.Log($"Ondamage ! {attacker} {Hp}");
// Temp 반격
CreatureController cc = attacker as CreatureController;
cc?.OnDamaged(this, 10000);
}
}
Monster
public class MonsterController : CreatureController
{
private void OnCollisionEnter2D(Collision2D collision)
{
PlayerController target = collision.gameObject.GetComponent<PlayerController>();
if (target == null)
return;
if (_coDotDamage != null) //
StopCoroutine(_coDotDamage);
_coDotDamage = StartCoroutine(CoStartDotDamage(target));
}
public void OnCollisionExit2D(Collision2D collision)
{
PlayerController target = collision.gameObject.GetComponent<PlayerController>();
if (target == null)
return;
if (_coDotDamage != null) // 기존에 뭔가 있는걸 대비
StopCoroutine(_coDotDamage);
_coDotDamage = null;
}
Coroutine _coDotDamage;
public IEnumerator CoStartDotDamage(PlayerController target)
{
while (true)
{
target.OnDamaged(this,2);
yield return new WaitForSeconds(0.1f);
}
}
protected override void OnDead()
{
base.OnDead();
if (_coDotDamage != null)
StopCoroutine(_coDotDamage);
_coDotDamage = null;
Managers.Object.Despawn(this);
}
}
이것저것 메모
init함수를 Awake대신 따로 만드는 이유
✔ Awake()에서 초기화하면 간결하고 라이프 사이클이 맞다. but
✔ Init()을 따로 만들고 Awake에서 호출?
✔ 초기화 시점을 정할 수 있다, 1번만 초기화할 수 있게.
✔ 상속받은 클래스에서 오버라이드 해서 추가해서 하면 좋다.
BaseController
public class BaseController : MonoBehaviour
{
void Awake()
{
Init();
}
bool _init = false;
public virtual bool Init()
{
if (_init)
{
return false;
}
_init = true;
return true;
}
}
클래스 다이어그램 확인
클래스 다이어그램 보기
✔ Controller 확인
obj as T
✔ obj 가 T로 변환 가능하면 변환하여 저장
✔ 불가능하면 null
가상함수 virtual
✔ 이 함수는 자식 클래스에서 재정의(Override)할 수 있다.
✔ 자식 클래스에서 필요에 따라 변경할 수 있도록 허용
몬스터 움직임
✔ Vector3 newPos = transform.position + dir.normalized * Time.DeltaTime * _speed; //fixedDeltaTime
✔ 1. transform.position = newPos;
✔ 2. GetComponent<Rigidbody2D>
().MovePosition(newPos);
✔ 1번 방식으로 하면 원하는 속도로 움직였다.
✔ 2번 방식으로 하면 원하는 속도보다 느리게 움직인다??
✔ MovePosition은 물리 업데이트 이기 때문에 차이가 생긴다.
해결법
1. Time.DeltaTime -> Time.FixedDeltaTime
1. GetComponent<Rigidbody2D>
().MovePosition(newPos); 을 Update말고 FixedUpdate에서
BaseController
void Update()
{
PlayerController pc = Managers.Object.Player;
if (pc == null)
return;
Vector3 dir = pc.transform.position - transform.position;
Vector3 newPos = transform.position + dir.normalized * Time.fixedDeltaTime * _speed; //fixedDeltaTime
//transform.position = newPos; //포지션으로 이동
GetComponent<Rigidbody2D>().MovePosition(newPos); //물리적인 이동
GetComponent<SpriteRenderer>().flipX = dir.x > 0;
}
잡담, 일기?
오브젝트 관리, 충돌 데미지
댓글남기기