feature image

2023年8月29日 | ブログ記事

プログラマフレンドリーなUnityの使い方

この記事は traP夏のブログリレー 9 日目の記事です

こんにちは、23B期待の新星(自称)のcobaltです。エディター拡張について書こうと思っていたのですが、その前に前提について書いた記事があってもいいと思ったので変更しました。

動機

言わずと知れた有名ゲームエンジン、Unity。Unityは様々な面でプログラマの面倒を見てくれますが、人間がゲームを作っている以上どうしてもミスというものは生じてしまいます。コード上のバグや、数値設定のミスなどです。

今回はUnityを使う上でのお作法というか、ミスを減らすための仕組みづくりの方法について紹介していきます。

ドキュメントコメントを書こう

さっきまでの書き出しに反してこれはUnity自体の話ではないのですが、C#には『XML ドキュメント コメント』という機能があります。これはC#のソースファイルにXML形式でコメントを書いておくと、コンパイラやIDEがそれに応じて便利機能を提供してくれるというものです。

Visual Studioユーザーであれば、シンボルをマウスオーバーするとこのような表示が出てくることがあるはずです。

IntelliSenseのクイックヒント

ドキュメントコメントを使えば、自分で作ったクラスにもこのような説明を付けることができます。例えば以下のようなクラスを作ったとします。

/// <summary>
/// 商品情報のクラス
/// </summary>
public class ProductEntry
{
    /// <summary>
    /// 日本円での税抜き価格 正の値である必要があります
    /// </summary>
    public readonly int Price;

    public ProductEntry(int price) => Price = price;

    /// <summary>
    /// 日本円での税抜き価格 正の値である必要があります
    /// </summary>
    /// <param name="TaxRate">乗算する税率の百分率</param>
    public int CalculateTaxIncludedPrice(float TaxRate) => (int)(Price * TaxRate / 100.0f);
}
ドキュメントコメント付きのクラス宣言

クラスを使う人には、以下のような説明が表示されます。

summaryタグによってクイックヒントが表示されている
パラメーターヒントの例

こういった説明文は、変数名などから見えにくいクラスの情報をプログラマに素早く伝え、バグを未然に防ぐための強力な手段になりえます。ドキュメントコメントには他にも、他のメンバーやWebサイトへのリンク、型パラメーターへの注釈、サンプルコードといった様々な内容を書くことができます。ただのコメントの代わりにドキュメントコメントを書いて、自然言語での説明も提供しましょう!

RequireComponentを使おう

Unityでコンポーネントの操作をするとき、GetComponentを使うことがあると思います。GetComponent<T>()はGameObjectにアタッチされているT型(またはその派生型)のコンポーネントを取得するという、Unityのコンポーネント指向を支える重要なメソッドです。

しかしGetComponentをするまでは、本当にオブジェクトにそのコンポーネントがアタッチされているのかは分かりません。もちろんTryGetComponentなどを使って例外処理やAddComponentをすればよいのですが、GetComponentの処理が走る前に仕組みで対策出来たらうれしいですよね?そこで登場するのがRequireComponentAttributeです。例えば下のような、BGMを再生するスクリプトを書いたとしましょう。

[RequireComponent(typeof(AudioSource))]
public class BgmPlayer : MonoBehaviour
{
    private AudioSource audioSource;
    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        audioSource.Play();
    }
}
AudioSourceでBGMを再生する例

3行目にある[RequireComponent(typeof(AudioSource))]が肝です。このBgmPlayerクラスは、アタッチされたGameObjectにAudioSourceが付いていることを前提とした内容がStartメソッド内に書いてあります。この前提をRequireComponentという形でコードに書くことで、Unityは「このクラスはAudioSourceを必要とする」ということを認識し、BgmPlayerをアタッチしたときに自動的にAudioSourceを追加します(既にAudioSourceがある場合以外)。こうすればPlayモードに入ってNullReferenceExceptionでエラーが出るより前に、自動的にアタッチし忘れ問題を防ぐことができます。

Rangeを使おう

RequireComponentの他にミスを減らすのに便利なAttributeとして、RangeAttributeがあります。これはシンプルに言うと、変数にセットできる値の範囲を制限するために使うAttributeです。

