새소식

Unity/PROJECT_S

[Unity] PROJECT_S / 23.09.11

  • -

오늘은 디펜스 시스템인 센트리 건을 구현하려 한다.

 

센트리 건 모델을 직접 만들어서 프로젝트 진행이 지체됐다.

 

먼저 플레이어의 총기와 센트리 건의 발사 디테일을 위해 총기 반동을 다시 구현했다.

(총알 생성 방식을 오브젝트 풀링 방식으로 수정해서 기존 총기 반동 코드는 삭제했다.)

 

Procedural Recoil System | Unity Tutorial

총기 반동 시스템을 디테일하게 구현하는 방법이 있어 참고했다.

 

Recoil.cs

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class Recoil : MonoBehaviour
{
    Vector3 currentRotation;    // 현재 회전값을 저장하는 변수
    Vector3 targetRotation;     // 목표 회전값을 저장하는 변수

    // X, Y, Z 축에 대한 반동의 세기를 조절하는 변수
    [SerializeField] float recoilX;
    [SerializeField] float recoilY;
    [SerializeField] float recoilZ;

    // 회전 보간 속도를 조절하는 변수
    [SerializeField] float snappiness;
    // 회전값을 원래 상태로 되돌리는 속도를 조절하는 변수
    [SerializeField] float returnSpeed; 

    void Update()
    {
        // 목표 회전값을 점진적으로 0으로 수렴시킴
        targetRotation = Vector3.Lerp(targetRotation, Vector3.zero, returnSpeed * Time.deltaTime);
        // 현재 회전값을 목표 회전값으로 점진적으로 보간
        currentRotation = Vector3.Slerp(currentRotation, targetRotation, snappiness * Time.fixedDeltaTime);
        transform.localRotation = Quaternion.Euler(currentRotation);
    }

    // 총기 발사 시 호출되는 메서드
    public void RecoilFire()
    {
        // 무작위로 X, Y, Z 축에 대한 반동을 적용
        targetRotation += new Vector3(recoilX, Random.Range(-recoilY, recoilY), Random.Range(-recoilZ, recoilZ));
    }
}

(새로 작성한 스크립트)

 

총알이 생성할 때 총알에 회전을 적용하는 것이 아니라 총기 자체나 총알 생성 지점에 회전을 적용한다.

 

탑뷰이므로 Y축만 회전하도록 했다.

총기 자체에 반동을 적용하여 총기가 회전하면서 총알을 발사한다.

 

블렌더로 제작한 센트리 건 3D 모델(기관총 모델 제외)

현대적인 느낌을 주기 위해 실제 기관총 모델을 사용했다.

 

터렛이 적을 향해 회전하고 일정거리에 도달하면 총알이 발사하도록 구현할 것이다.

 

Sentry_MG.cs

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class Sentry_MG : MonoBehaviour
{
    public float scanRange;             // 감지 범위
    public LayerMask targetLayer;       // 감지할 대상의 레이어 마스크
    public RaycastHit[] targets;        // 감지된 대상 배열
    public Transform nearestTarget;     // 가장 가까운 대상의 Transform

    public Transform turret;            // 터렛(Transform)의 회전 대상
    public Transform muzzlePoint;       // 총구 위치

    float nextFireTime;                  // 다음 발사 시간
    float fireRate = 0.1f;              // 발사 속도

    Recoil recoil;                      // 반동 관리용 스크립트

    float rayLength = 25f;              // 레이캐스트의 길이

    RaycastHit hit;                     // 레이캐스트 결과를 저장할 변수

    void Awake()
    {
        recoil = muzzlePoint.GetComponent<Recoil>();
    }

    void Update()
    {
        Debug.DrawRay(muzzlePoint.transform.position, muzzlePoint.forward * rayLength, Color.green);

        // 총구 위치의 전방으로 rayLength 만큼 광선을 쏴서 감지될 때
        if (Physics.Raycast(muzzlePoint.transform.position, muzzlePoint.transform.forward, out hit, rayLength))
        {
            // 연사속도 구현
            if (Time.time >= nextFireTime)
            {
                Fire(); // 발사 함수 호출
                nextFireTime = Time.time + fireRate;
            }
        }
    }

    void FixedUpdate()
    {
        // 감지 범위 내의 모든 대상을 감지
        targets = Physics.SphereCastAll(transform.position, scanRange, Vector3.forward, 0, targetLayer);
        // 가장 가까운 대상을 가져옴
        nearestTarget = GetNearest();

        if (nearestTarget != null)
        {
            // 대상을 향하는 방향 계산
            Vector3 targetDirection = nearestTarget.position - turret.position;
            targetDirection.y = 0;

            // 대상을 향하는 회전 각도 계산
            Quaternion targetRotation = Quaternion.LookRotation(targetDirection);

            // 터렛을 대상 방향으로 회전시킴
            turret.rotation = Quaternion.RotateTowards(turret.rotation, targetRotation, Time.deltaTime * 50);
        }
    }

    Transform GetNearest()
    {
        Transform result = null;
        float diff = 100;

        // 감지된 대상 중에서 가장 가까운 대상을 찾음
        foreach (RaycastHit target in targets)
        {
            Vector3 myPos = transform.position;
            Vector3 targetPos = target.transform.position;
            float curDiff = Vector3.Distance(myPos, targetPos);

            if (curDiff < diff)
            {
                diff = curDiff;
                result = target.transform;
            }
        }
        return result;  // 가장 가까운 대상의 Transform 반환
    }

    void Fire()
    {
        recoil.RecoilFire();    // 발사 시 반동 적용

        // 총알 객체 생성
        Transform bullet = GameManager.Instance.pool.Get(1).transform;
        bullet.position = muzzlePoint.position;
        bullet.rotation = muzzlePoint.rotation;

        Rigidbody rb = bullet.GetComponent<Rigidbody>();
        rb.velocity = muzzlePoint.forward * 100f;
    }

    void OnDrawGizmos() // 감지 범위를 시각화
    {
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, scanRange);
    }
}

(센트리 건 AI 코드)

 

적들만 구분하기 위해 [Enemy] 레이어를 추가하여 적 프리팹의 레이어를 변경한다.

 

센트리 건은 총알 생성 위치에 [Recoil] 스크립트를 적용했다.

 

스크립트의 로직은 다음과 같다.

 

1. 구체 범위 안에 적이 들어오면 가장 가까운 적을 바라본다.

 

2. 범위 안에 들어오면 레이캐스트되어 발사 함수를 호출한다.

 

시각화된 감지 범위

노란색은 회전을 위한 감지 범위이고, 초록색은 발사를 위한 감지 범위이다.

 

최종 결과

그럴싸한 디펜스 시스템이 완성됐다.

 

- 23.09.20 추가 -

 

이제 졸업작품을 준비하여 더 이상 진행하기 어려울 것 같다.

 

아쉽지만 여기서 프로젝트를 끝마친다.

'Unity > PROJECT_S' 카테고리의 다른 글

[Unity] PROJECT_S / 23-08-31  (0) 2023.08.31
[Unity] PROJECT_S / 23-08-22  (0) 2023.08.22
[Unity] PROJECT_S / 23-08-21  (0) 2023.08.21
[Unity] PROJECT_S / 23-08-18  (0) 2023.08.18
[Unity] PROJECT_S / 23-08-16  (0) 2023.08.17
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.