본문 바로가기
게임개발/게임플랫폼 응용 프로그래밍

좀비 서바이버 (2) 총과 슈터

by pudding81 2024. 3. 7.

 

 

 

애니메이션 IK

 

왼손 IK 설정 

 //왼손의 위치와 회전을 총의 왼쪽 손잡이에 맞춤

 

대상의 위치와 회전의 가중치를 설정

원래위치0- 목표위치1
 playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
 playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f);

 

대상이 사용할 위치와 회전을 설정
 playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandMount.position);
 playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandMount.rotation);

 

 

총 데이터

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;

// 총을 구현
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() {
        // 사용할 컴포넌트의 참조 가져오기
        bulletLineRenderer = GetComponent<LineRenderer>();
        gunAudioPlayer = GetComponent<AudioSource>();

        //총에서 발사되는 방향을 나타내는 두지점
        bulletLineRenderer.positionCount = 2;
        bulletLineRenderer.enabled = false; //비활성화 
    }

    private void OnEnable() {
        // 총을 활성화 할때 총 상태 초기화
        ammoRemain = gunData.startAmmoRemain; //탄알양
        magAmmo = gunData.magCapacity;//탄창 가득 채우기

        //총을 활성화 
        state = State.Ready;
        //총을 쏜 시점을 초기화
        lastFireTime = 0;


    }

    // 발사 시도
    public void Fire() {

        if (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
        {
            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);

       //시작위치
        bulletLineRenderer.SetPosition(0,fireTransform.position);
       //끝점위치
        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;
    }

    
}

 

플레이어의 총 발사 / 재장전

using UnityEngine;

// 주어진 Gun 오브젝트를 쏘거나 재장전
// 알맞은 애니메이션을 재생하고 IK를 사용해 캐릭터 양손이 총에 위치하도록 조정
public class PlayerShooter : MonoBehaviour {
    public Gun gun; // 사용할 총
    public Transform gunPivot; // 총 배치의 기준점
    public Transform leftHandMount; // 총의 왼쪽 손잡이, 왼손이 위치할 지점
    public Transform rightHandMount; // 총의 오른쪽 손잡이, 오른손이 위치할 지점

    private PlayerInput playerInput; // 플레이어의 입력
    private Animator playerAnimator; // 애니메이터 컴포넌트

    private void Start() {
        // 사용할 컴포넌트들을 가져오기
        playerInput = GetComponent<PlayerInput>();
        playerAnimator = GetComponent<Animator>();
    }

    private void OnEnable() {
        // 슈터가 활성화될 때 총도 함께 활성화
        gun.gameObject.SetActive(true);
    }
    
    private void OnDisable() {
        // 슈터가 비활성화될 때 총도 함께 비활성화
        gun.gameObject.SetActive(false);
    }

    private void Update() {
        // 입력을 감지하고 총 발사하거나 재장전

        if (playerInput.fire)
        {
            gun.Fire();
        }
        else if(playerInput.reload)
        {
            if (gun.Reload())
            {
                //재장전 성공시에만
                playerAnimator.SetTrigger("Reload");
            }
        }

        //탄알 UI갱신
        UpdateUI();


    }

    // 탄약 UI 갱신
    private void UpdateUI() {
        if (gun != null && UIManager.instance != null)
        {
            // UI 매니저의 탄약 텍스트에 탄창의 탄약과 남은 전체 탄약을 표시
            UIManager.instance.UpdateAmmoText(gun.magAmmo, gun.ammoRemain);
        }
    }

    // 애니메이터의 IK 갱신
    private void OnAnimatorIK(int layerIndex) {

        //총의 기준점을 오른쪽 팔꿈치로
        gunPivot.position = playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow);

        //왼손의 위치와 회전을 총의 왼쪽 손잡이에 맞춤
        playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
        playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f);

        playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandMount.position);
        playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandMount.rotation);

        //오른손의 위치와 회전을 총의 오른쪽 손잡이에 맞춤

        playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f);
        playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f);

        playerAnimator.SetIKPosition(AvatarIKGoal.RightHand, rightHandMount.position);
        playerAnimator.SetIKRotation(AvatarIKGoal.RightHand, rightHandMount.rotation);


    }
}

'게임개발 > 게임플랫폼 응용 프로그래밍' 카테고리의 다른 글

OverlapSphere  (0) 2024.03.08
Navigation AI 이동하기  (0) 2024.03.08
인터페이스  (0) 2024.03.07
Mathf.Atan2  (2) 2024.03.06
Mixamo 2개 애니메이션 합치기  (0) 2024.03.06