1. 程式人生 > >Cocos Creator + TypeScript 入門教程

Cocos Creator + TypeScript 入門教程

這不是 Cocos Creator 的入門教程,也不是TypeScript 的入門教程,這是 Cocos Creator+TypeScript 的入門教程。

前提

這裡假設你已經安裝成功了 Cocos Creator

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,你就會永遠失去我了

程式碼編輯器選擇

這裡只推薦兩個:

  1. Visual Studio Code
  2. 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檔案
這裡寫圖片描述
然後在專案裡的腳本里就可以得到對應的提示
這裡寫圖片描述

這裡寫圖片描述

屬性型別宣告

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));
    }

}

這後面的進度有點快,同學們自己消化一下,老師去吃飯了。