【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.2
        yield 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是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

日志自动提取---七牛Logkit观星应急工具

目录 七牛Logkit (Windows&Linux&Mac 等) 下载: 文档: windows配置过程: 1-下载 2-修改logkit-community基本配置 3-启动! 4-浏览器访问 5-添加配置吧 观星应急工具 &#xff08;Windows 系统日志&#xff09; 七牛Logkit (Windows&Linux&Mac 等) -…

WCCI 2024第三弹:忍者表演惊艳全场,盛大晚宴不容错过

WCCI 2024第三弹&#xff1a;忍者表演惊艳全场&#xff0c;盛大晚宴不容错过&#xff01; 会议之眼 快讯 会议介绍 IEEE WCCI&#xff08;World Congress on Computational Intelligence&#xff09;2024&#xff0c;即2024年IEEE世界计算智能大会&#xff0c;于6月30日至7月…

React@16.x(47)路由v5.x(12)源码(4)- 实现 Route

目录 1&#xff0c;原生 Route 的渲染内容2&#xff0c;实现 1&#xff0c;原生 Route 的渲染内容 对如下代码来说&#xff1a; import { BrowserRouter as Router, Route } from "react-router-dom"; function News() {return <div>News</div>; }func…

使用SSE实现echarts数据实时更新

区别 SSE 和 WebSocket 原理和实现方式的区别 SSE( Server-Sent Events) SSE 是基于传统的 HTTP 协议实现的&#xff0c;采用了长轮询&#xff08;long-polling&#xff09;机制。客户端通过向服务器发送一个 HTTP 请求&#xff0c;服务器保持连接打开并周期性地向客户端发送…

NoSQL之Redis高可用与优化

一、Redis高可用 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。 但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#xff0c;除了保证…

Android adb logcat日志过滤输出

Android adb logcat日志过滤输出 adb logcat 输出所有Android设备上的日志。 adb logcat *:Error 过滤输出日志级别只为Error的日志。 过滤某些标签或tag&#xff0c;依次执行: adb shell logcat grep | "你的标签或tag" Android Studio level过滤查看各个等级的日志…

Oracle连接mysql

oracle使用的11g&#xff0c;在一台windows服务器&#xff1b;mysql使用的是5.7版本&#xff0c;在另一台windows服务器&#xff0c;这两个服务器之间的网络是互通的。做BI时&#xff0c;要获取不同数据源的数据&#xff0c;这些数据源可能是Oracle&#xff0c;也可能是sqlserv…

股票分析-20240628

今日关注&#xff1a; 20240626 六日涨幅最大: ------1--------300386--------- 飞天诚信 五日涨幅最大: ------1--------300386--------- 飞天诚信 四日涨幅最大: ------1--------300386--------- 飞天诚信 三日涨幅最大: ------1--------300386--------- 飞天诚信 二日涨幅最…

【pytorch14】感知机

单层感知机模型 对于单层的感知机&#xff0c;它的激活函数是一个sigmoid 对于符号的定义做一个规范化&#xff0c;输入层每一层进行一个编号 输入是第0层&#xff0c;上标0表示属于输入层&#xff0c;下标0到n表示一共有n个节点(这里严格来说应该是0~n-1&#xff0c;为了书写…

Zoom使用的基本步骤和注意事项

Zoom是一款功能强大的视频会议软件&#xff0c;广泛应用于远程办公、在线教育、团队协作等多个场景。以下是Zoom使用的基本步骤和注意事项&#xff1a; 一、注册与登录 注册Zoom账户&#xff1a; 访问Zoom官方网站&#xff08;如zoom.us&#xff09;&#xff0c;点击“注册”…

Games101学习笔记 Lecture16 Ray Tracing 4 (Monte Carlo Path Tracing)

Lecture16 Ray Tracing 4 (Monte Carlo Path Tracing 一、蒙特卡洛积分 Monte Carlo Integration二、路径追踪 Path tracing1.Whitted-Style Ray Tracings Problems2.只考虑直接光照时3.考虑全局光照①考虑物体的反射光②俄罗斯轮盘赌 RR &#xff08;得到正确shade函数&#x…

精准畜牧业:多维传感监测及分析动物采食行为

全球畜牧业呈现出一个动态且复杂的挑战。近几十年来&#xff0c;它根据对动物产品需求的演变进行了适应&#xff0c;动物生产系统需要提高其效率和环境可持续性。在不同的畜牧系统中有效行动取决于科学技术的进步&#xff0c;这允许增加照顾动物健康和福祉的数量。精准畜牧业技…

JavaScript-WebAPI

文章目录 JS组成什么是 webApis 和APIDOM 简介document 对象 获取 DOM 对象利用css选择器来获取DOM元素选择指定css选择器的所有元素其他获取DOM元素方法&#xff08;了解&#xff09; 操作元素内容对象.innerText对象.innerHTML 操作元素属性操作元素常用属性操作元素样式属性…

pytorch中的contiguous()

官方文档&#xff1a;https://pytorch.org/docs/stable/generated/torch.Tensor.contiguous.html 其描述contiguous为&#xff1a; Returns a contiguous in memory tensor containing the same data as self tensor. If self tensor is already in the specified memory forma…

羊大师:羊奶养生,解锁健康之道的新密码

在探寻健康与养生的旅途中&#xff0c;我们总渴望找到那把开启健康之门的钥匙。而今&#xff0c;羊奶以其独特的营养价值和健康益处&#xff0c;正悄然成为那把解锁健康之道的新密码。 羊奶&#xff0c;自古以来便是自然赋予的珍贵礼物。它富含优质蛋白、多种维生素及矿物质&am…

pandas数据分析(6)

算数运算 和Numpy数组一样&#xff0c;DataFrame和Series也利用了向量化技术。例如&#xff1a; 不过pandas真正强大之初在于自动对齐机制&#xff1a;当对多个DataFrame使用算数运算符时&#xff0c;pandas会自动将它们按照列或行索引对齐。 结果DataFrame的索引和列是两个Da…

day02-统计数据

numpy统计学 1.求平均值[数组名.mean()/np.mean(数组名)] m1 np.arange(20).reshape((4,5))m1.mean() #9.5若想要求某一维的平均值&#xff0c;设置axis参数&#xff0c;多维数组元素指定&#xff1a; axis 0&#xff0c;将从上往下计算。axis 1&#xff0c;将从左往右计算…

VIM介绍

VIM&#xff08;Vi IMproved&#xff09;是一种高度可配置的文本编辑器&#xff0c;用于有效地创建和更改任何类型的文本。它是从 vi 编辑器发展而来的&#xff0c;后者最初是 UNIX 系统上的一个文本编辑器。VIM 以其键盘驱动的界面和强大的文本处理能力而闻名&#xff0c;是许…

拼接各列内容再分组统计

某个表格的第1列是人名&#xff0c;后面多列是此人某次采购的产品&#xff0c;一个人一次可以采购多个同样的产品&#xff0c;也可以多次采购。 ABCD1JohnAppleAppleOrange2PaulGrape3JohnPear4SteveLycheeGrape5JessicaApple 需要整理成交叉表&#xff0c;上表头是产品&…

透过 Go 语言探索 Linux 网络通信的本质

大家好&#xff0c;我是码农先森。 前言 各种编程语言百花齐放、百家争鸣&#xff0c;但是 “万变不离其中”。对于网络通信而言&#xff0c;每一种编程语言的实现方式都不一样&#xff1b;但其实&#xff0c;调用的底层逻辑都是一样的。linux 系统底层向上提供了统一的 Sock…