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教程(十七)敌人战斗状态的完善
Unity教程(十八)战斗系统 攻击逻辑
Unity教程(十九)战斗系统 受击反馈
Unity教程(二十)战斗系统 角色反击
如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录
文章目录
- Unity开发2D类银河恶魔城游戏学习笔记
- 前言
- 一、概述
- 二、闪烁特效的实现
- (1)创建闪烁特效材质
- (2)实现闪烁特效脚本
- 三、击退效果的实现
- 四、攻击方向问题的修复
- 总结 完整代码
- EntityFX.cs
- Entity.cs
- PlayerPrimaryAttackState.cs
前言
本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。
本节实现战斗系统的受击反馈部分。
Udemy课程地址
对应视频:
On Hit Fx
On Hit Impact
Attack’s direction hot fix
一、概述
本节我们实现角色的受击反馈,包括闪烁特效,击退效果两部分。
为实现特效,创建了实体特效组件,用于存放作用于实体的特效。闪烁特效用两种材质交替实现。击退效果设置受击角色速度实现。
除此之外,还修复了学习过程中发现的攻击方向bug。
二、闪烁特效的实现
在开始之前我们先整理一下脚本文件,把相应脚本放入创建的文件夹中,文件整理大致如下:
(1)创建闪烁特效材质
首先我们在Materials文件夹中创建闪烁特效FlashFXMaterial。
在Project面板中
右键->Create->Material->重命名为FlashFXMaterial
Shader选择GUI->Text Shader,Text Color选择白色
更换Playerd Animator的材质看一下效果
将原始材质与这个纯白的来回交替就可以产生闪烁的特效。
(2)实现闪烁特效脚本
首先我们创建一个特效组件EntityFX,用来实现实体的特效。
特效由更换精灵的材质实现,即要调用接口,修改对应实体下Animator中SpriteRenderer组件的Material属性。
首先要在EntityFX中获取相应SpriteRenderer组件,并设置两个变量分别存储原始材质和受击后的材质。还要创建变量flashDuration,定义受击后闪烁的时长。
//EntityFX:实体特效
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class EntityFX : MonoBehaviour
{private SpriteRenderer sr;[Header("Flash FX")][SerializeField] private Material hitMat;private Material originalMat;[SerializeField] private float flashDuration;private void Start(){sr = GetComponentInChildren<SpriteRenderer>();originalMat = sr.material;}
}
接着使用协程实现材质每隔一小段时间更替一次材质。
//闪烁特性private IEnumerator FlashFX(){sr.material = hitMat;yield return new WaitForSeconds(flashDuration);sr.material = originalMat;}
在Entity中创建特效组件EntityFX并赋值。
#region 组件public EntityFX fx { get; private set; }public Rigidbody2D rb { get; private set; }public Animator anim { get; private set; }#endregion//获取组件protected virtual void Start(){fx= GetComponent<EntityFX>();rb= GetComponent<Rigidbody2D>();anim= GetComponentInChildren<Animator>();}
在Entity的Damage函数中调用闪烁特效。
public virtual void Damage(){fx.StartCoroutine("FlashFX");Debug.Log(gameObject.name + " was damaged");}
将EntityFX拖到Player上作为组件,拖入闪烁特效作为受击材质,并给闪烁持续时间赋一个合适的值。
骷髅小怪Enemy_Skeleton同理,最终效果如下:
三、击退效果的实现
击退效果我们依然用协程实现。在实体受到攻击时,设置实体后退速度,等待一小段时间后恢复原来速度设置。
在Entity中创建击退效果相关的变量,分别为击退方向,是否被击退,击退持续的时间。
[Header("Knockback Info")][SerializeField] protected Vector2 knockbackDirection;[SerializeField] protected float knockbackDuration;protected bool isKnocked;
创建一个协程HitKnockback设置击退速度。一个实体受到攻击后,后退方向与它面向的方向相反。这里存在一个问题,在玩家从背后攻击骷髅时,一般骷髅会在检测后迅速转身,但个别时候会发生它没有转身就受到攻击的情况,这时骷髅会向它朝向的方向跌,这个问题本节暂不解决。
isKnocked用来记录实体是否处于被击退的状态,在击退效果持续期间我们需要让其他涉及设置速度的操作都不生效,在击退结束后再恢复原来状态里的速度设置。
protected virtual IEnumerator HitKnockback(){isKnocked = true;rb.velocity = new Vector2(knockbackDirection.x * -facingDir, knockbackDirection.y);yield return new WaitForSeconds(knockbackDuration);isKnocked= false;}
在速度设置和速度置零函数中添加一个判定,当处于击退状态时直接return,不执行后面设置速度的部分,直到击退结束。
#region 速度设置//速度置零public void ZeroVelocity(){if (isKnocked)return;rb.velocity = new Vector2(0, 0);}//设置速度public void SetVelocity(float _xVelocity, float _yVelocity){if (isKnocked)return;rb.velocity = new Vector2(_xVelocity, _yVelocity);FlipController(_xVelocity);}#endregion
在Damage函数中调用击退效果。
public virtual void Damage(){fx.StartCoroutine("FlashFX");StartCoroutine("HitKnockback");Debug.Log(gameObject.name + " was damaged");}
骷髅受到攻击后直接飘出去了。加大重力就可以解决这一问题。但需要注意加大重力后,骷髅的移速也会变迟缓,可以根据需要设定。
四、攻击方向问题的修复
在学习过程中,发现了一个问题,在个别先向左后向右跑的时候,角色面向右进行攻击时,攻击方向却是向左。
问题出在下图位置。在基本攻击状态PlayerPrimaryAttackState中,我们原本设置的是攻击时如果有输入,攻击方向为输入方向,但在进入攻击状态时xInput并不是最新的。
xInput的定义在状态基类PlayerState中,它的赋值在Update函数中进行,PlayerPrimaryAttackState继承自状态基类,所以每次进入攻击状态获取的xInput都是上次攻击Update中获取的。
教程里的改法是在进入状态时,xInput赋0。我选择了在进入时,重新获取一次xInput。
//进入public override void Enter(){base.Enter();xInput = Input.GetAxisRaw("Horizontal");//攻击方向问题教程改法//xInput = 0;if (comboCounter > 2 || Time.time > lastTimeAttacked + comboWindow)comboCounter = 0;player.anim.SetInteger("comboCounter",comboCounter);float attackDir = player.facingDir;if (xInput != 0)attackDir = xInput;player.SetVelocity(player.attackMovement[comboCounter].x * attackDir, player.attackMovement[comboCounter].y);stateTimer = 0.1f;}
总结 完整代码
EntityFX.cs
实体特效组件。实现闪烁特效。
//EntityFX:实体特效
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class EntityFX : MonoBehaviour
{private SpriteRenderer sr;[Header("Flash FX")][SerializeField] private Material hitMat;private Material originalMat;[SerializeField] private float flashDuration;private void Start(){sr = GetComponentInChildren<SpriteRenderer>();originalMat = sr.material;}//闪烁特效private IEnumerator FlashFX(){sr.material = hitMat;yield return new WaitForSeconds(flashDuration);sr.material = originalMat;}
}
Entity.cs
获取实体特效组件,调用闪烁特效。实现并调用击退效果,修改速度设置。
//Entity:实体类
using System.Collections;
using System.Collections.Generic;
using Unity.IO.LowLevel.Unsafe;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;public class Entity : MonoBehaviour
{[Header("Knockback Info")][SerializeField] protected Vector2 knockbackDirection;[SerializeField] protected float knockbackDuration;protected bool isKnocked;[Header("Flip Info")]protected bool facingRight = true;public int facingDir { get; private set; } = 1;[Header("Collision Info")]public Transform attackCheck;public float attackCheckRadius;[SerializeField] protected Transform groundCheck;[SerializeField] protected float groundCheckDistance;[SerializeField] protected Transform wallCheck;[SerializeField] protected float wallCheckDistance;[SerializeField] protected LayerMask whatIsGround;#region 组件public EntityFX fx { get; private set; }public Rigidbody2D rb { get; private set; }public Animator anim { get; private set; }#endregionprotected virtual void Awake(){}//获取组件protected virtual void Start(){fx= GetComponent<EntityFX>();rb= GetComponent<Rigidbody2D>();anim= GetComponentInChildren<Animator>();}// 更新protected virtual void Update(){}public virtual void Damage(){fx.StartCoroutine("FlashFX");StartCoroutine("HitKnockback");Debug.Log(gameObject.name + " was damaged");}protected virtual IEnumerator HitKnockback(){isKnocked = true;rb.velocity = new Vector2(knockbackDirection.x * -facingDir, knockbackDirection.y);yield return new WaitForSeconds(knockbackDuration);isKnocked= false;}#region 速度设置//速度置零public void ZeroVelocity(){if (isKnocked)return;rb.velocity = new Vector2(0, 0);}//设置速度public void SetVelocity(float _xVelocity, float _yVelocity){if (isKnocked)return;rb.velocity = new Vector2(_xVelocity, _yVelocity);FlipController(_xVelocity);}#endregion#region 翻转//翻转实现public virtual void Flip(){facingDir = -1 * facingDir;facingRight = !facingRight;transform.Rotate(0, 180, 0);}//翻转控制public virtual void FlipController(float _x){if (_x > 0 && !facingRight)Flip();else if (_x < 0 && facingRight)Flip();}#endregion#region 碰撞//碰撞检测public virtual bool isGroundDetected() => Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsGround);public virtual bool isWallDetected() => Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsGround);//绘制碰撞检测protected virtual void OnDrawGizmos(){Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x + wallCheckDistance, wallCheck.position.y));Gizmos.DrawWireSphere(attackCheck.position, attackCheckRadius);}#endregion
}
PlayerPrimaryAttackState.cs
修复攻击方向问题。
//PlayerPrimaryAttackState:基本攻击状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerPrimaryAttackState : PlayerState
{private int comboCounter;private float lastTimeAttacked;private float comboWindow = 2;public PlayerPrimaryAttackState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName){}//进入public override void Enter(){base.Enter();xInput = Input.GetAxisRaw("Horizontal");//攻击方向问题教程改法//xInput = 0;if (comboCounter > 2 || Time.time > lastTimeAttacked + comboWindow)comboCounter = 0;player.anim.SetInteger("comboCounter",comboCounter);float attackDir = player.facingDir;if (xInput != 0)attackDir = xInput;player.SetVelocity(player.attackMovement[comboCounter].x * attackDir, player.attackMovement[comboCounter].y);stateTimer = 0.1f;}//退出public override void Exit(){base.Exit();player.StartCoroutine("BusyFor", 0.15f);comboCounter++;lastTimeAttacked = Time.time;}// 更新public override void Update(){base.Update();if (stateTimer < 0)player.ZeroVelocity();if(triggerCalled)stateMachine.ChangeState(player.idleState);}
}
相关文章:

