本文很长,建议收藏食用。
课程来源:
游戏开发教程 | 零基础也可以用18堂课自制一款3D小游戏 | Cocos Creator 3D 中文教程(合集)p1~p6
简介:
资源下载:
适合学习人群:本教程假定你对编程有一定的了解,ts,js 学习过其中之一。
如果不曾了解过,可以参考 js 教程()或者 ts 教程(/)
下载引擎:/
关注官方微信公众号(CocosEngine),不定期推送福利活动、新手教程还有好玩的活动噢!
本文作者的bb:
项目已上传至:
引擎版本为Cocos 3d 1.2.0
本文毕竟是以文字的形式讲解视频的内容,因此部分重复的步骤(新建节点、设置对应的位置、属性等重复且类似的工作)不会在文中过多赘述,也不会逐一截图。
此外,本人对Cocos Creator 2.x版本已经掌握,可以制作2d小游戏,所以也并不完全是“零基础”。完全没用过Cocos 引擎肯定是不行的,本文也不会介绍@property
是什么意思、脚本、组件是什么之类的知识点。Cocos初学者请移步Cocos官网去看文档启蒙!
其他如遇看不懂的地方,可以私信本人补充,但仍然建议移步上方链接去看看视频,视频一共才9小时,大家一起加油!
本文只讲述从下载引擎,新建项目,到让小车运动、乘客运动内容。更多特效、音效、碰撞、界面部分不在本文展示。以下内容已经完全可以满足新手入门的需求!
打开CocosDashboard,下载一个cocos 3d 1.2.0引擎。虽然显示废弃,但是视频教程用的是这个,而且根据官网的描述,3d 1.2.0的项目可以直接移植到Cocos Creator 3.x版本上。因此为了学习方便,下一个即将废弃的3d 1.2.0吧。
下好后是这样的:
下载时要往下滑,滑到最底下找到废弃的版本。
在右下角新建项目
记得修改项目名字,叫Taxi或者什么,随意。
新建完成后,我们就得到了一个空的项目
策划给我们画的草图如下:
其中从start到end一共有4个建筑物,分别对应的是接客、送客、接客、送客操作。
windows系统下,ctrl+d复制当前节点。
因为我们的地图是要复用的,因此我们将它存为预制。
在asserts文件夹下新建一个prefab文件夹,再在prefab下建一个map文件夹,将层级管理器里的map101直接拖到这个文件夹里,保存这个预制。
至此场景已经搭建完了,和2d里的没有太大区别。
至此,之前的一切都是我们肉眼可见的范围,比如我们知道在哪接客,在哪送客。但是程序是不知道的,因此我们需要通过程序来处理接送客人的逻辑。
因此我们需要一个路径,来让程序得以控制它。
在map101节点下新建空节点path,并在path下新建空节点path01,这就是给我们当前的玩家小车来定制的路线。
同时,我们将小车模型资源放上去。
保存玩家可用的小车为预制。(将taxi01改名为car101)
把玩家可用的小车都做成这样的预制,并保存。
我们现在要控制小车从一个点到下一个点,并且根据路面来看,是有曲线的。因此我们需要根据道路来定制路线。
以下罗列了这些点应该有的结构。
框中的意思为:这些点分为开始点、普通点、接客点、送客点、结束点。
右侧的意思为:除了终点以外,对于每个点都有一个“下一站”。并且,我们移动的类型也有两种,有直线行驶,也有拐弯行驶。
左侧的意思为:每条线的障碍物小车的产生频率、产生的延迟时间、车速、车的种类等。
新建一个脚本,用枚举的方式来定义这些点的属性和路面样式。
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;/** 路上的点的种类*/
/** 路上的点的种类*/
enum ROAD_POINT_TYPE {NORMAL = 1,START,GREETING,GOODBYE,END,AISTART,//AISTART点用于区分小车行为,用于控制AI小车
}//将enum序列化识别,cocos 3d专属,否则无法在引擎的组件里显示
Enum(ROAD_POINT_TYPE)/**路的种类 */
enum ROAD_MOVE_TYPE {LINE = 1,CURVE,
}Enum(ROAD_MOVE_TYPE)
@property({type: ROAD_POINT_TYPE,displayOrder: 1//如果在引擎里属性检查器里这些property乱序了,用这个来排序。})type = ROAD_POINT_TYPE.NORMAL;@property({type: Node,/**不是end类型的点就不显示这个属性 */visible: function (this: RoadPoint) {pe != ROAD_POINT_TYPE.END}})nextStation: Node = null;@property({ type: ROAD_MOVE_TYPE })moveType = ROAD_MOVE_TYPE.LINE;@property({visible: function (this: RoadPoint) {pe != ROAD_POINT_TYPE.END && veType === ROAD_MOVE_TYPE.CURVE}})clockwise = true;//默认顺时针@property({type: Vec3,visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.GREETING || pe === ROAD_POINT_TYPE.GOODBYE}})direction = new Vec3(1, 0, 0);//接送客的方向,默认右边@property({visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.AISTART}})interval = 3;//AI产出间隔@property({visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.AISTART}})delayTime = 0;@property({visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.AISTART}})speed = 0.05;//小车速度@property({visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.AISTART}})/**如果要产出不同的车,用逗号分开。如"201,202" */cars = '201';//当前路径产出的小车的类型
import { _decorator, Component, Node, Vec3, Enum } from 'cc';
const { ccclass, property } = _decorator;/** 路上的点的种类*/
enum ROAD_POINT_TYPE {NORMAL = 1,START,GREETING,GOODBYE,END,AISTART,//AISTART点用于区分小车行为,用于控制AI小车
}//将enum序列化识别,cocos 3d专属,否则无法在引擎的组件里显示
Enum(ROAD_POINT_TYPE)/**路的种类 */
enum ROAD_MOVE_TYPE {LINE = 1,BEND,
}Enum(ROAD_MOVE_TYPE)@ccclass('RoadPoint')
export class RoadPoint extends Component {public static RoadPointType = ROAD_POINT_TYPE;public static RoadMoveType = ROAD_MOVE_TYPE;@property({type: ROAD_POINT_TYPE,displayOrder: 1//如果在引擎里属性检查器里这些property乱序了,用这个来排序。})type = ROAD_POINT_TYPE.NORMAL;@property({type: Node,/**不是end类型的点就不显示这个属性 */visible: function (this: RoadPoint) {pe != ROAD_POINT_TYPE.END}})nextStation: Node = null;@property({ type: ROAD_MOVE_TYPE })moveType = ROAD_MOVE_TYPE.LINE;@property({visible: function (this: RoadPoint) {pe != ROAD_POINT_TYPE.END && veType === ROAD_MOVE_TYPE.BEND}})clockwise = true;//默认顺时针@property({type: Vec3,visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.GREETING || pe === ROAD_POINT_TYPE.GOODBYE}})direction = new Vec3(1, 0, 0);//接送客的方向,默认右边@property({visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.AISTART}})interval = 3;//AI产出间隔@property({visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.AISTART}})delayTime = 0;@property({visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.AISTART}})speed = 0.05;//小车速度@property({visible: function (this: RoadPoint) {pe === ROAD_POINT_TYPE.AISTART}})/**如果要产出不同的车,用逗号分开。如"201,202" */cars = '201';//当前路径产出的小车的类型
}
根据游戏逻辑,可以整理出游戏中包含的几个大类:界面(UI)、音频资源、特效、车(玩家车、AI车)、乘客、关卡(地图)。
对于游戏的几个大类,我们应当对其设置不同的管理类:
MapManager
CarManager
CustomerManager
AudioManager
EffectManager
UIManager
这些管理部分,还要统一被一个流程控制的控制类来控制流程,来负责管理这几个类:GameCtrl
。
这些管理部分又可以衍生出自身要管理的部分,大致如下所示:
在本游戏中,我们采用事件的方式播放音效和特效。
import { _decorator, Component, Node } from 'cc';
import { CarManager } from './CarManager';
import { MapManager } from './MapManager';
const { ccclass, property } = _decorator;@ccclass('GameCtrl')
export class GameCtrl extends Component {@property({ type: MapManager })mapManager: MapManager = null;@property({ type: CarManager })carManager: CarManager = null;}
CustomEventListener
脚本,其中包含事件监听、取消监听、派发三个方法。import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;interface IEventData {func: Function;//回调target: any;//回调目标
}interface IEvent {[eventName: string]: IEventData[];
}@ccclass('CustomEventListener')
export class CustomEventListener extends Component {public static handle: IEvent = {};/*** 事件的注册* @param eventName 事件名* @param cb 回调* @param target 回调的对象*/public static on(eventName: string, cb: Function, target?: any) {//如果当前处理器没有相对应的事件名,则建立这个事件名的数据if (!this.handle[eventName]) {this.handle[eventName] = [];}//定义数据的结构,其中target与传入参数同名,可以将target:target简写如下const data: IEventData = { func: cb, target };//将这个事件记录上去this.handle[eventName].push(data);}/*** 事件的注销* @param eventName 事件名* @param cb 回调* @param target 回调的对象* @returns */public static off(eventName: string, cb: Function, target?: any) {const list = this.handle[eventName];//判断是否有注册过这个事件//假如没注册过或者长度小于等于零if (!list || list.length <= 0) {return;}//遍历for (let i = 0; i < list.length; i++) {const event = list[i];if (event.func === cb && (!target || target === event.target)) {list.splice(i, 1);//注销掉这个事件break;}}}/*** 事件派发* @param eventName 事件名* @param args 需要传递的参数*/public static dispatchEvent(eventName: string, ...args: any) {const list = this.handle[eventName];//判断是否有注册过这个事件//假如没注册过或者长度小于等于零if (!list || list.length <= 0) {return;}for (let i = 0; i < list.length; i++) {const event = list[i];event.func.apply(event.target, args);}}
}
第一步:小车的开始位置摆放
第二步:小车的运动,匀速从开始到结束
第三步:小车拐弯
第四步:小车匀加速和刹车
只要规定了起始点,小车自然能找到后面的道路。
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;@ccclass('GameMap')
export class GameMap extends Component {/**记录所有的开始点 */@property({type: [Node]})path: Node[] = [];
}
import { _decorator, Component, Node } from 'cc';
import { GameMap } from './GameMap';
const { ccclass, property } = _decorator;@ccclass('MapManager')
export class MapManager extends Component {public currPath: Node[] = [];//当前的路径/**重新更新当前关卡的数据 */public resetMap() {const currMap = de.children[0].getComponent(GameMap);this.currPath = currMap.path;}
}
import { _decorator, Component, Node } from 'cc';
import { Car } from './Car';
const { ccclass, property } = _decorator;@ccclass('CarManager')
export class CarManager extends Component {@property({type: Car})mainCar: Car = null;public resetCars(points: Node[]) {//没有点,抛出警告if (points.length <= 0) {console.warn("There is no points in this map");return;}//有点this._createMainCar(points[0]);}private _createMainCar(point: Node) {this.mainCar.setEntry(point);}
}
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;@ccclass('Car')
export class Car extends Component {/**根据这个点设置自己的位置 */public setEntry(entry: Node) {de.setWorldPosition(entry.worldPosition);}
}
import { _decorator, Component, Node } from 'cc';
import { CarManager } from './CarManager';
import { MapManager } from './MapManager';
const { ccclass, property } = _decorator;@ccclass('GameCtrl')
export class GameCtrl extends Component {@property({ type: MapManager })mapManager: MapManager = null;@property({ type: CarManager })carManager: CarManager = null;public onLoad() {setMap();setCars(this.mapManager.currPath);}
}
把相机摆放在小车上面,并调整一个合适的角度。
这里就随便调调,合适就行。
运行看看:
public _currRoadPoint: RoadPoint = null;private _pointA = new Vec3();private _pointB = new Vec3();/**根据这个点设置自己的位置 */public setEntry(entry: Node) {de.setWorldPosition(entry.worldPosition);this._currRoadPoint = Component(RoadPoint);if (!this._currRoadPoint) {console.warn("There is no RoadPoint in ", entry.name);return;}this._pointA.set(entry.worldPosition);this._pointB.set(this._Station.worldPosition);//计算朝向const z = this._pointB.z - this._pointA.z;if (z !== 0) {//排除干扰因素if (z < 0) {de.eulerAngles = new Vec3();} else {//翻180度de.eulerAngles = new Vec3(0, 180, 0);}} else {const x = this._pointB.x - this._pointA.x;if (x > 0) {de.eulerAngles = new Vec3(0, 270, 0);} else {de.eulerAngles = new Vec3(0, 90, 0);}}}
public start() {(Node.EventType.TOUCH_START, this._touchStart, this);(Node.EventType.TOUCH_END, this._touchEnd, this);}private _touchStart() {lMoving();}private _touchEnd() {lMoving(false);}
private _createMainCar(point: Node) {this.mainCar.setEntry(point);}public controlMoving(isRunning = true) {if (isRunning) {this.mainCar.startRunning();} else {this.mainCar.stopRunning();}}
public update(dt: number) {if (this._isMoving) {console.log("Moving");}}public startRunning() {if (this._currRoadPoint) {//游戏已经初始化this._isMoving = true;}}public stopRunning() {this._isMoving = false;}
public update(dt: number) {if (this._isMoving) {this._offset.de.worldPosition);//朝向哪就往哪运动switch (this._veType) {case RoadPoint.RoadMoveType.BEND:break;default:const z = this._pointB.z - this._pointA.z;if (z !== 0) {if (z > 0) {this._offset.z += this._curSpeed;} else {this._offset.z -= this._curSpeed;}} else {const x = this._pointB.x - this._pointA.x;if (x > 0) {this._offset.x += this._curSpeed;} else {this._offset.x -= this._curSpeed;}}break;}de.setWorldPosition(this._offset);}}
const _tempVec = new Vec3();
public update(dt: number) {if (this._isMoving) {this._offset.de.worldPosition);//朝向哪就往哪运动switch (this._veType) {case RoadPoint.RoadMoveType.BEND:break;default:const z = this._pointB.z - this._pointA.z;if (z !== 0) {if (z > 0) {this._offset.z += this._curSpeed;//容错if (this._offset.z > this._pointB.z) {this._offset.z = this._pointB.z;}} else {this._offset.z -= this._curSpeed;//容错if (this._offset.z < this._pointB.z) {this._offset.z = this._pointB.z;}}} else {const x = this._pointB.x - this._pointA.x;if (x > 0) {this._offset.x += this._curSpeed;//容错if (this._offset.x > this._pointB.x) {this._offset.x = this._pointB.x;}} else {this._offset.x -= this._curSpeed;//容错if (this._offset.x < this._pointB.x) {this._offset.x = this._pointB.x;}}}break;}de.setWorldPosition(this._offset);Vec3.subtract(_tempVec, this._pointB, this._offset);if (_tempVec.length() <= 0.01) {this._arrivalStation();}}}
private _arrivalStation() {console.log(");this._pointA.set(this._pointB);this._currRoadPoint = this._Component(RoadPoint);if (this._Station) {this._pointB.set(this._Station.worldPosition)} else {this._isMoving = false;this._currRoadPoint = null;}}
private _arrivalStation() {console.log(");this._pointA.set(this._pointB);this._currRoadPoint = this._Component(RoadPoint);if (this._Station) {this._pointB.set(this._Station.worldPosition)if (this._veType === RoadPoint.RoadMoveType.BEND) {if (this._currRoadPoint.clockwise) {//判断顺时针或逆时针this._originRotation = this._de.eulerAngles.y);this._targetRotation = this._originRotation - 90;//求顺时针centerPointif ((this._pointB.z < this._pointA.z && this._pointB.x > this._pointA.x) || (this._pointB.z > this._pointA.z && this._pointB.x < this._pointA.x)) {this._centerPoint.set(this._pointB.x, 0, this._pointA.z);} else {this._centerPoint.set(this._pointA.x, 0, this._pointB.z);}} else {this._originRotation = this._de.eulerAngles.y);this._targetRotation = this._originRotation + 90;//求逆时针centerPointif ((this._pointB.z > this._pointA.z && this._pointB.x > this._pointA.x) || (this._pointB.z < this._pointA.z && this._pointB.x < this._pointA.x)) {this._centerPoint.set(this._pointB.x, 0, this._pointA.z);} else {this._centerPoint.set(this._pointA.x, 0, this._pointB.z);}}Vec3.subtract(_tempVec, this._pointA, this._centerPoint);const r = _tempVec.length();this._rotMeasure = 90 / (Math.PI * r / 2);}} else {this._isMoving = false;this._currRoadPoint = null;}}/**工具函数:将所有负数角度都转化为正数角度 */private _conversion(value: number) {let a = value;if (a <= 0) {a += 360;}return a;}
switch (this._veType) {case RoadPoint.RoadMoveType.BEND:const offsetRotation = this._targetRotation - this._originRotation;const currRotation = this._de.eulerAngles.y)let nextStation = (currRotation - this._originRotation) + (this._currSpeed * this._rotMeasure * (this._targetRotation > this._originRotation ? 1 : -1));if (Math.abs(nextStation) > Math.abs(offsetRotation)) {nextStation = offsetRotation;}const target = nextStation + this._originRotation;_tempVec.set(0, target, 0);de.eulerAngles = _tempVec;const sin = Math.sin(nextStation * Math.PI / 180);//通过角度求出弧度const cos = s(nextStation * Math.PI / 180);const xLength = this._pointA.x - this._centerPoint.x;const zLength = this._pointA.z - this._centerPoint.z;const offx = xLength * cos + zLength * sin + this._centerPoint.x;const offz = -xLength * sin + zLength * cos + this._centerPoint.z;this._offset.set(offx, 0, offz)break;
// const sin = Math.sin(nextStation * Math.PI / 180);//通过角度求出弧度
// const cos = s(nextStation * Math.PI / 180);
// const xLength = this._pointA.x - this._centerPoint.x;
// const zLength = this._pointA.z - this._centerPoint.z;
// const offx = xLength * cos + zLength * sin + this._centerPoint.x;
// const offz = -xLength * sin + zLength * cos + this._centerPoint.z;
// this._offset.set(offx, 0, offz)//但是我数学不好,我不会用矩阵算旋转,我直接用下面这个API一步到位,真香
//绕轴旋转的API
ateY(this._offset, this._pointA, this._centerPoint, nextStation * Math.PI / 180);
@propertymaxSpeed = 0.2;//最大限速private _currSpeed = 0;private _acceleration = 0.2;//加速度
public startRunning() {if (this._currRoadPoint) {//游戏已经初始化this._isMoving = true;this._currSpeed = 0;this._acceleration = 0.2;}}public stopRunning() {this._acceleration = -0.3;// this._isMoving = false;}
import { _decorator, Component, Node, Vec3, CurveRange } from 'cc';
import { RoadPoint } from './RoadPoint';
const { ccclass, property } = _decorator;
const _tempVec = new Vec3();@ccclass('Car')
export class Car extends Component {@propertymaxSpeed = 0.2;//最大限速public _currRoadPoint: RoadPoint = null;private _pointA = new Vec3();private _pointB = new Vec3();private _currSpeed = 0;private _acceleration = 0.2;//加速度private _isMoving: boolean = false;//标记游戏是否已经开始private _offset = new Vec3();private _originRotation = 0;//旋转相关private _targetRotation = 0;//旋转相关private _centerPoint = new Vec3();//旋转相关private _rotMeasure = 0;//旋转相关度量值public update(dt: number) {if (this._isMoving) {this._offset.de.worldPosition);this._currSpeed += this._acceleration * dt;if (this._currSpeed > this.maxSpeed) {this._currSpeed = this.maxSpeed;//限速}if (this._currSpeed <= 0.001) {this._isMoving = false;//刹车到已经停止}//朝向哪就往哪运动switch (this._veType) {case RoadPoint.RoadMoveType.BEND:const offsetRotation = this._targetRotation - this._originRotation;const currRotation = this._de.eulerAngles.y)let nextStation = (currRotation - this._originRotation) + (this._currSpeed * this._rotMeasure * (this._targetRotation > this._originRotation ? 1 : -1));if (Math.abs(nextStation) > Math.abs(offsetRotation)) {nextStation = offsetRotation;}const target = nextStation + this._originRotation;_tempVec.set(0, target, 0);de.eulerAngles = _tempVec;//绕轴旋转的ateY(this._offset, this._pointA, this._centerPoint, nextStation * Math.PI / 180);break;default:const z = this._pointB.z - this._pointA.z;if (z !== 0) {if (z > 0) {this._offset.z += this._currSpeed;//容错if (this._offset.z > this._pointB.z) {this._offset.z = this._pointB.z;}} else {this._offset.z -= this._currSpeed;//容错if (this._offset.z < this._pointB.z) {this._offset.z = this._pointB.z;}}} else {const x = this._pointB.x - this._pointA.x;if (x > 0) {this._offset.x += this._currSpeed;//容错if (this._offset.x > this._pointB.x) {this._offset.x = this._pointB.x;}} else {this._offset.x -= this._currSpeed;//容错if (this._offset.x < this._pointB.x) {this._offset.x = this._pointB.x;}}}break;}de.setWorldPosition(this._offset);Vec3.subtract(_tempVec, this._pointB, this._offset);if (_tempVec.length() <= 0.01) {this._arrivalStation();}}}/**根据这个点设置自己的位置 */public setEntry(entry: Node) {de.setWorldPosition(entry.worldPosition);this._currRoadPoint = Component(RoadPoint);if (!this._currRoadPoint) {console.warn("There is no RoadPoint in ", entry.name);return;}this._pointA.set(entry.worldPosition);this._pointB.set(this._Station.worldPosition);//计算朝向const z = this._pointB.z - this._pointA.z;if (z !== 0) {//排除干扰因素if (z < 0) {de.eulerAngles = new Vec3();} else {//翻180度de.eulerAngles = new Vec3(0, 180, 0);}} else {const x = this._pointB.x - this._pointA.x;if (x > 0) {de.eulerAngles = new Vec3(0, 270, 0);} else {de.eulerAngles = new Vec3(0, 90, 0);}}}/**为什么在这里使用世界坐标,因为setPosition只设置相对父节点的位置,而使用世界坐标则是绝对的位置。 */public startRunning() {if (this._currRoadPoint) {//游戏已经初始化this._isMoving = true;this._currSpeed = 0;this._acceleration = 0.2;}}public stopRunning() {this._acceleration = -0.3;// this._isMoving = false;}private _arrivalStation() {console.log(");this._pointA.set(this._pointB);this._currRoadPoint = this._Component(RoadPoint);if (this._Station) {this._pointB.set(this._Station.worldPosition)if (this._veType === RoadPoint.RoadMoveType.BEND) {if (this._currRoadPoint.clockwise) {//判断顺时针或逆时针this._originRotation = this._de.eulerAngles.y);this._targetRotation = this._originRotation - 90;//求顺时针centerPointif ((this._pointB.z < this._pointA.z && this._pointB.x > this._pointA.x) || (this._pointB.z > this._pointA.z && this._pointB.x < this._pointA.x)) {this._centerPoint.set(this._pointB.x, 0, this._pointA.z);} else {this._centerPoint.set(this._pointA.x, 0, this._pointB.z);}} else {this._originRotation = this._de.eulerAngles.y);this._targetRotation = this._originRotation + 90;//求逆时针centerPointif ((this._pointB.z > this._pointA.z && this._pointB.x > this._pointA.x) || (this._pointB.z < this._pointA.z && this._pointB.x < this._pointA.x)) {this._centerPoint.set(this._pointB.x, 0, this._pointA.z);} else {this._centerPoint.set(this._pointA.x, 0, this._pointB.z);}}Vec3.subtract(_tempVec, this._pointA, this._centerPoint);const r = _tempVec.length();this._rotMeasure = 90 / (Math.PI * r / 2);}} else {this._isMoving = false;this._currRoadPoint = null;}}/**将所有负数角度都转化为正数角度 */private _conversion(value: number) {let a = value;if (a <= 0) {a += 360;}return a;}
}
用回我们之前使用的直线地图。
将customer模型加入场景中,并更改名字
新建CustomerManager脚本并挂在customerManger节点上
/**接客 */private _greetingCustomer() {this._isInOrder = true;CustomEventListener.dispatchEvent(EventName.GREETING, de.worldPosition, this._currRoadPoint.direction);}/**送客 */private _takingCustomer() {this._isInOrder = true;CustomEventListener.dispatchEvent(EventName.GOODBYE, de.worldPosition, this._currRoadPoint.direction);}private finishedWalk() {this._isInOrder = false;}
public update(dt: number) {if (!this._isMoving || this._isInOrder) return;
public setEntry(entry: Node, isMain = false) {this._isMainCar = isMain;
import { _decorator, Component, Node, Vec3, CurveRange } from 'cc';
import { Constants } from '../data/Constants';
import { CustomEventListener } from '../data/CustomEventListener';
import { RoadPoint } from './RoadPoint';
const { ccclass, property } = _decorator;const _tempVec = new Vec3();
const EventName = Constants.EventName;@ccclass('Car')
export class Car extends Component {@propertymaxSpeed = 0.2;//最大限速public _currRoadPoint: RoadPoint = null;private _pointA = new Vec3();private _pointB = new Vec3();private _currSpeed = 0;private _acceleration = 0.2;//加速度private _isMoving: boolean = false;//标记游戏是否已经开始private _offset = new Vec3();private _originRotation = 0;//旋转相关private _targetRotation = 0;//旋转相关private _centerPoint = new Vec3();//旋转相关private _rotMeasure = 0;//旋转相关度量值private _isMainCar: boolean = false;//是否玩家小车private _isInOrder = false;//是否在订单内public start(): void {(EventName.FINISHEDWALK, this.finishedWalk, this);}public update(dt: number) {if (!this._isMoving || this._isInOrder) return;this._offset.de.worldPosition);this._currSpeed += this._acceleration * dt;if (this._currSpeed > this.maxSpeed) {this._currSpeed = this.maxSpeed;//限速}if (this._currSpeed <= 0.001) {this._isMoving = false;//刹车到已经停止}//朝向哪就往哪运动switch (this._veType) {case RoadPoint.RoadMoveType.BEND:const offsetRotation = this._targetRotation - this._originRotation;const currRotation = this._de.eulerAngles.y)let nextStation = (currRotation - this._originRotation) + (this._currSpeed * this._rotMeasure * (this._targetRotation > this._originRotation ? 1 : -1));if (Math.abs(nextStation) > Math.abs(offsetRotation)) {nextStation = offsetRotation;}const target = nextStation + this._originRotation;_tempVec.set(0, target, 0);de.eulerAngles = _tempVec;//绕轴旋转的ateY(this._offset, this._pointA, this._centerPoint, nextStation * Math.PI / 180);break;default:const z = this._pointB.z - this._pointA.z;if (z !== 0) {if (z > 0) {this._offset.z += this._currSpeed;//容错if (this._offset.z > this._pointB.z) {this._offset.z = this._pointB.z;}} else {this._offset.z -= this._currSpeed;//容错if (this._offset.z < this._pointB.z) {this._offset.z = this._pointB.z;}}} else {const x = this._pointB.x - this._pointA.x;if (x > 0) {this._offset.x += this._currSpeed;//容错if (this._offset.x > this._pointB.x) {this._offset.x = this._pointB.x;}} else {this._offset.x -= this._currSpeed;//容错if (this._offset.x < this._pointB.x) {this._offset.x = this._pointB.x;}}}break;}de.setWorldPosition(this._offset);Vec3.subtract(_tempVec, this._pointB, this._offset);if (_tempVec.length() <= 0.01) {this._arrivalStation();}}/**根据这个点设置自己的位置 */public setEntry(entry: Node, isMain = false) {de.setWorldPosition(entry.worldPosition);this._currRoadPoint = Component(RoadPoint);this._isMainCar = isMain;if (!this._currRoadPoint) {console.warn("There is no RoadPoint in ", entry.name);return;}this._pointA.set(entry.worldPosition);this._pointB.set(this._Station.worldPosition);//计算朝向const z = this._pointB.z - this._pointA.z;if (z !== 0) {//排除干扰因素if (z < 0) {de.eulerAngles = new Vec3();} else {//翻180度de.eulerAngles = new Vec3(0, 180, 0);}} else {const x = this._pointB.x - this._pointA.x;if (x > 0) {de.eulerAngles = new Vec3(0, 270, 0);} else {de.eulerAngles = new Vec3(0, 90, 0);}}}/**为什么在这里使用世界坐标,因为setPosition只设置相对父节点的位置,而使用世界坐标则是绝对的位置。 */public startRunning() {if (this._currRoadPoint) {//游戏已经初始化this._isMoving = true;this._currSpeed = 0;this._acceleration = 0.2;}}public stopRunning() {this._acceleration = -0.3;// this._isMoving = false;}private _arrivalStation() {console.log(");this._pointA.set(this._pointB);this._currRoadPoint = this._Component(RoadPoint);if (this._Station) {this._pointB.set(this._Station.worldPosition)//假如是玩家小车并且是接客点或者送客点if (this._isMainCar) {if (this._pe === RoadPoint.RoadPointType.GREETING) {this._greetingCustomer();}else if (this._pe === RoadPoint.RoadPointType.GOODBYE) {this._takingCustomer();}}if (this._veType === RoadPoint.RoadMoveType.BEND) {if (this._currRoadPoint.clockwise) {//判断顺时针或逆时针this._originRotation = this._de.eulerAngles.y);this._targetRotation = this._originRotation - 90;//求顺时针centerPointif ((this._pointB.z < this._pointA.z && this._pointB.x > this._pointA.x) || (this._pointB.z > this._pointA.z && this._pointB.x < this._pointA.x)) {this._centerPoint.set(this._pointB.x, 0, this._pointA.z);} else {this._centerPoint.set(this._pointA.x, 0, this._pointB.z);}} else {this._originRotation = this._de.eulerAngles.y);this._targetRotation = this._originRotation + 90;//求逆时针centerPointif ((this._pointB.z > this._pointA.z && this._pointB.x > this._pointA.x) || (this._pointB.z < this._pointA.z && this._pointB.x < this._pointA.x)) {this._centerPoint.set(this._pointB.x, 0, this._pointA.z);} else {this._centerPoint.set(this._pointA.x, 0, this._pointB.z);}}Vec3.subtract(_tempVec, this._pointA, this._centerPoint);const r = _tempVec.length();this._rotMeasure = 90 / (Math.PI * r / 2);}} else {this._isMoving = false;this._currRoadPoint = null;}}/**接客 */private _greetingCustomer() {this._isInOrder = true;CustomEventListener.dispatchEvent(EventName.GREETING, de.worldPosition, this._currRoadPoint.direction);}/**送客 */private _takingCustomer() {this._isInOrder = true;CustomEventListener.dispatchEvent(EventName.GOODBYE, de.worldPosition, this._currRoadPoint.direction);}private finishedWalk() {this._isInOrder = false;}/**将所有负数角度都转化为正数角度 */private _conversion(value: number) {let a = value;if (a <= 0) {a += 360;}return a;}
}
import { _decorator, Component, Node } from 'cc';
import { Car } from './Car';
const { ccclass, property } = _decorator;@ccclass('CarManager')
export class CarManager extends Component {@property({type: Car})mainCar: Car = null;public resetCars(points: Node[]) {//没有点,抛出警告if (points.length <= 0) {console.warn("There is no points in this map");return;}//有点this._createMainCar(points[0]);}private _createMainCar(point: Node) {this.mainCar.setEntry(point, true);}public controlMoving(isRunning = true) {if (isRunning) {this.mainCar.startRunning();} else {this.mainCar.stopRunning();}}
}
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;enum EventName {GREETING = 'greeting',GOODBYE = 'goodbye',FINISHEDWALK = 'finishedd-walk'
}enum CustomerState {NONE,GREETING,GOODBYE
}@ccclass('Constants')
export class Constants {public static EventName = EventName;public static CustomerState = CustomerState;
}
import { _decorator, Component, Node, Vec2, Vec3, AnimationComponent } from 'cc';
import { Constants } from '../data/Constants';
import { CustomEventListener } from '../data/CustomEventListener';
const { ccclass, property } = _decorator;const EventName = Constants.EventName;
const _tempVec = new Vec3();@ccclass('CustomerManager')
export class CustomerManager extends Component {@property({type: [Node]})customers: Node[] = [];@propertywalkTime = 2;//运动时间private _currCustomer: Node = null;private _startPos = new Vec3();private _endPos = new Vec3();private _inTheOrder: boolean = false;//处于订单状态private _deltaTime = 0;private _state = Constants.CustomerState.NONE;public start(): void {(EventName.GREETING, this._greetingCustomer, this);(EventName.GOODBYE, this._takingCustomer, this);}public update(dt: number): void {//在订单中if (this._inTheOrder) {this._deltaTime += dt;if (this._deltaTime > this.walkTime) {//乘客已经走到this._deltaTime = 0;this._inTheOrder = false;this._currCustomer.active = false;if (this._state === Constants.CustomerState.GOODBYE) {this._currCustomer = null;}//给小车派发事件,你可以继续运动了CustomEventListener.dispatchEvent(EventName.FINISHEDWALK);} else {//逐元素向量线性插值Vec3.lerp(_tempVec, this._startPos, this._endPos, this._deltaTime / this.walkTime);this._currCustomer.setWorldPosition(_tempVec);}}}private _greetingCustomer(...args: any[]) {this._currCustomer = this.customers[Math.floor(Math.random() * this.customers.length)];this._state = Constants.CustomerState.GREETING;this._inTheOrder = true;if (!this._currCustomer) return;//没有顾客,不接const carPos = args[0];//小车的位置const direction = args[1];//方向Vec3.multiplyScalar(this._startPos, direction, 1.4);this._startPos.add(carPos);Vec3.multiplyScalar(this._endPos, direction, 0.5);this._endPos.add(carPos);this._currCustomer.setWorldPosition(this._startPos);this._currCustomer.active = true;if (direction.x !== 0) {//人处于我们的右边if (direction.x > 0) {this._currCustomer.eulerAngles = new Vec3(0, -90, 0);} else {this._currCustomer.eulerAngles = new Vec3(0, 90, 0);}} else {if (direction.z > 0) {this._currCustomer.eulerAngles = new Vec3(0, 180, 0);} else {this._currCustomer.eulerAngles = new Vec3();}}const animComp = this._Component(AnimationComponent);animComp.play('walk');}private _takingCustomer(...args: any[]) {this._state = Constants.CustomerState.GOODBYE;this._inTheOrder = true;const carPos = args[0];//小车的位置const direction = args[1];//方向Vec3.multiplyScalar(this._startPos, direction, 0.5);this._startPos.add(carPos);Vec3.multiplyScalar(this._endPos, direction, 1.4);this._endPos.add(carPos);this._currCustomer.setWorldPosition(this._startPos);this._currCustomer.active = true;if (direction.x !== 0) {//人处于我们的右边if (direction.x > 0) {this._currCustomer.eulerAngles = new Vec3(0, 90, 0);} else {this._currCustomer.eulerAngles = new Vec3(0, -90, 0);}} else {if (direction.z > 0) {this._currCustomer.eulerAngles = new Vec3();} else {this._currCustomer.eulerAngles = new Vec3(0, 180, 0);}}const animComp = this._Component(AnimationComponent);animComp.play('walk');}
}
本文发布于:2024-01-31 19:34:01,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170670084330855.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |