简介
在许多移动游戏中,虚拟操纵杆是一个重要的用户界面元素,用于控制角色或物体的移动。本文将介绍如何在Unity中实现虚拟操纵杆,提供了一段用于移动控制的代码。我们将讨论不同类型的虚拟操纵杆,如固定和跟随,以及如何在实际游戏中使用这些操纵杆。
unity2022版本实现虚拟操作杆可以查看这篇文章 点击查看
界面节点设置
1. 添加一个Canvas节点
首先,我们需要创建一个画布节点,这是我们整个界面的基础。这个节点将允许我们绘制和排列其他元素。
2. 在Canvas节点下添加一个Camera节点
接下来,我们将在Canvas节点下创建一个Camera节点。这个Camera节点是查看操作按钮的摄像头
2. 在Canvas节点下添加一个Joystick节点
这个Joystick节点将充当容器,用于组织和管理我们的界面元素。
3. 在Joystick节点下添加两个Sprite节点
在Joystick节点中,我们将添加两个Sprite节点。这两个Image节点具有不同的用途:
a. 背景节点:第一个Sprite节点将用作背景,为整个界面提供背景图像或颜色。
b. 操作按钮节点:第二个Sprite节点将用于显示操作按钮或其他交互元素。
截图可以这样:
脚本编写
简要说明:
因为编写的是虚拟操作杆 需要添加三个事件:
触摸开始(touchStart),拖动(touchMove),触摸结束(touchEnd)
在触摸开始记录拖动的一些起始坐标。
在拖动中移动操作按钮节点如果是操作角色移动这里就可以操作移动角色
在触摸结束的时候重置坐标
1.touchStart方法:描述touchStart方法,它处理当玩家触摸操纵杆时的行为。根据操纵杆类型(固定或跟随),它设置操纵杆的初始位置。
2.touchMove方法:详细解释touchMove方法,这是当玩家拖动操纵杆时执行的代码。说明如何计算操纵杆输入的方向,以及如何限制操纵杆的移动范围。
3.touchEnd方法:描述touchEnd方法,用于当玩家释放操纵杆时重置相关变量和位置,同时停止玩家的移动。在初始化引用的时候可以传入参数(JoystickType)控制虚拟操作杆是固定的还是跟随触摸点的
完整的脚本如下:
import { Component, assetManager, _decorator, Node, Prefab, instantiate, JsonAsset, UITransform, Vec3, Widget, Graphics, Enum, input, Input, EventTouch, RichText, Label, sys, CCString, Vec2, Camera } from "cc"
import utils from "../../../utils";
const { ccclass, property, type } = _decorator;
// 定义操纵杆的类型
export enum JoystickType {
FIXED, // 固定类型的操纵杆
FOLLOW // 跟随类型的操纵杆
}
// 定义操纵杆的显示类型
export enum JoystickShowType {
ALWAYS_DISPLAY, // 一直显示
NOT_ALWAYS_DISPLAY // 仅在操作时显示
}
@ccclass('Joystick')
export default class Joystick extends Component {
// 绑定Camera组件,用于获取屏幕触摸位置
@property(Camera)
public camera: Camera;
// 绑定操纵杆节点
@property(Node)
public joystick: Node;
// 绑定操纵杆背景节点
@property(Node)
public joystickBG: Node;
// 绑定操纵杆的父节点
@property(Node)
public joystickParent: Node;
// 存储操纵杆的移动向量
public joystickVec: Vec3;
// 指定操纵杆的类型(固定或跟随)
@property({
type: Enum(JoystickType),
tooltip: "类型"
})
public joystickType: JoystickType = JoystickType.FIXED;
// 指定操纵杆的显示类型(一直显示或仅操作时显示)
@property({
type: Enum(JoystickShowType),
tooltip: "显示类型"
})
public joystickShowType: JoystickShowType = JoystickShowType.ALWAYS_DISPLAY;
// 触摸开始时操纵杆的位置
private joystickTouchPos: Vec3;
// 操纵杆背景的原始位置
private joystickOriginalPos: Vec3;
// 操纵杆背景的半径
private joystickRadius: number;
// 标识是否开始拖动操纵杆
public startDrop: boolean;
// 初始化
start() {
// 根据操纵杆显示类型设置其初始显示状态
if (this.joystickShowType === JoystickShowType.NOT_ALWAYS_DISPLAY) {
this.joystickParent.active = false;
} else {
this.joystickParent.active = true;
}
// 保存操纵杆背景的原始位置
this.joystickOriginalPos = this.joystickBG.position.clone();
// 计算操纵杆背景的半径(背景节点宽度的一半)
this.joystickRadius = utils.getNodeSize(this.joystickBG).width / 2;
// 绑定触摸事件的处理函数
this.node.on(Input.EventType.TOUCH_END, this.touchEnd, this);
this.node.on(Input.EventType.TOUCH_START, this.touchStart, this);
this.node.on(Input.EventType.TOUCH_MOVE, this.touchMove, this);
}
// 在组件销毁时,解除触摸事件的绑定
protected onDestroy(): void {
this.node.off(Input.EventType.TOUCH_END, this.touchEnd, this);
this.node.off(Input.EventType.TOUCH_START, this.touchStart, this);
this.node.off(Input.EventType.TOUCH_MOVE, this.touchMove, this);
}
/**
* 获取触摸事件在节点坐标系中的位置
*
* @param e 触摸事件对象
* @returns 触摸事件在节点坐标系中的位置
*/
getEventPosInNodePos(e: EventTouch) {
// 获取触摸点的屏幕坐标
let p = e.getLocation();
// 将屏幕坐标转换为世界坐标
let word = this.camera.screenToWorld(utils.getVec3(p));
// 获取UITransform组件,将世界坐标转换为节点本地坐标
let uiTransform = this.node.getComponent(UITransform);
let localPos = uiTransform.convertToNodeSpaceAR(new Vec3(word.x, word.y));
return localPos;
}
// 触摸开始事件处理函数
touchStart(e: EventTouch) {
this.startDrop = true;
this.joystickParent.active = true;
// 根据操纵杆类型设置初始触摸位置
if (this.joystickType == JoystickType.FIXED) {
// 固定类型的操纵杆,设置触摸位置为操纵杆背景的原始位置
this.joystickTouchPos = this.joystickOriginalPos.clone();
} else if (this.joystickType == JoystickType.FOLLOW) {
// 跟随类型的操纵杆,将操纵杆和背景设置为触摸位置
this.joystick.setPosition(utils.nodePosToNodePos(this.getEventPosInNodePos(e), this.node, this.joystick.parent));
this.joystickBG.setPosition(this.joystick.position);
this.joystickTouchPos = this.joystick.position.clone();
}
// 处理触摸移动
this.touchMove(e);
}
/**
* 触摸移动事件处理函数
*
* @param e 触摸事件对象
*/
touchMove(e: EventTouch) {
// 如果没有开始拖动,则不处理
if (this.startDrop === false) {
return;
}
// 获取操纵杆触摸位置的副本
let joystickTouchPos = this.joystickTouchPos.clone();
// 获取触摸事件在节点坐标系中的位置
let dragPos = this.getEventPosInNodePos(e);
// 获取操纵杆背景相对于节点的本地坐标
let nodePos = utils.nodePosToNodePos(joystickTouchPos, this.joystickBG.parent, this.node);
// 计算操纵杆的移动向量,并归一化
let joystickVec = dragPos.clone().subtract(nodePos).normalize();
// 计算触摸点与操纵杆触摸位置之间的距离
let joystickDist = Vec3.distance(dragPos, nodePos);
// 限制操纵杆在指定半径范围内移动
if (joystickDist < this.joystickRadius) {
// 如果距离小于半径,直接设置操纵杆的位置
let pos = joystickTouchPos.add(joystickVec.multiplyScalar(joystickDist));
this.joystick.setPosition(pos);
} else {
// 如果距离大于半径,设置操纵杆到半径位置
let pos = joystickTouchPos.add(joystickVec.multiplyScalar(this.joystickRadius));
this.joystick.setPosition(pos);
}
}
/**
* 触摸结束事件处理函数
*
* @param e 触摸事件对象
*/
touchEnd(e: EventTouch) {
// 标识结束拖动
this.startDrop = false;
// 根据操纵杆显示类型设置其显示状态
if (this.joystickShowType === JoystickShowType.NOT_ALWAYS_DISPLAY) {
this.joystickParent.active = false;
} else {
this.joystickParent.active = true;
}
// 将操纵杆移动向量重置为零
this.joystickVec = Vec3.ZERO;
// 将操纵杆和背景位置重置为原始位置
this.joystick.setPosition(this.joystickOriginalPos);
this.joystickBG.setPosition(this.joystickOriginalPos);
}
}
大致效果如下:
固定
跟随
社交:
QQ群:859055710