這不是 Cocos Creator 的入門教程,也不是TypeScript 的入門教程,這是 Cocos Creator+TypeScript 的入門教程。
前提
無論學什么技術知識,官方文檔都應該是你第一個教程,所以請先至少閱讀新手上路這一節 http://docs.cocos.com/creator/manual/zh/getting-started/ 再來看這篇文章。
這里假設你已經安裝成功了 Cocos Creator。
TypeScript VS JavaScript
這里當然只會講優點:
- ts 是 js 的超集,所有 js 的語法 ts 都支持。
- ts 支持接近完美的代碼提示,js 代碼提示接近於沒有。
- ts 有類型定義,編譯時就可以排除很多無意義的錯誤。
- ts 可以重構,適合大型項目。
- ts 可以使用 es6 async之類的所有新語法。而 js Cocos Creator 還沒有完全支持es6。
- 最重要的一點:我以后的教程都會用 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<cc.Node> = [];
// 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));
}
}
第三步:可以玩了【github pages發布教程傳送門】
https://potato47.github.io/star-catcher/
這后面的進度有點快,同學們自己消化一下,老師去吃飯了。
參考:http://forum.cocos.com/t/typescript-creator-github/42200
http://docs.cocos.com/creator/manual/zh/scripting/typescript.html
https://www.tslang.cn/docs/home.html
源碼:https://github.com/potato47/star-catcher
原文:http://blog.csdn.net/potato47/article/details/79254524
————————————————