Shooting Game

Shooting Game

1.IDamageable

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();
        }
    }
}
上一篇:LightOJ 1356. Prime Independence


下一篇:Java逆矩阵计算