[Unity]一人称FPSゲーム-3Living Entity-2
1. Ememy
1) Unity Object
ControllerとAvatarはSimpleApoicase Assetを使用しています.(Asset Storeにある)
MaterialsもSimpleApoicase Assetを使用しています.
(AgentのサイズはNavigationタブで調整することもできます)
2) C# Code
敵には偵察・追跡・攻撃の3つの状態がある.
ステータスを0.25秒おきに更新するにはcoluceneを使用します.
using UnityEngine.AI;
using UnityEngine;
using System.Collections;
public class EnemyAI : LivingEntity
{
public NavMeshAgent agent;
public Transform playerPos;
public Player player;
public float damage = 1; //좀비가 주는 데미지
public LayerMask whatIsGround, whatIsPlayer, whatIsObstacle;
GameObject gameManager;
// 모델링 넣기 전 테스트 용으로 넣은 코드
Material skinMaterial; //적의 메테리얼
Color OriginColor;
float refreshRate = 0.25f; //path 업데이트 갱신 간격
//Patroling
public Vector3 walkPoint; //위치
bool walkPointSet = false; //해당 위치가 이미 세팅 되어있는지 확인
public float walkPointRange; //범위
//Chasing
bool setChase = false;
//Attacking
//bool setAttack;
public float timeBetweenAttacks;
bool alreadyAttacked;
//States
public float sightRange, attackRange;
public bool playerInSightRange, playerInAttackRange;
//충돌범위
float myCollisionRadius; //적의 충돌범위
float targetCollisionRadius;// 타겟 충돌범위
private void Awake()
{
}
protected override void Start()
{
base.Start();
gameManager = GameObject.Find("GameManager");
dead = false;
//플레이어 위치 할당
playerPos = GameObject.Find("Player(Clone)").transform;
player.dead = false;
//nav mesh agent 할당
agent = GetComponent<NavMeshAgent>();
//적과 플레이어 겹침 방지 위해
myCollisionRadius = GetComponent<CapsuleCollider>().radius;
targetCollisionRadius = playerPos.GetComponent<CapsuleCollider>().radius;
//상태 변화 시 색상 변화 위해
skinMaterial = GetComponent<Renderer>().material;
OriginColor = skinMaterial.color;
//Player의 초기 체력 값이 자꾸 스크립트로 리셋이 안되어서 Enemy 쪽에서 강제로 세팅 -> 추후 수정 필요
player.setHealth(player.startingHealth);
StartCoroutine(UpdatePath());
}
private void Update()
{
if (gameManager.GetComponent<GameManager>().isClear)
{
this.gameObject.SetActive(false);
}
}
IEnumerator UpdatePath()
{
Debug.Log("enemy updatePath");
Debug.Log(player.getHealth());
while (playerPos!=null && !player.GetComponent<Player>().dead)
{
//Debug.Log(!player.GetComponent<Player>().dead);
//Debug.Log(!dead);
if (!dead)
{
//Debug.Log("Player exist");
//시야와 공격범위 확인
playerInSightRange = Physics.CheckSphere(transform.position, sightRange, whatIsPlayer);
//Debug.Log(playerInSightRange);
playerInAttackRange = Physics.CheckSphere(transform.position, attackRange, whatIsPlayer);
//Debug.Log(playerInAttackRange);
if (!playerInSightRange && !playerInAttackRange && !setChase)
Patroling();
if ((playerInSightRange && !playerInAttackRange) || setChase)
ChasePlayer();
if ((playerInAttackRange && playerInSightRange))
{
setChase = false;
AttackPlayer();
}
if (gameManager.GetComponent<GameManager>().isClear)
{
this.gameObject.SetActive(false);
}
}
yield return new WaitForSeconds(refreshRate);
}
}
private void Patroling()
{
skinMaterial.color = OriginColor;
//Debug.Log("Patroling");
if (!walkPointSet)
SearchWalkPoint();
if (walkPointSet)
{
agent.SetDestination(walkPoint);
Vector3 distanceToWalkPoint = transform.position - walkPoint;
//Walkpoint에 도달 (거리 1 미만일 때)
if (distanceToWalkPoint.magnitude < 0.1f)
walkPointSet = false;
}
}
private void ChasePlayer()
{
setChase = true;
if (setChase)
{
//chasing할 때 색 바뀌도록 -> 모델링 입히기 전 테스트 위해
skinMaterial.color = Color.yellow;
//원래는 Collider의 Is Trigger를 체크하고 콜라이더 범위만큼 빼고 그 앞까지 오는 걸로 했었다
//그런데 계속 오류나서 Is Trigger를 해제하고 플레이어 위치로 찾아오도록 변경
//Vector3 dirToTarget = (playerPos.position - transform.position).normalized;
//Vector3 targetPosition = playerPos.position - dirToTarget * (myCollisionRadius + targetCollisionRadius);
walkPoint = new Vector3(playerPos.position.x, transform.position.y, playerPos.position.z);
if (agent.SetDestination(walkPoint))
{
Debug.Log("Follow Player");
}
}
}
private void AttackPlayer()
{
skinMaterial.color = Color.red;
Debug.Log("Attack");
setChase = false;
//플레이어 계속 추적
//agent.SetDestination(playerPos.position);
Vector3 dirToTarget = (playerPos.position - transform.position).normalized;
Vector3 targetPosition = playerPos.position - dirToTarget * (myCollisionRadius + targetCollisionRadius);
agent.SetDestination(targetPosition);
transform.LookAt(playerPos);
//Attack
if (!alreadyAttacked) {
Debug.Log("Attacking....");
//Attack Code 여기에
//Attack();
//IDamageble damagebleObject = player.GetComponent<IDamageble>();
if (player.dead != true)
{
//Attack();
//Debug.Log("Attacking....");
Debug.Log(player.getHealth());
player.TakeHit(damage);
Debug.Log(player.getHealth());
}
alreadyAttacked = true;
Invoke(nameof(ResetAttack), timeBetweenAttacks);
}
}
//랜덤으로 순찰할 위치 선정
private void SearchWalkPoint()
{
float randomZ = Random.Range(-walkPointRange, walkPointRange);
float randomX = Random.Range(-walkPointRange, walkPointRange);
//랜덤하게 패트롤할 위지 범위 안에서 선청
walkPoint = new Vector3(transform.position.x + randomX, transform.position.y, transform.position.z + randomZ);
//맵밖으로 안나갔는지 확인
if (Physics.Raycast(walkPoint, -transform.up, 2f, whatIsGround))
{
Vector3 tmpPoint = new Vector3(walkPoint.x, 20, walkPoint.z);
if (!Physics.Raycast(tmpPoint, -transform.up, Mathf.Infinity, whatIsObstacle))
{
walkPointSet = true;
// return;
}
}
else
{
Debug.Log("Can't find");
//SearchWalkPoint();
}
}
protected override void Die()
{
base.Die();
gameManager.GetComponent<GameManager>().IncreaseKillCount();
Destroy(this.gameObject);
}
private void ResetAttack()
{
alreadyAttacked = false;
}
//Sight Range, AttackRange를 EDITOR에서 확인하기 위해
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange);
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, sightRange);
}
#endif
}
2.発生した問題
1)Enemy Animator接続
できあがったAssetをどうやって貼ればいいのか分からないので、ちょっと苦労しました.
最初は、Assetのゾンビプリセット要素をコピーして予め作成したEnemy Prefabに貼り付けましたが、アニメーションコントローラにマッピングされていないかどうか分かりません.
だからAssetのゾンビプリセットをそのまま持ってきて、そこにEnemyスクリプトとNav Mesh Agentを加えます.
そしてそのままMesh Renderを使用したのは素材を着用していないのでSkind Mesh Renderを使用しました
Unityでは、Skind Mesh Render構成部品を使用してBoneアニメーションをレンダリングします.これらのBoneアニメーションは、事前に定義されたアニメーションシーケンスに基づいてメッシュ形状をレンダリングします.これは、キャラクタ(回転軸機能を持つマシンとは異なる)だけでなく、他のベンドジョイントのオブジェクトにも適用できる非常に有用な方法です.
2)Enemy偵察と交換問題
=敵が偵察や迎撃を行わない問題
これはコーラマシンとnavmesh agentの問題です.
敵のコーラ機と地上コーラ機が衝突し、敵は地面に貼られず、navmesh agentはこのルートに入ることができなかった.
これは,地図上のコーラ初期化器の初期化時に0.5の高さのスクリプトを記述したためである.
また、CollayderのIs Triggerをチェックした場合、プレイヤーと敵の直径を抜き、敵をプレイヤーの位置に移動させるというエラーも発生し続け、Is Triggerを解除し、敵がプレイヤーの位置に移動することを修正しました.
3)プレイヤーが攻撃するとすぐに死亡する
実はこれはEnemy側の問題ではなく、Playerスクリプトの問題ですが、Enemyが一時的に修正したのでここに書きます.
Attack関数を呼び出すと、プレイヤーがヒットしてすぐに死亡するので、ログを撮りましたが、プレイヤーが死亡してから再生成したとき、初期体力値は初期化されませんでした.
ただしプレイヤーと敵がprefabになってしまったためか同期が取れず、一時的な方法でEnemyを生成した際に、プレイヤーの体力を強制リセットした.
Reference
この問題について([Unity]一人称FPSゲーム-3Living Entity-2), 我々は、より多くの情報をここで見つけました https://velog.io/@yeju6540/Unity-1인칭-FPS-게임-3.-Gunテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol