我们来用Unity做个2D像素boss战

这篇具有很好参考价值的文章主要介绍了我们来用Unity做个2D像素boss战。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

从个人角度出发,《死亡细胞》有很多让我爱不释手的特征:优秀的操作手感,碎片化的剧情,变化多端的随机地图,多种特点明显的敌人,丰富的装备(技能)系统——以及精彩炫酷的Boss战。无论是守护者居所的大型Boss巨人,还是时钟室的小型Boss时间刺客,在和他们战斗的过程中都能感到一种很巧妙的压迫感,这种适量的压迫感让我注意力高度集中,不断从失败中学习——直到打败Boss的那一瞬间,心流转化为一种前所未有的成就感。


基于这样的想法动了写一篇小教程的念头,让我们一起来制作一个简单的Boss战。

首先想一下一场Boss战中都需要一些什么?

一只等着玩家击(rou)败(lin)的Boss怪物;

一名由玩家操作的角色;

以及一个看着不伤眼睛的场景。

当然其中的每个部分还有一些细分的组成模块。接下来一步一步地制作咱们的Boss战吧。

首先是新建Unity2D工程,然后将准备好的素材导进去。这里大家可以去Unity商店里面或者itch.io网站上去下载自己需要的素材。

https://assetstore.unity.com/

https://itch.io/

这里我选择的素材是一只火焰蜈蚣。导入好素材之后,我们发现大多数素材都是将一组完整的动画放在一张图中,而我们要做的就是把一张整合图中的每一张精灵图都“割”出来,具体的操作如下: 

我们来用Unity做个2D像素boss战

我们来用Unity做个2D像素boss战

 

PS:这里不建议使用Automatic模式进行切割,割出来的图片大小不一,就可能导致每张精灵图的中心点不一样。如果大家找的素材像我的这样是排列好的,就可以使用Grid By Cell Count模式来切割,保证每一张大小和中心点一致。

好了,我们照上面的流程将素材全部准备好之后,将每个动画都制作出来,并取好名字,方便之后使用。现在选择一张精灵图来作为Boss在游戏中的实体,我们来为Boss提前挂上要用到的各种组件。 

我们来用Unity做个2D像素boss战

 

PS:这里选择PolygonCollider2D(多边形碰撞体)的原因在于,这里的Boss不是一个常规的形状,而PolygonCollider2D支持可以手动拖拽出我们想要的形状,但是如果后面有在游戏中进行碰撞体更改的需求会很麻烦。因此这里大家也可以用固定形状的几个碰撞体来拼接出Boss的大体形状,从而避免上述问题。

Boss的AI中应该有一些什么样的行为?

脑袋稍微一转就能想到待机,受伤,攻击,死亡,移动等。这就是最简单的敌人AI中应该存在的状态。由于今天咱们做的不是普通的杂兵,好歹有一个title:Boss(我也是有尊严的!)。所以这里将状态划分为:待机,受伤,死亡,冲撞技能,火球技能,火柱技能。

现在我们再考虑一下各状态之间的切换条件是什么?是怎样的一个流程?

考虑完之后就可以直接上流程图了:

我们来用Unity做个2D像素boss战

想清楚流程以后,就可以开始编写有限状态机(FSM)了。由于这次的项目内容不多,所以我选择的是先用enum(枚举类)将Boss的所有状态列举出来,再使用switch case语句来将不同状态之间的对应方法区分开来。废话少说,上代码。

public enum BossState//将所有状态一一列出来
{
    FireBall,
    FirePillar,
    Dash,
    Idle,
    BeHit,
    Death,
}

BossState state;

void Awake
{
    state=BossState.Idle;//将Boss的初始状态设置为Idle
}