Unity教程(十九)战斗系统 受击反馈
Unity开发2D类银河恶魔城游戏学习笔记 Unity教程(零)Unity和VS的使用相关内容 Unity教程(一)开始学习状态机 Unity教程(二)角色移动的实现 Unity教程(三)角色跳跃的实现 Unity教程&…...

lanqiaoOJ 3744:小蓝的智慧拼图购物 ← pair+优先队列
【题目来源】https://www.lanqiao.cn/problems/3744/learning/【题目描述】 在小蓝的生日那天,他得到了一个由神秘人赠送的拼图游戏,每个拼图都有其特定的价值和相应的优惠券。小蓝决定要买下所有的拼图,但他希望能尽可能地节省花费。小蓝手中…...

Spring Boot教程之二十一:文件处理
Spring Boot – 文件处理 Spring Boot 是一种流行的、基于 Spring 的开源框架,用于开发强大的 Web 应用程序和微服务。由于它建立在 Spring 框架之上,因此它不仅具有 Spring 的所有功能,而且还包括某些特殊功能,例如自动配置、健康…...

【Linux】Linux的基本常识+指令
目录 1. 整体学习思维导图 2. 常见快捷键操作 3. 基本指令 pwd指令 whoami指令 ls 指令 touch指令 cd 指令 Stat 指令 mkdir 指令 alias指令 nano 指令 rmdir 和 rm 指令 man 指令手册 cp 命令 cat/echo/tac 指令 mv 指令 less 指令 head/tail 指令 date…...

