Cocoscreator: 实现虚拟摇杆

2,672 阅读3分钟

最近割草游戏算是一个比较火的游戏类型,尤其是弹壳特工队,曾连续3个月位居大陆,港台,美国,韩国等地区榜单前列,在全球市场表现持续亮眼,截止到去年2022年11月,收入已达到2亿美元。

游戏中,由于技能是非精准瞄准怪物,需要躲避怪物,需要有走位策略,因此需要一个虚拟摇杆来操控角色。本文用cocoscreator3.7.2来实现虚拟摇杆。

实现

要实现虚拟摇杆控制角色走路,这里面需要3个类:

  • JoyStick 类:实现了虚拟摇杆的逻辑,包括摇杆的位置、半径等属性,以及对触摸事件的响应,计算出摇杆的位置,并将结果赋值给 InputVector 类的静态变量,以便其他脚本读取。

  • InputVector 类:定义了虚拟摇杆的输入,包括水平方向和垂直方向的变量,作为 JoyStick 类计算出的结果的存储和传递接口。

  • Player 类:实现了角色的运动,包括计算角色的前进方向、旋转角度等属性,并通过 RigidBody 组件设置角色的物理状态,使其沿着摇杆的方向移动。

JoyStick.ts

import { _decorator, Component, Node, Vec3, v3, Input, EventTouch, math } from 'cc';
import { InputVector } from './InputVector';
const { ccclass, property } = _decorator;

@ccclass('JoyStick')
export class JoyStick extends Component {
    // 摇杆背景节点
    @property(Node)
    joyStickBg: Node = null;

    // 摇杆中心移动的点
    @property(Node)
    thumbnail: Node = null;

    // 摇杆半径
    radius: number = 120;

    // 摇杆初始位置
    originJoyStickPos: Vec3 = v3();

    start() {
        this.node.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
        this.node.on(Input.EventType.TOUCH_END, this.onTouchEnd, this);
        this.node.on(Input.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
        this.node.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
        // 保存初始位置
        this.originJoyStickPos = this.joyStickBg.worldPosition.clone();
    }

    onTouchStart(eventTouch: EventTouch) {
        let x = eventTouch.touch.getUILocationX();
        let y = eventTouch.touch.getUILocationY();
        this.joyStickBg.setWorldPosition(x, y, 0);
    }

    /**
     * 触摸移动
     * @param touchEvent 
     */
    onTouchMove(touchEvent: EventTouch) {
        // 获取摇杆在 UI 的位置
        let x = touchEvent.touch.getUILocationX();
        let y = touchEvent.touch.getUILocationY();

        let worldPosition = new Vec3(x, y, 0);
        let localPosition = v3();

        // 转化摇杆的位置到背景图的本地坐标
        this.joyStickBg.inverseTransformPoint(localPosition, worldPosition);
        let thumbnailPosition = v3();
        let len = localPosition.length();
        localPosition.normalize();
        Vec3.scaleAndAdd(thumbnailPosition, v3(), localPosition, math.clamp(len, 0, this.radius));

        this.thumbnail.setPosition(thumbnailPosition);

        // 将计算的结果赋予给 Input
        InputVector.horizontal = Number(this.thumbnail.position.x / this.radius);
        InputVector.vertical = Number(this.thumbnail.position.y / this.radius);
    }

    /**
     * 触摸结束
     * @param touchEvent 
     */
    onTouchEnd(touchEvent: EventTouch) {
        // 重置输入变量
        InputVector.horizontal = 0;
        InputVector.vertical = 0;

        // 摇杆的位置回归到初始化位置
        this.joyStickBg.worldPosition = this.originJoyStickPos;
        this.thumbnail.setPosition(v3());
    }
}

InputVector.ts

import { _decorator } from 'cc';
const { ccclass, property } = _decorator;

/**
 * 虚拟摇杆的输入
 * 避免和 `input` 重名使用 InputVector
 */
@ccclass('InputVector')
export class InputVector {

    private static _horizontal: number = 0;
    static get horizontal(): number {
        return this._horizontal;
    }

    static set horizontal(val: number) {
        this._horizontal = val;
    }

    private static _vertical: number = 0;
    static get vertical(): number {
        return this._vertical;
    }
    static set vertical(val: number) {
        this._vertical = val;
    }
}

Player.ts

import { Component, math, RigidBody, v3, Vec3, _decorator } from "cc";
import { InputVector } from "./InputVector";
const { ccclass, property } = _decorator;

let tempVec: Vec3 = v3()
let tempVelocity: Vec3 = v3();

@ccclass("Player")
export class Player extends Component {
  // 表示玩家的前进方向
  roleForward: Vec3 = v3();
  // 角色的刚体组件
  rigidbody: RigidBody | null = null;
  // 角色的线速度
  linearSpeed: number = 1.0;

  start () {
    // 获取刚体组件
    this.rigidbody = this.node.getComponent(RigidBody);
  }

  // 计算两个向量之间的夹角,并返回这个夹角的符号。
  private signAngle(from: Vec3, to: Vec3, axis: Vec3): number {
    const angle = Vec3.angle(from, to);
    Vec3.cross(tempVec, from, to);
    const sign = Math.sign(axis.x * tempVec.x + axis.y * tempVec.y + axis.z * tempVec.z);
    return angle * sign;
  }

  update(dt: number) {
    if (InputVector.horizontal === 0 && InputVector.vertical === 0) return;

    let x = InputVector.horizontal;
    let y = InputVector.vertical;

    // 将角色面朝的方向根据x和y的值进行更新
    this.roleForward.x = x;
    this.roleForward.z = -y;
    this.roleForward.y = 0;

    let a = this.signAngle(this.node.forward, this.roleForward, Vec3.UP);
    // 计算出一个旋转速度,这个速度将被应用在物体的角速度上
    let as = v3(0, a * 20, 0);
    // 将计算好的旋转速度应用到物体的角速度上
    this.rigidbody.setAngularVelocity(as);    
    this.onMove();
  }

  // 控制角色移动
  onMove() {
    // 计算当前速度,将线性速度乘以名为 roleForward 的向前矢量的大小得到
    let speed = this.linearSpeed * this.roleForward.length();
    // 设置临时速度矢量中的 x 轴速度
    tempVelocity.x = math.clamp(this.node.forward.x, -1, 1) * speed;
    // 在 tempVelocity 中设置 z 轴速度
    tempVelocity.z = math.clamp(this.node.forward.z, -1, 1) * speed;
    // 把临时速度赋值给刚体的线性速度属性
    this.rigidbody?.setLinearVelocity(tempVelocity);
  }
}

步骤

1,在cocoscreator编辑器创建一个场景,场景中添加一个立方体(Cube)来表示角色,将Player.ts拖入到Cube节点上

image.png 2,添加虚拟摇杆节点,把JoyStick.ts拖入到虚拟摇杆节点上

image.png

完成这两个步骤后,点击运行按钮就可以看到效果:

joystick_AdobeExpress (1).gif

到此,我们就简单实现了一个虚拟摇杆控制角色的程序