<今回やること!>
- こちらの記事でJavaScirptで作成したスクリプトをTypeScriptで書きます。
Cocos Creatorは、スクリプトをJavaScriptとTypeScriptで書くことが出来ます。
TypeScriptはJavaScriptから派生したもので、その名が示すように、型(Type)の定義が厳密になっています。
TypeScriptを使うメリットは、型定義が厳密であるがために、オートコンプリート(コードの自動補完)が確実に使えることです。JavaScriptの場合は値やオブジェクトの代入時に型が決まるので、オートコンプリートが機能しない場合があります。
そして型が厳密なことで代入ミスなどのケアレスミスが防げます。
TypeScriptに詳しい方からはメリットはまだまだあると言われそうですが、私がTypeScriptを使う1番のメリットは上記の点によりコーディングの効率が上がることです。また、とりあえずJavaScriptからTypeScriptへ書き換える場合には、それほど変更点がないのも良い点です。
以前、Cocos CreatorではCoffeeScriptが使用できましたが、現在は無くなっています。
ですがTypeScriptは、Microsoftが開発した言語で、エディタのVSCodeもMicrosoft製です。Googleの社内標準言語に採用されたとの記事がありましたし、人気のプログラミング言語でも上位に挙げられるので、将来性はありそうです。
TypeScriptのコードは、ファイルごとにまとめて記載します。
チュートリアルをやってない方は、チュートリアルの記事を御覧ください。
目次
使用環境
私が使用している環境です。
- Mac OS X El Capitan Version 10.11.6
- Cocos Creator Version 2.1.1
- ブラウザ:Google Chrome Version 73.0.3683.103 (64-bit)
TypeScriptプロジェクトの構成ファイルを追加
JavaScriptからTypeSciptへの変更点
大きく異なる部分について説明します。
プロパティの宣言
TypeScriptの場合、『Cocos Creator』のエディタの『Properties』パネルに表示する変数には、デコレータ『@property(型)』を付けます。
数字、ブーリアンの変数の宣言のようにデコレータと変数で、指定する型が異なる特殊なものもあります。
『cc.AudioClip』のデコレータは『@property({type: cc.AudioClip})』のようにラベルを書かないとエラーが出ました。
@property(cc.Prefab) // 『Properties』パネルへ表示する、cc.Prefab型 starPrefab: cc.Prefab = null; // cc.Prefab型で宣言し、デフォルト値はnull @property(cc.Integer) // 『Properties』パネルへ表示する、cc.Integer型 maxStarDuration: number = 0; // デフォルト値は0、number型で宣言 @property(cc.Boolean) // 『Properties』パネルへ表示する、cc.Boolean型 flag : boolean = true; // デフォルト値は0、boolean型で宣言 @property({type: cc.AudioClip}) // 『Properties』パネルへ表示する、cc.AudioClip型 scoreAudio: cc.AudioClip = null; // デフォルト値はnull、cc.AudioClip型で宣言 // デコレータ『@property(型)』がないので『Properties』パネルへ表示しない minStarDuration: number = 0; // 星が消えるまでの時間の最小値
その他のプロパティ宣言の例については下記のページが参考になります。
クラスのインポート
他のスクリプトファイルのクラスを呼び出す場合は、あらかじめインポートします。
import Game from "./Game"; // 同じフォルダのGame.tsからGameクラスをインポート
変数の宣言(var、let)
TypeScriptでは変数をletで宣言しています。
しかし最近はJavasScriptでもletを使うほうが良いです。
varとletの違いはスコープ(有効範囲)です。varは関数スコープ、letはブロックスコープです。
- スコープの違いの例
(function(){ var x = 1; // ① (function(){ var x = 2; // 別の関数内なのでこのxは①と別物 })(); cc.log("x = " + x); // x = 1と出力される })(); (function(){ var x = 1; // ② { var x = 2; // 同じ関数内なのでこのxは②と同じ } cc.log("x = " + x); // x = 2と出力される })(); (function(){ let x = 1; // ③ { let x = 2; // 同じブロック内なのでこのxは③と同じ } cc.log("x = " + x); // x = 1と出力される })();
TypeScript版のチュートリアルのスクリプト
チュートリアルでは、『Game.js』、『Star.js』、『Player.js』の3つのスクリプトファイルを作成しました。
それぞれをTypeScriptで作成して、『〜.ts』ファイルにしました。
『Game.ts』
const {ccclass, property} = cc._decorator; @ccclass export default class Game extends cc.Component { /* // オプションの設定例 @property({ type: cc.Node, tooltip: "『Player』ノード" }) player: cc.Node = null; */ @property(cc.Node) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 player: cc.Node = null; // 『Player』ノード取得用の変数 timer: number = 0; // タイマーを初期化 starDuration: number = 0; // 星の持続時間(星が消えるまでの時間)を初期化 @property(cc.Prefab) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 starPrefab: cc.Prefab = null; // 星のプレハブ取得用の変数 @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 maxStarDuration: number = 0; // 星が消えるまでの時間の最大値 @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 minStarDuration: number = 0; // 星が消えるまでの時間の最小値 @property(cc.Node) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 ground: cc.Node = null; // 『ground』ノード取得用の変数 @property(cc.Label) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 scoreDisplay: cc.Label = null; // 『score』ラベル取得用の変数 @property({type: cc.AudioClip})// 『Cocos Creatorエディタ』の『Properties』パネルへ表示 scoreAudio: cc.AudioClip = null; // 得点の効果音 @property(cc.Boolean)// 『Cocos Creatorエディタ』の『Properties』パネルへ表示 test: boolean = null; // 得点の効果音 groundY: number = 0; // 『ground』のアンカーポイントのy座標を取得 score: number = 0; // 得点を初期化 onLoad() { // 『ground』のアンカーポイントのy座標 this.groundY = this.ground.y + this.ground.height / 2; // 新しい星を生成 this.spawnNewStar(); } spawnNewStar() { // 事前に設定したテンプレートでシーンに新しいノードを生成 let newStar = cc.instantiate(this.starPrefab); // 新しく追加したノードを『Canvas』ノードの下に置く this.node.addChild(newStar); // 星にランダムな位置を設定 newStar.setPosition(this.getNewStarPosition()); // 生成した『Star』ノードの『Star』コンポーネントに、『Game』コンポーネントの実体を渡す。 newStar.getComponent('Star').init(this); // タイマーをリセットし、星の持続時間に乱数値(min〜max)を設定 this.starDuration = this.minStarDuration + Math.random() * (this.maxStarDuration - this.minStarDuration); // タイマーをリセット this.timer = 0; } getNewStarPosition() { // 星のアンカーポイントのy座標 = 地面の高さ+キャラクターのジャンプの高さの範囲の乱数 + 地面にめり込ませないための調整値 let randY = this.groundY + Math.random() * this.player.getComponent('Player').jumpHeight + 50; // x座標の最大値(絶対値)= 画面幅の半分 let maxX = this.node.width / 2; // 星のアンカーポイントのx座標=(-1から1の乱数)×(x座標の最大値) let randX = (Math.random() - 0.5) * 2 * maxX; // 星のアンカーポイントの位置を返す return cc.v2(randX, randY); } gainScore() { this.score += 1; // スコアを加算 // 『scoreDisplay』ラベルのスコア表示を更新 this.scoreDisplay.string = 'Score: ' + this.score.toString(); // 得点時の効果音を再生 cc.audioEngine.playEffect(this.scoreAudio, false); } gameOver() { this.player.stopAllActions(); // 『Player』ノードのジャンプアクションを停止 cc.director.loadScene('game'); // 『game』シーンをロードする } update(dt) { if (this.timer > this.starDuration) { // タイマーが星の持続時間を超えた場合 this.gameOver(); // ゲーム失敗のロジックを呼び出す return; } this.timer += dt; // タイマーを加算 } }
『Star.ts』
import Game from "./Game"; // 同じフォルダのGame.tsからGameクラスをインポート const {ccclass, property} = cc._decorator; @ccclass export default class Star extends cc.Component { @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 pickRadius: number = 0 ;// キャラクターと星の距離がこの値より小さい場合に得点とする game: Game = null; init(game:Game) { this.game = game; } getPlayerDistance () { // 『Player』ノードの位置を取得 let playerPos = this.game.player.getPosition(); // this.node(星ノード)と『Player』ノードの距離を計算 let dist = this.node.position.sub(playerPos).mag(); return dist; // 計算した距離を返す } onPicked () { // 『Game』コンポーネントの『spawnNewStar』メソッドを呼び出して、新しく星を生成 this.game.spawnNewStar(); // 『Game』スクリプトの得点用のメソッドを呼び出す this.game.gainScore(); // 『star』ノードを破棄 this.node.destroy(); } update (dt) { if (this.getPlayerDistance() < this.pickRadius) { // キャラクターと星の間の距離がpickRadiusより小さい場合 this.onPicked(); // onPickedメソッド(採取の処理)を呼び出す return; } // 経過時間と星の持続時間から、透明度の割合を計算 let opacityRatio = 1 - this.game.timer / this.game.starDuration; // 透明度の最小値 let minOpacity = 50; // 星のノードの透明度を設定 this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity)); } }
『Player.ts』
const {ccclass, property} = cc._decorator; @ccclass export default class Player extends cc.Component { @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 jumpHeight: number = 0; // キャラクターのジャンプの高さ @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 jumpDuration: number = 0; // キャラクターのジャンプの時間 @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 maxMoveSpeed: number = 0; // 最大移動速度 @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 accel: number = 0; // 加速度 @property({type: cc.AudioClip}) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示 jumpAudio: cc.AudioClip = null; // ジャンプの効果音のリソース accLeft: boolean = false; // 左の加速フラグをfalseにする accRight: boolean = false;; // 右の加速フラグをfalseにする xSpeed: number = 0; // キャラクタの現在の水平速度をにする setJumpAction () { // ジャンプアップ(ジャンプの最高地点まで移動)するアクションを定義 let jumpUp = cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight)).easing(cc.easeCubicActionOut()); // ジャンプダウン(ジャンプで地上まで落下)するアクションを定義 let jumpDown = cc.moveBy(this.jumpDuration, cc.v2(0, -this.jumpHeight)).easing(cc.easeCubicActionIn()); // アクションが終わった後に呼び出されるコールバック関数を追加 let callback = cc.callFunc(this.playJumpSound, this); // リピート(ジャンプアップ→ジャンプダウン→着地音再生→ジャンプアップ→・・・の繰り返し)するアクションを返す return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback)); } playJumpSound () { // 着地音を再生 cc.audioEngine.playEffect(this.jumpAudio, false); } onKeyDown (event) { // キーを押した時の処理 switch(event.keyCode) { case cc.macro.KEY.a: // 『a』キーの場合 this.accLeft = true; // 『右に加速フラグ』をtrueにする break; case cc.macro.KEY.d: // 『d』キーの場合 this.accRight = true;// 『左に加速フラグ』をtrueにする break; } } onKeyUp (event) { //キーを放した時の処理 switch(event.keyCode) { case cc.macro.KEY.a: // 『a』キーの場合 this.accLeft = false; // 『右に加速フラグ』をfalseにする break; case cc.macro.KEY.d: // 『d』キーの場合 this.accRight = false; // 『左に加速フラグ』をfalseにする break; } } onLoad () { // ジャンプアクションを作成 let jumpAction = this.setJumpAction(); // ジャンプアクションを開始 this.node.runAction(jumpAction); // キーダウンイベントのリスニング(受付)を開始 cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); // キーアップイベントのリスニング(受付)を開始 cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this); } onDestroy () { // ノードが破棄される時の処理 // キーダウンイベントのリスニング(受付)を終了 cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); // キーアップイベントのリスニング(受付)を終了 cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this); } update (dt) { if (this.accLeft) { // 左の加速フラグがtrueの場合 this.xSpeed -= this.accel * dt; // 現在の水平速度を更新 } else if (this.accRight) { // 右の加速フラグがtrueの場合 this.xSpeed += this.accel * dt; // 現在の水平速度を更新 } // 現在の水平速度が最大速度を超える場合 if ( Math.abs(this.xSpeed) > this.maxMoveSpeed ) { // 方向はそのままで、最大速度にする this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed); } this.node.x += this.xSpeed * dt; // 現在のキャラクタの水平位置を更新 } }
あとがき
private・public・protected等の宣言は、マニュアルやtsファイルのテンプレートで省略されていたので、同様に省略しました。
これからはTypeScriptで作ろうと思います。ブログ的には見る人を狭める気がしますが、JavaScript分かる人はTypeScriptすぐに分かるのではないかと思います。