ホームページへ

「Ninja Attract」ポートフォリオ

河原電子ビジネス専門学校 ゲームクリエイター科2年

氏名:米地 真央




目次

1.作品概要

目次へ


2.担当ソースコード

● ソースコード(cpp,h)
● シェーダー(fx,h)

目次へ


3.改造したエンジンコード

● ソースコード(cpp,h)

目次へ


4.ゲーム内容

 街中を自由にスイングで飛び回れる、3Dスイングアクションゲーム。
危険走行をする暴走車を、スイングで追いかけて捕まえろ!



目次へ


5.操作説明



目次へ


6.技術紹介

6.1.スイングについて

 このゲームのスイングアクションは、プレイヤーが鎖を射出し、鎖の先端がビルに引っ付き、そこを中心に振り子のような動きのスイングアクションで移動する。このスイングアクションは以下のような流れで実行している。

  1. ビルの鎖の射出先の候補地(以降スイングターゲット)の位置を計算する。
  2. 鎖が射出するスイングターゲットを探す。
  3. スイングターゲットに向かって、鎖を射出する。鎖がスイングターゲットまで届いたらスイング開始。
  4. 進む方向の上下の角度を求める。
  5. 進むスピードを求める。
  6. スイング中の左右の方向転換を計算する。
  7. プレイヤーに移動速度を加算する。

 それぞれの手順を詳しく見ていく。

  1. ビルのスイングターゲットの位置を計算する

    1. ビルのモデルデータをもとにAABBを作成する。まずは、モデルの頂点データを1つずつ調べ、最大座標と最小座標を求める。

    2. ( 最大座標 - 最小座標 ) × 0.5f を計算してAABBの半分のサイズを求める。

    3. ( 最大座標 + 最小座標 ) × 0.5f を計算してAABBのセンターポジションを求める。

    4. AABBのセンターポジションと半分のサイズから、AABBの8頂点を求める。

    5. 4.で求めたAABBは、まだ、オブジェクト座標系のAABBのため、ビルのワールド行列をAABBに乗算して、ワールド座標系のAABBを求める。

    6. このAABBの側面にスイングターゲットを等間隔で配置する。この時、一定以下の高さはスイング出来ないので、除外する。



  2. 鎖が射出するスイングターゲットを探す

    1. まず、スイングで進む方向を決める。移動入力があったらその方向に、無かったらカメラの前方向がスイングで進む方向になる。

    2. プレイヤーの座標から、スイングで進む方向に一定距離先の座標を基点に、有効範囲内にあって一番近いスイングターゲットを探し出す。この時、進む方向ベクトルと、プレイヤー座標からスイングターゲットへの方向ベクトルの内積が負になるスイングターゲットは候補から除外する。つまり、進む方向と逆方向のスイングターゲットは選ばないようにする。

      ▽上から見たスイングターゲットを探す処理

  3. スイングターゲットに向かって、鎖を射出する。鎖がスイングターゲットまで届いたらスイング開始

    1. 選んだスイングターゲットに向かって鎖を射出する。

    2. プレイヤーの座標からスイングターゲットの座標へ線形補完を行い、補間率を挙げていくことによって鎖を伸ばしていく。そのため、プレイヤーとスイングターゲットの間の距離に関係なく一定の時間で伸びきる。

    3. 鎖のモデルを拡大させてスイングターゲットまで伸ばしてしまうと、ゴムのように伸びた鎖のモデルになってしまう。それを防ぐために、鎖のモデルを2つ用意する。1つ目のモデルは手元から伸ばしていき、等倍まで伸びたら先端から2つ目の鎖のモデルの伸ばし始める。2つ目のモデルはそのままスイングターゲットまで伸ばす。こうすることで、カメラに映っている手元の鎖のモデルは正常な倍率で表示さる。2つ目のモデルはあまりカメラに映らないため、伸びていても目立たない。

    4. 鎖がスイングターゲットまで伸びきったら、スイングを開始する。



  4. 進む方向の上下の角度を求める

    1. スイング中の動きを計算していく。振り子のような動きで前の進むため、上下の動きがある。まずはその上下の角度を求める。

    2. 最初にプレイヤーの座標からスイングターゲットまでのベクトルを求めて、そのベクトルのY成分を消す。これでXZ平面でのプレイヤーからスイングターゲットへのベクトルが求まる。

    3. 次に、「鎖が射出するスイングターゲットを探す」ときに求めたスイングで進む方向、これをXZ平面での前方向ベクトルとする。



    4. XZ平面での前方向ベクトルに内積を使ってXZ平面でのプレイヤーからスイングターゲットへのベクトルを射影する。



    5. XZ平面での前方向ベクトルに射影して求めた長さを乗算して、XZ平面でのプレイヤーからスイングターゲットへのベクトルを、XZ平面での前方向ベクトルに射影したベクトルが求まる。

    6. プレイヤーの座標に 5. で求めたベクトルを加算するとXZ平面でのスイングターゲットをプレイヤーの前方向に直交するように移動させた座標が求まる。



    7. 6.の座標のY座標をスイングターゲットのY座標と同じにする。これでスイングターゲットをプレイヤーの前方向までずらした座標が求まる。この座標を振り子の支点に見立てて移動させる。プレイヤーの前方向にずらしたため、真っ直ぐ前に振り子運動をする。本来ならそのままのスイングターゲットの座標を支点とするのが正しいが、プレイヤーの操作性やスイングの爽快感を出すために、プレイヤーの前方向までずらしている。



    8. 前方向にずらしたスイングターゲットからプレイヤーへの方向ベクトルを求め、XZ平面での前方向ベクトルと内積をとる。

    9. 内積が負なら、プレイヤーはスイングターゲットより手前におり、正ならスイングターゲットより奥にいる。手前の時と奥の時で処理を変える。



    10. 手前にいるときの処理は、XZ平面での前方向ベクトルを下に回転させる。下向きに回転させるために回転軸を求める。上方向のベクトルXZ平面での前方向ベクトルの外積を求め、上下の回転に必要な横方向ベクトルが求まる。

    11. 次に回転量を求める。回転量は8.で求めた内積の数値に応じて決める。8.の内積は-1.0f~0.0fの値をとり、-1.0fの時は回転量0度、-0.5fのときは回転量90度、0.0fの時は回転量0度になるような二次関数の式で変化させる。これで最初は真っ直ぐ進み、だんだん下方向へ進むようになって、また真っ直ぐに戻っていくとう変化になる。

    12. 式は、y = -4(X + 0.5)^2 + 1.0となり、xに内積を入れる。するとyの値が0.0->1.0->0.0と変化する。そして回転量90度にyの値をかける。

    13. XZ平面での前方向ベクトルを10.で求めた横方向ベクトルを軸中心に、12.で求めた回転量だけ回転させ、上下の角度を決める。

    14. 9.の時に内積が正のとき処理は前方向にずらしたスイングターゲットからプレイヤーへの方向ベクトル横方向ベクトルで90度回転させたベクトルの方向に進む。

  5. 進むスピードを求める

