CodingReptile

ゆるゆると技術に関するメモを貯めていく

【Unity】SampleAsset の ThirdPersonCharacter のスクリプトを読む(キャラ移動の準備編)

前回 は変数の宣言部分を読んだので、続いて ThirdPersonCharacter.cs の中身をメソッド毎に読んでいきます。

void Start()

初期化がされています。全ての変数はあらかじめ宣言されており特別なことは特にしていないです。

唯一気になったのは 41 行目。

m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;

OR 演算子を使う場合、「左側を評価して false なら右側を評価する」
という使い方がされることがありますが、この場合は素直に bit 演算です。

論理 OR 演算子の結果が m_Rigidbody.constraints にセットされているようです。
試しに出力してみると以下の結果になりました。

Debug.Log(RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionY) => 6
Debug.Log(RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ) => 10
Debug.Log(RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezePositionZ) => 12

先頭 1 bit は全てなし(default)で、2 ~ 4bit 目が Position X, Y, Z のフラグになっています。
Rotation はもっと後ろの bit で管理されているようですね。
このようなフラグ管理は他の unity object でも同じなのではないでしょうか。

public void Move(Vector3 move, bool crouch, bool jump)

入力値を元にキャラクターを動かすメソッド。
移動方向、しゃがみ状態、ジャンプ状態を引数として受け取っています。

ThirdPersonCharacter ではキー入力した際に、入力した方向が(キャラクタのローカル座標系ではなく)カメラから見てどちらか、という基準でキャラクタを動かします。また、すぐに入力方向に移動を開始するのではなく、入力方向に対してキャラクタが正面を向くまでキャラクタを回転させます。つまり、グローバル座標における方向ベクトルがキャラクタのローカル座標における正面方向と一致させる必要があります。

53 行目の InverseTransformDirection はそのためのベクトルの変換をしています。inverse って逆数かなにかを示しているのかと勘ぐってしまいますが、ローカル座標からグローバル座標への変換の逆、という意味ですね。

続く 55 行目では、ProjectOnPlane を使用して、変換した move ベクトルを地面に投影しています。m_GroundNormal は CheckGroundStatus の中で更新されており、地面に対する法線ベクトルが代入されています。

平面を移動する場合と坂を移動する場合で同じ速度になるようにしているということですね。

56 行目の Mathf.Atan2 では何度方向転換する必要があるかを算出しています。move.z が現在の正面方向になるので、入力した方向との角度の差分は tan(move.x / move.z) で出すことができます。角度はラジアンで m_TurnAmount に格納されます。後は ApplyExtraTurnRotation() でキャラクタを回転させます。

void CheckGroundStatus()

地面に接しているか否かを判定して、それぞれの場合のパラメータを設定しています。
Physics.Raycast の第一引数は ray を投射する位置となり、現在位置から上方向に 0.1f 分のオフセットを含めて設定しています。(キャラクタのオブジェクトは足の裏が基準位置となっているため)
第二引数は ray の投射方向なので重力方向です。
第四引数は ray の投射距離の制限です。

(最初、すぐには 0.1f の意味を解釈できなかったので、この 0.1f は m_CheckGroundOffset みたいな変数で設定してあったら良かったのになぁと思いました。)

if 文の中では、接地フラグ・地面との法線ベクトル・ルートモーションの有効/無効化 をしています。

void ApplyExtraTurnRotation()

キャラクタを(ユーザが入力した方向に向くように)回転させます。
秒間何度回転させるかは Mathf.lerp で算出します。default の設定だと m_ForwardAmount(0 ~ 1)の値に応じて 180 ~ 360 度の間で決定されます。Time.deltaTime は前フレームからの経過時間を返すので、unity 上での処理に時間がかかった場合でも、実時間ベースでは正しい移動量となるように処理がされます。

void HandleGroundedMovement(crouch, jump)

ジャンプ中、かつ、しゃがんでいないときの状態を処理します。
m_Animator.GetCurrentAnimatorStateInfo(0) の引数はレイヤーインデックスとなっています。Animator ウィンドウで確認すると、今回は BaseLayer しかないため、0 しかありません。
if 文に入る状態を満たしていたら、m_Rigidbody.velocity に m_JumpPower を加味したベクトルをセットします。

void HandleAirborneMovement()

