DrawSaberの愉快なラグドール

9.5K Views

October 26, 22

スライド概要

USにおけるAndroid無料ゲームランキングで1位を達成した
Draw Saberというハイパーカジュアル製品で
使用しているラグドール制御の手法について
概要を紹介いたします。

また、そのような手法を採用するに至った経緯を、
ゲームデザインに絡めてお話しいたします。

こんな人におすすめ:
・ただ死んでいるだけのラグドールに飽きてきた方
・物理を積極的に応用したカジュアルゲームを作ることに興味のある方
・物理の素人でも笑いを取るだけなら案外どうにかなるぞ、と勇気づけられたい方

受講者が得られる知見:
・PID制御のUnityにおける応用例について
・ハイパーカジュアルゲームにおける物理の可能性

出演:
平山 尚 (株式会社カヤック)

--
初出: SYNC 2022 #UnitySYNC
https://events.unity3d.jp/sync/

profile-image

リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

DrawSaberの 愉快なラグドール 2022年10月 株式会社カヤック 平山尚

2.

平山と申します こんなゲームを作りました Draw Saber 2

3.

アメリカで1位になったことあります 3

4.

これのラグドールのお話をします なんか気色悪い動きしてますよね 4

5.

手法の骨子 ● AnimationClip/Animator不使用 ● 全てAddForceとAddTorque 5

6.

なぜそうした? 経緯 6

7.

実は 発想と手法の基礎は 別のゲームでできました 7

9.

社内イベントで作った奴です 輪をかけて気持ち悪いですね 詳細は弊社技術ブログ https://techblog.kayac.com/sumo-game-memories 9

10.

これを作るに際して 10

11.

AnimationClipを 作りたくなかった 入れたくなかった 11

12.

アニメーション工数問題 72時間 ● 社内イベントで作業時間合計 ● ● 3Dアニメータは希少 相撲のAnimationClipなんてストアにある? 作る/探す/組み込む、すべて無理 12

13.

それに 毎回同じ動きじゃ 飽きる 13

14.

だから AnimationClipは 入れない 14

15.

どうする? 15

16.

物理 16

17.

もちろんアレが念頭にあった ● ● 昔(2007ごろ)死ぬほど笑った 技術力に驚嘆した。 ○ 今回のも、技術ではこれの足元 にも及ばない。 https://store.steampowered.com/app/1604240/Sumotori_Dreams_Classic/ 17

18.

そしてこうなった DrawSaberも技術的にはだいたい同じです。 18

19.

手法 19

20.

AddForceとAddTorque 20

21.

位置と速度をいじるな。力をかけよ。 ● ● 現実世界ではテレポートはできない。超加速もできない。 だったらそれでゲームを作ってやろうじゃないか AddForceで立て、歩け、殴れ。 21

22.

例えば ● ● ● ● ● 手足をAddForceで敵に向かって飛ばす 体幹はAddTorqueで起こす 首をAddTorqueで敵に向ける 腰の位置をAddForceで操作する DrawSaberの場合は剣の位置と向き 22

23.

大問題 力とトルク どう決める? 23

24.

これは 「制御」だ 24

25.

制御と言えば PID制御 https://ja.wikipedia.org/wiki/PID%E5%88%B6%E5%BE%A1 25

26.

雑に言えば 目標値から遠いほど大きな力をかけて、 いい感じに収束させる 26

27.

バネダンパ法に似ているが 大きな利点がある 27

28.

バネダンパと違って 目標値に届かないことが少ない 28

29.

上がり切らない問題 ● 目標値から遠い状態が続くと力が強くなる(I制御) ○ ○ 例えばバイクのスピードを一定に保ちたい場合、バネダンパ制御だと坂が急だと登れないこと がある。PID制御ならそのうち登る (可能性が高い ) Saber/相撲でも、最初数秒は腰が低めになるが、だんだん上がってくる。 I制御スゴイ 29

30.

コード自体は簡単 誤差 = 目標値 - 現在値; fp = P制御定数 * 誤差; fi = I制御定数 * 蓄積誤差; fd = D制御定数 * (誤差 - 前の誤差) / deltaTime; f = fp + fi + fd; //これをAddForceに渡せ! 30

31.

目標値の設定例 ● ● ● ● ● 腰の高さは、歩幅が広いほど下げる 剣の制御は、お客さんが引いた線上に手の目標を置く 顔や体幹は敵に向ける 剣が前に出るほど、前足を前に出す などなど 31

32.

PIDの定数はどうやって決めるの? ● テキトーに入れてみよう!! ● 大→硬い。小→柔らい ○ ● DrawSaberの首などは弱めに設定して躍動感を演出 10:1:2から始めるといい感じがしている(根拠はない) ○ ガクガクしたら Dを下げるといいかもしれない。 ■ ArticulationBodyだと暴れやすいので Dは0.5から(根拠はない) 32

33.

回転は? Vector3のPIDは簡単だけど、 Quaternionはどうやるの? 33

34.

よくわかんない 正直教えてほしい 34

35.

現状「なんとなく」 ● 調べてもやり方出てこない。数学がわからない... ● いろいろやりました。 ○ ○ ○ 相撲: AddForceAtPositionを2箇所に正反対にかけて回す DrawSaber: ローカル座標系で 3つの角度に分解、独立に PID 最近:「log(目標値/現在値)」を3DのPIDしてトルクを算出 (付録にコード) ● まあまあ動いてます 35

36.

ところでダメージ表現 36

37.

これもPID ● PIDで出てきた力/トルクを弱めている。 ○ ● 例えば最大HP100で今10なら、力を1/10する。 それだけ ○ 力が徐々に弱まることで多彩な死に様が勝手に生成される ■ HPを部位ごとにしてあるので多彩さが増す ○ 物理の神万歳!! 37

38.

その他 ● Time.fixedDeltaTimeは1/60sではダメかも ○ ○ ● DrawSaberは1/120s(=0.08333333) 相撲は敢えて 1/60s ■ HDRPのモーションブラーがある関係で、多少暴れた方が面白かった 関節が増えるほど目に見えて不安定になる印象 ○ ラグドールがバラバラになったら PIDの定数を小さくする。伸びるし遅れるが。 38

39.

「相撲→DrawSaber」について少し 相撲→剣術 39

40.

相撲、ちょっと面白かったので ハイパーカジュアル化 したかった 40

41.

だがスマホと相性が悪い ● 相撲はボタンで攻撃 ● スマホにボタンはない 弊社ではゲームセンターの筐体に入れて置いている 41

42.

スマホはゲーム機じゃない ● ● ゲーム機向けの操作系がスマホでうまく行く気がしない スマホといえばタッチとドロー ドロー? 42

43.

そういえば以前こんな仕事した ● ParkMasterに参加した ● 「線を描き終わったら時間が動く」 43

44.

線を引くなら ● ● ● 手足は4つもあって小さい。線引くの面倒くさい 武器があれば動かす対象が一個に決まる 武器が長ければ、長い線を引かれても対応できる 44

45.

そういえば 私、ブシドーブレード好きなんですよ © 1997 SQUARE ENIX CO., LTD. All Rights Reserved./Light Weight https://www.jp.square-enix.com/game/detail/bushido/ 45

46.

DrawSaberになった 刃の向きの制御が難しそう ↓ どう当たっても斬れるようにレー ザーにした CPIは23cくらいだった 実際の企画書 46

47.

というわけで 47

48.

ありがとうござました ● ● ラグドール、ちょっと工夫すると愉快です PID制御は便利です。カメラ制御にも使ってます。 48

49.
[beta]
付録: 現状の回転の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