레식 방식 재장전
IEnumerator ReloadRoutine()
{
this.State = eState.Reload; // 상태를 Reload로 변경
this.source.PlayOneShot(gunData.reloadClip); // 장전 사운드 재생
yield return this.reloadTime; // 장전 시간만큼 대기
int ammoToReload = Mathf.Min(gunData.magCapacity - this.magAmmo, ammo); // 최대 탄창 용량이 되기위해 필요한 총알 수 또는 남은 총알 수 중 작은 값
magAmmo += ammoToReload; // 탄창 내 총알 수 증가
ammo -= ammoToReload; // 남은 총알 수 감소
this.State = eState.Ready; // 상태를 Ready로 변경
}
이렇게하면 최대 탄창이 되기위해 남은 탄약만큼만 소모시키면서 재장전을 할 수 있고,
만약 남은 총알이 더 적다면 그것만 재장전 할 수도 있다
재장전 로직
public bool Reload()
{
if (this.State == eState.Reload || this.ammo <= 0 || this.magAmmo >= this.gunData.magCapacity)
return false;// 이미 장전 중이거나, 남은 총알이 없거나, 탄창이 가득 찼을 때는 장전 불가
StartCoroutine(ReloadRoutine()); // 장전 코루틴 시작
return true; // 장전 시작 성공
}
bool로 재장전 성공 여부를 반환시킴
else if (this.input.Reload)
{
if (this.gun.Reload())// 입력이 있으면 재장전
{
//this.animator.SetTrigger("Reload"); // 재장전 애니메이션 트리거 설정
}
else
{
//재장전을 못할 때 소리가 나거나, 그에 따른 알림 처리
}
}
bool값을 이용해서 재장전 실패했을때의 처리를 할 수 있다
private void Shot()
{
this.magAmmo--; // 탄창 내 총알 수 감소
if (this.magAmmo <= 0)
{
this.Reload(); // 탄창 내 총알이 0이 되면 장전 시작
return;
}
Shot쪽에서 자동 재장전 되는건 그냥 썼는데 여기서도 사용할 수 있다
OnAnimatorIK
private void OnAnimatorIK(int layerIndex)
{
//animator IK 를 사용하여 손 위치 조정
//애니메이터의 실시간 IK업데이트
this.gunPivot.position = this.animator.GetIKHintPosition(AvatarIKHint.RightElbow); // 오른손 위치에 총 회전 중심점 위치 설정
this.animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1f);
// 오른손 IK 위치 가중치 설정
this.animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1f);
// 오른손 IK 회전 가중치 설정
this.animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1f);
// 왼손 IK 위치 가중치 설정
this.animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1f);
// 왼손 IK 회전 가중치 설정
//가중치 설정 안하고 하면 손 위치가 이상해진다
this.animator.SetIKPosition(AvatarIKGoal.RightHand, this.rightHandleMount.position);
this.animator.SetIKPosition(AvatarIKGoal.LeftHand, this.leftHandleMount.position);
this.animator.SetIKRotation(AvatarIKGoal.RightHand, this.rightHandleMount.rotation);
this.animator.SetIKRotation(AvatarIKGoal.LeftHand, this.leftHandleMount.rotation);
}
Living Entity (생명체 기반 클래스)
특징
1. 체력이 있다
2. 체력 증감 가능(health pack 을 얻거나, 공격을 맞거나)
3. 살거나 죽거나 할 수 있다.
체력 UI






