tag:crieit.net,2005:https://crieit.net/users/1642minestrone/feed 長野別荘の投稿 - Crieit Crieitでユーザー長野別荘による最近の投稿 2019-04-18T20:37:30+09:00 https://crieit.net/users/1642minestrone/feed tag:crieit.net,2005:PublicArticle/14932 2019-04-18T20:34:30+09:00 2019-04-18T20:37:30+09:00 https://crieit.net/posts/d022f730ace8cd2de23c552cbe318a01 文字列連結処理を比較してみた② <p>Unityのバージョン:2019.1.0f2</p> <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>前回の記事で(「<a href="https://crieit.net/posts/638492034141fa6e5cebd5e507698b59">文字列連結処理を比較してみた</a>」)定数だと最適化されるよ、連結数が違うと結果が変わるよ、とコメントを頂いたので、条件を変えて再度比較してみました。</p> <h1 id="比較内容"><a href="#%E6%AF%94%E8%BC%83%E5%86%85%E5%AE%B9">比較内容</a></h1> <p>比べた文字連結方法は以下の通りです。</p> <h3 id="エントリーNo.1:+"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.1%3A%2B">エントリーNo.1:+</a></h3> <pre><code>string s = "a" + "b"; </code></pre> <h3 id="エントリーNo.2:StringBuilder"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.2%3AStringBuilder">エントリーNo.2:StringBuilder</a></h3> <pre><code>StringBuilder builder = new StringBuilder(); for(int i = 0; i < n; ++i) { builder.Clear(); builder.Append(”a”).Append("b"); string s = builder.ToString(); } </code></pre> <h3 id="エントリーNo.3:Concat"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.3%3AConcat">エントリーNo.3:Concat</a></h3> <pre><code>string s = string.Concat("a", "b"); </code></pre> <h3 id="エントリーNo.4:Format"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.4%3AFormat">エントリーNo.4:Format</a></h3> <pre><code>string s = string.Format("{0}{1}", "a", "b"); </code></pre> <h3 id="エントリーNo.5:$&quot;&quot;"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.5%3A%24%26quot%3B%26quot%3B">エントリーNo.5:$""</a></h3> <pre><code>string a = "a", b = "b"; string s = $"{a}{b}"; </code></pre> <h3 id="エントリーNo.6:StringBuilderTemporary"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.6%3AStringBuilderTemporary">エントリーNo.6:StringBuilderTemporary</a></h3> <pre><code>string str = StrOpe.i +"a" + "b"; </code></pre> <p>※参考<br /> <a target="_blank" rel="nofollow noopener" href="http://baba-s.hatenablog.com/entry/2017/12/11/090000">【Unity】string の連結を StringBuilder に置き換えてパフォーマンスを改善できる「StringBuilderTemporary」紹介</a></p> <h3 id="エントリーNo.7:FastString"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.7%3AFastString">エントリーNo.7:FastString</a></h3> <pre><code>FastString fast = new FastString(64); for(int i = 0; i < n; ++i) { fast.Clear(); fast.Append(”a”).Append("b"); string s = fast.ToString(); } </code></pre> <p>※参考<br /> <a target="_blank" rel="nofollow noopener" href="http://baba-s.hatenablog.com/entry/2017/12/27/083200">【Unity】string や StringBuilder よりもメモリ割り当てが少なく高速な文字列クラス「FastString」紹介</a></p> <h3 id="エントリーNo.8:UtilsFormat"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.8%3AUtilsFormat">エントリーNo.8:UtilsFormat</a></h3> <pre><code>string str = StringUtils.Format("{0}{1}", "a", "b"); </code></pre> <p>※参考<br /> <a target="_blank" rel="nofollow noopener" href="http://baba-s.hatenablog.com/entry/2016/09/12/100000">【Unity】ボックス化をなるべく回避して GC の発生を抑える string.Format「StringUtils」</a></p> <h1 id="パターン1:5個の文字列連結をN回繰り返す"><a href="#%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B31%EF%BC%9A5%E5%80%8B%E3%81%AE%E6%96%87%E5%AD%97%E5%88%97%E9%80%A3%E7%B5%90%E3%82%92N%E5%9B%9E%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99">パターン1:5個の文字列連結をN回繰り返す</a></h1> <h2 id="検証コード"><a href="#%E6%A4%9C%E8%A8%BC%E3%82%B3%E3%83%BC%E3%83%89">検証コード</a></h2> <pre><code>using System; using System.Text; using UnityEngine; using UnityEngine.Profiling; using StrOpe = StringOperationUtil.OptimizedStringOperation; public class Test : MonoBehaviour { const int repeat = 100000; // N string tmp1 = "a"; string tmp2 = "b"; string tmp3 = "c"; string tmp4 = "d"; string tmp5 = "e"; StringBuilder _builder; FastString _fast = new FastString(64); void Start() { _builder = new StringBuilder(); PlayTest(Plus, "+"); PlayTest(StringBuilder, "StringBuilder"); PlayTest(Concat, "Concat"); PlayTest(Format, "Format"); PlayTest(Interpolation, "$\"\""); PlayTest(StringBuilderTemporary, "StringBuilderTemporary"); PlayTest(FastString, "FastString"); PlayTest(UtilsFormat, "UtilsFormat"); } void PlayTest(Func<string> func, string title) { Profiler.BeginSample(title); string s = string.Empty; for (int i = 0; i < repeat; ++i) { s = func(); } Profiler.EndSample(); } string Plus() { string str = tmp1 + tmp2 + tmp3 + tmp4 + tmp5; return str; } string StringBuilder() { _builder.Clear(); _builder.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5); string str = _builder.ToString(); return str; } string Concat() { string str = string.Concat(tmp1, tmp2, tmp3, tmp4, tmp5); return str; } string Format() { string str = string.Format("{0}{1}{2}{3}{4}", tmp1, tmp2, tmp3, tmp4, tmp5); return str; } string Interpolation() { string str = $"{tmp1}{tmp2}{tmp3}{tmp4}{tmp5}"; return str; } string StringBuilderTemporary() { string str = StrOpe.i + tmp1 + tmp2 + tmp3 + tmp4 + tmp5; return str; } string FastString() { _fast.Clear(); _fast.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5); string str = _fast.ToString(); return str; } string UtilsFormat() { string str = StringUtils.Format("{0}{1}{2}{3}{4}", tmp1, tmp2, tmp3, tmp4, tmp5); return str; } } </code></pre> <h2 id="検証結果"><a href="#%E6%A4%9C%E8%A8%BC%E7%B5%90%E6%9E%9C">検証結果</a></h2> <h3 id="N=10"><a href="#N%3D10">N=10</a></h3> <p><a href="https://crieit.now.sh/upload_images/023b32b0c0effa655734e065612b242f5cb75c9a7f6e2.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/023b32b0c0effa655734e065612b242f5cb75c9a7f6e2.PNG?mw=700" alt="5_10.PNG" /></a></p> <h3 id="N=50"><a href="#N%3D50">N=50</a></h3> <p><a href="https://crieit.now.sh/upload_images/96e77a1523759bd128d32ca2c06b46a55cb765378ea02.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/96e77a1523759bd128d32ca2c06b46a55cb765378ea02.PNG?mw=700" alt="5_50.PNG" /></a></p> <h3 id="N=100"><a href="#N%3D100">N=100</a></h3> <p><a href="https://crieit.now.sh/upload_images/cf6c5a861bc6ea75e20b62254bbada1d5cb765419ea5c.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/cf6c5a861bc6ea75e20b62254bbada1d5cb765419ea5c.PNG?mw=700" alt="5_100.PNG" /></a></p> <h3 id="N=1000"><a href="#N%3D1000">N=1000</a></h3> <p><a href="https://crieit.now.sh/upload_images/3c2786f4c09673deb26ec36a8e52ed405cb75cb11e65f.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3c2786f4c09673deb26ec36a8e52ed405cb75cb11e65f.PNG?mw=700" alt="5_1000.PNG" /></a></p> <h1 id="パターン2:10個の文字列連結をN回繰り返す"><a href="#%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B32%EF%BC%9A10%E5%80%8B%E3%81%AE%E6%96%87%E5%AD%97%E5%88%97%E9%80%A3%E7%B5%90%E3%82%92N%E5%9B%9E%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99">パターン2:10個の文字列連結をN回繰り返す</a></h1> <h2 id="検証結果"><a href="#%E6%A4%9C%E8%A8%BC%E7%B5%90%E6%9E%9C">検証結果</a></h2> <h3 id="N=10"><a href="#N%3D10">N=10</a></h3> <p><a href="https://crieit.now.sh/upload_images/7ad21654cf5556590e72451435bc34855cb75d1724fc3.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7ad21654cf5556590e72451435bc34855cb75d1724fc3.PNG?mw=700" alt="10_10.PNG" /></a></p> <h3 id="N=50"><a href="#N%3D50">N=50</a></h3> <p><a href="https://crieit.now.sh/upload_images/25cbeabfbc145665c1b6471f563ba86d5cb766cf2abe3.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/25cbeabfbc145665c1b6471f563ba86d5cb766cf2abe3.PNG?mw=700" alt="10_50.PNG" /></a></p> <h3 id="N=100"><a href="#N%3D100">N=100</a></h3> <p><a href="https://crieit.now.sh/upload_images/4c0e67f81fb2501b01ab079be63ae4185cb766d8e65a9.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4c0e67f81fb2501b01ab079be63ae4185cb766d8e65a9.PNG?mw=700" alt="10_100.PNG" /></a></p> <h3 id="N=1000"><a href="#N%3D1000">N=1000</a></h3> <p><a href="https://crieit.now.sh/upload_images/7f1df8b3c4d5e8a4ee3d02275563f3365cb75d225a2e4.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7f1df8b3c4d5e8a4ee3d02275563f3365cb75d225a2e4.PNG?mw=700" alt="10_1000.PNG" /></a></p> <h1 id="パターン2:N個の文字列を連結する"><a href="#%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B32%EF%BC%9AN%E5%80%8B%E3%81%AE%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E9%80%A3%E7%B5%90%E3%81%99%E3%82%8B">パターン2:N個の文字列を連結する</a></h1> <h2 id="検証コード"><a href="#%E6%A4%9C%E8%A8%BC%E3%82%B3%E3%83%BC%E3%83%89">検証コード</a></h2> <pre><code>using System; using System.Text; using UnityEngine; using UnityEngine.Profiling; using StrOpe = StringOperationUtil.OptimizedStringOperation; public class Test : MonoBehaviour { const int repeat = 100000; // N string tmp = "a"; StringBuilder _builder; FastString _fast = new FastString(repeat + 1); void Start() { _builder = new StringBuilder(); PlayTest(Plus, "+"); PlayTest(StringBuilder, "StringBuilder"); PlayTest(Concat, "Concat"); PlayTest(Format, "Format"); PlayTest(Interpolation, "$\"\""); PlayTest(StringBuilderTemporary, "StringBuilderTemporary"); PlayTest(FastString, "FastString"); PlayTest(UtilsFormat, "UtilsFormat"); } void PlayTest(Func<string> func, string title) { Profiler.BeginSample(title); string s = func(); Profiler.EndSample(); } string Plus() { string str = string.Empty; for (int i = 0; i < repeat; ++i) str += tmp; return str; } string StringBuilder() { _builder.Clear(); for (int i = 0; i < repeat; ++i) _builder.Append(tmp); string str = _builder.ToString(); return str; } string Concat() { string str = string.Empty; for (int i = 0; i < repeat; ++i) str = string.Concat(str, tmp); return str; } string Format() { string str = string.Empty; for (int i = 0; i < repeat; ++i) str = string.Format("{0}{1}", str, tmp); return str; } string Interpolation() { string str = string.Empty; for (int i = 0; i < repeat; ++i) str = $"{str}{tmp}"; return str; } string StringBuilderTemporary() { string str = string.Empty; for (int i = 0; i < repeat; ++i) str = StrOpe.i + str + tmp; return str; } string FastString() { for (int i = 0; i < repeat; ++i) _fast.Append(tmp); string str = _fast.ToString(); return str; } string UtilsFormat() { string str = string.Empty; for (int i = 0; i < repeat; ++i) str = StringUtils.Format("{0}{1}", str, tmp); return str; } } </code></pre> <h3 id="N=10"><a href="#N%3D10">N=10</a></h3> <p><a href="https://crieit.now.sh/upload_images/3024bd511477d218438a22db936592535cb761863636a.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3024bd511477d218438a22db936592535cb761863636a.PNG?mw=700" alt="10.PNG" /></a></p> <h3 id="N=50"><a href="#N%3D50">N=50</a></h3> <p><a href="https://crieit.now.sh/upload_images/dd29da933422eec845a9702258d198d75cb7630a2614f.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/dd29da933422eec845a9702258d198d75cb7630a2614f.PNG?mw=700" alt="50.PNG" /></a></p> <h3 id="N=100"><a href="#N%3D100">N=100</a></h3> <p><a href="https://crieit.now.sh/upload_images/71c2ee968a5fe761022d61d2d4482e885cb76315d35ec.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/71c2ee968a5fe761022d61d2d4482e885cb76315d35ec.PNG?mw=700" alt="100.PNG" /></a></p> <h3 id="N=1000"><a href="#N%3D1000">N=1000</a></h3> <p><a href="https://crieit.now.sh/upload_images/1b3dfd0b2548e302b1391ecfe1b62ea65cb7633496f7c.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1b3dfd0b2548e302b1391ecfe1b62ea65cb7633496f7c.PNG?mw=700" alt="1000.PNG" /></a></p> <h1 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h1> <p>正確なしきい値は出していませんが、数が少ない連結(50個くらいまで)は「<strong>$""</strong>」、それ以上はStringBuilderを使えばいいのかな。</p> 長野別荘 tag:crieit.net,2005:PublicArticle/14878 2019-03-25T18:39:08+09:00 2019-03-25T18:40:26+09:00 https://crieit.net/posts/638492034141fa6e5cebd5e507698b59 文字列連結処理を比較してみた <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>Unityでの文字列連結のパフォーマンスについては、いつもお世話になっている<a target="_blank" rel="nofollow noopener" href="http://baba-s.hatenablog.com/">コガネブログさん</a>がたびたび記事を上げてくださっています。<br /> ただ、いろんな方法があるので結局どれを使えばいいのか分からないな~となることが度々ありました。<br /> そこで、今回は自分が見たことある、思いつく文字列連結の処理の速度とGCを比較してみました。</p> <h1 id="比較内容"><a href="#%E6%AF%94%E8%BC%83%E5%86%85%E5%AE%B9">比較内容</a></h1> <p>比べた文字連結方法は以下の通りです。</p> <h3 id="エントリーNo.1:+"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.1%3A%2B">エントリーNo.1:+</a></h3> <blockquote> <p>string s = "a" + "b";</p> </blockquote> <p>一番ノーマルなパターンです。</p> <h3 id="エントリーNo.2:StringBuilder(New)"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.2%3AStringBuilder%28New%29">エントリーNo.2:StringBuilder(New)</a></h3> <blockquote> <p>StringBuilder builder = new StringBuilder();<br /> builder.Append(”a”).Append("b");<br /> string s = builder.ToString();</p> </blockquote> <p>毎回StringBuilderのインスタンスを作成します。</p> <h3 id="エントリーNo.3:StringBuilder(Repeat)"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.3%3AStringBuilder%28Repeat%29">エントリーNo.3:StringBuilder(Repeat)</a></h3> <blockquote> <p>StringBuilder builder = new StringBuilder();<br /> for(int i = 0; i < n; ++i)<br /> {<br /> builder.Clear();<br /> builder.Append(”a”).Append("b");<br /> string s = builder.ToString();<br /> }</p> </blockquote> <p>あらかじめStringBuilderのインスタンスを作成しておきます。</p> <h3 id="エントリーNo.4:Concat"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.4%3AConcat">エントリーNo.4:Concat</a></h3> <blockquote> <p>string s = string.Concat("a", "b");</p> </blockquote> <p>string.Concatで連結します。</p> <h3 id="エントリーNo.5:Format"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.5%3AFormat">エントリーNo.5:Format</a></h3> <blockquote> <p>string s = string.Format("{0}{1}", "a", "b");</p> </blockquote> <p>string.Formatで連結します。</p> <h3 id="エントリーNo.6:$&quot;&quot;"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.6%3A%24%26quot%3B%26quot%3B">エントリーNo.6:$""</a></h3> <blockquote> <p>string a = "a", b = "b";<br /> string s = $"{a}{b}";</p> </blockquote> <p>C#6以降の文字列補完で連結します。</p> <h3 id="エントリーNo.7:StringBuilderTemporary"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.7%3AStringBuilderTemporary">エントリーNo.7:StringBuilderTemporary</a></h3> <blockquote> <p>string str = StrOpe.i +"a" + "b";</p> </blockquote> <p>こちらのブログの方法です。<br /> <a target="_blank" rel="nofollow noopener" href="http://baba-s.hatenablog.com/entry/2017/12/11/090000">【Unity】string の連結を StringBuilder に置き換えてパフォーマンスを改善できる「StringBuilderTemporary」紹介</a></p> <h3 id="エントリーNo.8:FastString(New)"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.8%3AFastString%28New%29">エントリーNo.8:FastString(New)</a></h3> <blockquote> <p>FastString fast = new FastString(64);<br /> fast.Append("a").Append("b");<br /> string str = fast.ToString();</p> </blockquote> <p>こちらのブログの方法です。毎回インスタンスを作成します。<br /> <a target="_blank" rel="nofollow noopener" href="http://baba-s.hatenablog.com/entry/2017/12/27/083200">【Unity】string や StringBuilder よりもメモリ割り当てが少なく高速な文字列クラス「FastString」紹介</a></p> <h3 id="エントリーNo.9:FastString(Repeat)"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.9%3AFastString%28Repeat%29">エントリーNo.9:FastString(Repeat)</a></h3> <blockquote> <p>FastString fast = new FastString(64);<br /> for(int i = 0; i < n; ++i)<br /> {<br /> fast.Clear();<br /> fast.Append(”a”).Append("b");<br /> string s = fast.ToString();<br /> }</p> </blockquote> <p>No.8の方法で、あらかじめインスタンスを生成しておきます。</p> <h3 id="エントリーNo.10:UtilsFormat"><a href="#%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BCNo.10%3AUtilsFormat">エントリーNo.10:UtilsFormat</a></h3> <blockquote> <p>string str = StringUtils.Format("{0}{1}", "a", "b");</p> </blockquote> <p>こちらのブログの方法です。<br /> <a target="_blank" rel="nofollow noopener" href="http://baba-s.hatenablog.com/entry/2016/09/12/100000">【Unity】ボックス化をなるべく回避して GC の発生を抑える string.Format「StringUtils」</a></p> <h1 id="パターン1:10個の文字列連結をN回繰り返す"><a href="#%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B31%EF%BC%9A10%E5%80%8B%E3%81%AE%E6%96%87%E5%AD%97%E5%88%97%E9%80%A3%E7%B5%90%E3%82%92N%E5%9B%9E%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99">パターン1:10個の文字列連結をN回繰り返す</a></h1> <h2 id="検証コード"><a href="#%E6%A4%9C%E8%A8%BC%E3%82%B3%E3%83%BC%E3%83%89">検証コード</a></h2> <pre><code>using System; using System.Text; using UnityEngine; using UnityEngine.Profiling; using StrOpe = StringOperationUtil.OptimizedStringOperation; public class Test : MonoBehaviour { const int repeat = 10000; const string tmp1 = "a"; const string tmp2 = "b"; const string tmp3 = "c"; const string tmp4 = "d"; const string tmp5 = "e"; const string tmp6 = "f"; const string tmp7 = "g"; const string tmp8 = "h"; const string tmp9 = "i"; const string tmp10 = "j"; StringBuilder _builder; FastString _fast = new FastString(64); void Start() { _builder = new StringBuilder(); PlayTest(Plus, "+"); PlayTest(StringBuilderNew, "StringBuilder(New)"); PlayTest(StringBuilderRepeat, "StringBuilder(Repeat)"); PlayTest(Concat, "Concat"); PlayTest(Format, "Format"); PlayTest(Interpolation, "Interpolation"); PlayTest(StringBuilderTemporary, "StringBuilderTemporary"); PlayTest(FastStringNew, "FastString(New)"); PlayTest(FastStringRepeat, "FastString(Repeat)"); PlayTest(UtilsFormat, "UtilsFormat"); } void PlayTest(Func<string> func, string title) { Profiler.BeginSample(title); string s = string.Empty; for (int i = 0; i < repeat; ++i) { s = func(); } Profiler.EndSample(); } string Plus() { string str = tmp1 + tmp2 + tmp3 + tmp4 + tmp5 + tmp6 + tmp7 + tmp8 + tmp9; return str; } string StringBuilderNew() { StringBuilder builder = new StringBuilder(); builder.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5).Append(tmp6).Append(tmp7).Append(tmp8).Append(tmp9); string str = builder.ToString(); return str; } string StringBuilderRepeat() { _builder.Clear(); _builder.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5).Append(tmp6).Append(tmp7).Append(tmp8).Append(tmp9); string str = _builder.ToString(); return str; } string Concat() { string str = string.Concat(tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9); return str; } string Format() { string str = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}", tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9); return str; } string Interpolation() { string str = $"{tmp1}{tmp2}{tmp3}{tmp4}{tmp5}{tmp6}{tmp7}{tmp8}{tmp9}"; return str; } string StringBuilderTemporary() { string str = StrOpe.i + tmp1 + tmp2 + tmp3 + tmp4 + tmp5 + tmp6 + tmp7 + tmp8 + tmp9; return str; } string FastStringNew() { FastString fast = new FastString(64); fast.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5).Append(tmp6).Append(tmp7).Append(tmp8).Append(tmp9); string str = fast.ToString(); return str; } string FastStringRepeat() { _fast.Clear(); _fast.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5).Append(tmp6).Append(tmp7).Append(tmp8).Append(tmp9); string str = _fast.ToString(); return str; } string UtilsFormat() { string str = StringUtils.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}", tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9); return str; } </code></pre> <h2 id="検証結果"><a href="#%E6%A4%9C%E8%A8%BC%E7%B5%90%E6%9E%9C">検証結果</a></h2> <h3 id="N=10"><a href="#N%3D10">N=10</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c9205fbe751c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c9205fbe751c.png?mw=700" alt="image.png" /></a></p> <h3 id="N=100"><a href="#N%3D100">N=100</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c920453da9cf.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c920453da9cf.png?mw=700" alt="image.png" /></a></p> <h3 id="N=1000"><a href="#N%3D1000">N=1000</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c9204a66f5c4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c9204a66f5c4.png?mw=700" alt="image.png" /></a></p> <h3 id="N=10000"><a href="#N%3D10000">N=10000</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c9204fc05785.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c9204fc05785.png?mw=700" alt="image.png" /></a></p> <h3 id="N=100000"><a href="#N%3D100000">N=100000</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c92054aa786c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c92054aa786c.png?mw=700" alt="image.png" /></a></p> <p><strong>「+」</strong> と <strong>「$""]</strong> が速度、GC両方ともに優れているという結果になりました。なんで?<br /> どのサイトを見ても、+で連結するよりStringBuilder使った方がいいよって書いてあるのになんでこうなった?</p> <h1 id="パターン2:N個の文字列を連結する"><a href="#%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B32%EF%BC%9AN%E5%80%8B%E3%81%AE%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E9%80%A3%E7%B5%90%E3%81%99%E3%82%8B">パターン2:N個の文字列を連結する</a></h1> <h2 id="検証コード"><a href="#%E6%A4%9C%E8%A8%BC%E3%82%B3%E3%83%BC%E3%83%89">検証コード</a></h2> <pre><code>using System; using System.Text; using UnityEngine; using UnityEngine.Profiling; using StrOpe = StringOperationUtil.OptimizedStringOperation; public class Test : MonoBehaviour { const int repeat = 100000; const string tmp1 = "a"; const string tmp2 = "b"; const string tmp3 = "c"; const string tmp4 = "d"; const string tmp5 = "e"; const string tmp6 = "f"; const string tmp7 = "g"; const string tmp8 = "h"; const string tmp9 = "i"; const string tmp10 = "j"; StringBuilder _builder; FastString _fast = new FastString(repeat + 1); void Start() { _builder = new StringBuilder(); PlayTest(Plus, "+"); PlayTest(StringBuilderNew, "StringBuilder(New)"); PlayTest(StringBuilderRepeat, "StringBuilder(Repeat)"); PlayTest(Concat, "Concat"); PlayTest(Format, "Format"); PlayTest(Interpolation, "$\"\""); PlayTest(StringBuilderTemporary, "StringBuilderTemporary"); PlayTest(FastStringNew, "FastString(New)"); PlayTest(FastStringRepeat, "FastString(Repeat)"); PlayTest(UtilsFormat, "UtilsFormat"); } void PlayTest(Func<string> func, string title) { Profiler.BeginSample(title); string s = func(); Profiler.EndSample(); } string Plus() { string str = string.Empty; for (int i = 0; i < repeat; ++i) { str += tmp1; } return str; } string StringBuilderNew() { StringBuilder builder = new StringBuilder(); for (int i = 0; i < repeat; ++i) { builder.Append(tmp1); } string str = builder.ToString(); return str; } string StringBuilderRepeat() { _builder.Clear(); for (int i = 0; i < repeat; ++i) { _builder.Append(tmp1); } string str = _builder.ToString(); return str; } string Concat() { string str = string.Empty; for (int i = 0; i < repeat; ++i) { str = string.Concat(str, tmp1); } return str; } string Format() { string str = string.Empty; for (int i = 0; i < repeat; ++i) { str = string.Format("{0}{1}", str, tmp1); } return str; } string Interpolation() { string str = string.Empty; for (int i = 0; i < repeat; ++i) { str = $"{str}{tmp1}"; } return str; } string StringBuilderTemporary() { string str = string.Empty; for (int i = 0; i < repeat; ++i) { str = StrOpe.i + str + tmp1; } return str; } string FastStringNew() { FastString fast = new FastString(repeat + 1); for (int i = 0; i < repeat; ++i) { fast.Append(tmp1); } string str = fast.ToString(); return str; } string FastStringRepeat() { for (int i = 0; i < repeat; ++i) { _fast.Append(tmp1); } string str = _fast.ToString(); return str; } string UtilsFormat() { string str = string.Empty; for (int i = 0; i < repeat; ++i) { str = StringUtils.Format("{0}{1}", str, tmp1); } return str; } } </code></pre> <h2 id="検証結果"><a href="#%E6%A4%9C%E8%A8%BC%E7%B5%90%E6%9E%9C">検証結果</a></h2> <h3 id="N=10"><a href="#N%3D10">N=10</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c9876c4c9879.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c9876c4c9879.png?mw=700" alt="image.png" /></a></p> <h3 id="N=100"><a href="#N%3D100">N=100</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c989ab321443.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c989ab321443.png?mw=700" alt="image.png" /></a></p> <h3 id="N=1000"><a href="#N%3D1000">N=1000</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c989b58b60ad.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c989b58b60ad.png?mw=700" alt="image.png" /></a></p> <h3 id="N=10000"><a href="#N%3D10000">N=10000</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c989bec3d855.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c989bec3d855.png?mw=700" alt="image.png" /></a></p> <h3 id="N=100000"><a href="#N%3D100000">N=100000</a></h3> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c989ccf40a6d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c989ccf40a6d.png?mw=700" alt="image.png" /></a></p> <p>今回は <strong>「FastString」</strong> と <strong>「StringBuilder」</strong> の2強です。<br /> なるほど、1つのstringに繰り返し連結させる場合はStringBuilderの方が優れているんですね。</p> <h1 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h1> <p>10数個の文字列を一度に連結して、複数の文字列を生成する場合は <strong>「$""」</strong> か <strong>「+」</strong> を(可読性を考えるなら「$""」でしょう)、<br /> 1つのstringに繰り返し連結して長文を作りたいときは <strong>「FastString」</strong> か <strong>「StringBuilder」</strong> で連結するのがよさそうです。</p> <p>「$""」が一番使う機会が多そうですね。</p> 長野別荘 tag:crieit.net,2005:PublicArticle/14857 2019-03-04T19:59:51+09:00 2019-03-04T19:59:51+09:00 https://crieit.net/posts/Tilemap-5c7d052704d2c Tilemapからミニマップを作成してみた② <p>前回の続きです。<br /> <a href="https://crieit.net/posts/Tilemap-5c73afa5127c1">Tilemapからミニマップを作成してみた①</a></p> <p>(今はこんな感じ)<br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738d4e6de7d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738d4e6de7d.png?mw=700" alt="image.png" /></a></p> <h1 id="テクスチャサイズを調整する(余白を作る)"><a href="#%E3%83%86%E3%82%AF%E3%82%B9%E3%83%81%E3%83%A3%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E8%AA%BF%E6%95%B4%E3%81%99%E3%82%8B%EF%BC%88%E4%BD%99%E7%99%BD%E3%82%92%E4%BD%9C%E3%82%8B%EF%BC%89">テクスチャサイズを調整する(余白を作る)</a></h1> <p>上記の画像を見ると、カメラのサイズに対してマップのサイズが小さく見えます。<br /> これは背景(透明黒色の部分)が十分な大きさで作成されていないからです。<br /> なので、テクスチャのサイズを少し大きくします。</p> <p><strong>1.MinMap.csを以下のように変更します。</strong></p> <pre><code>public class MiniMap : MonoBehaviour { [SerializeField] Transform _grid; [SerializeField] Image _image; // マップの色 [SerializeField] Color _wallColor; [SerializeField] Color _groundColor; [SerializeField] Color _noneColor; // 余白 [SerializeField] int _space = 20; // マップ用テクスチャ Texture2D _texture; void Start() { Tilemap groundTilemap = _grid.Find("Ground").GetComponent<Tilemap>(); Tilemap wallTilemap = _grid.Find("Wall").GetComponent<Tilemap>(); // テクスチャ作成 Vector3Int size = new Vector3Int(wallTilemap.size.x + _space * 2, wallTilemap.size.y + _space * 2, wallTilemap.size.z); _texture = new Texture2D(size.x, size.y, TextureFormat.ARGB32, false); // こうしないと、画像がぼやける _texture.filterMode = FilterMode.Point; Vector3Int origin = new Vector3Int(wallTilemap.origin.x - _space, wallTilemap.origin.y - _space, wallTilemap.origin.z); ...... </code></pre> <p><strong>2.「MinMap」の「Space」をちょうどいい値に調整</strong><br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738fbb26606.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738fbb26606.png?mw=700" alt="image.png" /></a></p> <p>サイズがちょうどいい感じになりました。<br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c739030da383.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c739030da383.png?mw=700" alt="image.png" /></a></p> <h1 id="マップ背景を透明にする"><a href="#%E3%83%9E%E3%83%83%E3%83%97%E8%83%8C%E6%99%AF%E3%82%92%E9%80%8F%E6%98%8E%E3%81%AB%E3%81%99%E3%82%8B">マップ背景を透明にする</a></h1> <p>今はマップの後ろが不透明なので、例えばここに敵がいた場合見つけることができず、プレイヤーにストレスを与えることになります。<br /> なのでマップの後ろを見えるようにします。</p> <p><strong>1.「MiniMap」の「None Color」をちょうどいい透明度にする</strong><br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c73732ee5289.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c73732ee5289.png?mw=700" alt="image.png" /></a></p> <p><strong>2.「MiniMap>Camera」の「Clear Flags」を「Depth Only」に変更する</strong><br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c73730b33b20.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c73730b33b20.png?mw=700" alt="image.png" /></a></p> <p>そうすると、こんな感じになります。<br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c7373b11c51a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c7373b11c51a.png?mw=700" alt="image.png" /></a></p> <h1 id="マップをプレイヤーに追従させる"><a href="#%E3%83%9E%E3%83%83%E3%83%97%E3%82%92%E3%83%97%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%AB%E8%BF%BD%E5%BE%93%E3%81%95%E3%81%9B%E3%82%8B">マップをプレイヤーに追従させる</a></h1> <p>現在はプレイヤーが動いてもマップは止まったままです。<br /> <a href="https://crieit.now.sh/upload_images/2e4682c2d53316c16fb671d688230ec05c7375e3eee77.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2e4682c2d53316c16fb671d688230ec05c7375e3eee77.gif?mw=700" alt="blog.gif" /></a><br /> これを、プレイヤーに合わせて描画内容が変わるようにします。</p> <p><strong>1.Standard Assetsをインポートする</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://assetstore.unity.com/packages/essentials/asset-packs/standard-assets-32351">Standard Assets</a></p> <p><strong>2.「MiniMap>Camera」に「FollowTarget」を追加し、設定を以下のように変更</strong></p> <ul> <li>「Target」にプレイヤーオブジェクトを設定</li> <li>「Offset」をCameraの座標と同じにする<br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c7379b4b7b04.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c7379b4b7b04.png?mw=700" alt="image.png" /></a></li> </ul> <p>こんな感じになりました。<br /> <a href="https://crieit.now.sh/upload_images/2e4682c2d53316c16fb671d688230ec05c739175ccf1f.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2e4682c2d53316c16fb671d688230ec05c739175ccf1f.gif?mw=700" alt="blog.gif" /></a></p> <h1 id="マップにプレイヤーを表示する"><a href="#%E3%83%9E%E3%83%83%E3%83%97%E3%81%AB%E3%83%97%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B">マップにプレイヤーを表示する</a></h1> <p><strong>1.プレイヤーオブジェクトの下に、マップ上で表示するSprite Rendererを作成し、設定を以下のように変更</strong></p> <ul> <li>「Layer」を「MiniMap」に変更</li> <li>「Order in Layer」を「MiniMap>Canvas」よりも上にする</li> </ul> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c73a8b1b223b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c73a8b1b223b.png?mw=700" alt="image.png" /></a></p> <p>表示されました。<br /> <a href="https://crieit.now.sh/upload_images/2e4682c2d53316c16fb671d688230ec05c73a99c6d1d5.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2e4682c2d53316c16fb671d688230ec05c73a99c6d1d5.gif?mw=700" alt="blog.gif" /></a></p> <h1 id="壁を細くする"><a href="#%E5%A3%81%E3%82%92%E7%B4%B0%E3%81%8F%E3%81%99%E3%82%8B">壁を細くする</a></h1> <p>今回のステージの壁は、3Grid使って表示しています。<br /> ただこれをそのままマップ上で表示すると壁が太く見えるので、地面タイルに隣接している壁のみ表示するように変更します。<br /> 隣接しているか確認するのは、上下左右斜めの8方向です。</p> <p><strong>1.「MiniMap.cs」を以下のように変更</strong></p> <pre><code>...... // 地面タイルが存在するか確認する方向 Vector3Int[] directions = new Vector3Int[] { new Vector3Int(-1, 0, 0), new Vector3Int(1, 0, 0), new Vector3Int(0, -1, 0), new Vector3Int(0, 1, 0), new Vector3Int(-1, -1, 0), new Vector3Int(1, -1, 0), new Vector3Int(1, 1, 0), new Vector3Int(-1, 1, 0), }; // テクスチャ座標ごとの色を求める for (int y = 0; y < size.y; ++y) { for (int x = 0; x < size.x; ++x) { // Tilemapのグリッド座標 Vector3Int cellPos = new Vector3Int(origin.x + x, origin.y + y, 0); // マップカラー Color color = _noneColor; // 壁タイルが存在する if (wallTilemap.GetTile(cellPos) != null) { // 8方向のどこかに地面タイルが存在するかチェック for (int i = 0; i < directions.Length; ++i) { if (groundTilemap.GetTile(cellPos + directions[i]) != null) { color = _wallColor; break; } } } // 地面タイルが存在する else if (groundTilemap.GetTile(cellPos) != null) { color = _groundColor; } _texture.SetPixel(x, y, color); } } ...... </code></pre> <p>壁が細くなりました。<br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c73ad143dbda.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c73ad143dbda.png?mw=700" alt="image.png" /></a></p> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>一応、最終的なMiniMap.csを書いておきます。<br /> 次は、一度確認した箇所だけ表示されるマップや、フロア単位で表示されるマップを作ろうと思います。</p> <pre><code>using UnityEngine; using UnityEngine.Tilemaps; using UnityEngine.UI; public class MiniMap : MonoBehaviour { [SerializeField] Transform _grid; [SerializeField] Image _image; // マップの色 [SerializeField] Color _wallColor; [SerializeField] Color _groundColor; [SerializeField] Color _noneColor; // 余白 [SerializeField] int _space = 20; // マップ用テクスチャ Texture2D _texture; void Start() { Tilemap groundTilemap = _grid.Find("Ground").GetComponent<Tilemap>(); Tilemap wallTilemap = _grid.Find("Wall").GetComponent<Tilemap>(); // テクスチャ作成 int width = wallTilemap.size.x + _space * 2; int height = wallTilemap.size.y + _space * 2; Vector3Int size = new Vector3Int(width, height, wallTilemap.size.z); _texture = new Texture2D(size.x, size.y, TextureFormat.ARGB32, false); // こうしないと、画像がぼやける _texture.filterMode = FilterMode.Point; Vector3Int origin = new Vector3Int(wallTilemap.origin.x - _space, wallTilemap.origin.y - _space, wallTilemap.origin.z); // 地面タイルが存在するか確認する方向 Vector3Int[] directions = new Vector3Int[] { new Vector3Int(-1, 0, 0), new Vector3Int(1, 0, 0), new Vector3Int(0, -1, 0), new Vector3Int(0, 1, 0), new Vector3Int(-1, -1, 0), new Vector3Int(1, -1, 0), new Vector3Int(1, 1, 0), new Vector3Int(-1, 1, 0), }; // テクスチャ座標ごとの色を求める for (int y = 0; y < size.y; ++y) { for (int x = 0; x < size.x; ++x) { // Tilemapのグリッド座標 Vector3Int cellPos = new Vector3Int(origin.x + x, origin.y + y, 0); // マップカラー Color color = _noneColor; // 壁タイルが存在する if (wallTilemap.GetTile(cellPos) != null) { // 8方向のどこかに地面タイルが存在するかチェック for (int i = 0; i < directions.Length; ++i) { if (groundTilemap.GetTile(cellPos + directions[i]) != null) { color = _wallColor; break; } } } // 地面タイルが存在する else if (groundTilemap.GetTile(cellPos) != null) { color = _groundColor; } _texture.SetPixel(x, y, color); } } // テクスチャ確定 _texture.Apply(); // テクスチャをImageに適用 _image.rectTransform.sizeDelta = new Vector2(size.x, size.y); _image.sprite = Sprite.Create(_texture, new Rect(0, 0, size.x, size.y), Vector2.zero); // _imageをGridの中心に移動 Vector2 leftDownWorldPos = wallTilemap.CellToWorld(origin); Vector2 rightUpWorldPos = wallTilemap.CellToWorld(origin + size); _image.transform.position = (leftDownWorldPos + rightUpWorldPos) * 0.5f; } private void OnDestroy() { Destroy(_texture); } } </code></pre> 長野別荘 tag:crieit.net,2005:PublicArticle/14837 2019-02-25T18:04:37+09:00 2019-03-02T19:01:16+09:00 https://crieit.net/posts/Tilemap-5c73afa5127c1 Tilemapからミニマップを作成してみた① <p>Unityのバージョン:2018.3.5f1</p> <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>ダンジョン探索型のゲームって、だいたい端っこにダンジョンのミニマップがついてますよね。自分の位置とか一度発見した宝箱の位置とかがわかるやつ。<br /> 今回はUnity2017で実装されたTilemapを用いて作成したステージに対応した、ミニマップの作成法を考えてみました。</p> <p>ただ、AssetStoreには既に高品質のミニマップ機能がいくつもあるので、無理に自作する必要はないと思います。<br /> 自分はAssetStoreのツールがTileMapに対応しているのかチェックするのが面倒だった(英語から逃げた)のと、ちょっと調べただけでも参考になる記事がいくつもあったから試してみただけです。</p> <p>ちなみにできあがったものがこちらになります。<br /> <a href="https://crieit.now.sh/upload_images/2e4682c2d53316c16fb671d688230ec05c73adf8cc3b3.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2e4682c2d53316c16fb671d688230ec05c73adf8cc3b3.gif?mw=700" alt="blog.gif" /></a></p> <h1 id="ステージ作成"><a href="#%E3%82%B9%E3%83%86%E3%83%BC%E3%82%B8%E4%BD%9C%E6%88%90">ステージ作成</a></h1> <p>ステージはこんな感じでTilemapで作成しています。<br /> <a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6d85e04b0bc.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6d85e04b0bc.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <p>Gridオブジェクトの下に</p> <h6 id="Ground(通路)"><a href="#Ground%EF%BC%88%E9%80%9A%E8%B7%AF%EF%BC%89">Ground(通路)</a></h6> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6d869da0492.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6d869da0492.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <h6 id="Wall(周りの壁)"><a href="#Wall%EF%BC%88%E5%91%A8%E3%82%8A%E3%81%AE%E5%A3%81%29">Wall(周りの壁)</a></h6> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6d871639522.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6d871639522.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <h6 id="Item(オブジェクト)"><a href="#Item%EF%BC%88%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%29">Item(オブジェクト)</a></h6> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6d9946e0bf6.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6d9946e0bf6.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <p>の3つのTileMapがあります。<br /> Colliderが設定されているのは「Wall」と「Item」です。</p> <h1 id="マップ画面表示"><a href="#%E3%83%9E%E3%83%83%E3%83%97%E7%94%BB%E9%9D%A2%E8%A1%A8%E7%A4%BA">マップ画面表示</a></h1> <p>まずは右下にマップ用の画面を表示します。</p> <p><strong>1.空のオブジェクト「MiniMap」を作成し、その下に新しくCameraを作成</strong></p> <p><strong>2.Cameraの設定を以下のように変更</strong></p> <ul> <li>「Position」のz座標を「0以下」にする</li> <li>「Projection」を「Orthographic」にする</li> <li>「Viewport Rect」を調整して好きな位置・大きさにウインドウを変更</li> <li>「Size」を調整して写す範囲を変更</li> <li>「Audio Listener」コンポーネントを削除</li> </ul> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c7241262e134.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c7241262e134.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <p>右下にマップができました。<br /> <a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c7241ca32923.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c7241ca32923.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <h1 id="マップ用テクスチャ作成・表示"><a href="#%E3%83%9E%E3%83%83%E3%83%97%E7%94%A8%E3%83%86%E3%82%AF%E3%82%B9%E3%83%81%E3%83%A3%E4%BD%9C%E6%88%90%E3%83%BB%E8%A1%A8%E7%A4%BA">マップ用テクスチャ作成・表示</a></h1> <p>今はマップカメラに映っている内容が、Main Cameraに映っている内容と同じです。<br /> これを壁と地面が単色で塗られた画像に変えていきます。<br /> まずは、Tilemapの情報からマップ用のテクスチャを作成します。</p> <p><strong>1.MiniMap.csを作成し、MiniMapオブジェクトにコンポーネントを追加する</strong><br /> <strong>2.MiniMap.csに以下を記述</strong></p> <pre><code>using UnityEngine; using UnityEngine.Tilemaps; using UnityEngine.UI; public class MiniMap : MonoBehaviour { [SerializeField] Transform _grid; [SerializeField] Image _image; // マップの色 [SerializeField] Color _wallColor; [SerializeField] Color _groundColor; [SerializeField] Color _noneColor; // マップ用テクスチャ Texture2D _texture; void Start() { Tilemap groundTilemap = _grid.Find("Ground").GetComponent<Tilemap>(); Tilemap wallTilemap = _grid.Find("Wall").GetComponent<Tilemap>(); // テクスチャ作成 Vector3Int size = wallTilemap.size; _texture = new Texture2D(size.x, size.y, TextureFormat.ARGB32, false); // こうしないと、画像がぼやける _texture.filterMode = FilterMode.Point; Vector3Int origin = wallTilemap.origin; // テクスチャ座標ごとの色を求める for (int y = 0; y < size.y; ++y) { for (int x = 0; x < size.x; ++x) { // Tilemapのグリッド座標 Vector3Int cellPos = new Vector3Int(origin.x + x, origin.y + y, 0); // 壁タイルが存在する if (wallTilemap.GetTile(cellPos) != null) { _texture.SetPixel(x, y, _wallColor); } // 地面タイルが存在する else if (groundTilemap.GetTile(cellPos) != null) { _texture.SetPixel(x, y, _groundColor); } // なにもない場所 else { _texture.SetPixel(x, y, _noneColor); } } } // テクスチャ確定 _texture.Apply(); // テクスチャをImageに適用 _image.rectTransform.sizeDelta = new Vector2(size.x, size.y); _image.sprite = Sprite.Create(_texture, new Rect(0, 0, size.x, size.y), Vector2.zero); // _imageをGridの中心に移動 Vector2 leftDownWorldPos = wallTilemap.CellToWorld(origin); Vector2 rightUpWorldPos = wallTilemap.CellToWorld(origin + size); _image.transform.position = (leftDownWorldPos + rightUpWorldPos) * 0.5f; } private void OnDestroy() { Destroy(_texture); } } </code></pre> <p><strong>3.MiniMapオブジェクトの下に「Canvas」を、さらに下に「Image」を作成する</strong></p> <ul> <li>「Canvas」の「Order in Layer」をTilemapのTilemap Rendererより大きくしておく</li> </ul> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c72a3bd077e6.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c72a3bd077e6.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <p><strong>4.MiniMapオブジェクトのMiniMapの設定を以下のように変更</strong></p> <ul> <li>「Grid」に「Grid」オブジェクトを設定</li> <li>「Image」に「MiniMap>Canvas>Image」オブジェクトを設定</li> <li>「Wall Color」にミニマップに表示される壁の色を設定</li> <li>「Ground Color」にミニマップに表示される地面の色を設定</li> <li>「None Color」にミニマップに表示される壁の外側の色を設定</li> </ul> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c72a40b7d4f1.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c72a40b7d4f1.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <p>実行すると、こんな感じになります。<br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738a7328d0e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738a7328d0e.png?mw=700" alt="image.png" /></a></p> <hr /> <hr /> <p><strong>MiniMap.csでやっていることは</strong></p> <ol> <li>「Wall」のTilemapと同じサイズのTextureを作成する</li> </ol> <pre><code> Tilemap groundTilemap = _grid.Find("Ground").GetComponent<Tilemap>(); Tilemap wallTilemap = _grid.Find("Wall").GetComponent<Tilemap>(); // テクスチャ作成 Vector3Int size = wallTilemap.size; _texture = new Texture2D(size.x, size.y, TextureFormat.ARGB32, false); // こうしないと、画像がぼやける _texture.filterMode = FilterMode.Point; </code></pre> <p>FilterModeをPointにしないとこんな感じになります。</p> <p><a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738af6a489e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738af6a489e.png?mw=700" alt="image.png" /></a></p> <p>3.Tilemapのタイルの有無から、テクスチャ座標の色を決定する</p> <pre><code> Vector3Int origin = wallTilemap.origin; // テクスチャ座標ごとの色を求める for (int y = 0; y < size.y; ++y) { for (int x = 0; x < size.x; ++x) { // Tilemapのグリッド座標 Vector3Int cellPos = new Vector3Int(origin.x + x, origin.y + y, 0); // 壁タイルが存在する if (wallTilemap.GetTile(cellPos) != null) { _texture.SetPixel(x, y, _wallColor); } // 地面タイルが存在する else if (groundTilemap.GetTile(cellPos) != null) { _texture.SetPixel(x, y, _groundColor); } // なにもない場所 else { _texture.SetPixel(x, y, _noneColor); } } } </code></pre> <p>4.ImageをTextureと同じサイズに変更し、SpriteにTextureを適用する。その後、Imageの中心をステージの中心に合わせる</p> <pre><code> // テクスチャ確定 _texture.Apply(); // テクスチャをImageに適用 _image.rectTransform.sizeDelta = new Vector2(size.x, size.y); _image.sprite = Sprite.Create(_texture, new Rect(0, 0, size.x, size.y), Vector2.zero); // _imageをGridの中心に移動 Vector2 leftDownWorldPos = wallTilemap.CellToWorld(origin); Vector2 rightUpWorldPos = wallTilemap.CellToWorld(origin + size); _image.transform.position = (leftDownWorldPos + rightUpWorldPos) * 0.5f; </code></pre> <h1 id="マップカメラに、マップ画像のみ表示する"><a href="#%E3%83%9E%E3%83%83%E3%83%97%E3%82%AB%E3%83%A1%E3%83%A9%E3%81%AB%E3%80%81%E3%83%9E%E3%83%83%E3%83%97%E7%94%BB%E5%83%8F%E3%81%AE%E3%81%BF%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B">マップカメラに、マップ画像のみ表示する</a></h1> <p>このままだと肝心のステージの画像が隠されてしまうので、マップカメラにはマップ画像だけを表示し、逆にMain Cameraではマップ画像を非表示にします。</p> <p><strong>1. Edit>Project Settings>Tags and Layers でLayersのウインドウを開き、「MiniMap」を追加する</strong><br /> <a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c72b01a82b0a.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c72b01a82b0a.PNG?mw=700" alt="キャプチャ.PNG" /></a></p> <p><strong>2.「MiniMap>Canvas」のLayerを「MiniMap」に変更する</strong><br /> <a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c72b3164a262.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c72b3164a262.PNG?mw=700" alt="キャプチャ.PNG" /></a><br /> <strong>3.「MiniMap>Camera」の「Culling Mask」を「MiniMap」に変更する</strong><br /> <a href="https://crieit.now.sh/upload_images/7f2a1b77138882ff74a4d710aea207da5c72b0ffd8252.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7f2a1b77138882ff74a4d710aea207da5c72b0ffd8252.png?mw=700" alt="無題.png" /></a><br /> <strong>4.「Main Camera」の「Culling Mask」を「MiniMap以外」を選択状態に変更する</strong><br /> <a href="https://crieit.now.sh/upload_images/7f2a1b77138882ff74a4d710aea207da5c72b2943966f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7f2a1b77138882ff74a4d710aea207da5c72b2943966f.png?mw=700" alt="無題.png" /></a></p> <p>この状態で実行するとこうなります。<br /> <a href="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738d491e8a0.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/647903421c08d6a44f7ccbac22fa43445c738d491e8a0.png?mw=700" alt="image.png" /></a></p> <p>だいぶそれっぽい感じになってきました。<br /> 疲れたので、残りはまた今度書きます。</p> 長野別荘 tag:crieit.net,2005:PublicArticle/14810 2019-02-18T19:43:14+09:00 2019-02-22T07:57:08+09:00 https://crieit.net/posts/TextMeshProGUI TextMeshProGUIを動かした時、どの方法が一番負荷を下げられるのか試してみた <p>Unityのバージョン:2018.3.5f1</p> <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>アクションやRPGゲームでよく、敵からダメージを受けたときに頭上からダメージ数字がポーンと飛び出る演出がありますよね。<br /> あれを作ろうとしたんですけど、どう作るか悩んで手が止まりました。<br /> というのも、最近こういう記事を見たからです。</p> <p><a target="_blank" rel="nofollow noopener" href="http://kan-kikuchi.hatenablog.com/entry/uGUI_Change_Cost">uGUI(Image)とSpriteRendererの静止時と動作時の負荷比較【Unity】【uGUI】【最適化】</a></p> <p>uGUIを1つでも動かすと負荷が上昇するらしいです。</p> <p>では、Canvas上の全てのTextが動く場合は、1つにまとめたほうがいいのか個別にしたほうがいいのか。<br /> 個別に動かす場合、Textを動かす場合とCanvasを動かす場合で負荷は変わるのか。<br /> 分からないので実際動かしてみました。</p> <h1 id="比較内容"><a href="#%E6%AF%94%E8%BC%83%E5%86%85%E5%AE%B9">比較内容</a></h1> <p>今回、以下の4つのパターンをそれぞれ100個、500個、1000個同時に表示してアニメーションさせた場合の平均FPSを比較しました。</p> <hr /> <h4><strong>①1つのCanvasに全てのTextを表示+Textをアニメーションさせる</strong></h4> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c63080044b68.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c63080044b68.PNG?mw=700" alt="comp1.PNG" /></a><br /> Scene上の「WorldCanvas」の下に「Text1」オブジェクトを生成します。<br /> Animatorで「Text1」をアニメーションさせます。</p> <h4><strong>②1つのCanvasに1つのTextを表示+Textをアニメーションさせる</strong></h4> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6308571f8a4.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c6308571f8a4.PNG?mw=700" alt="comp2.PNG" /></a><br /> Scene上に「Text2」オブジェクトを生成します。<br /> Animatorで「Text2/Canvas/Text」をアニメーションさせます。</p> <h4><strong>③1つのCanvasに1つのTextを表示+Canvasをアニメーションさせる</strong></h4> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c630949e4e21.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c630949e4e21.PNG?mw=700" alt="comp3.PNG" /></a><br /> Scene上に「Text3」オブジェクトを生成します。<br /> Animatorで「Text3/Canvas」をアニメーションさせます。</p> <h4><strong>④1つのCanvasに1つのTextを表示+親オブジェクトをアニメーションさせる</strong></h4> <p><a href="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c630a2c96e56.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7dc02e2912691e940f847326c911d8715c630a2c96e56.PNG?mw=700" alt="comp4.PNG" /></a><br /> Scene上に「Text4」オブジェクトを生成します。<br /> Animatorで「Text4」をアニメーションさせます。</p> <hr /> <p><strong>また、共通条件は以下の通りです。</strong></p> <ul> <li>アニメーションはAnimatorで実装(最初はDoTweenでアニメーションさせようとしたのですが、同時に動かせる数に制限があったのでAnimatorに変更)</li> <li>実行から5秒後に計測開始</li> <li>10秒間計測したときの平均FPSを計算</li> </ul> <h1 id="比較結果"><a href="#%E6%AF%94%E8%BC%83%E7%B5%90%E6%9E%9C">比較結果</a></h1> <div class="table-responsive"><table> <thead> <tr> <th></th> <th>Canvas1つ+Text移動</th> <th>Canvas個別+Text移動</th> <th>Canvas個別+Canvas移動</th> <th>Canvas個別+オブジェクト移動</th> </tr> </thead> <tbody> <tr> <td><strong>100</strong></td> <td>60.4</td> <td>60.4</td> <td>60.3</td> <td>60.4</td> </tr> <tr> <td><strong>500</strong></td> <td>60.4</td> <td>43.3</td> <td>59.1</td> <td>59.4</td> </tr> <tr> <td><strong>1000</strong></td> <td>49.7</td> <td>32.7</td> <td>30.3</td> <td>32.5</td> </tr> </tbody> </table></div> <p>結果を見る限り、<strong>1つのCanvasに全てのTextを表示する</strong>のが1番早かったです。<br /> ただ、500個動かしたときと1000個動かしたときの結果について、2番以下の結果が異なっているのが少し気になります。</p> <p>とりあえず、最初に参照した記事と合わせると、動くものは動くもの、動かないものは動かないもので1つにまとめるのが良いということでしょうかね。</p> 長野別荘