g=重力加速度g = 重力加速度\\

l=振り子の長さl = 振り子の長さ\\

v=任意の場所での振り子の速度v任 = 任意の場所での振り子の速度\\

cosθ=任意の場所の角度\cos\theta任 = 任意の場所の角度\\

cosθ=一番上の時の角度\cos\theta上 = 一番上の時の角度\\

V=2lg(cosθcosθ)V任 = \sqrt{2lg}(\cos\theta任 - \cos\theta上)

  1. スイングのスピードは、任意の位置での振り子の速度の公式を使用。

  2. cosΘ任には、前方向にずらしたスイングターゲットからプレイヤーへの方向ベクトル下方向ベクトルの内積が入る。

  3. cosΘ上には、一番上の時の角度は90度のため0.0が入る。

  4. gに重力加速度、lに振り子の長さ(鎖の長さ)を入れる。

  5. ここで、前方向にずらしたスイングターゲットからプレイヤーへの方向ベクトルXZ平面での前方向ベクトルと内積をとって、プレイヤーがスイングターゲットより手前側にいた場合と奥側にいた場合で処理をわける。

  6. 手前側にいた場合、任意の位置での振り子の速度の公式の、角度による減速を行わない。そのため√2lgのみ行う。

  7. 奥側にいた場合、任意の位置での振り子の速度の公式の、すべての計算を行う。上に行けば行くほど減速していく。

  8. スイング中の左右の方向転換を計算する

    1. 左右の移動入力があれば、カメラの左右方向に力を加える。
    2. 力が加わって急に方向が変わり過ぎないように、現在の左右への方向転換量と目標の方向転換量を線形補完を行って緩やかに変化するようにしている。
  9. プレイヤーに移動速度を加算する

    1. これまで計算したスイングの移動ベクトルをプレイヤーに加算する。
    2. プレイヤーの移動速度が速くなり過ぎないように、速度制限を付ける。