void Update
{
    switch (state)
    {
      switch (state)
        {
            case BossState.FireBall:
                {
                    FireBallSkill();
                    break;
                }
            case BossState.FirePillar:
                {
                    FirePillarSkill();

                    break;
                }
            case BossState.Dash:
                {
                    DashSkill();
                    break;
                }
            case BossState.Idle:
                {
                    IdleProccess();
                    break;
                }
            case BossState.BeHit:
                {
                    BeHitProccess();
                    break;
                }
            case BossState.Death:
                {
                   DeathProccess();
                    break;
                }
    }
}


像这样,我们就搭好了一个最简单的有限状态机的架子,接下来只需要去编写对应状态下的逻辑就行了。

PS:这种有限状态机的优点是简单易懂,但同时缺点也很明显——将所有的代码都放在了一起,不管是后期的维护修改,还是添加新的内容,都会很麻烦。所以我们可以将状态机细分出:状态机系统,状态基类,挂载在对象的实际脚本,利用继承状态基类来编写不同的状态,状态机系统控制状态方法调用和增加删除,再在对象身上的实际脚本中通过注册事件来将各个状态添加进来。这里就给大家“抛个砖”,感兴趣的朋友可以自行研究一下。

现在就按照顺序编写不同状态中的代码吧。

FireBallSkill和FirePillarSkill

将这两个状态放在一起讲是因为它们的主要逻辑相似。FireBall中Boss会连续喷出火球A,这些火球A呈散弹状的扇形排列向前方飞行,碰到玩家之后就对玩家造成伤害。FirePillar是Boss在半血之后追加的技能,会召唤从天上落下的火球B,火球B的位置是随机的,碰到地面或者玩家之后会生成火柱,对玩家造成伤害。

那么先准备好两种火球的预制体。做好需要的动画,挂好需要的组件。然后给这俩都编写一个脚本,用来控制生成出来以后的飞行、攻击玩家等逻辑。下面的代码是火球A代码。

public class FireBall : MonoBehaviour
{
    GameObject Boss;
    Animator animator;
    Collider2D collider2d;
    Vector3 dir;//初始的方向
    public float Speed;//速度
    public float Damge;//伤害
    public float LifeTime;//存在时间

    void Start()
    {
        Boss = GameObject.Find("Boss");
        animator = GetComponent<Animator>();
        collider2d = GetComponent<Collider2D>();
        dir = transform.localScale;
        Speed = 5;
        Damge = 8f;
        LifeTime = 7f;
    }

    void Update()
    {
        Move();
        LifeTime -= Time.deltaTime;
        if (LifeTime <= 0 || Boss.GetComponent<FireCentipede>().isDead)
        {
            Destroy(gameObject);
        }
    }
    public void Destroy()
    {
        Destroy(gameObject);
    }
    public void Move()//飞行
    {
        if (Boss.transform.localScale.x < 0)
        {
            transform.localScale = new Vector3(dir.x, dir.y, dir.z);
            transform.position += Speed * -transform.right * Time.deltaTime;
        }
        else if (Boss.transform.localScale.x > 0)
        {
            transform.localScale = new Vector3(-dir.x, dir.y, dir.z);
            transform.position += Speed * transform.right * Time.deltaTime;
        }
    }
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))//对玩家造成伤害
        {
            animator.Play("Hit");
            collider2d.enabled = false;
            if (transform.position.x < collision.transform.position.x)
            {
                collision.GetComponent<PlayerCharacter>().BeHit(Vector2.right,Damge);
            }
            else if (transform.position.x >= collision.transform.position.x)
            {
                collision.GetComponent<PlayerCharacter>().BeHit(Vector2.left,Damge);
            }
        }
        else if (collision.CompareTag("Ground"))//碰到地面爆炸
        {
            animator.Play("Hit");
            collider2d.enabled = false;
        }
    }
}
大家会发现Destroy方法很奇怪,只有一句话,而且在脚本中没有调用的地方。它真正的作用是作为动画帧事件,在动画播放到指定帧时进行调用。一会儿我会介绍动画帧事件的使用方法,这里咱们先继续。

预制体做好之后,咱们在Boss脚本中编写生成它们的方法。

火球A想实现出散弹的效果,主要思路就是按照一定的角度差值来依次改变每一个火球的Rotation。生成方法如下:

for (int i = -5; i < 2; i++)
  {
       GameObject fireball = Instantiate(Fireball, null);
       Vector3 dir = Quaternion.Euler(0, i * 15, 0) * -transform.right;
       fireball.transform.position = Mouth.position + dir * 1.0f;
       fireball.transform.rotation = Quaternion.Euler(0, 0, i * 15);
   }
火球B是随机生成在空中,然后向下坠落。我们将其X轴的值设置为随机值就好了。生成方法如下:

for (int i = 0; i < 5; i++)
  {
       int r = Random.Range(-14, 14);//这里的范围是平台的长度
       GameObject firepillar = Instantiate(FirePillar, null);
       firepillar.transform.position = new Vector3(r, 6, 0);
  }
然后将这两个方法设置为动画帧事件,操作如下:

双击对应的动画,弹出Animation窗口,再点击红圈处生成一个事件,将其拖拽到需要触发事件的那一帧的位置。 

