애니메이션 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 |