本篇文章小编给大家分享一下CocosCreator入门教程之用TS制作第一个游戏代码示例,文章代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看。
TypeScript VS JavaScript
这里当然只会讲优点:
1. ts 是 js 的超集,所有 js 的语法 ts 都支持。
2. ts 支持接近完美的代码提示,js 代码提示接近于没有。
3. ts 有类型定义,编译时就可以排除很多无意义的错误。
4. ts 可以重构,适合大型项目。
5. ts 可以使用 es6 async之类的所有新语法。而 js Cocos Creator 还没有完全支持es6。
6. 最重要的一点:我以后的教程都会用 ts 写,如果你不用 ts,你就会永远失去我了。
代码编辑器选择
这里只推荐两个:
Visual Studio Code
WebStorm
vs code 的优点是快,与cocos creator 结合的好,一些功能需要自己安装插件。
webstorm 的优点是所有你想要的功能都先天内置了,缺点是占内存,个人感觉还有点丑。
对于我自己来说,我在公司用 WebStorm,在家用 VS Code。
如果你还是不知道用哪个,我只能先推荐你用VS Code 因为下面的内容是面向VS Code。
学习 TypeScript
既然要用ts开发游戏,肯定要知道ts的语法,我这一篇文章不可能把所有ts的语法都讲完,所以https://www.tslang.cn/docs/home.html,当然,不一定要一次性全看完,你可以先看个大概,遇到问题再补习。
TypeScript 环境配置
任意打开一个项目,把这几个都点一遍
控制台会输出
打开编辑器,你会发现一个名字为 creator.d.ts 的脚本
creator 的提示都依靠这个脚本,引擎的api变动也要及时更新这个脚本,所以每次更新引擎的时候都要重新点一次上面那个“更新VS Code只能提示数据“来重新生成creator.d.ts。
资源管理器右键新建一个ts脚本,点开后你会发现有很多没用的东西,而且还会有一个提示错误(1.81)。。。
// - [English] http://www.cocos2d-x.org/docs/editors_and_tools/creator-chapters/scripting/typescript/index.html // Learn Attribute: // - [Chinese] http://www.cocos.com/docs/creator/scripting/reference/attributes.html // - [English] http://www.cocos2d-x.org/docs/editors_and_tools/creator-chapters/scripting/reference/attributes/index.html // Learn life-cycle callbacks: // - [Chinese] http://www.cocos.com/docs/creator/scripting/life-cycle-callbacks.html // - [English] http://www.cocos2d-x.org/docs/editors_and_tools/creator-chapters/scripting/life-cycle-callbacks/index.html const {ccclass, property} = cc._decorator; @ccclass export default class NewClass extends cc.Component { @property(cc.Label) label: cc.Label = null; @property text: string = 'hello'; // LIFE-CYCLE CALLBACKS: // onLoad () {}, start () { }, // update (dt) {}, }
编辑器右上角“打开程序安装路径“,
static-》template-》new-script.ts
这个脚本就是新建ts脚本的默认样式,我们来重新编辑一下,编辑后的脚本如下
const {ccclass, property} = cc._decorator; @ccclass export class NewClass extends cc.Component { }
重新新建一个ts脚本,你会发现跟刚才编辑的默认脚本是一个样子了。
配置自己的声明文件
以d.ts为后缀名的文件,会被识别为声明文件,creator.d.ts是引擎的声明文件,我们也可以定义自己的声明文件,需要注意的是声明文件要放在assets文件外,因为assets文件里的脚本都会被引擎编译,而声明文件的作用就是写代码时提示一下,编译之后就不需要了。
举个栗子
在项目的根目录添加一个global.d.ts文件
然后在项目里的脚本就可以得到对应的提示
更多类型定义戳 https://www.tslang.cn/docs/handbook/declaration-files/introduction.html
属性类型声明
const LEVEL = cc.Enum({EASY:1,HARD:2}); @ccclass export class Game extends cc.Component { // 整型 @property(cc.Integer) intVar: number = 0; // 浮点型 @property(cc.Float) floatVar: number = 0; // 布尔型 @property(cc.Boolean) boolVar: boolean = false; // 节点 @property(cc.Node) nodeVar: cc.Node = null; // 节点数组 @property([cc.Node]) nodeArrVar: Array= []; // Label @property(cc.Label) labelVar: cc.Label = null; // 预制体 @property(cc.Prefab) prefabVar: cc.Prefab = null; // 点 @property(cc.Vec2) vec2Var: cc.Vec2 = cc.v2(); // 自定义节点 @property(Player) palyerVar: Player = null; // 重点来了,自定义枚举 /** * 全局变量 * const LEVEL = cc.Enum({EASY:1,HARD:2}); */ @property({ type:LEVEL }) enumVa = LEVEL.EASY; }
用 TypeScript 写一个游戏
最后我们来切身体会一下TypeScript的柔软丝滑。
挑一个熟悉的游戏来写,官方文档里有一个摘星星的游戏,我们用Ts重新写一下。
第一步:新建一个工程
第二步:写几个脚本
Game.ts
import { Player } from "./Player"; const { property, ccclass } = cc._decorator; @ccclass export class Game extends cc.Component { // 这个属性引用了星星的预制资源 @property(cc.Prefab) private starPrefab: cc.Prefab = null; // 星星产生后消失时间的随机范围 @property(cc.Integer) private maxStarDuration = 0; @property(cc.Integer) private minStarDuration = 0 // 地面节点,用于确定星星生成的高度 @property(cc.Node) private groundNode: cc.Node = null; // player 节点,用于获取主角弹跳的高度,和控制主角行动开关 @property(cc.Node) public playerNode: cc.Node = null; // score label 的引用 @property(cc.Label) private scoreLabel: cc.Label = null; // 得分音效资源 @property(cc.AudioClip) private scoreAudio: cc.AudioClip = null; // 地面节点的Y轴坐标 private groundY: number; // 定时器 public timer: number; // 星星存在的持续时间 public starDuration: number; // 当前分数 private score: number; protected onLoad() { // 获取地平面的 y 轴坐标 this.groundY = this.groundNode.y + this.groundNode.height / 2; // 初始化计时器 this.timer = 0; this.starDuration = 0; // 生成一个新的星星 this.spawnNewStar(); // 初始化计分 this.score = 0; } // 生成一个新的星星 public spawnNewStar() { // 使用给定的模板在场景中生成一个新节点 let newStar = cc.instantiate(this.starPrefab); // 将新增的节点添加到 Canvas 节点下面 this.node.addChild(newStar); // 为星星设置一个随机位置 newStar.setPosition(this.getNewStarPosition()); // 将 Game 组件的实例传入星星组件 newStar.getComponent('Star').init(this); // 重置计时器 this.starDuration = this.minStarDuration + cc.random0To1() * (this.maxStarDuration - this.minStarDuration); this.timer = 0; } // 新星星的位置 public getNewStarPosition() { let randX = 0; // 根据地平面位置和主角跳跃高度,随机得到一个星星的 y 坐标 let randY = this.groundY + cc.random0To1() * this.playerNode.getComponent('Player').jumpHeight + 50; // 根据屏幕宽度,随机得到一个星星 x 坐标 let maxX = this.node.width / 2; randX = cc.randomMinus1To1() * maxX; // 返回星星坐标 return cc.p(randX, randY); } // called every frame protected update(dt: number) { // 每帧更新计时器,超过限度还没有生成新的星星 // 就会调用游戏失败逻辑 if (this.timer > this.starDuration) { this.gameOver(); return; } this.timer += dt; } // 得分 public gainScore() { this.score += 1; // 更新 scoreDisplay Label 的文字 this.scoreLabel.string = 'Score: ' + this.score.toString(); // 播放得分音效 // 不加as any就会报错,不信你试试 cc.audioEngine.play(this.scoreAudio as any, false, 1); } // gg private gameOver() { this.playerNode.stopAllActions(); //停止 player 节点的跳跃动作 cc.director.loadScene('game'); } }
Player.ts
const { ccclass, property } = cc._decorator; @ccclass export class Player extends cc.Component { // 主角跳跃高度 @property(cc.Integer) private jumpHeight: number = 0; // 主角跳跃持续时间 @property(cc.Integer) private jumpDuration: number = 0; // 最大移动速度 @property(cc.Integer) private maxMoveSpeed: number = 0; // 加速度 @property(cc.Integer) private accel: number = 0; // 跳跃音效资源 @property(cc.AudioClip) private jumpAudio: cc.AudioClip = null; private xSpeed: number = 0; private accLeft: boolean = false; private accRight: boolean = false; private jumpAction: cc.Action = null; private setJumpAction() { // 跳跃上升 let jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut()); // 下落 let jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn()); // 添加一个回调函数,用于在动作结束时调用我们定义的其他方法 let callback = cc.callFunc(this.playJumpSound, this); // 不断重复,而且每次完成落地动作后调用回调来播放声音 return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback)); } private playJumpSound() { // 调用声音引擎播放声音 cc.audioEngine.play(this.jumpAudio as any, false, 1); } private addEventListeners() { cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this); cc.find("Canvas").on(cc.Node.EventType.TOUCH_START, this.onScreenTouchStart,this); cc.find("Canvas").on(cc.Node.EventType.TOUCH_CANCEL, this.onScreenTouchEnd, this); cc.find("Canvas").on(cc.Node.EventType.TOUCH_END, this.onScreenTouchEnd,this); } private moveLeft() { this.accLeft = true; this.accRight = false; } private moveRight() { this.accLeft = false; this.accRight = true; } private stopMove() { this.accLeft = false; this.accRight = false; } private onScreenTouchStart(event: cc.Event.EventTouch) { if (event.getLocationX() > cc.winSize.width/2) { this.moveRight(); } else { this.moveLeft(); } } private onScreenTouchEnd() { this.stopMove(); } private onKeyDown(event: cc.Event.EventKeyboard) { switch ((event as any).keyCode) { case cc.KEY.a: case cc.KEY.left: this.moveLeft(); break; case cc.KEY.d: case cc.KEY.right: this.moveRight(); break; } } private onKeyUp(event: cc.Event.EventKeyboard) { switch ((event as any).keyCode) { case cc.KEY.a: case cc.KEY.left: this.stopMove(); break; case cc.KEY.d: case cc.KEY.right: this.stopMove(); break; } } // use this for initialization protected onLoad() { // 初始化跳跃动作 this.jumpAction = this.setJumpAction(); this.node.runAction(this.jumpAction); // 加速度方向开关 this.accLeft = false; this.accRight = false; // 主角当前水平方向速度 this.xSpeed = 0; // 初始化输入监听 this.addEventListeners(); } // called every frame protected update(dt: number) { // 根据当前加速度方向每帧更新速度 if (this.accLeft) { this.xSpeed -= this.accel * dt; } else if (this.accRight) { this.xSpeed += this.accel * dt; } // 限制主角的速度不能超过最大值 if (Math.abs(this.xSpeed) > this.maxMoveSpeed) { // if speed reach limit, use max speed with current direction this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed); } // 根据当前速度更新主角的位置 this.node.x += this.xSpeed * dt; if (this.node.x <= -this.node.parent.width / 2) { this.node.x = this.node.parent.width / 2; } if (this.node.x > this.node.parent.width / 2) { this.node.x = -this.node.parent.width / 2; } } }
Star.ts
import { Game } from "./Game"; const {ccclass,property} = cc._decorator; @ccclass export class Star extends cc.Component { // 星星和主角之间的距离小雨这个数值时,就会完成收集 @property(cc.Integer) private pickRadius: number = 0; private game: Game = null; public init(game:Game) { this.game = game; } getPlayerDistance() { // 根据 player 节点位置判断距离 let playerPos = this.game.playerNode.getPosition(); // 根据两点位置计算两点之间距离 let dist = cc.pDistance(this.node.position, playerPos); return dist; } onPicked() { // 当星星被收集时,调用 Game 脚本中的接口,生成一个新的星星 this.game.spawnNewStar(); // 调用 Game 脚本的得分方法 this.game.gainScore(); // 然后销毁当前星星节点 this.node.destroy(); } // called every frame update(dt:number) { // 每帧判断和主角之间的距离是否小于收集距离 if (this.getPlayerDistance() < this.pickRadius) { // 调用收集行为 this.onPicked(); return; } // 根据 Game 脚本中的计时器更新星星的透明度 let opacityRatio = 1 - this.game.timer/this.game.starDuration; let minOpacity = 50; this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity)); } }