Unity教程(十五)敌人战斗状态的实现

Unity开发2D类银河恶魔城游戏学习笔记

Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进

Unity教程(十)Tile Palette搭建平台关卡
Unity教程(十一)相机
Unity教程(十二)视差背景

Unity教程(十三)敌人状态机
Unity教程(十四)敌人空闲和移动的实现
Unity教程(十五)敌人战斗状态的实现
Unity教程(十六)敌人攻击状态的实现


如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录


文章目录

  • Unity开发2D类银河恶魔城游戏学习笔记
  • 前言
  • 一、概述
  • 二、实现Player的检测
  • 三、创建接地状态
  • 三、实现战斗状态
    • (1)创建战斗状态
    • (2)状态切换
  • 总结 完整代码
    • Enemy.cs
    • SkeletonGroundedState.cs
    • SkeletonIdleState.cs
    • SkeletonMoveState.cs
    • SkeletonBattleState.cs
    • Enemy_Skeleton.cs


前言

本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。

本节实现敌人的战斗状态。
对应b站视频:
【Unity教程】从0编程制作类银河恶魔城游戏P50

一、概述

本节中我们实现骷髅的战斗状态。
骷髅小怪在检测到玩家时,会向着玩家移动。在移动到一定距离时停止移动进入攻击状态。
由于我们想让骷髅在空闲和移动时均能转入战斗状态,我们创建超级状态接地状态,直接写接地状态到战斗状态的切换即可。
状态切换条件如下:
在这里插入图片描述

在这里插入图片描述

二、实现Player的检测

玩家的检测在Enemy基类中实现。
碰撞检测的详细讲解见Unity教程(四)碰撞检测

这里我们不再仅仅使用bool类型作为函数的返回值,而是使用RaycastHit2D返回更详细的信息。
RaycastHit2DUnity官方手册
Raycast包含的变量如下表:

变量介绍
centroid用于执行投射的图元的质心
collider射线命中的碰撞体
distance从射线原点到撞击点的距离
fraction射线上发生命中的距离的分数
normal射线命中的表面的法线矢量
point世界空间中射线命中碰撞体表面的点
rigidbody附加到命中的对象的 Rigidbody2D
transform命中的对象的变换

我们要新建一个过滤器WhatIsPlayer进行检测。
这里我们以骷髅的位置为起点,向它面向的方向发射射线检测,检测的最大距离设置为50.

为了方便调试我们还要绘制出攻击检测的线条,在Enemy里对实体中的OnDrawGizmos()函数进行重写。
攻击检测的线条绘制:起点为Enemy_Skeleton的位置,向右attackDistance找到终点。
在Enemy中添加如下代码:

    [SerializeField] protected LayerMask WhatIsPlayer;[Header("Attack Info")]public float attackDistance;public virtual RaycastHit2D IsPlayerDetected()=>Physics2D.Raycast(transform.position, Vector2.right * facingDir, 50 ,WhatIsPlayer);protected override void OnDrawGizmos(){base.OnDrawGizmos();Gizmos.color = Color.yellow;Gizmos.DrawLine(transform.position, new Vector3(transform.position.x + attackDistance * facingDir, transform.position.y));}

将Enemy_Skeleton的大小调大一点
在这里插入图片描述

在这里插入图片描述

创建一个新的WallCheck,并拖到Enemy_Skeleton中
在这里插入图片描述
在这里插入图片描述
将Wallcheck往下拖一些,以免与攻击检测重合。
在Enemy中重写OnDrawGizmos(),换一个颜色绘制出攻击检测。
给attackDistancce赋一个恰当的值。
在这里插入图片描述
在这里插入图片描述

将Enemy_Skeleton中WhatIsPlayer改为Player
在这里插入图片描述
新建Player和Enemy层。
在这里插入图片描述
在这里插入图片描述
Player改为Player层,Enemy_Skeleton改为Enemy层,修改时选择将它们所有子物体也改为对应层次。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、创建接地状态

创建接地状态SkeletonGroundedState,它继承自EnemyState。
在子菜单中创建构造函数,生成重写。
添加Enemy_Skeleton enermy,传递骷髅小怪独有的变量和函数,并修改构造函数。

//SkeletonGroundedState:骷髅接地状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkeletonGroundedState : EnemyState
{protected Enemy_Skeleton enemy;public SkeletonGroundedState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy,string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName){enemy=_enemy;}public override void Enter(){base.Enter();}public override void Exit(){base.Exit();}public override void Update(){base.Update();}
}

将SkeletonIdleState和SkeletonMoveState的父类改为SkeletonGroundedState,这时两个子类构造函数参数与父类相同,可以删除原有构造函数和Enemy_Skeleton enemy,直接在子菜单重新生成一个构造函数了。
在这里插入图片描述

