좀짓막(좀비 집짓고 막기) [#5] - 목표물 타게팅/설치 기능 만들기
1. 목표물 타게팅 후 공격
이번 시간엔 터렛이 자동으로 목표물을 타게팅해서 총알을 발사하는 기능을 구현할 생각이다. 그걸 위해서 Physics.Overlapsphere를 사용하여 좀비의 콜라이더를 검출하고 콜라이더가 검출 될 경우, 가장 가까이 있는 좀비를 공격하는 알고리즘을 먼저 구현할 생각이다.
그걸 위해서 일단 콜라이더가 정상적으로 검출되는지 먼저 찍어 보았다.
그 후엔 포문을 돌려서 콜라이더가 있을 시 좀비와의 거리가 정상적으로 출력되는지 찍어 보았다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Turret : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Collider[] zombies = Physics.OverlapSphere(this.transform.position, 10f,1<<6);
if(zombies != null)
{
for(int i = 0; i < zombies.Length; i++)
{
float dis = Vector3.Distance(this.transform.position, zombies[i].transform.position);
Debug.Log(dis);
}
}
}
}
이제 Vector3.Distance를 이용해서 가장 가까운 거리에 있는 좀비를 터렛이 바라보도록 해보겠다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Turret : MonoBehaviour
{
Collider target;
float targetDis = Mathf.Infinity;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Collider[] zombies = Physics.OverlapSphere(this.transform.position, 10f,1<<6);
if(zombies != null)
{
for(int i = 0; i < zombies.Length; i++)
{
float dis = Vector3.Distance(this.transform.position, zombies[i].transform.position);
if(targetDis > dis)
{
targetDis = dis;
target = zombies[i];
this.transform.LookAt(target.transform);
Debug.Log(target);
}
}
targetDis = Mathf.Infinity;
}
}
}
이번엔 터렛이 좀비를 발견할 경우 총알을 발사하도록 설정할 생각이다.
터렛 게임오브젝트에 하위오브젝트로 FirePos를 만들고 위치를 터렛 총구의 앞쪽 부분에 배치하였다.
그 후 터렛의 FIrePos가 터렛의 회전을 따라가는지 확인해보았다.
이제 터렛에 불릿 게임오브젝트를 할당하고 좀비에게 총알을 쏘는 기능을 구현해보도록 하겠다.
bullet에는 Bullet스크립트를 달아서 생성되고 나서 자동으로 날아가도록 만들 생각이다.
잘 발사된다.
이제 공격하는 부분을 코루틴으로 만들어서 총알이 1초마다 1발씩 발사되도록 해보겠다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Turret : MonoBehaviour
{
[SerializeField]
private GameObject bullet;
[SerializeField]
private Transform firePos;
Collider target;
float targetDis = Mathf.Infinity;
bool isAttack = true;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
FindTarget();
StartCoroutine(Fire());
}
private void FindTarget()
{
Collider[] zombies = Physics.OverlapSphere(this.transform.position, 10f, 1 << 6);
if (zombies != null)
{
for (int i = 0; i < zombies.Length; i++)
{
float dis = Vector3.Distance(this.transform.position, zombies[i].transform.position);
if (targetDis > dis)
{
targetDis = dis;
target = zombies[i];
this.transform.LookAt(target.transform);
}
}
targetDis = Mathf.Infinity;
}
}
private IEnumerator Fire()
{
if(isAttack == true)
{
Instantiate(bullet, this.firePos.position, this.firePos.rotation);
isAttack = false;
yield return new WaitForSeconds(1f);
isAttack = true;
}
}
}
이제 총알이 벽이나 좀비에 닿을 경우 Destroy함수를 이용해서 자기 자신을 파괴하도록 할 생각이다. 덤으로 총알에 트레일 이펙트도 붙여 주겠다.
일단 총알이 벽이나 적에게 충돌할 경우 Debug가 잘 출력되는지 확인해보았다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
[SerializeField]
private float force = 1500f;
private Rigidbody rb;
// Start is called before the first frame update
void Start()
{
this.rb = GetComponent<Rigidbody>();
this.rb.AddForce(transform.forward * force);
}
private void OnTriggerEnter(Collider other)
{
if(other.tag == "Enemy")
{
Debug.Log("Zombie");
}
else if(other.tag == "Wall")
{
Debug.Log("Wall");
}
}
}
잘 작동한다 지금은 총알이 벽이나 적에게 닿을 경우 파괴되도록 할 생각이지만 나중에 오브젝트 풀링을 활용해서 불릿을 비활성화한 후 재사용하는 방향으로 변경할 예정이다.
근데 하다보니 터렛에 문제점이 있는걸 발견했다. 터렛이 타겟이 없음에도 총알을 자동으로 발사했는데, 이 부분은 콜라이더 검출 함수가 null일때 타겟을 찾는 것이 아니라 검출함수의 Length가 1이상일때 타겟을 찾도록 변경해서 해결 했다.
private void FindTarget()
{
Collider[] zombies = Physics.OverlapSphere(this.transform.position, 10f, 1 << 6);
if (zombies.Length >= 1)
{
for (int i = 0; i < zombies.Length; i++)
{
float dis = Vector3.Distance(this.transform.position, zombies[i].transform.position);
if (targetDis > dis)
{
targetDis = dis;
target = zombies[i];
this.transform.LookAt(target.transform);
}
}
StartCoroutine(Fire());
targetDis = Mathf.Infinity;
}
Debug.Log(zombies.Length);
}
이제 좀비에 hp를 주고 게임매니저로 생성한 좀비를 터렛이 공격하면 좀비가 불릿에 맞을 경우 hp가 감소, hp가 0이하가 되면 파괴되도록 하겠다.
private void OnTriggerEnter(Collider other)
{
if(other.tag == "Bullet")
{
onAttacked();
}
}
public void TakeDamage(int damage)
{
this.hp -= damage;
if(hp <= 0)
{
Destroy(this.gameObject);
}
}
zombie.onAttacked = () =>
{
zombie.TakeDamage(turret.GetDamage());
};
public int GetDamage()
{
return this.damage;
}
좀비에 onAttacked 액션함수와 TakeDamage메서드를 정의하고 GameManager에서 Turret으로부터 데미지를 받아 TakeDamage에 인수로 전달해 준다음 onAttacked가 실행될때 TakeDamage가 실행되도록 하였다.
2. 설치 기능 구현
이번에는 아이콘을 드래그해서 각종 오브젝트를 설치할 수 있도록 만들 생각이다
수정하기 전에 간단하게 UI를 만들어 줬다.
그리고 AGrid 스크립트에 SetFulled와 SetEmpty를 만들고 게임매니저의 Set메서드에 넣어서 맵을 수정할때 같이 수정하도록 만들어 주려고 했다. 근데 널레퍼런스 에러가 나면서 스크립트가 실행되지 않았다. 최근에 이러한 문제를 많이 겪어봐서 직관적으로 스크립트가 호출되는 순서 문제일거라 생각했다 그래서 AGrid의 Set메서드에 접근할때 구조물을 설치할때만 접근하도록 스크립트를 바꿔 주었다.
구조물 설치가 A* 알고리즘에 잘 반영되었다.