版本:2.4.10
之前用Egret时写过一个虚拟摇杆 Egret虚拟摇杆 ,这里用Cocos实现。
一 演示效果

二 摇杆原理
和Egret的虚拟摇杆实现原理是一样的,用正切函数Math.atan2来获取触摸点和原点的角度。

三 虚拟摇杆实现
1. 使用Math.atan2正切函数获取触摸点距离原点的弧度,弧度=Math.atan2(触摸点Y-原点Y,触摸点X-原点X),再把弧度转成角度,角度 = 弧度*180/Math.PI 。
2. 触摸移动时,小圆移动到触摸位置,小圆不能超出大圆范围,用变量 circleRadius 设置小圆移动范围,这个值一般是大圆半径,但是大小圆图片可能留白较多,所以还是要自己设置。
3. 触摸开始、滑动、结束都会抛出事件,用于处理其它事件。
4. 为了防止多点触摸,需要比较 cc.Event.EventTouch的getID()。
const { ccclass, property, menu } = cc._decorator;
/**
* 虚拟摇杆
* @author chenkai 2022.9.5
*/
@ccclass
@menu("framework/Joystick")
export default class Joystick extends cc.Component {
/**触摸开始 */
public static START: string = "JoystickEvent_TouchStart";
/**触摸移动 */
public static MOVE: string = "JoystickEvent_TouchMove";
/**触摸结束 */
public static END: string = "JoystickEvent_TouchEnd";
/**大圆 */
@property({ type: cc.Node, tooltip: "大圆" })
bigCircle: cc.Node = null;
/**小圆 */
@property({ type: cc.Node, tooltip: "小圆" })
smallCircle: cc.Node = null;
/**小圆移动范围半径 (小圆移动范围在大圆以内,所以小圆移动范围半径约等于大圆的半径)*/
@property({ type: cc.Integer, tooltip: "小圆移动半径" })
circleRadius: number = 0;
/**大圆初始位置 */
private _bigCircleInitPos: cc.Vec2 = new cc.Vec2(0, 0);
/**触摸ID,防多点触摸 */
private _touchID: number = 0;
onLoad() {
this._bigCircleInitPos = new cc.Vec2(this.bigCircle.x, this.bigCircle.y);
}
onDestroy() {
}
/**启动虚拟键盘 */
public start() {
this.node.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this);
this.node.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
this.node.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this);
this.node.on(cc.Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
}
/**停止虚拟键盘 */
public stop() {
this.node.off(cc.Node.EventType.TOUCH_START, this.onTouchStart, this);
this.node.off(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
this.node.off(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this);
this.node.off(cc.Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
}
/**触摸开始 */
private onTouchStart(e: cc.Event.EventTouch) {
//触摸点世界坐标转成局部坐标
let pos = this.node.convertToNodeSpaceAR(e.getLocation());
this.bigCircle.setPosition(pos);
this.smallCircle.setPosition(0, 0);
this._touchID = e.getID();
this.node.emit(Joystick.START);
}
/**触摸移动 */
private onTouchMove(e: cc.Event.EventTouch) {
//防多点触摸
if (this._touchID != e.getID()) {
return;
}
//获取触摸点和虚拟摇杆的距离、弧度
let location = e.getLocation(); //触摸点当前世界坐标
let startLocation = e.getStartLocation(); //触摸起始点世界坐标
let dist = cc.Vec2.distance(location, startLocation); //触摸点和大圆距离 (大圆坐标 = 触摸起始点坐标)
let radian = Math.atan2(location.y - startLocation.y, location.x - startLocation.x); //触摸点和大圆夹角弧度
//触摸点在大圆范围内,则小圆位置移动到当前触摸点
if (dist <= this.circleRadius) {
let smallPos = this.bigCircle.convertToNodeSpaceAR(location);
this.smallCircle.setPosition(smallPos);
//触摸点在圆环范围外,如果小圆移动到当前触摸点就会跑出大圆了,所以小圆位置移动到大圆边缘
} else {
this.smallCircle.x = Math.cos(radian) * this.circleRadius;
this.smallCircle.y = Math.sin(radian) * this.circleRadius;
}
//派发移动事件
this.node.emit(Joystick.MOVE, radian * 180 / Math.PI);
}
/**触摸结束 */
private onTouchEnd(e: cc.Event.EventTouch) {
//防多点触摸
if (this._touchID != e.getID()) {
return;
}
//大圆回到起始位置
this.bigCircle.setPosition(this._bigCircleInitPos);
//小圆回到原点
this.smallCircle.setPosition(0, 0);
//派发触摸结束事件
this.node.emit(Joystick.END);
}
/**触摸结束,在虚拟摇杆之外的位置松开手指 */
private onTouchCancel(e: cc.Event.EventTouch) {
this.onTouchEnd(e);
}
}
四 实际使用
joystick是挂组件Joystick.ts的节点,这个节点的高宽也是触摸范围,如果需要全屏触摸都能使用摇杆,那么节点高宽就要设置为全屏大小。
bigCircle是大圆
smallCircle是小圆
angleLab是角度文本

const { ccclass, property } = cc._decorator;
@ccclass
export default class MainScene extends cc.Component {
@property({ type: Joystick, tooltip: "虚拟摇杆" })
joystick: Joystick = null;
@property({ type: cc.Label, tooltip: "角度文本" })
angleLab: cc.Label = null;
onLoad() {
//触摸开始
this.joystick.node.on(Joystick.START, () => {
console.log("触摸开始");
});
//触摸移动
this.joystick.node.on(Joystick.MOVE, (angle: number) => {
this.angleLab.string = "角度:" + Math.round(angle);
});
//触摸结束
this.joystick.node.on(Joystick.END, () => {
console.log("触摸结束");
this.angleLab.string = "角度:" + 0;
});
//摇杆启动
this.joystick.start();
}
}
浙公网安备 33010602011771号