【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

玩家移动跳跃控制

csharp 复制代码
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);
    }

}

攻击动画配置

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

轻攻击


重攻击

csharp 复制代码
[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的连贯性

效果

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

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

csharp 复制代码
[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 敌人脚本,实现击退和受击动画

csharp 复制代码
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,调用击退敌人

csharp 复制代码
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

csharp 复制代码
private Animator hitAnimator;       // 敌人的受击特效动画控制器

hitAnimator = transform.GetChild(0).GetComponent<Animator>(); // 敌人的受击特效动画控制器在子对象中

// 外部调用,使敌人进入受击状态
 public void GetHit(Vector2 direction)
 {
     //...
     hitAnimator.SetTrigger("Hit"); // 播放受击特效动画
 }

效果

攻击停顿和屏幕震动

新增AttackSense

csharp 复制代码
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调用

csharp 复制代码
[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值应该可以实现这种效果,连续攻击可能用局部顿帧好一点,下面大概分享一下思路

csharp 复制代码
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是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

相关推荐
yours_Gabriel3 分钟前
java基础:面向对象(二)
java·开发语言·笔记·学习
Enaium6 分钟前
Rust入门实战 编写Minecraft启动器#3解析资源配置
java·开发语言·rust
我写代码菜如坤8 分钟前
Unity中遇到“Input Button unload_long_back_btn is not setup”问题
开发语言
许思王20 分钟前
【Python】组合数据类型:序列,列表,元组,字典,集合
开发语言·人工智能·python
雪 狼1 小时前
unity对于文件夹的操作
windows·unity·游戏引擎
虫小宝1 小时前
如何在Java中实现PDF生成
java·开发语言·pdf
菜鸡且互啄692 小时前
在线教育平台,easyexcel使用案例
java·开发语言
电饭叔3 小时前
《python程序语言设计》2018版第5章第52题利用turtle绘制sin函数
开发语言·python
weixin_452600693 小时前
如何为老化的汽车铅酸电池充电
开发语言·单片机·安全·汽车·电机·电源模块·充电桩
Java资深爱好者4 小时前
如何在std::map中查找元素
开发语言·c++