Merge pull request #157 from silverskyvicto/translate-ja/6-space-game

translate 6-space-game into japanese
pull/158/head
Jen Looper 4 years ago committed by GitHub
commit d1c9283e1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,224 @@
# スペースゲーム構築プロジェクト その 1: イントロダクション
![video](../../images/pewpew.gif)
## レッスン前の小テスト
[レッスン前の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/29)
### ゲーム開発における継承とコンポジション
以前のレッスンでは、プロジェクトの規模が非常に小さかったため、構築したアプリの設計アーキテクチャを気にする必要はあまりありませんでした。しかし、アプリケーションの規模や範囲が大きくなると、アーキテクチャの決定がより大きな関心事になります。JavaScript で大規模なアプリケーションを作成するには、大きく分けて2つのアプローチがあります。*コンポジション* と *継承* です。どちらにも長所と短所がありますが、ゲームの文脈から説明してみましょう。
✅ これまでに書かれたプログラミング本の中で最も有名なものの一つは、[デザインパターン](https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2))と関係があります。
ゲームには `ゲームオブジェクト` があり、これは画面上に存在するオブジェクトです。つまり、それらは直交座標系上に位置しており、`x` と `y` の座標を持つことが特徴です。ゲームを開発していくうちに、すべてのゲームオブジェクトには、すべてのゲームに共通する標準的な特性があることに気づくでしょう。
- **location-based** すべてではないにしても、ほとんどのゲーム要素は位置情報に基づいています。これは、場所、`x` と `y` を持つことを意味します
- **movable** これらは、新しい場所に移動することができるオブジェクトです。これは典型的にはヒーローやモンスター、NPC (プレイヤーではないキャラクター) などですが、例えば木のような静的なオブジェクトはそうではありません
- **self-destructing** これらのオブジェクトは、削除のために自分自身を設定する前に、一定期間だけ存在します。通常、これは `dead` または `destroyed` ブール値で表され、このオブジェクトがレンダリングされなくなることをゲームエンジンに通知します
- **cool-down** 「クールダウン」は、短命なオブジェクトの典型的なプロパティです。典型的な例としては、数ミリ秒しか見られない爆発のようなテキストやグラフィック効果があります。
✅ パックマンのようなゲームについて考えてみましょう。このゲームでは、上に挙げた4つのオブジェクトの種類を特定できますか?
### 行動の表現
上で説明したのは、すべてゲームオブジェクトが持つことのできる動作です。では、それらをどのようにエンコードするのでしょうか? この動作をクラスやオブジェクトに関連付けられたメソッドとして表現することができます。
**クラス**
クラスに特定の振る舞いを追加するために `クラス``継承` と組み合わせて使うという考え方です。
✅ 継承は理解しておくべき重要な概念です。[継承に関する MdN の記事](https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)で詳しく解説しています。
コードで表現すると、ゲームオブジェクトは通常このようになります。
```javascript
//GameObject クラスを設定します。
class GameObject {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
}
}
//このクラスは GameObject の固有のクラスプロパティを拡張します。
class Movable extends GameObject {
constructor(x,y, type) {
super(x,y, type)
}
//この移動可能なオブジェクトは、画面上で移動することができます。
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//これは Movable クラスを拡張した特定のクラスで、継承しているすべてのプロパティを利用することができます。
class Hero extends Movable {
constructor(x,y) {
super(x,y, 'Hero')
}
}
//一方、このクラスは GameObject のプロパティのみを継承しています。
class Tree extends GameObject {
constructor(x,y) {
super(x,y, 'Tree')
}
}
//hero は動くことができます
const hero = new Hero();
hero.moveTo(5,5);
//しかし木にはできません
const tree = new Tree();
```
✅ パックマンのヒーロー (例えば、Inky、Pinky、Blinky など) と、それが JavaScript でどのように書かれているかを、数分かけて再定義してみましょう。
**コンポジション**
オブジェクトの継承を処理する別の方法として、*コンポジション* を使用する方法があります。すると、オブジェクトは次のように動作を表現します。
```javascript
//定数の gameObject を作成する
const gameObject = {
x: 0,
y: 0,
type: ''
};
//そして定数の movable
const movable = {
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//そして、定数 movableObject は定数 gameObject と movable で構成されます。
const movableObject = {...gameObject, ...movable};
//次に、movableObject のプロパティを継承する新しい Hero を作成する関数を作成します。
function createHero(x, y) {
return {
...movableObject,
x,
y,
type: 'Hero'
}
}
//...そして gameObject プロパティのみを継承する静的オブジェクト。
function createStatic(x, y, type) {
return {
...gameObject
x,
y,
type
}
}
//hero を作って動かします
const hero = createHero(10,10);
hero.moveTo(5,5);
//そして、周りに立つだけの木を作ります
const tree = createStatic(0,0, 'Tree');
```
**どのパターンを使えばいいのか?**
どちらのパターンを選ぶかはあなた次第です。JavaScript はこれらのパラダイムの両方をサポートしています。
--
ゲーム開発に共通するもう一つのパターンは、ゲームのユーザーエクスペリエンスとパフォーマンスを処理する問題を扱っています。
## Pub/sub パターン
✅ Pub/Sub は 'publish-subscribe' の略です。
このパターンは、あなたのアプリケーションのバラバラな部分が別のものを知ってはいけないという考えを示しています。それはなぜでしょうか? 様々な部分が分離されていると、一般的に何が起こっているのかを見るのがとても簡単になります。また、必要に応じて突然動作を変更することも容易になります。どのようにしてこれを達成するのでしょうか? いくつかの概念を確立することで実現します。
- **message**: メッセージは通常、オプションのペイロード (メッセージの内容を明確にするデータ) を伴ったテキスト文字列です。ゲームにおける典型的なメッセージは `KEY_PRESSED_ENTER` です
- **publisher**: この要素はメッセージを**発行し**、すべての購読者に送信します
- **subscriber**: この要素は、特定のメッセージを *待ち受け*、このメッセージを受信した結果として、レーザーを発射するなどのタスクを実行します
実装されているのはかなり小さいですが、かなり強力なパターンです。以下に実装方法を紹介します。
```javascript
//リスナーを含む EventEmitter クラスを設定します。
class EventEmitter {
constructor() {
this.listeners = {};
}
//メッセージを受信したときに、リスナーにそのペイロードを処理させます。
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
//メッセージを受信したときに、リスナーにそのペイロードを処理させます。
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach(l => l(message, payload))
}
}
}
```
上記のコードを使用するために、非常に小さな実装を作成することができます。
```javascript
//メッセージ構造を設定します
const Messages = {
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT'
};
//上記で設定した EventEmitter を呼び出します。
const eventEmitter = new EventEmitter();
//hero を設定します。
const hero = createHero(0,0);
//イベント送信者に hero が左に移動するメッセージを監視することを知らせ、それに基づいて行動するようにします。
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
});
//keyup イベントをリッスンするためにウィンドウを設定し、具体的には左矢印がヒットした場合、hero を左に移動するためのメッセージを発します。
window.addEventListener('keyup', (evt) => {
if (evt.key === 'ArrowLeft') {
eventEmitter.emit(Messages.HERO_MOVE_LEFT)
}
});
```
上ではキーボードイベント `ArrowLeft` を接続して `HERO_MOVE_LEFT` メッセージを送信している。私たちはそのメッセージを聞き、その結果として `hero` を移動させます。このパターンの強みは、イベントリスナーとヒーローがお互いのことを知らないことです。`ArrowLeft` を `A` キーにリマップすることができます。さらに、eventEmitter の `on` 関数にいくつかの編集を加えることで、`ArrowLeft` で全く異なることが可能になります。
```javascript
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
});
```
ゲームが成長するときに物事がより複雑になったとしても、このパターンは複雑さの中で同じままで、あなたのコードはきれいなままになります。このパターンを採用することをお勧めします。
---
## 🚀 チャレンジ
pub-sub パターンがどのようにゲームを盛り上げるかを考えてみましょう。どのパーツがイベントを発生させ、それに対してゲームはどのように反応しますか? 新しいゲームとそのパーツがどのように振る舞うかを考えて、創造性を発揮するチャンスです。
## レッスン後の小テスト
[レッスン後の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/30)
## 復習と自己学習
Pub/Sub については、[こちらを読んでみてください](https://docs.microsoft.com/ja-jp/azure/architecture/patterns/publisher-subscriber)。
## 課題
[ゲームのモックアップ](assignment.ja.md)

@ -0,0 +1,11 @@
# ゲームのモックアップ
## 説明書
レッスンで使用したコードサンプルを使用して、好きなゲームの表現を書いてください。単純なゲームである必要がありますが、目標は、クラスまたは構成パターンと pub/sub パターンのいずれかを使用して、ゲームがどのように起動するかを示すことです。創造力を発揮してください。
## ルーブリック
| 基準 | 模範的な例 | 適切な | 改善が必要 |
| -------- | ------------------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------- |
| | 3つの要素を画面上に配置して操作する | 2つの要素を画面上に配置して操作する | 1つの要素を画面上に配置して操作する |

@ -0,0 +1,216 @@
# スペースゲーム構築プロジェクト その 2: ヒーローとモンスターをキャンバスに描く
## レッスン前の小テスト
[レッスン前の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/31)
## Canvas
Canvas は HTML 要素で、デフォルトでは何のコンテンツもありません。何もない白紙の状態です。その上に描画することで、Canvas に追加する必要があります。
✅ MDN の [Canvas API についての詳細](https://developer.mozilla.org/ja/docs/Web/API/Canvas_API)はこちらをご覧ください。
ページの本文の一部として、一般的にどのように宣言されているかをご紹介します。
```html
<canvas id="myCanvas" width="200" height="100"></canvas>
```
上では `id`, `width`, `height` を設定しています。
- `id`: 設定することで、これと対話する必要があるときに参照を取得できるようになります
- `width`: これは要素の幅です
- `height`: これは要素の高さです
## 簡単な幾何学図形の描画
キャンバスは、直交座標系を使って物を描いています。したがって、何かがどこにあるかを表現するために x 軸と y 軸を使用しています。`0,0` の位置が左上の位置で、右下の位置がキャンバスの幅と高さと言ったところです。
![the canvas's grid](../canvas_grid.png)
> 画像は [MDN](https://developer.mozilla.org/ja/docs/Web/Guide/HTML/Canvas_tutorial/Drawing_shapes) より
キャンバス要素に描画するには、以下の手順を踏む必要があります。
1. Canvas 要素への**参照を取得します**
1. canvas 要素の上にある Context 要素の**参照を取得します**
1. context 要素を使用して**描画操作を行います**
上記の手順のコードは、通常次のようになります。
```javascript
// 赤い四角を描きます。
//1. キャンバスの参照を取得します
canvas = document.getElementById("myCanvas");
//2. コンテキストを 2D に設定して基本的な図形を描画します。
ctx = canvas.getContext("2d");
//3. 赤で塗りつぶします。
ctx.fillStyle = 'red';
//4. そして、これらのパラメータで矩形を描画し、位置とサイズを設定します。
ctx.fillRect(0,0, 200, 200) // x, y, width, height
```
✅ Canvas API は主に 2D の図形に焦点を当てていますが、Web サイトに 3D の要素を描画することもできます。そのためには、[WebGL API](https://developer.mozilla.org/ja/docs/Web/API/WebGL_API) を使用するとよいでしょう。
Canvas API を使っていろいろなものを描くことができます。
- **幾何学的な形状**、我々はすでに長方形を描画する方法を示しましたが、あなたが描くことができるはるかに多くのものがあります
- **テキスト**は、任意のフォントと色でテキストを描くことができます
- **画像**では、例えば .jpg や .png のような画像アセットに基づいて画像を描くことができます
✅ やってみましょう! 長方形の描き方は知っていると思いますが、ページに円を描くことはできますか? CodePen に掲載されている面白い Canvas のドローイングを見てみましょう。ここに[特に印象的な例](https://codepen.io/dissimulate/pen/KrAwx)があります。
## 画像アセットの読み込みと描画
画像アセットをロードするには、`Image` オブジェクトを作成して `src` プロパティを設定します。そして、`load` イベントを待ち受け、それがいつ使用可能になるかを知ることができます。コードは次のようになります。
### アセットの読み込み
```javascript
const img = new Image();
img.src = 'path/to/my/image.png';
img.onload = () => {
// イメージが読み込まれて使用可能な状態になっています
}
```
### アセットの読み込みパターン
上記のようなコンストラクトで包んだ方が使いやすいですし、フルロードした時だけ操作しようとするのでおすすめです。
```javascript
function loadAsset(path) {
return new Promise((resolve) => {
const img = new Image();
img.src = path;
img.onload = () => {
// イメージが読み込まれて使用可能な状態になっています
resolve(img);
}
})
}
// 使いまわします
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
}
```
ゲームアセットを画面に描画するには、以下のようなコードになります。
```javascript
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d");
ctx.drawImage(heroImg, canvas.width/2, canvas.height/2);
ctx.drawImage(monsterImg, 0, 0);
}
```
## さあ、あなたのゲームを作り始める時が来ました。
### 何を構築するか
Canvas 要素を使って Web ページを作成します。これは黒い画面 `1024 * 768` をレンダリングする必要があります。画像は2枚用意しました。
- Hero の宇宙船
![Hero の宇宙船](../solution/assets/player.png)
- 5*5 monster
![Monster の宇宙船](../solution/assets/enemyShip.png)
### 開発を始めるための推奨ステップ
あなたのために作成されたファイルを `your-work` サブフォルダ内で探します。以下のファイルが含まれているはずです。
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
このフォルダのコピーを Visual Studio Code で開きます。ローカルの開発環境が設定されている必要があり、できれば Visual Studio Code に NPM と Node がインストールされている必要があります。もしあなたのコンピュータに `npm` がセットアップされていない場合は、[ここにその方法があります](https://www.npmjs.com/get-npm)。
`your_work` フォルダに移動してプロジェクトを開始します。
```bash
cd your-work
npm start
```
以上で、`http://localhost:5000` というアドレスに HTTP サーバが起動します。ブラウザを開いて、そのアドレスを入力してください。今は何も表示されていませんが、そのうち変わるでしょう。
> 注: 画面上の変更を確認するには、ブラウザを更新してください。
### コードの追加
以下の問題を解決するために必要なコードを `your-work/app.js` に追加します。
1. 黒い背景のキャンバスを**描画します**
> tip: `/app.js` の適切な TODO の下に 2 行を追加し、`ctx` 要素を黒にして上下の座標を 0,0 にし、高さと幅をキャンバスと同じにします。
2. テクスチャを**読み込みます**
> tip: プレイヤーと敵の画像を追加するには `await loadTexture` を使用し、画像パスを渡してください。まだ画面には表示されていません!
3. 下半分の画面中央にヒーローを**描画します**
> tip: heroImg を画面に描画するには `drawImage` API を使用し、`canvas.width / 2 - 45` と `canvas.height - canvas.height / 4)` を設定します。
4. 5*5 のモンスターを**描画します**
> tip: これで、画面上に敵を描画するコードのコメントを解除することができるようになりました。次に、`createEnemies` 関数に移動して、それを構築する。
まず、いくつかの定数を設定します。
```javascript
const MONSTER_TOTAL = 5;
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
const STOP_X = START_X + MONSTER_WIDTH;
```
そして、モンスターの配列を画面上に描画するループを作成します。
```javascript
for (let x = START_X; x < STOP_X; x += 98) {
for (let y = 0; y < 50 * 5; y += 50) {
ctx.drawImage(enemyImg, x, y);
}
}
```
## 結果
仕上がりはこんな感じになるはずです。
![hero と 5 * 5 モンスターで黒画面](../partI-solution.png)
## Solution
まずはご自身で解決してみていただきたいですが、行き詰った場合は [solution](../solution/app.js) を参考にしてみてください。
---
## 🚀 チャレンジ
2D を中心とした Canvas API での描画について学んできましたが、[WebGL API](https://developer.mozilla.org/ja/docs/Web/API/WebGL_API) を参考に、3D オブジェクトを描画してみましょう。
## レッスン後の小テスト
[レッスン後の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/32)
## 復習と自己学習
Canvas API については、[こちらを読んでいただく](https://developer.mozilla.org/ja/docs/Web/API/Canvas_API)ことで詳しく知ることができます。
## 課題
[Canvas API で遊ぶ](assignment.ja.md)

@ -0,0 +1,11 @@
# Canvas API で遊ぶ
## 説明書
Canvas API の要素を 1 つ選んで、その周りに何か面白いものを作りましょう。星が繰り返される小さな銀河を作ることができますか? 色のついた線で面白いテクスチャを作ることができますか? インスピレーションを得るために CodePen を見ることができます (ただし、コピーはしないでください)。
## ルーブリック
| 基準 | 模範的な例 | 適切な | 改善が必要 |
| -------- | --------------------------------------------------------- | ----------------------------------- | --------------------- |
| | 興味深い質感や形状を示すコードが提出されている | コードは送信されたが実行されない | コードが提出されていない |

@ -0,0 +1,387 @@
# スペースゲーム構築プロジェクト その 3: モーションの追加
## レッスン前の小テスト
[レッスン前の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/33)
ゲームは、あなたが画面上を走り回るエイリアンを持っているまでは、あまり楽しいものではありません! このゲームでは、2種類の動きを使用しています。このゲームでは、2種類の動きを利用していきます。
- **キーボード/マウスの動き**: ユーザーがキーボードやマウスを操作して画面上のオブジェクトを移動させたとき
- **ゲームで誘導された動き**: ゲームが一定の時間間隔でオブジェクトを移動させたとき
では、どのようにして画面上で物を動かすのでしょうか? それはすべて直交座標に基づいています。オブジェクトの位置 (x,y) を変更してから、画面を再描画します。
通常、画面上で *移動* を行うには、以下の手順が必要です。
1. オブジェクトの**新しい位置を設定します**。これはオブジェクトが移動したと認識するために必要です
2. **画面をクリアします**が、これは描画の合間に画面をクリアする必要があります。背景色で塗りつぶす矩形を描くことでクリアできます
3. 新しい場所にオブジェクトを**再描画します**。これにより、ある場所から別の場所にオブジェクトを移動させることができます
コードではこんな感じになります。
```javascript
// hero の場所を決めます。
hero.x += 5;
// hero がいる長方形をクリアします。
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ゲームの背景と hero 描画し直します。
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ ヒーローを毎秒何フレームも描き直すとパフォーマンスコストが発生する理由が思いつきますか? [このパターンの代替案](https://www.html5rocks.com/ja/tutorials/canvas/performance/)を読んでみてください。
## キーボードイベントの処理
コードに特定のイベントをアタッチすることでイベントを処理します。キーボードイベントはウィンドウ全体でトリガーされますが、`click` のようなマウスイベントは特定の要素をクリックすることに接続することができます。このプロジェクトではキーボードイベントを使用します。
イベントを処理するには、ウィンドウの `addEventListener()` メソッドを使用し、2つの入力パラメータを指定する必要があります。最初のパラメータはイベントの名前で、例えば `keyup` のようなものです。2 番目のパラメータは、イベントの結果として呼び出される関数です。
以下に例を示します。
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = キーの文字列表現
if (evt.key === 'ArrowUp') {
// 何か処理をします。
}
})
```
キーイベントには、どのキーが押されたかを確認するために使用できる2つのプロパティがあります。
- `key`、これは押されたキーの文字列表現で、例えば `ArrowUp` のようなものです
- `keyCode`、これは数値表現であり、例えば `37``ArrowLeft` に対応します
✅ キーイベントの操作はゲーム開発以外でも有用です。他にはどのような用途が考えられますか?
### 特殊なキー: 注意事項
ウィンドウに影響を与える *特殊な* キーがあります。つまり、`keyup` イベントを聞いているときに、これらの特別なキーを使ってヒーローを動かした場合、水平スクロールも行われるということです。そのため、ゲームを構築する際には、このビルトインブラウザの動作を *shut-off* した方が良いかもしれません。このようなコードが必要です。
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // 矢印キー
case 32:
e.preventDefault();
break; // スペース
default:
break; // 他のキーをブロックしないでください。
}
};
window.addEventListener('keydown', onKeyDown);
```
上記のコードでは、矢印キーとスペースキーの *デフォルト* の動作が確実にシャットオフされます。*shut-off* メカニズムは `e.preventDefault()` を呼び出すときに発生します。
## ゲームで誘導された動き
`setTimeout()``setInterval()` 関数のようなタイマーを使うことで、オブジェクトの位置を目盛りや時間間隔ごとに更新することができます。これは次のようなものです。
```javascript
let id = setInterval(() => {
//敵を Y 軸で動かす
enemy.y += 10;
})
```
## ゲームループ
ゲームループとは、基本的には一定の間隔で呼び出される関数の概念です。ユーザーに見えるべきものはすべてループに描画されるので、ゲームループと呼ばれています。ゲームループはゲームの一部であるすべてのゲームオブジェクトを利用し、何らかの理由でゲームの一部ではない場合を除いて、すべてのオブジェクトを描画します。例えば、あるオブジェクトがレーザーで撃たれて吹き飛んでしまった場合、そのオブジェクトは現在のゲームループの一部ではなくなります (これについては後のレッスンで詳しく説明します)。
ゲームループがどのようなものか、コードで表現すると次のようになります。
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
上記のループは `200` ミリ秒ごとに呼び出され、キャンバスを再描画します。あなたのゲームに合った最適な間隔を選択することができます。
## スペースゲームの続き
既存のコードを使って、それを拡張していきます。パート で完成させたコードから始めるか、[パートⅡのスターター](../your-work)のコードを使います。
- **ヒーローの移動**: 矢印キーを使ってヒーローを移動できるようにコードを追加します
- **敵を移動する**: また、敵が与えられたレートで上から下に移動することを確認するためにコードを追加する必要があります
## 推奨される手順
あなたのために作成されたファイルを `your-work` サブフォルダ内で探します。以下のファイルが含まれているはずです。
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
次のコマンドをタイピングして、あなたのプロジェクトを `your_work` フォルダから開始します。
```bash
cd your-work
npm start
```
上記は、アドレス `http://localhost:5000` の HTTP サーバーを起動します。ブラウザを開いてそのアドレスを入力すると、今はヒーローと全ての敵が表示されるはずです。ただしまだ何も動いていません。
### コードの追加
1. `hero``enemy`、`game object`のための**オブジェクトを追加し**、それらは `x``y` のプロパティを持っている必要があります。([継承や合成](../../translations/README.ja.md)の部分を覚えておいてください)
*ヒント* `game object``x``y` を持ち、それ自身をキャンバスに描画する機能を持つものでなければなりません。
> tip: 以下のようにコンストラクタを定義した新しい GameObject クラスを追加してから、キャンバスに描画します。
```javascript
class GameObject {
constructor(x, y) {
this.x = x;
this.y = y;
this.dead = false;
this.type = "";
this.width = 0;
this.height = 0;
this.img = undefined;
}
draw(ctx) {
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
}
}
```
次に、この GameObject を拡張して、ヒーローと敵を作成します。
```javascript
class Hero extends GameObject {
constructor(x, y) {
...x, y, type, speedが必要です。
}
}
```
```javascript
class Enemy extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 98), (this.height = 50);
this.type = "Enemy";
let id = setInterval(() => {
if (this.y < canvas.height - this.height) {
this.y += 5;
} else {
console.log('Stopped at', this.y)
clearInterval(id);
}
}, 300)
}
}
```
2. (ヒーローを上下左右に動かす) キーナビゲーションを処理するための**キーイベントハンドラを追加します**
*REMEMBER* これは直交座標系で、左上は `0,0` です。また、*デフォルトの動作*を止めるコードを追加することも忘れないでください*。
> tip: onKeyDown 関数を作成して、それをウィンドウにアタッチします。
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
```
この時点でブラウザのコンソールを確認し、キー入力がログに記録されているかどうかを確認します。
3. [Pub Sub パターン](../../translations/README.ja.md)を**実装する**と、残りの部分に続くようにコードをきれいに保つことができます
この最後の部分を行うには
1. ウィンドウに**イベントリスナーを追加します**
```javascript
window.addEventListener("keyup", (evt) => {
if (evt.key === "ArrowUp") {
eventEmitter.emit(Messages.KEY_EVENT_UP);
} else if (evt.key === "ArrowDown") {
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
} else if (evt.key === "ArrowLeft") {
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
} else if (evt.key === "ArrowRight") {
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
}
});
```
1. メッセージを発行して購読するための **EventEmitter クラスを作成します**
```javascript
class EventEmitter {
constructor() {
this.listeners = {};
}
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach((l) => l(message, payload));
}
}
}
```
1. **定数を追加**して EventEmitter を設定します
```javascript
const Messages = {
KEY_EVENT_UP: "KEY_EVENT_UP",
KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **ゲームを初期化します**
```javascript
function initGame() {
gameObjects = [];
createEnemies();
createHero();
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
hero.y -=5 ;
})
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
hero.y += 5;
});
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
hero.x -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **ゲームのループを設定します**
window.onload 関数をリファクタリングしてゲームを初期化し、良い間隔でゲームループを設定します。レーザービームも追加します。
```javascript
window.onload = async () => {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
heroImg = await loadTexture("assets/player.png");
enemyImg = await loadTexture("assets/enemyShip.png");
laserImg = await loadTexture("assets/laserRed.png");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. 一定間隔で敵を移動させる**コードを追加します**
関数 `createEnemies()` をリファクタリングして敵を作成し、それを新しい gameObjects クラスにプッシュします。
```javascript
function createEnemies() {
const MONSTER_TOTAL = 5;
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
const STOP_X = START_X + MONSTER_WIDTH;
for (let x = START_X; x < STOP_X; x += 98) {
for (let y = 0; y < 50 * 5; y += 50) {
const enemy = new Enemy(x, y);
enemy.img = enemyImg;
gameObjects.push(enemy);
}
}
}
```
そして `createHero()` 関数を追加して hero にも同様の処理を行います。
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
最後に `drawGameObjects()` 関数を追加して描画を開始します。
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
あなたの敵はあなたのヒーローの宇宙船で前進を開始する必要があります!
---
## 🚀 チャレンジ
ご覧のように、関数や変数、クラスを追加し始めると、あなたのコードは「スパゲッティコード」になってしまうことがあります。コードをより読みやすく整理するにはどうしたらいいでしょうか? 1つのファイルに存在していても、あなたのコードを整理するためのシステムをスケッチしてみましょう。
## レッスン後の小テスト
[レッスン後の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/34)
## 復習と自己学習
フレームワークを使わずにゲームを書いているうちに、JavaScript を使ったゲーム開発用の canvas フレームワークがたくさん出てきました。時間をかけて[これらについて読む](https://github.com/collections/javascript-game-engines)。
## 課題
[コードをコメントする](assignment.ja.md)

@ -0,0 +1,11 @@
# コードをコメントする
## 説明書
ゲームフォルダ内の現在の /app.js ファイルに目を通し、コメントを付けて片付ける方法を見つけてください。コードはいとも簡単に制御不能になります。今はコメントを追加して、後で使えるように読みやすいコードにする良い機会です。
## ルーブリック
| 基準 | 模範的な例 | 適切な | 改善が必要 |
| -------- | ------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------- |
| | `app.js` のコードは完全にコメントされ、論理的なブロックに整理されています。 | `app.js` のコードは適切にコメントされています。 | `app.js` のコードはやや乱れていて、良いコメントがありません。 |

@ -0,0 +1,298 @@
# スペースゲーム構築プロジェクト その 4: レーザーを追加して衝突を検出する
## レッスン前の小テスト
[レッスン前の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/35)
このレッスンでは、JavaScript でレーザーを撃つ方法を学びます! 私たちはゲームに2つのものを追加します。
- **レーザー**: このレーザーはあなたのヒーローの宇宙船から垂直に上向きに撃たれます
- **衝突検出**、発射する能力を実装する一環として、我々はまたいくつかの素敵なゲームのルールを追加します
- **レーザーが敵に当たる**: レーザーが当たると敵が破壊される
- **レーザーがトップ画面に当たる**: 画面上部に当たるとレーザーが破壊されます
- **敵とヒーローの衝突**: 敵とヒーローがぶつかると破壊されます
- **敵が画面の下に当たる**: 敵が画面下に当たると敵とヒーローが破壊されます
要するに、あなた - *ヒーロー* - は画面の下部に移動するために管理する前に、レーザーですべての敵をヒットする必要があります。
✅ これまでに書かれた最初のコンピュータゲームについて少し調べてみてください。その機能は何だったのでしょうか?
一緒にヒーローになりましょう!
## 衝突検出
どのようにして衝突を検出するのか? ゲームオブジェクトを移動する長方形と考える必要があります。なぜでしょうか? ゲームオブジェクトの描画に使われる画像は矩形です。`x`、`y`、`width`、`height` を持っています。
2 つの長方形、つまりヒーローと敵が交差すると、衝突します。そのときに何が起こるかは、ゲームのルール次第です。したがって、衝突検出を実装するためには以下のものが必要です。
1. ゲームオブジェクトの矩形表現を取得する方法。こんな感じです
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
```
2. 比較関数で、この関数は次のようになります
```javascript
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
```
## オブジェクトをどうやって破壊するか
ゲーム内のオブジェクトを破壊するには、一定の間隔で発生するゲームループの中で、このアイテムをペイントしないようにゲームに知らせる必要があります。これを行うには、以下のように、何かが起こったときにゲームオブジェクトを *dead* としてマークする方法があります。
```javascript
// 衝突が発生します
enemy.dead = true
```
その後、画面を再描画する前に、このように、*dead* オブジェクトをソートしていきます。
```javascript
gameObjects = gameObject.filter(go => !go.dead);
```
## レーザーの撃ち方
レーザーを発射することは、キーイベントに反応して、特定の方向に移動するオブジェクトを作成することになります。そのため、以下のような作業を行う必要があります。
1. **レーザーオブジェクトを作成します**: ヒーローの船の上部から、作成時に画面の上部に向かって上向きに移動を開始します
2. **キーイベントにコードを添付します**: レーザーを撮影しているプレイヤーを表すキーボードのキーを選択する必要があります
3. キーを押すと**レーザーのように見えるゲームオブジェクトを作成します**
## レーザーのクールダウン
レーザーは、例えば*スペース*のように、キーを押すたびに発射する必要があります。短時間で多くのレーザーが発射されるのを防ぐために、この問題を解決する必要があります。これを修正するには、いわゆる *クールダウン*、タイマーを実装することで、レーザーはそれほど頻繁にしか発射することができないことを保証します。以下の方法で実装できます。
```javascript
class Cooldown {
constructor(time) {
this.cool = false;
setTimeout(() => {
this.cool = true;
}, time)
}
}
class Weapon {
constructor {
}
fire() {
if (!this.cooldown || this.cooldown.cool) {
// レーザーを作る
this.cooldown = new Cooldown(500);
} else {
// 何もしない-まだクールダウンしていない。
}
}
}
```
✅ スペースゲームシリーズのレッスン1を参照して、*クールダウン*について思い出してください。
## 何を構築するか
前回のレッスンで使用した既存のコード (クリーンアップしてリファクタリングしたはずのコード) を使用して、それを拡張します。
パート II のコードから始めるか、[パートIII - スターター](../../your-work)のコードを使用してください。
> ヒント: 作業に使用するレーザーはすでにアセットフォルダにあり、コードで参照されています。
- **衝突検出を追加します**、レーザーが何かに衝突するときは、次のルールを適用する必要があります
1. **レーザーが敵に当たる**: レーザーが当たると敵が破壊される
2. **レーザーが画面上部に当たる**: レーザーが画面の上部に当たると破壊される
3. **敵とヒーローの衝突**: 敵とヒーローがぶつかると破壊される
4. **画面下に敵が当たる**: 敵が画面下部に当たると敵とヒーローが破壊される
## 推奨される手順
あなたのために作成されたファイルを `your-work` サブフォルダ内で探します。以下のファイルが含まれているはずです。
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| index.html
-| app.js
-| package.json
```
次のコマンドをタイピングして、あなたのプロジェクトを `your_work` フォルダから開始します。
```bash
cd your-work
npm start
```
上記は、`http://localhost:5000` というアドレスで HTTP サーバを起動します。ブラウザを開いてそのアドレスを入力すると、今はヒーローと全ての敵が表示されるはずですが、まだ何も動いていません。
### コードの追加
1. **衝突を処理するためにゲームオブジェクトの矩形表現を設定する** 以下のコードでは、`GameObject` の矩形表現を取得することができます。GameObject クラスを編集して拡張します
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
}
```
2. **衝突をチェックするコードの追加** これは、2 つの矩形が交差するかどうかをテストする新しい関数になります
```javascript
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
```
3. **レーザー発射機能の追加**
1. **キーイベントメッセージを追加します**。*スペース*キーはヒーローの宇宙船の真上にレーザーを作成する必要があります。Messages オブジェクトに3つの定数を追加します
```javascript
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
```
1. **スペースキーを処理します**。スペースを扱うために、`window.addEventListener` のキーアップ関数を編集します
```javascript
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
```
1. **リスナーを追加します**。スペースバーがヒットした時にヒーローが発射できるように `initGame()` 関数を編集します
```javascript
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
```
そして、敵がレーザーに衝突したときの動作を保証するために、新しい `eventEmitter.on()` 関数を追加します
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
})
```
1. **オブジェクトを移動させ**、レーザーが徐々に画面上部に移動するようにします。先ほどと同じように `GameObject`を継承した Laser クラスを新たに作成します
```javascript
class Laser extends GameObject {
constructor(x, y) {
super(x,y);
(this.width = 9), (this.height = 33);
this.type = 'Laser';
this.img = laserImg;
let id = setInterval(() => {
if (this.y > 0) {
this.y -= 15;
} else {
this.dead = true;
clearInterval(id);
}
}, 100)
}
}
```
1. **衝突の処理**、レーザーの衝突ルールの実装 衝突したオブジェクトのヒットをテストする `updateGameObjects()` 関数を追加します
```javascript
function updateGameObjects() {
const enemies = gameObjects.filter(go => go.type === 'Enemy');
const lasers = gameObjects.filter((go) => go.type === "Laser");
// レーザーが当たる
lasers.forEach((l) => {
enemies.forEach((m) => {
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
first: l,
second: m,
});
}
});
});
gameObjects = gameObjects.filter(go => !go.dead);
}
```
必ず `window.onload` のゲームループに `updateGameObjects()` を追加してください。
4. レーザーに**クールダウンを実装**して、それがそんなに頻繁にしか発射できないようにします
最後に、Hero クラスをクールダウンできるように編集します
```javascript
class Hero extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 99), (this.height = 75);
this.type = "Hero";
this.speed = { x: 0, y: 0 };
this.cooldown = 0;
}
fire() {
gameObjects.push(new Laser(this.x + 45, this.y - 10));
this.cooldown = 500;
let id = setInterval(() => {
if (this.cooldown > 0) {
this.cooldown -= 100;
} else {
clearInterval(id);
}
}, 200);
}
canFire() {
return this.cooldown === 0;
}
}
```
この時点で、あなたのゲームはいくつかの機能を持っています! 矢印キーで移動したり、スペースバーでレーザーを発射したり、敵はあなたがそれらを打つときに消えることができます。よくできました!
---
## 🚀 チャレンジ
爆発を追加しましょう! [スペースアートレポ](../../solution/spaceArt/readme.txt)のゲームアセットを見て、レーザーがエイリアンに当たったときに爆発を追加してみてください。
## レッスン後の小テスト
[レッスン後の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/36)
## 復習と自己学習
これまでのゲームの間隔を実験してみてください。それらを変更するとどうなるでしょうか? [JavaScript のタイミングイベント](https://www.freecodecamp.org/news/javascript-timing-events-settimeout-and-setinterval/)についての詳細はこちら。
## 課題
[衝突を調べる](assignment.ja.md)

@ -0,0 +1,11 @@
# 衝突を調べる
## 説明書
衝突がどのように機能するかを理解するために、衝突するいくつかのアイテムで非常に小さなゲームを構築します。キープレスやマウスクリックでアイテムを移動させ、アイテムが当たったときに何かが起こるようにします。隕石が地球に衝突したり、バンパーカーに衝突したりします。創造力を発揮してください。
## ルーブリック
| 基準 | 模範的な例 | 適切な | 改善が必要 |
| -------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | ----------------- |
| | アイテムがキャンバスに描画され、基本的な衝突が発生し、反応が発生する完全な動作コードサンプルが作成されます。 | コードが不完全な場合 | コードの誤動作 |

@ -0,0 +1,190 @@
# スペースゲーム構築プロジェクト その 5: スコアリングとライフ
## レッスン前の小テスト
[レッスン前の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/37)
このレッスンでは、ゲームに得点を加えてライフを計算する方法を学びます。
## 画面上にテキストを描画
ゲームのスコアを画面に表示できるようにするには、画面にテキストを配置する方法を知っておく必要があります。その答えは、canvas オブジェクトの `fillText()` メソッドを使用することです。また、使用するフォント、テキストの色、配置 (左、右、中央) などの他の要素を制御することもできます。以下は、画面上にテキストを描画するコードです。
```javascript
ctx.font = "30px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "right";
ctx.fillText("show this on the screen", 0, 0);
```
✅ [canvas にテキストを追加する方法](https://developer.mozilla.org/ja/docs/Drawing_text_using_a_canvas)について詳しくはこちらをご覧ください。そうすれば自由にファンシーな外観を作ることができます。
## ゲームの概念としてのライフ
ゲームにおけるライフの概念は数字でしかありません。スペースゲームの文脈では、自分の宇宙船がダメージを受けたときに一つずつ差し引かれるライフを割り当てるのが一般的です。これを数字ではなく、ミニシップやハートのようにグラフィカルに表現できるといいですね。
## 何を構築するか
以下のようなものをゲームに追加してみましょう。
- **ゲームのスコア**: 敵艦が破壊されるごとに、ヒーローにはいくつかのポイントが与えられるはずですが、私たちは1隻につき100ポイントをお勧めします。ゲームスコアは左下に表示されます
- **ライフ**: あなたの宇宙船には3つのライフがあります。敵の船が衝突するたびにライフを失います。ライフスコアは右下に表示され、次のグラフィックで作成されます。![life image](../solution/assets/life.png)
## 推奨される手順
あなたのために作成されたファイルを `your-work` サブフォルダ内で探します。以下のファイルが含まれているはずです。
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| index.html
-| app.js
-| package.json
```
次のように入力して、`your_work` フォルダからプロジェクトを起動します。
```bash
cd your-work
npm start
```
上記のようにすると、`http://localhost:5000` というアドレスに HTTP サーバーが起動します。ブラウザを開いてそのアドレスを入力すると、ヒーローと敵が表示され、左右の矢印を打つとヒーローが動き、敵を撃ち落とすことができます。
### コードの追加
1. `solution/assets/` フォルダから `your-work` フォルダに**必要なアセットをコピー**して、`life.png` アセットを追加します。lifeImg を window.onload 関数に追加します
```javascript
lifeImg = await loadTexture("assets/life.png");
```
1. アセットのリストに `lifeImg` を追加します
```javascript
let heroImg,
...
lifeImg,
...
eventEmitter = new EventEmitter();
```
2. **変数を追加します**。あなたの合計スコア (0) とその左に (3) のライフを表すコードを追加し、画面上にこれらのスコアを表示します
3. **`updateGameObjects()` 関数を拡張します**。`updateGameObjects()` 関数を拡張し、敵の衝突に対応するようにします
```javascript
enemies.forEach(enemy => {
const heroRect = hero.rectFromGameObject();
if (intersectRect(heroRect, enemy.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_HERO, { enemy });
}
})
```
4. **`life``points` を追加します**
1. **変数を初期化します**。`Hero` クラスの `this.cooldown = 0` の下で、ライフとポイントを設定します
```javascript
this.life = 3;
this.points = 0;
```
1. **画面上に変数を描画します**。これらの値を画面に描画します
```javascript
function drawLife() {
// TODO, 35, 27
const START_POS = canvas.width - 180;
for(let i=0; i < hero.life; i++ ) {
ctx.drawImage(
lifeImg,
START_POS + (45 * (i+1) ),
canvas.height - 37);
}
}
function drawPoints() {
ctx.font = "30px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "left";
drawText("Points: " + hero.points, 10, canvas.height-20);
}
function drawText(message, x, y) {
ctx.fillText(message, x, y);
}
```
1. **Game ループにメソッドを追加します**。`updateGameObjects()` の下の window.onload 関数にこれらの関数を追加します
```javascript
drawPoints();
drawLife();
```
1. **ゲームのルールを実装します**。以下のゲームルールを実装します
1. **ヒーローと敵の衝突ごとに**、ライフを差し引きます
`Hero` クラスを拡張してこの差し引きを行います
```javascript
decrementLife() {
this.life--;
if (this.life === 0) {
this.dead = true;
}
}
```
2. **レーザーが敵に当たるたびに**、ゲームスコアを100点アップさせます
このインクリメントを行うために Hero クラスを拡張します
```javascript
incrementPoints() {
this.points += 100;
}
```
これらの機能を衝突イベントエミッタに追加します
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
})
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
});
```
✅ JavaScript/Canvas を使用して作成された他のゲームを少し調べてみてください。これらのゲームに共通する特徴は何ですか?
この作品が終わる頃には、右下に小さな「ライフ」の船、左下にポイントが表示され、敵と衝突するとライフカウントが減少し、敵を撃つとポイントが増加するのがわかるはずです。よくできました! これでゲームはほぼ完成です。
---
## 🚀 チャレンジ
あなたのコードはほぼ完成しています。次のステップをイメージできますか?
## レッスン後の小テスト
[レッスン後の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/38)
## 復習と自己学習
ゲームのスコアやライフを増減させる方法をいくつか研究してみましょう。[PlayFab](https://playfab.com) のような面白いゲームエンジンがあります。これらのエンジンを使用することで、どのようにゲームを強化することができるでしょうか?
## 課題
[スコアリングゲームの構築](assignment.ja.md)

@ -0,0 +1,11 @@
# スコアリングゲームの構築
## 説明書
ライフとポイントを独創的な方法で表示するゲームを作成します。提案としては、ライフをハートで表示し、ポイントを画面中央下部の大きな数字で表示することです。[無料のゲームリソース](https://www.kenney.nl/)はこちらをご覧ください。
# ルーブリック
| 基準 | 模範的な例 | 適切な | 改善が必要 |
| -------- | ---------------------- | --------------------------- | -------------------------- |
| | フルゲームが提示されています | 部分的に提示されたゲーム | 部分的なゲームにはバグが含まれています |

@ -0,0 +1,223 @@
# スペースゲーム構築プロジェクト その 6: 終了と再起動
## レッスン前の小テスト
[レッスン前の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/39)
ゲーム内での表現方法や *終了条件* の表現方法は様々です。なぜゲームが終了したのかは、ゲームを作る側のあなた次第です。ここでは、あなたが今まで作ってきた宇宙ゲームの話をしていると仮定して、いくつかの理由を挙げてみましょう。
- **`N` 隻の敵の宇宙船を撃破しました**: それはあなたがレベルを完了するために N 隻の敵の宇宙船を破壊する必要があることを別のレベルにゲームを分割する場合はかなり一般的です
- **あなたの船が破壊されました**: 自分の船が破壊されるとゲームに負けるゲームは間違いなくあります。もう一つの一般的なアプローチは、ライフの概念を持っているということです。あなたの宇宙船が破壊されるたびに、それはライフを差し引きます。すべてのライフが失われると、ゲームを失うことになります
- **`N` ポイントを集めました**: もう一つの一般的な終了条件は、ポイントを集めることです。どのようにポイントを獲得するかはあなた次第ですが、敵の宇宙船を破壊したり、破壊された時にドロップするアイテムを集めたりと、様々な活動にポイントを割り当てるのが一般的です
- **レベルをクリアしました**: これには、`X` 隻の敵の宇宙船を破壊したり、`Y` ポイントを集めたり、特定のアイテムを集めたりするなど、いくつかの条件が含まれている場合があります
## 再起動
人々があなたのゲームを楽しめば、彼らはそれを再プレイしたいと思う可能性が高いです。何らかの理由でゲームが終了したら、再起動するための代替手段を提供すべきです。
✅ どのような条件でゲームが終了したか、そしてどのように再起動を促されるかを少し考えてみてください。
## 何を構築するか
これらのルールをゲームに追加していくことになります。
1. **ゲームの勝利**。全ての敵の宇宙船を撃破したらゲームの勝利です。さらに、何らかの勝利メッセージを表示します
1. **再起動すること**。全てのライフが失われたり、ゲームに勝利したら、ゲームを再起動する方法を提供する必要があります。覚えておいてください! ゲームを再初期化する必要があり、以前のゲームの状態をクリアする必要があります
## 推奨される手順
あなたのために作成されたファイルを `your-work` サブフォルダ内で探します。以下のファイルが含まれているはずです。
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| life.png
-| index.html
-| app.js
-| package.json
```
次のように入力して、`your_work` フォルダからプロジェクトを起動します。
```bash
cd your-work
npm start
```
上記のようにすると、`http://localhost:5000` というアドレスに HTTP サーバーが起動します。ブラウザを開いて、そのアドレスを入力してください。ゲームがプレイ可能な状態になっているはずです。
> ヒント: Visual Studio Code の警告を避けるためには、`window.onload` 関数を編集して `gameLoopId` をそのまま (`let` を省略して) 呼び出し、ファイルの先頭に gameLoopId を `let gameLoopId;` と独立して宣言します。
### コードの追加
1. **終了条件を追跡します**。敵の数を追跡したり、ヒーローの宇宙船が破壊されたかどうかを追跡するコードを追加します
```javascript
function isHeroDead() {
return hero.life <= 0;
}
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
}
```
1. **メッセージハンドラにロジックを追加します**。これらの条件を処理するために `eventEmitter` を編集します
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
if (isHeroDead()) {
eventEmitter.emit(Messages.GAME_END_LOSS);
return; // 勝利前に敗北
}
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.GAME_END_WIN, () => {
endGame(true);
});
eventEmitter.on(Messages.GAME_END_LOSS, () => {
endGame(false);
});
```
1. **新しいメッセージタイプを追加します**。これらのメッセージを定数オブジェクトに追加します
```javascript
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
```
2. 選択したボタンの押下でゲームを再起動する**再起動コードを追加します**
1. **`Enter` キーの押下を待ち受けます**。この押下を待ち受けるために、ウィンドウの eventListener を編集します
```javascript
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
```
1. **再起動メッセージを追加します**。このメッセージをメッセージ定数に追加します
```javascript
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
```
1. **ゲームルールの実装** 以下のゲームルールを実装します
1. **プレイヤーの勝利条件**。敵の宇宙船を全て撃破した場合、勝利のメッセージを表示します
1. まず、関数 `displayMessage()` を作成します
```javascript
function displayMessage(message, color = "red") {
ctx.font = "30px Arial";
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
```
1. 関数 `endGame()` を作成します
```javascript
function endGame(win) {
clearInterval(gameLoopId);
// 塗り終わったことを確認するために遅延を設定します
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (win) {
displayMessage(
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
"green"
);
} else {
displayMessage(
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
);
}
}, 200)
}
```
1. **再起動ロジック**。すべてのライフが失われたとき、またはプレイヤーが勝ったときに、ゲームを再起動できることを表示します。さらに、リスタートキーが押されるとゲームを再起動します (どのキーをリスタートにマッピングするかはあなたが決めることができます)
1. 関数 `resetGame()` を作成します
```javascript
function resetGame() {
if (gameLoopId) {
clearInterval(gameLoopId);
eventEmitter.clear();
initGame();
gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawPoints();
drawLife();
updateGameObjects();
drawGameObjects(ctx);
}, 100);
}
}
```
1. `initGame()` でゲームをリセットするために `eventEmitter` の呼び出しを追加します
```javascript
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
```
1. EventEmitter に `clear()` 関数を追加します
```javascript
clear() {
this.listeners = {};
}
```
👽 💥 🚀 おめでとうございます、隊長! あなたのゲームは完成しました! よくできました! 🚀 💥 👽
---
## 🚀 チャレンジ
音を追加しましょう!レーザーが当たった時や、ヒーローが死んだ時、勝った時など、ゲームを盛り上げるために音を追加することはできますか? JavaScript を使ってサウンドを再生する方法については、こちらの [sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play) をご覧ください。
## レッスン後の小テスト
[レッスン後の小テスト](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/40)
## 復習と自己学習
あなたの課題は新鮮なサンプルゲームを作成することです。だから、どんなゲームを作れそうかを確認するために、そこにある面白いゲームのいくつかを探索してください。
## 課題
[サンプルゲームを作る](assignment.ja.md)

@ -0,0 +1,19 @@
# サンプルゲームを作る
## インストラクション
異なる終了条件で練習する小さなゲームを構築してみてください。ポイントの数を取得する間に変化し、主人公はすべてのライフを失うか、またはすべてのモンスターが退治されてしています。コンソールベースのアドベンチャーゲームのようなシンプルなものを作ってみてください。インスピレーションとして以下のゲームの流れを使用してください。
```
Hero> Strikes with broadsword - orc takes 3p damage
Orc> Hits with club - hero takes 2p damage
Hero> Kicks - orc takes 1p damage
Game> Orc is defeated - Hero collects 2 coins
Game> ****No more monsters, you have conquered the evil fortress****
```
## ルーブリック
| 基準 | 模範的な例 | 適切な | 改善が必要 |
| -------- | ---------------------- | --------------------------- | -------------------------- |
| | フルゲームが提示されています | 部分的に提示されたゲーム | 部分的なゲームにはバグが含まれています |

@ -0,0 +1,31 @@
# スペースゲームの構築
より高度な JavaScript の基礎を教えるスペースゲーム
このレッスンでは、あなた自身のスペースゲームを作る方法を学びます。「スペースインベーダー」というゲームをプレイしたことがある方は、このゲームも同じように、宇宙船を操縦して、上から降ってくるモンスターを攻撃するというものです。完成したゲームはこんな感じです。
![Finished game](../images/pewpew.gif)
この6つのレッスンでは、次のことを学びます。
- Canvas 要素と**相互にやりとりして**画面上に物を描く
- 直交座標系を**理解する**
- Pub-Sub パターンを**学び**、メンテナンスや拡張が容易なサウンドゲームアーキテクチャを作成できるようになる
- Async/Await を**活用して**ゲームリソースをロードする
- キーボードイベントを**処理する**
## 概要
- 理論
- [JavaScript を使ったゲーム構築入門](../1-introduction/translations/README.ja.md)
- 実践
- [キャンバスへの描画](../2-drawing-to-canvas/translations/README.ja.md)
- [画面の周りの要素の移動](../3-moving-elements-around/translations/README.ja.md)
- [衝突の検出](../4-collision-detection/translations/README.ja.md)
- [スコアの保持](../5-keeping-score/translations/README.ja.md)
- [ゲームの終了と再開](../6-end-condition/translations/README.ja.md)
## クレジット
このために使用したアセットは https://www.kenney.nl/ から入手しました。
ゲームを作るのが好きなら、これらのアセットはとても良いもので、多くは無料で、一部有料のものがあります。
Loading…
Cancel
Save