三、实现战斗状态

(1)创建战斗状态

创建战斗状态SkeletonBattleState,它继承自EnemyState。
在子菜单中创建构造函数,生成重写。添加Enemy_Skeleton enermy,并修改构造函数。

    private Enemy_Skeleton enemy;public SkeletonBattleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName){enemy=_enemy;}

在EnemySkeleton类中创建battleState。这里条件变量仍然选择“Move”,因为战斗状态仍然播放移动动画。

 #region 状态public SkeletonIdleState idleState { get; private set; }public SkeletonMoveState moveState { get; private set; }public SkeletonBattleState battleState { get; private set; }#endregionprotected override void Awake(){base.Awake();idleState = new SkeletonIdleState(stateMachine,this,this,"Idle");moveState = new SkeletonMoveState(stateMachine, this,this, "Move");battleState = new SkeletonBattleState(stateMachine, this, this, "Move");}

(2)状态切换

当检测到玩家时,骷髅由接地状态转换为战斗状态。
在SkeletonGroundedState的Update()函数中添加

    public override void Update(){base.Update();if(enemy.IsPlayerDetected())stateMachine.ChangeState(enemy.battleState);}

骷髅进入战斗状态时,要随玩家位置的变化改变移动方向。因此我们引入player的位置这个变量,通过它与骷髅位置的对比确定战斗状态骷髅移动的方向。

player.position.x > enemy.transform.position.x,玩家在骷髅右边,移动方向向右
player.position.x < enemy.transform.position.x,玩家在骷髅左边,移动方向向左

//SkeletonBattleState:骷髅战斗状态
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkeletonBattleState : EnemyState
{private Transform player;private Enemy_Skeleton enemy;private int moveDir;public SkeletonBattleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName){enemy=_enemy;}public override void Enter(){base.Enter();player = GameObject.Find("Player").transform;}public override void Exit(){base.Exit();}public override void Update(){base.Update();if(player.position.x > enemy.transform.position.x)moveDir = 1;else if(player.position.x < enemy.transform.position.x)moveDir = -1;enemy.SetVelocity(enemy.moveSpeed * moveDir, enemy.rb.velocity.y);}
}

效果如下:
在这里插入图片描述

而当玩家进入骷髅的攻击距离时,骷髅停止移动切换为攻击状态。根据上述内容所讲,骷髅与玩家的距离可由RaycastHit2D里的distance获得。攻击状态我们先控制输出代替,具体攻击状态内容我们下一节再进行实现。
Update中添加如下代码:

    public override void Update(){base.Update();if (enemy.IsPlayerDetected()){if (enemy.IsPlayerDetected().distance < enemy.attackDistance){Debug.Log("attack");enemy.ZeroVelocity();return;}}if(player.position.x > enemy.transform.position.x)moveDir = 1;else if(player.position.x < enemy.transform.position.x)moveDir = -1;enemy.SetVelocity(enemy.moveSpeed * moveDir, enemy.rb.velocity.y);}

在这里插入图片描述

总结 完整代码

Enemy.cs

添加碰撞检测和攻击距离变量,实现攻击检测的函数和攻击检测的绘制

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Enemy : Entity
{[SerializeField] protected LayerMask WhatIsPlayer;[Header("Move Info")]public float moveSpeed = 1.5f;public float idleTime = 2.0f;[Header("Attack Info")]public float attackDistance;public EnemyStateMachine stateMachine;protected override void Awake(){base.Awake();stateMachine = new EnemyStateMachine();}protected override void Update(){base.Update();stateMachine.currentState.Update();}public virtual RaycastHit2D IsPlayerDetected()=>Physics2D.Raycast(transform.position, Vector2.right * facingDir, 50 ,WhatIsPlayer);protected override void OnDrawGizmos(){base.OnDrawGizmos();Gizmos.color = Color.yellow;Gizmos.DrawLine(transform.position, new Vector3(transform.position.x + attackDistance * facingDir, transform.position.y));}
}

SkeletonGroundedState.cs

创建接地状态,由移动和空闲状态继承。实现切换到战斗状态。

//SkeletonGroundedState:骷髅接地状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkeletonGroundedState : EnemyState
{protected Enemy_Skeleton enemy;public SkeletonGroundedState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy,string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName){enemy=_enemy;}public override void Enter(){base.Enter();}public override void Exit(){base.Exit();}public override void Update(){base.Update();if (enemy.IsPlayerDetected())stateMachine.ChangeState(enemy.battleState);}
}

SkeletonIdleState.cs

修改构造函数

