当前位置: 首页 > news >正文

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);}
}

http://www.mrgr.cn/news/79522.html

相关文章:

  • docker-compose 之 禅道(zentao) 社区版
  • QT 中 多线程(备查)
  • 医疗 UI 设计中色彩搭配的重要性
  • linux网络编程 | c | select实现多路IO转接服务器
  • 支持自定义离线地图地理区域,查询组件及数据源功能增强,DataEase开源BI工具v2.10.3 LTS发布
  • Windows下Docker Desktop+k8s安装和部署程序
  • 避大坑!Vue3中reactive丢失响应式的问题
  • 链表OJ题型讲解与总结
  • 力扣每日一题 - 999. 可以被一步捕获的棋子数
  • 默认插槽,具名插槽(v-slot:具名,name=‘ ‘),作用域插槽
  • Ubuntu Linux 图形界面工具管理磁盘分区和文件系统(八)
  • 电子商务人工智能指南 1/6 - 搜索、广告和发现
  • 1-1 C语言链表
  • 报错:Invalid HTTP method: PATCH executing PATCH http://XXX.XXX
  • HarmonyOS(63) ArkUI 自定义占位组件NodeContainer
  • centos部署SkyWalking并在springcloud项目中用法举例
  • openharmony开发资料合集
  • 13.在 Vue 3 中使用OpenLayers加载鹰眼控件示例教程
  • 18.[极客大挑战 2019]BabySQL1
  • 书后习题答案:《Python程序设计基础(第2版)》,电子工业出版社,2020.01
  • SpringBoot自动配置底层核心源码
  • 电子商务人工智能指南 2/6 - 需求预测和库存管理
  • 【开源】A064—基于JAVA的民族婚纱预定系统的设计与实现
  • FastAPI解决跨域报错net::ERR_FAILED 200 (OK)
  • 木棍的长度
  • (七)腾讯cloudstudio+Stable-Diffusion-webui AI绘画教程-安装Stable-Diffusion-WebUI