using System.Collections;
using UnityEngine;
using UnityEngine.AI; // AI, 내비게이션 시스템 관련 코드를 가져오기
// 적 AI를 구현한다
public class Enemy : LivingEntity
{
public LayerMask whatIsTarget; // 추적 대상 레이어
private LivingEntity targetEntity; // 추적할 대상
private NavMeshAgent pathFinder; // 경로계산 AI 에이전트
public ParticleSystem hitEffect; // 피격시 재생할 파티클 효과
public AudioClip deathSound; // 사망시 재생할 소리
public AudioClip hitSound; // 피격시 재생할 소리
private Animator enemyAnimator; // 애니메이터 컴포넌트
private AudioSource enemyAudioPlayer; // 오디오 소스 컴포넌트
private Renderer enemyRenderer; // 렌더러 컴포넌트
public float damage = 20f; // 공격력
public float timeBetAttack = 0.5f; // 공격 간격
private float lastAttackTime; // 마지막 공격 시점
// 추적할 대상이 존재하는지 알려주는 프로퍼티
private bool hasTarget
{
get
{
// 추적할 대상이 존재하고, 대상이 사망하지 않았다면 true
if (targetEntity != null && !targetEntity.dead)
{
return true;
}
// 그렇지 않다면 false
return false;
}
}
private void Awake()
{
// 초기화
pathFinder = GetComponent<NavMeshAgent>();
enemyAnimator = GetComponent<Animator>();
enemyAudioPlayer = GetComponent<AudioSource>();
enemyRenderer = GetComponent<Renderer>();
}
// 적 AI의 초기 스펙을 결정하는 셋업 메서드
public void Setup(float newHealth, float newDamage, float newSpeed, Color skinColor)
{
startingHealth = newHealth;
damage = newDamage;
pathFinder.speed = newSpeed;
enemyRenderer.material.color = skinColor;
}
private void Start()
{
// 게임 오브젝트 활성화와 동시에 AI의 추적 루틴 시작
StartCoroutine(UpdatePath());
}
private void Update()
{
// 추적 대상의 존재 여부에 따라 다른 애니메이션을 재생
enemyAnimator.SetBool("HasTarget", hasTarget);
}
// 주기적으로 추적할 대상의 위치를 찾아 경로를 갱신
private IEnumerator UpdatePath()
{
// 살아있는 동안 무한 루프
while (!dead)
{
if(hasTarget)
{
pathFinder.isStopped = false;
pathFinder.SetDestination(targetEntity.transform.position);
}
else
{
pathFinder.isStopped = true;
Collider[] colliders =
Physics.OverlapSphere(transform.position, 20f, whatIsTarget);
for(int i = 0; i < colliders.Length; i++)
{
LivingEntity livingEntity =
colliders[i].GetComponent<LivingEntity>();
if(livingEntity != null && !livingEntity.dead)
{
targetEntity = livingEntity;
break;
}
}
}
// 0.25초 주기로 처리 반복
yield return new WaitForSeconds(0.25f);
}
}
// 데미지를 입었을때 실행할 처리
public override void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal)
{
hitEffect.transform.position = hitPoint;
hitEffect.transform.rotation = Quaternion.LookRotation(hitNormal);
hitEffect.Play();
enemyAudioPlayer.Play();
// LivingEntity의 OnDamage()를 실행하여 데미지 적용
base.OnDamage(damage, hitPoint, hitNormal);
}
// 사망 처리
public override void Die()
{
// LivingEntity의 Die()를 실행하여 기본 사망 처리 실행
base.Die();
Collider[] enemyColliders = GetComponents<Collider>();
foreach(Collider collider in enemyColliders)
{
collider.enabled = false;
}
pathFinder.isStopped = true;
pathFinder.enabled = false;
}
private void OnTriggerStay(Collider other)
{
// 트리거 충돌한 상대방 게임 오브젝트가 추적 대상이라면 공격 실행
if(!dead && Time.time >= lastAttackTime + timeBetAttack)
{
LivingEntity attackTarget = other.GetComponent<LivingEntity>();
if(attackTarget != null &&
attackTarget == targetEntity)
{
lastAttackTime = Time.time;
Vector3 hitPoint =
other.ClosestPoint(transform.position);
Vector3 hitNormal =
transform.position - other.transform.position;
attackTarget.OnDamage(damage, hitPoint, hitNormal);
}
}
}
}
IEnumerator UpdatePath()
{
//살아있는동안 반복
while(!dead)
{
//추적할 대상이 존재하고, 대상이 사망하지 않았다면 (hasTarget : targetEntity != null && targetEntity.dead)
if(hasTarget)
{
//네비메쉬 사용
pathFinder.isStopped = false;
//네비메쉬의 도착지를 targetEntity의 포지션으로 정해줘
pathFinder.SetDestination(targetEntity.transfrom.position);
}
//추적할 대상이 없거나 사망했다면
else
{
//네비매쉬를 멈춰줘
pathFinder.isStopped = true;
//반지름 20의 구형 콜라이더를 내 위치에서 whatIsTarget쪽으로 발사해서
//닿은 콜라이더의 정보들을 Collider[]에 담아죠
Collider[] colliders =
Physics.OverlapSphere(transform.position, 20f, whatIsTarget)
for(int i = 0; i < colliders.Length; i++)
{
//colliders[i]의 LivingEntity스크립트를 가지고 있는
//colliders[i]의 컴포넌트를 참조해와서 livingEntity에 할당해
LivingEntity livingEntity =
colliders[i].GetComponent<LivingEntity>();
//구형콜라이더와 충돌한 livingEntity를 가지고 있는 녀석이 있고, 사망하지 않았다면
if(livingEntity != null && !livingEntity.dead)
{
//타겟을 구형콜라이더와 충돌한 녀석으로 지정
targetEntity = livingEntity;
break;
}
//0.25초 주기로 반복해서 실행
yield return new WaitForSeconds(0.25f);
}
}
}
}
public override void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal)
{
hitEffect.transform.position = hitPoint;
hitEffect.transform.rotation = Quaternion.LookRotation(hitNormal);
hitEffect.Play();
enemyAudioPlayer.Play();
//LivingEntity의 Ondamage()를 실행하여 데미지 적용
base.Ondamage(damage, hitPoint, hitNormal);
}
//상속받은 메소드
public override void Die()
{
//LivingEntity의 Die()를 실행하여 기본 사망 처리 실행
base.Die()
//사망했다면, enemyColliders에 사망한 녀석의 Collider의 컴포넌트를 가져와
Collider[] enemyColliders = GetComponents<Collider>();
foreach(Collider collider in enemyColliders)
{
//사망한 오브젝트는 사용하지 않겠음
collider.enabled = false;
}
pathFinder.isStopped = true;
pathFinder.enabled = false;
}
//트리거 콜라이더에 닿아있는 동안에
private void OnTriggerStay(Collider other)
{
//트리거 충돌한 상대방 게임 오브젝트가 추적 대상이라면 공격 실행
if(!dead && Time.time >= lastAttackTime + timeBetAttack)
{
//트리거 충돌했을 때 LivingEntity를 가지고 있는 other의
//LivingEntity스크립트의 컴포넌트를 참조해와
LivingEntity attackTarget = other.GetComponent<LivingEntity>();
//LivingEntity를 가진 녀석과 충돌했고, 그게 타겟이라면(플레이어라면)
if(attackTarget != null && attackTarget == targetEntity)
{
//쿨타임 초기화
lastAttackTime = Time.time;
//맞은곳은 좀비 포지션과 맞은녀석의 가까운 곳
Vector3 hitPoint =
other.ClosestPoint(transform.position);
//맞은 방향은 좀비의 포지션에서 맞은녀석쪽으로
Vector hitNormal =
transform.position - other.transform.position;
attackTarget.OnDamage(damage, hitPoint, hitNormal);
}
}
}
#EnemySpawner
// 적을 생성하고 생성한 적에게 추적할 대상을 할당
private void CreateEnemy(float intensity)
{
//★Lerp : 최소값과 최대값을 정해놓고 중간값이 무엇인지 구하는것
float health = Mathf.Lerp(healthMin, healthMax, intensity);
//
float damage = Mathf.Lerp(damageMin, damageMax, intensity);
//
float speed = Mathf.Lerp(speedMin, speedMax, intensity);
//Color.Lerp : strongEnemyColor(Red)
//흰색 : RGB(255, 255, 255), 빨강 : RGB(255, 0, 0) 이 사이의 중간점.
//0에 가까울수록 흰색에 가깝고 1에 가까울수록 빨강에 가까워짐
Color skinColor = Color.Lerp(
Color.white, strongEnemyColor, intensity);
Transform spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)];
//좀비를 생성해서 좀비를 참조함
Enemy enemy = Instantiate(enemyPrefab, spawnPoint.position, spawnPoint.rotation);
enemy.Setup(health, damage, speed, skinColor);
enemies.Add(enemy);
//익명 메소드(람다식) : void 형태로 메소드로 추가해준 것
//enemy.onDeath += () => enemies.Remove(enemy);
//enemy.onDeath += () => Destroy(enemy.gameObject,10f);
//enemy.onDeath += () => GameManager.instance.AddScore(100);
enemy.onDeath += () =>
{
enemies.Remove(enemy);
Destroy(enemy.gameObject, 10f);
GameManager.instance.AddScore(100);
};
}
}
RGB같은 경우 흰색은 (255, 255, 255), 빨간색은 (255, 0, 0)이기 때문에 G,B값에서 0~255사이의 값으로 조율이 될것이다.
#UI Manager
using UnityEngine;
using UnityEngine.SceneManagement; // 씬 관리자 관련 코드
using UnityEngine.UI; // UI 관련 코드, Text를 쓸 때 사용
// 필요한 UI에 즉시 접근하고 변경할 수 있도록 허용하는 UI 매니저
public class UIManager : MonoBehaviour {
// 싱글톤 접근용 프로퍼티
public static UIManager instance
{
get
{
if (m_instance == null)
{
m_instance = FindObjectOfType<UIManager>();
}
return m_instance;
}
}
private static UIManager m_instance; // 싱글톤이 할당될 변수
public Text ammoText; // 탄약 표시용 텍스트
public Text scoreText; // 점수 표시용 텍스트
public Text waveText; // 적 웨이브 표시용 텍스트
public GameObject gameoverUI; // 게임 오버시 활성화할 UI
// 탄약 텍스트 갱신
public void UpdateAmmoText(int magAmmo, int remainAmmo) {
ammoText.text = magAmmo + "/" + remainAmmo;
}
// 점수 텍스트 갱신
public void UpdateScoreText(int newScore) {
scoreText.text = "Score : " + newScore;
}
// 적 웨이브 텍스트 갱신
public void UpdateWaveText(int waves, int count) {
waveText.text = "Wave : " + waves + "\nEnemy Left : " + count;
}
// 게임 오버 UI 활성화
public void SetActiveGameoverUI(bool active) {
gameoverUI.SetActive(active);
}
// 게임 재시작
public void GameRestart() {
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
Scale With Screen Size : UI의 크기를 스크린 사이즈로 설정
Interactable : 버튼과 상호작용 하겠음 (★버튼 사용시 체크)
On Click() : 버튼을 클릭했을 때 사용할 기능
> +로 추가해 준 후 어느 상황에서 실행할건지 설정, 오브젝트를 넣으면 그 안에 포함된 스크립트의 메소드들을 사용할 수 있음.
다음으로는 화면에 효과를 적용할건데,
우선 Window > PackageManager > Packages : In Project > Post Precessing이 있는지 확인. 없다면 In Project를 Unity Registry로 변경하여 검색 후 다운.
Post Processing
Main Cam > Rendering Path > Use Graphics Settings 를 Deferred로 변경
forward : 가볍게 표현하다보니 생략되거나 대충 표현되는 경우가 많음
Deferred : 빛에의한 연산을 나중으로 미룸. 세팅한것들을 거의 온전하게 표현
MSAA(Anti 얼라이어싱) : 계단현상. 그래픽 성능이 떨어져서 그림자가 각져서
나오는 그런 현상들을 완화시켜주는 역할.
Deferred를 키면 정상작동을 안할 뿐더러 Deferred를 사용하면 굳이 안써도 됨.
Add Component > Rendering > Post-process Layer
Layer는 인스펙터에 있는 Layer. > PostProcessing으로 체크.
AA(Anti-aliasing) : Fast Approximate(FXAA)>기능은 높지 않지만
빨리빨리 처리해줌
오브젝트 생성(하이어아키 3D Object > Post-process Volume)
Layer : PostProcessing으로 변경
Is Global : 게임 안의 전체에 적용시키겠다.
체크 안하면 오브젝트에만 적용
Profile에
Motion Blur : 모션 잔상
Bloom : 뽀샤시 효과
Color Grading : 인스타그램 사진필터 ㅋㅋ 색상 대비, 감마값 변경
Chromatic Aberration : 색수차? 방사능, 중독효과사용할때 보통 사용
이미지의 경계가 번지고 3원색이 분리가 됨
Vignette : 화면 모서리의 채도랑 명도를 조절
Grain : 화면에 입자효과
효과를 적용시켜주면 화면이 이렇게 변한다.
'수업 복습' 카테고리의 다른 글
2D RogueLike (1) | 2023.09.06 |
---|---|
Zombie - MultiPlay - (0) | 2023.08.30 |
Zombie 복습2 - 플레이어 스크립트 - (2) | 2023.08.27 |
Zombie - 좀비, 플레이어 UI - (0) | 2023.08.22 |
Zombie 복습 - 총 세팅, 플레이어 애니메이션 세팅 - (0) | 2023.08.22 |