1 はじめに
設計ができなくて、設計なしでゲームを制作していたのですが、制作途中で「これ設計ちゃんとやった方が絶対いいな」と感じてデザインパターンを勉強してました。するとObserverパターンでUniRxがすごい便利らしいので勉強することにしました。
今はR3というUniRxの進化系みたいなのが出ているらしいのですが、UniRxの方が情報が豊富で勉強しやすそうなのでUniRxの勉強していきます。 最初に言っておくのですが、勉強しながら記事を書いているので、正しいことを書いているかは保証できません。ですのでこの記事だけでUniRxを学ぶのではなく、様々なものと並行して見ていただき身につけてもらえると幸いです。
この記事は今後UniRxを勉強する人や、自分の理解を深めるために書いていこうと思います。また、以下の記事を大いに参考にしてます。非常にわかりやすいので絶対に見た方がいいです。
【UniRx】UnityではじめるReactive Extensions (https://annulusgames.com/blog/unirx-introduction-rx/
2 UniRxの導入
UniRxの導入ですが、すごく簡単で
1 Unity Asset Store でUniRxと検索すると 『UniRx - Reactive Extensions for Unity』 というアセットが出てくるのでそれを追加する。
2 UnityのPackage Managerからdownload
3 UnityのPackage Managerからdownload
4 そのままInstallを押す。
するとプロジェクトのPluginに追加されているのでこれで準備完了です。
3 LINQについて
早速UniRx について書きたいところなのですが、UniRxにはLINQ(Language Integrated Query)という機能が使われており、UniRxと密接に関係しています。ですのでまずは基本的なLINQの考え方を理解する必要があります。
LINQはC#や.NET言語などで非常によく使われる機能です。皆さんも聞いたことがあると思います。しかしLINQはなぜこんなにも使われるているのでしょう? それはデータの集合(例えば配列やリスト)の操作を簡潔かつ効率的に行える点にあります。UniRxとなんの関係があるのか?と思われるかもしれませんが、僕もそう思ってました。しかしそれは後から面白い形でわかるのでまずは説明します。
LINQはデータの集合の操作に使うと言いましたが、簡単な具体例を出します。
以下のような配列があったとします。
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
このリストから偶数だけを取り出したい場合、Whereメソッドを使います。
Where
コレクションから特定の条件にあった要素だけを抽出するためのメソッド。
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
このコードを詳しく解説すると:
1: numbers.Where(n => n % 2 == 0):
・ numbersは元のリストです。
・ Whereメソッドに渡しているn => n % 2 == 0はラムダ式で、ここでnは元リストの各要素を指します。
・ n % 2 == 0という条件は、「nが2で割り切れる(偶数である)」ことを意味します。
・ この条件に一致する要素だけが新しいコレクションに追加されます。
2: .ToList():(ここは一旦気にしなくてもいいです)
・ Whereメソッドの結果は、フィルタリングされた要素を含むコレクション(IEnumerable)を返します。
・ ToList()を使うことで、この結果をリスト形式(List)に変換しています。
これにより、evenNumbersには偶数のみが含まれるリストが作成されます。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
では次の例を見てみましょう。
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// 名前と年齢をセットにしたディクショナリを作成
Dictionary<string, int> people = new Dictionary<string, int>
{
{ "Alice", 25 },
{ "Bob", 30 },
{ "Charlie", 22 },
{ "Diana", 28 },
{ "Evan", 35 },
{ "Fiona", 27 },
{ "George", 32 },
{ "Hannah", 29 },
{ "Ian", 24 },
{ "Julia", 31 }
};
// ディクショナリの内容を表示
foreach (var person in people)
{
Console.WriteLine($"Name: {person.Key}, Age: {person.Value}");
}
}
}
これは人の名前と年齢をセットにしたディクショナリです。このディクショナリから
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
1: 27歳以上の人を取り出してリストにする。
2: 取り出した年齢(int型)をString型に
3: 27歳以上の人の人数を表示
という処理を行いたい時どうしますか?LINQを使わなかった場合、大体以下のようになると思います。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
1: まずは27歳以上の人を集めるための新たなディクショナリ、peopleAbove27を用意する。
2: foreachを使い、if文の中でオブジェクトの年齢が27以上の場合、peopleAbove27に追加する。
3: その際To Stringを使って年齢をStringに変換する。
4: 別のforeachの中で新たにつくったpeopleAbove27の中身をConsole.WriteLineで表示する。
5: 最後にpeopleAbove27に何人登録されているかを表示する。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// 名前と年齢をセットにしたディクショナリを作成
Dictionary<string, int> people = new Dictionary<string, int>
{
{ "Alice", 25 },
{ "Bob", 30 },
{ "Charlie", 22 },
{ "Diana", 28 },
{ "Evan", 35 },
{ "Fiona", 27 },
{ "George", 32 },
{ "Hannah", 29 },
{ "Ian", 24 },
{ "Julia", 31 }
};
// 27歳以上の人を格納するためのリスト
List<KeyValuePair<string, string>> peopleAbove27 = new List<KeyValuePair<string, string>>();
// ループを使って27歳以上の人をフィルタリングし、年齢をstring型に変換
foreach (var person in people)
{
if (person.Value >= 27)
{
// 年齢をstring型に変換してリストに追加
peopleAbove27.Add(new KeyValuePair<string, string>(person.Key, person.Value.ToString()));
}
}
// フィルタリングされた結果を表示
Console.WriteLine("People aged 27 and above:");
foreach (var person in peopleAbove27)
{
Console.WriteLine($"Name: {person.Key}, Age: {person.Value}");
}
// 27歳以上の人数を表示
Console.WriteLine($"Number of people aged 27 and above: {peopleAbove27.Count}");
}
}
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
これと同じことをLINQを使って表現すると以下のようになります。
1: 27歳以上の人を格納する新たな変数を作成する。
2: ディクショナリを作る際に様々な処理を行うことができる。
3: 「Whereメソッド」を使って元のリストの27歳以上の人間をフィルタリング
4: 「Selectメソッド」をメソッドチェーンにしてフィルタリングしたオブジェクトの年齢をまとめてString型に。
*メソッドチェーンとはWhere().Select()のようにメソッドを連鎖させること。
5: 「ToList」をメソッドチェーンにして結果をリスト形式に変換。
6: foreachでリストの中身を表示
7: 最後にリストの人数を表示
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
// 名前と年齢をセットにしたディクショナリを作成
Dictionary<string, int> people = new Dictionary<string, int>
{
{ "Alice", 25 },
{ "Bob", 30 },
{ "Charlie", 22 },
{ "Diana", 28 },
{ "Evan", 35 },
{ "Fiona", 27 },
{ "George", 32 },
{ "Hannah", 29 },
{ "Ian", 24 },
{ "Julia", 31 }
};
// LINQを使って27歳以上の人をフィルタリングし、年齢をstring型に変換してリストに格納
var peopleAbove27 = people
.Where(person => person.Value >= 27)
.Select(person => new KeyValuePair<string, string>(person.Key, person.Value.ToString()))
.ToList();
// フィルタリングされた結果を表示
Console.WriteLine("People aged 27 and above:");
foreach (var person in peopleAbove27)
{
Console.WriteLine($"Name: {person.Key}, Age: {person.Value}");
}
// 27歳以上の人数を表示
Console.WriteLine($"Number of people aged 27 and above: {peopleAbove27.Count}");
}
}
この二つの方法の中で大きく異なっている部分は、新たなリストを作る部分です。
// 27歳以上の人を格納するためのリスト
List<KeyValuePair<string, string>> peopleAbove27 = new List<KeyValuePair<string, string>>();
// ループを使って27歳以上の人をフィルタリングし、年齢をstring型に変換
foreach (var person in people)
{
if (person.Value >= 27)
{
// 年齢をstring型に変換してリストに追加
peopleAbove27.Add(new KeyValuePair<string, string>(person.Key, person.Value.ToString()));
}
}
// LINQを使って27歳以上の人をフィルタリングし、年齢をstring型に変換してリストに格納
var peopleAbove27 = people
.Where(person => person.Value >= 27)
.Select(person => new KeyValuePair<string, string>(person.Key, person.Value.ToString()))
.ToList();
どうでしょう。LINQを使っている方が非常にスッキリかけてると思いませんか?。
LINQなしだとforeachの中でif文を使うので少しみにくく、冗長になってしまいます。
それに対してLINQを使うと、
『Where』『Select』『ToList』
といったメソッドがあることでそこでなんの処理をしているかがわかりやすく、かつ短くまとめることができています。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
ではここでイラストを用いて、LINQのイメージを掴もうと思います。
配列を、箱が横に並んでいるものだと見立てるとわかりやすいです。
「並んでいるデータから、条件に合うオブジェクトに対して操作を行う」
これがLINQのイメージです。
LINQのイメージがわかったことで、ようやくUniRxの概念を説明することができます。
3 UniRxについて
まずUniRxは主に、イベント処理をObserverパターンで行うものです。イベント処理とはボタンが押されたらジャンプする。 敵と衝突したら攻撃する。といった処理のことです。 UniRxではこのイベント処理を 「時間の流れの中から、条件に合うイベントが起きた時にそれに対して何かしらの処理を行う」 ことで表現します。
そして時間は、「点の集まり」という捉え方をすることができます。
先ほどLINQでは、「並んでいるデータの中から条件に合うオブジェクトに対して操作を行う」 と書きました。
つまりUniRxとは、 「時間を点の集まりとして捉え、そこにLINQを用いることでイベントが起きた点のみに対して操作を行う」 ことでイベント処理を表現するものなのです。
このままだとまだまだ長くなりそうなので、続きは別の記事に書します。 僕も勉強しながらなので、時間はかかってしまいますが質問などあればコメントください。 また間違っている箇所など、指摘がある場合はぜひ教えていただけると助かります! ここまで読んでいただきありがとうございました。