Unity/Unity FPS게임 프로젝트(오버워치라이크)

[Unity 게임프로젝트] FPS 게임<오버워치>(4) - 적 봇 생성, 총 슈팅 기능, 재장전 모드 구현

ForMan_ 2024. 5. 22. 15:43

1. 적 봇 생성

 

오버워치 훈련장처럼 적 봇 유닛을 생성해주었습니다.

일정 Hp를 가지고 있고, Hp가 0이 되면 사라지는 기능을 구현했습니다.

그리고 일정시간 뒤에 해당 리스폰 포지션에서 다시 살아나는 기능도 구현했습니다.

 

일단, 적 봇 유닛을 만들어줍니다.

 

[Enemy.cs]

public class Enemy : MonoBehaviour
{
	Rigidbody rigidEnemy;
    [SerializeField]
    // 해당 적 봇의 원래 포지션.
    public Vector3 originPos;

    // 체력.
    public float maxHp;
    public float currHp;
    public float moveSpeed;

    public bool isMoveRight;
    
    void Start()
    {
        rigidEnemy = GetComponent<Rigidbody>();
        currHp = maxHp;
        isMoveRight = true;
    }
    void Update()
    {
    	// 필드에 존재하면 움직이라는 함수.
        if(gameObject.activeSelf == true)
        {
            Move();
        }
    }
    void Move()
    {
        // 필드에서 임의로 설정한 이동구간.
        if (transform.position.x > 8)
            isMoveRight = false;
        else if (transform.position.x < -15)
            isMoveRight = true;

        if (isMoveRight == true)
            transform.Translate(moveSpeed * Vector3.right * Time.deltaTime, Space.World);
        else
            transform.Translate(-moveSpeed * Vector3.right * Time.deltaTime, Space.World);
    }
    // 데미지를 입었을 때 호출되는 함수.
    public void Damaged(float _damage)
    {
        currHp -= _damage;

        if(currHp <= 0)
        {
            gameObject.SetActive(false);
            // 킬로그 표시.
            UIManager.m.ActiveEvent_ImgHitEnemy("Kill");
        }
    }
    // 다시 생성될 때 호출되는 초기화 함수.
    public void Init()
    {
        currHp = maxHp;
        isMoveRight = true;
    }
}

 

[EnemySpawner.cs]

public class EnemySpawner : MonoBehaviour
{
    [SerializeField]
    Enemy enemy;

    public float coolTime = 2f;


    void Update()
    {
    	// 적 봇 유닛이 디액티브되면 사망으로 판정.
        if(enemy.gameObject.activeSelf == false)
        {
            ReCreateEnemy();
        }
    }
    
    // 적 봇 사망 후 일정 시간 후 재생성되는 함수.
    void ReCreateEnemy()
    {
        coolTime -= Time.deltaTime;

        // 쿨타임 후 포지션, 체력, 쿨타임 초기화한 다음 재생성.
        if(coolTime <= 0)
        {
            enemy.gameObject.transform.position = enemy.originPos;
            enemy.Init();
            coolTime = 2f;
            enemy.gameObject.SetActive(true);
        }
    }
}

2. 총 슈팅

솔져는 히트스캔이기 때문에 총을 쏘면 바로 적이 피격되는 기능을 구현했습니다.

(Raycast 활용)

 

[AimController.cs]

public class AimController : MonoBehaviour
{
    // 총알 피격 이펙트.
    public GameObject bulletEffect;

    // 유닛카메라.
    [Header("Camera")]
    public Camera theCamera;

    [Header("Fire Stat")]
    public float weaponRange = 100f;
    public float damage = 20f;
    
    [Header("Ray")]
    // 레이저를 쐈을 때 맞은 대상.
    RaycastHit hit;
    
    public void SetAim()
    {
    	// Physics,Raycast매서드 사용.
        // (현재 카메라의 포지션에서, 카메라 앞방향(z값)으로 발사, 대상, 레이저 길이)
        if(Physics.Raycast(theCamera.transform.position, theCamera.transform.forward, out hit, weaponRange))
        {
        	// 헤드 판정.
            if (hit.collider.tag == "Head")
            {
                Enemy enemy = hit.transform.GetComponent<Enemy>();

                if (enemy != null)
                {
                	// 조준선에 헤드를 맞췄다는 이미지 생성.
                    UIManager.m.ActiveEvent_ImgHitEnemy("Head");
                    // 적에게 데미지를 주는 함수.
                    enemy.Damaged(damage*2);
                }

            }
            // 몸샷 판정.
            else if(hit.collider.tag == "Body")
            {
                Enemy enemy = hit.transform.GetComponent<Enemy>();

                if (enemy != null)
                {
                	// 조준선에 몸샷을 했다는 이미지 생성.
                    UIManager.m.ActiveEvent_ImgHitEnemy("Body");
                    enemy.Damaged(damage);
                }
            }
            // 적이 아닌 벽 등 다른 오브젝트일 때는 해당 지점에 총알이 박힌 듯한 bulletEffect생성.
            else
            {
                GameObject effect = Instantiate(bulletEffect, hit.point, Quaternion.LookRotation(hit.normal));

                Destroy(effect, 0.2f);
            }
        }
    }
}

 