Rocky Linux 9.3系统搭建Slurm环境【笔记】
实践环境:Rocky Linux 9.3 [root@m1 ~]# cat /etc/redhat-release Rocky Linux release 9.3 (Blue Onyx) [root@m1 ~]# uname -r 5.14.0-362.8.1.el9_3.x86_64 [root@m1 ~]#主机名和IP ● 控制节点m1:10.1.1.10 ● 计算节点c1:10.1.1.11 ● 计算节点c2:10.1.1.12 一、…...

原生微信小程序使用原子化tailwindcss
这里使用了第三方库来实现:https://weapp-tw.icebreaker.top/ 官方配置步骤一: https://weapp-tw.icebreaker.top/docs/quick-start/native/install 官方配置步骤二:https://weapp-tw.icebreaker.top/docs/quick-start/native/install-plugin 我下面的操作步骤跟官方步骤…...

《掌握Nmap:全面解析网络扫描与安全检测的终极指南》
nmap # 简介(帮助) 用法:nmap [扫描类型] [选项] {目标指定内容} 简介(帮助) 用法:nmap [扫描类型] [选项] {目标指定内容} 一、目标指定: 可以传入主机名、IP 地址、网络等。 例如&a…...

k8s-Informer概要解析(2)
Client-go 主要用在 k8s 控制器中 什么是 k8s Informer Informer 负责与 kubernetes APIServer 进行 Watch 操作,Watch 的资源,可以是 kubernetes 内置资源对象,也可以 CRD。 Informer 是一个带有本地缓存以及索引机制的核心工具包&#x…...