我们来用Unity做个2D像素boss战

 再点击那一个指针,弹出AnimationEvent的界面,在里面的Object中选择对应的脚本,在Function中添上对应函数的名字,搞定。 

我们来用Unity做个2D像素boss战

PS:在使用动画帧事件的时候有几点需要注意:1.三个参数只能传递三者之一,要不就无参数。2.Function事件函数的来源必须是有Animator组件。3.Object如果是空的,那它就会默认选择自身所绑定的脚本作为事件函数提供。

完成动画帧事件的绑定之后,我们只要播放动画就可以自动生成出火球了。所以在FireBallSkill方法和FirePillarSkill方法中,我们只要负责播放动画、以及按照流程图中的条件和流程切换状态就万事OK啦。代码如下:

public void FireBallSkill()
    {
        C_ani.Play("Attack");
        FirePillarCd -= Time.deltaTime;//火柱技能的CD
        if (FireBallAttackTime <= 0 && !isDead)//技能释放完且没有死亡
        {
            state = BossState.Idle;
        }
        else if (isDead)
        {
            state = BossState.Death;
        }
    }
public void FirePillarSkill()
    {
        C_ani.Play("OtherAttack");
        background.isChange = true;//背景变黑
        Pillar1.GetComponent<Collider2D>().enabled = false;//关闭空中的平台
        Pillar2.GetComponent<Collider2D>().enabled = false;//关闭空中的平台
        FirePillarCd = 20f;//重置CD
        if (FirePillarAttackTime <= 0 && !isDead)
        {
            state = BossState.Idle;
        }
        else if (isDead)
        {
            state = BossState.Death;
        }
    }
这样,咱们就完成FireBall状态和FirePillar状态的编写了。

DashSkill

在Dash状态中,这里的设计是让Boss从一边突进到另一边去,过程中碰到了玩家就对玩家造成伤害。所以我们需要做的就是让Boss移动就好了。这里直接使用了Rigidbody2D实现移动。当然,也要写好切换状态的条件,在这里是到达指定点后就进行状态的切换。这一部分就不再多做阐述了。

Idle,BeHit,Death

Idle状态的主要作用是给玩家一段攻击Boss的时间——靶子时间到。另外,基于本文的设计,Boss半血以后技能都会有所加强,这些变化也放在了Idle状态中进行。而BeHit状态下就是当玩家攻击到Boss之后受伤的动画播放以及血量减少。Death状态是当Boss血量小于等于零之后播放死亡动画和删除Boss实体。代码如下:

public void IdleProccess()
    {
        C_ani.Play("Idle");
        background.isBack = true;//背景变回初始状态
        Pillar1.GetComponent<Collider2D>().enabled = true;//平台开启
        Pillar2.GetComponent<Collider2D>().enabled = true;//平台开启
        FirePillarCd -= Time.deltaTime;//火柱技能CD
        if (Hp <= MaxHp / 2 && IdleTime > 0)//半血以后增加火球/火柱的释放次数
        {
            FireBallAttackTime = 5;
            FirePillarAttackTime = 7;
            IdleTime -= Time.deltaTime;
        }
        else if (Hp > MaxHp / 2 && IdleTime > 0)
        {
            FireBallAttackTime = 3;
            FirePillarAttackTime = 7;
            IdleTime -= Time.deltaTime;
        }
        if (IdleTime <= 0 && !isHit && !isDead)
        {
            if (FirePillarCd <= 0 && Hp <= MaxHp / 2)
            {
                state = BossState.FirePillar;
            }
            else
            {
                state = BossState.Dash;
            }
        }
        else if (isHit && !isDead)
        {
            state = BossState.BeHit;
        }
        else if (isDead)
        {
            state = BossState.Death;
        }
    }

    public void BeHitProccess()
    {
        C_ani.Play("BeHit");//播放受击动画
        IdleTime -= Time.deltaTime;
        if (!isHit && !isDead)
        {
            state = BossState.Idle;
        }
        else if (isDead)
        {
            state = BossState.Death;
        }
    }
    public void BeHit(float Damge)//玩家HitBox攻击到Boss调用的方法
    {
        Hp -= Damge;
        isHit = true;
        HitAudio.Play();//音频的播放
        EffectAni.Play("1");//打击效果的播放
    }
好,Boss的主要逻辑就编写完毕了。

