うどんてっくメモ

技術的なメモをまったりと

【Rust】ゲームエンジンBevyのサンプルゲームを作った話 -ECSの基本実装の説明-

はじめに

先日Rust製のゲームエンジンBevyを使ったサンプルゲームを公開しました。 github.com

こちらの公開したサンプルゲームをベースに、Bevyで実装する際の基礎的な知識となるECSアーキテクチャの実装を説明しようと思います。

Bevy

BevyはRust製のデータ指向なゲームエンジンです。

bevyengine.org

無料のオープンソースで、ソースコードGitHub上に公開されています。

github.com

ECSアーキテクチャを採用しており、開発も結構盛んに行われています。執筆時点ではver0.5が最新の安定バージョンとして公開されています。

2Dおよび3Dにも対応しており、クロスプラットフォーム対応、ホットリロード可能など、発展途上ながらも多くの機能を備えています。筆者はAmethystというゲームエンジンの本も書いたりしたのですが、このBevyも触ってみて期待しているゲームエンジンのひとつです。

サンプルゲームについて

サンプルゲームではBevyの提供するECSアーキテクチャに則って、シンプルな2Dゲームのフローを実装しています。

f:id:myudon:20210918185533g:plain

プレイヤーを操作して落ちてくる岩を回避しながら林檎をとる、ごくごくシンプルなゲームです。
レベルデザインなどをしっかりやっている訳ではなく、ゲームのサンプルを実装したというよりはシステムのサンプルを実装したというのが正しいかもしれません。
Rustの文法としても比較的シンプルに実装しているので、初学者の方でも読み解きやすいかなと思います。(The Rust Programming Languageを一通り読めば多分大丈夫です。)

このゲームフローを構成するEntity、Component、Systemそれぞれの実装について説明します。

BevyにおけるECSの実装

BevyではEntityとComponentについては構造体、Systemについてはメソッドで定義を行います。それぞれの実装について、サンプルゲームの例を元に見ていきます。

EntityとComponent

まずはEntityですが、識別するidを持つシンプルなstructとして定義されています。

#[derive(Clone, Copy, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub struct Entity {
    pub(crate) generation: u32,
    pub(crate) id: u32,
}

EntityはWorldというゲーム中のEntityとComponentを統括する機能で管理されます。

次にComponentです。サンプルゲーム内では林檎、岩、プレイヤーがあり、これらの機能はComponentとしてシンプルなstructで実装されています。 例としてプレイヤーの実装を示します。

const PLAYER_LIFE: i32 = 3;

// プレイヤーを表すComponent、基本的にライフの値のModel
pub struct Player {
    pub life: i32,
}

impl Default for Player {
    fn default() -> Self {
        Player::new(PLAYER_LIFE)
    }
}

impl Player {
    pub fn new(life: i32) -> Self {
        Player {
            life
        }
    }
    
    pub fn reset(&mut self) {
        self.life = PLAYER_LIFE;
    }
}

このPlayerをプレイヤーの機能を果たすComponentとして取り扱います。
structをComponentとして処理する工程は至ってシンプルで、Entityの生成時に紐付けるだけです。Unofficial Bevy Cheat Bookのサンプルを下記に示します。

struct SampleComponent;

pub fn setup(mut commands: Commands) {
    commands
        .spawn()
        .insert(SampleComponent);
}

CommandはWorldのEntityとComponentに対してmutableな操作を行う機能です。Entityの生成や削除、Componentの操作などを行う際にはCommandを介して処理します。 実際のPlayerの紐付けの実装を次に示します。

// 必要なデータを用意
let player_texture_handle = asset_server.load("textures/player.png");
let player_position = Vec3::new(0., -200., 0.);
let player_rotation = Quat::from_axis_angle(Vec3::Y, 0.);
let player_scale = Vec3::splat(1.);
commands
// スプライト表示系のComponentと共にEntityを作る
    .spawn_bundle(SpriteBundle {
        material: materials.add(player_texture_handle.into()),
        sprite: Sprite::new(Vec2::new(90., 90.)),
        transform: Transform {
        translation: player_position,
            rotation: player_rotation,
            scale: player_scale,
        },
        ..Default::default()
    })
// ゲーム側でプレイヤーのEntityとして紐付けたいComponent
    .insert_bundle((
        Player::default(),
        Mover::default(),
        GameScene
    ));

CommandからEntityを生成してメソッドチェーンの形でComponentを紐付けています。ちなみにPlayerと一緒に紐づけているMoverはその名の通りサンプルゲーム中のプレイヤーや林檎といったものを動かすためのComponentです。このようにEntityを生成して必要な機能のComponentを紐付ける、というのが基本的なオブジェクトの生成フローとなります。

System

サンプルゲームではsrc/system配下にゲームロジックを形成する以下のsystemの実装があります。

├── collision.rs // プレイヤーと落ちてくるものの衝突判定
├── display.rs // UIなどの表示
├── object_spawn.rs // 落ちてくる林檎と岩の生成
├── player.rs // ユーザー入力の処理
├── time.rs // 時間の処理
└── translate.rs // プレイヤーや落ちてくるものの移動処理

それぞれがComponentへの作用を担っており、対象となるComponentの配列を舐めて処理を行います。ECSアーキテクチャの特徴的な処理ですね。 前述にもあるようにBevyではその作用をシンプルなメソッドとして実装します。例としてtranslate.rsの実装を示します。

// Moverの速度に沿った位置更新
pub fn translate_mover_system(mut query: Query<(&Mover, &mut Transform)>) {
    // 速度に伴ってtransformの位置情報を更新
    for (mover, mut transform) in query.iter_mut(){
        let velocity = &mover.velocity;
        transform.translation.x += velocity.x;
        transform.translation.y += velocity.y;
    }
}

引数としてQueryという型を渡していますが、これがComponentの参照に用いる機能です。ジェネリクスで対象となるComponentの型を宣言し、イテレータを使用して作用を処理します。 また、Entityの生成や破棄を行いたい場合にもCommandを引数にして処理します。collision.rsの実装の一部を抜粋します。

