VisualStudioでクラス名や変数名の命名規則をチェックする【C#】
概要
クラス名や変数名などの命名規則を決めていてもうっかり間違えることがよくあるので、Visual Studioの命名規則をチェックする機能を使ってみました。命名規則にあっていない部分に線が引かれるようになります。
設定例(今筆者が使っている設定)
表示例
設定方法
設定画面の開き方
- 画面上部のメニューから「ツール」→「オプション」を選択
- 「テキスト エディター」 → 「C#」 → 「コードスタイル」 → 「名前指定」 を選択
仕様
仕様というのはパスカルケースやキャメルケースなどのルールを何に対して適用するかを指定する設定のことです。例えば「クラス」や「privateなフィールド」「publicなメソッド」などがあります。
仕様はウィンドウの下にある「仕様の管理」から種類を増やしたり内容を変更できます。
「〇〇〇と△△△と□□□は全てパスカルケース」という命名規則を作りたい場合はそれらをまとめた仕様を作ると綺麗にまとまります。
シンボルの種類の中にいくつかわかりにくいものがありますが「parameter」はメソッドの引数、「type parameter」はGenericの型引数、「local」はローカル変数になります。
残念なところ
「キャメルケース」はスネークケースが違反にならない
「キャメルケース」というスタイルにはアンダースコアを含まないというルールがないためスネークケースが違反になりません。
同じように「パスカルケース」でスクリーミングスネークケース(全て大文字のスネークケース)が違反にならなかったり、意図通りのチェックをしてくれないことがあります。
感想
いくつか残念なところはあるものの個人的にはこれでも十分だと思いました。意図せず命名規則違反をすることが減りましたし、今まで気づいていなかった命名規則違反に気付くことができました。重要度をどれにするかはまだ決まっていませんがしばらくは「エラー」にしてみようと思っています。
環境
- Visual Studio 2019 (ver16.1.5)
4つの頂点座標からRectを求める【C#】
概要
与えられた複数の頂点(Vector2)が矩形(Rect)になるかどうかを判定して処理を分岐させたかったので、与えられた頂点からRectを求める関数を実装してみました。
実装
Rectを求める関数の実装:
using System.Collections.Generic; using UnityEngine; public static class RectUtility { /// <summary> /// 頂点が矩形を表すならtrueを返し、その情報をresultに格納する /// </summary> public static bool ToRect( out Rect result, IList<Vector2> vertices, float allowedError = 0 // 許容できる誤差 ) { result = default; if (vertices == null || vertices.Count != 4) { return false; } // 0番目の頂点から見て左 or 右にある頂点を特定 var h = GetHorizontalVertexIndex(vertices, 0, allowedError); if (h <= 0) { return false; } // 0番目の頂点から見て上 or 下にある頂点を特定 var v = GetVerticalVertexIndex(vertices, 0, allowedError); if (v <= 0) { return false; } // 0番目の頂点から見て斜めの位置にある頂点を特定 var d = GetDiagonalVertexIndex(vertices, 0, allowedError); if (d <= 0) { return false; } // 位置関係に問題がないかを確認 if (!( Equals(vertices[h].x, vertices[d].x, allowedError) && Equals(vertices[v].y, vertices[d].y, allowedError) )) { return false; } // 位置やサイズを設定 result = new Rect( Mathf.Min(vertices[h].x, vertices[0].x), Mathf.Min(vertices[v].y, vertices[0].y), Mathf.Abs(vertices[h].x - vertices[0].x), Mathf.Abs(vertices[v].y - vertices[0].y) ); return true; } /// <summary> /// selfIndex番目の頂点から斜めの位置にある頂点のIndexを取得する /// </summary> private static int GetDiagonalVertexIndex( IList<Vector2> vertices, int selfIndex, float allowedError ) { var self = vertices[selfIndex]; for (int i = 0; i < vertices.Count; ++i) { if (i == selfIndex) { continue; } if ( !Equals(vertices[i].x, self.x, allowedError) && !Equals(vertices[i].y, self.y, allowedError) ) { return i; } } return -1; } /// <summary> /// selfIndex番目の頂点から上 or 下の位置にある頂点のIndexを取得する /// </summary> private static int GetVerticalVertexIndex( IList<Vector2> vertices, int selfIndex, float allowedError ) { var self = vertices[selfIndex]; for (int i = 0; i < vertices.Count; ++i) { if (i == selfIndex) { continue; } if (Equals(vertices[i].x, self.x, allowedError)) { return i; } } return -1; } /// <summary> /// selfIndex番目の頂点から左 or 右の位置にある頂点のIndexを取得する /// </summary> private static int GetHorizontalVertexIndex( IList<Vector2> vertices, int selfIndex, float allowedError ) { var self = vertices[selfIndex]; for (int i = 0; i < vertices.Count; ++i) { if (i == selfIndex) { continue; } if (Equals(vertices[i].y, self.y, allowedError)) { return i; } } return -1; } /// <summary> /// 誤差を許容するfloatの比較 /// </summary> private static bool Equals(float a, float b, float allowedError) { return Mathf.Abs(a - b) <= allowedError; } }
使用例:
var list = new Vector2[] { new Vector2(1, -1), new Vector2(1, 0), new Vector2(3, -1), new Vector2(3, 0) }; if (RectUtility.ToRect(out Rect rect, list)) { Debug.Log(rect); }
ToRectの第三引数を指定すれば誤差を許容することもできます。
出力結果:
(x:1.00, y:-1.00, width:2.00, height:1.00)
※ 回転した状態の矩形には対応していません
環境
- Unity 2018.4.2f1
- VisualStudio 2019
Tilemapの情報をスクリプトから取得する【Unity】
概要
Tilemapの情報をスクリプトで取得して処理したかったので取得方法を調べてみました。
取得方法
タイルがどこに存在するかを取得する
タイルが存在する範囲はTilemap.cellBoundsで知ることができます。この範囲のセルに対してTilemap.HasTileを使うことでどのセルにタイルが存在するかを取得できます。
実装例:
public static void OutputPosition(Tilemap tilemap) { var builder = new StringBuilder(); var bound = tilemap.cellBounds; for (int y = bound.max.y - 1; y >= bound.min.y; --y) { for (int x = bound.min.x; x < bound.max.x; ++x) { builder.Append(tilemap.HasTile(new Vector3Int(x, y, 0)) ? "■" : "□"); } builder.Append("\n"); } Debug.Log(builder.ToString()); }
出力例:
cellBoundsの外側にタイルを置くと自動的にこの範囲は拡張されますが、そのタイルを消しても自動的に縮小されることはありません。縮小したい場合はTilemap.CompressBoundsを使って縮小できます。
タイルにどのSpriteが設定されているかを取得する
タイルにどのSpriteが設定されているか知りたいときはTilemap.GetTile<Tile>を使ってTile型の値を取得しTile.spriteをチェックします。
実装例:
public static void OutputSpriteType(Tilemap tilemap) { // 使われているSpriteをリストアップ var bound = tilemap.cellBounds; var spriteList = new List<Sprite>(); for (int y = bound.max.y - 1; y >= bound.min.y; --y) { for (int x = bound.min.x; x < bound.max.x; ++x) { var tile = tilemap.GetTile<Tile>(new Vector3Int(x, y, 0)); if (tile != null && !spriteList.Contains(tile.sprite)) { spriteList.Add(tile.sprite); } } } // どの場所でそのSpriteが使われているかを出力 var builder = new StringBuilder(); for (int y = bound.max.y - 1; y >= bound.min.y; --y) { for (int x = bound.min.x; x < bound.max.x; ++x) { var tile = tilemap.GetTile<Tile>(new Vector3Int(x, y, 0)); if (tile == null) { builder.Append("_"); } else { var index = spriteList.IndexOf(tile.sprite); builder.Append(index); } } builder.Append("\n"); } Debug.Log(builder.ToString()); }
出力例:
特定のタイルがある範囲を取得する
特定のタイルがある範囲を取得したい場合はGetTileは使わずにTilemap.GetCellCenterLocalやTilemap.originなどを使って範囲を取得します。
実装:
public static Rect OutputTilePosition(Tilemap tilemap, int x, int y) { var rect = new Rect(); rect.center = tilemap.GetCellCenterLocal(new Vector3Int(x, y, 0)) + tilemap.origin; rect.size = tilemap.cellSize; return rect; }
環境
- Unity 2018.4.2f1
- VisualStudio 2019
2Dの地形に3DのColliderを付けてみる【Unity】
概要
少し前の記事で地形は2Dで3Dモデルに2DのColliderを付ける検証をしました。今回は逆に地形の方に3DのColliderを付ける検証をしてみました。3DモデルはRigidbodyのZ方向の移動とXY方向の回転を禁止(Freeze)してRigidbody2Dのような動きしかしないようにしています。また、こちらの問題が発生するためDefault Solver Iterationsを100に変更して検証しています。
今回はTilemapだけでなくSpriteShapeも使っています。
検証
Tilemap + シンプルな3Dオブジェクト
TilemapにはこのようにColliderを付けました。
Colliderのz軸方向の厚みが0でも問題なく動くことが多いですが後述する理由により厚みを付けています。
Cylinderなどのオブジェクトは以下のようにFreezeにチェックを入れてRigidbody2Dのような動きをするようにしています。
これらのオブジェクトを地形に落下させてみるとこのようになります。
特に問題なく動作しています。
Tilemap + 複雑なモデル
次に複雑なモデルを使ってみます。
これも問題なさそうに見えますが…
3Dモデルに2D Colliderを付けたときと同じ問題
3Dモデルに2DのColliderを付けたときと同じでPerspectiveの問題が発生します。
これもOrthographicにすればはみ出すことはなくなりますが3D感が薄れます。
Colliderのz座標に注意
2DのColliderを使ったときとは違ってz座標にも注意する必要があります。例えば、地形のColliderの厚みを0にすると以下のように一部のColliderが地形と当たらなくなることがあります。
これは地形のColliderに厚みを付ければいいだけなので付け忘れないように注意していれば大丈夫です。
SpriteShape
SpriteShapeでも同じように検証してみます。ColliderはBox Colliderを複数組み合わせました。
Sprite Shapeでも特に問題なさそうです。
Perspectiveの問題が目立ちにくい分 Sprite Shapeの方がいいかもしれません。
感想
2Dの地形に3DのColliderを付けても大きな問題はなさそうです。
3Dモデルは人型に限定しなければ様々なバリエーションがありアニメが付いていることも多いと考えると、3Dモデルに2DのColliderを付けるよりも2Dの地形に3DのColliderを付けた方が想定外のトラブルでハマる可能性が低くなりそうな気がしました。
検証に使ったAsset (記事を書いた時点では全て無料Asset)
- Small Red Dragon - Asset Store (ドラゴン)
- "Unity-Chan!" Model - Asset Store (Unityちゃん)
- Troll Giant - Asset Store (トロール)
- Fantasy Rhino - Asset Store (サイ)
- SD Martial Arts Girl Xia-Chan - Asset Store (SDキャラクター)
- Toon Devil Gorgos - Asset Store (悪魔)
- Too Cube Forest, the free 2D platformer game tile set. - Asset Store (Tilemap)
- 2D Game Kit - Asset Store (SpriteShape用テクスチャ)
環境
- Unity 2018.4.0f1
FreezeをOnにしたRigidbodyを積み上げると勝手に動くことがある問題を解決する【Unity】
概要
Rigidbodyの動きを制限して2Dゲームのような動きをさせようと検証していたらRigidbodyが不自然な動きをすることがあったので調査してみました。
問題の再現方法
- 新規3Dプロジェクトを作る
- Terrainを作る
- カメラとの位置関係を調整するために座標を(-250,0,-250)にする
- 白いままだとわかりにくいので適当なTextureで色を付ける
- 落下させるprefabを作る
- Cubeを新規作成
- Rigidbodyを付ける
- ConstraintsをいくつかOnにする
- Freeze PositionのZをOn
- Freeze RotationのX, YをOn
- prefabとして保存
- 作ったprefabを、落下したら縦に積み重なるようにいくつか配置
- 位置や個数によって再現するかどうかが変わる
- 今回は5つ置いて、それぞれ高さを1, 4, 7, 10, 14にした
プレビューするとCubeが勝手に動くのが見えます。
Freezeを全てOffにするとこの問題は発生しなくなります。
対策
1. Default Solver Iterationsを大きくする
Default Solver Iterationsは計算回数を増やすことで精度を高くするもののようです。最大値は255です。この値を大きくするほど不自然な動きをすることが無くなっていきました。
この値を変更して解決するのでこの問題はバグではなく計算誤差によるものなのかもしれません。
ちなみに、マニュアルを読む限りこの値を大きくすると処理コストが増えそうな気がするのですが、Profilerで見た限り明らかな処理の増加は見られませんでした。わからなかっただけで微妙に増えているのかもしれません。
→ Rigidbodyの数を300まで増やしてRigidbody同士が接触しやすい状態にして計測したらDefault Solver Iterationsが大きい方が明らかに処理が増えていました。(2019/06/17 21:00修正)
2. Sleep Thresholdを大きくする
Sleep Thresholdを増やしても問題が発生しなくなりました。Rigidbodyは運動エネルギーが小さくなったときに動きを止めSleep状態になります。この値を増やすことで勝手に動き出す前にSleep状態に入ったのだと思います。
ただ、この値を増やしすぎると不自然な状態で動きが止まってしまうこともありました。処理負荷の面では良いですが調整に苦労するかもしれません。
試したけどダメだったこと
ちなみに、以下の変更も試してみましたが問題は解決しませんでした。
- PhysicsMaterialを付けてFrictionの値を大きくしてもダメ
- Enable Adaptive ForceをOnにしてもダメ
- RigidbodyのIterpolateを変更してもダメ
- RigidbodyのCollision Detectionを変更してもダメ
- Unity 2019.1.2f1にしてもダメ
- 2Dでプロジェクトを作ってもダメ
環境
- Unity 2018.4.0f1
3Dモデルに2DのColliderを付けてみる【Unity】
概要
地形は2Dでキャラクターは3Dモデルを使う2Dゲームを考えているのですが、2DのColliderと3DのColliderは衝突しないので3Dモデルに2DのColliderを付ける検証をしてみました。
検証
1.シンプルな形状
まずはCubeやCylinderなどのシンプルな形状のモデルにColliderを付けてみます。Cubeなら↓こんな感じです。RigidbodyやColliderが2Dのものになっています。
地形はTilemapを使いました。
これは問題なく使えてそうです。
2.複雑なモデル(複数のCollider)
次にAsset Storeで配布されている複雑なモデルを使ってみます。配布されているモデルには3D用のColliderが付いているのでそれらは削除して2D用のColliderに付け替えます。Colliderは形状に応じて複数使っています。
2DのColliderを付けるときにGameObjectがX軸、Y軸方向に回転しているとColliderも3次元的に回転してしまうので注意が必要です。
この例でも問題なく使えているように見えます。実はダメなモデルもあったのですがそれについては後述します。
3.動くモデル
アニメに合わせてColliderを動かしてみます。この例ではクジラのしっぽのBoneに体とは別のColliderを付けています。
アニメに合わせてColliderを動かすことも問題なさそうです。
4.うまくいかない例
でも、実は3Dモデルの中でいくつか当たり判定がうまく設定できないものがありました。
これはカメラがPerspectiveになっているため、z軸方向に厚みがあるモデルだと画面の位置によってモデルの見え方が異なることが原因です。
カメラのProjectionをOrthographicにするとこの問題は発生しなくなりますが、それでは見栄えが悪くなり3Dモデルを使っている意味が薄れてしまいます。Cubeで見るとその見栄えの差がわかりやすいかと思います。
Orthographicにするのではなくz軸方向に厚みのある部分には当たり判定を付けないとか、そういうモデルを使わないという回避策の方がいいかもしれません。
感想
いくつか問題はありますが3Dモデルに2DのColliderを付けてゲームをつくることもできないことはなさそうです。ただ、今回は簡単な検証をしただけなので他にも問題が出てくる可能性はあります。Orthographicで妥協できるなら比較的安全そうですが、Perspectiveは苦労するかもしれません。
(2019/06/21 21:39追記)
この記事とは逆に 2Dの地形に3DのColliderを付ける検証もしてみました。
検証に使ったAsset (記事を書いた時点では全て無料Asset)
- Small Red Dragon - Asset Store (ドラゴン)
- "Unity-Chan!" Model - Asset Store (Unityちゃん)
- Troll Giant - Asset Store (トロール)
- Fantasy Rhino - Asset Store (サイ)
- SD Martial Arts Girl Xia-Chan - Asset Store (SDキャラクター)
- Sport Car - 3D model - Asset Store (車)
- Humpback Whale - Asset Store (クジラ)
- Toon Devil Gorgos - Asset Store (悪魔)
- Too Cube Forest, the free 2D platformer game tile set. - Asset Store (Tilemap)
環境
- Unity 2018.4.0f1
ProCamera2Dのカメラが別のシーンに移動してしまう問題を解決する【Unity】
概要
ProCamera2Dのカメラを配置したシーンをAdditiveでロードすると何故かカメラが遷移前のシーンに移動してしまうことがありました。これはProCamera2Dの振動機能(ProCamera2DShake)などを使ったときにカメラの親GameObject (ProCamera2DShakeContainer)が自動で作られる処理の問題のようだったので対策を調べてみました。
対処方法1: カメラを別GameObjectの子にする
簡単な対処はカメラをルートに置かないことです。ProCamera2DShakeContainerがカメラの親GameObject(CameraContainer)の子になるので同じシーンになります。
対処方法2: ソースコードを修正する
カメラをルートに置きたい場合はソースコードの修正が必要になりそうです。ProCamera2DShake.csの82行目で"ProCamera2DShakeContainer"という名前のGameObjectをnewしているところを変更します。
変更後のコード
var obj = new GameObject("ProCamera2DShakeContainer"); UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(obj, ProCamera2D.gameObject.scene); _shakeParent = ProCamera2D.transform.parent = obj.transform;
開発者の方も問題を認識してはいるようなのでいずれこういう修正をしなくても済むようになるかもしれません。
参考
環境
- Unity 2018.4.0f1
- ProCamera2D 2.6.10
- VisualStudio 2019