UE5基本数据类型
bool: 表示布尔值,只有两个取值:true 或 false,用于表示逻辑条件。int8: 表示 8 位的有符号整数,范围是 −128−128 到 127127。uint8: 表示 8 位的无符号整数,范围是 00 到 255255。int16: 表示 16 位的有符号整数&am…...

Next.js 系统性教学:中间件与国际化功能深入剖析
更多有关Next.js教程,请查阅: 【目录】Next.js 独立开发系列教程-CSDN博客 目录 一、Next.js 中间件 (Middleware) 功能解析 1.1 什么是中间件? 1.2 Next.js 中间件的工作机制 1.3 中间件的功能应用 身份验证与授权 请求重定向 修改请…...

鸿蒙HarmonyOS元服务应用开发实战完全指导
内容提要 元服务概述 元服务开发流程 第一个元服务开发 元服务部署与运行 一、服务概述 1、什么是元服务 在万物互联时代,人均持有设备量不断攀升,设备种类和使用场景更加多样,使得应用开发、应用入口变得更加复杂。在此背景下&#x…...

CT中的2D、MPR、VR渲染、高级临床功能
CT中的2D、MPR、VR渲染 在CT(计算机断层扫描)中,2D、MPR(多平面重建)、VR(体积渲染)是不同的图像显示和处理技术,它们各自有独特的用途和优势。下面分别介绍这三种技术:…...

