Episode 07

Some Loose Ends——一些待解决的问题

解决了Enemy攻击Player造成伤害,优化了Projectile在Enemy内部生成无法造成伤害以及Projectile在Enemy边界的问题,补充了相关注释,下一步可以利用TakeHit(float damage, RaycastHit hit)形成粒子效果。

Enemy

using System.Collections;
using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]

public class Enemy : LivingEnitity
{
    public enum State { Idle, Chasing, Attacking };//定义状态枚举类型

    State currentState;//定义当前状态
    NavMeshAgent pathfinder;//定义导航网格代理
    Transform target;//定义目标位置
    LivingEnitity targetEnity;//定义目标
    Material skinmaterial;//定义外皮材质
    Color originalColour;//定义初始颜色

    float attackDistanceThreshold = .5f;//攻击距离阈值
    float timeBetweenAttacks = 1;//攻击间隔时间
    float damage = 1;

    float nextAttackTime;
    float myCollisionRadius;//enemy的碰撞半径
    float targetCollisionRadius;//player的碰撞半径

    bool hasTarget;//是否存在目标

    protected override void Start()
    {
        base.Start();
        //追踪Player
        pathfinder = GetComponent<NavMeshAgent>();
        skinmaterial = GetComponent<Renderer>().material;
        originalColour = skinmaterial.color;

        if (GameObject.FindGameObjectWithTag("Player") != null)
        {
            currentState = State.Chasing;
            target = GameObject.FindGameObjectWithTag("Player").transform;
            hasTarget = true;//目标存在

            targetEnity = target.GetComponent<LivingEnitity>();
            targetEnity.OnDeath += OnTargetDeath;//添加OnTargetDeath方法,当health归0时调用。

            myCollisionRadius = GetComponent<CapsuleCollider>().radius;
            targetCollisionRadius = GetComponent<CapsuleCollider>().radius;

            StartCoroutine(UpdatePath());
        }
    }

    void OnTargetDeath()
    {
        hasTarget = false;
        currentState = State.Idle;
    }

    void Update()
    {
        if (hasTarget)
        {
            if (Time.time > nextAttackTime)
            {
                float sqrDstToTarget = (target.position - transform.position).sqrMagnitude;
                //间隔距离小于攻击距离阈值
                if (sqrDstToTarget < Mathf.Pow(attackDistanceThreshold + myCollisionRadius + targetCollisionRadius, 2))
                {
                    nextAttackTime = Time.time + timeBetweenAttacks;
                    StartCoroutine(Attack());
                }
            }
        }
    }

    IEnumerator Attack()
    {
        currentState = State.Attacking;
        pathfinder.enabled = false;//取消追逐

        Vector3 orginalPosition = transform.position;//初始位置
        Vector3 dirToTarget = (target.position - transform.position).normalized;//攻击方向
        Vector3 attackPosition = target.position - dirToTarget * myCollisionRadius;//攻击到的位置

        float attackSpeed = 3;
        float percent = 0;

        skinmaterial.color = Color.red;
        bool hasAppliedDamage = false;

        while (percent <= 1)
        {
            //percent为0.5时候完成了攻击
            if (percent >= 0.5 && !hasAppliedDamage)
            {
                hasAppliedDamage = true;
                targetEnity.TakeDamage(damage);
            }

            percent += Time.deltaTime * attackSpeed;
            float interpolation = (-Mathf.Pow(percent, 2) + percent) * 4;
            //线性位置变换,使攻击动作平滑
            transform.position = Vector3.Lerp(orginalPosition, attackPosition, interpolation);

            yield return null;
        }

        skinmaterial.color = originalColour;
        currentState = State.Chasing;
        pathfinder.enabled = true;
    }

    //不在Update里面使用SetDestination()防止计算开销过大
    IEnumerator UpdatePath()
    {
        float refreshRate = .25f;//0.25s计算一次
        while (hasTarget)
        {
            if (currentState == State.Chasing)
            {
                Vector3 dirToTarget = (target.position - transform.position).normalized;
                //追逐player间隔一点距离
                Vector3 targetPosition = target.position - dirToTarget * (myCollisionRadius + targetCollisionRadius + attackDistanceThreshold / 2);
                //防止Die()后继续调用SetDestination()报错
                if (!dead)
                {
                    pathfinder.SetDestination(targetPosition);
                }
            }
            yield return new WaitForSeconds(refreshRate);
        }
    }
}

LivingEntity

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LivingEnitity : MonoBehaviour, IDamageable
{
    public float startingHealth;
    protected float health;
    protected bool dead;

    public event System.Action OnDeath;

    //赋初始生命值
    protected virtual void Start()
    {
        health = startingHealth;
    }

    //被击中后health值减少到0,调用Die()
    public void TakeHit(float damage, RaycastHit hit)
    {
        TakeDamage(damage);
    }

    public void TakeDamage(float damage)
    {
        health -= damage;
        if (health <= 0 && !dead)
        {
            Die();
        }
    }

    protected void Die()
    {
        dead = true;
        if (OnDeath != null)
        {
            OnDeath();
        }
        Destroy(gameObject);
    }
}

Projectile

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Projectile : MonoBehaviour
{
    //设置渲染层级
    public LayerMask collisionMask;
    float speed = 10;//移动速度
    float damage = 1;//伤害
    float lifetime = 3;//生命周期
    float skinWidth = .1f;//EnemySkin宽度

    private void Start()
    {
        //生命周期结束后销毁Projectile
        Destroy(gameObject, lifetime);
        //解决Projectile在Enemy内部生成时Bug
        Collider[] initialCollisions = Physics.OverlapSphere(transform.position, .1f, collisionMask);
        if (initialCollisions.Length > 0)
        {
            OnHitObject(initialCollisions[0]);
        }
    }

    public void SetSpeed(float newSpeed)
    {
        speed = newSpeed;
    }

    void Update()
    {
        float moveDistance = Time.deltaTime * speed;
        //判断是否发生碰撞
        CheckCollisions(moveDistance);
        //Projectile移动
        transform.Translate(Vector3.forward * moveDistance);
    }

    void CheckCollisions(float moveDistance)
    {
        Ray ray = new Ray(transform.position, transform.forward);
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, moveDistance + skinWidth, collisionMask, QueryTriggerInteraction.Collide))
        {
            OnHitObject(hit);
        }
    }

    //生成在Enemy外,传入射线击中的物体后销毁
    void OnHitObject(RaycastHit hit)
    {
        IDamageable damageableObject = hit.collider.GetComponent<IDamageable>();
        if (damageableObject != null)
        {
            damageableObject.TakeHit(damage, hit);
        }
        Destroy(gameObject);
    }

    //生成在Enemy内,传入收集器的第一个物体销毁
    void OnHitObject(Collider collider)
    {
        IDamageable damageableObject = collider.GetComponent<IDamageable>();
        if (damageableObject != null)
        {
            damageableObject.TakeDamage(damage);
        }
        Destroy(gameObject);
    }
}

Physics.OverlapSphere(Vector3 position, float radius, int layerMask):函数一旦被调用,将会返回以参数1为原点和参数2为半径的球体内“满足一定条件”的碰撞体集合,此时我们把这个球体称为 3D相交球。

IDamageable

using UnityEngine;

public interface IDamageable
{
    void TakeHit(float damage, RaycastHit hit);
    void TakeDamage(float damage);
}

拓展:

posted @ 2023-03-21 10:43  Felix-Fu  阅读(12)  评论(0编辑  收藏  举报