pub fn collision(
    mut commands: Commands,
    mut game: ResMut<Game>,
    mut player_query: Query<(&mut Player, &Transform, &Sprite)>,
    mut apple_query: Query<(Entity, &mut Apple, &Transform, &Sprite)>,
    mut block_query: Query<(Entity, &mut Block, &Transform, &Sprite)>) {
    
    if let Ok((mut player, player_transform, player_sprite)) = player_query.single_mut() {
        for (block_entity, block, block_transform, block_sprite) in block_query.iter_mut() {
            if block_transform.translation.y < OBJECT_DESPAWN_Y {
                commands.entity(block_entity).despawn(); 
                break;
            }
    ...

このように、Systemの実装ではCommandおよびComponentのQueryを引数としたメソッドを実装することになります。 実装したメソッドは専用の機能を用いてゲームフローに組み込みます。Unofficial Bevy Cheat Bookより引用した実装を以下に示します。

fn main() {
    App::build()
        // ...

        // run it only once at launch
        .add_startup_system(init_menu.system())
        .add_startup_system(debug_start.system())

        // run it every frame update
        .add_system(move_player.system())
        .add_system(enemies_ai.system())
        .run();
}

into traitを介してメソッドはSystemに変換されています。Amethystだと専用のtraitを実装したstructとして表現していますが、こちらはよりシンプルな形と言えます。 また、サンプルゲームではSystemSetという機能を用いてタイトル画面やゲーム中といったゲーム中の状態ごとのSystemの分割を行っています。

// ゲーム内の状態
// lib.rs
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum GameState {
    // クリックしてゲーム開始を待つ画面
    Title,
    // ゲーム中
    Playing,
    // クリックしてゲーム終了を待つ画面
    GameOver,
}

// SystemSetの構築
// system.rs
pub fn title_enter_system_set() -> SystemSet {
    // タイトル画面の文字を出す
    SystemSet::on_enter(GameState::Title)
        .with_system(setup_title_ui.system())
}

pub fn game_enter_system_set() -> SystemSet {
    // ゲーム中に必要な文字の表示とかを出す
    SystemSet::on_enter(GameState::Playing)
        .with_system(setup_game_ui.system())
}

pub fn game_update_system_set() -> SystemSet {
    // ゲームのルールを構成するためのSystem
    SystemSet::on_update(GameState::Playing)
        .with_system(collision.system())
        .with_system(check_spawn_object.system())
        .with_system(player_input_system.system())
        .with_system(translate_mover_system.system())
        .with_system(game_time_display_system.system())
        .with_system(game_score_display_system.system())
        .with_system(player_life_display_system.system())
        .with_system(update_game.system())
}

// SystemSetの組み込み
// main.rs
fn main() {
    App::build()
        // ...
        // Titleの開始、更新、終了でのSystem
        .add_system_set(title_enter_system_set())
        .add_system_set(title_update_system_set())
        .add_system_set(title_exit_system_set())
        // Playingの開始、更新、終了でのSystem
        .add_system_set(game_enter_system_set())
        .add_system_set(game_update_system_set())
        .add_system_set(game_exit_system_set())
        // GameOverの開始、更新、終了でのSystem
        .add_system_set(game_over_enter_system_set())
        .add_system_set(game_over_update_system_set())
        .add_system_set(game_over_exit_system_set())
        .run();
}

一定複雑なSystemの管理を行う場合にはSystemSetを活用するのがおすすめです。 以上がSystemの実装になります。

おわりに

ざっくりとBevyで開発する上で一番大切なECSアーキテクチャを実装する部分について説明しました。ゲーム内のオブジェクトとロジックをBevy上でどうやって組み上げるか、なんとなく伝わっていたら幸いです。
BevyはECSアーキテクチャをかなり直感的に実装できるな、という感覚が筆者的にはあって良かったです。

参考文献

公式ドキュメントのIntroductionです。Bevyを使う際にはまず一読しておくのがおすすめです。

bevyengine.org

Bevyの実装をする上で参考になる非公式のドキュメントです。Bevyの根幹となる機能についてある程度まとめてくれています。文中でも引用させていただきました。

Introduction - Unofficial Bevy Cheat Book

また、こちらのブログでもBevyでHello Worldするまでの手順を取り上げています。参考にさせていただきました。

blog.livedoor.jp

【Unity】新しいInputSystemのInputStateHistoryを使って入力情報を記録する

はじめに

本記事では新しいInputSystemの機能の説明や、導入手順などは省略します。
検証しているツールとパッケージのバージョンは以下の通りです。

  • Unity 2021.1.3f1
  • Input System 1.0.2

バージョンによっては挙動が違うことがありますので、ご注意ください。

InputStateHistoryの使い方

新しいInputSystemにおけるInputControlの記録を行うための機能です。 InputControlについてはこちらのドキュメントを参照してください。
Controls | Input System | 1.0.2

簡単なサンプルをまず示します。

// 生成
var history = new InputStateHistory(Mouse.current.leftButton);

// 記録開始
history.StartRecording();

// 何かしらするとして

// 記録終了
history.StopRecording();

foreach (var record in history)
{
     Debug.Log(record.ReadValue<float>());
}

history.Dispose();

Mouse.current.leftButtonはマウスの左クリックのInputControlです。記録するInputControlを元にInputStateHistoryを生成し、StartとStopを呼ぶというシンプルな使い方です。
InputStateHistory自体はRecordという記録対象のInputControlに加え、時間や前後のInputControlを参照できるstructのIEnumerableとなっています。
Mouse.current.leftButtonはクリックしてなければ0、クリックしたら1を返すInputControlです。なので、上のサンプルでは0と1という入力の値の履歴を取得することができます。

InputStateHistoryのコンストラクタに渡すのは、記録対象となるControlです。 Mouse、GamePad、JoyStick、TouchScreenといった異なる入力インターフェイスについても、InputControlを実装しているものについては記録対象とすることが可能です。

// クリックしたかどうかを取るButtonControl
var mouse_history_is_click = new InputStateHistory(Mouse.current.leftButton);

// 移動量を取るVector2Control
var mouse_history_position = new InputStateHistory(Mouse.current.delta);

// クリック回数を取るIntegerControl
var mouse_history_click_count = new InputStateHistory(Mouse.current.clickCount);

// タッチ情報をとるTouchControl
var touch_history = new InputStateHistory(TouchScreen.current.primaryTouch);

// GamePadのAボタンを取るButtonControl
var gamepad_history = new InputStateHistory(Gamepad.current.aButton);

コンストラクタには具体的なInputControlだけでなく、パスでの指定も可能です。InputControlのパスの表現についても前述のドキュメントに詳しく書いてあります。
このように任意のInputControlについてよしなに記録を行えるのがInputStateHistoryの機能となります。

開始と終了を宣言するだけではなく、入力を記録したときに処理を挟むことも可能です。

// イベントの登録
history.onRecordAdded += record => Debug.Log(record.ReadValue<float>());

InputControlについてはそのControl自体を知りたいという場合は少なく、たとえばボタンであればクリックしたかどうか、カーソルであれば位置など、大抵はControlが観測する値についてだけ関心がある場合が多いです。 その場合はジェネリクス付きのInputStateHistroyを活用することでシンプルに値を取得可能です。

var history = new InputStateHistory<Vector2>(Touchscreen.current.position);
history.StartRecording();

// 何かしらの処理

history.StopRecording();

foreach (var record in history)
{
     // Vector2が返ってくる
     Debug.Log(record.ReadValue());
}

history.Dispose();

また、入力情報が多すぎるので直近のデータが一定数だけ欲しいという場合にはhistoryDepthを指定することで実現できます。

var history = new InputStateHistory<Vector2>(Gamepad.current.leftStick);
history.historyDepth = 100;
history.StartRecording();

終わりに

入力履歴をデータ化することで、たとえば特定の事象を引き起こす操作の再現や、検証ルーチンの自動化などさまざまな活用が期待できます。 ここで紹介したInputStateHistoryの他にも入力情報を可視化するVisualizerやTestなど、検証に有効な機能が色々と用意されています。 InputSystemの機能を活用して実装の改善だけでなく、開発の効率化にも目を向けていきたいです。

Plastic SCM+Unity入門 -Plastic SCM概要と導入とUnity用プラグイン-

はじめに

文中で説明、および検証しているツールやパッケージのバージョンは以下の通りです。バージョンによっては説明に差異がある場合もありますのでご注意ください。

  • Plastic SCM 10.0.16.5397
  • Unity2021.1.0f1
  • Plastic SCM Plugin for Unity (beta) 1.4.1

Plastic SCMとUnity

Plasctic SCMはCodice Softwareが提供するバージョン管理ツールです。

f:id:myudon:20210502201034p:plain www.plasticscm.com

2020年にUnityがこのCodice Softwareの買収およびUnityとPlastic SCMの連携を公式のブログで発表しました。

blogs.unity3d.com

バージョン管理ツールといえばGitやSVNといった有名なものがありますが、他の著名なツールとの比較は公式サイトで公開されています。Plastic SCMが最強そうな比較になっていますが、特徴の雰囲気は掴めると思います。

Plastic SCM - Comparisons

公式の比較の中でも言及されていますが、Plastic SCMの特徴には大きく以下のようなものが挙げられます。

  • 集中型か分散型か選択可能
  • 巨大なバイナリを扱える
  • ゲームクリエイターをターゲットにしたサポート

集中型か分散型か選択可能

バージョン管理ツールにはSVNのように使用者が単一のリポジトリに対して操作を行う集中型と、Gitのように使用者それぞれのローカル環境にリポジトリのクローンを用意して操作を行う分散型が存在します。Plastic SCMは両方に対応し、用途に合わせて切り替えが可能になっています。

巨大なバイナリを扱える

巨大なバイナリというのは100MBを超えるようなファイルを指します。ゲーム開発でいえば2Dおよび3Dのクリエイティブコンテンツが大きくなりがちですが、それを取り扱えるのはゲーム開発にとって都合のいいポイントのひとつです。また、データの送受信が高速だったり、処理が高速だったりするのもPlastic SCMは売りとして掲げています。こちらは自分は検証していないのでゲーム開発のケースでどれほど効率化が見込めるかは言い切れませんが、公式で説明があるので興味のある方は参照してみてください。

Plastic SCM - Performance - Optimized network channel for high latency Plastic SCM - Performance - Performance results of Plastic SCM

ゲームクリエイターをターゲットにしたサポート

Plastic SCMはゲーム開発のサポートを大きく意識しており、公式サイトでもゲーム開発者に向けた案内が存在します。

Plastic SCM - The version control for game developers

たとえば現状であれば次のようなサポートが提供されています。

クリエイター向けのGUIツールは「Gluon」という名前でエンジニア用のGUIツールとは別に用意されています。クリエイターには不要だったり過度に高度だったりする機能を排除してシンプルな操作性を実現し、クリエイティブのプレビュー確認を可能にしてくれます。ゲーム開発ではクリエイターがバージョン管理ツールを触ることも多く技術的なハードルが課題に上がりがちですが、そこをしっかりフォローしてくれているのはありがたいです。公式のドキュメントはこちらになります。

Plastic SCM version control · Plastic Gluon guide

Unity用のプラグインについては後述しますので、詳しくはそちらを参照してください。Unity上でPlastic SCMの操作を行うためのプラグインとなっています。

Plastic SCMの導入

Plastic SCMにはCloudとEnterpriseの2つのプランが存在します。基本的に有償ですが、Cloudは使用形態やストレージの使用量によって無料になります。

f:id:myudon:20210425190443p:plain:w640

本記事ではこのCloudの導入について説明します。Cloudの申し込みは公式サイトより可能です。

Plastic SCM - Licensing and pricing options

フォームに必要な情報を入力し、正常に完了するとメールが届きます。

f:id:myudon:20210502205725p:plain:w640

次にツールをインストールします。公式サイトからインストーラーをダウンロードし、実行します。 必要な設定を入力してインストールしてください。(特別な設定などもないため詳細な説明は省略します。)

Plastic SCM - Download Plastic SCM

インストールが完了するとGUIツールおよびCLIが使えるようになります。CLIは「cm」コマンドで確認できます。

>cm version
10.0.16.5397

詳しいコマンドの説明は「cm help」と叩くか、公式のドキュメントを参考にしてください。

Plastic SCM version control · CLI guide

次にGUIツールのセットアップを行います。 インストールしてPlastic SCMを立ち上げるとサインインの画面になるので、登録したメールアドレスを入力します。サインインが完了すると集中型か分散型か最初の設定として聞かれるので、使用する方を選択してください。

f:id:myudon:20210502203456p:plain:w640

以上の手順でGUIツールのセットアップが完了となります。

Plastic SCMの基本的な機能

バージョン管理ツールということで、大元のリポジトリとのやりとりが存在します。他のバージョン管理ツール同様にブランチで変更を管理します。変更を統合する際にはマージして、とこの辺は特段変わった点はありません。

リポジトリおよびブランチに関する基礎的なコマンドをざっくりと紹介します。

  • checkin: ブランチへの変更の反映 (Gitでいうcommit)
  • update: ブランチの変更の取り込み (Gitでいうcheckout)
  • push: ローカルリポジトリのブランチをリモートリポジトリに反映 (Gitでいうpush) ※分散型でやる場合
  • pull: リモートリポジトリのブランチをローカルリポジトリに反映 (Gitでいうpull) ※分散型でやる場合

基本的には他のバージョン管理ツールでも見かけるような操作です。集中型と分散型が選択できるということで、分散型の時のみローカルのリポジトリをリモートのリポジトリに反映するためにpush/pullを活用します。

そしてGUIツールではワークスペースや手元の変更が確認できたり、上記の処理をGUI操作だけで行うことが可能です。

f:id:myudon:20210503005545p:plain:w640

f:id:myudon:20210503005603p:plain:w640

ブランチの確認やマージなどもGUI操作で完結します。ブランチエクスプローラーではブランチのダイアグラムを可視化します。

f:id:myudon:20210503000402p:plain:w640 f:id:myudon:20210503010149p:plain:w640

UnityのPlastic SCMプラグイン

Unity用のプラグインは現状Asset Storeで公開されています。(今後はPackage Managerへの移行を検討してそう?)

assetstore.unity.com

自分のアセットとして追加したら、Package Managerからプロジェクトへインポートします。

f:id:myudon:20210425192744p:plain:w640

導入したらWindowメニューからPlastic SCMを選択します。ワークスペースの作成を促されるので、プロジェクト用のワークスペースを作成します。

f:id:myudon:20210425195023p:plain:w640

f:id:myudon:20210425195040p:plain:w640

ワークスペースの作成が完了するとEditor Windowが開きます。checkinなどの操作がUnity上でさくっとできるのは便利です。 現在のブランチの履歴もUnity上で確認することが可能です。

f:id:myudon:20210425200641p:plain:w640

このようにGUIのレイアウトもある程度整っていて使いやすいです。しかし、本当に基礎的な操作と確認ぐらいしか現状できないのと、読み込みが遅い感触があるので、現状はPlastic SCM側のツールやCLIを使った方が便利かなというのが個人的な感想です。ただ、まだベータ版なので今後に期待したいです。

おわりに

Plastic SCMは日本ではそんなに使用事例がなく、日本語の情報も少ないです。自分はUnityが連携するという発表をして初めて知り、調べた次第です。 ゲーム開発にフォーカスしており、Unityと連携しているということで、今後も注目していきたいツールです。

参考資料

詳しいPlastic SCMに関する説明は公式が提供しているPlastic SCM Bookを参考にしてください。英語ですが、詳しい機能やその思想や用途について説明されています。

www.plasticscm.com

【Unity】アプリケーションの検証を自動化しQAを効率化する新機能「Unity Automated QA」の紹介

はじめに

つい先日、Unity公式が開発中であるAutomated QAという機能が紹介されました。

blogs.unity3d.com

QA効率化のためにUI操作を記録し、それを再生できるという機能っぽいです。この記事では機能の概要と使い方の説明、および触ってみての所感について話していきます。

※ 本記事で使用するUnityおよびAuromated QAのバージョンは以下の通りです。バージョンによっては挙動および機能が異なる場合がございますので、ご了承ください。特にAutomated QAはかなりpreviewの機能なので、大きく内容が変わる可能性もあります。

  • Unity 2021.1.0f1
  • Automated QA 0.2.0-preview.3 - April 08, 2021

Automated QAとは

公式でAutomated QAについて機能の概要がこう書かれています。

The Automated QA package enables users to record and playback touch or drag interactions with the UI of a Unity Project and optionally use recordings to drive Unity Tests – in the editor, on an iOS or Android device.

ユーザーのUI操作を記録し、リプレイとして実行できるという機能です。どのように活用できるかというと、

  • QAが検証でルーチン化できる部分を記録し、自動で検証
  • QA側が確認した問題の発生手順を記録し、エンジニア側で再現(逆も然り)

といったように、アプリケーションの検証、いわばQAのフローを効率化できる機能になっています。実際に簡単なUIを配置して、使ってみた動画がこちらになります。ボタンの押下を記録し、再生しています。

f:id:myudon:20210418162345g:plain

この機能はタップとドラッグの2つの操作を記録できるのですが、特徴的なのが対象のGameObjectの名前とタグも記録されるというところです。たとえば「Button」という名前のUIのGameObjectをタップした場合はその「Button」という名前とそのタグも一緒に記録されます。たとえばリプレイした時にちゃんと想定する画面になっているかといったように、一連の操作に対するオブジェクトの有無の検証に活用できる機能です。

Automated QAの導入

Automated QAはPackage ManagerのAdd package from git URLから追加します。「com.unity.automated-testing」と入力してください。

f:id:myudon:20210417222440p:plain

正常に導入が完了するとWindowのメニューに表示されるようになります。

f:id:myudon:20210418181958p:plain

Recorded Playback

UIの操作の記録と再生はRecorded PlaybackのWindowから実行します。メニューで選択するとWindowが開きます。

f:id:myudon:20210417222804p:plain

f:id:myudon:20210417222850p:plain

Recordを押すとWindowが閉じ、シーンがPlayされます。Play中のUI操作は記録され、終了すると記録した情報がjson形式で保存されます。保存される場所はWindow上で指定できるRecording assets pathになり、デフォルトだとAssets/Recordingsに指定されています。jsonの中身は主にUI操作の位置や対応するGameObjectの情報の配列になっています。

f:id:myudon:20210417223336p:plain

保存されたデータはRecorded PlaybackのWindowに表示されています。Playを押すとシーンのPlayと共に記録されているUI操作が順に実行されます。

f:id:myudon:20210417223512p:plain

実行中に前述した操作の対象となるGameObjectがなかった場合(記録では「Button」を押しているはずがなかった場合)はとりあえず座標に基づいて操作の実行はしながらもwarningを出すというのが現状っぽいです。

f:id:myudon:20210418015603p:plain

この辺は何か別の結果レポートだったり、独自のログを含むエラーだったり、検証結果として出力する仕組みが欲しいところです。ただ、自動で操作して意図したヒエラルキーになっているかどうか検証するには十分便利かなと思います。

Composite Recordings

ゲームをPlayして終了するまでの一連の操作の記録だけではなく、部分的な記録だったり、記録同士の組み合わせるComposite Recordingsという機能も用意されています。メニューからAutomated QA→Advanced→Composite Recordingsを選択するとWindowが開きます。

f:id:myudon:20210418015935p:plain f:id:myudon:20210418015846p:plain

前述したRecorded Playbackと同様に記録の開始と停止、実行ができます。違う点としては部分的な記録であるSegment Recordingが記録できることです。SAVE SEGMENTを押すとその時点までの操作を断片的に出力してくれます。

f:id:myudon:20210418162940g:plain

生成したSegment Recordingは好きに組み合わせて一連の操作にするComposite Recordingの生成が可能です。CREATE COMPOSITE RECORDINGを押すとComposite Recordingを生成する画面になります。

f:id:myudon:20210418173422p:plain

+と-で追加と削除をして、SAVE COMPOSITE RECORDINGを押すとひとつの記録となったデータが生成されます。ちなみに組み合わせるのは前述したRecording Playbackでも可能です。
この機能は似たような操作の自動化を量産するのに役立ちます。たとえばあるUIの遷移フローの検証をしたいと考えてみます。画面の種類をアルファベットとして、A-B-C、A-D-B-Cといった部分的に一致しつつも分岐するフロー全てを自動で操作したいと考えた時に、何回も同じ部分を記録するのはやや手間です。そこでComposite Recordingsを活用することでそれぞれのA-B、B-Cといった単体の遷移操作を記録し、組み合わせで実現することが可能になります。網羅的な検証を自動化する際に活躍する機能ですね。

Automated QAの展望

手元で操作のリプレイ検証ができるだけでも一定の効率化は達成できますが、さらなる機能改善として公式のブログでは次の2つが書かれています。

Coming Soon: Composite Agents

We are adding support for the creation of game playing agents composed of recordings, C# scripts, and ML-Agents.

リプレイだけではなく、C#スクリプトおよびML-Agentsを用いたAIでも自動操作を可能にする予定っぽいですね。UI操作をスクリプトで書けるようになるのは必要になりそうだなと思います。たとえば適当にUIを押すようなモンキーテストだったり、条件によって分岐するようなちょっと複雑なテストだったりと、ある程度ロジックを挟み込みたいケースは需要があり、それを普段の実装と同じC#で実現できるのは便利そうです。そして、ML-AgentsによるAI操作ですが、これはかなり期待したい内容ですね。やはり自動テストの理想型はリプレイのテストやエンジニアが実装するテストではなく「自動でよしなにチェックするテスト」です。Unityの提供するML-Agentsによってテストを学習し、検証が必要な部分を自動で判定して処理する仕組みができたら最強だなと思っています。また、これはComposite Recordingsの機能の一環として実装されるそうなので、ここまではリプレイでここからはAIで最後はC#、みたいな合わせ技も可能そうです。

Coming Soon: Run tests on real devices in the cloud from the Unity Editor or CI

We’ll handle the infrastructure so that cloud on-device testing is even easier than local on-device testing. And we’ll make the resulting pass/fail status, logs, and screenshots available directly from both the Unity Editor and via API.

クラウド上の端末で動くような仕組みも検討しているっぽいです。エディタ上および手元の端末で動くだけでも有用ですが、クラウド上で動く仕組みができると自動化できる規模感が大きく変わります。物理的な制約を外れてスケールできるため、それこそ1000台の端末で全てのインゲームのステージをテストするなんてことも可能になるわけです。まぁお値段もそれなりにかかってしまうことが想定されるので法人向けかなと思いますが、個人的には注目したい機能です。公式ドキュメントではAWS Device Farmとの連携が可能になるという感じでした。

[Early Access] Running Recorded Tests on a Cloud Device Farm This package includes support for running Recorded Tests (and other Unity Tests) on Android Phones hosted on AWS Device Farm - no AWS account needed.

Through June 2021, we’re offering 100 hours of cloud testing free to every organization with a Unity Pro license. After the free trial, cloud testing will cost $10 per device hour. Please email us at a >AutomatedQA@unity3d.com for access!

おわりに

Automated QA、熱いですね。特に昨今のモバイルのゲームのクオリティや規模感はどんどん上がっており、それに伴うQAのコストもどんどん上がっています。いろいろな企業がCEDECなどのカンファレンスで自動テストのノウハウでこういった課題を解決するという発表も最近はよく見る気がしていますが、そういった中で公式からQAの効率化機能を提供してくれるのはありがたいなと思います。まだ開発も始まったばかりっぽい機能で今後どうなるかは未知数ですが、引き続きチェックしていきたいですね。

参考資料

この記事を書くにあたって、公式のマニュアルを大きく参考にさせていただきました。ここでは紹介できなかった機能もあるので参考にしてみてください。

docs.unity3d.com

【Unity】Device Simulatorで複数の端末でのプレビューを爆速で検証する

はじめに

Unityでモバイル向けのアプリケーションを開発している時、どうしてもぶち当たる問題の一つがさまざまなデバイスの解像度に合わせたレイアウトの問題です。 各種解像度やセーフエリア対応など、UIをただ配置するだけでも気を使うことが多く開発者が頭を抱えている問題の一つでしょう。該当の端末にインストールして確認して、という繰り返しはつらいものがあります。
そこで活躍するのがUnityのDevice Simulatorという機能です。Device Simulatorを使うことで端末ごとのレイアウトをエディタ上で再現して確認を爆速で行うことが可能です。 今回はそのDevice Simulatorの導入やその一連の機能について紹介します。

※ 本記事で使用するUnityおよびDevice Simulatorのバージョンは以下の通りです。バージョンによっては挙動および機能が異なる場合がございますので、ご了承ください。

  • Unity 2021.1.0f1
  • Device Simulator 2.2.4-preview - November 30, 2020

Device Simulatorとは

Device SimulatorはUnity公式が提供している機能です。Gameビューのサイズをさまざまな端末に対応して変更し、描画のレイアウトを確認することができる便利機能です。

f:id:myudon:20210328204037p:plain

Unity2020まではPackage Managerでの提供でしたが、Unity2021からは標準機能として導入されています。標準機能として活用できるということで、今後もアップデートされていきそうだなーと期待しています。

Device Simulatorの導入

Device SimulatorはUnity2021以降であれば標準で入っていますが、パッケージを更新したかったり、Unity2020以前を使用されている方はPackage Managerを参照する必要があります。 本記事を執筆している時点ではまだpreviewのパッケージですので、Project Setting -> Package Managerの画面にあるEnable Preview Packageを有効にしてください。

f:id:myudon:20210328210723p:plain

設定してPackage ManagerのUnity Registryで検索すると出てきます。

f:id:myudon:20210328210653p:plain

インストールするとGameビューにDevice Simulatorに切り替えるメニューが出てきます。これでSimulatorを選択したら導入は完了です。

f:id:myudon:20210328211102p:plain

Device Simulatorの機能

導入するとGameビューが端末に表示されているような見た目になります。iPhoneの上部のノッチも反映されています。GameビューなのでもちろんこのままゲームをPlayすればこの画面上に表示されますし、入力も同様に行うことができます。

f:id:myudon:20210403154014p:plain

そしてDevice Simulatorの大きな特徴がセーフエリアやノッチを加味したエリアの可視化です。右上にある「Safe Area」ボタン押すと有効になります。デフォルトでは緑色の枠で表示されており、UIを安全に配置できる領域を確認することが可能です。

f:id:myudon:20210403154634p:plain f:id:myudon:20210403154744p:plain

画面のレイアウト作りで複数端末での見栄えを検証する時にはおすすめしたい機能です。

見た目の設定

見た目の解像度やセーフエリアの設定は左上のタブから切り替えることができます。iPhoneiPad、Pixel、Nexus、Galaxy、Galaxy Noteなどさまざまな端末の解像度が用意されています。

f:id:myudon:20210328211606p:plain

PortraitやLandscapeといった画面の向きの設定は左のパネルから調整できます。

f:id:myudon:20210328212341p:plain

画面の回転やズームなどは先述の「Safe Area」の有効化ボタンと同じく右上のメニューから調整可能です。

f:id:myudon:20210328212031p:plain

また、ApplicationクラスのAPIについて一部の値をGUI上から設定することができます。GUIで設定する情報だけでなく他にも色々な値が端末情報に合わせて変更されるのですが、これについては次に詳しく説明します。

f:id:myudon:20210404192633p:plain

f:id:myudon:20210404193020p:plain

Screenクラスなど値の上書きとSimulation Scope

Device Simulatorの特徴は単純な描画のプレビューだけでなく、Screenクラス、SystemInfo、Applicationクラスで参照できる情報も上書きします。たとえばScreen.widthやheight、safeAreaといった端末依存の情報を活用して独自の処理を行いたい時の検証を行うことが可能です。少し多いですが、上書きされるAPIを網羅して紹介します。

Screen

  • autorotateToLandscapeLeft
  • autorotateToLandscapeRight
  • autorotateToPortrait
  • autorotateToPortraitUpsideDown
  • currentResolution
  • cutouts
  • dpi
  • fullScreen
  • fullScreenMode
  • height
  • orientation
  • safeArea

SystemInfo

  • copyTextureSupport
  • deviceModel
  • deviceType
  • graphicsDeviceID
  • graphicsDeviceName
  • graphicsDeviceType
  • graphicsDeviceVendor
  • graphicsDeviceVendorID
  • graphicsDeviceVersion
  • graphicsMemorySize
  • graphicsMultiThreaded
  • graphicsShaderLevel
  • graphicsUVStartsAtTop
  • hasDynamicUniformArrayIndexingInFragmentShaders
  • hasHiddenSurfaceRemovalOnGPU
  • hasMipMaxLevel
  • hdrDisplaySupportFlags
  • maxComputeBufferInputsCompute
  • maxComputeBufferInputsDomain
  • maxComputeBufferInputsFragment
  • maxComputeBufferInputsGeometry
  • maxComputeBufferInputsHull
  • maxComputeBufferInputsVertex
  • maxComputeWorkGroupSize
  • maxComputeWorkGroupSizeX
  • maxComputeWorkGroupSizeY
  • maxComputeWorkGroupSizeZ
  • maxCubemapSize
  • maxTextureSize
  • minConstantBufferOffsetAlignment
  • npotSupport
  • operatingSystem
  • operatingSystemFamily
  • processorCount
  • processorFrequency
  • processorType
  • renderingThreadingMode
  • supportedRandomWriteTargetCount
  • supportedRenderTargetCount
  • supports2DArrayTextures
  • supports32bitsIndexBuffer
  • supports3DRenderTextures
  • supports3DTextures
  • supportsAccelerometer
  • supportsAsyncCompute
  • supportsAsyncGPUReadback
  • supportsAudio
  • supportsComputeShaders
  • supportsCubemapArrayTextures
  • supportsGeometryShaders
  • supportsGpuRecorder
  • supportsGraphicsFence
  • supportsGyroscope
  • supportsHardwareQuadTopology
  • supportsInstancing
  • supportsLocationService
  • supportsMipStreaming
  • supportsMotionVectors
  • supportsMultisampleAutoResolve
  • supportsMultisampledTextures
  • supportsRawShadowDepthSampling
  • supportsRayTracing
  • supportsSeparatedRenderTargetsBlend
  • supportsSetConstantBuffer
  • supportsShadows
  • supportsSparseTextures
  • supportsTessellationShaders
  • supportsTextureWrapMirrorOnce
  • supportsVibration
  • systemMemorySize
  • unsupportedIdentifier
  • usesLoadStoreActions
  • usesReversedZBuffer

Application

  • internetReachability
  • isConsolePlatform
  • isEditor
  • isMobilePlatform
  • platform
  • systemLanguage
  • LowMemoryCallback

SystemInfoとApplicationクラスのAPIまで上書きしてくれる便利な機能ですが、逆にエディタ拡張などで都合が悪い場合もあるかと思います。その場合に役立つのがSimulation Scopeという設定です。Project Settings -> Device Simulatorの所で設定が可能になっています。

f:id:myudon:20210404184327p:plain

標準ではdefault assemblyにチェックが入っており、全ての実装箇所でAPIが上書きされます。これを外すと下のCustom Assembliesに設定した名前のasmdef配下でのみ上書きが有効となります。(ちなみに、ScreenクラスだけはこのSimulation Scopeの設定の影響を受けず、常に反映されるっぽいです。)

独自で定義した端末情報の追加

PreferencesにDevice Simulatorの設定があり、そこでセーフエリアのハイライト表示設定などの細かな設定が可能です。

f:id:myudon:20210403002600p:plain

そして一番上の項目ですが、独自で定義した端末情報を使う際のファイルの読み込み先の指定になります。Unityの管理外のディレクトリでも可能です。端末情報はjson形式で定義されており、必要な値を設定します。

{
    "friendlyName": "Sample Device",
    "version": 1,
    "Screens": [
        {
            "width": 1080,
            "height": 1920,
            "dpi": 450.0
        }
    ],
    "SystemInfo": {
        "operatingSystem": "Android"
    }
}

このjsonは.device.jsonという拡張子をつけて保存します。正しく設定されればDevice Simulatorの起動時にロードされ、選択肢に追加されます。

f:id:myudon:20210404191609p:plain

詳細なデータ構造は公式ドキュメントを参照してください。先述のScreenやSystemInfo、ApplicationクラスのAPIについて書き換える値も設定できます。 iPhoneやGalaxyといった公式側で定義されている端末以外の画面サイズやセーフエリアの検証を行いたい時に活用しましょう。

Adding Custom Devices | Device Simulator | 2.2.4-preview

おわりに

Device Simulatorを活用することで、実機で画面を見たり値を検証しないといけない場面の開発コストを削減することが可能です。このUIはセーフエリアに対応して、背景はフルスクリーンにして、といった実機ならではの検証も迅速にUnity Editor上で確認できるのは便利ですね。まだpreviewの機能ということで機能が最低限だったり、たまに挙動が怪しい場合もありますが個人的には期待している機能の一つです。

参考文献

この記事を書くにあたって参考にさせていただいた公式ドキュメントです。

Introduction | Device Simulator | 2.2.4-preview

【C#】開発を加速させる便利な定義済みRiderライブテンプレートの紹介【Unity】

Riderのライブテンプレート機能、みなさんは使っていますでしょうか?
自分はUnityの開発においてかなり活用していて、これなしでは生きていけない体になっているのですが、職場で思ったより活用されてなかったので紹介記事を書きました。
本記事はライブテンプレートの中でも予めRiderが用意してくれている定義済みのライブテンプレートを紹介します。

ライブテンプレートとは

ライブテンプレートはいわゆるコードスニペットのようなコードの定型を生成する機能になります。
ifやforといったおなじみの定型構文やプロパティの定義などの同じようなコードを量産する上でそれらを事前に定義したテンプレートにしたがって生成してくれます。 ライブテンプレートが強力なのが、テンプレート内の変数の扱いです。テンプレートの一部を変数にして定義することができるのですが、その変数について生成するタイミングでコードの文脈から任意の値を挿入したり、入力カーソルの位置をよしなに合わせたりすることができます。どういうことかよくわからないという方はこの後紹介しているライブテンプレートの挙動を見てもらうとわかりやすいです。

ライブテンプレートの定義はPreferencesのEditor -> Live Templatesで確認できます。

f:id:myudon:20210311223828p:plain

ここでテンプレートの追加や削除、編集が行えます。テンプレートごとにDescriptionが設定されており、ライブテンプレートを使ってコードを挿入したい時にはそのDescriptionを入力するとRider側から提示されます。

f:id:myudon:20210311224042p:plain

そして、ライブテンプレートには汎用的に使えるものとしてあらかじめRider側に標準で定義されているものがあります。今回はその一部で筆者が使っているものを紹介します。

C#の定義済みライブテンプレート

まずはUnity問わず使えるC#のライブテンプレートについて紹介します。基本文法に関するものが多いです。

for

シンプルながらも便利度が高いのがfor文のライブテンプレートです。

f:id:myudon:20210311215406g:plain

for文のスコープがささっと生成され、中身もいい感じに定義しちゃえます。

forr

逆順のforループが必要なことがあったりしますが、その時にはrを一つ多くつけると逆順のfor文を生成してくれます。

f:id:myudon:20210311215406g:plain

foreach

foreachでループを書きたい場合はこちらです。

f:id:myudon:20210311221752g:plain

prop

プロパティをささっと作れるのがpropというライブテンプレートです。getterとsetterを含めた定義部分を生成してくれます。

f:id:myudon:20210316145322g:plain

prppg

プロパティの定義でsetterだけprivateにしたいというのもよくあるパターンです。その際にはpropの後ろにgをつけてあげます。

f:id:myudon:20210316145903g:plain

個人的にはこっちの方がよく使うかなという印象です。

if

コード内の任意の要素を用いてif文を書くというのは何回でもあるケースですが、そんな時の実装の手間を少し抑えてくれるのがifのライブテンプレートです。

f:id:myudon:20210311224910g:plain

Unity用の定義済みライブテンプレート

Riderの強みと言えばUnityの開発に対する手厚いサポートです。ライブテンプレートについてもUnity用のものがいくつか存在します。

sfield

SerializeFieldでインスペクターから依存メンバをアタッチしたい!というのはよくあるケースです。sfieldは[SerializeField]のアトリビュート付きのprivateメンバを量産するのに活躍します。

f:id:myudon:20210311224801g:plain

log

Unityのデバッグ出力でおなじみ、Debug.Logを生成してくれます。Debug.Logで何かを囲みたいあなたにうってつけのライブテンプレートです。

f:id:myudon:20210311223257g:plain

logerr

Debug.LogErrorで囲みたい時はこちら

f:id:myudon:20210311223130g:plain

おわりに

いかがでしたでしょうか。普段繰り返しで書いているようなコードをパパッと生成できるので、積み重ねていくとそこそこの時間短縮になります。Riderを使っててライブテンプレートに触れたことない人はぜひとも触って欲しい機能です。この記事で紹介は定義済みのものの中でも一部しか抜粋していないので、全部確認したい方はPreferencesの設定か、この公式のリンクを参考にしてください。

pleiades.io

ちなみに今回はライブテンプレートについてRider側で定義済みのものを紹介しましたが、当然自分で定義することもできます。自分もUnityの開発およびプロジェクトの開発に便利なライブテンプレートをいくつか作成して活用しています。時間があった時にはこのオリジナルのライブテンプレートの作成についても記事を書きたいです。

【Unity】新しいInputSystemのEnhancedTouchで端末のタッチ入力を処理する

Unityの新しいInputSystemの機能であるEnhancedTouchという機能を使って手軽に端末のタッチ入力が処理できたのでメモです。

はじめに

本記事では新しいInputSystemの機能の説明や、導入手順などは省略します。
以下のリンク先が大変参考になりましたので、確認していただけると幸いです。(一番下のリンク先が公式ドキュメントになりますので、情報が多いです。)

【Unity】新しいInput System入門 - 従来のUnityEngine.Inputに代わる高機能な入力管理システム - LIGHT11

Unityの新しい入力システムInputSystemを使ってみる | Unityを使った3Dゲームの作り方(かめくめ)

Namespace UnityEngine.InputSystem | Input System | 1.0.2

検証しているツールとパッケージのバージョンは以下の通りです。

  • Unity 2021.1.0b1
  • Input System 1.0.2

バージョンによっては挙動が違うことがありますので、ご注意ください。

EnhancedTouch

EnhancedTouchは新しいInputSystemで端末のタッチ入力周りの情報の参照やイベント処理を行うための機能です。
EnhancedTouch.Touchを使うにはEnhancedTouchSupportを有効にする必要があります。

// UnityEngine.Touchではないので注意
using UnityEngine.InputSystem.EnhancedTouch;

// EnhancedTouchの有効化
EnhancedTouchSupport.Enable();
// いろいろな機能を持つEnhancedTouch.Touch
var touch = Touch;

一点注意なのですが、EnhancedTouchはInputSystemで入力のイベント処理を実装するための機能であるInputActionと併用ができないそうです。
本記事では紹介していない機能ですが、こちらを使う場合も多いと思いますのでお気をつけください。

さて、Touchはタッチ情報を格納しているstructで、タッチ入力について管理されている情報を参照できます。

void DebugTouch(Touch touch)
{
    // タッチの状況(BeganとかMoveとか)
    TouchPhase phase = touch.phase;
    // タッチの座標
    Vector2 screenPosition = touch.screenPosition;
    // タッチを始めた座標
    Vector2 startScreenPosition = touch.startScreenPosition;
}

ゲーム内のタッチ入力はTouchのstaticなメンバとして管理されています。こちらは読み取り用のpublicなプロパティが用意されています。

// 端末の処理できる長さのタッチ入力情報の配列
// 過去のタッチ入力の座標などがキャッシュされている
ReadOnlyArray<Finger> fingerList = Touch.fingers;
// 現在のタッチ入力情報
ReadOnlyArray<Finger> activeFingerList = Touch.activeFingers;


// イベント処理
// 指を離した時
Touch.onFingerUp += OnFingerUp;
// 指が触れた時
Touch.onFingerDown += OnFingerDown;
// 指を動かした(ドラッグ)時
Touch.onFingerMove += OnFingerMove;

void OnFingerUp(Finger finger)
{
   Debug.Log(finger.screenPosition);
}
        
void OnFingerDown(Finger finger)
{
    Debug.Log(finger.screenPosition);
}
        
void OnFingerMove(Finger finger)
{
    Debug.Log(finger.screenPosition);
}

ここで出てきたFingerはTouchの情報をまとめているclassです。座標や状態などの情報を保持しており、更新処理もよしなにやってくれます。

// 一番最後に触れたタッチ入力の情報を保持するFingerインスタンス
Finger finger = Touch.activeFingers.Last();

// 現在のタッチ入力の中での順番
int index = finger.index;
// タッチ入力の座標
Vector2 screenPosition = finger.screenPosition;
// 現在タッチされているかどうか
bool isActive = finger.isActive;
// 現在のタッチ入力情報(座標や状態は更新される)
Touch currentTouch = finger.currentTouch;
// タッチ入力の履歴(IReadOnlyList<Touch>)
TouchHistory touchHistory = finger.touchHistory;

タッチした座標や入力の数などに基づいて処理を行いたい時にFingerを通して情報を参照します。EnhancedTouchだとこういった情報をまとめて管理してくれるのでお手軽です。
入力情報の履歴の参照など、他にもいくつか機能があるので使う方は最初に紹介した公式のドキュメントを参考にしてみてください。

終わりに

とりあえずシンプルに画面をタップしたときの挙動を定義したい時にEnhancedTouchを使うとさくっと実装ができそうです。
まだまだ情報の浅い新しいInputSystemですが、いろいろな機能を探っていきたいです。