空中での動きを制御します。
162 行目では三項演算子を使って m_GroundCheckDistance の値をセットしています。CheckGroundStatus() では足裏から上方向に 0.1f のオフセットした上で下方向に 0.1f の ray を投射していました。落下中は m_GroundCheckDistance が 0.01f になるので ray が地面に届かなくなります。
0.1f に戻す判定は CheckGroundStatus() ではなく、このメソッド内での処理で行うということのようです。(直感的には CheckGroundStatus() でやりそうですが)




というわけで、今回はキャラの移動に必要な判定や値のセット部分を読みました。 疲れたのでアニメーションの適用部分としゃがみモーションに関する部分は次回に。。

次回読むところ

  • void UpdateAnimator(Vector3 move)
  • public void OnAnimatorMove()
  • void PreventStandingInLowHeadroom()
  • void ScaleCapsuleForCrouching(bool crouch)

【Unity】SampleAsset の ThirdPersonCharacter のスクリプトを読む(変数の宣言編)

先日、MMD モデルを Unity 上で動かす際、SampleAsset の ThirdPersonController を利用することでお手軽にやりたかったことが実現できました。(ref. Unity内でMMDモデルを自由に走らせる手軽な方法 - テラシュールブログ

f:id:bcsaitama:20161027023050p:plain

unity および c# の初心者としては、オブジェクトの操作という基本的な実装を担っているスクリプトはしっかり抑えておきたいところです。将来的に拡張して利用したりすることも考えて、勉強がてら以下の2つのスクリプトの中身をじっくり読んでいきます。なお、参考資料は記事の最後にまとめています。

  • ThirdPersonCharacter.cs
  • ThirdPersonUserControl.cs

一度にスクリプトの全てを読んでまとめるのは骨が折れるので、今回は変数の宣言部分に絞ってみます。

[SerializeField] float m_MovingTurnSpeed = 360;
[SerializeField] float m_StationaryTurnSpeed = 180;
[SerializeField] float m_JumpPower = 12f;
[Range(1f, 4f)][SerializeField] float m_GravityMultiplier = 2f;
[SerializeField] float m_RunCycleLegOffset = 0.2f; //specific to the character in sample assets, will need to be modified to work with others
[SerializeField] float m_MoveSpeedMultiplier = 1f;
[SerializeField] float m_AnimSpeedMultiplier = 1f;
[SerializeField] float m_GroundCheckDistance = 0.1f;

Rigidbody m_Rigidbody;
Animator m_Animator;
bool m_IsGrounded;
float m_OrigGroundCheckDistance;
const float k_Half = 0.5f;
float m_TurnAmount;
float m_ForwardAmount;
Vector3 m_GroundNormal;
float m_CapsuleHeight;
Vector3 m_CapsuleCenter;
CapsuleCollider m_Capsule;
bool m_Crouching;

パッと見てまず思ったのは private なのか public なのか分からない! ということ。
c# では変数の宣言時にアクセス修飾子を付けない場合は、自動的に private になるそうです。へ〜。

次に目につくのは、いちいち変数の先頭についている m_。 inspecter 上では消えている。 f:id:bcsaitama:20161027025533p:plain

調べてみると、m_ , _ , k などはどれも private 変数の先頭につけて他の変数と明確に区別するためのもののよう。 今プロジェクトで書いている perl のコードでは _ を使っていますが、それと同じということでした。 unity では ObjectNames.NicifyVariableName が inspecter に表示するときにこれを消してくれている。

次に、[SerializeField]。これは 公式ドキュメント によると

Unity が private フィールドを強制的にシリアライズします。
...
Unity がスクリプトをシリアライズする際、public フィールドのみシリアライズします。 それに加えて Unity で private フィールドをシリアライズさせたい場合、 フィールドに SerializeField 属性を追加できます。

と書いてあります。 シリアライズは「オブジェクトをバイト列に変換して外部と受け渡しできるようにすること」なので、シリアライズしていないものはスクリプトの中でしか利用できません。端的に言うと、 private 変数は inspecter 上に表示されず、値の操作ができません、ということ。
しかし、それでは不便なので SerializeField 属性を付けて inspecter から気軽に値を変えられるようにしているのですね。

最後に、もうひとつの attribute である [Range(1f, 4f)]
m_GravityMultiplier がこれを利用していますが、名前通り変数のレンジを指定するものです。inspecter 上では値を調整するための UI が提供さています。便利!

まとめ

  • c# では変数宣言時にアクセス修飾子を付けない場合は、自動的に private になる
  • m_ , s_ , _ などを変数の先頭につけていても inspecter 上はそれらが除かれて表示される
  • inspecter から気軽に private 変数の値を変えたい場合には、SerializeField 属性を付ける
  • Range 属性を付けると値の制約が設定され、inspecter 上の UI が提供される

変数の宣言だけ見てみましたがしっかり収穫がありました。 どれも基礎的なことですが、ちゃんと理解しておくの大事。

参考URL

SQLステートメントとMySQLで一貫性読み取りが機能しない場合

うっかり運用中のサービスでユーザから参照されるテーブルのパーティションを作成してしまい、MySQLの一貫性読み取りが機能しない場合に遭遇したのでまとめ。ついでに SQL ステートメントについてちゃんとドキュメントを読んだ。

一貫性読み取り

トランザクション分離レベルが REPEATABLE READ である場合、同一トランザクション内では最初の SELECT 文を発行した時点でスナップショットが作成され、以降はそのスナップショットを参照するため他のトランザクションによる変更の影響を受けない。

SQL ステートメント

DCL

データ制御言語 (Data Control Language)。権限を管理するための SQL ステートメント。以下のステートメントが含まれる。

  • GRANT(権限の付与)
  • REVOKE(権限の削除)

DML

データ操作言語 (Data Manipulation Language)。個々のテーブルを操作するための SQL ステートメント。以下のステートメントが含まれる。

  • INSERT
  • UPDATE
  • DELETE
  • SELECT .. FOR UPDATE

DDL

データ定義言語 (Data Definition Language)。データベース自体を操作するための SQL ステートメント。以下のステートメントが含まれる。

  • CREATE
  • ALTER TABLE
  • DROP TABLE
  • TRUNCATE(DELETE 文とは動作が異なる)

DDL ステートメントは自動的に現在のトランザクションをコミットし、それらをロールバックすることはできない。

一貫性読み取りが機能しない場合

特定の DDL ステートメントでは、一貫性読み取りが機能しない。

  • DROP TABLE

    MySQL が削除されたテーブルを使用できず、そのテーブルは InnoDB によって破棄されるため、一貫性読み取りが機能しない。

  • ALTER TABLE

    元のテーブルの一時コピーが作成され、元のテーブルは一時コピーが構築されるときに削除される。そのため、一貫性読み取りが機能しない。MySQL 5.6.6 時点では、ER_TABLE_DEF_CHANGED エラー「Table definition has changed, please retry transaction」が返される。

パーティション作成は ALTER TABLE なので、以下のような順序で操作が行われるとエラーとなってしまう。

  1. トランザクション開始
  2. tableA の参照クエリを発行 (ok)
  3. tableA のパーティション作成
  4. tableA の参照クエリを発行 (error)

参考URL

Redshift で UNIX time を TIMESTAMP (タイムゾーン有) に変換する

Amazon Redshift で時刻が UNIX time で記録されており TIMESTAMP 型に直したくて悩んだ。integer なので CAST が使えない。

で、結論から言うとこう。

select *, CONVERT_TIMEZONE('JST', TIMESTAMP 'epoch' + unix_time * INTERVAL '1 second') time_stamp from user limit 1;

postgreSQL自体の再起動が許されるのであれば、以下でタイムゾーンを設定してから再起動でタイムゾーン変更ができるらしい。タイムゾーンの設定が合っていれば CONVERT_TIMEZONE が不要になる。

timezone = JST-9

参考: * PostgreSQLのデフォルトのタイムゾーン - かずきのBlog@hatena * CAST 関数および CONVERT 関数 - Amazon Redshift

6/23 追記

DATE_ADD を使うやり方がありました。こっちの方が覚えやすそう。

select *, DATE_ADD('second', "time", '1970-01-01 09:00:00') time_stamp from gacha limit 1;

ブログ始めました

いわゆる技術ブログを開設しました。
趣味のブログにおもむろに作ったスクリプト投下したりしてましたが、やはり別で持っていた方が良いなということで。
肩肘張らずに頻度重視で、細々とした日々の学習内容を綴っていきたい所存。