あーなたーの燃える手で〜私をだ〜きしめて〜(あいさつ)
こんにちは。おれです。
みなさん屁はこいていますか、おれはこいています。
ということで、おじさんがへでとぶゲーム「へとびおじ」というゲームをつくりました。
こちらからあそべます → へとびおじ
ありがたいことに記事にしていただいたりして、いかついテック系のニュースに屁で飛ぶおじさんが紛れ込む珍事となりました。
「屁で飛ぶ」ゲームが面白い:NEWS Weekly Top10 - ITmedia NEWS
ざっくりどんな感じで作っていたのか書いていきます。
概要
- 開発期間 : 1ヶ月ほど
- つかった技術 : Phaser3.55.2, Tiled, TypeScript
企画
以前からなんかはちゃめちゃに難しいゲームつくりたいな〜と常々考えておりました。
当初はふつうにおじさんがジャンプして進んでいくようなものを想定していましたが、屁をこいて飛んだほうが憎たらしくていいだろうということで、へをこいて飛ぶおじさんのゲームをつくることを決めました。
決めたからには、つくるしかないのです。
Phaser
なにでつくろうか色々迷ったんですが、javascriptのゲームエンジンPhaserを使うことにしました。ファイルサイズがエンジン部分で1MBぐらいなので、Unityとかからwasmをビルドするのに比べてサイズが小さく済むのがjsエンジンの長所です。
だいたい開発するときというのは環境をつくるのが一番おっくうです。 Phaser + TypeScriptの環境がすぐ作れるイカしたテンプレートを見つけたので、これを使うことにしました。
yandeu/phaser-project-template: 🕹️ Phaser 3 - Starter Template with TypeScript and webpack.
Tiled
Tiledは2Dゲームのマップをつくるときに使うツールです。
こんな具合で、1マスずつタイル画像を置いていくことで、ゲームのマップをつくることができます。 Tiledの存在は知っていましたが使ったことはありませんでした。今回いっちょやったろかいという気になり、やりました。
Tiledの作業
おおまかな作業の流れは
- タイル用の画像を作り、Tiledに読み込む
- 読み込んだタイル画像で、マップを書いていく
- 書いたマップ情報をJson形式で書き出して、phaserで読むこむ
といった感じです。 Tiledエディタで保存すると同時にJsonに書き出す設定もあるので、これを設定しておくとマップ描いてすぐ動かせるのでデバッグが楽ですね。
マップ以外のオブジェクトの設置
Tiled使う前に気になっていたことですが、地面などのタイルはマップに描くことができても、アイテムとか、コインとか、敵キャラとか、マップではないもの配置したい時はどうするの〜?ふえ〜ん。ピー。と、思っていました。
それを解決する機能がこれです。
Tiledはタイル以外にオブジェクトというものを好きな位置に置くことができます。やりたいことがこれで大体実装できます。
上の画像はCheckPointという名前のオブジェクトを置いているところです。名前は自分でつけることができます。 おじさんがここを通ると、チェックポイント通過という扱いになり、おじさんが死んだときにここへ戻ってこれます。
このようにTiledをオブジェクトを配置して、そしてPhaser側のプログラムでこれを解釈します。
以下がチェックポイントを配置している部分のプログラムです。
//チェックポイント
this.checkPoints = this.map.filterObjects('CheckPoints', obj => obj.name === 'CheckPoint');
//x座標で昇順にソート
this.checkPoints.sort((a, b) => a.x - b.x);
for(let i = 0; i < this.checkPoints.length; i++){
if(this.config.playerType == PlayerType.GOLD) continue;
const checkPoint = this.physics.add.sprite(0, 0, 'goal');
checkPoint.setScale(2);
checkPoint.body.allowGravity = false;
checkPoint.setOrigin(0.5, 0.96);
checkPoint.setPosition(this.checkPoints[i].x!, this.checkPoints[i].y!);
}
いろいろ端折りますが、this.map にはTiledから書き出したデータが入っていると思ってください
this.map = this.make.tilemap({ key: 'map' });
this.mapからCheckPointの座標を取得して、これを元にゲームに配置するような感じですね。
座標さえ取得してしまえば、あとはプログラムで好きになように使えるので、アイテムでもコインでも敵キャラだろうと、いかようにも配置できるというわけです。
タイルマップ画像
ちなみに、タイルマップに使う画像は以下から使わせてもらいました。
itch.io. ゲームだけじゃなくて、フリー画像素材もいっぱいあるんですね。すげえや。
への効果音
への効果音は、当初ラッパとか楽器の音とかにしようかな〜と思っていました。 しかしこのサイトと出会ったことで、すべての歯車が狂い出しました。
おなら効果音フリー素材サイト - Onara Sound MP3
なんとおなら専門の効果音サイトです。 これはもう使うしかないということになり、非常にリアルなサウンドが完成いたしました。 本当にありがとうございます。
このようにおならをいっぱい読み込んで
this.onaraSounds = [
scene.sound.add('onara1'),
scene.sound.add('onara2'),
scene.sound.add('onara3'),
scene.sound.add('onara4'),
scene.sound.add('onara5'),
scene.sound.add('onara6'),
scene.sound.add('onara7'),
scene.sound.add('onara8'),
scene.sound.add('onara9'),
scene.sound.add('onara10'),
scene.sound.add('onara11')
]
屁のパワー + ランダムで、音のピッチを変えることで、バリエーション豊かなおならサウンドを出すことに成功しました
playSE(rate : number){
let randomRate = Phaser.Math.FloatBetween(1.3, 0.8); // ランダムな再生速度を設定
randomRate = randomRate * (1 - rate) + (randomRate * 0.5);
const randomSound = Phaser.Math.RND.pick(this.onaraSounds);
randomSound.play({ rate: randomRate , volume: this.seVolume});
}
おならエフェクト
へをこくゲームなので、おならにはこだわりたいわけです。
そこでおなら専用のクラスを用意して実装していました。 こんな感じです。
export default class Onara extends Phaser.GameObjects.Container {
parent: Phaser.GameObjects.Container;
parentSprite: Phaser.GameObjects.Sprite;
onara: Phaser.GameObjects.Particles.ParticleEmitterManager;
onaraEmitter;
onaraRoll: Phaser.GameObjects.Particles.ParticleEmitterManager;
onaraRollEmitter;
startOnara: Phaser.GameObjects.Particles.ParticleEmitterManager;
startOnaraEmitter;
onaraRespawn: Phaser.GameObjects.Particles.ParticleEmitterManager;
onaraRespawnEmitter;
hakaseOnara: Phaser.GameObjects.Particles.ParticleEmitterManager;
//略
おならはPhaserのParticle機能を使って実装しました。 Particleというのは、煙とか火とか、細かい絵をたくさん出すときに使う機能ですね。これでおならをたくさん出すというわけです。
以下は発射する瞬間のおならの例
this.startOnara = scene.add.particles(spriteKey);
this.startOnaraEmitter = this.startOnara.createEmitter({
speed: 140,
scale: { start: 0.05, end: 0.6},
angle: { min: 40, max: 200 },
rotate: { start: 50, end: 120 },
lifespan: 600,
frequency : 13,
alpha : { start: 1, end: 0},
accelerationX: -10, // X軸方向に負の加速度を与える
accelerationY: -10,
})
this.startOnaraEmitter.startFollow(this.parent,0,10);
this.startOnaraEmitter.stop();
createEmitterというので、おならの数や大きさ、角度、透明度、スピードなどを設定していきます。いい感じのおならが出るまで、これを何度も設定し、幾たびもおならを見ていくことで、正気を失っていきます。
レベルデザイン
ほかのプログラムとかはなんかまあふつうにつくったので特筆することはないのですが やはり一番時間がかかったのはマップをつくる作業ですね。
ほとんどの開発期間はこれです。 ずーーーっと描いてた。マップ描いて、動かして試して、描いて、試して、屁こいて、描いて、試すの繰り返し。 描きまくっているのですが、もうずっとわからない。正解がわからない。むずかしい。ゲームの核の部分なんですが、もう全然わからない。ミーーーッ!!ちょうむずかしい。
と思いながら、やってたら、実際にプレイするのも超難しいゲームになりました。
キャラ選択
あまりにも難しいゲームになってしまったため、キャラクターを増やして、比較的簡単にクリアできる難易度のキャラを追加しました。
プログラム的には、プレイヤーが増えたのでインターフェースというのを作って、 プレイヤー共通の項目をまとめたりしました。
共通の項目をつくることで、相手が誰だろうが機能を呼び出すことができるという寸法です。
import { PlayerState } from "../constant/playerState";
import { PlayerType } from "../constant/playerType";
export interface IPlayer extends Phaser.GameObjects.Container {
state: PlayerState;
type: PlayerType;
body: Phaser.Physics.Arcade.Body;
sprite: Phaser.GameObjects.Sprite;
jump(): void;
chargeStart(): void;
update(time: number, delta: number): void;
respawn(x: number, y: number): void;
Miss(): void;
}
おわり
こんな感じです!!! なにかの参考になれば幸いです。
よければ遊んでやってください。 へとびおじ