设置好一些技能CD、Boss血量,技能伤害等数据,再简单的制作一个玩家控制的主角,搭建一个场景,挂上Cinemachine来实现摄像机的跟随,范围限制等。

 

工程地址如下:

https://pan.baidu.com/s/1czDhXjecYDR1hzbEwtbpYg

提取码: im9u 文章来源地址https://www.toymoban.com/news/detail-420785.html

到了这里,关于我们来用Unity做个2D像素boss战的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 线程和进程的区别(从JVM角度出发)

    线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。 根本区别:进程是操作系

    2024年01月22日
    浏览(91)
  • DSP28335学习笔记-产生PWM波 ——从应用角度出发

    关于DSP28335的书籍有很多,但都是从乏味的寄存器开始入手。对于新手可能不是很友好,因此关于DSP的学习记录我打算从应用入手去学习寄存器的用法和配置。 此次实验用J4模块引脚的XPWM1和XPWM2分别产生ePWM1A和ePWM1B,对应芯片GPIO0和GPIO1引脚。 黄色线为EPWM1A,紫色线为EPWM1B。

    2024年02月06日
    浏览(35)
  • 从辩证的角度看待chatGPT,我们该如何对待?

    注:本文的ChatGPT一定程度上也可以理解成其他相关的人工智能机器人等 ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI 研发的聊天机器人程序 ,于2022年11月30日发布。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来进行对话,

    2024年02月08日
    浏览(59)
  • 从辩证的角度看待chatGPT,我们将何去何从?

    注:本文的ChatGPT一定程度上也可以理解成其他相关的人工智能机器人等 ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI 研发的聊天机器人程序 ,于2022年11月30日发布。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来进行对话,

    2024年02月04日
    浏览(63)
  • 【AI人工智能】从技术角度看,我们离超级人工智能还有多远?

    目录 前言 超级人工智能是什么? 一、计算能力 二、算法支持 三

    2024年02月06日
    浏览(52)
  • 连接万物,创造未来,多角度看物联网技术如何影响我们的生活

    连接万物,创造未来。从智能家居到智慧医疗,从智能车联到智慧城市,物联网技术的影响已经悄然渗透到了我们的方方面面。接下来我们将从物联网技术概述、发展的历史原因、物联网技术应用、创新和挑战几个角度深度了解物联网已经深入我们日常生活。 物联网技术(

    2024年02月07日
    浏览(67)
  • 基于unity的google小恐龙游戏5----第四个BOSS

    上一篇文章我实现了第三个BOSS,蜂后,这个boss实际上跟眼珠子没有什么大区别,不过还是有一些需要斟酌写好的,无论是什么,就算他再简单也好,也总是有他的意义。我觉得搞这样的练习最重要还是体会过程,积累一些经验。最后总会有收获的。本来我是想写一些新的东

    2024年02月19日
    浏览(36)
  • 从计算机视觉(Computer Vision)的角度出发,从传统机器学习的特征工程、分类器设计和优化,到深度学习的CNN架构设计、训练优化、模型压缩与推理部署,详细阐述了图像识别领域最新的技术发展方向

    作者:禅与计算机程序设计艺术 在现代信息技术的快速发展过程中,图像识别技术越来越重要。早期的人工智能算法主要侧重于特征提取、分类或回归任务。近几年,随着神经网络(Neural Networks)在图像识别领域的不断突破,很多研究人员将目光投向了深度学习(Deep Learni

    2024年02月10日
    浏览(43)
  • P2-Net:用于2D像素与3D点匹配的局部特征的联合描述符和检测器(ICCV 2021)

    1)论文 :P2-Net: Joint description and detection of local features for pixel and point matching 2)论文地址 : https://openaccess.thecvf.com/content/ICCV2021/papers/Wang_P2-Net_Joint_Description_and_Detection_of_Local_Features_for_Pixel_ICCV_2021_paper.pdf 3)代码地址 :https://github.com/BingCS/P2-Net 4)论文来源 :ICCV 2021 5)论文作

    2023年04月09日
    浏览(58)
  • 我们的愿景是在个人计算机上实现量子霸权

    简介 采用扩展的量子二进制算法。在经典计算机上实现量子计算机。我们的景愿是在个人计算机上实现量子霸权。 此计算机的字长是64位,等效数据位为32位字长的量子计算机。我们采用量子扩展二进制,共有4个字符:0,1,Q,P可以进行经典和量子算法。我们将在汇编层实现字

    2024年02月04日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包