[UnitController.cs]

public class UnitController : MonoBehaviour
{
    AimController aimController;
    
    public ParticleSystem muzzleFlash;
    
    [Header("Shoot")]
    // 슈팅 딜레이 속도.
    public float shootRate;
    public float shootDelayTime;
    private float maxBullet = 30;
    public float currBullet;
    
    void Start()
    {
        rigidUnit = GetComponent<Rigidbody>();
        unitAnimController = GetComponent<UnitAnimationController>();
        capsuleCollider = GetComponent<CapsuleCollider>();
        
        aimController = GetComponent<AimController>();
    }
    void Update()
    {
        Crouch();            // 앉기.
        IsGround();          // 땅에 닿았는지 판별.
        Jump();              // 점프.
        Move();              // 기본 이동.
        UnitRotation();      // 유닛 좌우 회전.
        CameraRotation();    // 카메라 회전
        
        Shoot();             // 좌클릭 - 기본공격
    }
    
    void Shoot()
    {
        shootDelayTime += Time.deltaTime;
		
        // 마우스 좌클릭을 눌렀을 때 슈팅 딜레이 타임이 슈팅속도보다 크고 총알이 있다면,
        // 레이저 발사. 총구 화염 이펙트 플레이. 슈팅사운드 플레이.
        // 슈팅할 때마다 딜레이타임 초기화. 총알 감소. 총알 텍스트 변경.
        if (Input.GetMouseButton(0) && shootDelayTime > shootRate && currBullet > 0)
        {
            aimController.SetAim();
            muzzleFlash.Play();
            AudioManager.m.audioSoldierFire.Play();

            shootDelayTime = 0;
            currBullet--;
            UIManager.m.CountBullet(currBullet, maxBullet);
        }
    }
}

3. 재장전

키보드 R버튼을 누르거나 현재 총알이 0이면 자동으로 장전하는 기능을 구현했습니다.

 

 [UnitController.cs]

public class UnitController : MonoBehaviour
{
    AimController aimController;
    
    [Header("Effect")]
    public ParticleSystem muzzleFlash;
    
    [Header("Shoot")]
    public float shootRate;
    public float shootDelayTime;
    
    // 최대, 현재 총알개수.
    private float maxBullet = 30;
    public float currBullet;
    
    void Start()
    {
    	rigidUnit = GetComponent<Rigidbody>();
        unitAnimController = GetComponent<UnitAnimationController>();
        capsuleCollider = GetComponent<CapsuleCollider>();
        aimController = GetComponent<AimController>();
        
        currSpeed = walkSpeed;
        originPosY = myCamera.transform.localPosition.y;
        currCrouchPosY = originPosY;
        
        // 현재 총알 = 최대 총알로 시작.
        currBullet = maxBullet;
        // UI 총알 텍스트 변경.
        UIManager.m.CountBullet(currBullet, maxBullet);
    }
    void Update()
    {
        Crouch();            // 앉기.
        IsGround();          // 땅에 닿았는지 판별.
        Jump();              // 점프.
        Move();              // 기본 이동.
        UnitRotation();      // 유닛 좌우 회전.
        CameraRotation();    // 카메라 회전
        
        Shoot();             // 좌클릭 - 기본공격
        Reload();            // 장전.
    }
    
	void Shoot()
    {
    	// 장전 중일 땐 슈팅 불가.
        if (isReloading == true)
            return;

        shootDelayTime += Time.deltaTime;

        if (Input.GetMouseButton(0) && shootDelayTime > shootRate && currBullet > 0)
        {
            aimController.SetAim();
            muzzleFlash.Play();
            AudioManager.m.audioSoldierFire.Play();

            shootDelayTime = 0;
            currBullet--;
            UIManager.m.CountBullet(currBullet, maxBullet);
        }
    }
    // 재장전 함수.
    void Reload()
    {
    	// 키보드 R키를 누르고 현재 총알이 최대 총알과 다르다면 또는 현재총알이 없다면
		// 장전 중 모드로 변경, 장전 애니메이션 트루.
        if ((Input.GetKeyDown(KeyCode.R) && currBullet != maxBullet) || currBullet <= 0)
        {
            isReloading = true;
            unitAnimController.isReload(true);

            StartCoroutine("ReloadRoutine");
        }
    }
    // 1.5초 뒤에 현재 총알 최대로 만들고, 총알 텍스트 변경, 장전 중 모드 해제.
    IEnumerator ReloadRoutine()
    {
        yield return new WaitForSeconds(1.5f);

        currBullet = maxBullet;
        UIManager.m.CountBullet(currBullet, maxBullet);
        unitAnimController.isReload(false);
        isReloading = false;
    }
}

 

[Result]

지금 아바타 애니메이션 조정을 못해서 총구의 위치가 이상합니다.

그래도 게임시작하면 적 봇 유닛이 움직이고, 마우스 좌클릭을 누르면 슈팅이 되고, R키를 누르면 장전이 됩니다.


https://www.youtube.com/watch?v=mHV31Uj_PsM