利用docker-compose来搭建flink集群
1.前期准备 (1)把docker,docker-compose,kafka集群安装配置好 参考文章: 利用docker搭建kafka集群并且进行相应的实践-CSDN博客 这篇文章里面有另外两篇文章的链接,点进去就能够看到 (2&…...

力扣打卡10:K个一组翻转链表
链接:25. K 个一组翻转链表 - 力扣(LeetCode) 这道题需要在链表上,每k个为一组,翻转,链接。 乍一看好像比较容易,其实有很多细节。比如每一组反转后怎么找到上一组的新尾,怎么找到…...

深度学习详解
深度学习(Deep Learning,DL)是机器学习(Machine Learning,ML)中的一个子领域,利用多层次(深层)神经网络来自动从数据中提取特征和规律,模仿人脑的神经系统来进…...

鸿蒙分享(一):添加模块,修改app名称图标
码仓库:https://gitee.com/linguanzhong/share_harmonyos 鸿蒙api:12 新建公共模块common 在entry的oh-package.json5添加dependencies,引入common模块 "dependencies": {"common": "file:../common" } 修改app名称&…...

【Redis】not support: redis
1、查看redis进程 2、查看是否安装redis扩展,此处以宝塔为例...

【集群划分】含分布式光伏的配电网集群电压控制【33节点】
目录 主要内容 模型研究 1.节点电压灵敏度的计算 2.Kmeans聚类划分 3.集群K值 部分代码 运行结果 下载链接 主要内容 该程序参考文献《含分布式光伏的配电网集群划分和集群电压协调控制》,基于社团检测算法,实现基于电气距离和区域电压调节能…...

嵌入式蓝桥杯学习5 定时中断实现按键
Cubemx配置 打开cubemx。 前面的配置与前文一样,这里主要配置基本定时器的定时功能。 1.在Timer中点击TIM6,勾选activated。配置Parameter Settings中的预分频器(PSC)和计数器(auto-reload Register) 补…...

【Java】类似王者荣耀游戏
r77683962/WangZheYouDianRongYao 运行效果图: 类似王者荣耀游戏运行效果图_哔哩哔哩_bilibili...

C++<基本>:union是没有构造函数和析构函数的
今天发现当我在union中包含了多个结构体时,结构体有默认构造函数时,编译报错。 问题点: union不支持构造函数和析构函数union中的元素本身也是不支持构造函数和析构函数的。包含union的结构体也不支持构造函数和析构函数。 出错代码如下&a…...

SQL中IN和NOT操作符的用法
1. IN操作符(布尔逻辑) 在SQL中,IN 是一个用于检查某个字段值是否包含在给定的多个可能值中的布尔操作符。它经常与条件表达式一起使用,通常出现在WHERE子句中。 用法: IN操作符用来确定某个字段的值是否存在于给定…...

C++平常学习用的
4.1 友元函数 4.2 友元类 5.2 类模板 7.2 虚函数dynamic_cast运算 7.2 纯虚函数和抽象类...

JAVA |日常开发中Servlet详解
JAVA |日常开发中Servlet详解 前言一、Servlet 概述1.1 定义1.2 历史背景 二、Servlet 的生命周期2.1 加载和实例化2.2 初始化(init 方法)2.3 服务(service 方法)2.4 销毁(destroy 方法) 三、Se…...

QT实战--QTreeWidget实现两种行颜色+QListWidget样式
本文主要介绍了QTreeWidget实现两种行颜色、点击打开父节点以及设置父子节点之间距离,同时附带介绍了QListWidget样式 树效果图: 列表效果图: 1.树样式的实现 1)使用代码: m_pLeftTreeWidget = new QTreeWidget(this);m_pLeftTreeWidget->setObjectName("suolue_t…...

RPA在IT运维中的实践:自动化监控与维护
一、引言 1. IT运维面临的挑战与RPA的机遇 在IT运维领域,日常的监控、维护和故障响应等工作占据了大量的时间和资源。随着技术的发展,RPA技术提供了自动化这些重复性任务的可能性,从而释放IT团队的潜力,让他们能够专注于更复杂和…...

C# 设置方法执行超时,则执行下一个方法
最近在开发过程中遇到了一个问题,在进行通讯连接时,如果没有连接的话会延时几十秒,而且还设置不了连接超时时间,于是我就想着有没有一种可以判断这个方法的执行时间超过多少秒,就跳出执行其他方法,经过大量…...

【iOS】UIImagePickerController
【iOS】UIImagePickerController 前言 笔者简单学习了iOS开发如何调用本地的一个相册的内容,下面简单介绍一下相关内容。 介绍 UIImagePickerController是iOS平台上的一个类,用于在应用程序中访问设备的照片库、相机和视频录制功能。它提供了一个用户…...

现代企业营销模式创新:链动 2+1 模式 AI 智能名片商城小程序的应用与价值
摘要:本文旨在探讨现代企业面临的客户环境变化以及相应的营销模式变革需求,重点分析链动 21 模式 AI 智能名片商城小程序在满足现代企业营销沟通即时性、精准性、社会性和方便性要求方面的作用,并阐述其对企业在未来市场竞争中取得胜利的重要…...

springboot+Loki+Loki4j+Grafana搭建轻量级日志系统
文章目录 前言一、日志组件介绍 1.1 Loki组件1.2 Loki4j组件1.3 Grafana 二、组件下载安装运行 Loki下载安装运行Grafana下载安装运行 三、创建springboot项目总结 前言 日志在任何一个web应用中都是不可忽视的存在,它已经成为大部分系统的标准组成部分。搭建日志…...