2D / 3D摇杆控制角色移动(原理讲解 + 源码分享)CocosCreator | 您所在的位置:网站首页 › 3d绘画艺术家 › 2D / 3D摇杆控制角色移动(原理讲解 + 源码分享)CocosCreator |
2D / 3D摇杆控制角色移动(原理讲解 + 源码分享)CocosCreator
源码在末尾 前言一年前我在Cocos论坛发了一篇封装2D摇杆的文章,因为对角色移动和转向这些逻辑都写在了摇杆脚本里面,有个小伙伴提出了宝贵的建议,我认为他说的很对,就重新整理下再加个3D版本的摇杆。 效果 如何使用 节点的结构 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; } } }绑定好节点 详细的内容可以看我之前写的文章:三角函数在游戏中的应用 move方法就是用来移动摇杆中心点的,需要绑定在摇杆背景上,其实应该给摇杆中心点绑定事件,因为摇杆中心点很小,如果给摇杆中心点绑定事件玩家将很难控制,摇杆的背景比较大,所以把事件都绑定在背景上是不错的选择,这样体验更好 finish方法是在结束移动摇杆的时候调用的,将摇杆位置弹回原处,并设置角色移动向量为(0, 0) 效果 一共有三个脚本 因为加了Z轴,而摇杆获取的vector的y是在二维下的,是Y轴向上X轴向右的结果。角色是三维的,是X轴向右Z轴向后的结果,所以结合角色最终移动向量的时候最后一个参数是-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 实验室设备网 版权所有 |