Three.jsで3D描画をするのであれば
Scene
レンダラー
ライト
カメラ
が必要です。
幅は600、高さは400とします。それからスコアや自機、ボスのlifeが表示される部分は2Dになるのですが、両者を混在させる方法がわからなかったのでcanvasを3つ作りました。真ん中にあるのが3D描画をする部分です。
1 2 3 4 5 6 7 8 9 10 |
let scene: THREE.Scene = new THREE.Scene(); let renderer: THREE.WebGLRenderer; let light: THREE.AmbientLight; let camera: THREE.PerspectiveCamera; const RendererWidth = 600; const RendererHeight = 400; let HeaderContext: CanvasRenderingContext2D; let FooterContext: CanvasRenderingContext2D; |
現在ゲームはどのような状態にあるのか、対ボス戦なのか、通常の戦闘なのか、ゲームオーバーになっている状態なのか。それを格納するのがGameStatusという変数です。ページにアクセスしたらいきなりはじまるのはおかしいので、最初は Status.GameOver;にしています。
最初は第一ステージからはじまります。
自機オブジェクトと自機から発射された弾丸、敵と敵が発射した弾丸、爆発の火球を格納する配列、背景色を格納する変数を用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
enum Status { Battle, Boss, GameOver, } let GameStatus: Status = Status.GameOver; let Stage: number = 1; let jiki: Jiki; let JikiBurrets: JikiBurret[] = []; let Enemies: Enemy[] = []; let EnemyBurrets: EnemyBurret[] = []; let Explosions: Explosion[] = []; let CanvasBackColor: string = "black"; |
ページが読み込まれたら初期化の処理をおこないます。またキー操作に対応できるようにしておきます。
1 2 3 |
window.addEventListener("DOMContentLoaded", init); document.onkeydown = OnKeyDown; // OnKeyDownとOnKeyUp関数は後述 document.onkeyup = OnKeyUp; |
では初期化の処理をみてみましょう。init()関数で初期化の処理をおこないます。上下のCanvasのコンテキストを取得してメインのCanvasのレンダラーを作成します。そしてカメラと光源を生成します。
それから3Dっぽさを演出するために緑色の直線を縦横に描画します。そして自機オブジェクトを作成しますが、Sceneにはまだ追加しません。ゲームが開始されたときに追加し、自機が爆破されたら取り除きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
function init() { let HeaderCanvas: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById('HeaderCanvas'); HeaderCanvas.width = RendererWidth; HeaderCanvas.height = 60 HeaderContext = HeaderCanvas.getContext("2d"); let FooterCanvas: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById('FooterCanvas'); FooterCanvas.width = RendererWidth; FooterCanvas.height = 40; FooterContext = FooterCanvas.getContext("2d"); // レンダラーを作成 renderer = new THREE.WebGLRenderer({ canvas: <HTMLCanvasElement>document.getElementById('MainCanvas') }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(RendererWidth, RendererHeight); // カメラを作成 camera = new THREE.PerspectiveCamera(45, RendererWidth / RendererHeight, 0.01, 60); camera.position.set(0, -6, 5); camera.lookAt(new THREE.Vector3(0, 7, 0)); // 光源 light = new THREE.AmbientLight(0xFFFFFF, 1.0); light.position.set(0, -1, 1); light.intensity = 2; scene.add(light); // 縦横の直線を描画して3Dっぽさを演出する for (let y = -50; y < 50; y += 2) AddLineEW(y); for (let x = -50; x < 50; x += 2) AddLineNS(x); ShowStringGameOver(); scene.remove(GameOverSprite); jiki = new Jiki(); tick(); } |
3Dっぽさを演出する(?)ための直線を描画するための関数です。
直線を描画するためには
var line_geometry = new THREE.Geometry();
といった処理が必要だよと多くのサイトに書かれているのですが、これだとエラーになってしまいます。これはTypeScriptの側に問題があるようです。そこで前の行に //@ts-ignore をつけています。これでうまくいきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
// LineEWsは直線の描画位置を変更するときに必要 let LineEWs: THREE.Line[] = []; // 縦横に直線を描画する function AddLineEW(y) { const line_material = new THREE.LineBasicMaterial({ color: 0x008000 }); //geometryの宣言と生成 //@ts-ignore var line_geometry = new THREE.Geometry(); //頂点座標の追加 line_geometry.vertices.push( new THREE.Vector3(-50, y, -2), new THREE.Vector3(50, y, -2), ); //線オブジェクトの生成 let line = new THREE.Line(line_geometry, line_material); //sceneにlineを追加 scene.add(line); LineEWs.push(line); } function AddLineNS(x) { const line_material = new THREE.LineBasicMaterial({ color: 0x008000 }); //geometryの宣言と生成 //@ts-ignore var line_geometry = new THREE.Geometry(); //頂点座標の追加 line_geometry.vertices.push( new THREE.Vector3(x, -50, -2), new THREE.Vector3(x, 50, -2), ); //線オブジェクトの生成 let line = new THREE.Line(line_geometry, line_material); //sceneにlineを追加 scene.add(line); } |
次にアニメーション関連の処理です。ここではオブジェクトの移動や当たり判定をおこなっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
// tick()が何回実行されたか? ボス戦を開始時期を知るために必要 let tickCount = 0; function tick() { tickCount++; requestAnimationFrame(tick); // フィールドに描画されている縦横の直線を手前に向かって移動させ // 自機が前に移動しているように見せかける MoveLines(); // 当たり判定(ゲームオーバーになったあとは考えなくてよい) if (GameStatus != Status.GameOver) { // 自機から発射された弾丸は敵に命中したか? CheckMyBurretsHit(); // 敵機から発射された弾丸は自機に命中したか? CheckEnemyBurretsHit(); // 自機が敵そのものと衝突してしまったか? CheckJikiCollidedEnemis(); // 自機を移動させる jiki.Move(); } // ゲームオーバーになった弾丸の動きが止まってしまわないように // GameStatus == Status.Noneであっても処理は必要 // 自機の弾丸を移動させる JikiBurretsMove(); // 敵を移動させる EnemieMove(); // 敵の弾丸を移動させる EnemyBurretsMove(); // 爆発を移動させる ExplosionsMove(); // 必要なら新しい敵をつくる CreateNewEnemyIfNeed(); // レンダリング renderer.render(scene, camera); // 得点やボスのLifeなどを表示する ShowEtc(); } |
MoveLines()は、フィールドに描画されている縦横の直線を手前に向かって移動させ自機が前に移動しているように見せかけるための関数です。
1 2 3 4 |
function MoveLines() { LineEWs.forEach(x => { x.translateY(-0.1); }); LineEWs.forEach(x => { if (x.position.y < -2) x.translateY(50); }); } |
CheckMyBurretsHit関数は自機から発射された弾丸は敵に命中したかを判定するものです。オブジェクトの中心同士の距離とオブジェクトの幅の半分の合計を比較することで当たり判定をしています。敵に命中した場合は敵のlifeをひとつ減らし、自作関数のBurretHit関数を呼び出して敵の種類や状態によって適切な処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function CheckMyBurretsHit() { for (let i = 0; i < Enemies.length; i++) { if (Enemies[i].life <= 0) continue; for (let j = 0; j < JikiBurrets.length; j++) { if (JikiBurrets[j].life <= 0) continue; let radis2 = (Enemies[i].size * 0.5 + JikiBurrets[j].size * 0.5) ** 2; let distance2 = (Enemies[i].X - JikiBurrets[j].X) ** 2 + (Enemies[i].Y - JikiBurrets[j].Y) ** 2; if (radis2 > distance2) { if (Enemies[i].constructor === Boss && Boss.isLock) break; Enemies[i].life--; BurretHit(Enemies[i]); JikiBurrets[j].life = 0; break; } } } } |
CheckEnemyBurretsHit関数とCheckJikiCollidedEnemis関数は敵の攻撃が自機にダメージを与えるかを判定します。ダメージをうけたときはJikiDamageという自作関数で処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// 敵の弾丸は自機に命中したか? function CheckEnemyBurretsHit() { for (let i = 0; i < EnemyBurrets.length; i++) { if (EnemyBurrets[i].life <= 0) continue; let radis2 = (EnemyBurrets[i].size * 0.5 + jiki.size * 0.5) ** 2; let distance2 = (EnemyBurrets[i].X - jiki.X) ** 2 + (EnemyBurrets[i].Y - jiki.Y) ** 2; if (radis2 > distance2) { EnemyBurrets[i].life = 0; JikiDamage(); } } } // 自機が敵そのものと衝突してしまったか? function CheckJikiCollidedEnemis() { for (let i = 0; i < Enemies.length; i++) { if (Enemies[i].life <= 0) continue; let radis2 = (Enemies[i].size * 0.5 + jiki.size * 0.5) ** 2; let distance2 = (Enemies[i].X - jiki.X) ** 2 + (Enemies[i].Y - jiki.Y) ** 2; if (radis2 > distance2) { Enemies[i].life = 0; JikiDamage(); } } } |
以下の関数はオブジェクトの移動にかんするものです。なにもしないと当たらなかった弾丸はどこまでも飛び続けます。描画する必要がないところまで飛んでしまった弾丸はSceneと配列からはずします。また撃墜された敵や爆発がおわって不要になった火球も取り除きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// 自機の弾丸を移動させる function JikiBurretsMove() { // 離れた位置まで飛びすぎた弾丸やすでに何かに命中した弾丸は描画対象からはずす JikiBurrets.forEach(x => x.Move()); let noNeedBurrets = JikiBurrets.filter(x => !(x.life >= 1 && x.Y < 40)); JikiBurrets = JikiBurrets.filter(x => x.life >= 1 && x.Y < 40); noNeedBurrets.forEach(x => x.RemoveScene(scene)); } // 敵を移動させる function EnemieMove() { // 離れた位置まで移動しすぎた敵や撃墜された敵は描画対象からはずす Enemies.forEach(x => x.Move()); let noNeedEnemies = Enemies.filter(x => !(x.life >= 1 && x.Y > -1 && x.Y < 40)); Enemies = Enemies.filter(x => x.life >= 1 && x.Y > -1 && x.Y < 40); noNeedEnemies.forEach(x => x.RemoveScene(scene)); } // 敵の弾丸を移動させる function EnemyBurretsMove() { // 離れた位置まで飛びすぎた弾丸やすでに何かに命中した弾丸は描画対象からはずす EnemyBurrets.forEach(x => x.Move()); let noNeedEnemyBurrets = EnemyBurrets.filter(x => !(x.life >= 1 && x.Y > -1 && x.Y < 50 && x.X < 50 && x.X > -50)); EnemyBurrets = EnemyBurrets.filter(x => x.life >= 1 && x.Y > -1 && x.Y < 50 && x.X < 50 && x.X > -50); noNeedEnemyBurrets.forEach(x => x.RemoveScene(scene)); } // 爆発を移動させる function ExplosionsMove() { Explosions.forEach(x => x.Move()); let noNeedExplosions = Explosions.filter(x => x.life < 1); Explosions = Explosions.filter(x => !(x.life < 1)); noNeedExplosions.forEach(x => x.RemoveScene(scene)); } |
新しい敵をつくるための関数を示します。ただボス戦がはじまる直前やボスを倒した直後は一時的に敵の出現をとめます。isIntervalフラグがセットされているときは新しい敵はつくられません。
新しいステージがはじまってtickCount > BeginBossTickCountとなり、ザコ敵もいなくなったらしばらくのインターバルの後にボスが出現して対ボス戦がはじまります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
// 必要なら新しい敵をつくる。ただしisInterval == trueのときは生成しない let isInterval: boolean = false; function CreateNewEnemyIfNeed() { // ステージクリア直後などisInterval=trueになる。 // このフラグが立っているときは新しい敵はつくらない if (isInterval) return; // 新しいステージがはじまって一定時間するとボス戦がはじまる let BeginBossTickCount = 3000; if (tickCount < BeginBossTickCount) { CreateEnemy1(); CreateEnemy2(); if (GameStatus == Status.Battle) PlayBattleBGM.Check(); } else { // ゲームオーバー以降はザコ敵が表示されるだけでボス戦ははじまらない if (GameStatus == Status.GameOver) { tickCount = 0; // 対ボス戦の最中にゲームオーバーになった場合は // ボスはフィールド上からいなくなる if (boss != null) boss.life = 0; } else { // 新しいステージがはじまってtickCount > BeginBossTickCountとなり、 // ザコ敵もいなくなったらボス戦がはじまる if (Enemies.length == 0) { if (GameStatus == Status.Battle) { PlayBattleBGM.Stop(); // 通常戦闘時のBGMを停止 isInterval = true; // すぐにボスを出現させずに2秒間の間をもたせる setTimeout(BeginBoss, 2000); } } } // ボスのLifeが低下してきたらザコ敵も出現させ、ゲームの難易度を上げる if (GameStatus == Status.Boss) { PlayBattleBossBGM.Check(); if (boss.life < Boss.LifeMax / 2) // 50%を切ったらタイプ2の敵を出現させる CreateEnemy2(); if (boss.life < Boss.LifeMax / 4) // 25%を切ったらさらにタイプ2の敵も出現させる CreateEnemy1(); } } } // 敵を生成して描画されるようにする function CreateEnemy1() { if (tickCount % 100 == 50) { let enemy: Enemy1 = new Enemy1(); Enemies.push(enemy); enemy.AddScene(scene); } } function CreateEnemy2() { if (tickCount % 100 == 0) { let enemy: Enemy2 = new Enemy2(); Enemies.push(enemy); enemy.AddScene(scene); } } // ボス戦を開始する let boss: Boss = null; function BeginBoss() { // ボスを生成、シーンに追加 boss = new Boss(); Enemies.push(boss); boss.AddScene(scene); // eStatusをStatus.Bossに変えてBGMも変える PlayBattleBossBGM.Start(); GameStatus = Status.Boss; isInterval = false; } |
メインのCanvasの描画処理がおわったらスコアや自機のlifeなどの情報を上下のCanvasに表示させます。以下はそのための関数です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// 得点やボスのLifeなどを表示する function ShowEtc() { HeaderContext.clearRect(0, 0, RendererWidth, 60); HeaderContext.fillStyle = CanvasBackColor; HeaderContext.fillRect(0, 0, RendererWidth, 70); ShowBossLife(); ShowScore(); ShowJikiLife() } function ShowJikiLife() { FooterContext.fillStyle = CanvasBackColor; FooterContext.fillRect(0, 0, RendererWidth, 40); if (jiki.life > 0 && GameStatus != Status.GameOver) { FooterContext.fillStyle = "#0f0"; FooterContext.fillRect(20, 10, (RendererWidth - 40) * (jiki.life / jiki.LifeMax), 20); } } function ShowBossLife() { HeaderContext.fillStyle = CanvasBackColor; HeaderContext.fillRect(0, 40, RendererWidth, 20); // 当然のことながらボスのlifeはボスがいるときだけ表示する if (GameStatus == Status.Boss && boss.life > 0) { HeaderContext.fillStyle = "yellow"; HeaderContext.fillRect(20, 40, (RendererWidth - 40) * (boss.life / Boss.LifeMax), 20); } } // スコアを表示する let score = 0; function ShowScore() { HeaderContext.fillStyle = "white"; HeaderContext.font = "30px 'MS ゴシック'"; let scoreText = "" + score; HeaderContext.fillText(scoreText, 10, 30); } |
以下は自機から発射された弾丸が命中した場合の点数加算の処理と爆発の処理に関する関数です。どんな敵に命中したのか、敵がうけたダメージによって爆発の規模を変えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
// 自機の弾丸が敵に命中した function BurretHit(x: Enemy) { // どんな敵に命中したのか? ボスか? if (x.constructor === Boss) { BurretHitBoss(x); } else { if (x.life <= 0) { // ザコ敵に命中してlifeを0にした場合は通常の爆発 Explode(x.X, x.Y); score += 50; } else { // ザコ敵に命中したがlifeが残っている場合は小爆発 SmallExplode(x.X, x.Y); } } } function BurretHitBoss(boss: Boss) { // ボスに命中してlifeを0にした場合は大爆発 if (boss.life <= 0) { BigExplode(boss.X, boss.Y); // ボス戦は終了したのでボス戦のBGMは停止する PlayBattleBossBGM.Stop(); score += boss.score; // ボスのまわりにいるザコ敵も爆発させ、点数加算 Enemies.forEach(x => { x.life = 0; Explode(x.X, x.Y); score += x.score / 2; }); // 5秒間のインターバルをおき、つぎのステージに isInterval = true; setTimeout(BeginNextStage, 5000); } else { // ボスに命中したがlifeが残っている場合は小爆発 SmallExplode(boss.X, boss.Y); score += 10; } } // ボスを倒したので次のステージへ function BeginNextStage() { isInterval = false; tickCount = 0; GameStatus = Status.Battle; Stage++; PlayBattleBGM.Start(); } // 普通の爆発 function Explode(centerX: number, centerY: number) { let baseSpeed = 0.1; for (let i = 0; i < 10; i++) { let r = Math.random(); let vx = r * baseSpeed * 2 - baseSpeed; r = Math.random(); let vy = r * baseSpeed * 2 - baseSpeed; r = Math.random(); let vz = r * baseSpeed * 2 - baseSpeed; let enp = new Explosion(centerX, centerY, vx, vy, vz, 1); enp.AddScene(scene); Explosions.push(enp); } PlaySoundeffect.Explosion(); } // ちょっとした爆発 function SmallExplode(centerX: number, centerY: number) { let enp = new Explosion(centerX, centerY, 0, 0, 0, 1); enp.AddScene(scene); Explosions.push(enp); } // ボスを倒したときの大爆発 function BigExplode(centerX: number, centerY: number) { let baseSpeed = 0.3; for (let i = 0; i < 30; i++) { let r = Math.random(); let vx = r * baseSpeed * 2 - baseSpeed; r = Math.random(); let vy = r * baseSpeed * 2 - baseSpeed; r = Math.random(); let vz = r * baseSpeed * 2 - baseSpeed; let enp = new Explosion(centerX, centerY, vx, vy, vz, 3); enp.AddScene(scene); Explosions.push(enp); } PlaySoundeffect.BigExplosion(); } |
自機が被弾したときの処理に関する関数を示します。被弾時に立て続けにやられないように一時的に「無敵状態」にします。また背景を一瞬赤くします。そして自機のlifeが0になったらゲームオーバーです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// いまは無敵状態か? let MutekiMode = false; function JikiDamage() { if (!MutekiMode) { jiki.life--; PlaySoundeffect.Damage(); Explode(jiki.X, jiki.Y); MutekiMode = true; setTimeout(EndMutekiMode, 1000); // 被弾時は背景を一瞬赤くする(0.1秒) BackColor("red"); setTimeout(BackColor, 100, "black"); // 自機のlifeが0になったらゲームオーバー if (jiki.life <= 0) { // 自機が描画されないようにSceneから除去 scene.remove(jiki.Mesh); // BGM停止 PlayBattleBGM.Stop(); PlayBattleBossBGM.Stop(); // ゲームオーバーの表示 GameStatus = Status.GameOver; ShowStringGameOver(); } } } function EndMutekiMode() { MutekiMode = false; } function BackColor(color: string) { renderer.setClearColor(color, 1); CanvasBackColor = color; } |
ゲームオーバーになったらその旨画面上に表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// ゲームオーバー時はその旨表示する let GameOverSprite: THREE.Sprite = null; let PressSKeySprite: THREE.Sprite = null; function ShowStringGameOver() { if (GameOverSprite == null) { let canvasTexture = new THREE.CanvasTexture(CreateCanvasForTexture(500, 500, 'GAME OVER', 50)); let scaleMaster = 5; GameOverSprite = CreateSprite(canvasTexture, { x: scaleMaster, y: scaleMaster, z: scaleMaster, }, { x: 0, y: 0, z: 4 }); } if (PressSKeySprite == null) { let canvasTexture2 = new THREE.CanvasTexture(CreateCanvasForTexture(500, 500, 'START PRESS S KEY', 50)); let scaleMaster2 = 3; PressSKeySprite = CreateSprite(canvasTexture2, { x: scaleMaster2, y: scaleMaster2, z: scaleMaster2, }, { x: 0, y: 0, z: 3 }); } scene.add(GameOverSprite); scene.add(PressSKeySprite); } function CreateSprite(texture, scale, position): THREE.Sprite { const spriteMaterial = new THREE.SpriteMaterial({ map: texture }); const sprite = new THREE.Sprite(spriteMaterial); sprite.scale.set(scale.x, scale.y, scale.z); sprite.position.set(position.x, position.y, position.z); return sprite; }; function CreateCanvasForTexture(canvasWidth: number, canvasHeight: number, text: string, fontSize: number): HTMLCanvasElement { // 貼り付けるcanvasを作成。 const canvasForText: HTMLCanvasElement = document.createElement('canvas'); const ctx = canvasForText.getContext('2d'); ctx.canvas.width = canvasWidth; // 小さいと文字がぼやける ctx.canvas.height = canvasHeight; // 小さいと文字がぼやける // 透過する背景を描く ctx.fillStyle = 'rgba(0, 0, 0, 0.0)'; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.fillStyle = 'white'; ctx.font = `${fontSize}px 'MS ゴシック'`; ctx.fillText( text, // x方向の余白/2をx方向開始時の始点とすることで、横方向の中央揃えをしている。 (canvasWidth - ctx.measureText(text).width) / 2, // y方向のcanvasの中央に文字の高さの半分を加えることで、縦方向の中央揃えをしている。 canvasHeight / 2 + ctx.measureText(text).actualBoundingBoxAscent / 2 ); return canvasForText; }; |
次にキーが押されたときの処理に関する関数を示します。
方向キーが押されたらその方向に移動できることを示すフラグ(Jikiクラス内にある)をセットし、離されたらクリアします。また弾丸発射はスペースキー、ゲームスタートはSキーです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
function OnKeyDown(e: KeyboardEvent) { if (e.keyCode == 37) { // ← jiki.MoveLeft = true; } if (e.keyCode == 38) { // ↑ jiki.MoveFront = true; } if (e.keyCode == 39) { // → jiki.MoveRight = true; } if (e.keyCode == 40) { // ↓ jiki.MoveBack = true; } if (e.keyCode == 32 && GameStatus != Status.GameOver) { // Space キー jiki.Shot(); } if (e.keyCode == 83) { // S キー GameStart(); } } function OnKeyUp(e: KeyboardEvent) { if (e.keyCode == 37) { jiki.MoveLeft = false; } if (e.keyCode == 38) { jiki.MoveFront = false; } if (e.keyCode == 39) { jiki.MoveRight = false; } if (e.keyCode == 40) { jiki.MoveBack = false; } } function GameStart() { Enemies.forEach(x => x.life = 0); EnemyBurrets.forEach(x => x.life = 0);; JikiBurrets.forEach(x => x.life = 0);; Explosions.forEach(x => x.life = 0);; jiki.AddScene(scene); jiki.X = 0; jiki.life = jiki.LifeMax; score = 0; Stage = 1; GameStatus = Status.Battle; tickCount = 0; PlayBattleBGM.Start(); if (GameOverSprite != null) scene.remove(GameOverSprite); if (PressSKeySprite != null) scene.remove(PressSKeySprite); } |
あとはコンパイルして.jsにしてHTMLファイルに書けば完成です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<!DOCTYPE html> <html> <head> <title>3Dの縦スクロールシューティングゲーム</title> <meta charset="UTF-8" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script> <style> #container { position:relative; height: 500px; } #HeaderCanvas { position: absolute; top: 0px; left: 0px; height:60px; } #MainCanvas { position: absolute; top: 60px; left: 0px; height:400px; } #FooterCanvas { position: absolute; top: 460px; left: 0px; height:40px; } </style> </head> <body> <div id = "container"> <canvas id="HeaderCanvas"></canvas> <canvas id="MainCanvas"></canvas> <canvas id="FooterCanvas"></canvas> <div> <script src="./data-url.js"></script> <script src="./sound.js"></script> <script src="./game-character.js"></script> <script src="./explosion.js"></script> <script src="./jiki.js"></script> <script src="./enemy.js"></script> <script src="./app.js"></script> </body> </html> |
生成されたjsは次回示します。
Three.jsでシューティングゲームをつくってみる 仕上げ(その2)のhtmlファイルの中にあるdata-url.jsとはどこに書いてあるのかが知りたいです。
掲載するのを忘れていました。これをどうぞ。
https://lets-csharp.com/wp-content/uploads/2021/02/shooting-game-threejs/data-url.js