- 投稿日:2020-10-10T23:35:31+09:00
【Unity】オブジェクトをなめらかに移動させる【コルーチン】
オブジェクトをなめらかに動かしたい。
でも、Update()の中に書くのは邪魔だし、他のスクリプトから移動を開始させたいときはコルーチンを使う。やってること
動かしたいオブジェクトにアタッチしているスクリプトにコルーチンの処理を書く。
コルーチン内には1フレームずつの繰り返し処理を書き、Lerpで目的地までの移動をなめらかにする。
コルーチンを別スクリプトから起動する。【環境】
・Mac OSX El Capitan
・Unity versiton:2018.3.0【スクリプト】
Move.csusing System.Collections; using UnityEngine; public class Move : MonoBehaviour { public float speed; public Coroutine myCor; void Update() { //このスクリプト内でコルーチンをスタートさせる場合 //if (Input.GetKeyDown(KeyCode.Space)) { // myCor = StartCoroutine(MoveTo(goal)); //}else if(Input.GetKeyDown(KeyCode.Backspace)){ // StopCoroutine(myCor); //} } //MoveToをスタートさせるメソッド //外部からコルーチンを呼び出すときはこのメソッドを使う public void StartCor(Vector3 goal) { if (myCor != null) { StopCoroutine(myCor);//StartCoroutine()する前に停止させて、重複して実行されないようにする。 } myCor = StartCoroutine(MoveTo(goal)); } //goalの位置までスムーズに移動する public IEnumerator MoveTo(Vector3 goal) { while (Vector3.Distance(transform.position, goal) > 0.05f) { Vector3 nextPos = Vector3.Lerp(transform.position, goal, Time.deltaTime * speed); transform.position = nextPos; yield return null;//ここまでが1フレームの間に処理される } transform.position = goal; print("終了"); yield break;//処理が終わったら破棄する } }他のスクリプトから直接Move.csのMoveToをStopCoroutine()しようとするとエラーが吐かれます。
詳しくはこっち
https://qiita.com/maqiita/items/0f9fa1fd2fbd6ded2fee
- 投稿日:2020-10-10T22:12:17+09:00
UnityのGLを使って数なし数直線を作ってみた
はじめに
Unityで画面上に線を引くためには様々な手段がありますが、その中でもGLと言うOpenGLのイミディエイトモードと同様のコマンドを実行することができるグラフィックスライブラリがあるらしいです。
今回はそれを使って数直線(数なし)←は?を作ってみたのでご紹介したいと思います。
※2D上でしか検証していないので3Dでの動作は保証できませんコードはGithubにて公開しています。https://github.com/HarumaroJP/GLMarkLineDrawer
使い方
Githubに公開している二つのスクリプトをUnityに入れて、GLMarkLineDrawer.csをGameObjectにくっつけるだけです。
設定欄はこんな感じです。
名前 用途 Paths Transform型で線の通る座標を指定します Color 数直線の色を指定します Width 線の幅を指定します Edge Mark Length 外側のメモリの長さを指定します。 Inside Mark Length 内側のメモリの長さを指定します。 Interval Count メモリの分割数を指定します。 最初はVector3型で指定するようにしようかなと思ったのですが、かなり面倒になるので、Transform型を使うことにしました。
GLについて
まず前提としてGLクラスでは、このような書き方で描画します。
//頂点マトリックスが漏れないようにするためのおまじない GL.PushMatrix(); { //描画開始 GL.Begin(GL.QUADS); { GL.Color(Color.white); GL.Vertex3(x,y,z); } GL.End(); } GL.PopMatrix();・
GL.PushMatrix() , GL.PopMatrix()
は、行列マトリックスが範囲外に漏れないようにするためのおまじないのようなものです。複雑な描画をする際には必要になります。今回は一応書いていますが、簡単な描画の場合は必要ないかも..?・
GL.Begin() , GL.End()
の間で、描画する頂点を指定することができます。また、引数でメッシュの形成方法を指定する方法ができます。「GL Primitives」で検索するといろいろな方法が出てくるので、調べてみてください。今回は、四角形を形成するように設定しています。・
GL.Color
は色を設定するためのメソッドです。このメソッドで設定して実行された後に面が描画された場合、設定した色が反映されるようになります。・
GL.Vertex3(x,y,z) or GL.Vertex(Vector3)
で頂点を設定することができます。またブロック文で囲っている箇所がありますが、これは特に必要というわけではありません。可読性が上がるので、付けた方が良いというだけです。
Drawerの解説
一旦コードを貼ります。
GLMarkLineDrawer.csusing System; using UnityEngine; [ExecuteInEditMode] public class GLMarkLineDrawer : MonoBehaviour { [SerializeField] private GLMarkLine.LineSettings settings = new GLMarkLine.LineSettings(new Transform[0], Color.white, 100f, 2f, 1f, 10); private GLMarkLine line = new GLMarkLine(); //ラインのインスタンスを生成 private void OnRenderObject() { line.Draw(settings); //描画 } }こちらの方はあまり説明は必要ないと思いますが、
一応補足をしておくと、Monobehaviourの
OnRenderObject()
はカメラがシーンをレンダリングした後に呼び出されるメソッドで、今回はこのメソッドを通して描画しています。また設定のパラメータは構造体で管理しています。あまり構造体で自前のコンストラクタは使いたくなかったのですが、入れたらすぐ使えるようにしたかったので、今回は自前のコンストラクタで初期化しています。
本体の解説
次が本体のコードです。
GLMarkLine.csusing System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Rendering; public class GLMarkLine { //設定パラメータ用のstruct [Serializable] public struct LineSettings { public Transform[] paths; public Color color; public float width; public float edgeMarkLength; public float insideMarkLength; public int intervalCount; public LineSettings(Transform[] paths, Color color, float width, float edgeMarkLength, float insideMarkLength, int intervalCount) { this.paths = paths; this.color = color; this.width = width; this.edgeMarkLength = edgeMarkLength; this.insideMarkLength = insideMarkLength; this.intervalCount = intervalCount; } } public LineSettings settings; private LineSettings _settings; private IReadOnlyList<Vector3> _paths = new List<Vector3>(); private List<Vector3> currentPaths = new List<Vector3>(); private Material lineMaterial; private float relativeWidth; public bool enabled = true; private static readonly int SrcBlend = Shader.PropertyToID("_SrcBlend"); private static readonly int DstBlend = Shader.PropertyToID("_DstBlend"); private static readonly int Cull = Shader.PropertyToID("_Cull"); private static readonly int ZWrite = Shader.PropertyToID("_ZWrite"); //描画するために使うマテリアルの初期化 private void InitMaterial() { if (!lineMaterial) { lineMaterial = new Material(Shader.Find("Hidden/Internal-Colored")); lineMaterial.hideFlags = HideFlags.HideAndDontSave; lineMaterial.SetInt(SrcBlend, (int) BlendMode.SrcAlpha); lineMaterial.SetInt(DstBlend, (int) BlendMode.OneMinusSrcAlpha); lineMaterial.SetInt(Cull, (int) CullMode.Off); lineMaterial.SetInt(ZWrite, 0); } } public void Draw(LineSettings currentSetting) { settings = currentSetting; //座標が入っていなかったら描画しない if (!enabled || settings.paths.Length <= 0 || settings.paths.Any(t => t == null)) return; IReadOnlyList<Vector3> vecPaths = settings.paths.Select(t => t.position).ToList(); InitMaterial(); lineMaterial.SetPass(0); GL.PushMatrix(); { GL.Begin(GL.QUADS); { GL.Color(settings.color); //設定が変更されていなかったら計算しない if (_settings.Equals(settings) && _paths.SequenceEqual(vecPaths)) { DotVertexes(currentPaths.ToArray()); } else { currentPaths.Clear(); _settings = settings; _paths = vecPaths; Vector3 v0, v1, o; //解像度に対する幅を求める relativeWidth = 1.0f / Screen.width * settings.width * 0.5f; for (int index = 0; index < _paths.Count - 1; index++) { v0 = _paths[index]; v1 = _paths[index + 1]; //2点の単位ベクトルを求める o = (new Vector3(v1.y, v0.x, 0.0f) - new Vector3(v0.y, v1.x, 0.0f)).normalized; DrawLine2D(v0, v1, o); DrawMark2D(v0, v1, o); } } } GL.End(); } GL.PopMatrix(); } //2点に線を引く関数 void DrawLine2D(Vector3 v0, Vector3 v1, Vector3 o) { Vector3 n = o * relativeWidth; Vector3[] vertex = new[] { new Vector3(v0.x - n.x, v0.y - n.y, 0.0f), new Vector3(v0.x + n.x, v0.y + n.y, 0.0f), new Vector3(v1.x + n.x, v1.y + n.y, 0.0f), new Vector3(v1.x - n.x, v1.y - n.y, 0.0f), }; DotVertexes(vertex); foreach (Vector3 v in vertex) { currentPaths.Add(v); } } //2点にメモリをつける関数 void DrawMark2D(Vector3 v0, Vector3 v1, Vector3 o) { Vector3 markLength, _v0, _v1, _o; Vector3 _unitVec = (v1 - v0) / settings.intervalCount; List<Vector3> _pos = new List<Vector3>(); for (int i = 0; i < settings.intervalCount + 1; i++) { _pos.Add(v0 + _unitVec * i); } for (int i = 0; i < _pos.Count; i++) { Vector3 vec = _pos[i]; float length = (i == 0 || i == _pos.Count - 1 ? settings.edgeMarkLength : settings.insideMarkLength); markLength = o * length; _v0 = new Vector3(vec.x - markLength.x, vec.y - markLength.y, 0.0f); _v1 = new Vector3(vec.x + markLength.x, vec.y + markLength.y, 0.0f); _o = (new Vector3(_v1.y, _v0.x, 0.0f) - new Vector3(_v0.y, _v1.x, 0.0f)).normalized; DrawLine2D(_v0, _v1, _o); } } //与えられた座標配列に頂点を打つ関数 void DotVertexes(Vector3[] pos) { foreach (Vector3 v in pos) { GL.Vertex3(v.x, v.y, v.z); } } }コードが長くなっているので、細かい説明は割愛します。
1、数直線に幅を持たせたい
Unity側で用意されているGLクラスには、ラインの幅を設定するための方法が用意されていません。
なので、自分でメッシュの頂点座標を計算して描画する必要があります。(OpenGLの方だとあるらしい)実装方法としては、ある直線があったとしてその直線を囲む四角形の頂点座標を算出し、描画すれば作ることができます。(要するに赤い点の座標を求めたい)
ここからは少し、数学の話になります。
下のテキストを見てください。ここでは、上の図のような座標があったとして、まず頂点との差分(緑の線の部分)を求めます。
左のページでは、二つの頂点座標から垂直なベクトルを求めています。しかし、これだけだと二つの頂点の距離によって緑の線の長さも変わってしまいます。そこで長さを1に固定した単位ベクトルを求めることで、それに適当な幅をかけると狙い通りの長さにすることができます。
そして最後にその差分を、元の頂点座標に足し合わせることで4点が求まります。この部分の実装はこのようになっています。
//垂直なベクトルを求め、正規化する o = (new Vector3(v1.y, v0.x, 0.0f) - new Vector3(v0.y, v1.x, 0.0f)).normalized; void DrawLine2D(Vector3 v0, Vector3 v1, Vector3 o) { //単位ベクトルに設定された幅をかける Vector3 n = o * relativeWidth; //オフセットを足して、頂点座標を求める Vector3[] vertex = new[] { new Vector3(v0.x - n.x, v0.y - n.y, 0.0f), new Vector3(v0.x + n.x, v0.y + n.y, 0.0f), new Vector3(v1.x + n.x, v1.y + n.y, 0.0f), new Vector3(v1.x - n.x, v1.y - n.y, 0.0f), }; //描画する(自作関数) DotVertexes(vertex); }2、数直線にメモリを付けたい
お気づきの方もいるかもしれませんが、1番の方法を利用すれば簡単に作ることができます。
先ほどの4点をそれぞれv0,v1とし、新たにまた4点を求めるだけです。
まとめ
これを実装するのに約半日潰してしまった...
多分LineRendererよりは軽くなっている気がする(気がするだけ)。
正直線を引くだけならLineRendererで良い気がします。たくさん線を引きたいのであれば、GLの方が高速かも?たくさん機能を追加すれば、もっと利便性は上がると思います。でも僕はやりません()
- 投稿日:2020-10-10T18:28:15+09:00
UnityからC#のファイルをVscodeで開いても補完機能が効いていなかったので効くようにした
C#の補完機能が効いていない
Vscodeの拡張で、下記を入れていたが補完が効いていなかった。
- C#
- Debugger for Unity
そのため、以下の記事を参考にさせていただきながら補完を効くように作業しました。
ほぼ記事内容をなぞっただけですが、備忘させてください...
超絶謝謝参考記事: UnityでVSCodeを使うための最低限必要な設定homebrewのインストール
以下URLからコードコピってきて、terminalに貼り付けて、homebrewのインストール
https://brew.sh/index_ja/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"インストールできたか確認。
brew -v Homebrew 2.5.5 Homebrew/homebrew-core (git revision 62667b; last commit 2020-10-10)入った。
Monoのインストール
brew install monoログを見てもSUCCESS的なの出てなくて、完了したか分からなかったのでversion確認してみる
mono --version Mono JIT compiler version 6.12.0.93 (2020-02/620cf538206 Tue Aug 25 14:04:52 EDT 2020) Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com TLS: SIGSEGV: altstack Notification: kqueue Architecture: amd64 Disabled: none Misc: softdebug Interpreter: yes LLVM: yes(610) Suspend: hybrid GC: sgen (concurrent by default)入ってる。
.NET CLI toolsのインストール
https://dotnet.microsoft.com/download/
上記からダウンロードしてインストール。
Vscodeの拡張機能をインストール
以下三つ
- C#
- Debugger for Unity
- Mono Debug
Mono Debugだけインストールしていなかったので、新たにインストール
Unity上でVscodeを標準エディタに設定する
メニューバーから、
Preferences > External Tools > External Script Editor
の箇所を、Visual Studio Codeにする。
Visual Studio Codeがプルダウンに出いていない場合、Browseから検索。Vscodeを再度確認
一応再起動して、VscodeでC#のファイルを開く。
int型の変数に、float型の変数を代入しようとしてみると、
電球が出た!!!
やりました。ありがとうございます捗ります。気になる箇所二点
1. VscodeでC#のファイルを開いた時に、右下にerrorが出る。
The .NET Core SDK cannot be located. .NET Core debugging will not be enabled. Make sure the .NET Core SDK is installed and is on the path..NETをインストールしたから出ないと思ったの出てきた。
気になる。
下記記事を参考に出ないようにしました。
VSCodeで「The .NET Core SDK cannot be located.」メッセージを抑止
2. 変数,関数の宣言をしたコードの上にreferencesが出てくる
邪魔だなと思ったので下記記事を参考にさせて頂き、非表示に
Visual Studio CodeのReference informationを非表示にする方法
- 投稿日:2020-10-10T17:16:30+09:00
【Unity】Coroutine Continue Failureのエラーメッセージ
他のスクリプトのコルーチンに対してStopCoroutine()しようとすると、エラーが吐き出されました。
でも実行はされるので、問題はないけどエラーメッセージが邪魔...。
他のスクリプトから直接StopCoroutine()するのではなく、StopCoroutine()させるメソッドを作って間接的に実行させました。【環境】
・Mac OSX El Capitan
・Unity versiton:2018.3.0【実行状況】
BallオブジェクトがStartオブジェクトとGoalオブジェクトの間を行き来するプログラムです。
BallオブジェクトにはMove.cs、StartとGoalオブジェクトにはKickBall.csをアタッチしています。Move.csのIEnumerator MoveTo( Vector3 goal)をKickBall.csから呼び出していました。
Move.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class Move : MonoBehaviour { public float speed; //goalの位置までスムーズに移動する public IEnumerator MoveTo( Vector3 goal) { while (Vector3.Distance(transform.position, goal) > 0.05f) { Vector3 nextPos = Vector3.Lerp(transform.position, goal, Time.deltaTime * speed); transform.position = nextPos; yield return null;//ここまでが1フレームの間に処理される } transform.position = goal; print("終了"); yield break;//処理が終わったら破棄する } }KickBall.csusing UnityEngine; public class KickBall : MonoBehaviour { public Transform target; private Vector3 targetPos; private Coroutine myCor; // Start is called before the first frame update void Start() { targetPos = target.position; } private void OnTriggerEnter(Collider other) { //Move.csのコルーチンを止めようとするとエラーメッセージが出た if (other.GetComponent<Move>().myCor != null) { StopCoroutine(other.GetComponent<Move>().myCor); } other.GetComponent<Move>().myCor = StartCoroutine(other.GetComponent<Move>().MoveTo(targetPos)); } }【解決方法】
下記フォーラムによると、
https://answers.unity.com/questions/989547/coroutine-continue-failure-when-using-stopcoroutin.htmlMake sure to call StopCoroutine() on the same object (MonoBehavior) that you started the coroutine.
コルーチンをスタートさせたオブジェクト(MonoBehavior)と同じオブジェクト内でStopCoroutine()を呼び出していることを確かめてください。とあります。
同じMonoBehaviorでStartCoroutine()してるんですが、StopCoroutine()を外から呼び出すことでエラーメッセージを吐いてしまうのかも、と思い、下記のように変更してメッセージが出ないようにしました。Move.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class Move : MonoBehaviour { public float speed; private Coroutine myCor; //MoveToをスタートさせるメソッド //外部からコルーチンを呼び出すときはこのメソッドを使う public void StartCor(Vector3 goal) { if (myCor != null) { StopCoroutine(myCor);//StartCoroutine()する前に停止させて、重複して実行されないようにする。 } myCor = StartCoroutine(MoveTo(goal)); } //goalの位置までスムーズに移動する public IEnumerator MoveTo( Vector3 goal) { while (Vector3.Distance(transform.position, goal) > 0.05f) { Vector3 nextPos = Vector3.Lerp(transform.position, goal, Time.deltaTime * speed); transform.position = nextPos; yield return null;//ここまでが1フレームの間に処理される } transform.position = goal; print("終了"); yield break;//処理が終わったら破棄する } }KickBall.csusing UnityEngine; public class KickBall : MonoBehaviour { public Transform target; private Vector3 targetPos; private Coroutine myCor; // Start is called before the first frame update void Start() { targetPos = target.position; } private void OnTriggerEnter(Collider other) { //StartCor()を使ってMove.csのMoveToを開始 other.GetComponent<Move>().StartCor(targetPos); } }
- 投稿日:2020-10-10T17:00:35+09:00
【Unity】FungusをLuaで使用する方法
Fungusはビジュアルスクリプティングで会話パートを実装できるアセットです。
ビジュアルスクリプティングなのでUnityEditor上で会話を打ち込む必要があります。
簡単な会話パートならUnityEditor上で済むと思いますが、本格的にアドベンチャーゲームを作る、もしくは会話だけUnityを知らない人に作ってもらおうとすると大変です。
会話パートをLuaに記述することで解決しようと思います。
ステップ0 Luaとは?
C#とは別の昔からゲームに組み込まれてきた汎用スクリプト言語です。
ステップ1 Fungusをアセットストアからインポートする。
https://assetstore.unity.com/packages/templates/systems/fungus-34184
Asset StoreでFungusを検索しダウンロード&インポートします。
ステップ2 Flowchart、LuaEnvironment、LuaScriptをシーンに配置する。
メニューの
Tools > Fungus > Create
から
- Flowchart
- LuaEnvironment
- LuaScript
をシーンに配置します。
ステップ3 Luaファイルを配置する。
任意のフォルダにLuaファイルを配置します。
Luaファイルの文字コードはUTF-8にしてください。
UTF-8でないと日本語が文字化けしてしまいます。ステップ4 Luaファイルを編集する。
Fungusで会話を表示したい場合は
say("会話")を記述してください。
改行は\r\nで表現できます。ステップ5
Lua Scriptの
- Lua Enviromentに先ほどHierarchyに配置したLuaEnvironmentをドラッグ&ドロップしてください。
- Lua Fileに任意の場所に保存したLuaファイルをドラッグ&ドロップしてください。
- Lua Scriptに直接Luaコマンドを打ち込めます。Lua Fileがある場合は、Lua Fileが実行された後に、Lua Scriptのコマンドが実行されます。
ステップ6 Luaの実行タイミングをスクリプトで操作する。
初期設定ではシーン開始時にLuaが実行されます。
実行タイミングを変更したい場合は、
LuaScript > Execute Handler > On Event
をStartからNothingにに変更してください。LuaScriptを他のスクリプトでGetComponentして、OnExecuteメソッドを実行してください。
- 投稿日:2020-10-10T16:50:10+09:00
インターフェース
インターフェースのどこが有効なのかよくわからないけど、
この投稿はわかりやすい
- 投稿日:2020-10-10T16:24:29+09:00
[wpf] ボタンのIsDefault・IsCancelを設定する
はじめに
仕事でOKボタンを押したらシャットダウンする自作windowを作る機会があったのですが、
「windowが表示されたときにEnterキー押したら、OKボタンのクリックイベントが実行されるようにしてね」と言われました。
言われるまではクリックでok等のボタンイベントが正常か確認していましたが、キーイベントでの挙動は見ていなかったのでこの機会に少しまとめてみました。
IsDefaultとIsCancelという便利なプロパティがwpfのボタンには存在するようなので、それを使ってみます。
作ったサンプル
テキストボックスを1つと、ボタンを5つ並べました。
default(okCancel)と書かれたボタンにIsDefault=Trueを割り当て、escape(ync)と書かれたボタンにIsCancel=Trueを割り当てています。
各ボタンにはクリックイベントを割り当てていて、show NodDefWin以外はメッセージボックスを表示させています。
xaml
<Grid Background="#b33e5c"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Grid Grid.ColumnSpan="3"> <TextBlock x:Name="text" Text="result" /> </Grid> <!--Defaultのボタン--> <Grid Grid.Column="0"> <Button Content="default(okCancel)" Width="100" Height="30" Click="Button_Default" IsDefault="True" /> </Grid> <Grid Grid.Column="1"> <StackPanel> <!--Escape時に選択されるボタン--> <Button Content="escape(ync)" Width="100" Height="30" IsCancel="True" Click="Button_Escape" /> <Button Content="onlyOk" Width="100" Height="30" Click="Button_OnlyOK" /> <Button Content="YsNo" Width="100" Height="30" Click="Button_YesNo" /> </StackPanel> </Grid> <Grid Grid.Column="2"> <Button Content="show NoDefWin" Width="100" Height="30" Click="Button_Custom" /> </Grid> </Grid>xaml.cs
private void Button_Default(object sender, RoutedEventArgs e) { var r = MessageBox.Show("Enter default ok", "caption", MessageBoxButton.OKCancel); this.text.Text = r.ToString(); } private void Button_Escape(object sender, RoutedEventArgs e) { var r = MessageBox.Show("Enter", "caption", MessageBoxButton.YesNoCancel); this.text.Text = r.ToString(); } private void Button_OnlyOK(object sender, RoutedEventArgs e) { var r = MessageBox.Show("Enter", "caption", MessageBoxButton.OK); this.text.Text = r.ToString(); } private void Button_YesNo(object sender, RoutedEventArgs e) { var r = MessageBox.Show("Enter", "caption", MessageBoxButton.YesNo); this.text.Text = r.ToString(); } /// 自作のwindowクラスを表示する private void Button_Custom(object sender, RoutedEventArgs e) { var s = new SubWindow(); s.ShowDialog(); }動作
Enterキー
default(okCancel) と書かれたボタンの IsDefault=True にしているので、アプリ起動時にEnterを押すとこのボタンのClickイベントが実行されて、MessageBoxButton.OKCancelのメッセージボックスが表示されます。
MessageBoxButton.OKCancelのメッセージボックスは OKボタンがIsDefault=Trueで キャンセルボタンがIsCancel=Trueになっています。
ですので、Enterを押すとテキストブロックにOKが表示され、Escapeを押すとCancelが表示されます。テキストブロックの文字がCancelになっていますね。
escapeキー
escapeキーを押すと IsCancel=trueにしているescape(ync)を表示しているボタンのclickイベントが実行されます。
MessageBoxButton.YesNoCancel で表示しているメッセージボックスも デフォルトでYes、EscapeでCancelが選択されるようです。
OKのみ / YesNo
MessageBoxButton.OKのメッセージボックスは デフォルト・Escape共にtrueのようで、Enter・escapeどちらでもokが選択されます。
MessageBoxButton.YesNoのメッセージボックスは デフォルトでyesが選択されるようですが、Escapeでは反応がありません。
IsCancelというプロパティ名なので、キャンセルボタンがない場合は基本的にはtrueにしないほうがいいようです。
escapeを押してもcloseしてくれないメッセージボックス。
show NoDefWin(自作window)
show NodefWinボタンで表示する自作windowはボタンを2つ並べているwindowです。
<!--一部のみ--> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Text="okでシャットダウンします" /> <Grid Grid.Column="0"> <Button Content="ok" Width="100" Height="30" Click="Button_Close"/> </Grid> <Grid Grid.Column="1"> <Button Content="cancel" Width="100" Height="30" Click="Button_Close"/> </Grid>Button_Closeイベントはthis.Close()のみです。
IsDefaultもIsCancelもtrueにしていないので、Enter・Escapeキーを押しても反応がありません。まぁ、当然ですね。
ということで自作windowでOK・キャンセルボタンを設置するときはIsDefaultとIsCancelプロパティを使ってあげようというお話でした。
まとめ
windowsの仕様に倣うのであれば、okタイプのボタンならDefault=Trueにして、キャンセルのボタンならIsCancel=Trueにしたほうがいいと改めて感じました。
・・・評価仕様でこういう確認見たことないな。
参考サイト
- 投稿日:2020-10-10T10:58:55+09:00
【Unity Test Runner】テスト駆動開発でプレイヤーHPクラスを実装する
はじめに
テスト駆動開発(TDD)に興味があったので、いろいろ調べて挑戦してみました。
今回はその忘備録として、テスト駆動開発でプレイヤーのHPクラスを実装した話を書こうと思います。
環境
Unity : 2019.4.10f1
Visual Studio : 2019 ver16.7.2Test Framework : 1.1.16
最新は1.1.18なんですね。アップデートしてない・・・。コード全文(Gist)
https://gist.github.com/poyoppo/904ec3acca61b0ded405563010696a7c
コード内の英語コメントが初心者すぎるけど気持ちで読んでください(丸投げ)テスト駆動開発とは
めちゃくちゃざっくりいうと、先にテストコードを書いてからプログラムを実装する開発方法です。
テスト駆動で開発するとプログラムの振る舞いがテストによって担保される、バグが減る、リファクタリングしやすくなる、などいろいろ良いことづくしなようですね。
初学者のわたしがアレコレ説明するよりも分かりやすい記事がたくさんあるのでご覧ください(丸投げ)参考になる記事
なぜテストコードを書く必要があるのか?
僕たちがテスト駆動開発をする理由テスト駆動開発する!!
それでは早速テストを書いていきたいと思います。
Unity Test Runnerの導入についてはこちらの記事が分かりやすいです。
Unityでテストを書くのが当然になる時代に今から備えようテストコードに「満たすべき仕様」を書いていく
今回はゼルダのようなHPを実装することにしたので、次のような仕様が求められそうです。
- HPはハートで表される
- ハートのかけら4つでハートが1つ増える(最大HPアップ)
- 最小ダメージはハート4分の1つ
- その他、基本的なHPの仕様(回復・ダメージ・HPは最大HPを超えない・HPは-1以下にならない・etc...)
「こんなことを調べる必要があるな」というものを、とりあえず書いていきます。
テストコード[Test] public void A_コンストラクタテスト() { } [Test] public void A_コンストラクタの引数に0以下を渡すと例外が発生する() { } [Test] public void D_IsFullプロパティテスト() { } [Test] public void G_回復メソッドテスト() { } [Test] public void G_HPは最大HPを超えない() { } [Test] public void G_回復メソッドの引数に負の値を指定すると例外が発生する() { } // 以下略わたしの場合、テストコード内では分かりやすさ優先で日本語で書いていきます。
英語力のある方は英語でまったく問題なしです。
(メソッド名などに日本語を使えるのは、[Test]属性が付いているから?とか、テストコード内だから?とか思っていましたが、ふつうのクラスでも日本語で書けるんですね。そんなことしないけど・・・)テストコード内のメソッドの順番と、Test Runnnerウィンドウのメソッドの順番(50音・ABC順になる?)が異なり分かりづらいので、大文字の英字_テスト名()にしています。
また、コンストラクタのテストはA_コンストラクタテスト()、プロパティのテストはD_プロパティテスト()・・・のようにテストしたい項目によって先頭の英字を変えました。
こうすることによってTest Runnerウィンドウ上の表示が何もしないよりは分かりやすくなります。
ちなみにテストをカテゴリ分けすることもできるようですが、詳しく調べてません。
【Unity】Unity Test Runner のテストをカテゴリ分けする方法テストコードを書く
実際にテストコードを書いていきます。
テスト対象であるPlayerHpの実装より先にテストコードを書きます。例えば、コンストラクタのテストをするのであれば、しっかりインスタンスが生成されていること、初期状態は最大HPと現在HPが等しいことなどが分かれば良さそうです。
また、インスタンス生成時に0以下の値を渡すと例外が発生するようにしたいです。以上の仕様をテストするコードが以下です。
※今回はPlayerHpクラスの生成時に、内部で「初期ハート数*4」という計算をすることをこの時点で決めてました。
(最初はハート4分の1つ=0.25fという扱いにしてたのですが、不便すぎたので・・・)テストコード[Test] public void A_コンストラクタテスト() { // 初期HPはハート3つ分 var playerHp = new PlayerHp(3); // playerHp が存在する Assert.That(playerHp, Is.Not.Null); // 最大HP, 現在のHPの初期値は12(3 * 4 = 12)である Assert.That(playerHp.MaxHp, Is.EqualTo(12)); Assert.That(playerHp.CurrentHp, Is.EqualTo(12)); } [Test] public void A_コンストラクタの引数に0以下を渡すと例外が発生する() { TypeInitializationException ex = Assert.Throws<TypeInitializationException>(() => new PlayerHp(0)); Assert.AreEqual(ex.TypeName, typeof(PlayerHp).FullName); Assert.That(ex.InnerException, Is.TypeOf<ArgumentOutOfRangeException>()); } // 以下略テストにはNunit.FrameworkのAssertクラスを使います。
基本的には次のAssert.Thatメソッドを用いて書きます。Assert.That(テストしたい値, 期待する値);Assert.AreEqual()という書き方もあるようですが、現在はAssert.Thatで書くのが推奨されているようです。
NUnitのAssert.ThatメソッドにIsとかHasとかを入れて柔軟なテストコードを書こうPlayerHpクラスに最小限の実装を書く
このままだとコンパイルエラーになるので、PlayerHpクラスにプロパティやコンストラクタを定義しておきます。あとからテストする回復メソッドやダメージメソッドも定義だけしておきます。
中身の実装はしません。PlayerHp.cspublic class PlayerHp { private int maxHp; private int currentHp; public int MaxHp { get { return maxHp; } } // 最大HP public int CurrentHp { get { return currentHp; } } // 現在のHP // コンストラクタ public PlayerHp(int initialHeartCount) { } // メソッドも同様に書いていく public void HealHp(int healPoint) { } public void DamageHp(int damagePoint) { } }テスト実行→失敗
プロパティなどを定義するとコンパイルが通るので、Unityに戻ってテストを実行してみます。
・・・。
なぜコードそのものを書く前にテストを書くのか、不思議に思う方もいらっしゃるでしょう。その理由は、コードを書いてからテストを書くようにすると、開発者はしばしばテストが通るようにテストを書いてしまうことがあるからです。まず失敗するテストを書けば、テストが失敗するのには妥当な理由(たとえば、必要な機能が正しく実装されていない)があることを確信でき、誤検知を排除することができます。
Unity Test Runner でテスト駆動開発を試す(Unity公式ブログより)
「実装してないので失敗する」ということが分かるのが重要なようです。
テスト対象スクリプトを実装する
ここまできて初めてPlayerHpの実装をしていきます。
PlayerHp.cs// 最大HP計算用の定数 private const int PIEACE_AMOUNT = 4; // コンストラクタ public PlayerHp(int initialHeartCount) { if (initialHeartCount <= 0) { throw new TypeInitializationException(typeof(PlayerHp).FullName, new ArgumentOutOfRangeException()); } int initHp = initialHeartCount * PIEACE_AMOUNT; maxHp = initHp; currentHp = initHp; } // 以下略改めてテスト実行
今度はテストが通るはずです。
この繰り返しで他のメソッドも実装していきます。ほかのテストもすべて通るようになればPlayerHpクラスは完成です。
まとめ
やってみる前はとっつきにくいイメージでしたが、挑戦してみると面白く、テストがすべて通ったときの気持ちよさもやみつきになりそうです・・・。
ただ慣れるまでは「テストコード自体が間違っている」ということも起きかねないので、色んなクラスのテストを書いてみて経験を積んでいきたいと思います。一度やってみたくらいではテスト駆動開発による恩恵をすべて体感することはできませんでしたが、テスト駆動を用いていなかった今までと比べて、「どう書けばいいのか分からない」が無くなる(減る)というのが自分にとっての一番のメリットに感じました。
次の挑戦
各テスト前後に実行される属性である[SetUp][TearDown]などには触れなかったので、今後使ってみようと思います。
テスト毎にPlayerHpをインスタンス化しているところも[SetUp]を用いて書き直せそうですねさいごに
もっと良い実装方法やテスト方法があったら教えていただけると嬉しいです。
また、誤字脱字・コードの間違いなどがあればそちらもご指摘いただけると助かります
最後まで読んでくださりありがとうございました!