C#のIEquatableについて学び直したまとめ
はじめに
開発中にあるクラスにIEquatableが実装されていないと指摘を受けたのですが、それについて理解が曖昧なまま実装して修正してしまったので改めて学び直したメモを残します。
IEquatableインターフェイスとは
2つのオブジェクトが等しいかどうかを調べるEqualsメソッドを実装することを保証するためのインターフェイスです。
ここでいう2つのオブジェクトが等しいという場合の等しいがどんな状態を表すかが重要で、IEquatableを実装しなくてもクラス同士の比較自体はできます。 では、実装する場合としない場合で何が違うのかを比べてみます。
IEquatableを実装しない場合とする場合の比較
実装しない場合、参照型であるクラスと値型になる構造体で挙動が変わります。 今回は参照型であるクラスについてのみ説明していきます。
以下のようなクラスが存在するとします。
namespace Sample.Equals { public class EqualClass { private int _rank; private string _name; public EqualClass(int rank, string name) { _rank = rank; _name = name; } public string GetRank() { return $"{_name} : Rank {_rank}"; } } }
このとき、以下のような比較をするとコメントで示されたように、falseになります。
EqualClass sample1 = new EqualClass(0, "hoge"); EqualClass sample2 = new EqualClass(0, "hoge"); Debug.Log(sample1.Equals(sample2)); // False
ここで、IEquatable
public bool Equals(EqualClass equalClass) { if (equalClass == null) { return false; } return (this._rank == equalClass._rank && this._name == equalClass._name); }
そうすると、さっきの比較を行うと結果はtrueを返すようになります。
このような挙動になる理由は、Equalsを実装する前にはオブジェクトの参照先を比較しています。 そのため、先程の例ではsample1とsample2で別のインスタンスが作られているため、中の値が等しくてもfalseを返すようになっているのです。 しかし、Equalsを実装した場合はその中身でそれぞれのメンバの値を比較して結果を返しているため、値が等しければtrueを返します。
このように、直接メンバの値によって比較したい場合は、IEquatableインターフェイスを実装する必要があります。
IEquatableを実装すべき場合
先程説明したように、参照型のオブジェクトで値の比較をしたい場合はEqualsの実装が必要です。 では、このように値の比較をしたくなるのはどんな場合にあるのでしょうか?
大きなものの一つに、ListやArray、Dictionaryといったジェネリックなコレクションクラスで利用したい場合が挙げられます。 例えば、先程のEqualClassを下記のようにListでまとめたかったとします。
List<EqualClass> sampleList = new List<EqualClass>() { new EqualClass(0, "hoge"), new EqualClass(1, "fuga"), };
ここで、rankが0で名前がhogeだった人がリストの何番目に入っているか調べたくなったときに、以下のように記述した際に、IEquatableが実装されていないと見つからなかったことになり-1を返します。
EqualClass hoge = new EqualClass(0, "hoge"); Debug.Log(sampleList.IndexOf(hoge)); // -1
このように、意図せず比較を使っている場合もあるため常にIEquatableは実装したほうが良いと思います。
余談ですが、Dictionaryのキーに使いたい場合は、IEquatableインターフェイスの他にGetHashCode()もオーバーライドする必要があります。
IEquatableとIEquatable(Object)の違い
このように比較をする可能性のあるクラスには必ず実装しておいたほうが良さそうなIEquatableですが、二種類あります。 この2つの違いは何なのでしょうか?
2つのEqualsは以下のようなメソッドになっています。
bool IEquatable<T>(T obj) bool IEquatable(object obj)
前者は先程まで説明していたジェネリックなIEquatableで後者は引数にobject型を取るEqualsです。 これらの違いとしては、objectの方は比較の際にボクシングが発生するためパフォーマンス的にあまり良くないですが、ジェネリックな方はボクシングが発生しない分パフォーマンスが良くなります。
一見objectの方はパフォーマンスも悪く、使うタイミングがないように思えますが、クラスによってはobjectの方のみを呼び出すようになっているものもあるため、常にジェネリックの方を実装していたとしてもobjectの方も実装したほうが良いそうです。
まとめ
理解が曖昧だったEqualについて整理しました。 こうやってまとめて振り返ってみると、今回指摘されたのはListで利用する可能性のあるクラスだったからでした。 こんな感じで日々の開発の中でふと疑問に思ったことはしっかりと時間の取れるときに調べてまとめることでより理解を深めていきたいですね。 間違っているところがあれば、是非コメントください。
参考
IEquatable(T) インターフェイス (System)
自作クラスのEqualsメソッドをオーバーライドして、等価の定義を変更する - .NET Tips (VB.NET,C#...)
クラッシュロワイヤルのAPIが公開された話
はじめに
今週はじめにクラッシュロワイヤルのAPIが一部公開されたことがクラッシュロワイヤルの海外版アプリ内の告知で発表されていました。 それについてどれくらい使えるものだったのかと思ったことについて書きます。
公開されたもの
APIはこちらでユーザー登録をすることで使えるようになります。
現時点で取得できる情報は以下の一覧のようです。
クラン情報
- クランの検索
- 任意のクランの詳細情報
- クランメンバーのリスト
- クラン対戦の戦歴
- 現在行われているクラン対戦の情報
プレイヤー情報
- プレイヤーの詳細情報
- 今後手に入る宝箱のリスト
- 最近のバトルの戦歴
トーナメント情報
- 全トーナメントの名前
- トーナメントの詳細情報
カード情報
- 全カードの情報(コスト、アイコン、Maxレベル)
地域情報
- 全地域のID
- 特定の地域の詳細情報
- 特定の地域のクランランキング
- 特定の地域のユーザーランキング
- 特定の地域のクラン対戦ランキング
できそうなこと
基本的な情報はすべて取得できるようです。 取得できない情報を探すのが大変なぐらい公開されていそうでした。
中でもびっくりしたのが、他のプレイヤーの戦歴を見れることです。 試合自体はオープンに行われており、誰でも観戦は自由にできるゲームなので公開しても問題ないのかもしれませんが、クラッシュロワイヤルはプロプレイヤーも多くいるゲームなので、そのプレイヤーたちのデッキであったり戦歴を見れるのは非常に嬉しいです。 これを使えば、有名プレイヤーたちのデッキの傾向や戦歴の分析ができそうです。
思ったこと
おそらくゲームのAPIを公開するというのは非常に珍しいのではないかと思います(他ゲームで公式で公開されているものがあれば知りたいです)。 公式サイトにも書かれていますがAPIを公開することで熱心なユーザーが自発的に便利なアプリやサイトを開発してくれれば、そのゲームをやっている人はよりゲームを楽しめるようになり、それ自体が有名になればそれらのサイトやアプリをきっかけでゲームを始めるような人も出てくるようになると思います。
ニコニコ動画のようなユーザーによる盛り上げによって自然と人気になるスタイルは非常に良いと思いますし、今回のクラッシュロワイヤルのAPI公開はそれを促す施策として非常に効果的だろうなと思いました。 ゲームは広告やSNS運用で集客をするのが当たり前になっていると思いますが、このような熱心なユーザーを利用しての集客方法もあるのだなと新しく気づかされました。
まとめ
少し試してみたところ、有名プレイヤーの戦歴やどんなデッキを使っているのかも見れるようだったので有名プレイヤーたちのデッキの傾向や戦歴を見れるようなアプリを作ってみたくなりました。 こういったものが公開されるゲームが増えるとユーザーの熱によってゲームコミュニティ自体が広がって行くと思うので、どんどんやってほしいなあと思いました。
【Unity】uGUIの当たり判定を広げる
はじめに
かなり小さいサイズのボタンを作成する場合にImageのサイズは変えたくないけど、判定だけ少し広めに取りたいということがあります。 今回は、そのような場合に判定を広げる方法とその広げた判定がどんな感じになっているかをGizmoで見えるようにする方法を紹介します。
完成したもの
以下のような白いボタンに対して、判定が緑の範囲となるような感じで調整ができます。
ソースコード
using UnityEngine; using UnityEngine.UI; namespace UIColliderSample { [RequireComponent(typeof(RectTransform))] [RequireComponent(typeof(CanvasRenderer))] public class InvisibleGraphic : Graphic { private RectTransform _rectTransform; private Rect _rect = new Rect(); private Rect GetRect() { if (_rectTransform == null) { _rectTransform = GetComponent<RectTransform>(); } _rect.center = (Vector2) _rectTransform.position + _rectTransform.rect.center; Vector2 size = _rectTransform.rect.size; size.x *= _rectTransform.lossyScale.x; size.y *= _rectTransform.lossyScale.y; _rect.size = size; return _rect; } protected override void OnPopulateMesh(VertexHelper vh) { base.OnPopulateMesh(vh); vh.Clear(); } private void OnDrawGizmos() { var rect = GetRect(); Gizmos.color = new Color(0.0f, 1.0f, 0.0f, 0.5f); Gizmos.DrawCube(rect.center, rect.size); } } }
ソースコードの説明
判定を広げる部分に関してはkanさんのブログを参考にしました。 やっていることとしてはGraphicを継承することで判定を取れるようにして、そのままだと白い四角が描画されてしまうので以下の部分で頂点を空にすることで描画されないようにしています。
protected override void OnPopulateMesh(VertexHelper vh) { base.OnPopulateMesh(vh); vh.Clear(); }
Gizmoの部分では、このスクリプトがアタッチされているオブジェクトのRectTransformを見て、そのサイズになるようにRectを表示しています。 RectTransformに対して正しいRectになるように、以下の部分でRectを作成しています。
private Rect GetRect() { if (_rectTransform == null) { _rectTransform = GetComponent<RectTransform>(); } _rect.center = (Vector2) _rectTransform.position + _rectTransform.rect.center; Vector2 size = _rectTransform.rect.size; size.x *= _rectTransform.lossyScale.x; size.y *= _rectTransform.lossyScale.y; _rect.size = size; return _rect; }
Rectのサイズはscaleも考慮する必要があるので、lossyScaleを掛けています。
使い方
作成した上記のスクリプトを当たり判定を広げたいuGUIに子オブジェクトを作成してそれにアタッチする。
まとめ
判定を広げるのをもっと簡単にできたらいいなあ…と思いました。 もしもっと良いやり方があれば教えてください!
あとなにげにGizmoを初めていじりました。
参考
【Unity】Canvasでパフォーマンスに関わることについて調べてみた
はじめに
今後UI周りに広く関わることになったので、Canvasに関して気になってはいたけど調べていなかったことについて調べてみました。
目次は以下です。
CanvasとSpriteAtlasの関係
はじめに、SpriteAtlasでまとめたSpriteを使うUIを複数Canvasに分ける場合と分けない場合でDrawCallにどんな違いが出るのかを調べました。 SpriteAtlasに関してはこちらを参照してください
描画したUIは以下のようなものです。 表示されているものをすべて一つのSpriteAtlasにまとめました。
これらのことから、アトラス化により各UIで使われるSpriteが一つのSpriteAtlasになっていたとしても、Canvasが分かれていては別途DrawCallが発生してしまうことがわかります。 SpriteAtlasとして効果を発揮するならば、複数Canvasを用いてUIを構成する場合はCanvasごとにアトラス化するのが良さそうです。
動くUIと動かないUIでCanvasを分ける
先程の例をみるとCanvasを分けることにはデメリットしかないように見えますが、UnityのCanvasの仕様上Canvas内に一つでも動くUIが存在するとCanvas全体にRebuild処理が走り結構な負荷がかかるそうです。 そのため、動くUIと動かないUIに関してはCanvasを分けるべきだと言われています。
今回は、実際に動くUIと動かないUIを分割する場合としない場合でどれくらいの差があるのかを調べてみました。
動くUIとしてx方向にscaleするアニメーションをしたImageを、動かないUIとしてただのImageを用意しました。
差を見ると分かる通り、動くUIが一つあるだけでUI全体に対して描画の更新がかかっているようです。
また、動くUIとはなんなのか曖昧だったので試してみたところ、ImageのColorを変えたり、enableを変更するだけでも更新がかかるようでした。
大きく分けるならば、常時動いているようなUIとたまに動くUIと全く動かないUIをそれぞれ3つのCanvasに分けてやるのが良さそうです。
まとめ
Canvasは分けすぎても良くないし、分けなくても良くないことがわかりました。 基本的には、常時動くUIとたまに動くUIと動かないUIでCanvasを分けて、それぞれでアトラス化をしてやれば良さそうです。
Profilerをあまり使い慣れていなくて計測が雑なので、もしもっと詳しく計測できる方法や間違っていることがあれば教えてください。
(追記) DeepProfileによる検証し直し
動くUIと動かないUIをCanvasとして分ける場合と分けない場合の違いについての計測で、DeepProfileを用いて計測し直した結果を残しておきます。
- 動くUI × 1 と 動かないUI ×1000を同じCanvasにしたとき
DeepProfileのCalls
DeepProfileのHierarchy
- 動くUI × 1 と 動かないUI×1000を別のCanvasにしたとき
DeepProfileのCalls
DeepProfileのHierarchy
DeepProfileで計測した結果が上記のようになりました。 動くUIを分けなかった場合、WaitForJobGroupIDで非常に時間がかかっていることがわかります。 実際は1000個も同じCanvasにUIが存在することは殆どないと思いますが、分けると分けないとで処理時間がかなり変わってくることがはっきりとわかったのでやっぱり分けたほうが良さそうです。
参考
エンジニア以外にも使ってほしい!PlantUMLのススメ
はじめに
最近一から設計を考えて実装をする機会が多く、実装前にチームメンバーに自分の考えを共有しなければならないことが多くありました。 その際に、PlantUMLによって自分の考えを図示してから話し合いを行うことで様々な利点があったので共有します。
まだPlantUML歴2週間程度のひよっこなので間違ってるところがあったらぜひ教えてください。
(書いてから思いましたが、UMLをオススメする記事になってしまった気がします)
UML
PlantUMLについて説明をする前に、UMLについての説明をします。
統一モデリング言語(とういつモデリングげんご、UML、英: Unified Modeling Language)は、主にオブジェクト指向分析や設計のための、記法の統一がはかられた(Unified)モデリング言語(Modeling Language)である。
だそうです。
簡単に言うと、ソフトウェアに関する設計や処理の流れやデータ構造といったものを図示していくための記法を定めたものです。
UMLは、データ構造などの構造を表す構造図と動作や変化を表す振る舞い図の大きく二種類に分類されます。
構造図としては、クラス図、パッケージ図、オブジェクト図などがあります。 振る舞い図としては、アクティビティ図、ユースケース図、状態遷移図などがあります。
今回は、それぞれの図の具体的な使いみちは他のサイトに譲りますが、UMLがどんなものか見てもらうためにクラス図についてのみ紹介します。
クラス図
その名の通り、ソフトウェアを構成するクラスがどのような関連を持っているかを図示するものです。 あるクラスが他のクラスとどんな関係にあり、どんな変数を保持しているかが図でわかるようになります。
具体的なクラス図は以下です。
この図を見ると、Animalが抽象クラスで、Dog、Cat、Humanはそれを実装したクラスとなり、ZooはAnimalのリストを持ってるクラスだということがわかります。
PlantUML
PlantUMLとは、上記で説明したUMLをDSLを記述することですばやく作成できるものです。
先程のクラス図は以下のように記述することで生成されます。
@startuml abstract class Animal { - type - color + run } class Dog { + run + eat } class Cat { + run + sleep } class Human { + run + talk } class Zoo { - animalList } Animal <|-- Dog Animal <|-- Cat Animal <|-- Human Zoo *-- Animal @enduml
このように、テキストベースでUMLを記述することができるのがPlantUMLです。(UML以外にも図示できるらしいです)
各図を作図するためにある程度記法を覚える必要がありますが、慣れてしまえばdraw.ioなどで図を書くよりも早く、そしてきれいに図示できると思います。
PlantUML(UML)の利点
PlantUML(UML)を使っていて感じた利点としては以下のようなものがありました。
図を作成することで思考の整理ができる
コードを実際に書く前に設計を図示しておくことで自分の思考が整理され、曖昧だった箇所がはっきりして実装時の手戻りが減ります。
人との考えの共有ができる
言葉で設計について相談するよりも、このようなツールで図示をしてそれをベースに相談したほうが圧倒的に精度が高いと感じました。 言葉の受け取り方は人それぞれな部分もあり、自分が思っていたことと相手の思っていたことが違い手戻りが発生してしまうようなことがあります。 しかし、図示をして相談をすればそういった不明確な要素を排除できます。
実装が楽になる
あらかじめ設計を図示しておくことで、あとはそのとおりに作っていくだけでよくなるので実装時の負担が減ります。 実装中に設計を考えながらやると行き当たりばったりな実装になりがちで、良くないコードになってしまいます。
ドキュメントとして残せる
PlantUMLはテキストベースなのでGit管理もしやすく、ドキュメントとして残す際に便利です。
PlantUMLの欠点
記法を覚えるのが大変
僕も未だに記法をしっかりと覚えられていないので、書くときに少し時間がかかってしまいます。 頑張って覚えましょう。
どの図を使っていいかわからない
UMLについてしっかりと理解しないと、クラス図で書くべきなのかオブジェクト図で書くべきなのかがわからなくなってしまったりします。 頑張って理解しましょう。
まとめ
PlantUMLはエンジニアだけが使うツールではないと思います。 例えば、プランナーさんがエンジニアに仕様を伝える際にも、ユースケース図やシーケンス図が活きてくると思います。
是非みんなでPlantUMLを覚えて、適切な議論ができるようにしていきましょう!