优化玩家体验:Cocos Creator 中的动画防抖策略

优化玩家体验:Cocos Creator 中解决动画抖动问题

1. 问题描述

在 Cocos Creator 开发 2D 游戏时,玩家控制器(PlayerController)是游戏角色的核心组件,负责处理玩家输入并更新角色状态。然而,玩家在进行快速连续操作时,角色动作可能会出现抖动现象,尤其在执行跳跃动作时更为明显。具体表现为,尝试播放跳跃动画时,BodyAnim 组件在第二次尝试获取动画状态时返回 null,导致动画无法正常播放,影响游戏体验。

此问题在 Cocos Creator 官方技术文档的快速上手:制作第一个 2D 游戏中也有所体现。

图 1 问题描述图片描述

2. 原因分析

抖动问题通常源于玩家输入过快,游戏逻辑未能及时响应。针对 BodyAnim 返回 null 的问题,可能的原因包括:

  • 动画状态在短时间内频繁变化,导致组件暂时不可用。

  • 动画播放过程中,角色节点或动画组件被重置或销毁。

3. 解决方案

为解决这一问题,我们采用防抖(Debouncing)技术,通过设置延迟时间,减少短时间内重复触发动作,从而降低抖动。

3.1 防抖逻辑实现

以下为防抖逻辑的实现步骤:

  1. 定义防抖延迟时间变量 _debounceDelay

    private _debounceDelay: number = 0.01; // 默认设置为0.01秒
    
  2. 使用 scheduleOnce 方法安排防抖任务,在指定延迟时间后执行。

    this.scheduleOnce(() => {
        // 防抖逻辑代码
        // 尝试获取动画状态并播放跳跃动画
    }, this._debounceDelay);
    

3.2 确定合适的延迟时间

合适的延迟时间取决于游戏需求和上下文。以下是一些建议:

  • 测试不同延迟时间,观察抖动情况并调整至最佳值。

  • 考虑玩家输入速度,快速输入可能需要更长的延迟时间。

  • 考虑动画播放时间,较长动画可能适用较短延迟。

  • 收集玩家反馈,了解操作延迟的接受程度。

3.3 完整代码示例

以下为包含防抖逻辑的玩家控制器组件示例:

/**
 * @author MYXH <1735350920@qq.com>
 * @license GNU GPL v3
 * @version 0.0.1
 * @date 2024-12-30
 * @description 玩家控制器
 */

import {
    _decorator,
    Component,
    Vec3,
    EventMouse,
    input,
    Input,
    Animation,
} from "cc";
const { ccclass, property } = _decorator;

/**
 * @description 添加一个放大比
 */
export const BLOCK_SIZE = 40;

@ccclass("PlayerController")
export class PlayerController extends Component {
    /**
     * @description 是否开始跳跃
     */
    private _startJump: boolean = false;

    /**
     * @description 跳跃步数:一步或者两步
     */
    private _jumpStep: number = 0;

    /**
     * @description 当前跳跃时间
     */
    private _curJumpTime: number = 0;

    /**
     * @description 跳跃时间
     */
    private _jumpTime: number = 0.1;

    /**
     * @description 移动速度
     */
    private _curJumpSpeed: number = 0;

    /**
     * @description 当前的位置
     */
    private _curPos: Vec3 = new Vec3();

    /**
     * @description 位移
     */
    private _deltaPos: Vec3 = new Vec3(0, 0, 0);

    /**
     * @description 目标位置
     */
    private _targetPos: Vec3 = new Vec3();

    /**
     * @description 防抖延迟时间
     */
    private _debounceDelay: number = 0.01;

    /**
     * @description 身体动画
     */
    @property(Animation)
    BodyAnim: Animation = null;

    /**
     * @description 开始
     * @returns void
     */
    start() {
        input.on(Input.EventType.MOUSE_UP, this.onMouseUp, this);
    }

    /**
     * @description 重置
     * @returns void
     */
    reset() {}

    /**
     * @description 鼠标抬起事件
     * @param event 鼠标事件
     * @returns void
     */
    onMouseUp(event: EventMouse) {
        if (event.getButton() === 0) {
            this.jumpByStep(1);
        } else if (event.getButton() === 2) {
            this.jumpByStep(2);
        }
    }

    /**
     * @description 跳跃
     * @param step 跳跃的步数 1 或者 2
     * @returns void
     */
    jumpByStep(step: number) {
        if (this._startJump) {
            return;
        }

        this._startJump = true; // 标记开始跳跃
        this._jumpStep = step; // 跳跃的步数 1 或者 2
        this._curJumpTime = 0; // 重置开始跳跃的时间

        const clipName = step == 1 ? "oneStep" : "twoStep"; // 根据步数选择动画

        // 防抖逻辑,确保在延迟后获取动画状态
        this.scheduleOnce(() => {
            if (this.BodyAnim) {
                const state = this.BodyAnim.getState(clipName); // 获取动画状态
                if (state) {
                    this._jumpTime = state.duration; // 获取动画的时间

                    this._curJumpSpeed =
                        (this._jumpStep * BLOCK_SIZE) / this._jumpTime; // 根据时间计算出速度
                    this.node.getPosition(this._curPos); // 获取角色当前的位置
                    Vec3.add(
                        this._targetPos,
                        this._curPos,
                        new Vec3(this._jumpStep * BLOCK_SIZE, 0, 0)
                    ); // 计算出目标位置

                    // 播放动画
                    if (step === 1) {
                        // 调用 BodyAnim 的 play 方法,播放名为 "oneStep" 的动画
                        this.BodyAnim.play("oneStep");
                    } else if (step === 2) {
                        // 否则如果 step 等于 2
                        // 调用 BodyAnim 的 play 方法,播放名为 "twoStep" 的动画
                        this.BodyAnim.play("twoStep");
                    }
                }
            }
        }, this._debounceDelay);
    }

    /**
     * @description 更新
     * @param deltaTime 时间间隔
     * @returns void
     */
    update(deltaTime: number) {
        if (this._startJump) {
            this._curJumpTime += deltaTime; // 累计总的跳跃时间
            if (this._curJumpTime > this._jumpTime) {
                // 当跳跃时间是否结束
                // end
                this.node.setPosition(this._targetPos); // 强制位置到终点
                this._startJump = false; // 清理跳跃标记
            } else {
                // tween
                this.node.getPosition(this._curPos);
                this._deltaPos.x = this._curJumpSpeed * deltaTime; //每一帧根据速度和时间计算位移
                Vec3.add(this._curPos, this._curPos, this._deltaPos); // 应用这个位移
                this.node.setPosition(this._curPos); // 将位移设置给角色
            }
        }
    }
}

4. 结论

通过实现防抖逻辑,我们有效地解决了玩家控制器在处理快速连续输入时的抖动问题。通过测试和调整延迟时间,我们确保了玩家体验的流畅性和游戏动作的准确性。

在游戏开发过程中,关注此类细节对于提升玩家体验至关重要。本文旨在帮助 Cocos Creator 开发者更好地理解和解决类似问题,以制作更优质的游戏。同时,也希望能够为 Cocos Creator 官方技术文档提供有益的补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

末影小黑xh

感谢朋友们对我的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值