둥글게 깎이는 체력바
Living Entity 구현
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LivingEntity : MonoBehaviour,IDamageable
{
public int maxHp = 100; // 최대 체력
public int hp { get; protected set; } // 현재 체력
public bool isDead { get; protected set; } // 생존 여부
public Action DieAction; // 죽음 이벤트 액션
protected virtual void OnEnable()
{
this.isDead = false;
this.hp = this.maxHp;
}
public virtual void OnDamage(int damage, Vector3 hitPoint, Vector3 hitNormal)
{
//체력 있어야함
//체력이 0이 됐을때 Action 실행
this.hp -= damage; // 데미지 적용
if(this.hp <= 0&&!this.isDead)//아직 죽지 않은 상태에서 체력이 0 이하가 되었을때
{
Die();
}
}
public virtual void Die()
{
this.isDead = true; // 죽음 상태로 변경
this.DieAction();
this.gameObject.SetActive(false); // 오브젝트 비활성화
}
public virtual void RestoreHealth(int amount)
{
if (this.isDead)
{
return; // 이미 죽은 상태라면 체력 회복 불가
}
this.hp += amount; // 체력 회복
this.hp = Mathf.Min(this.hp, this.maxHp); // 최대 체력 초과 방지
}
}
virtual로 만들고, protected를 활용해서 상속된 다음 구현한다.
상속 예시
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class WomanHealth : LivingEntity
{
public Slider healthSlider; // 체력 슬라이더 UI
public AudioClip deathClip; // 죽는 소리
public AudioClip damageClip; // 데미지 받는 소리
public AudioClip itemGetClip; // 아이템 획득 소리
public AudioSource audioSource; // 오디오 소스
private WomanMovement movement; // 여성 캐릭터의 이동 스크립트
private WomanShooter shooter; // 여성 캐릭터의 슈팅 스크립트
private void Awake()
{
this.audioSource = GetComponent<AudioSource>();
this.movement = GetComponent<WomanMovement>();
this.shooter = GetComponent<WomanShooter>();
}
protected override void OnEnable()
{
base.OnEnable();
// 추가적인 초기화 작업이 필요하다면 여기에 작성
}
public override void OnDamage(int damage, Vector3 hitPoint, Vector3 hitNormal)
{
base.OnDamage(damage, hitPoint, hitNormal);
// 추가적인 데미지 처리 로직이 필요하다면 여기에 작성
}
public override void RestoreHealth(int amount)
{
base.RestoreHealth(amount);
// 추가적인 체력 회복 로직이 필요하다면 여기에 작성
}
public override void Die()
{
base.Die();
// 추가적인 죽음 처리 로직이 필요하다면 여기에 작성
// 예: 사운드 재생, 애니메이션 트리거 등
}
private void OnTriggerEnter(Collider other)
{
}
}
public class Zombie :LivingEntity
{
private NavMeshAgent agent; // 네비게이션 에이전트
private AudioSource source;
private Animator animator;
private MeshRenderer meshRenderer; // 메쉬 랜더러
//오디오소스, 네브메쉬, 콜라이더, 오디오클립, blood Effect, 랜더러
public LayerMask targetLayer; // 타겟 엔티티 레이어 마스크
private LivingEntity targetEntity; // 타겟 엔티티
public ParticleSystem hitEffect; // 피격 이팩트
public AudioClip hitClip; // 피격 소리
public AudioClip deathClip; // 죽음 소리
public int damage = 20;
public float timeBetweenAttack = 0.5f; // 공격 간격
private float lastAttackTime; // 마지막 공격 시간
private readonly int hashHasTarget = Animator.StringToHash("HasTarget"); // 애니메이터 해시
private readonly int hashDie = Animator.StringToHash("Die"); // 애니메이터 해시
//추적 대상이 있는지 알려주는 프로퍼티
private bool hasTarget
{
get
{
return this.targetEntity != null && !this.targetEntity.isDead;
}
}
void Awake()
{
this.source = GetComponent<AudioSource>();
this.agent = GetComponent<NavMeshAgent>();
this.animator = GetComponent<Animator>();
this.meshRenderer = GetComponent<MeshRenderer>();
}
public void Setup(ZombieData data) //좀비의 스펙을 결정하는 함수(이동속도, 체력 등)
{
this.maxHp = data.hp; // 좀비의 최대 체력 설정
this.hp = this.maxHp;
this.damage = data.damage; // 좀비의 공격력 설정
this.agent.speed = data.moveSpeed; // 네비게이션 에이전트의 이동 속도 설정
this.meshRenderer.material.color = data.skinColor; // 좀비의 색상 설정
}
private void Start()
{
StartCoroutine(this.UpdatePath()); // 경로 업데이트 코루틴 시작
}
void Update()
{
this.animator.SetBool(this.hashHasTarget,this.hasTarget);
}
private WaitForSeconds pathUpdateTime = new WaitForSeconds(0.25f);
IEnumerator UpdatePath()
{
while (!this.isDead)
{
if (hasTarget)
{
this.agent.isStopped = false;
this.agent.SetDestination(this.targetEntity.transform.position); // 타겟 엔티티의 위치로 경로 설정
}
else
{
this.agent.isStopped = true; // 타겟이 없으면 이동 중지
Collider[] cols = Physics.OverlapSphere(transform.position, 10f, this.targetLayer); // 주변의 타겟 엔티티 검색
for (int i = 0; i < cols.Length; i++)
{
LivingEntity entity = cols[i].GetComponent<LivingEntity>();
if (entity != null && !entity.isDead)
{
this.targetEntity = entity; // 타겟 엔티티 설정
break; // 첫 번째 타겟을 찾으면 루프 종료
}
}
}
yield return this.pathUpdateTime; // 0.25초마다 경로 업데이트
}
}
public override void OnDamage(int damage, Vector3 hitPoint, Vector3 hitNormal)
{
if (!isDead)
{
this.hitEffect.transform.position = hitPoint; // 피격 이펙트 위치 설정
this.hitEffect.transform.rotation = Quaternion.LookRotation(hitNormal); // 피격 이펙트 회전 설정
this.hitEffect.Play(); // 피격 이펙트 재생
this.source.PlayOneShot(this.hitClip); // 피격 소리 재생
}
base.OnDamage(damage, hitPoint, hitNormal);
}
public override void Die()
{
base.Die();
//자신의 모든 콜라이더 비활성화
Collider[] cols = GetComponents<Collider>(); // 콜라이더 가져오기
foreach (Collider col in cols)
{
col.enabled = false; // 콜라이더 비활성화
}
this.agent.isStopped = true; // 네비게이션 에이전트 정지
this.agent.enabled = false; // 네비게이션 에이전트 비활성화
this.source.PlayOneShot(this.deathClip); // 죽음 소리 재생
this.animator.SetTrigger(hashDie); // 죽음 애니메이션 트리거
}
public void OnTriggerStay(Collider other)
{
//트리거가 충돌한게 추적 대상이라면 공격 실행
if (!this.isDead && Time.time >= this.lastAttackTime + timeBetweenAttack)
{
LivingEntity entity = other.GetComponent<LivingEntity>();
if(entity != null && this.targetEntity == entity)
{
this.lastAttackTime = Time.time; // 마지막 공격 시간 갱신
Vector3 hitPos = other.ClosestPoint(this.transform.position);
//상대방 피격 위치와 피격 방향을 근갓값으로 계산
Vector3 hitNormal = (this.transform.position - other.transform.position);
// 공격되는 방향을 계산
entity.OnDamage(this.damage, hitPos, hitNormal.normalized); // 공격 실행
}
}
}
}'2025유니티수업' 카테고리의 다른 글
| 0709 유니티(헬기, ) (0) | 2025.07.09 |
|---|---|
| 0708 유니티(좀비 서바이벌 완성 , scope zoom) (0) | 2025.07.08 |
| 0704 깃허브(Branch 복습, Branch 동기화),유니티( Car(Cinemachine dolly cart 이용) ,Zombie Survival) (0) | 2025.07.04 |
| 0703 유니티(Car) (0) | 2025.07.03 |
| 0702 유니티(RPG 카메라 무빙) (0) | 2025.07.02 |