夜光蛾6を進めていたので解説が遅くなりました。
.
ボーンの個別操作に挑戦した。
そして失敗中。ポニーテールが激しく暴れまわるのだった……。
打ち上げ→追撃ジャンプ→空中連撃→ふっとばしの導入。
これは簡単だった。
●Blender
新しいモーションを作っていたところ、ドープシート(アクション)に謎のボーンが大量登録されてるのに気付きました。
どうも、最初に練習で入れてのちに削除したボーンが残っていたようです。
あとドープシート(ドープシート)に、リグ・メッシュ・ランプの項目があって、それぞれにモーションが登録してあるのがすごく謎です。
なぜランプにキャラのモーションが登録されているんだ……。
これを削除するとドープシート(アクション)のほうのモーションも消えてしまいます。
仕組みがサッパリわからない。
とりあえず、古いボーンのほうは削除して、ドープシートのものはそのままにしておきました。
.
削除できないモーションがありました。
Shift+「×」でも消せません。
モーションのリストの一番後ろ二つなんだけど、ソフトの仕組み上特別な設定になってるとかだろうか。
これも仕組みがサッパリわからない。
.
●ボーンの個別制御。
これは相当悩みました。
プログラム関係ではここ数年で一番悩んだかもしれないぞ……。
しかし、まさにちょうど同じころに同じ問題に取り組んでいる方がいて、ブログに解説記事を上げていました。
答えがでてるじゃん……。
これはすごいと喜び勇んで読んだものの、僕の脳味噌ではサッパリ意味がわかりませんでした。
とはいえ、理解できる部分も二点ありました。
ここから考えていけばなんとかなるかもしれないぞ……。
まず一点目は
・MATRIX MV1GetFrameLocalMatrix( int MHandle, int FrameIndex ) ;
・MATRIX MV1GetFrameLocalWorldMatrix( int MHandle, int FrameIndex ) ;
です。
下の関数はなんとなくわかるものの、上についてはDXライブラリのサイトの解説でも
「MHandle のモデルハンドルが示すモデルに含まれるフレームの変換行列を取得します。」
としか書かれていません。全く意味がわからない。
そこで上記ブログの解説記事です。
これはつまり、「自分は親フレームのどこにいるか、どんな姿勢なのか」を取得できる関数だということです。
親フレームと密接に関わる情報っぽいです。こいつを活かすには親フレームを攻略せねばならない。
そして下の関数です。これはつまり、自分の位置と姿勢をワールド座標に転写する感じなのではあるまいか?
ローカル座標をワールド座標に変換する行列を取得するわけですよ。座標だけでなく姿勢も変換できそうな臭いを放っている。
まぁ、便利なんじゃないの?という感じです。
しかしその真価は、応用方法にあるのだった……。
これが、上記のブログでなるほど!と思った二点目です。
その応用方法とは?
それは……
……逆行列……
逆? 行列?
下の関数で取得した行列を逆行列にすると、ワールド座標を自分のローカル座標に置き換えられるわけですよ! すごい!
わかってる人には当たり前なんだろうと思いますが、素人にはこれがわからない。
逆行列の存在とローカル・ワールド変換の関数を知っていても、それを組み合わせて何ができるかということには気付かないわけです。
いや、行列とかをちゃんと学んだ人ならわかるんだろうな……。
しかし僕は県内有数の底辺校出身。特攻の拓とかカメレオンみたいな90年代ヤンキーまんがに出てきたような高校に通っていたのです。(あれよりは相当平和だったけど外見上は似ていた。シンナーでラリったやつが奇声を上げながら校舎内を走り回ったりするレベル)
当然、行列も微積分もまったくわからないのだった。三角関数だって自分でおぼえたんだぜ……。難しいことはすべてゲーム製作とSF小説でおぼえた。
.
ここで、ボーンの構造というか仕組みを説明しておきます。僕も全然わかってないんですが、ボーンを個別に動かすのに必要な知識くらいはなんとか得たので、自分の頭の中を整理するためにもまとめてみます。間違ってるかもしれないので注意が必要だぞ……。
ボーンの構造として、個々のボーンは独自の座標系を持っておりワールド座標とは何の関係もなく動いています。
つまりワールド座標の「上」が、ローカル座標の「上」とは限らない。
個々のボーンにとって大事なのは親だけど、その親の座標も姿勢も知らない。ボーン本人にとっては、自分こそが世界の中心で、付け根の部分を原点とした空間に一人で住んでいます。ワールド座標の上下左右なんて全く無視です。
ただ、上記のフレーム変換行列の中には「自分の付け根(原点)は、親の世界のどこにあるか、その世界でどっちを向いているか」が記されています。
で、こんなローカル空間が繋がったものが一つのモデル(リグ)を構成しているわけです。
あるボーンが動けば子ボーンごと動くので、例えば肩動かすだけで肘も手首も指も一緒に動くわけですね。
しかしこのとき子ボーンたちは「自分が動いた!」とは思っていないのです。
地球が自転しても、地球が公転しても、天の川銀河が自転しても、きみは自分が動いたとは思わないだろう。きみにとっての「上」は、地球にとっての「上」や太陽にとっての「上」とは違うだろう。しかし地球が自転すればきみも回っているのだ……。
子ボーンは自分の空間しか眼中にない。普通の手段では外から触ることもできない。
だから普通でない手段でワールド座標の情報を知る必要があり、その変換作業がややこしくて難しいということです。
つまりあるボーンAを動かすためには、親ボーンBの視点でAや目標位置の座標を取得し、親Bのローカル座標内で移動や回転ベクトルを算出し、それををAの座標変換行列に掛け合わせる必要があります。
親Bも自分の位置や姿勢を理解してはいないものの、MV1GetFrameLocalWorldMatrixの逆行列で座標を取り込めば親Bの原点を中心とした座標になるわけです。そこで移動量を計算し親の座標系で子に適用すれば、ワールド座標でも望む結果が得られるということです。
たぶんこれであってるはず……。
.
ここまで読んだ方は、変な例えばかりで余計意味がわかんねぇよ……と思ったことだろう。僕も思った。
説明すらできないほど難解だと思っていただきたい。というか、こんなこと長々と書く意味ないよな……。
とにかく重要なのは、ローカル座標とワールド座標はなんの関係もなくて、そのローカル座標が連結されてるものを制御するなんて、人類の知能では理解できっこないのでは?ということです。
その証拠に、今回の動画ではものすごい勢いで髪が暴れている。
ひどい時には寄生獣かバイオハザード4かという暴れ具合であった……。
僕の頭では理解するのは無理ではあるまいか?
しかし理解せねばゲームは作れない。なので思考錯誤を繰り返すのだった。
続く。
.
●打ち上げ、空中コンボ、横吹っ飛び
コンボアクションに必須の打ち上げからの連続技です。
このあたりは何の問題もなくできました。
・攻撃について
本体オブジェクトの攻撃アニメーションが指定した時間になると、攻撃オブジェクト(要するに弾)が生成され、弾は攻撃箇所のボーン(拳や足先など)に張り付きます。
本体オブジェクトは弾生成時に自分のポインタと攻撃箇所のボーンの番号を与えるので、弾はそれを使って目的のボーンに張り付くわけです。
VECTOR MV1GetFramePosition( int MHandle, int FrameIndex ) ;
モデルのハンドルとボーンの番号を与えるだけ!
これを使って一発で位置を取得です。なんの計算もいらないぞ……。なんて便利なんだ……。
弾は前回説明した体力値構造体のデータも持っています。(本体を通じてキャラ情報構造体を見に行き、攻撃情報を手に入れます)
さらに、攻撃属性の情報もあります。今のところ、通常攻撃・打ち上げ攻撃・ふっとばし攻撃・叩き落し攻撃の四つです。
当たった攻撃が通常攻撃属性だった場合、転倒値が残っていればよろめき状態へ、なければ転倒状態に対象を移行させます。
打ち上げ等では、転倒値の残りが一定以上ならよろめき状態へ、一定以下なら移動ベクトルを与えてからそれぞれの状態に移行させています。
.
打ち上げ、空中技のヒット中などは重力が半分以下になり滞空時間が長くなっています。
本当は常に一律の重力にしたいところだけど、まぁこのあたりはゲーム的演出として取り入れざるを得ない。
.
空中技ヒット時にはプレイヤー・敵共に少しジャンプします。これによって空中に留まります。
.
空中コンボ中に敵の位置が変わってしまい空振りすることが多かったので、空中ヒット後は敵を強制的に引き寄せるようにしました。
座標を直接操作しているため、壁際などではめり込み関係の不具合が出るかも。
巨大な敵相手でも変なことになりそう。
この先注意が必要な部分です。
敵のXZ方向の移動ベクトルもヒット時にリセットしています。
.
最初に殴った相手をロックオンする仕様ではあるもの、その最初の一撃が当たりにくいことに気づきました。
敵が勝手気ままに動き回ることもあって、かなり当たらない。
動画にもその様子が赤裸々に記録されている。
これはまずい……。やはり自動ロックオンは必要なのか。