首页 > 其他分享 >Unity-敌人(Enemy)

Unity-敌人(Enemy)

时间:2023-11-14 19:25:45浏览次数:37  
标签:Enemy 函数 攻击 void 目标 Unity 敌人 public

Unity-敌人(Enemy)

引言

​ 敌人是每个游戏中不可缺少的部分,设计得好的敌人可以给游戏增添很多乐趣,设计得差的则会非常影响我们的游戏体验。

​ 经过这段时间的学习,我们已经接触了非常多的敌人代码的写法,但是就是没有系统的归类,导致每次写敌人,都要从头开始。现在是时候将他们进行一个系统性的整理了。

逻辑思考

基本框架

​ 我们首先来想一想,敌人需要执行什么操作,方便我们进行后面的构思。

​ 首先,我们的敌人需要移动、攻击、受伤这些基本的操作,有些敌人还会跳跃。

​ 为了实现敌人的移动,我们可能需要两个固定的目标点、一个发现目标时 记录目标的点,还可能需要 转向 的函数,还有 切换目标 的函数,以实现我们 巡逻移动、或朝向目标移动的目的。

​ 为了实现敌人的攻击,我们需要 动画器组件,播放对应的攻击动画,同时还需要一些 参数,记录敌人自己的 攻击距离、攻击速度。因为我们使用了状态机,所以只需要写好攻击函数,使敌人进入攻击状态后每帧调用攻击函数就可以了。

​ 为了实现敌人的受伤,我们需要创建一个 接口,里面包含一个抽象的受伤(GetHit)函数,需要子类实现,玩家的受伤函数也可以继承这个接口。

敌人需要的基本参数

​ 根据上述构思的大概框架,我们可以简单想一下我们需要哪些参数

​ 首先,敌人需要一些最基本的参数:

  1. 生命值(health)
  2. 移动速度(speed)
  3. 跳跃力(jumpForce)
  4. 死亡判定变量(isDead)
  5. 巡逻点A、B(pointA,pointB)
  6. 目标点(targetPoint)

​ 其次,还需要一些特殊的参数,记录某些值或是执行某些代码

  1. 目标点 Transform 集合:记录一个范围之内的所有物体的 Transform
  2. 攻击参数:由几个参数组合而成,分别为攻击间隔(attackRate)、攻击时的时间点(nextAttack)、攻击距离(attackRange)
  3. 各种组件:动画器(Animator)、刚体(Rigidbody)
  4. 状态机及其子类对象

敌人需要的基本函数

​ 构思完基本参数后,我们可以开始构思我们该如何写我们的函数了

​ 首先还是基本的函数:

  1. 移动函数:每帧都调用,让敌人朝向目标点移动,还需调用转向函数,保证敌人一直朝向目标
  2. 攻击函数:状态机调用,当目标进入敌人攻击范围后攻击
  3. 受伤函数:实现接口,由攻击放调用,在角色受到攻击时,根据传进的数值变量减少相应的生命值。

​ 其次,是根据为了辅助基本函数的执行,需要添加的函数

  1. 切换巡逻目的地函数:状态机 调用,达到目标点后,转换敌人当前的巡逻目标点,需要在程序开始调用一次,为目标点赋值
  2. 转向函数:在移动函数中调用,判断目标点在敌人的那一边,并将敌人的方向朝向目标点
  3. 有关于触发器(Trigger)的函数:当指定目标进入敌人视野范围,则将目标添加进入目标点集合,离开则删除

​ 以上这些函数,有些是在敌人自身的类中调用,有的则需要在状态机中调用,我们也来简单理一下这其中的逻辑。

​ 假设这个敌人只有简单的两个状态:巡逻和攻击,那么:

1.巡逻状态:

​ 敌人默认状态,需要在进入该状态就调用切换巡逻目标点函数,将初始目标点初始化。

​ 此外,还需要每帧都判断与目标点的距离,如果到达了目标点,则在此进入该状态,重置目标点。在该状态中也需要持续判断攻击目标集合是否为0,如果不为0则切换为攻击状态。

2.攻击状态:

​ 进入攻击状态,就要立刻将攻击目标集合中的第一个值赋值给目标点(重置目标点),敌人将持续朝向目标点移动。

​ 在该状态中,要持续判断攻击目标集合的个数,并执行相应的函数,包括但不限于切换状态、切换攻击目标........

其他

​ 写完这些基本的参数与函数后,我们就需要将我们的有限状态机加入敌人的基类,详细的可以看有限状态机的笔记,这里不过多赘述。

最终代码

​ 根据上述对敌人的构思,产生了如下所述的代码,在此已经做了关键注释

