public interface IDamageable
{
void TakenDamage(float _damageAmount);
}
2.LivingEntity
using System;
using UnityEngine;
//这个脚本是PlayerController和Enemy的父、基类,PlayerController和Enemy是这个类的子类、继承类
//C#他是这不支持多继承的,也就是说只能继承一个父类。但是我们虽然不能多继承,我们可以用接口配合父类来代替多继承
public class LivingEntity : MonoBehaviour, IDamageable
{
public float maxHealth;
[SerializeField] protected float health; //protected级别就是,外界不能访问这个变量,只有子类可以访问
protected bool isDead;
//MARKER 无论是玩家人物、还是敌人、都会阵亡,阵亡对于它们来说都是一个「事件」
//事件的简略声明格式:private/public + event key + Delagate + eventName (on + Foo)
public event Action onDeath;
protected virtual void Start() //子类可以覆盖、覆写,也可以使用Base.Start
{
health = maxHealth;
}
//MARKER 事件的触发,是由事件拥有者的内部逻辑触发的
protected void Die()
{
isDead = true;
Destroy(gameObject);
if (onDeath != null)
onDeath();
}
//无论是敌人还是人物,都会受到伤害
//意思就是:当人物或者敌人受伤时,就会扣血,血量小于0,则触发内部逻辑中的事件
//人物:GameOver,敌人取消追击
//敌人:判断场上是否还有敌人,如果全部被消灭,则开启下一波敌人
public void TakenDamage(float _damageAmount)
{
health -= _damageAmount;
if (health <= 0 && isDead == false)
{
Die();
}
}
}
3.Enemy
using System.Collections;
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class Enemy : LivingEntity
{
private NavMeshAgent navMeshAgent;
private Transform target; //追击 PLAYER
[SerializeField] private float updateRate; //每3秒做出一次新的反应,追击敌人
private bool hasTarget;
//private void Start()
//{
// navMeshAgent = GetComponent<NavMeshAgent>();
// //因为之后我们敌人生成时,Player可能已经阵亡了,就不会追击Player,但是我们这里又想要获取他的Trans组件,所以首先要判断
// if(GameObject.FindGameObjectWithTag("Player") != null)
// {
// target = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();
// }
// StartCoroutine(UpdatePath());
//}
protected override void Start()
{
base.Start();
navMeshAgent = GetComponent<NavMeshAgent>();
//因为之后我们敌人生成时,Player可能已经阵亡了,就不会追击Player,但是我们这里又想要获取他的Trans组件,所以首先要判断
if (GameObject.FindGameObjectWithTag("Player") != null)
{
hasTarget = true;
target = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();
target.GetComponent<LivingEntity>().onDeath += TargetDeath;
StartCoroutine(UpdatePath());
}
}
private void TargetDeath()
{
Debug.Log("hasTarget is False");
hasTarget = false;
}
private void Update()
{
//navMeshAgent.SetDestination(target.position);//OPTIONAL AI 契而不舍类敌人~
}
//OPTIONAL AI 摸鱼类敌人【反应迟钝类敌人】更适合AI制作
IEnumerator UpdatePath()
{
//while(target != null)
while (hasTarget)
{
if (isDead == false)
{
Vector3 preTargetPos = new Vector3(target.position.x, 0, target.position.z); //玩家位置信息
navMeshAgent.SetDestination(preTargetPos);
}
yield return new WaitForSeconds(updateRate); //过3秒后,再次去遍历上面几行
}
}
}
4.PlayerController
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class PlayerController : LivingEntity
{
private Rigidbody rb;
private Vector3 moveInput;
[SerializeField] private float moveSpeed;
protected override void Start()
{
base.Start();
rb = GetComponent<Rigidbody>();
}
private void Update()
{
moveInput = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));
LookAtCursor();
if (Input.GetKeyDown(KeyCode.Space)) //模拟,人物受到攻击后,每次扣2滴血
{
TakenDamage(2.0f);
}
}
private void FixedUpdate()
{
rb.MovePosition(rb.position + moveInput * moveSpeed * Time.fixedDeltaTime);
}
private void LookAtCursor()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Plane plane = new Plane(Vector3.up, Vector3.zero);
float distToGround;
if (plane.Raycast(ray, out distToGround))
{
Vector3 point = ray.GetPoint(distToGround);
Vector3 rightPoint = new Vector3(point.x, transform.position.y, point.z);
transform.LookAt(rightPoint);
}
}
}
5.GunController
using UnityEngine;
public class GunController : MonoBehaviour
{
public Transform firePoint;
public GameObject projectilePrefab;
[SerializeField] private float fireRate = 0.5f;
private float timer;
private void Update()
{
if (Input.GetMouseButton(0)) //GetMouseButtonDown一直按住鼠标左键进行发射
Shot();
}
//经典的计时器限制频率问题
public void Shot()
{
timer += Time.deltaTime;
if (timer > fireRate)
{
timer = 0;
GameObject spawnProjectile = Instantiate(projectilePrefab, firePoint.position, firePoint.rotation);
}
}
}
6.Projectile
using UnityEngine;
public class Projectile : MonoBehaviour
{
[SerializeField] private float shootSpeed;
[SerializeField] private float damage = 1.0f;
[SerializeField] private float lifetime;
public LayerMask collisionLayerMask;
private void Start()
{
Destroy(gameObject, lifetime); //当子弹发射过了lifetime秒后,子弹需要被销毁,避免一直在场景中消耗性能
}
private void Update()
{
transform.Translate(Vector3.forward * shootSpeed * Time.deltaTime);
CheckCollision();
}
private void CheckCollision()
{
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hitInfo; //结构体类型:射线击中目标的具体信息
if (Physics.Raycast(ray, out hitInfo, shootSpeed * Time.deltaTime, collisionLayerMask,
QueryTriggerInteraction.Collide))
{
//击中敌人了,敌人要受伤了
HitEnemy(hitInfo);
}
}
private void HitEnemy(RaycastHit _hitInfo)
{
//IDamageable damageable = _hitInfo.collider.GetComponent<IDamageable>();
IDamageable damageable = _hitInfo.collider.GetComponent<LivingEntity>();
if (damageable != null)
damageable.TakenDamage(damage);
Destroy(gameObject); //当子弹击中敌人后,子弹需要被销毁
}
}
7.Wave
//Struct、ScriptableObject都是可以的
using System;
[Serializable]
public class Wave
{
public int enemyNum; //每一波【敌人的总数】
public float timeBtwSpawn; //每一波当中,前后敌人出现的【间隔时间】
}
8.Spawner
using UnityEngine;
public class Spawner : MonoBehaviour
{
public GameObject enemyPrefab;
public Wave[] waves; //List OJBK
[Header("JUST FOR CHECK ! ! !")] [SerializeField]
private Wave currentWave; //当前波
[SerializeField] private int currentIndex; //当前波在数组集合中的【序号、索引 Index】
public int waitSpawnNum; //这一波还剩下多少敌人没有生成,等于0以后的话,不该再生成新的敌人了
public int spawnAliveNum; //这一波的敌人还存活了多少个,少于0的话,则应该触发,下一波敌人的逻辑执行
public float nextSpawnTime;
private void Start()
{
NextWave();
}
private void NextWave()
{
currentIndex++;
Debug.Log(string.Format("Current Wave : {0}", currentIndex));
if (currentIndex - 1 < waves.Length)
{
currentWave = waves[currentIndex - 1]; //一开始Index = 1,所以currentWave = waves[currentIndex - 1] 数组0的位置
waitSpawnNum = currentWave.enemyNum;
spawnAliveNum = currentWave.enemyNum;
}
}
//生成敌人了!
private void Update()
{
if (waitSpawnNum > 0 && Time.time > nextSpawnTime) //间隔事件内,产生敌人
{
waitSpawnNum--; //等待生成的敌人 - 1
GameObject spawnEnemy = Instantiate(enemyPrefab, transform.position, Quaternion.identity); //生成新敌人
//每当生成新的敌人,就要将这个敌人的”阵亡事件处理器“【【订阅】】到事件onDeath上
spawnEnemy.GetComponent<Enemy>().onDeath += EnemyDeath; //CORE
nextSpawnTime = Time.time + currentWave.timeBtwSpawn;
}
}
//当敌人阵亡时,spawnAliveNum - 1,spawnAliveNum = 0时候,下一波
//每次有敌人阵亡,我们都会判断,这是不是场上最后一个敌人,最后一个敌人如果阵亡了,那么就开始下一波的进攻
private void EnemyDeath()
{
spawnAliveNum--;
if (spawnAliveNum <= 0)
{
NextWave();
}
}
}