//SkeletonIdleState:骷髅空闲状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkeletonIdleState : SkeletonGroundedState
{public SkeletonIdleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton enemy, string _animBoolName) : base(_stateMachine, _enemyBase, enemy, _animBoolName){}public override void Enter(){base.Enter();stateTimer = enemy.idleTime;}public override void Exit(){base.Exit();}public override void Update(){base.Update();if (stateTimer < 0)stateMachine.ChangeState(enemy.moveState);}
}

SkeletonMoveState.cs

//SkeletonMoveState:骷髅移动状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkeletonMoveState : SkeletonGroundedState
{public SkeletonMoveState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton enemy, string _animBoolName) : base(_stateMachine, _enemyBase, enemy, _animBoolName){}public override void Enter(){base.Enter();}public override void Exit(){base.Exit();}public override void Update(){base.Update();enemy.SetVelocity(enemy.moveSpeed*enemy.facingDir,enemy.rb.velocity.y);if(!enemy.isGroundDetected() || enemy.isWallDetected()){enemy.Flip();stateMachine.ChangeState(enemy.idleState);}}
}

SkeletonBattleState.cs

创建战斗状态,实现切换到攻击状态,和随玩家移动改变方向。

//SkeletonBattleState:骷髅战斗状态
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkeletonBattleState : EnemyState
{private Transform player;private Enemy_Skeleton enemy;private int moveDir;public SkeletonBattleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName){enemy=_enemy;}public override void Enter(){base.Enter();player = GameObject.Find("Player").transform;}public override void Exit(){base.Exit();}public override void Update(){base.Update();if (enemy.IsPlayerDetected()){if (enemy.IsPlayerDetected().distance < enemy.attackDistance){Debug.Log("attack");enemy.ZeroVelocity();return;}}if(player.position.x > enemy.transform.position.x)moveDir = 1;else if(player.position.x < enemy.transform.position.x)moveDir = -1;enemy.SetVelocity(enemy.moveSpeed * moveDir, enemy.rb.velocity.y);}
}

Enemy_Skeleton.cs

创建战斗状态

