はじめに
皆さんこんにちは。y_masuyamaと申します。
最近、Reactを学び始めたばかりですが、何かアプリを作ってみたいという思いが強くなってきました。
そこで、昔懐かし電流イライラ棒系びっくりFlashゲームを再現してみました。
作成したもの
- ソースコード(StackBlitzで公開しています)
- 📱スマホでは遊べません(💻PCでお楽しみください)
- 🖥️ブラウザ全画面表示でのみ遊べます
- ゲーム推奨画面サイズは1920 × 1080です(Canvasサイズを固定値1280 × 720で設定してるため)
- 🎵音や驚き要素が含まれているのでご注意ください
- 📚筆者環境(Mac + Chrome)でのみ動作確認をしています
ゲーム内容
全3面の「電流イライラ棒」系のゲームです。
3面の細い迷路を超える際に驚きの画像と音が表示されます。
使用技術
使用技術 | 説明 |
---|---|
StackBlitz | Reactの動作環境として使用しています(コンソールを見る限り、環境はViteっぽいです)。 |
HTML, CSS | 迷路部分はcanvas タグで実装しています。 |
React | JavaScriptのライブラリです。 |
TypeScript | JavaScriptのスーパーセット言語です。 |
howler.js | 音声出力ライブラリです。 |
* ReactやTypeScriptなどのバージョンについてはソースコードのpackage.jsonに記載してますのでこちらご確認ください。
コードの解説
簡単にコードの解説を行います。
なお、ここでは大幅に省略しているので、詳細は上記リンクのソースコードを参照してください。
ゲーム画面の遷移
ReactのuseStateフックを使って実装しています。
App.tsx
に以下のようなstate
とsetState
関数を作成し、ステートが変わることで画面が切り替わるようにしています。
state
const [scene, setScene] = useState<'title' | 'game' | 'ending'>('title');
stateのset関数
const gameStart = () => setScene('game');
const gameOver = () => setScene('title');
const viewEnding = () => setScene('ending');
タイトル画面
Playボタンを押すとgameStart関数が発火し、迷路ゲーム画面が表示されるように実装しています。
export const Title = ({ handleClick }: Props) => {
return (
<!-- 省略 -->
<button onClick={handleClick} className="title__button">
<span className="title__buttonText">play</span>
</button>
);
};
迷路ゲーム画面
以下のコンポーネントで実装しています。
- Mazeコンポーネント
- 迷路切り替え用のコンポーネント
- Level1, Level2, Level3コンポーネント
- 迷路ゲームのコンポーネントで、ステージごとに存在します
迷路の切り替え
タイトル画面と同じようにReactのuseStateフックを使って実装しています。
Maze.tsxに以下のようなstateを作成し、ステートが変わることで画面が切り替わるようにしています。
state
const [level, setLevel] = useState(1);
迷路の描画
迷路はcanvas要素で描画してます。
Canvas要素の取得
canvas要素のメソッドを使いたいため、React公式ドキュメント(ref で DOM を操作する)を参考にcanvasのDOMを取得しております。
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const ctx = canvasRef.current?.getContext('2d');
// canvasがなければ早期return
if (!ctx) return;
// 以降canvasを使いたい場合の処理
}, []);
useEffectフックはReact外部のシステムと同期させるフックで、上記のようにDOMを操作したい場合も使えます。
また、上記で取得しているgetContext('2d')はcanvas要素の2次元の描画コンテキストオブジェクトを作成するメソッドです。
Canvas要素での画面描画
迷路の作成はcanvasのfillRectメソッドを使って描画してます。
const ctx = canvasRef.current?.getContext('2d');
// canvasがなければ早期return
if (!ctx) return;
// 背景部分
ctx.fillStyle = 'black'; // 色の指定
ctx.fillRect(0, 0, width, height); // x, y, 横幅, 縦幅を指定して染め上げる
迷路の描画の部分に関してあまり上手いやり方ではないのですが長方形を重ねることで迷路を作成してます。
// 迷路部分
ctx.fillStyle = 'aqua'; // 色の指定
ctx.fillRect(500, 40, 610, 60); // x, y, 横幅, 縦幅を指定して染め上げる
ctx.fillRect(500, 40, 300, 670); // x, y, 横幅, 縦幅を指定して染め上げる
// ゴール部分
ctx.fillStyle = 'red'; // 色の指定
ctx.fillRect(1050, 40, 80, 60); // x, y, 横幅, 縦幅を指定して染め上げる
また文字の挿入はfillTextメソッドで実装してます。
// 文字追加
ctx.font = 'bold 24px verdana, sans-serif ';
const welcomeMessage = 'Level1';
ctx.textAlign = 'start';
ctx.textBaseline = 'bottom';
ctx.fillStyle = 'white';
ctx.fillText(welcomeMessage, 1050, 130);
迷路にロジックを追加
迷路が描画できたら、次にゲームオーバーとクリアの判定ロジックを追加します。
ロジックの仕組みはシンプルで、以下の流れで実装しています。
- マウスポインタの位置を取得
- 取得した位置にあるcanvas要素の色を取得
- 取得した色に応じて処理を追加
- 黒ならゲームオーバー
- 赤ならクリア
マウスポインタの位置を取得
マウスの動き自体はpointermoveイベントで取得してます。
Reactで元のブラウザイベントはnativeEventで取得できるので、canvas内でのポインターの位置は以下のコードで取得できます。
<canvas
onPointerMove={(e) => {
// マウスポインタの位置取得
const x = e.nativeEvent.offsetX;
const y = e.nativeEvent.offsetY;
}
/>
今回のゲームでは実装してないのですが、pointermoveイベントは頻繁に発生するイベントになります。
複雑な処理を追加する場合、処理頻度を制御(スロットリング)するなどパフォーマンス対策が必要です。
取得した位置にあるcanvas要素の色を取得
ポインターの位置が分かればcanvasでその箇所のイメージデータをgetImageDataメソッドで取得できます。
1ピクセル分取り出したいので以下のコードとなります。
const [r, g, b] = ctx.getImageData(ポインタのx座標, ポインタのy座標, 1, 1).data;
取得した色に応じて処理を追加
上記で取得した色情報に応じて、処理を追加しています。
ゲームオーバーの処理は以下のように実装します。
if (r === 0 && g === 0 && b === 0) {
gameOver();
}
また、ゴールの処理は以下のように実装します。
if (r === 255 && g === 0 && b === 0) {
goNextLevel();
}
最後の迷路だけびっくり要素(見えないゴール)を追加
最後の迷路だけ他とは違い見えないゴールを設置する必要があります。
ちょっとここの実装は無理矢理にはなりますがcanvasの上部に見えないDOM(cssでz-indexを調整)を置くことで実現してます。
<>
// 見えないDOM
<div
className="gameOver"
onMouseEnter={() => {
viewEnding();
}}
></div>
<canvas
onPointerMove={(e) => {
const ctx = canvasRef.current?.getContext('2d');
if (!ctx) return;
const x = e.nativeEvent.offsetX;
const y = e.nativeEvent.offsetY;
const [r, g, b] = ctx.getImageData(x, y, 1, 1).data;
if (r === 0 && g === 0 && b === 0) {
gameOver();
}
}}
ref={canvasRef}
width={width}
height={height}
/>
</>
エンディング画面
エンディング画面はhowler.jsで音声が鳴るように実装してます。
// セットアップ
const sound = new Howl({
src: ['./sound.wav'],
});
// 音声の再生
sound.play();
// 音量の変更
Howler.volume(1);
終わりに
今回はReactとcanvasを使って昔懐かしのFlashゲームを再現しました。
技術的にはシンプルですが、Reactの基本を学ぶアプリとしては良いものになったのかと思います。
最後までお読みいただき、ありがとうございました!
おまけ:作成したゲームの元ネタ
以下の動画が元ネタとなっています。(びっくり要素が含まれているのでご注意ください)
また、上記のFlashゲームを体験した男性の動画もあります。
株式会社BTMではエンジニアの採用をしております。
ご興味がある方はぜひコチラをご覧ください