Unity教程(十五)敌人战斗状态的实现
Unity开发2D类银河恶魔城游戏学习笔记
Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进
Unity教程(十)Tile Palette搭建平台关卡
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();}}相关文章:
Unity教程(十五)敌人战斗状态的实现
Unity开发2D类银河恶魔城游戏学习笔记 Unity教程(零)Unity和VS的使用相关内容 Unity教程(一)开始学习状态机 Unity教程(二)角色移动的实现 Unity教程(三)角色跳跃的实现 Unity教程&…...
利用深度学习实现验证码识别-3-ResNet18
在当今数字化时代,验证码作为一种重要的安全验证手段,广泛应用于各种网络场景。然而,传统的验证码识别方法往往效率低下,准确率不高。今天,我们将介绍一种基于 ResNet18 的验证码识别方法,它能够高效、准确…...
UDP通信实现
目录 前言 一、基础知识 1、跨主机传输 1、字节序 2、主机字节序和网络字节序 3、IP转换 2、套接字 3、什么是UDP通信 二、如何实现UDP通信 1、socket():创建套接字 2、bind():绑定套接字 3、sendto():发送指定套接字文件数据 4、recvfrom():接收指定地址信息的数据 三…...
windows下使用vscode编写运行以及调试C/C++
vscode支持类似于vs的断点调试c/c,也可以直接编译&运行c/c 先是编译运行 c/c的方法 微软官方起初设定的科学做法(这也是现在的科学做法)是通过在vscode集成控制台写命令行的方式来实现编译运行程序的,但也可以通过code runner插件…...
python容器4--集合
(1) 什么是集合 集合:Python中使用关键字set表示 集合中存储多个、没有顺序的、不能重复的、可以是不同类型的多个数据! (2) 集合的声明 python中通过set()或者花括号声明空集合、非空集合 # 声明空集…...
MySQL record 01 part
更改密码: alter user rootlocalhost identified with mysql_native_password by ‘123456’; 注意: 在命令行方式下,每条MySQL的命令都是以分号结尾的,如果不加分号,MySQL会继续等待用户输入命令,直到MyS…...
2024年高教社杯全国大学生数学建模竞赛A题思路(2024数学建模国赛A题思路)
A题 “板凳龙” 闹元宵 “板凳龙”,又称“盘龙”,是浙闽地区的传统地方民俗文化活动。人们将少则几十条,多则上百条的板凳首尾相连,形成蜿蜒曲折的板凳龙。盘龙时,龙头在前领头,龙身和龙尾相随盘旋,整体呈圆盘状。一般来说,在舞龙队能够自如地盘入和盘出的前提下,盘龙…...
Go语言基础语法 20240904更新
代码开源地址 https://github.com/zhangdapeng520/zdpgo_basic 快速入门 示例代码: package mainimport "fmt"func main() {fmt.Println("Hello World") }第一行代码 package 用来声明包名。main包时整个程序的入口包,在一个Go语…...
软件测试 | 性能测试
性能测试的概念 为了 发现系统性能问题 或 获取系统性能相关指标 而进行的测试。 常见性能测试指标 并发数 即并发用户数。 从业务层面看,并发用户数指的是 实际使用系统的用户总数。从后端服务器层面看,指的是 web服务器在一段时间内处理浏览器请求而建…...
Arduino IDE
Arduino IDE(集成开发环境)的安装过程是一个相对直观且易于操作的流程,主要步骤包括下载、安装和配置。以下将详细阐述Arduino IDE的安装过程,同时提供一些背景信息和注意事项,确保安装过程顺利进行。 一、Arduino ID…...
统计学习方法与实战——统计学习方法之感知机
感知机 感知机三要素分析模型策略损失函数选择 算法原始形式对偶形式 相关问题 例子iris数据集分类实战数据集查看 显示结果sklearn 实战感知机 习题解答习题2.1解题步骤反证法 习题2.2习题2.3凸壳线性可分线性可分证明凸壳不相交证明充分性:凸壳不相交\Rightarrow⇒…...
语言学习有捷径?没错!这4个方法让你轻松搞定英语翻译
现在全世界都在用英语,这门语言真的超级重要。不管你是学习、上班还是出去玩,会点英语翻译肯定能帮上大忙。但是,对很多人来说,翻译英语还是挺难的。别急,今天我就来给你介绍几个超好用的英语翻译工具,让你…...
聊一聊大型网站稳定性建设思路
目录 架构阶段的稳定性建设项目 编码阶段的稳定性建设 测试阶段的稳定性建设 发布阶段的稳定性建设 运行阶段的稳定性建设项目 故障发生时的稳定性建设 网站稳定性的建设是一项综合的系统工程,就像人的健康一样,如果平时不注意健康饮食、不注意锻炼…...
Nginx常用配置
Windows版本Nginx开机自启动 可直接下载已经配置好的文件,点击即可下载:Windows版本Nginx1.26.0 下载WinSW v2.12.0 首先从https://github.com/winsw/winsw/releases下载WinSW v2.12.0 下载Nginx 下载地址https://nginx.org/en/download.html 修…...
前端开发中遇到的小问题以及解决方案记录2
1、H5中适配屏幕的工具-postcss-px-to-viewport postcss-px-to-viewport。因为设计稿一般给的都是375px宽度的,所以假如一个字体是16px,那么在开发中不能直接写死为16px,因为各个厂商的手机屏幕大小是不同的,所以要根据屏幕大小去…...
Qt-常用控件(3)-输入类
1. QLineEdit QLineEdit 用来表示单行输入框.可以输入一段文本,但是不能换行 核心属性 属性说明text输入框中的文本inputMask输入内容格式约束maxLength最大长度frame是否添加边框echoMode显示方式. QLineEdit::Normal :这是默认值,文本框会显示输入的文本。QLineE…...
使用Docker启动Redis容器并映射端口
在现代软件开发中,Redis 是一种非常流行的开源内存数据结构存储,通常用作数据库、缓存或消息传递系统。Docker 是一个开源的应用容器引擎,它允许开发者打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux …...
用fastapi搭建cpca地址提取服务接口
以前的客户地址比较乱,现在想提取出省份城市, 开始了解分词技术,后发现python有这样的库 cpca提取地址挺不错,可以从垃圾地址中提取省市区以及区号。 文章会用fastapi搭建服务端 通过post调用cpca,提取来了后&#…...
libvncclient编写多线程qt的VNC客户端
概述 使用qt和libvncclient编写vnc的客户端程序,多线程读写,拒绝卡顿。qt环境:5.15.3libvncclient:0.9.14下载地址:https://github.com/LibVNC/libvncserver/releases 编译libvncclient 打开CMakeList文件ÿ…...
视频处理基础之gradio框架实现
这些函数是用于处理视频文件的Python代码片段,它们依赖于ffmpeg和ffprobe工具,这些工具是FFmpeg项目的一部分,用于处理视频和音频数据。下面是每个函数的用途和用法的总结: 1. ffmpeg_installed() 函数: - 用途&am…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
