2D / 3D摇杆控制角色移动(原理讲解 + 源码分享)CocosCreator 您所在的位置:网站首页 3d绘画艺术家 2D / 3D摇杆控制角色移动(原理讲解 + 源码分享)CocosCreator

2D / 3D摇杆控制角色移动(原理讲解 + 源码分享)CocosCreator

2023-08-10 08:58| 来源: 网络整理| 查看: 265

2D / 3D摇杆控制角色移动(原理讲解 + 源码分享)CocosCreator

源码在末尾

前言

一年前我在Cocos论坛发了一篇封装2D摇杆的文章,因为对角色移动和转向这些逻辑都写在了摇杆脚本里面,有个小伙伴提出了宝贵的建议,我认为他说的很对,就重新整理下再加个3D版本的摇杆。 在这里插入图片描述

2D摇杆

效果 请添加图片描述

如何使用 节点的结构 在这里插入图片描述 吸取了上次的教训,这次分两个脚本实现2D摇杆 在这里插入图片描述 Joystick放在parent节点(摇杆背景和摇杆中心点父节点)上 Player放在角色上

Joystick.ts

import { _decorator, Component, Node, CCFloat, CCBoolean, Vec2, Vec3, math, log, Event, EventTouch, UITransformComponent, UITransform, CameraComponent } from 'cc'; const { ccclass, property } = _decorator; @ccclass('Main') export default class Joystick extends Component { @property({displayName: "canvas下的相机,只拍UI的那个", tooltip: "canvas下的相机,只拍UI的那个", type: CameraComponent}) camera: CameraComponent = null!; @property({displayName: "父节点", tooltip: "摇杆中心点和背景的父节点,需要用这个节点来做坐标转换", type: UITransformComponent}) parent: UITransformComponent = null!; @property({displayName: "摇杆背景", tooltip: "摇杆背景", type: Node}) bg: Node = null!; @property({displayName: "摇杆中心点", tooltip: "摇杆中心点", type: Node}) joystick: Node = null!; @property({displayName: "最大半径", tooltip: "摇杆移动的最大半径", type: CCFloat}) max_R: number = 135; @property({displayName: "是否禁用摇杆", tooltip: "是否禁用摇杆,禁用后摇杆将不能摇动"}) is_forbidden: boolean = false; // 角色旋转的角度,不要轻易修改 angle: number = 0; // 移动向量 vector: Vec2 = new Vec2(0, 0); onLoad () { // 绑定事件 // 因为摇杆中心点很小,如果给摇杆中心点绑定事件玩家将很难控制,摇杆的背景比较大,所以把事件都绑定在背景上是不错的选择,这样体验更好 // 手指移动 this.bg.on(Node.EventType.TOUCH_MOVE,this.move,this); // 手指结束 this.bg.on(Node.EventType.TOUCH_END,this.finish,this); // 手指取消 this.bg.on(Node.EventType.TOUCH_CANCEL,this.finish,this); } update () { // 如果角色的移动向量为(0, 0),就不执行以下代码 if (this.vector.x == 0 && this.vector.y == 0) { return; } // 求出角色旋转的角度 let angle = this.vector_to_angle(this.vector); // 赋值给angle,Player脚本将会获取angle this.angle = angle; } // 手指移动时调用,移动摇杆专用函数 move (event: EventTouch) { // 如果没有禁用摇杆 if(this.is_forbidden == false){ /* 通过点击屏幕获得的点的坐标是屏幕坐标 必须先用相机从屏幕坐标转到世界坐标 再从世界坐标转到节点坐标 就这个问题折腾了很久 踩坑踩坑踩坑 */ // 获取触点的位置,屏幕坐标 let point = new Vec2(event.getLocationX(), event.getLocationY()); // 屏幕坐标转为世界坐标 let world_point = this.camera.screenToWorld(new Vec3(point.x, point.y)); // 世界坐标转节点坐标 // 将一个点转换到节点 (局部) 空间坐标系,这个坐标系以锚点为原点。 let pos = this.parent.convertToNodeSpaceAR(new Vec3(world_point.x, world_point.y)); // 如果触点长度小于我们规定好的最大半径 if (pos.length() // 如果不 // 将向量归一化 let pos_ = pos.normalize(); // 归一化的坐标 * 最大半径 let x = pos_.x * this.max_R; let y = pos_.y * this.max_R; // 赋值给摇杆 this.joystick.setPosition(x, y); } // 把摇杆中心点坐标,也就是角色移动向量赋值给vector this.vector = new Vec2(this.joystick.position.x, this.joystick.position.y); } // 如果摇杆被禁用 else { // 弹回摇杆 this.finish(); } } // 摇杆中心点弹回原位置专用函数 finish () { // 摇杆坐标和移动向量都设为(0,0) this.joystick.position = new Vec3(0, 0); this.vector = new Vec2(0, 0); } // 角度转弧度 angle_to_radian (angle: number): number { // 角度转弧度公式 // π / 180 * 角度 // 计算出弧度 let radian = Math.PI / 180 * angle; // 返回弧度 return(radian); } // 弧度转角度 radian_to_angle (radian: number): number { // 弧度转角度公式 // 180 / π * 弧度 // 计算出角度 let angle = 180 / Math.PI * radian; // 返回弧度 return(angle); } // 角度转向量 angle_to_vector (angle: number): Vec2 { // tan = sin / cos // 将传入的角度转为弧度 let radian = this.angle_to_radian(angle); // 算出cos,sin和tan let cos = Math.cos(radian);// 邻边 / 斜边 let sin = Math.sin(radian);// 对边 / 斜边 let tan = sin / cos;// 对边 / 邻边 // 结合在一起并归一化 let vec = new Vec2(cos, sin).normalize(); // 返回向量 return(vec); } // 向量转角度 vector_to_angle (vector: Vec2): number { // 将传入的向量归一化 let dir = vector.normalize(); // 计算出目标角度的弧度 let radian = dir.signAngle(new Vec2(1, 0)); // 把弧度计算成角度 let angle = -this.radian_to_angle(radian); // 返回角度 return(angle); } }

Player.ts

// 导入Joystick脚本 import joy from "./Joystick" import { _decorator, Component, Node, CCLoader, CCFloat, Vec2, log } from 'cc'; const { ccclass, property } = _decorator; @ccclass('Player') export class Player extends Component { @property({displayName: "摇杆脚本所在节点", tooltip: "摇杆脚本Joystick所在脚本", type: joy}) joy: joy = null!; @property({displayName: "角色", tooltip: "角色", type: Node}) player: Node = null!; @property({displayName: "是否根据方向旋转角色", tooltip: "角色是否根据摇杆的方向旋转"}) is_angle: boolean = true; @property({displayName: "是否禁锢角色", tooltip: "是否禁锢角色,如果角色被禁锢,角色就动不了了"}) is_fbd_player: boolean = false; @property({displayName: "角色移动速度", tooltip: "角色移动速度,不建议太大,1-10最好", type: CCFloat}) speed: number = 3; // 角色的移动向量 vector: Vec2 = new Vec2(0, 0); // 角色旋转的角度 angle: number = 0; update () { // console.log("vector", this.vector.toString(), "angle", this.angle); // 如果没有禁锢角色 if (this.is_fbd_player == false) { // 获取角色移动向量 this.vector = this.joy.vector; // 向量归一化 let dir = this.vector.normalize(); // 乘速度 let dir_x = dir.x * this.speed; let dir_y = dir.y * this.speed; // 角色坐标加上方向 let x = this.player.position.x + dir_x; let y = this.player.position.y + dir_y; // 设置角色坐标 this.player.setPosition(x, y); } // 如果根据方向旋转角色 if (this.is_angle == true) { // 获取角色旋转的角度 this.angle = this.joy.angle; // 对角色进行旋转 this.player.angle = this.angle; } } }

绑定好节点 在这里插入图片描述 在这里插入图片描述 原理讲解 每句代码我都写了非常详细的注释,属性也都加了中文的显示名称请添加图片描述 先实现在规定范围内移动摇杆,都写在Joystick脚本里面 Joystick脚本里面封装了四个方法,分别是角度转弧度,弧度转角度,角度转向量和向量转角度

详细的内容可以看我之前写的文章:三角函数在游戏中的应用

move方法就是用来移动摇杆中心点的,需要绑定在摇杆背景上,其实应该给摇杆中心点绑定事件,因为摇杆中心点很小,如果给摇杆中心点绑定事件玩家将很难控制,摇杆的背景比较大,所以把事件都绑定在背景上是不错的选择,这样体验更好 在这里插入图片描述 首先获取触点坐标,转化为parent节点局部空间坐标系 2.x的convertToNodeSpaceAR在Node下,而3.x的Node下就没有这个方法了,这个方法在UITransformComponent下 还有一个值得注意的点,2.x将触摸得到的点直接使用convertToNodeSpaceAR就可以完成转换,而3.x必须先用相机的screenToWorld方法把屏幕坐标转到世界坐标,然后再使用convertToNodeSpaceAR转到节点坐标(踩坑踩坑踩坑,这个问题我折腾了好几天) 我们只希望摇杆在规定好的max_R(最大半径)的范围内,不希望摇杆超出这个范围 所以要判定一下触点的坐标在不在规定的最大半径范围内,通过length获取触点坐标的长度,也就是触点距离原点的长度 在这里插入图片描述 比如我想求点A坐标的长度,求出的结果就是绿色线段的长度 并在最后设置好角色移动向量,Player脚本会获取vector来控制角色移动

finish方法是在结束移动摇杆的时候调用的,将摇杆位置弹回原处,并设置角色移动向量为(0, 0) 在这里插入图片描述 update里面求出角色旋转的角度,其实就是把vector向量转为角度 在这里插入图片描述 在Player脚本的update里面获取Joystick脚本的vector和angle,并根据需要设置角色的坐标和旋转角度 在这里插入图片描述

3D摇杆

效果请添加图片描述 既然有了摇杆,就再加一个跳跃按钮和视角的移动吧 原理讲解 节点结构 在这里插入图片描述

一共有三个脚本 在这里插入图片描述 Joystick放在摇杆背景和摇杆中心点父节点上,Player放在角色上。UI放在canvas上,视角移动和跳跃相关逻辑写在这里 JoyStick和2D摇杆的Joystick区别不大,去掉了angle属性,因为3D摇杆不需要计算角色旋转 ,还去掉了封装的四个方法,分别是角度转弧度,弧度转角度,角度转向量和向量转角度,这些在3D摇杆里面都用不到了 2D摇杆是直接对角色坐标进行加减,3D摇杆中没有那么做,而是给角色加上了刚体和碰撞体,并且撸了一个地形 Player和2D摇杆的Player区别也不是很大,属性去掉了angle(角色旋转的角度)和is_angle(是否根据方向旋转角色),player的类型由Node改成了RigidBodyComponent,update全都不一样了 Player.ts中的update

update () { // 如果没有禁锢角色 if (this.is_fbd_player == false) { // 获取角色目标移动向量 this.vector = this.joy.vector; // 归一化 let dir = this.vector.normalize(); // 乘速度 let x = dir.x * this.speed; let y = dir.y * this.speed; // 获取角色当前移动向量 let vc = new Vec3(0, 0, 0); this.player.getLinearVelocity(vc); // 结合成角色最终移动向量,因为摇杆获取的是Y轴,而最终设置的线性速度应该是Z轴,所以最后一个参数是负的 let vec = new Vec3(x, vc.y, -y); // 向量四元数乘法 Vec3.transformQuat(vec, vec, this.player.node.getRotation()); // 设置角色移动向量 this.player.setLinearVelocity(vec); } }

因为加了Z轴,而摇杆获取的vector的y是在二维下的,是Y轴向上X轴向右的结果。角色是三维的,是X轴向右Z轴向后的结果,所以结合角色最终移动向量的时候最后一个参数是-y 在这里插入图片描述 在这里插入图片描述 左右移动视角其实移动的是角色Y旋转,角色是主相机的父节点,所以相机也会跟着动,需要用到向量四元数乘法来根据角色朝向算出新的三维向量,最后设置线性速度

UI.ts

// 导入Player脚本 import player from "./Player"; import { _decorator, Component, Node, SystemEventType, EventMouse, Vec3, CCFloat, Vec2, EventTouch } from 'cc'; const { ccclass, property } = _decorator; @ccclass('UI') export class UI extends Component { @property({displayName: "Player脚本所在节点", tooltip: "Player脚本所在节点", type: player}) player: player = null!; @property({ displayName: "移动视角事件目标节点", tooltip: "代码将把移动视角的事件绑定到这个节点上,推荐把这个节点的宽高设置成和canvas一样,并给四个方向加上widget", type: Node }) target: Node = null!; @property({displayName: "相机", tooltip: "相机", type: Node}) camera: Node = null!; @property({displayName: "相机移动速度", tooltip: "相机移动速度", type: CCFloat}) angle_speed: number = 0.1; @property({displayName: "跳跃的高度", tooltip: "跳跃的高度,代码会根据这个值设置角色刚体的Y线性速度", type: CCFloat}) jump_height: number = 5; @property({displayName: "跳跃按钮禁用时间", tooltip: "按一次跳跃按钮禁用多长时间,单位是秒", type: CCFloat}) jump_btn_time: number = 1; @property({displayName: "相机上下移动限制", tooltip: "限制相机X旋转,X是向上移动限制的角度,Y是向下移动限制的角度"}) cam_att: Vec2 = new Vec2(-25, -50); // 是否可以跳跃 is_jump: boolean = true; onLoad () { let self = this; // 给canvas绑定触摸移动事件 this.target.on(SystemEventType.TOUCH_MOVE, function (e: EventTouch) { // 获取鼠标距离上一次事件移动的距离对象,对象包含 x 和 y 属性 let D = e.getDelta(); // 上下左右移动视角 // 左右移动视角是移动角色的Y旋转 if (D.x self.player.node.eulerAngles = self.player.node.eulerAngles.add3f(0, -D.x * self.angle_speed, 0); } // 上下移动视角是移动相机的X旋转 if (D.y self.camera.eulerAngles = self.camera.eulerAngles.add3f(D.y * self.angle_speed, 0, 0); } // 限制相机上下移动范围 let angle = self.camera.eulerAngles; if (self.camera.eulerAngles.x > self.cam_att.x) { self.camera.eulerAngles = new Vec3(self.cam_att.x, angle.y, angle.z); } if (self.camera.eulerAngles.x if (this.is_jump == true) { // 获取角色移动向量 let vc = new Vec3(0, 0, 0); this.player.player.getLinearVelocity(vc); // 设置角色Y的移动向量,让角色跳起来 this.player.player.setLinearVelocity(new Vec3(vc.x, this.jump_height, vc.z)); // console.log("点击了跳跃按钮"); let self = this; // 不可以再次跳跃 this.is_jump = false; // 规定时间后恢复跳跃 this.scheduleOnce(function () { self.is_jump = true; }, this.jump_btn_time); } } }

跳跃就是设置角色刚体线性速度的Y,其他的都不动 视角的上下移动因为没有相机弹簧,第三人称上下移动视角的时候会很奇怪很奇怪,所以加了视角上下移动的限制

想用相机弹簧可以去看白玉无冰大佬的文章 https://mp.weixin.qq.com/s/NCn8Ygk_I_nRnhmbHQeZwQ

2D摇杆源代码:https://gitee.com/propertygame/cocos-creator3.x-demos/tree/master/2DJoystick 3D摇杆源代码:https://gitee.com/propertygame/cocos-creator3.x-demos/tree/master/3DJoystick 技术交流Q群:1130122408 更多内容请关注微信公众号

请添加图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有