1. はじめに最近、Cocos を学び始めました。Typescript 構文を学んだ後、Cocos の公式ドキュメントを見に行きました。数日間いじくった後、非常に単純なスネークを作成しましたが、衝突検出も適切に行われていませんでした。退屈だったので、しばらく放置していました。ここ数週間、私は再び Cocos を手に取り、テトリスを実装するというアイデアを思いつきました。最初はオンラインで情報を探そうと思ったのですが、Cocos 用の Tetris の開発に関する記事が非常に少ないことがわかりました (検索方法が間違っていた可能性もあります)。さらにイライラしたのは、私が見つけた共有記事の数が少なく、コード コメントがほとんどなかったことです。私の理解力が十分ではなかった可能性もあります。その後、数日費やしましたが、それでも完全に理解することはできませんでした。そこで自分で書いてみることにし、2週間後、ついに完成しました。 記事の最後に、参考までにcocosプロジェクトファイル全体を添付します。コードがうまく書かれていないので、アドバイスをお願いします。 2. 解決すべきいくつかの重要な問題1. ゲームエリアにブロックをどのように保存しますか? テトリスはピクセルゲームなので、各ブロックをピクセルと見なすことができ、ゲームエリア全体がピクセルの集合体になります。Cocos と組み合わせて、各ブロックを cc.Node 型として定義すると、ゲームエリアは cc.Node 型の 2 次元配列を使用してブロックを保存できるため、回転、移動、積み重ね、削除などの重要な操作に便利です。ここでは 20*10 の 2 次元配列を使用しています。 //ゲームエリア全体のグリッドは2次元配列ボックスに保存されます: cc.Node[][] = []; // ボックスの2次元配列を初期化します。この配列の[0][0]はゲームエリアの左下隅にあります。InitBox() { (i = 0; i < 20; i++ とします) { this.box[i] = []; (j = 0; j < 10; j++) の場合 { this.box[i][j] = null; } } // 異なるブロック セットを生成します this.buildBlock(); } 2. 各タイプのブロックセットの構築 ご存知のとおり、テトリスには逆Z型、L型、逆L型、Z型、ストライプ型、T型、正方形の7種類のブロックがあります。 各ブロック セットは 4 つの小さなブロックで構成されていることがわかります。この機能を使用して、統一された構築方法を構築できます。 後からの使用の利便性を考慮して、まずは小さなブロックごとにプレハブと空のノードのプレハブを定義しました。このプレハブによって生成されたノードは、後で構築されるブロックノードのインストールに使用されます。つまり、構造的には父と息子の関係なのです。 // 正方形のサブブロック @property(cc.Prefab) block_0: cc.Prefab = null; //Z字型のサブブロック @property(cc.Prefab) block_1: cc.Prefab = null; //左L字型サブブロック @property(cc.Prefab) block_2: cc.Prefab = null; //右L字型サブブロック @property(cc.Prefab) block_3: cc.Prefab = null; //アンチZサブブロック @property(cc.Prefab) block_4: cc.Prefab = null; //ロングストリップサブブロック @property(cc.Prefab) block_5: cc.Prefab = null; //T字型サブブロック @property(cc.Prefab) block_6: cc.Prefab = null; //ブロックコレクションの中心 @property(cc.Prefab) 現在のブロック中心 = null; //現在のブロックcurrentBlock: cc.Node = null; //currentBlockCentreの特定の実装currentBlockPart01: cc.Node = null; //4つのサブブロックの特定の実装currentBlockPart02: cc.Node = null; currentBlockPart03: cc.Node = null; currentBlockPart04: cc.Node = null; ブロックの色と種類のランダム生成に関しては、組み込みの Math.random() を選択しました。 ブロックを構築します(){ this.rand = Math.floor(7 * Math.random()); // 7 個の中からランダムに 1 つを選択して構築します。this.chooseColor(this.rand); this.chooseType(this.rand); } 次のステップは、入力 rand パラメータに基づいて、ビルディング ブロック セットの色とタイプを選択することです。具体的には、このブロック セットの中心点を選択する必要があります。サブブロックの中心を選択し、位置を (0, 0) に設定するのが最適です。この方法では、その後のローテーションを実装するのが非常に便利になります。中心点を選択すると、他の子ブロックの位置はこの中心点に従って設定されます。Cocos の子ノードの位置は親ノードを基準にしています。子ノードの位置が (0, 0) に設定されている場合、子ノードの位置は親ノードの中心点になります。 また、各サブブロックのプレハブサイズは 60×60 であり、ゲームエリア内の各グリッド間の間隔は 60 であることを意味します。 このコードのセクションはかなり長いので、詳細は説明しません。 //ブロックセットの色を選択する chooseColor(rand) { … //Z字型ブロックの色 if (rand == 1) { this.currentBlockPart01 = cc.instantiate(this.block_1); this.currentBlockPart02 = cc.instantiate(this.block_1); this.currentBlockPart03 = cc.instantiate(this.block_1); this.currentBlockPart04 = cc.instantiate(this.block_1); this.currentBlock = cc.instantiate(this.currentBlockCentre); this.node.addChild(this.currentBlock); this.currentBlock.setPosition(30, 510); // 次のドロップに備えて、現在生成されているブロックの位置をゲームエリアの上に設定します} // 左のL字型の正方形の色 if (rand == 2) … } //図形を選択 chooseType(rand) { … //ジグザグを作成する if (rand == 1) { //Z字型左 this.currentBlockPart01.setPosition(-60, 0); this.currentBlockPart01Pos = cc.v2(18, 4); //現在のブロックの位置を、currentBlockを基準にして初期化します。 //Z シェイプの場合 this.currentBlockPart02.setPosition(0, 0); this.currentBlockPart02Pos = cc.v2(18, 5); //Z字型 this.currentBlockPart03.setPosition(0, -60); this.currentBlockPart03Pos = cc.v2(17, 5); //Z字型の右 this.currentBlockPart04.setPosition(60, -60); this.currentBlockPart04Pos = cc.v2(17, 6); } // 左L型を作成する if (rand == 2) … } 3. 作成したブロックセットをノード2次元配列と組み合わせる方法 上記のコードには、currentBlockPart0XPos という変数が含まれており、ボックス ノードの 2 次元配列内の現在の操作可能なブロック セット currentBlock の各サブブロック currentBlockPart0X の特定の位置を定義します。これら 4 つの変数は非常に便利です。現在操作可能なブロックを移動した後、その位置情報をボックス ノードの 2 次元配列に保存できます。 //現在のサブブロックの位置 currentBlockPart01Pos: cc.Vec2 = null; currentBlockPart02Pos: cc.Vec2 = null; currentBlockPart03Pos: cc.Vec2 = null; currentBlockPart04Pos: cc.Vec2 = null; その後、操作可能なブロックのセットが変更されるたびに、次の 2 つのメソッドを呼び出して、ボックス配列内の操作可能なブロック セットの位置を更新できます。 //現在の操作ブロックの位置情報を読み取ります。set checkCurrentBlockPos() { this.box[this.currentBlockPart01Pos.x][this.currentBlockPart01Pos.y] = this.currentBlockPart01; this.box[this.currentBlockPart02Pos.x][this.currentBlockPart02Pos.y] = this.currentBlockPart02; this.box[this.currentBlockPart03Pos.x][this.currentBlockPart03Pos.y] = this.currentBlockPart03; this.box[this.currentBlockPart04Pos.x][this.currentBlockPart04Pos.y] = this.currentBlockPart04; } // 現在の操作ブロックセットの位置情報をクリアします。前の位置のdeleteCurrentBlockPos() { this.box[this.currentBlockPart01Pos.x][this.currentBlockPart01Pos.y] = null; this.box[this.currentBlockPart02Pos.x][this.currentBlockPart02Pos.y] = null; this.box[this.currentBlockPart03Pos.x][this.currentBlockPart03Pos.y] = null; this.box[this.currentBlockPart04Pos.x][this.currentBlockPart04Pos.y] = null; } 4. ブロックセットを移動および回転する 移動に関しては、ほとんどのテトリスゲームの操作方法に従います。左ボタンは左に移動し、右ボタンは右に移動し、上ボタンは回転し、下ボタンは下に移動し、自動落下があります。 //自動的にドロップする autoDown() { this.schedule(() => { // 下の境界に達するまで落下し続ける if (this.isClashBottom()) { this.deleteRow(); //行削除検出 this.buildBlock(); //新しいブロックセットを作成 } else if (this.isClashBlockDown()) { //他のブロックに当たるまで落下し続ける this.isGameOver(); //ゲームオーバーかどうかを判断 this.deleteRow(); このブロックを構築します。 } それ以外 { // 1ブロック下 this.currentBlock.y -= 60; this.deleteCurrentBlockPos(); this.currentBlockPart01Pos.x -= 1; this.currentBlockPart02Pos.x -= 1; this.currentBlockPart03Pos.x -= 1; this.currentBlockPart04Pos.x -= 1; this.checkCurrentBlockPos(); } }, 1); } //キーボード監視 onKeyDown(e) { スイッチ (e.keyCode) { ケースcc.macro.KEY.left: if (this.isClashLeft()) { // 左の境界の区切りに当たるかどうかを判断します。 } else if (this.isClashBlockLeft()) { //現在の操作ブロックが左ブレークで他のサブブロックと衝突するかどうかを判断します。 } それ以外 { this.currentBlock.x -= 60; this.deleteCurrentBlockPos(); this.currentBlockPart01Pos.y -= 1; this.currentBlockPart02Pos.y -= 1; this.currentBlockPart03Pos.y -= 1; this.currentBlockPart04Pos.y -= 1; this.checkCurrentBlockPos(); 壊す; } ケースcc.macro.KEY.right: … ケースcc.macro.KEY.up: //形状を変更するif (this.isClashLeft()) { //左の境界に当たるかどうかを判断します。 } else if (this.isClashRight()) { // 右の境界ブレークに当たるかどうかを判断します。 } else if (this.isClashBottom()) { // 下限のブレークに達するかどうかを判断します。 } else if (this.isClashBlockLeft()) { //現在の操作ブロックが左ブレークで他のサブブロックと衝突するかどうかを判断します。 } else if (this.isClashBlockRight()) { //現在の操作ブロックの右側が他のサブブロックと衝突するかどうかを判断します break; } else if (this.isClashBlockDown()) { //現在の操作ブロックがbreak以下の他のサブブロックと衝突するかどうかを判断します。 } それ以外 { this.deleteCurrentBlockPos(); this.changeShape(); //回転と形状の変更 this.checkCurrentBlockPos(); 壊す; } cc.macro.KEY.downの場合: … } } 回転部分に関しては、実はショートカットをしています。いくつかのサブブロックの位置を意図的に中心点として設定することで、回転操作が可能になっています。 図中の灰色の円で示されるサブブロックが私が設定した中心点です。中心点を2次元座標系の原点とすると、y軸の上半分、y軸の下半分、x軸の左半分、x軸の右半分、第1象限、第2象限、第3象限、第4象限の8つの領域に分割できます。 Z 回転を例にとると、4 つの座標軸上のサブブロックの x と y が変更されるのに対し、象限上のサブブロックでは x と y のいずれか 1 つだけが変更され、値は元の値の反対になることがわかります。このように回転を実装すると、実際にはサブブロックの位置のみが変更され、サブブロックの方向は変更されません。 //回転変更 shapechangeShape() { this.whichPartChange(this.currentBlockPart01、this.currentBlockPart01Pos); this.whichPartChange(this.currentBlockPart02、this.currentBlockPart02Pos); this.whichPartChange(this.currentBlockPart03、this.currentBlockPart03Pos); this.whichPartChange(this.currentBlockPart04、this.currentBlockPart04Pos); } // 判定するパーツを渡す whichPartChange(currentBlockPart: cc.Node, currentBlockPartPos: cc.Vec2) { // currentBlockPartPos の位置を左から上、上から右、右から下、下から左に回転するために使用される変更パラメータ。象限では必要ありません。let modParameterX = Math.abs(currentBlockPart.position.x / 60); modParameterY = Math.abs(currentBlockPart.position.y / 60)とします。 modParameterMax = Math.max(modParameterX, modParameterY); とします。 //y軸の上半分 if (currentBlockPart.position.x == 0 && currentBlockPart.position.y > 0) { //行-列+ 現在のブロックパーツ位置.x -= modParameterMax; 現在のブロックパーツ位置.y += modParameterMax; //現在のブロックの位置を回転します currentBlockPart.setPosition(currentBlockPart.position.y, currentBlockPart.position.x); } // x軸の左半分 else if (currentBlockPart.position.x < 0 && currentBlockPart.position.y == 0) { … } //y軸の下半分 else if (currentBlockPart.position.x == 0 && currentBlockPart.position.y < 0) { … } //x軸の右半分 else if (currentBlockPart.position.x > 0 && currentBlockPart.position.y == 0) { … } //最初の象限if (currentBlockPart.position.x > 0 && currentBlockPart.position.y > 0) { //わかりました- (currentBlockPart.position.x >= 60 && currentBlockPart.position.y >= 60) の場合 { 現在のブロックパーツ位置.x -= 2; } それ以外 { 現在のブロックパーツ位置.x -= 1; } //現在のブロックの位置を回転しますcurrentBlockPart.setPosition(currentBlockPart.position.x, -currentBlockPart.position.y); } //第2象限 else if (currentBlockPart.position.x < 0 && currentBlockPart.position.y > 0) { … } //第3象限 else if (currentBlockPart.position.x < 0 && currentBlockPart.position.y < 0) { … } //第4象限 else if (currentBlockPart.position.x > 0 && currentBlockPart.position.y < 0) { … } } 5. 境界とブロックの検出 境界検出には、左境界検出、右境界検出、下境界検出の 3 種類があります。ブロック検出には、現在操作可能なブロックセットの下の検出、左への検出、右への検出の 3 種類があります。 //左の境界と衝突するかどうかを判定する isClashLeft(): boolean { this.currentBlockPart01Pos.y - 1 < 0 || this.currentBlockPart02Pos.y - 1 < 0 || の場合 this.currentBlockPart03Pos.y - 1 < 0 || this.currentBlockPart04Pos.y - 1 < 0) { true を返します。 } false を返します。 } //右の境界と衝突するかどうかを判定する isClashRight(): boolean { … } // 下境界と衝突するかどうかを判定する isClashBottom(): boolean { … } //他のブロックと衝突するかどうかを判定する(下) isClashBlockDown(): ブール値 { //下方向のブロック衝突を検出します if (this.box[this.currentBlockPart01Pos.x - 1][this.currentBlockPart01Pos.y] != null && !this.isCurrentBlockChild(this.box[this.currentBlockPart01Pos.x - 1][this.currentBlockPart01Pos.y]) || this.box[this.currentBlockPart02Pos.x - 1][this.currentBlockPart02Pos.y] != null && !this.isCurrentBlockChild(this.box[this.currentBlockPart02Pos.x - 1][this.currentBlockPart02Pos.y]) || this.box[this.currentBlockPart03Pos.x - 1][this.currentBlockPart03Pos.y] != null && !this.isCurrentBlockChild(this.box[this.currentBlockPart03Pos.x - 1][this.currentBlockPart03Pos.y]) || this.box[this.currentBlockPart04Pos.x - 1][this.currentBlockPart04Pos.y] != null && !this.isCurrentBlockChild(this.box[this.currentBlockPart04Pos.x - 1][this.currentBlockPart04Pos.y])) { true を返します。 } } //他のブロックと衝突するかを判断する(左) クラッシュブロック左() { … } //他のブロックと衝突しそうかどうかを判断(右) クラッシュブロック右() { … } //現在の操作ブロックセットの子ブロックであるかどうかを判定します isCurrentBlockChild(judgeObj: cc.Node): boolean { (i = 0; i < 4; i++ とします) { (judgeObj === this.currentBlock.children[i])の場合{ true を返します。 } } false を返します。 } 各子ブロックはブロックを検出すると、左、右、または隣を見て他のブロックがあるかどうかを判断する必要があり、判断対象のブロックが自分自身と同じ親クラスのブロックである可能性があるため、判断する際には、現在操作しているブロックセットの子ブロックであるかどうかも判断する必要があります。 6. ブロックの列全体を削除する ゲーム内のブロックを列ごとに見ると、空洞になっている部分があることに注意してください。この場合、空洞になっているブロックを 1 グリッド下に移動する方法を検討する必要があります。そのため、rowDown() メソッドでは、列全体が下降しているときに、同じ列の前のセルが空であると判断された場合、null が割り当てられ、次のセルに移動したばかりのブロックの情報が削除されます。 //行削除検出 deleteRow() { (i = 0; i < 18; i++ とします) { count = 0 とします。 (j = 0; j < 10; j++) の場合 { if (this.box[i][j] != null) { カウント++; } } // すべてのブロックが行に存在する場合if (count == 10) { (j = 0; j < 10; j++) の場合 { //削除をブロック this.box[i][j].removeFromParent(); this.box[i][j] = null; } this.rowDown(i); i--; //rowDown(i) の後、行全体が 1 グリッド下に移動するため、i-- です。そうしないと、複数の行を削除できず、ゲームが正常に実行されません。} } } //すべてのブロックを1グリッド下に移動します rowDown(i: number) { //現在削除されている行である i の値を記録します。let k = i; // 列のトラバーサル for (let j = 0; j < 10; j++) { //temp: 現在削除されている行の上にブロック要素の行がいくつあるかを計算するために使用されます(中間層の空洞化を含む) temp = -1 とします。 (i = k; i < 18; i++) の場合 { 温度++; if (this.box[i][j] != null) { this.box[i - 1][j] = this.box[i][j]; this.box[i][j].y -= 60; if (this.box[i + 1][j] == null) { this.box[temp + k][j] = null; } } } } } 3.最後に書く全体的に言えば、最も重要な問題について説明すべきでした。不明な点がある場合は、元のプロジェクト ファイルをダウンロードしてください。 リンク: Baidu Netdisk 抽出コードを入力してください 抽出コード: c4ss CocosCreator でテトリス ゲームを作る方法についての記事はこれで終わりです。CocosCreator に関する関連コンテンツについては、123WORDPRESS.COM で過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: CSS3マスクレイヤーのくり抜き効果を実現するさまざまな方法
>>: 基本構造、ドキュメント タイプ、ヘッダー、本文などの一般的な HTML 要素の概要。
目次リアクトファイバーの作成1. 始める前に2. React.renderから始める3. 終了リアク...
目次FastDFSについて1. 画像を検索する2. イメージをインストールする3.1. 必要なディレ...
マクロタスクとマイクロタスクJavaScript はシングルスレッド言語です (マルチスレッドの場合...
目次序文JSON.stringify の 6 つの機能特集1特集2特集3特集4特集5特集6手動で文字...
この記事では、Vueでアップロードされた画像に透かしを追加する具体的な実装コードを参考までに共有しま...
問題を見つける最近、以前のデータを入力していたときに、プログラムが突然次のエラーを報告しました。 [...
質問Docker でローカル データベースにアクセスするにはどうすればよいでしょうか? 127.0....
日常の開発タスクでは、データ テーブル内のグループ化フィールドに基づいて統計データを取得するために、...
macにbrewを使ってphp56をインストールしたところ、 opensslがバージョン1.1だった...
ネットワーク通信の概要オンライン ゲームを開発する場合、必然的にネットワーク通信に対処する必要があり...
Vue+iview メニューとタブのリンク現在、vue+iview を使用してバックエンド管理システ...
今日、Tomcat サーバーの設定時にちょっとした問題が発生したので、参考までにいくつかご説明したい...
url-loader をダウンロード 糸を追加 -D URLローダー モジュール: { ルール: {...
目次単一マシンの展開オンラインプルミラーを見るRabbitMQを作成して実行するMQコンテナを正常に...
MySQL クエリ結果の行フィールドの結合は、次の 2 つの関数を使用して実装できます。 1. co...