9.1K Views
October 26, 22
スライド概要
USにおけるAndroid無料ゲームランキングで1位を達成した
Draw Saberというハイパーカジュアル製品で
使用しているラグドール制御の手法について
概要を紹介いたします。
また、そのような手法を採用するに至った経緯を、
ゲームデザインに絡めてお話しいたします。
こんな人におすすめ:
・ただ死んでいるだけのラグドールに飽きてきた方
・物理を積極的に応用したカジュアルゲームを作ることに興味のある方
・物理の素人でも笑いを取るだけなら案外どうにかなるぞ、と勇気づけられたい方
受講者が得られる知見:
・PID制御のUnityにおける応用例について
・ハイパーカジュアルゲームにおける物理の可能性
出演:
平山 尚 (株式会社カヤック)
--
初出: SYNC 2022 #UnitySYNC
https://events.unity3d.jp/sync/
リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。
DrawSaberの 愉快なラグドール 2022年10月 株式会社カヤック 平山尚
平山と申します こんなゲームを作りました Draw Saber 2
アメリカで1位になったことあります 3
これのラグドールのお話をします なんか気色悪い動きしてますよね 4
手法の骨子 ● AnimationClip/Animator不使用 ● 全てAddForceとAddTorque 5
なぜそうした? 経緯 6
実は 発想と手法の基礎は 別のゲームでできました 7
8
社内イベントで作った奴です 輪をかけて気持ち悪いですね 詳細は弊社技術ブログ https://techblog.kayac.com/sumo-game-memories 9
これを作るに際して 10
AnimationClipを 作りたくなかった 入れたくなかった 11
アニメーション工数問題 72時間 ● 社内イベントで作業時間合計 ● ● 3Dアニメータは希少 相撲のAnimationClipなんてストアにある? 作る/探す/組み込む、すべて無理 12
それに 毎回同じ動きじゃ 飽きる 13
だから AnimationClipは 入れない 14
どうする? 15
物理 16
もちろんアレが念頭にあった ● ● 昔(2007ごろ)死ぬほど笑った 技術力に驚嘆した。 ○ 今回のも、技術ではこれの足元 にも及ばない。 https://store.steampowered.com/app/1604240/Sumotori_Dreams_Classic/ 17
そしてこうなった DrawSaberも技術的にはだいたい同じです。 18
手法 19
AddForceとAddTorque 20
位置と速度をいじるな。力をかけよ。 ● ● 現実世界ではテレポートはできない。超加速もできない。 だったらそれでゲームを作ってやろうじゃないか AddForceで立て、歩け、殴れ。 21
例えば ● ● ● ● ● 手足をAddForceで敵に向かって飛ばす 体幹はAddTorqueで起こす 首をAddTorqueで敵に向ける 腰の位置をAddForceで操作する DrawSaberの場合は剣の位置と向き 22
大問題 力とトルク どう決める? 23
これは 「制御」だ 24
制御と言えば PID制御 https://ja.wikipedia.org/wiki/PID%E5%88%B6%E5%BE%A1 25
雑に言えば 目標値から遠いほど大きな力をかけて、 いい感じに収束させる 26
バネダンパ法に似ているが 大きな利点がある 27
バネダンパと違って 目標値に届かないことが少ない 28
上がり切らない問題 ● 目標値から遠い状態が続くと力が強くなる(I制御) ○ ○ 例えばバイクのスピードを一定に保ちたい場合、バネダンパ制御だと坂が急だと登れないこと がある。PID制御ならそのうち登る (可能性が高い ) Saber/相撲でも、最初数秒は腰が低めになるが、だんだん上がってくる。 I制御スゴイ 29
コード自体は簡単 誤差 = 目標値 - 現在値; fp = P制御定数 * 誤差; fi = I制御定数 * 蓄積誤差; fd = D制御定数 * (誤差 - 前の誤差) / deltaTime; f = fp + fi + fd; //これをAddForceに渡せ! 30
目標値の設定例 ● ● ● ● ● 腰の高さは、歩幅が広いほど下げる 剣の制御は、お客さんが引いた線上に手の目標を置く 顔や体幹は敵に向ける 剣が前に出るほど、前足を前に出す などなど 31
PIDの定数はどうやって決めるの? ● テキトーに入れてみよう!! ● 大→硬い。小→柔らい ○ ● DrawSaberの首などは弱めに設定して躍動感を演出 10:1:2から始めるといい感じがしている(根拠はない) ○ ガクガクしたら Dを下げるといいかもしれない。 ■ ArticulationBodyだと暴れやすいので Dは0.5から(根拠はない) 32
回転は? Vector3のPIDは簡単だけど、 Quaternionはどうやるの? 33
よくわかんない 正直教えてほしい 34
現状「なんとなく」 ● 調べてもやり方出てこない。数学がわからない... ● いろいろやりました。 ○ ○ ○ 相撲: AddForceAtPositionを2箇所に正反対にかけて回す DrawSaber: ローカル座標系で 3つの角度に分解、独立に PID 最近:「log(目標値/現在値)」を3DのPIDしてトルクを算出 (付録にコード) ● まあまあ動いてます 35
ところでダメージ表現 36
これもPID ● PIDで出てきた力/トルクを弱めている。 ○ ● 例えば最大HP100で今10なら、力を1/10する。 それだけ ○ 力が徐々に弱まることで多彩な死に様が勝手に生成される ■ HPを部位ごとにしてあるので多彩さが増す ○ 物理の神万歳!! 37
その他 ● Time.fixedDeltaTimeは1/60sではダメかも ○ ○ ● DrawSaberは1/120s(=0.08333333) 相撲は敢えて 1/60s ■ HDRPのモーションブラーがある関係で、多少暴れた方が面白かった 関節が増えるほど目に見えて不安定になる印象 ○ ラグドールがバラバラになったら PIDの定数を小さくする。伸びるし遅れるが。 38
「相撲→DrawSaber」について少し 相撲→剣術 39
相撲、ちょっと面白かったので ハイパーカジュアル化 したかった 40
だがスマホと相性が悪い ● 相撲はボタンで攻撃 ● スマホにボタンはない 弊社ではゲームセンターの筐体に入れて置いている 41
スマホはゲーム機じゃない ● ● ゲーム機向けの操作系がスマホでうまく行く気がしない スマホといえばタッチとドロー ドロー? 42
そういえば以前こんな仕事した ● ParkMasterに参加した ● 「線を描き終わったら時間が動く」 43
線を引くなら ● ● ● 手足は4つもあって小さい。線引くの面倒くさい 武器があれば動かす対象が一個に決まる 武器が長ければ、長い線を引かれても対応できる 44
そういえば 私、ブシドーブレード好きなんですよ © 1997 SQUARE ENIX CO., LTD. All Rights Reserved./Light Weight https://www.jp.square-enix.com/game/detail/bushido/ 45
DrawSaberになった 刃の向きの制御が難しそう ↓ どう当たっても斬れるようにレー ザーにした CPIは23cくらいだった 実際の企画書 46
というわけで 47
ありがとうござました ● ● ラグドール、ちょっと工夫すると愉快です PID制御は便利です。カメラ制御にも使ってます。 48
付録: 現状の回転のPID制御(自信全くない)
public Vector3 Update(
Quaternion c, // 現在値
Quaternion g, // 目標値
float dt){
var dq = g * Quaternion.Inverse(c);
var e = Log(dq);
var t = settings.kp * e;
t += settings.ki * state.eSum;
state.eSum += (e + state.prevE) * 0.5f * dt; //台形
t += settings.kd * (e - state.prevE) / dt;
state.prevE = e;
return t;
}
static Vector3 Log(Quaternion q){ // ToAngleAxisでいいのかも
Vector3 ret;
var vlSq = (q.x * q.x) + (q.y * q.y) + (q.z * q.z);
if (vlSq == 0f){ // w = 1か-1で、いずれにせよ0度
ret = Vector3.zero;
}else{
var theta = Mathf.Acos(Mathf.Clamp(q.w, -1f, 1f)) * 2f;
if (theta >= Mathf.PI){ // [-pi,pi]
theta -= Mathf.PI * 2f;
}
var mul = theta / Mathf.Sqrt(vlSq);
ret.x = q.x * mul;
ret.y = q.y * mul;
ret.z = q.z * mul;
}
return ret;
}
もっといい方法募集 ! twitterの @hirashoまで!
49