​ 知识点较为细碎,在这里不做总结归纳,相对重要的为如下几点:

  1. 与外部物体、有限状态机配合进行的攻击函数:85行
  2. 新的转向代码:118行
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class Enemy : MonoBehaviour, IDamageable
{

    [Header("基本参数")]
    public float speed;//速度值
    public float jumpForce;//跳跃力
    public float health;//生命值
    public bool isDead;//死亡判定变量
    public Transform pointA, pointB;//巡逻点
    public Transform targetPoint;//目标点
    //目标点的Transform集合
    public List<Transform> attackList = new List<Transform>();

    [Header("Attack Setting")]
    //若当前时间点 > 攻击间隔 + 上一次攻击的时间点,那么就可以攻击
    public float attackRate;//攻击间隔(攻击速度)
    [HideInInspector]public float nextAttack;//在攻击时,记录此次攻击的时间点
    public float attackRange,skillRange;//普通攻击距离和特殊攻击距离

    [Header("基本组件")]
    private GameObject alarmSign; //遇到敌人标识
    [HideInInspector]public Animator anim;//动画器组件
    [HideInInspector]public Rigidbody2D rb;//刚体

    [Header("状态机相关")]
    EnemyBaseState currentState; //创建状态机对象
    [HideInInspector]public int animState;//控制动画状态的参数
    public PatrolState patrolState = new PatrolState();//实例化巡逻类对象
    public AttackState attackState = new AttackState();//实例化攻击类对象

    public void Awake()
    {
        InitComponent();//初始化组件
    }
    protected virtual void Start()
    {
        TransitionState(patrolState);//进入巡逻类,并初始化目标点
    }

    protected virtual void Update()
    {
        anim.SetBool("dead",isDead); //实时同步动画器中的bool值
        
        if(isDead)  //假如角色死亡
        {
            rb.velocity = Vector2.zero; //将速度设置为0
            return; //直接返回,不执行下列代码
        }

        currentState.OnUpdate(this);//各个状态中每一帧都需要执行的函数

        anim.SetInteger("state",animState);//设置动画机中的状态
    }

    public void TransitionState(EnemyBaseState state)//切换状态类的函数
    {
        currentState = state;
        currentState.EnterState(this);//进入状态需要执行的函数
    }

    public virtual void InitComponent()//初始化组件
    {
        anim = GetComponent<Animator>();
        rb = GetComponent<Rigidbody2D>();

        //获得第一个子类的GameObject组件
        alarmSign = transform.GetChild(0).gameObject;
    }

    public virtual void Movement()//移动函数
    {
        if(targetPoint != null)
        {
            transform.position = Vector2.MoveTowards(transform.position, targetPoint.position, speed*Time.deltaTime);
        }
        FilpDirection();//转向函数
    }

    protected virtual void Jump(){}//跳跃函数

    public virtual void AttackAction()//基础攻击函数
    {
       	//如果攻击距离大于与目标点的距离
        if(Vector2.Distance(targetPoint.position, transform.position) < attackRange)
        {
            //且攻击cd已经转好
            if(Time.time > attackRange + nextAttack)
            {
                //播放攻击动画
                anim.SetTrigger("attack");
                //对目标造成伤害的具体实现,将用外部的物体的方法执行

                //刷新cd
                nextAttack = Time.time + attackRange;
            }
        }
    }

    public virtual void SkillAttack()//特殊攻击函数
    {
        if(Vector2.Distance(targetPoint.position, transform.position) < skillRange)
        {
            if(Time.time > attackRange + nextAttack)
            {
                //播放攻击动画
                anim.SetTrigger("skill");

                //刷新cd
                nextAttack = Time.time + attackRange;
            }
        }
    }

    protected void FilpDirection()//转向函数
    {
       /*由于不同的素材原来的方向不同,我们单纯的使用改变 Transform 缩放的方法,会出现应素材不同而代码不同的问题,大大降低了代码的复用性
       为提高代码复用性,这里采用修改 Tramsform 旋转的方法,达到调整敌人朝向的问题*/
        
        if(transform.position.x < targetPoint.position.x)//当目标点在右侧
        {
            //将y方向的旋转改为0
            transform.rotation = Quaternion.Euler(0f, 0f, 0f);
        }
        else
        {
            //将y方向的旋转改为180
            transform.rotation = Quaternion.Euler(0f, 180f, 0f);
        }
    }

    public void SwitchPoint()//转换默认目标点
    {
        //将目标切换为最近的目标点
        if(Mathf.Abs(pointA.position.x - transform.position.x) > Mathf.Abs(pointB.position.x - transform.position.x))
        {
            targetPoint = pointA;
        }
        else
        {
            targetPoint = pointB;
        }
    }

    //受伤函数(实现接口)
    public void GetHit(float damage) 
    {
        health -= damage;

        if(health < 1)
        {
            health = 0;
            isDead = true;
        }

        anim.SetTrigger("hit");
    }

    //以下函数均为碰撞体进入时会触发的函数
    public void OnTriggerStay2D(Collider2D collision) 
    {
        if(!attackList.Contains(collision.transform))
        {
            attackList.Add(collision.transform); 
        } 
    }

    public void OnTriggerExit2D(Collider2D collision) 
    {
        attackList.Remove(collision.transform);
    }
    
}

