인터페이스를 사용하는 이유:

 

사용하지 않으면 다른 클래스의 타입을 모두 검사해야하기 때문에

즉, OnDamage()메서드를 실행하기만 하면 되서

 

 

 

얘네들은 원래 Gun안에 있어야 하지만.

 

이렇게 바꿔서 스크립터블 오브젝트로 동작할 수 있도록 상속

에셋을 생성하는 메뉴를 만들기 위해 특성을 클래스에 추가

CreateAssetMenu는 스크립터블 오브젝트 타입에 추가할 수 있다.

해당 타입의 에셋을 생성할 수 있는 버튼을 Assets와 Create(+버튼) 메뉴에 추가

"(경로)"  "(기본 파일명)", order =메뉴 상에서 순서

 

 

이번에 중점적으로 볼 것은 라인렌더이다. => Gun.cs

라인 렌더러 사이즈 2로 바꾼후 수치를 입력하면 좌표로 쏜다는 걸 알 수 있다.

 

 

 

this.bulletLineRenderer.enabled = false;

 

 

 

 

선을 그려서 총을 쏠때 방향을 확인해야 한다.

 

 

hit.normal => 노말을 쓴다는 건 애니메이션 방향을 정해주기 위해?

 

 

else 문은 타겟이 맞지 않았을 때 ex)허공에 쏠 때

탄알의 궤적을 잡아주려하기 위해

 

실행을 하면 Null 오류가 날 것인데 그러면 if로 조건을 넣어주면 된다.

using UnityEngine;

[CreateAssetMenu(menuName = "Scriptable/GunData", fileName = "Gun Data")]
public class GunData : ScriptableObject
{
    public AudioClip shotClip; // 발사 소리
    public AudioClip reloadClip; // 재장전 소리

    public float damage = 25; // 공격력

    public int startAmmoRemain = 100; // 처음에 주어질 전체 탄약
    public int magCapacity = 25; // 탄창 용량

    public float timeBetFire = 0.12f; // 총알 발사 간격
    public float reloadTime = 1.8f; // 재장전 소요 시간
}

 

using System.Collections;
using UnityEngine;
using UnityEngine.UIElements;

// 총을 구현
public class Gun : MonoBehaviour
{
    // 총의 상태를 표현하는 데 사용할 타입을 선언
    public enum State
    {
        Ready, // 발사 준비됨
        Empty, // 탄알집이 빔
        Reloading // 재장전 중
    }

    public State state { get; private set; } // 현재 총의 상태

    public Transform fireTransform; // 탄알이 발사될 위치

    public ParticleSystem muzzleFlashEffect; // 총구 화염 효과
    public ParticleSystem shellEjectEffect; // 탄피 배출 효과

    private LineRenderer bulletLineRenderer; // 탄알 궤적을 그리기 위한 렌더러

    private AudioSource gunAudioPlayer; // 총 소리 재생기

    public GunData gunData; // 총의 현재 데이터

    private float fireDistance = 50f; // 사정거리

    public int ammoRemain = 100; // 남은 전체 탄알
    public int magAmmo; // 현재 탄알집에 남아 있는 탄알

    private float lastFireTime; // 총을 마지막으로 발사한 시점

    private void Awake() {
        // 사용할 컴포넌트의 참조 가져오기
        gunAudioPlayer = GetComponent<AudioSource>();
        this.bulletLineRenderer = GetComponent<LineRenderer>();
        this.bulletLineRenderer.positionCount = 2;
        this.bulletLineRenderer.enabled = false;
    }

    private void OnEnable() {
        ammoRemain = gunData.startAmmoRemain;
        magAmmo = gunData.magCapacity;
        // 총 상태 초기화
        this.state = State.Ready;
        lastFireTime = 0;
    }

    // 발사 시도
    public void Fire() {
        if(this.state == State.Ready && Time.time >= lastFireTime + gunData.timeBetFire)
        {
            lastFireTime = Time.time;
            Shot();
        }
    }

    // 실제 발사 처리
    private void Shot() {
        RaycastHit hit;
        Vector3 hitPosition =Vector3.zero;
        //시작점, 방향, 히트정보, 거리
        if(Physics.Raycast(fireTransform.position, fireTransform.forward, out hit, fireDistance))
        {
            //충돌한 콜라이더의 게임오브젝트에 붙어 있는 IDamageable 인터페이스를 상속받고 있는 컴포넌트를 가져온다.
            IDamageable target = hit.collider.GetComponent<IDamageable>();
            if (target != null)
            {
                target.OnDamage(gunData.damage, hit.point, hit.normal);
            }
            hitPosition = hit.point;
        }
        else
        {
            //탄알이 최대 사정거리까지 날아갔을 때의 위치를 충돌 위치로 사용 => out hit가 빠진다.
            hitPosition = fireTransform.position + fireTransform.forward * fireDistance;
        }
        StartCoroutine(ShotEffect(hitPosition));
        magAmmo--;
        if(magAmmo <= 0)
        {
            state = State.Empty;
        }
    }

    // 발사 이펙트와 소리를 재생하고 탄알 궤적을 그림
    private IEnumerator ShotEffect(Vector3 hitPosition) {

        muzzleFlashEffect.Play();
        shellEjectEffect.Play();

        //총격 소리 재생
        gunAudioPlayer.PlayOneShot(gunData.shotClip);

        //라인랜더러의 시작 위치
        this.bulletLineRenderer.SetPosition(0, fireTransform.position);

        //라인랜더러의 끝 위치
        this.bulletLineRenderer.SetPosition(1, hitPosition);

        // 라인 렌더러를 활성화하여 탄알 궤적을 그림
        bulletLineRenderer.enabled = true;

        // 0.03초 동안 잠시 처리를 대기
        yield return new WaitForSeconds(0.03f);

        // 라인 렌더러를 비활성화하여 탄알 궤적을 지움
        bulletLineRenderer.enabled = false;
    }

    // 재장전 시도
    public bool Reload()
    {
        if (state == State.Reloading || ammoRemain <= 0 || magAmmo >= gunData.magCapacity)
        {
            return false;
        }
        StartCoroutine(ReloadRoutine());
        return true;
    }

    // 실제 재장전 처리를 진행
    private IEnumerator ReloadRoutine() {
        // 현재 상태를 재장전 중 상태로 전환
        state = State.Reloading;

        gunAudioPlayer.PlayOneShot(gunData.reloadClip);
        // 재장전 소요 시간 만큼 처리 쉬기
        yield return new WaitForSeconds(gunData.reloadTime);

        int ammoToFill = gunData.magCapacity - magAmmo;
        
        if(ammoRemain < ammoToFill)
        {
            ammoToFill = ammoRemain;
        }
        magAmmo += ammoToFill;
        ammoRemain -= ammoToFill;
        // 총의 현재 상태를 발사 준비된 상태로 변경
        state = State.Ready;
    }

    private void Update()
    {
        //test
        if(Input.GetMouseButtonDown(0))
        {
            this.Fire();
        }
    }

}

하이어라이키에 있는 프리팹을 Gun프리팹에 모두 할당시키기 위해 Apply All

 

 

 

'산대특 > 게임 클라이언트 프로그래밍' 카테고리의 다른 글

Sprite Shooter - Destroy Barrel and Layer  (0) 2024.03.13
Zombie - IK  (0) 2024.03.07
Tank - Rigidbody.MovePosition  (0) 2024.03.06
UniRun  (4) 2024.03.05
Quaternion  (1) 2024.03.04

+ Recent posts