こちらは、2023年2月7日に公開された以下のドキュメントを翻訳したものとなります。
How to make your HTML5 Games Awesome!
ビデオゲームのクオリティは、しばしば「どれだけ洗練されているか」で決まります。ディテールへのこだわりや仕上げの工夫によって、ゲームがより素晴らしいものになります。この記事ではゲーム開発でのブラッシュアップの重要性や、ゲーム体験全体をどれくらい向上できるかについて解説します。
PlayCanvasで開発されたシンプルなアステロイドゲーム「Space Rocks!」を例に、わずかなディテールでも大きなインパクトを与えられる点を紹介します。
ゲームジュースとは、ゲームのより快適なプレイのために追加される小さな視覚/聴覚効果を指すデザイン用語です。画面揺れやパーティクルエフェクト、プレイヤーが特定のアクションを起こしたときに発生するサウンドエフェクトなどがこれに該当します。ゲームジュースはゲーム全体の雰囲気を高め、より没入感のある楽しいゲームに仕上げるためのものです。
この記事では特に、ゲームをどのようにゲームジュースでブラッシュアップできるかを見ていきます。
スタート地点
これがゲームジュースを入れる前のスタート地点でした。ゲームの機能は十分で遊びごたえもあるのですが視覚的/聴覚的な効果が少なく、真にプレイヤーを惹きつけられるようなゲームにはなっていません。結果として、少し退屈で面白味に欠ける印象になっています。
しかし、ディテールに気を配ってゲームジュースを慎重に実装していけば、このシンプルなアステロイドゲームをより面白く満足のいくものに変えることができます。
どうすれば改善できるか?
ゲームジュースに含める項目は、そのゲームで最も多いインタラクションやコアメカニクスに絞り込むようにします。
- シューティング
- 小惑星の破壊
- 小惑星との衝突
上記3点に留意し、どうすればこのゲームを改善できるか考えてみましょう。
シューティング
このままではあまり面白くありません:
改善できる点はたくさんあります。発射のクールダウンを簡単に減らせるスクリプトによって、発射速度を上げることができます。
Gun.attributes.add('cooldown', {
type: 'number',
default: 0.25,
title: 'Cooldown',
description: 'How long the gun has to wait between firing each bullet'
});
Gun.prototype.update = function (dt) {
this._cooldownTimer -= dt;
if (this.app.mouse.isPressed(pc.MOUSEBUTTON_LEFT) && this.canFire()) {
this.fireBullet();
}
};
それでは、もっと予測不可能なシューティングにしましょう。シューティングにさらに広がりを持たせます!
Gun.attributes.add('spread', {
type: 'number',
default: 10,
title: 'Bullet Spread',
description: 'Up to how many degrees each bullet should vary in Y rotation.'
});
Gun.prototype.applySpreadOn = function (bullet) {
var rotation = this.entity.getEulerAngles();
rotation.y += getRandomDeviation(this.spread);
bullet.setEulerAngles(rotation);
};
簡単ですが、インパクトのある変更ですね!試しに入れた値では、以下のようになります:
いろいろな値を試してみて、どの値でのプレイが楽しいか確認してください!
よくなってきてはいますが、まだ改善点はありますね。今度はビジュアル面を考えてみましょう。効果的なビジュアルにするにはどんなことができるでしょうか?
PlayCanvas には、パフォーマンスにほとんど影響を与えずにシーンに大量のライトを配置することができる素晴らしい機能があります!これはクラスターライティングと呼ばれています。
この素晴らしい技術を活用すれば、すべての弾丸に点光源を設定できます。
さらに、弾丸が何かに当たったらキラキラと光るようにしましょう。ただ弾丸が消えるだけでなく視覚的な効果が加わり大きな違いが生まれます。粒子の爆発はやはり素晴らしいものです。
素晴らしいですね!弾丸はかなり良い感じになりました。でも、まだ小惑星を撃つ部分がつまらないですね。変更してみましょう。
小惑星の破壊
まず、小惑星を背景よりも目立たせます。背景のテクスチャをもう少し明るくしてみましょう。
はるかに良いですね!でも、小惑星自体をもっと美しくできないでしょうか?現状ではかなり低解像度のテクスチャでマッピングされています。しかも、異なるのは回転方法のみなのでどれも同じように見え、バリエーションがありません。
小惑星のメッシュとテクスチャを新規にインポートしてみましょう。
良くなりました!さらに、生成される小惑星の大きさをランダム化する簡単なコンポーネントを追加しました。
var ScaleRandomizer = pc.createScript('scaleRandomizer');
ScaleRandomizer.attributes.add('baseScale', {
type: 'number',
title: 'Base Scale',
description: 'The base scale to deviate from'
});
ScaleRandomizer.attributes.add('scaleDeviation', {
type: 'number',
title: 'Scale Deviation',
description: 'The amount by which the effective scale should deviate from the base scale'
});
// initialize code called once per entity
ScaleRandomizer.prototype.initialize = function () {
this.entity.setLocalScale(this.getRandomScale());
};
ScaleRandomizer.prototype.getRandomScale = function () {
var deviation = getRandomFloatDeviation(this.scaleDeviation, 3);
var randomScale = this.baseScale + deviation;
return new pc.Vec3(randomScale, randomScale, randomScale);
};
素晴らしいですね、小惑星がよりきれいに見えるようになりました。次に、背景が静止しているように見えるので改善しましょう。
- 背景に小惑星を追加して、躍動感を出せないでしょうか?
- 小惑星を破壊するときに破片を残し、さらにその破片を破壊できないでしょうか?
- 現在、このゲームは時間経過とともに難易度が増しています。たとえば背景のテクスチャを変更するなど、難易度を視覚的に表現できないでしょうか?
それでは、これらのアイディアを実践してみましょう!
背景の小惑星には小惑星スポナークラスを再利用し、スポーンポイントを少し下に移動しました。
出来る限りパフォーマンスに影響を与えないようテンプレートを複製してFakeAsteroidにリネームし、MoverとRotatorコンポーネント以外のすべてのコンポーネントを削除しました。
これは、コンポーネントベースのアーキテクチャを使用するメリットの1つです。コードを書いたり修正したりすることなく、オブジェクトの挙動を素早く変更できます!
またFakeAsteroidのテクスチャをかなり暗くし、プレイヤーの気を逸らさないようにしました。
「フラグメント」小惑星は通常の小惑星よりもかなり小さくし、通常の小惑星が破壊された際にはフラグメントを生成するコンポーネントを設定した点のみが異なります。
FragmentSpawner.attributes.add('minMaxCount', {
type: 'vec2',
title: 'Min, Max Count',
description: 'The minimum and maximum amount of fragments to spawn.'
});
FragmentSpawner.prototype.spawnFragments = function () {
if (FragmentSpawner.fragmentParent === null) {
return;
}
var spawnCount = getRandomInRange(this.minMaxCount.x, this.minMaxCount.y);
for (i = 0; i < spawnCount; i++) {
this.spawnSingleFragment();
}
};
FragmentSpawner.prototype.spawnSingleFragment = function () {
var fragment = this.fragmentTemplate.resource.instantiate();
fragment.reparent(FragmentSpawner.fragmentParent);
var position = this.getFragmentPosition();
fragment.setPosition(position);
};
それでは、小惑星が破壊された際のパフや小粒子を追加してはどうでしょうか?
ネットで複数のテクスチャを収集し、弾丸のヒットパーティクルエフェクトを複製して修正しました。パーティクル効果のスポーンには、弾丸で使用したものと同じコンポーネントを使用しました:
// A script that spawns a particle effect on death
var DeathEffect = pc.createScript('deathEffect');
DeathEffect.attributes.add('particleEffects', {
type: 'asset',
assetType: 'template',
array: true,
title: 'Particle Effect Templates',
});
DeathEffect.effectParent = null;
// initialize code called once per entity
DeathEffect.prototype.initialize = function () {
this.entity.on('destroy', this.onDestroy, this);
if (!DeathEffect.effectParent) {
DeathEffect.effectParent = this.entity.parent;
}
};
DeathEffect.prototype.onDestroy = function () {
for (var i = 0; i < this.particleEffects.length; i++) {
var effect = this.particleEffects[i].resource.instantiate();
effect.setPosition(this.entity.getPosition());
effect.reparent(effectParent);
}
};
最後に、背景には青い空間のマテリアルの透明度をゼロに近づけるスクリプトを追加しました。これで下にある紫のマテリアルが徐々に見えるようになりました。
// A script that manages ambient color.
var AmbientManager = pc.createScript('ambientManager');
AmbientManager.attributes.add('startingColor', {
type: 'rgba',
title: 'Starting Color',
description: 'The starting color for the ambient'
});
AmbientManager.attributes.add('finalColor', {
type: 'rgba',
title: 'Final Color',
description: 'The final color for the ambient'
});
AmbientManager.attributes.add('targetMaterial', {
type: 'asset',
assetType: 'material',
title: 'Target Material',
description: 'The material whose color to set (Matching the ambient color)'
});
// initialize code called once per entity
AmbientManager.prototype.initialize = function () {
this.updateTransition(0);
};
AmbientManager.prototype.updateTransition = function (transitionProgress) {
var color = new pc.Color();
color.lerp(this.startingColor, this.finalColor, transitionProgress);
var mat = this.targetMaterial.resource;
mat.emissive = color;
mat.opacity = color.a;
mat.update();
};
小惑星にこれらの変更を加えた最終結果がこちらです。
格段に良くなりましたね!スタート地点とは見違えるほどになりました。
小惑星との衝突
最後に変更するのは、小惑星がぶつかった時です!まるで、乗っている車が段差に乗り上げたときのように衝撃的にしなければなりません!
もう少し分かりやすくお伝えしますね。現時点では、左上の「n lives left」カウンターが減っていくだけです。ぶつかったことがすぐに分かるだけでなく、ライフがいくつ残っているのかプレイヤーが一目で分かるようにしなければなりません。
宇宙船のモデルをダウンロードし、Blenderでトップダウンレンダリングを行いました。その結果、シンプルな無地のアイコンが出来上がりました:
地味ですがヘルスカウンターを作るには十分です。半透明にして公開してみましょう。ヘルスカウンターはこのアイコンを3つまで表示し、残りのライフを示します。
さらに、ヘルスの変化に合わせて「ジャンプ」させたりゲームのワールドに向かって内側に回転させたりして、立体感を出してみましょう。
コンポーネントを使うと簡単におこなえますので、スコアカウンターも同様に設定してみましょう:
よりシンプルかつ素敵になります!
次は、あの「ぶつかる」感覚を再現してみましょう。ぶつかるたびに画面が揺れるようにすればいいのです。さらに、スローモーションにすることでインパクトが増しますね。
スローモーションにするのはかなり簡単で、1つのコンポーネントでおこなえます。
var BulletTimeEffect = pc.createScript('bulletTimeEffect');
BulletTimeEffect.attributes.add('effectDuration', {
type: 'number',
default: 1,
title: 'Effect Duration',
description: 'How long the bullet time effect should last.'
});
BulletTimeEffect.attributes.add('timeCurve', {
type: 'curve',
title: 'Time Curve',
description: 'How much the time scale should be over unscaled time.'
});
// initialize code called once per entity
BulletTimeEffect.prototype.initialize = function () {
this._time = 0;
this.entity.on('destroy', function () {
this.app.timeScale = 1;
}, this);
};
// update code called every frame
BulletTimeEffect.prototype.update = function (dt) {
this._time += (dt / this.app.timeScale) / this.effectDuration;
this.app.timeScale = this.timeCurve.value(this._time);
this.app.timeScale = Math.min(1, this.app.timeScale);
};
画面揺れはもう少し複雑です(もちろん、魔法ではありません!)。基本的なロジックは、単純にカメラをランダムに動かすことです。これをおこなうにはカメラの元の位置を追跡し、それをランダムに移動させるスクリプトを使用します。それぞれの新フレームの開始時に元の位置にリセットし、それを繰り返します。
this.entity.setPosition(originalPosition);
this.entity.translate(this.getRandomTranslation());
上記の getRandomTranslation() メソッドは、単純にランダムな Vector3 を返せば動作します。しかし問題は、この方法では特に揺れの距離が大きい場合、カメラが揺れているのではなくジッターしているように感じられる点です。これはゲーム酔いの原因になります。
では、他にどのような方法があるでしょうか?もっと数学的に複雑な方法で乱数を得ることができます。これによって、ジッターではなく滑らかな揺れを実現できます。この乱数の取り方がPerlin Noiseです。
Perlin Noise は爆発の視覚効果から Minecraft のワールド生成まで、あらゆるメディアで素晴らしいものの作成に使用されています。数学に興味がある場合には、 この素晴らしい記事でより詳しく知ることができます。
今回のゲームでは Perlin Noise を使ってみましょう。私たちが行った実装は perlin-camera-shake.js と perlin-noise.js スクリプトで見ることができます。
最後に、衝撃を受けたときに小さな衝撃波を追加しましょう。小惑星の爆発や弾丸の衝突のエフェクトと同様に、パーティクルシステムを使いましょう。シンプルな円形のテクスチャを用意し、ネガティブなものを示すために赤で着色し、プレイヤーが攻撃を受けるたびにエフェクトを発生させるスクリプトを追加しました。
エフェクトを組み合わせた結果は以下のようになります。
プレイヤーが撃たれた場合以外にも、画面揺れがあることにお気づきでしょうか?私はこのエフェクトが大好きなので、小惑星の爆発や弾丸の発射にも追加しました。
まとめ
上記で追加したエフェクトによって、ゲームの見た目もプレイも見違えるように改善されました。小惑星の破壊だけでも気持ちの良いものですが、それ以外のエフェクトは破壊の体験をさらに高めるためにあるのです。
最後の仕上げとして、 PlayCanvas が提供するいくつかの後処理エフェクトを追加しました。「ビネット」、「ブルーム」、「色収差」です。さらに、CRTスキャンラインをオーバーレイで追加しレトロな雰囲気を演出しました。
皆様にこのガイドを活用いただけますと幸いです。プロジェクトは公開されており ドキュメント上のゲームデモ としてアクセスできますので、ぜひご参照ください。
PlayCanvas はブラウザゲームを構築できる、クラウドベースの優れたゲームエンジンです。また、多くの開発者に馴染みのある Unity や Unreal のような感覚で使用できる素晴らしいエディターが搭載されています。
もっと知りたいですか?
ゲームをブラッシュアップしたい方は、以下のリソースをご覧ください!
PlayCanvas は、クラウド上で動作する素晴らしいWebファーストのゲームエンジンです。何もダウンロードする必要がなく、無料で利用開始できます!
PlayCanvas – The Web-First Game Engine
このredditの投稿には、約60秒で出来る多くのトリックがまとめられています!
GDCの講演で、ゲームジュースについてもっと分かりやすく、詳細に取り上げているものがあります。役立つ情報が満載です!
Juice it or lose it – a talk by Martin Jonasson & Petri Purho
私の大好きなゲームジュース、画面揺れについて詳細に解説した素晴らしいINDIGOクラスがあります!
Vlambeer – The art of screenshake by Jan Willem Nijman
Paulo Oliveira
コメント
0件のコメント
サインインしてコメントを残してください。