查看原文
其他

EasyCtrl,轻松搞定角色控制器输入!

iwae 老菜喵
2024-10-10

大家有没有遇到过多平台的控制器需求,产品经理又要手机可以摇杆控制,又要求支持PC,突出一个折磨打工人。(冰冰老菜喵合并啦!)


EasyCtrl是一个简单的角色控制器,同时CTRL也是“”,“”,“Rap”,“篮球”的简称。

通过EasyCtrl可以支持多种不同的控制输入,轻松控制角色唱跳RAP打篮球,同时在PC端,支持多按键Combo设置,和实时修改按键。

控制逻辑解耦

由于我们可能对接多个不同的控制器,如果在update里管理这些控制器会有大量的if else 不仅性能低,后期也不好维护。

这里二喵采用了中继的设计模式,以角色的位移为例子,把角色的位移分成了横向纵向,这样一套控制器就可以适用于2D和3D不同的场景。

        Vec3.copy(tempV3_0, this.node.position);
        position.h = tempV3_0[_h];
        position.v = tempV3_0[_v];
        let dirV = ctrl.v, dirH = ctrl.h, move = false;
        if (dirV != 0 || dirH != 0) {
            const speedMag = speed * dt * ctrl.mag;
            const eulerY = -Math.atan2(dirV, dirH) * rad + this.camera._targetRotation.y;
            const targetRad = eulerY / rad;
            this._speedH = lerp(this._speedH, speedMag * Math.sin(targetRad), dt * 5);
            this._speedV = lerp(this._speedV, speedMag * Math.cos(targetRad), dt * 5);
            quatFromAngleY(tempQ_0, eulerY);
            move = true;
            /* the maxStep should be angle instead of degree, already issued to engine team*/
            Quat.rotateTowards(tempQ_0, this.node.rotation, tempQ_0, dt * 360 * 1.8);
            position.h += this._speedH, position.v += this._speedV;
            tempV3_0[_h] = position.h, tempV3_0[_v] = position.v;
            this.node.rotation = tempQ_0;
            this.play("walk");
        } else {
            this._speedH = this._speedV = 0;
            this.play("idle");
        }
        if (move && !aa.ctrl.isPlaying) {
            tempV3_0[_h] = position.h, tempV3_0[_v] = position.v;
            this.node.position = tempV3_0;
        }

其实角色控制器中横向和纵向的向量长度来自中继层InputCtrl,所以这里把InputCtrl 设计成了单例,不同的控制器就可以和角色控制器解耦合。

以虚拟摇杆控制器为例,我们先把虚拟摇杆位移的方向归一化

   touchMove(touch: Touch) {
        if (!this.joyStick.active) return;
        touch.getUILocation(_tempVec2);
        this.joyStick.getWorldPosition(_tempVec3);
        _tempVec2.x -= _tempVec3.x, _tempVec2.y -= _tempVec3.y;
        this.distance = _tempVec2.lengthSqr();
        this.distance = Math.min(this.distance,this.maxRadius);
        _tempVec2.normalize();
        ctrl.mag = (this.distance / this.maxRadius) * 1.2;
        ctrl.h = _tempVec2.y, ctrl.v = _tempVec2.x;
        _tempVec2.multiplyScalar(this.distance);
        const euler = -Math.atan2(_tempVec2.x, _tempVec2.y) / rad;
        this.indexArrow.rotation = Quat.fromAngleZ(_tempQuat,euler);
        this.control.setPosition(_tempVec2.x, _tempVec2.y);
    }

就可以把归一化后的横向和纵向的向量告诉InputCtrl,之后角色控制器不需要处理是哪个控制器在控制的,只需要读取横向和纵向的向量即可。

当我们得到了横向和竖向的向量长度,我们可以使用更节省的方法来结算euler,由于引擎只提供了quatFromAngleZ,这里补充了一个quatFromAngleY的方法,四元素的运算量会更少。

const halfToRad = 0.5 * Math.PI / 180.0;

export function quatFromAngleY<Out extends IQuatLike>(out: Out, y: number) {
    y *= halfToRad;
    out.x = out.z = 0;
    out.y = Math.sin(y);
    out.w = Math.cos(y);
    return out;
}

在转向上,为了防止四元素锁死,这里使用 rotateTowards 的方法

/* the maxStep should be angle instead of degree, already issued to engine team*/
Quat.rotateTowards(tempQ_0, this.node.rotation, tempQ_0, dt * 360 * 1.8);

键盘控制器

对于键盘来说,由于我们需要组合按键,这里的逻辑就会复杂一点。

首先我们构建出按键事件组合按键按键绑定 这3个类型的数据结构

export type KeyEvent = {
    down?: Function
    press?: Function
    up?: Function
}
export type KeyCombo = KeyCode[];

export type KeyBinding = {
    combo: KeyCombo,
    event: KeyEvent,
    label?: Label;
}
export type BindingConfig = {
    name: string,
    label?: Label
}

这样每个方法,我们都可以通过按键事件一次性地注册,按下,按起,持续按住的3个状态。

程序使用时候也比较简单

  aa.ctrl
        .add("forward",[KeyCode.KEY_W],this._moveForward,this.labels[0])
        .add("backward",[KeyCode.KEY_S],this._moveBackward,this.labels[1])
        .add("left",[KeyCode.KEY_A],this._moveLeft,this.labels[2])
        .add("right",[KeyCode.KEY_D],this._moveRight,this.labels[3])
        .add("dance",[KeyCode.KEY_X],this._danceAction,this.labels[4])
        .add("rap",[KeyCode.KEY_N,KeyCode.KEY_B],this._rapAction,this.labels[5])
        .add("basketball",[KeyCode.KEY_C,KeyCode.KEY_X],this._basketballAction,this.labels[6])

一次性就给角色添加了上下左右移动和唱跳rap篮球的操作。

同时我们使用了名字作为KeyBinding的键值,这样就可以在游戏内实时的去修改任意行为的按键绑定。

游戏内动态切换按键绑定时候,我们先通过行为事件名称做键值,然后动态修改eyBinding的KeyCombo按键组合即可。

这里的多按键判定比较简单,会依次记录150ms内按下的按键,来判定是否是组合按键

获取本文源码

为了制作这个Demo,二喵亲自制作了iKun的手绘贴图模型,布线精良,适合于休闲游戏

关注老菜喵,回复 “ctrl” 即可免费获取本文Demo。


点击阅读原文线上试玩



继续滑动看下一个
老菜喵
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存