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

【unity实战】使用Unity实现动作游戏的攻击 连击 轻重攻击和打击感

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
  • 素材下载:
  • 玩家移动跳跃控制
  • 攻击动画配置
    • 轻攻击
    • 重攻击
  • 攻击时禁止移动和攻击移动补偿
  • 敌人击退和播放受击动画
  • 受击特效
  • 攻击停顿和屏幕震动
  • 局部顿帧(补充)
  • 参考
  • 源码
  • 完结

前言

注意本文为自己的学习记录笔记,主要是对游戏攻击 连击 轻重攻击和打击感进行探究,其中打击感实现一般依靠播放受击动画、击退、攻击特效、时停和屏幕震动反馈等来实现,如果你有其他的好方法也欢迎补充。

素材下载:

人物
https://legnops.itch.io/red-hood-character
在这里插入图片描述

敌人
https://jesse-m.itch.io/skeleton-pack
在这里插入图片描述

环境
https://szadiart.itch.io/pixel-fantasy-caves
在这里插入图片描述

攻击特效
https://v-ktor.itch.io/pixelated-attackhit-animations
在这里插入图片描述

玩家移动跳跃控制

public class PlayerController : MonoBehaviour
{[Header("移动和跳跃参数")] public float moveSpeed; // 移动速度public float jumpForce; // 跳跃力量new private Rigidbody2D rigidbody; // 刚体组件private Animator animator; // 动画控制器private float input; // 输入private bool isGround; // 是否在地面上[SerializeField] private LayerMask layer; // 地面碰撞层[SerializeField] private Vector3 check; // 地面检测向量void Start(){rigidbody = GetComponent<Rigidbody2D>();animator = GetComponent<Animator>();}void Update(){input = Input.GetAxisRaw("Horizontal");isGround = Physics2D.OverlapCircle(transform.position + new Vector3(check.x, check.y, 0), check.z, layer);animator.SetFloat("Horizontal", rigidbody.velocity.x);animator.SetFloat("Vertical", rigidbody.velocity.y);animator.SetBool("isGround", isGround);Move();Attack();}void Move(){// 根据输入来移动角色rigidbody.velocity = new Vector2(input * moveSpeed, rigidbody.velocity.y);// 处理跳跃if (Input.GetButtonDown("Jump") && isGround){rigidbody.velocity = new Vector2(0, jumpForce);animator.SetTrigger("Jump"); // 触发跳跃动画}// 根据水平速度方向更新角色朝向if (rigidbody.velocity.x < 0)transform.localScale = new Vector3(-1, 1, 1); // 向左else if (rigidbody.velocity.x > 0)transform.localScale = new Vector3(1, 1, 1); // 向右}private void OnDrawGizmos(){Gizmos.DrawWireSphere(transform.position + new Vector3(check.x, check.y, 0), check.z);}}

攻击动画配置

攻击动画分为轻攻击和重攻击

轻攻击

在这里插入图片描述
在这里插入图片描述

重攻击

在这里插入图片描述

[Header("攻击")]
public float interval = 2f; // 攻击间隔时间
private float timer; // 计时器
private bool isAttack; // 是否正在攻击
private string attackType; // 攻击类型
private int comboStep; // 连击步骤void Update()
{//...Attack();
}void Attack()
{// 轻攻击输入检测if (Input.GetKeyDown(KeyCode.Return) && !isAttack){isAttack = true;attackType = "Light";comboStep++; // 连击步骤加一if (comboStep > 3)comboStep = 1; // 连击步骤循环timer = interval; // 设置计时器animator.SetTrigger("LightAttack"); // 触发轻攻击动画animator.SetInteger("ComboStep", comboStep); // 设置连击步骤参数}// 重攻击输入检测if (Input.GetKeyDown(KeyCode.RightShift) && !isAttack){isAttack = true;attackType = "Heavy";comboStep++; // 连击步骤加一if (comboStep > 3)comboStep = 1; // 连击步骤循环timer = interval; // 设置计时器animator.SetTrigger("HeavyAttack"); // 触发重攻击动画animator.SetInteger("ComboStep", comboStep); // 设置连击步骤参数}// 处理连击计时器if (timer != 0){timer -= Time.deltaTime;if (timer <= 0){timer = 0;comboStep = 0; // 重置连击步骤}}
}//攻击结束
public void AttackOver()
{isAttack = false;
}

配置每个攻击动画,在执行的位置执行攻击结束事件,通常攻击结束事件都不会放在动画的最后一帧,因为连击一般都存在预输入,也就是在上一个动画还未结束时就进行输入,这样能很好的提升combo的连贯性
在这里插入图片描述
效果
在这里插入图片描述

攻击时禁止移动和攻击移动补偿

攻击时我们不希望玩家还能移动,但是简单粗暴的禁止移动又会影响我们的攻击操作手感,在死亡细胞等游戏中,攻击时会朝前方以一个较小的速度移动,这样可以一定程度的补偿攻击时无法移动的缺陷
在这里插入图片描述

[Header("攻击补偿速度")]
public float lightSpeed; // 轻攻击速度
public float heavySpeed; // 重攻击速度void Move()
{// 如果玩家没有在攻击状态下,根据输入来移动角色if (!isAttack)rigidbody.velocity = new Vector2(input * moveSpeed, rigidbody.velocity.y);else{// 如果正在攻击,则根据攻击类型设置速度if (attackType == "Light")rigidbody.velocity = new Vector2(transform.localScale.x * lightSpeed, rigidbody.velocity.y);else if (attackType == "Heavy")rigidbody.velocity = new Vector2(transform.localScale.x * heavySpeed, rigidbody.velocity.y);}// ...
}

配置
在这里插入图片描述
效果
在这里插入图片描述

敌人击退和播放受击动画

配置敌人受击动画
在这里插入图片描述
配置玩家每个动画的攻击范围
在这里插入图片描述
新增Enemy 敌人脚本,实现击退和受击动画

public class Enemy : MonoBehaviour
{public float speed;                 // 敌人的移动速度private Vector2 direction;          // 受击时的移动方向private bool isHit;                 // 是否正在受击状态private AnimatorStateInfo info;     // 动画状态信息private Animator animator;          // 敌人的主动画控制器new private Rigidbody2D rigidbody;  // 敌人的刚体组件void Start(){// 获取组件的引用animator = transform.GetComponent<Animator>();rigidbody = transform.GetComponent<Rigidbody2D>();}void Update(){info = animator.GetCurrentAnimatorStateInfo(0); // 获取当前动画状态信息if (isHit){rigidbody.velocity = direction * speed; // 根据受击方向设置速度if (info.normalizedTime >= .6f)isHit = false; // 当动画播放超过60%时结束受击状态}}// 外部调用,使敌人进入受击状态public void GetHit(Vector2 direction){transform.localScale = new Vector3(-direction.x, 1, 1); // 根据受击方向调整朝向isHit = true; // 进入受击状态this.direction = direction; // 设置受击方向animator.SetTrigger("Hit"); // 播放主动画的受击动画状态}
}

修改PlayerController,调用击退敌人

private void OnTriggerEnter2D(Collider2D other)
{if (other.CompareTag("Enemy")){// 根据角色朝向确定敌人受击方向if (transform.localScale.x > 0)other.GetComponent<Enemy>().GetHit(Vector2.right); // 右侧受击else if (transform.localScale.x < 0)other.GetComponent<Enemy>().GetHit(Vector2.left); // 左侧受击}
}

效果
在这里插入图片描述

受击特效

在这里插入图片描述
特效动画配置
在这里插入图片描述
修改Enemy

private Animator hitAnimator;       // 敌人的受击特效动画控制器hitAnimator = transform.GetChild(0).GetComponent<Animator>(); // 敌人的受击特效动画控制器在子对象中// 外部调用,使敌人进入受击状态public void GetHit(Vector2 direction){//...hitAnimator.SetTrigger("Hit"); // 播放受击特效动画}

效果
在这里插入图片描述

攻击停顿和屏幕震动

新增AttackSense

public class AttackSense : MonoBehaviour
{private static AttackSense instance;public static AttackSense Instance{get{if (instance == null)instance = FindObjectOfType<AttackSense>(); // 查找当前场景中的 AttackSense 实例return instance;}}private bool isShake; // 是否正在进行摄像机震动// 暂停游戏一段时间public void HitPause(int duration){StartCoroutine(Pause(duration));}// 使用协程实现暂停功能IEnumerator Pause(int duration){float pauseTime = duration / 60f; // 将帧数转换为实际暂停时间Time.timeScale = 0.2f; // 将游戏时间缩放设为0.2yield return new WaitForSecondsRealtime(pauseTime); // 等待指定的暂停时间Time.timeScale = 1; // 恢复游戏时间正常}// 触发摄像机震动效果public void CameraShake(float duration, float strength){if (!isShake) // 如果当前没有进行震动StartCoroutine(Shake(duration, strength));}// 使用协程实现摄像机震动效果IEnumerator Shake(float duration, float strength){isShake = true; // 标记正在进行震动Transform camera = Camera.main.transform; // 获取主摄像机的 Transform 组件Vector3 startPosition = camera.position; // 记录摄像机震动前的初始位置while (duration > 0){// 将摄像机位置随机偏移一定范围来模拟震动效果camera.position = Random.insideUnitSphere * strength + startPosition;duration -= Time.deltaTime; // 每帧减去时间yield return null; // 等待下一帧}camera.position = startPosition; // 震动结束后将摄像机位置恢复到初始位置isShake = false; // 结束震动状态}
}

修改PlayerController调用

[Header("打击感")]
public float shakeTime; // 摇晃时间
public int lightPause; // 轻攻击暂停时间
public float lightStrength; // 轻攻击相机震动强度
public int heavyPause; // 重攻击暂停时间
public float heavyStrength; // 重攻击相机震动强度private void OnTriggerEnter2D(Collider2D other)
{if (other.CompareTag("Enemy")){// 根据攻击类型处理打击感if (attackType == "Light"){AttackSense.Instance.HitPause(lightPause); // 轻攻击暂停AttackSense.Instance.CameraShake(shakeTime, lightStrength); // 相机震动}else if (attackType == "Heavy"){AttackSense.Instance.HitPause(heavyPause); // 重攻击暂停AttackSense.Instance.CameraShake(shakeTime, heavyStrength); // 相机震动}// 根据角色朝向确定敌人击退方向if (transform.localScale.x > 0)other.GetComponent<Enemy>().GetHit(Vector2.right); // 右侧受击else if (transform.localScale.x < 0)other.GetComponent<Enemy>().GetHit(Vector2.left); // 左侧受击}
}

配置参数
在这里插入图片描述
最终效果
在这里插入图片描述

局部顿帧(补充)

前面的顿帧这个思路其实不太好,可以看看有的游戏,他们不是所有物体都停顿的,一般是攻击者和受击者停顿,其他的不受影响。animator有个scale播放速度scale值应该可以实现这种效果,连续攻击可能用局部顿帧好一点,下面大概分享一下思路

public class AttackController : MonoBehaviour
{public Animator animator;public float slowMotionTimeScale = 0.5f; // 慢动作时的时间缩放值public void PerformAttack(){StartCoroutine(AttackCoroutine());}IEnumerator AttackCoroutine(){// 播放攻击动画前先设置慢动作animator.speed = slowMotionTimeScale;// 等待攻击动画播放完成yield return new WaitForSeconds(animator.GetCurrentAnimatorStateInfo(0).length / slowMotionTimeScale);// 恢复正常时间缩放animator.speed = 1f;}
}

在这个示例中,PerformAttack 方法可以被调用来开始攻击动作。在攻击动作开始时,时间缩放被设置为 slowMotionTimeScale,然后通过 Coroutine 等待攻击动画播放完成后,再恢复为正常速度。

通过这种方法,你可以在游戏中实现局部的动画停顿效果,而不是整体减速,从而增强游戏的视觉冲击力和玩家的体验。

参考

https://www.bilibili.com/video/BV1fX4y1G7tv

源码

整理好我会放上来

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

相关文章:

  • 航空数据管控系统-①项目准备阶段:任务2:项目技术预研(技术架构)
  • 对标 GPT-4o 的开源实时语音多模态模型:Moshi
  • 【BUUCTF-PWN】9-ciscn_2019_n_8
  • Matplotlib 简介
  • 康姿百德磁性床垫好不好,效果怎么样靠谱吗
  • Hadoop集群搭建
  • threejs 微信小程序原生版本的使用 obj模型的加载
  • 学会python——用python制作一个登录和注册窗口(python实例十八)
  • python-Django项目:图书后台管理系统
  • SpringMVC之文件上传下载
  • spring6框架解析(by尚硅谷)
  • 降级Spring Boot版本
  • Generative Modeling by Estimating Gradients of the Data Distribution
  • ChatGPT-5:开创对话式AI的新纪元
  • 【MindSpore学习打卡】应用实践-计算机视觉-SSD目标检测:从理论到实现
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【React系列】如何构建React应用程序
  • Android 控件背景颜色处理
  • C# 免费离线人脸识别 2.0 Demo
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • E-HPC支持多队列管理和自动伸缩
  • go语言学习初探(一)
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • Node 版本管理
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • Wamp集成环境 添加PHP的新版本
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 二维平面内的碰撞检测【一】
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 算法系列——算法入门之递归分而治之思想的实现
  • 用mpvue开发微信小程序
  • $GOPATH/go.mod exists but should not goland
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (1)(1.13) SiK无线电高级配置(五)
  • (12)Hive调优——count distinct去重优化
  • (JS基础)String 类型
  • (剑指Offer)面试题34:丑数
  • (三)elasticsearch 源码之启动流程分析
  • (三)终结任务
  • (转)nsfocus-绿盟科技笔试题目
  • (转载)Linux网络编程入门
  • .dat文件写入byte类型数组_用Python从Abaqus导出txt、dat数据
  • .net 反编译_.net反编译的相关问题
  • .net 获取url的方法
  • @FeignClient 调用另一个服务的test环境,实际上却调用了另一个环境testone的接口,这其中牵扯到k8s容器外容器内的问题,注册到eureka上的是容器外的旧版本...
  • @RunWith注解作用
  • [ASP.NET MVC]如何定制Numeric属性/字段验证消息
  • [C#]C# OpenVINO部署yolov8图像分类模型
  • [C#]C# winform部署yolov8目标检测的openvino模型
  • [C#]C#学习笔记-CIL和动态程序集
  • [Google Guava] 1.1-使用和避免null
  • [hdu4622 Reincarnation]后缀数组
  • [HNOI2010]BUS 公交线路
  • [javaSE] GUI(事件监听机制)