目次へ

6.2.インスタンシング描画

 インスタンシング描画とは、同じモデルを複数表示するときに、本来表示する数だけドローコールを呼ばなければいけないところを、1回のドローコールだけで実現できる技術である。ドローコールを減らすことでCPUのパフォーマンスを向上できる。

POINT インスタンシング描画はGPUのパフォーマンスを向上してくれるのではなく、CPUのパフォーマンスを向上してくれるものである。
ドローコールを削減しても、結局GPUで計算する頂点数やライティングの量は変わらないためである。



 インスタンシング描画の手順は以下の通りである。

  1. (ここからCPU側の処理)ワールド行列の配列の用意し、ワールド行列の配列をGPUのシェーダーリソースビューに渡す。この時、配列のはずは動的のため、定数バッファではなくストラクチャードバッファを使用する。
  2. ワールド行列の配列を更新する。
  3. ワールド配列の配列を使用し、ストラクチャードバッファを更新する
  4. 表示するモデルの数をインスタンス数として指定して、モデルのドローコールを呼ぶ。
  5. (ここからGPU側の処理)GPU側で送られてきたワールド行列の配列をtレジスタを介して変数に保持。
  6. 頂点シェーダーに、HLSLが用意してあるSV_InstanceIDセマンティクスを指定した引数を追加し、現在処理しているインスタンス番号が分かるようにする。
  7. ワールド行列の配列からインスタンス番号で適切なワールド行列を取得し、座標変換を行う。

目次へ

6.3.FlyWeightパターン

 大量にあるビルや街路樹は、同じモデルやテクスチャを使用しているものが沢山ある。街路樹は全て同じモデルが使用されている。ビルのモデルは29あるが、違うモデルでも同じテクスチャを使用しているところが何か所もある。このような場合では、FlyWeightパターンを使用すれば、大幅にメモリ使用量を節約することができる。

▽同じモデルを使用しているの街路樹

▽同じテクスチャを使用しているビル

普通に読み込むと、同じモデルやテクスチャを使用していても、使用する分だけオブジェクトの生成を行うことになる。その時の使用メモリ量は、オブジェクつに 必要のメモリ量×オブジェクトの数になってしまう。
だが、FlyWightパターンを使用した場合、最初に生成するときにリソースバンクにリソースを登録する。2回目以降は、リソースバンクからリソースの参照をため 新たに生成する必要がなくなる。そのため、使用メモリ量は、オブジェクト1つに必要なメモリ量だけになる。
これにより、当初は20GB以上のメモリを使用していたステージが、約5GBのメモリ使用量で生成することが出来た。

▽FlyWightパターン無しの場合

▽FlyWightパターン有りの場合

目次へ

6.4.D3D12のエラー、警告の回避

 VisualStudioの出力ウィンドウに表示されるD3D12のエラー、警告を全て回避した。このエラーが表示されていても、ゲームは動いているため、今まで気にしていなかったが、今回はこれらのエラーを全て除去することが出来た。



遭遇したエラーは主に次の三種類である。

  1. STATE_CREATION WARNING #0: UNKNOWN
  2. EXECUTION ERROR #613: RENDER_TARGET_FORMAT_MISMATCH_PIPELINE_STATE
  3. EXECUTION ERROR #538: INVALID_SUBRESOURCE_STATE

これらのエラーの種類、発生した場所、解決方向は以下の通りである。

  1. STATE_CREATION WARNING #0: UNKNOWN
    1. 警告の種類
      • この警告はメモリリークの警告である。生成したオブジェクトを最後まで解放していないことが原因で出る警告だ。
    2. 発生した場所
      • この警告は、ゲームを終了した時に発生。
      • 出力ウィンドウには、次のように表示された。
