最近割草游戏算是一个比较火的游戏类型,尤其是弹壳特工队,曾连续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节点上
2,添加虚拟摇杆节点,把JoyStick.ts拖入到虚拟摇杆节点上
完成这两个步骤后,点击运行按钮就可以看到效果:
到此,我们就简单实现了一个虚拟摇杆控制角色的程序