//Enemy_Skeleton:骷髅敌人
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Enemy_Skeleton : Enemy
{#region 状态public SkeletonIdleState idleState { get; private set; }public SkeletonMoveState moveState { get; private set; }public SkeletonBattleState battleState { get; private set; }#endregionprotected override void Awake(){base.Awake();idleState = new SkeletonIdleState(stateMachine,this,this,"Idle");moveState = new SkeletonMoveState(stateMachine, this,this, "Move");battleState = new SkeletonBattleState(stateMachine, this, this, "Move");}protected override void Start(){base.Start();stateMachine.Initialize(idleState);}protected override void Update(){base.Update();}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/53859.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

react js 路由 Router

完整的项目,我已经上传了 资料链接 起因, 目的: 路由, 这部分很难。 原因是, 多个组件,进行交互,复杂度比较高。 我看的视频教程 1. 初步使用 安装: npm install react-router-dom 修改 index.js/ 或是 main.js 把 App, 用 BrowserRouter 包裹起来 2. Navigate 点击…

redis基本数据类型和常见命令

引言 Redis是典型的key-value&#xff08;键值型&#xff09;数据库&#xff0c;key一般是字符串&#xff0c;而value包含很多不同的数据类型&#xff1a; Redis为了方便我们学习&#xff0c;将操作不同数据类型的命令也做了分组&#xff0c;在官网&#xff08; Commands | Do…

TS 常用类型

我们经常说TypeScript是JavaScript的一个超级 TypeScript 常用类型 TypeScript 是 JS 的超集&#xff0c;TS 提供了 JS 的所有功能&#xff0c;并且额外的增加了&#xff1a;类型系统 所有的 JS 代码都是 TS 代码 JS 有类型&#xff08;比如&#xff0c;number/string 等&…

《JavaEE进阶》----14.<SpringMVC配置文件实践之【验证码项目】>

本篇博客介绍的是Google的开源项目Kaptcha来实现的验证码。 这种是最简单的验证码。 也是很常见的一种验证码。可以去看项目结果展示。就可以明白这个项目了。 前言&#xff1a; 随着安全性的要求越来越高、很多项目都使用了验证码。如今验证码的形式也是有许许多多、更复杂的图…

基于SpringBoot的古城墙景区管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的古城墙景区管理系…

2024数学建模国赛官方评阅标准发布!

​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑​↑…

C++类与对象(下)--最后的收尾

内部类 • 如果⼀个类定义在另⼀个类的内部&#xff0c;这个内部类就叫做内部类。内部类是⼀个独⽴的类&#xff0c;跟定义在 全局相⽐&#xff0c;他只是受外部类类域限制和访问限定符限制&#xff0c;所以外部类定义的对象中不包含内部类。 #include<iostream> using…

安装FTP服务器教程

一。安装vsftpd yum install vsftpd 二。修改配置文件&#xff0c;匿名账户具有访问&#xff0c;上传和创建目录的权限 vim /etc/vsftpd/vsftpd.conf &#xff08;红色进行设置放开YES&#xff09; local_enable&#xff1a;本地登陆控制&#xff0c;no表示禁止&#xff0c;ye…

3. 轴指令(omron 机器自动化控制器)——>MC_MoveAbsolute

机器自动化控制器——第三章 轴指令 4 MC_MoveAbsolute变量▶输入变量▶输入输出变量▶输入输出变量 功能说明▶指令详情▶时序图▶重启运动指令▶多重启动运动指令▶异常 示例程序1▶参数设定▶动作示例▶梯形图▶结构文本(ST) 示例程序2▶参数设定▶动作示例▶梯形图▶结构文…

RDMA应用场景及效果

GPU Direct 参考&#xff1a;网络架构如何支持超万卡的大规模 AI 训练&#xff1f;| AICon_芯片与网络_InfoQ精选文章 GPU 网络的情况已经发生了很大变化。每个 GPU 都有自己的内部互联&#xff0c;例如 NVIDIA 的 A100 或 H800&#xff0c;它们内部的 NVLink 互联可以达到 6…

单个 java 虚拟机 生产者消费者

一、通过 java.lang.Object#wait()&#xff0c;java.lang.Object#notify&#xff0c;java.lang.Object#notifyAll来实现 生产者&#xff0c;消费者 public abstract class Goods {protected String type;protected String goodName;protected int number;public abstract …

【Authing身份云-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

香港打工人√三天通过微软mos认证

在繁忙的香港&#xff0c;时间就是金钱&#xff0c;效率就是生命。作为一名香港的打工人&#xff0c;在这座竞争激烈的城市中&#xff0c;不断提升自我是保持竞争力的关键。最近&#xff0c;我完成了一项挑战&#xff1a;在短短三天内通过微软MOS认证大师。以下是我备考的经验分…

微调大模型:提高其代码修复能力的尝试

目录 一、作品背景&#xff1a; 二、作品目标&#xff1a; 三、作品技术方案&#xff1a; (1)标记化 (2)量化 (3) LoRA&#xff08;低秩自适应&#xff09;配置 (4)训练配置 (6)模型保存 四、作品效果&#xff1a; 一、作品背景&#xff1a; 随着大型模型技术的日益成…

SOMEIP_ETS_107: SD_Consider_Entries_Order

测试目的&#xff1a; 验证DUT在接收到包含两个条目的消息时&#xff0c;能够按照正确的顺序处理&#xff1a;首先是带有TTL 0的SubscribeEventgroup以删除订阅&#xff0c;然后是常规的SubscribeEventgroup以重新订阅。 描述 本测试用例旨在确保DUT能够正确处理订阅消息的顺…

Frida0C - Module相关API

亲爱的读者你们好啊&#xff0c;今天主要分享一下 frida 相关的学习文档&#xff0c;见文章最后一节。 Module var module Process.findModuleByAddress(Module.findBaseAddress("libc.so")) module.enumerateSymbols()enumerateSymbols 返回该 so 的符号表。 还…

Java项目: 基于SpringBoot+mybatis+maven医院管理系统(含源码+数据库+任务书+开题报告+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismaven医院管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

ffmpeg安装测试(支持cuda支持SRT)

文章目录 背景安装ffmpeg直接下载可执行文件选择版本选择对应系统版本下载测试Linux下安装 查看支持协议以及编码格式 常见错误缺少 libmvec.so.1LD_LIBRARY_PATH 错误 GPU加速测试SRT服务器搭建下载srs5.0源码解压安装配置启动 SRT推流测试SRT播放测试 背景 在音视频开发测试中…

视频监控管理平台LntonAIServer视频智能分析抖动检测算法应用场景

在视频监控系统中&#xff0c;视频画面的稳定性对于确保监控效果至关重要。抖动现象是指视频画面中存在不稳定或频繁晃动的情况&#xff0c;这可能会影响视频的清晰度和可读性。LntonAIServer通过引入抖动检测功能&#xff0c;帮助用户及时发现并解决视频流中的抖动问题&#x…

【Python】从基础到进阶(七):深入理解Python中的异常处理与调试技巧

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、异常处理概述1. 什么是异常&#xff1f;2. 异常的捕获与处理 三、常见的异常类型四、自定义异常五、调试与日志记录1. 使用assert进行调试2. 使用日志记录 六、案例&#xff1a;文件操作与异常处理1. 需求分析2…