Unityのインスペクターからステータスなどを設定するとき、プレイヤーのジャンプ力を強く設定し過ぎて吹き飛んで行ってしまったり、弾速を遅くしすぎてゲームにならなかったりした経験がある人もいるのではないでしょうか。intやfloatは人間の感覚では値の範囲が極めて広いので、適正な数値設定がどれくらいなのか分からなくなりがちです。そこでRangeAttributeの出番です。メンバ変数に[Range(/*定数値*/,/*定数値*/)]と付けてみましょう

public class PlayerMovement : MonoBehaviour
{
    [SerializeField][Range(3.0f, 6.0f)] float MovementSpeed;
    [SerializeField][Range(2.0f, 5.0f)] float JumpHeight;

	(略)
}

するとインスペクタの表示がいい感じのスライダー表示に変わり、その範囲の値を設定することが一目でわかります。

RangeAttributeなし
RangeAttributeあり

負の値を入力して欲しくない時や、スライダーで簡単に値を調整したいときなどに使うといいでしょう。ちなみにint型にも使えます。

(発展編)OnDrawGizmoは便利だよ

UnityにはGizmo(ギズモ)という機能があります。これはプレビューやデバッグなどによく使われる、ゲームには登場しない視覚要素の一種で、例えばDirectionalLightを選択すると表示される光のアイコンと光の方向を表す黄色い線は、どちらもGizmoの一種です。

Gizmoの例(右上)

Gizmoは自作したスクリプトなどにも付けることができます。例えば弾幕ゲームで、3-Way弾を撃つ方向をプレビューする以下のようなギズモは

次のようなたった四行のメソッドで描くことができます。

private void OnDrawGizmos()
{
    Gizmos.color = Color.red;
    Gizmos.DrawLine(transform.position, transform.position + new Vector3(0, 5));
    Gizmos.DrawLine(transform.position, transform.position + Quaternion.Euler(0, 0, 20) * new Vector3(0, 5));
    Gizmos.DrawLine(transform.position, transform.position + Quaternion.Euler(0, 0, -20) * new Vector3(0,5));
}

常に表示されるのが邪魔であれば、OnDrawGizmosの代わりにOnDrawGizmosSelectedを使うと良いです。3-Way弾の拡散の角度をインスペクタからスライダーを弄って調整し、その結果をリアルタイムにGizmoでプレビューする、といったことが出来るわけです。そう考えると便利そうに聞こえませんか?

(発展編)エディター拡張を書こう

なんだかんだ私はエディター拡張が大好きなので最後はこれです。エディター拡張は機能がとてつもなく多いので別の記事にまとめて書こうと思うのですが、エディター拡張それ自体は特に難しい技術ではありません。インスペクター拡張でコンポーネントに不正な値を入力できないようにしたり、Property Drawerで自作クラスをいい感じに表示できるようにしたり、ウィンドウを追加してステージに登場する敵の情報を一括管理したりと、プログラマやレベルデザイナーの助けになる様々な機能を拡張することができるようになっています。特に他のプログラマやレベルデザイナーが触れる部分は、いい感じのUIになっていると嬉しいでしょう。

まとめ

プログラマであれば、人間はミスをするということを(多くは自分がミスをした経験を通して)痛いほど知っていることでしょう。C#やUnityも人間がミスをするということをよく知っているので、それを防ぐような様々な機能を提供してくれています。ここに書いた機能はいずれも「無くても動く」ものでゲーム開発で必須ではありませんが、ミスを減らし効率や体験を向上させるためにもぜひ活用していただきたいです。

明日は@karoさんの記事です。お楽しみに!

cobalt icon
この記事を書いた人
cobalt

23B コンピュータの天才 1つのC#を操る

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2023年11月21日
School Breakin' Tag -新感覚おにごっこ-
s9 icon s9
2023年4月17日
ポケモンを飼いたい夢を叶える
tqk icon tqk
2023年9月3日
タイピング&アクション『TypeTheCode』作りました
wal icon wal
2023年4月25日
【驚愕】作曲4年目だった男が大学3年間ゲームサウンドに関わった末路...【ゲームサウンドのお仕事について】
tenya icon tenya
2021年8月12日
CPCTFを支えたWebshell
mazrean icon mazrean
2023年4月25日
15時間でゲームを作った #Oxygenator
Komichi icon Komichi
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記