标签:Enemy,函数,攻击,void,目标,Unity,敌人,public
From: https://www.cnblogs.com/MMMMrD/p/17832321.html

相关文章

  • Unity-Light(含Unity2021-2d项目升级Urp渲染管线)
    Unity-Light(含Unity2021-2d项目升级Urp渲染管线)普通渲染管线(比较老旧的光效升级方式,已舍弃)​ 要使场景和角色拥有光效,那就得让他们先暗下来,给他们添加相应的材质场景材质的添加​ 选中需要添加材质的场景,在右侧框内的“材质”菜单中,选中Default-Diffuse材质人物材质的添加​......
  • Unity-单例模式
    Unity-单例模式前言​ 对于某些特殊的类,我们希望在整个程序的生命周期只创建一个该类的对象,或是希望在其他类没有持有该类的引用,就可以调用该类中的函数,我们就需要将这个类写成单例模式单例的简单实现publicclassTest(){ pubicabstractTestInstance;//创建程序中该......
  • Unity-射线
    Unity-射线前言​ 在游戏开发的过程中,许多功能的实现都需要物理检测,而发射射线是Unity中物理检测的通用方法。例如,我们需要检测玩家(Player)脚下是否是地面(图层为Ground),只需要从脚底发射一条射线,检测Player脚下GameObject的图层是否为Ground即可。​ 射线和物理检测何其重要,......
  • Unity-对象池 & 多对象池
    Unity-对象池&多对象池简介​ 在制作游戏的过程中,人物和boss的设计往往会有释放多个子弹的攻击方式。我们可以用直接创造子弹然后销毁的办法来实现这些技能的效果,但当子弹开始变多,游戏就会不断的消耗我们的内存。为了解决这个问题,开发者们就引入了状态机。普通对象池创建思......
  • Unity-协程
    Unity-协程协程的简单实现​ 一般的程序执行都是线性的,也就是必须一行一行的执行代码。​ 使用Unity提供的协程,就可以类似于开辟另一条线程,调整根据你所写的代码,调整下一行代码执行的时间。项目示例​ 下面的例子是一个U3DDemo中的代码,实现最简单的Enemy追击Player的......
  • Unity3D 如何用unity引擎然后用c#语言搭建自己的服务器
    Unity3D是一款强大的游戏开发引擎,可以用于创建各种类型的游戏。在游戏开发过程中,经常需要与服务器进行通信来实现一些功能,比如保存和加载游戏数据、实现多人游戏等。本文将介绍如何使用Unity引擎和C#语言搭建自己的服务器,并给出技术详解以及代码实现。对惹,这里有一个游戏开发交流......
  • Unity播放Hap格式视频 (大分辨下流畅播放以及帧同步解决方案)
    前言:之前对于项目上播放大分辨率视频(特别是大于4k分辨率的)常常会感觉相当的头疼,最开始使用的是Unity自带的VideoPlayer,发现效果并不理想,更换为AVPro后发现播放是流畅了但不能操作视频快进,只要一快进就会出现卡顿,最后偶然间发现了一款用于播放Hap格式视......
  • Unity MMORPG 背包系统如何设计
    前言MMORPG游戏中背包系统是很重要的一个模块,大部分的背包系统的讲解,都是讲如何设计UI,如何显示这些,其实这些东西并不是背包系统的核心,接下来我们来分析一下背包系统的数据结构如何设计,能让策划和程序很好的工作,以及非常方便的扩展。对惹,这里有一个游戏开发交流小组,希望大家可......
  • Unity Android Studio 设置自启动应用
    前言最近有需求,需把Unity软件发布到android平台后开机启动应用,在网上查了很多资料,现整理如下Unity部分新建项目,平台设置为android tips:需要勾选ExportProject以便于导入AndroidStudio,使用Unity版本为2021.3.32f1AndroidStudio部分 androidstudio......
  • Unity底层是如何处理C#的
    在面试中,我们经常会被问到Unity的底层是如何处理C#,本节给通过一下3个点来给大家详细的分析这个问题:(1) C#的发展历史;(2) Unity为什么用C#;(3) il2cpp解决了什么问题;  C#的发展历史 C#没有出来之前,当时Java凭借Java虚拟机+Java字节码解释执行,让Java代码移植编写......