D3D12 WARNING: Live Object at 0x000001CCE55079B0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
  1. 解決方法

  2. EXECUTION ERROR #613: RENDER_TARGET_FORMAT_MISMATCH_PIPELINE_STATE

    1. エラーの種類
      • このエラーは、レンダリングターゲットを生成するときに指定したカラーフォーマットと、そのレンダリングターゲットに描画するオブジェクトのカラーフォーマットが違っている、というエラーだ。
    2. 発生した場所
      • G-Bufferにモデルを描画するときに発生。
      • エフェクト、スプライトを描画するときに発生。
      • 出力ウィンドウには、次のように表示された。
D3D12 ERROR: ID3D12CommandList::DrawIndexedInstanced: The render target format in slot 0 does not match that specified by the current pipeline state. (pipeline state = R16G16B16A16_FLOAT, RTV ID3D12Resource* = 0x000001CC784F0360:'Unnamed ID3D12Resource Object') [ EXECUTION ERROR #613: RENDER_TARGET_FORMAT_MISMATCH_PIPELINE_STATE]
  1. 解決方法

  2. EXECUTION ERROR #538: INVALID_SUBRESOURCE_STATE

    1. エラーの種類
      • これはレンダリングターゲットのサブリソースが不正、というエラーだ。
    2. 発生した場所
      • ポストエフェクトをかけたあと、スプライトを描画使用としたときに発生。
      • 出力ウィンドウには、次のように表示された。
D3D12 ERROR: ID3D12CommandList::DrawIndexedInstanced: Resource state (0x0: D3D12_RESOURCE_STATE_[COMMON|PRESENT]) of resource (0x000001CC784F0360:'Unnamed ID3D12Resource Object') (subresource: 0) is invalid for use as a render target. Expected State Bits (all): 0x4: D3D12_RESOURCE_STATE_RENDER_TARGET, Actual State: 0x0: D3D12_RESOURCE_STATE_[COMMON|PRESENT], Missing State: 0x4: D3D12_RESOURCE_STATE_RENDER_TARGET. [ EXECUTION ERROR #538: INVALID_SUBRESOURCE_STATE]
  1. 解決方法

目次へ


7.ゲーム的にこだわったところ

7.1.スイングに爽快感を出すための工夫

 スイングの爽快感を出すために、スイングで速く移動できるようにしたが、速すぎてとても操作が難しくなってしまった。そのため、速度はそのままで速く動いているように見せるために、バネカメラの減衰率をスイング中に動的に調整した。スイング中は減衰率を上げて、カメラがより遅れてくるようにする。これによりプレイヤーが加速して動いたかのように見える。

目次へ

7.2.IBLの影響度を書いたテクスチャの使用

 IBLを利用して、モデルに空を反射させている。この時IBLの影響度をモデルに貼られているテクスチャから決めている。これにより、ビルに空を反射させるとき、壁はそこそこ反射、窓にはくっきりと反射させることができる。

▽IBLの影響度の設定のない空の反射

▽IBLの影響度の設定がある空の反射

▽ビルの窓に貼っているテクスチャ。rgbaの内、gの値がIBLの影響度

目次へ

7.3.入力情報のセーブとロードによる演出

 タイトルシーンでプレイヤーがスイングで遠くに飛んでいく演出がある。これは、入力情報のセーブとロードに依って実現した。入力情報は、コントローラーの入力情報とそのフレームのデルタタイムを毎フレーム収集している。

  1. タイトル画面で、自分でプレイヤーを動かす。その時の入力情報を保存する。
  2. 保存した入力情報を読み込んで、プレイヤーに渡して再生する。

目次へ

7.4.VRoidから自作ゲームで表示できるモデルデータへの変換

 このゲームのプレイヤーのモデルには、VRoidというキャラクターモデリングソフトを使用して自作したモデルを使用している。だが、このVRoidはモデルのエクスポートがvrmファイルという独自の形式しかサポートしていない。このゲームではtkmという学内エンジン用のフォーマットのモデルを使用している。このモデルに変換するために3ds Maxという3DCGソフトに取り込む必要がある。

  1. UniVRMパッケージを使いVroidからUnityへ(テクスチャのカラーが変)
  2. Windows10標準搭載の3D Builderを使いobjに変換(UVが変)

目次へ

リンク