Unityを使わずにフリスビーを犬に届けよ!を作ってみる(6)の続きです。
フリスビーを犬に届けよ!の元ネタ
Unishar-ユニシャー【Unityでのゲーム開発を手助けするメディア】
前回までUnityを使わずにつくる「フリスビーを犬に届けよ!」を作成してきましたが、このゲームをいつもお世話になっているT.Umezawaさんにレビューしていただきました。
ここで問題になったのは
(1)視認性の悪さ。壁の隙間を通るときにブロックの上面下面がみえにくい。
(2)3Dゲームは2Dゲームのように1ドット単位でかわすことができないので、計算的な難しさだけでなくゲームとして成り立たせるための難しさもある。
(3)見えていない部分で当たり判定が出ることがある。
上記の改善策を考えます。
視認性の悪さ
落ち着いた感じにするため全体的に暗めにしたのがよくなかったみたいです。ブロックの面のなかで直接光があたらない部分も暗くなり、背景も黒なので視認性が悪くなっています。
対策として背景色を変えるというのはどうでしょうか?
背景色を変えるだけなら
1 2 3 4 5 |
// シーンが以下のように宣言されているなら const scene = new THREE.Scene(); // これで好きな色に変えられる scene.background = new THREE.Color(0xdddddd); |
これだと見やすいですが、個人的には黒背景のほうが落ち着いているように思います。
ゲームとして成り立たせるための難しさ
このゲームに関しては、フリスビーとカメラのY座標によってはブロックの上面または下面が見えないため、当たり判定としては正確でもユーザー目線でみると「え?これで死ぬの?このクソゲーが!」となってしまうのではないでしょうか。
最初はとくになにも考えずにブロックの壁の奥行きの厚さを128にしていました。これを小さい値にするとブロックの上面または下面が見えにくいという問題は解消します。
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 |
// 移動する障害物をつくる https://lets-csharp.com/frisbee-movable-obstacle/ を参照 const BLOCK_THICKNESS = 12; // 128ではなくもっと小さな値にする let fixedObstacles = []; connection.on("SendFixedObstaclesToClient", function (xs, ys, width, height, max) { // 古い障害物をシーンから取り除く for (let i = 0; i < fixedObstacles.length; i++) scene.remove(fixedObstacles[i]); fixedObstacles = []; const material = new THREE.MeshLambertMaterial({ color: 0x004000 }); let xArray = xs.split(','); let yArray = ys.split(','); for (let i = 0; i < xArray.length; i++) { const geometry = new THREE.BoxGeometry(width, height, BLOCK_THICKNESS); let obj = new THREE.Mesh(geometry, material); obj.position.x = xArray[i]; obj.position.y = yArray[i]; obj.position.z = 0; scene.add(obj); fixedObstacles.push(obj); // シーンから取り除くことを考えて配列にも格納しておく } maxX = max; }); let movableObstacles = []; connection.on("SendMovableObstaclesToClient", function (xs, ys, width, height) { // 移動する障害物も同様に }); |
これなら見えないところにぶつかってクソゲーよばわりされることは避けられようです。ただ壁が薄いとチープにみえてしまいます。
逆に極端に厚くするとクソゲーの完成です。
ブロックの隙間をくぐり抜けるとき隙間がどうなっているのか見えにくいという問題は、カメラとフリスビーのY座標を合わせることで対応できそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// クライアントサイドにおける処理 https://lets-csharp.com/frisbee-clientside/ を参照 connection.on("EndUpdateToClient", function () { if (maxX - CANVAS_WIDTH / 2 < cameraTargetX) camera.position.x = maxX - CANVAS_WIDTH / 2; else if (CANVAS_WIDTH / 2 < cameraTargetX) camera.position.x = cameraTargetX; else camera.position.x = CANVAS_WIDTH / 2; let b = false; // camera.position.y = 32 * 8 camera.position.y = cameraTargetY; camera.position.z = 650; // camera.lookAt(new THREE.Vector3(camera.position.x, 32 * 8, 0)); camera.lookAt(new THREE.Vector3(camera.position.x, cameraTargetY, 0)); renderer.render(scene, camera); // レンダリング }); |
これだと当たり判定の紛らわしさはありません。ただ背景が移動するので別の面で難しさがでてきます。
フリスビーを光源に
全体的に暗いのとブロックの暗部を照らし出すためにフリスビーを点光源にします。これだとブロックの隙間を通過するときに衝突してはいけない部分を明々と照らし出すことができます。ただフリスビーの周囲が明るくなったがゆえに周囲の暗さが気になります。
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 |
let pointLight = null; connection.on("EndUpdateToClient", function () { if (maxX - CANVAS_WIDTH / 2 < cameraTargetX) camera.position.x = maxX - CANVAS_WIDTH / 2; else if (CANVAS_WIDTH / 2 < cameraTargetX) camera.position.x = cameraTargetX; else camera.position.x = CANVAS_WIDTH / 2; camera.position.y = cameraTargetY; camera.position.z = 650; camera.lookAt(new THREE.Vector3(camera.position.x, camera.position.y, 0)); // フリスビーを点光源にすることで周囲を明るくする // 第二引数をあまり大きな値にすると障害物をつきぬけてその向こうにある障害物まで明るくなってしまう if (pointLight == null) { pointLight = new THREE.PointLight(0xFFFFFF, 8, 32 * 8, 1.0); scene.add(pointLight); } pointLight.position.x = cameraTargetX; pointLight.position.y = camera.position.y; pointLight.position.z = 0; renderer.render(scene, camera); // レンダリング }); |
このゲームはフリスビーと障害物の立体感を出すために3Dにしていますが、Z座標は関係ありません。3DオブジェクトのZ座標はつねに0です。ところが3DゲームをまじめにつくりはじめるとXYZの3つの座標を考えないといけません。カービィと3D表現の相性が悪かった理由や遊びやすくするための当たり判定の仕掛けについて語られる『星のカービィ ディスカバリー』開発者インタビューが公開中には以下のような興味深いことが書かれています。
カービィはプレイする年齢層がとても広く、難易度にも気を配らなくてはいけない。これは戦闘の難易度という点だけではなく、攻撃を当てる、ジャンプをする、敵を吸い込むなど、プレイヤーが思い描いたとおりのアクションを決められるようにして、なるべく多くの人が気持ちよくプレイできるように配慮する必要があるという意味でもある。
その配慮の解決策が、ファジーな攻撃や着地の判定だ。まず本作では、カメラ・カービィ・敵の位置から、プレイヤーからの攻撃が敵に当たっているように見える範囲を自動で計算しているそうだ。そして、厳密には攻撃が当たっていないが攻撃が当たっているように見える角度であれば、攻撃を当てる処理をする。少し大変そうな計算だが、これによりストレスなくスムーズに敵と戦えるようになる。
「実際に」当たっているかとプ「レイヤーが見た」当たっているかには差があります。そもそも人間の目が立体を立体として捉えることができるのはなぜでしょうか?それは人間には目がふたつあり、右目と左目でみえる微妙な違いからその物体が近くにあるか遠くにあるかを認識できるからです。そのため片目では針に糸を通すのが難しくなります。
しかし3Dゲームの場合は平面に描画しているだけなので右目も左目もありません。そのため物理エンジンだけに頼った3Dゲームは、苦労して作ったわりには評価されずクソゲーの烙印を押されてしまうことが多いのです。
重要なのは当たっているかどうかではなく、当たっているように見えるかどうかです。ゲームの場合は厳密さよりもそれらしさのほうが大切なのです。
ぜんぜんゲームともプログラミングとも関係ありませんが、これはラムダ技術部さんの動画、「【理系】超精密なフライドチキンを作ろう」です。
これがネタとして成り立つのは、人間が重視しているのは厳密さや超精密さではなく、「それらしさ」であるからです。