YAMADA TAISHI’s diary

ゲームについてとか私の日記とか。このブログのあらゆるコードは好きにどうぞ。利用規約があるものは記事内のGitHubのRepositoryのリンクで貼られていると思うので、そちらを参照ください。

2020年の振り返りと2021年の抱負(ただのポエム)

あけましておめでとうございます! 今年もよろしくお願いします。2021年がやってきました!
と、言ってももうすでに1月後半です。
こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です!
2020年も大変な年でした。何をしたのか振り返り、今年は何をしていくのか考えていこうと思います。

去年の記事 orotiyamatano.hatenablog.com

目次


振り返り

祖父が亡くなった


正月早々父方の祖父がなくなりました。
もう身内が亡くなったのは4年連続です。
もうね疲れた。

社内勉強会主催


今年は社外の勉強会に参加することは少なかったのですが、社内の勉強会を主催することが多かったです。
一昨年末ぐらいから社内勉強会の運営チームを発足し、コロナで途切れながらも1年社内勉強会を続けることが出来ました。
偉い私。

orotiyamatano.hatenablog.com

Game A Week


Game A Weekというものに挑戦していました。
全然出来なかったけど、学びは得た。

orotiyamatano.hatenablog.com

筋トレ


筋トレしすぎると風邪をひくと一昨年は学びを得たと思っていたんですが、正確には筋トレをして栄養をしっかり取らないと風邪をひくだったのだという学びを得ました。
どうやら、免疫力向上や筋肉の分解抑制に働く、遊離アミノ酸グルタミンを体内で大量消費してしまい、風邪をひきやすくなってしまうというのが原因っぽいです。
グルタミンパウダーを摂るようになってから今年は1回も風邪をひいてません。(まぁそもそも外に出てないというのもあるけど)

cp.glico.jp

とにかく筋トレを断続的に続けています。
コロナ渦になってから運動不足がやばいので、もっと運動していきたいですね。

コロナ


3月になったらめっちゃ、やばくなってきたコロナ。
会社でも4月頃ぐらいだったか、関連会社でコロナ感染者が出て急遽リモートワークに移行。
その後、緊急事態宣言。
それから半年以上出社することなく自宅で仕事をするライフスタイルに変わりました。
通勤しなくても良いのはめっちゃいいけど、運動不足とか気分転換とか集中が出来なかったりとか悪いところもあって一長一短。
個人的にはリモートワークは続けたい。

自分の休暇がほぼ無かった


単純に忙しかった。
有給消化しなさすぎて、働き方改革関連で怒られるから有給をとれ、と現在進行系で怒られてる。

コンシューマー開発


一昨年からコンシューマーの開発やってたやつが、ついに発売されたんです。
やったね。

それはそれとして、詳しくは言えないですが、新規案件にも入りまして絶賛コンシューマー開発中です。
どちゃくそ頑張ってます。

ADHDって診断された


個人的に今年の中で一番でかい出来事。
リモートワークになって、仕事に集中できなくてボロが出てきてとても辛くなったので、病院に言って診てもらったら、ADHDだと診断されました。
現在薬を飲んで対応中。私のやらかしのいろいろなことがADHDのせいだと分かって色々納得感がやばい。
時間間隔がずれるのもADHD(そのせいでリズムゲームが苦手で、スケジュールの見積もり精度も低いし、時間にも間に合わない)だし、
方向感覚が無いのもADHD、物忘れが酷いのもADHD、時々過集中なのもADHDだし逆に集中出来ないのもADHD。マジかって感じ。
薬を飲んでるお陰で毎日集中できてる。ありがたい。

orotiyamatano.hatenablog.com

生ハムの原木を食べた


美味しかった。

彼女を作りたくて活動


コロナで途中で活動辞めたけど、心が折れただけで終わった。
多くは語るまい……。

シェーダー勉強


シェーダーの基礎が分かったので良かった。

仮想通貨


仮想通貨に手を出してみました。
コロナで仮想通貨が暴落してたので、買ってみました。
下がってるときが買い時と思ったんです。

多少赤字を出しつつも基本的にはプラスです。
やっぱり、下がってる時に買うと儲けられるみたいですね。

振り返りまとめ


2020年はコロナで色々皆さん大変でしたね。私は引きこもりにストレスを感じないタイプなので大丈夫でしたが、気晴らしに外に出たい時もありました。
ずっと家にいると日々が一瞬で過ぎ去ってしまい何もしていないような気がしてきます。何もしてないわけじゃないんですけどね。
ゲームもやれてない割に色々出来てないのでちょっと、もどかしいですネ。でも仕事のほうは、めっちゃがんばれてるので良かったです。
(一昨年と違ってUEも触れてるし)
今年はどうなるかわからないですけど、今の仕事がいい感じに進めば良いなぁと思ってます。

2020年目標達成度


去年の目標に対してどのぐらい達成できたでしょうか。

去年の目標は
・社内勉強会を盛り上げたい
・Game A Weekをしてみたい
・体力をつけたい
でした。

社内勉強会を盛り上げたい


盛り上げることが出来たかは分かりませんが、続けることができてるのでまぁ、達成と言ってもいいでしょう。
今年も頑張っていきたい。
去年どんな風に頑張ったかは別途また記事を書こうと思います。

Game A Weekをしてみたい


少ししかできず、完成したものもほぼ無かったのですが、挑戦自体は出来たので、学びは得ることが出来たと思います。
何か工夫して創作活動をもっとしたいですね。

体力をつけたい


こちらは運動不足もあって微妙ですが、風邪をひくことは無かったので悪くは無かったのではないでしょうか。
もう少し筋トレをして今年は腹筋を割りたいです。

2021年の目標


ちょっと、どうしようか迷いましたが、「何かしらゲームをリリースする」のを目標にしたいと思います。
色々制作したいと思っていたのですが数年放置している気がするので、いい加減何かしら形にしてリリースがしたいです。
ゲームプログラマー歴も4年目に差し掛かってるのでそろそろ本気出したい。
という訳で2021年疲れない程度に頑張っていこうと思います!

まとめ


今年は何が出来るのか分からないですが、色々やっていきたいと思っています。
ADHDの薬を飲むようになってから1日でやれることが少しだけ増えた気がします。
(前まではずっと眠かったのですが、最近はずっと眠いということが少なくなった気がします!)
ちゃんとゲーム作っていきたい。そして、〇〇のゲームの人だって言ってもらえるようになりたいです。
(何回も同じこと言ってるな)
ま、やっていき。

初心者による初心者のためのシェーダー芸解説

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) / Twitter )です。

本記事はUnityゲーム開発者ギルドアドベントカレンダー2の19日目の記事です。

adventar.org

今回は初心者による初心者のためのシェーダー芸解説です。

目次


どうしてシェーダー芸?


Unityゲーム開発者ギルドアドベントカレンダーですが、今回はUnityではなく。シェーダー芸にについて書こうと思います。

Unityじゃなく?なんでシェーダー芸なのか?
Unityゲーム開発者ギルドで、10月末ぐらいからシェーダー芸の勉強を初めたからです!

では、なぜ勉強を初めたのか?

なんとなく!
楽しそう
だったから!

というのもありますが。
主に3つの理由があります。

メイン目的: シェーダー をツイッターでつぶやいて見栄えを良くしたい(ゲーム画面のツイートと比べてGLSLだけで済むので楽そうだと思った)
サブ目的:ゲームの表現の幅を広げたい。マテリアル周りに詳しくなりたい、レンダリングまわり詳しくなりたい
サブ目的:VJみたいなの憧れる

という理由です。
最近会社のSlackワークスペースに #shader勉強部屋 というのを設立して
シェーダー芸を勉強中

11月から
約1ヶ月で12のシェーダーを作成しました
※最近は1週間に1つくらいのペース

シェーダー芸とは


そもそもシェーダー芸とは?
簡単に言うとシェーダーだけでリアルタイムの演出動画や作品をつくること!

フラグメントシェーダー、バーテックスシェーダーなど種別は問わないですが
基本的にシェーダー芸というとGLSLのフラグメントシェーダーを表すことが多い気がします。

今回私が作ったのもGLSLのフラグメントシェーダーです。

いろんな方の作品は以下サイトで見ることが出来ます。
GLSL SandBox
glslsandbox.com

shadertoy
www.shadertoy.com

もしくはTwitterで #shader #つぶやきGLSLなどを検索すると出ます。

何か役に立つのか?
ぶっちゃけ、表現力は上がるが、ゲーム制作にあんまり意味は無い!
楽しい!

役に立たなくてもいい!
なぜならシェーダー「芸」だから
元々「芸」って役立つものじゃないしね!

シェーダー基礎解説

※私が今回紹介するコードは Twigl で使えるコードです

次がコード
(因みにレギュレーションはClassic)

コード

precision highp float;
uniform vec2 resolution;

float circle(vec2 p, float r) {
    return length(p) - r;
}
void  main(){
    vec2 st =(gl_FragCoord.xy*2.-resolution)/min(resolution.x,resolution.y);
    float pct = circle(st,0.5);
    float d = step(pct,0.2);
    vec3 color=vec3(d);
    gl_FragColor=vec4(color,1.0);
}

普通のプログラムと全然違いますね!

何をやってるのか?というと

の表示です。

座標
をもとに模様を描いている

どういう感じで処理されるのか?
フラグメントシェーダーは、同時にすべてのピクセルが処理されます。

CPUは同時に処理するのに向いてないのでGPUを使って処理されます。

小ネタ:
最近PCを自作したのですが、CPU、メモリ、電源、マザボだけ指して、
動作確認をしたらグラフィック処理がされず画面が表示されませんでした。
なんでかな~?と思っていましたが、それもそのはずでIntelのCPUならオンボード(グラフィック機能あり)で処理されますが
RyzenのCPUはオンボードではないCPUもあり、別途グラフィックボードを購入する必要があったりします。
そのためVGA(グラフィック表示システム)がエラーを引き起こし表示出来たかったのですよね。

組み込み変数・定義の説明

GLSLには組み込み変数や定義があったりします。
今回はその一部を紹介しておこうと思います。
ついでに通常に変数についても少し触れておきます。

precision highp float; //floatの精度指定です
uniform vec2 resolution; //スクリーンのサイズの取得(変数名は別で定義可能)
gl_FragCoord→ピクセルの位置を表します
gl_FragColor→最終的なピクセルカラーです
(gl_FragColorは新しいGLSLではもう非推奨らしいけど,twgl準拠)
他にもマウスの位置を取得とか、時間の取得とか、前フレームのテクスチャの情報を保存しておくとがあります

プログラマーなら分かると思うけど……
vec2→ x,y が格納できる変数定義
vec3 → x,y,z が格納できる変数定義
vec4→ x,y,z,wが格納できる変数定義

などがあります。今回はとりあえず
これらがわかっていれば大丈夫です。

とはいえ同時にピクセル処理されると言われても
ピンと来ない方が多いと思います、

(理解しやすいように)
時間に応じて
塗りつぶしをしてみるプログラムを書いてみようと思います。

precision highp float;
uniform float time; //1secで取得 floatで時間を取得する
void main(){
    float d = 0.;
    if(gl_FragCoord.x>time*60.){
        d = 1.;
    }

    vec3 color=vec3(d);
    gl_FragColor = vec4(color,1.0);
}

bit.ly

見ての通りx軸を1秒1分単位で順番に塗り潰していく処理です。
y軸の場合はこんな感じ。 bit.ly

なんとなく
分かってきましたでしょうか?
xは左が0で右端が解像度ごとに変わり、
yは下が0で上が解像度ごとに変わります。

組み込み関数


シェーダーには組み込み関数というものがあります。
ifで処理をすると処理負荷が高いので代わりになるものが用意されています。

今回使う簡単なものを紹介いたします。

step(a, b)→
a>bだったら0.0に、それ以上なら1.0になる

length(x)→
xの長さをfloatで返す

本格的に解説に入る前に

本格的に解説に入る前に↓プログラムの解説を行います。

vec2 st = ( gl_FragCoord.xy * 2. - resolution) / min( resolution.x, resolution.y );

解像度xyを取得しmin関数でで小さい方の数値を取得し割ります。
数値を表示すると画面では以下のような感じになります。

まずlengthの説明

軽くlengthの説明をしましたがもう少し深いlengthの話をしようと思います。
length(x)は
xの長さをfloatで返すとのことでしたが、一番最初のスクリプトを短くしてlengthにだけ焦点をあてて見るとこういった↓スクリプトが出来上がります。

precision highp float;
uniform vec2 resolution;
void  main(){
    vec2 st =(gl_FragCoord.xy*2.-resolution)/min(resolution.x,resolution.y);
    float pct = length(st); //関数内にあったlengthをそのまま利用
    vec3 color=vec3(pct);
    gl_FragColor=vec4(color,1.0);
}

分かりやすいですね。
stの中心位置はxとyが共に0に近いので0、それぞれ中心を離れるほど1に近づきます。

では、最初のスクリプトに近づけて、circle関数を作って-0.5をしてみましょう。

precision highp float;
uniform vec2 resolution;

float circle(vec2 p, float r) {
    return length(p) - r;
}
void  main(){
    vec2 st =(gl_FragCoord.xy*2.-resolution)/min(resolution.x,resolution.y);
    float pct = circle(st,0.5);
    vec3 color=vec3(pct);
    gl_FragColor=vec4(color,1.0);
}

するとこうなりました。
何が変わったのかというと、-0.5された文だけ円の黒い所が広がりました。
ソレだけですね。

今回のlengthはこれだけにのみ使っています。

stepの説明

これでスクリプトの半分は何をしているのか分かりました。
ではstepは何をやっているのでしょうか?

step(a, b)は
a>bだったら0.0に、それ以上なら1.0になる

でした。
つまり……。

precision highp float;
uniform vec2 resolution;

float circle(vec2 p, float r) {
    return length(p) - r;
}
void  main(){
    vec2 st =(gl_FragCoord.xy*2.-resolution)/min(resolution.x,resolution.y);
    float pct = circle(st,0.5);
    float d = step(pct,0.2);    //ココを追加、a>bだったら0.0に、それ以上なら1.0になる
    vec3 color=vec3(d);
    gl_FragColor=vec4(color,1.0);
}

これで元の画像に!

これからシェーダーを勉強する場合

ネットの色んなシェーダーをパクって改変して覚えていくことを
私は推奨します!
覚えたコードは保存しておくと便利!

例えばさっきの
シェーダーを改変する
ならこうです

precision highp float;
uniform vec2 resolution;
uniform float time;
float circle(vec2 p, float r) {
    return length(p) - r;
}
void  main(){
    vec2 st =(gl_FragCoord.xy*2.-resolution)/min(resolution.x,resolution.y);
    float pct = circle(st,0.5);
    float d = step(sin(time*4.0)*0.5+0.7,pct);  //a>bだったら0.0に、それ以上なら1.0になる
    vec3 color=vec3(d);
    gl_FragColor=vec4(color,1.0);
}

↓時間とsinを使うことで円を拡大縮小することが出来ます。 bit.ly

サンプルコードを2つ解説


参考までに私が書いたコードを紹介します。
口頭で説明したほうが早いのですが、ブログなので長文で解説させて頂きます。

precision highp float;
uniform vec2 resolution;
uniform float time;
mat2 rotate2d(float _angle){
    return mat2(cos(_angle),-sin(_angle),
        sin(_angle),cos(_angle));
}
float square(vec2 p) {
    return abs(p.x) + abs(p.y);
}
void main(){
  vec2 r = resolution,p = (2.*gl_FragCoord.xy - r) / min(r.x,r.y);
  float a = sin(time * 5.0) * 0.5 + 0.5;
  vec2 d = mix(vec2(length(p),0.7), vec2(square(p* rotate2d(radians(time*180.))),0.5), a);  //線形補間
  vec3 color = vec3(step(0.5,d.x));
  gl_FragColor = vec4(color,1.0);
}

bit.ly

まず、rotate2d関数は回転を表しています。2次元行列を使ってxとyの値を入れ替えています。
_angleと書いてあるとおり角度によってどのぐらい入れ替えるかが決定します。
今回はradians(time*180.)の値を入れているので1秒間に180度回転するってことですね。
mix(a,b,c)というのがありますね。
こちらはaとbどちらをどのぐらい表示するかをcの値で線形補間で遷移させるものです。
左のlengthは先程もでた円ですので右側が資格になります。
つまりのこるsquare関数は回転を止めていただくと分かりやすいですが、ダイヤ型の正方形を表示させる命令式です。

どうでしょうか?いっけん難しそうに見えますが、パーツに別ければ理解できそうな気がしてきませんか?

因みに回転に使われているSinCosはこんな動きになります。
三角関数で使われるものですが、この程度の話であれば深く考える必要はないと思います。
徐々に慣れていきましょう。

もう一つ解説


もう一つ解説してみようと思います。

precision highp float;
uniform vec2 resolution;
uniform float time;
void main(){
 vec2 r=resolution,p=(gl_FragCoord.xy*2.-r)/min(r.x,r.y);
 float d = length(p);
 d = (10.* d)+time*-10.;
 d = abs(sin(d));
 d = step(0.5,d);
 gl_FragColor = vec4(vec3(d,0.0,0.0),1.0);
}

bit.ly

まず一番簡単な一番最後のvec4(vec3(d,0.0,0.0),1.0)の部分。
こちらですがRGBAとなっており、色を表しています。目がチカチカしますね。
time-10.部分がtimeの速さになります。
10.
dの部分は円の太さです。
ここはlengthから値を取得してきて×10した値の絶対値(abs)をsin関数に入れて-1~1の値で遷移させ、
最終的にstepできっぱりと別れるように色分けしています。
簡単ですね。

と言った感じで色々シェーダーを見ると複雑なシェーダーも簡単なものなら読み解くことが出来ます。

まとめ


今回は2Dシェーダーの解説しかしなかったけれど、
シェーダーは奥が深いです。
現在私は3Dの表示(レイマーチング)の勉強をしているけれど、またまだやることが多いです。

最近はUnityや3Dソフト内でノードベースのシェーダーの表示処理がありますが、
やっていることはプログラムと同じな上、ノードベースであるぶんやれる表現に限りがあります。

色々表現手段を増やすためや処理負荷を下げるためにプログラマはシェーダーの勉強が欠かせません。

また、表現手段が増えなくても自分が書いたコードが動くのは見ていて楽しいです。
皆さんもこれをきっかけにシェーダーの勉強をやってみてはいかがでしょうか?
シェーダー芸は楽しいぞ!
以上!やまだたいしでした。

参考資料


www.iquilezles.org

qiita.com

qiita.com

docs.google.com

その他のシェーダー芸


orotiyamatano.hatenablog.com

TextMeshProの使い方【後編】

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) / Twitter )です。
TextMeshProの使い方後編です!

前編はこちら orotiyamatano.hatenablog.com


目次


設定


早速前回の続きからです。

Atlas & Material についてです。
こちらは生成されたFontアトラスとそのマテリアルの参照を表しています。
変更は出来ません。

Font Weights は
太字と斜体がフォントの外観をどのように変更するかを制御できるものです。
文字のスタイルによって表示するフォントアセットを指定することが出来るようです。
設定すれば、italic(斜体)のときはAのフォント、太字(Bold)の時はBのフォントみたいな感じで使い分けができそう。
また下の方の項目でテキストの太さと間隔を調整することもできるようです。
今回は触らないので詳細は割愛。

Fallback Font Assets は
フォントを複数のアセットに分散し管理できる機能のようです。
今回は常用漢字と英数字とひらがなだけなので8192*8192でギリギリ納まるので問題ないですが、
納まりきらない時に分散出来るらしいです。
また、1つのフォントにすべての文字が含まれていない場合に別のフォントを読み込ませることで代替表示させるために用いるようです。
別のTMP_FontAssetを指定するだけのようです。

Character Table は
その名の通りフォントアセットに含まれる文字をUnicode値で検索するために使用される辞書のようです。
打ち込んだ文字と表示される文字が別(例えば「あ」と打ち込んだのに「い」と表示される)の場合に値を編集してなおすことが出来るようです。
下記のGlyph TableのIDと紐付いているようです。

Glyph Table は
登録した文字とその設定が一覧表示されます。
特定の文字が表示されない、文字が崩れているという場合にココで検索するとどんな状態で文字が保存されているか調べることが出来ると思います。
値が変更することが出来るので文字の位置が合わないといったことがある場合ココを編集すれば良さそうです。

Glyph Adjustment Table は
特定の文字2つを選んで文字間を調整するものっぽい?
その組み合わせの場合のみ文字間が調整されるようです。
上手く動かないという話もあるようなので、あまり機能としては信用しないほうが良いかも?

いざベイク!


各ベイク設定について


では、テクスチャのアトラス化をするためにベイクをやっていこうと思います。
フォントアセットのUpdate Atlas Textureをクリックします。

すると、ウィンドウで開くと思います。
前半で設定した値と違っても反映されると思うので大丈夫だと思いますが、怖い人は合わせて置くと良いでしょう。
因みに私の設定はこんな感じにしました。

Sampling Point Size は大元となるフォントのポイントの大きさですが、コレはベイクする速度を考えてAuto Sizingにしておくことにしました。
Paddingはベイクされるときの文字間です。小さすぎると文字を表示する際に隣の文字が表示されてしまうことがあるのでデフォルトの9のままを適用しました。
Packing Method は ベイクするスピードで遅いほど精度が高くなるのですが、特にこだわりは無いのでFastにしておきます。
Atlas Resolutionはアトラステクスチャのサイズです。今回は常用漢字ひらがな・カタカナ、英数字、ちょっとした記号なのでいっぱい入る8192*8192のサイズにしておきます。
Character Setには焼き込む文字の指定方法を指定します。後で変更するので詳細は割愛。
Select Font Asset は実際に焼き込む設定のアセットです。特に変更することはありません。
Character SequenceはCharacter Setに指定する方法によって入れる設定が変わります。詳細は割愛。
Render Modeはベイクするときの設定です。これは前編で解説しているので割愛します。SDFAAで今回は焼き込みます。
Get Kerning Pairs は カーニング情報をフォントデータからコピーするか?です。フォントによってはそもそも含まれない場合もあるのでfalseでいいです。
あるやつはtrueの方がキレイに表示できると思いますがソコまで気にする必要も無いかと……。

ベイクする文字の指定


ベイクする文字の指定方法はいくつかあります。

  1. ASCII→ASCIIコードでの指定です。ASCIIコード表にある全ての文字がベイクされます。
  2. Extended ASCII→拡張ASCIIコード指定です。拡張ASCIIのすべての文字がベイクされます。
  3. ASCII Lowercase→ ASCII Lowercaseコード指定です。
  4. ASCII Uppercase→ ASCII Uppercaseコード指定です。
  5. Numbers+Symbols→番号記号またはナンバーサインと呼ばれる文字列がベイクされます。
  6. Custom Range→Decimal(10進数)で指定された文字(文字コード)をベイクします。今回これを使います。
  7. Unicode Range(Hex)→ Hex(16進数)Unicodeで指定された文字コードをベイクします。
  8. Custom Characters→指定した文字がベイクされます。(文字をそのまま入力する)
  9. Character from File→文字が入力されたテキストを指定します。そのテキストがベイクされます。(多分)

ASCII系↓ theasciicode.com.ar

Numbers+Symbols↓ coolsymbol.com


今回ベイクするのは常用漢字などです。
↓ここの文字列を使わせていただくことにしました。
gist.github.com

準備が出来たらGenerate Font Atlasをクリックします。

するとベイクが始まります。

始まらない場合、エラーが表示されるので、そのエラーを解決してベイクしましょう。
性能の低いPCでは固まってしまうこともあるので触らずに気長に待ちましょう。

エラーがなければすべてベイクされるはずです。

Saveをクリックして保存しておきましょう。

ここまで来て言うのは何なんですが、画像を見ると included 2434/2635 とすべてベイク出来ていないようです。
いらない文字が入ってしまっていただけだとは思いますが気になる方は
↓こちらのほうを利用してCustom Charactersでベイクし直したほうが良いかも知れません。
gist.github.com


ベイクされたことでアセットのマテリアルとテクスチャが保存されたはずです。
びっしりと白い文字が割り当てられてるか確認してみましょう。

これでもう使えます。
マテリアルとテクスチャの設定が出来るのですが、要望があれば別途記事を書きます。
ココに関しては他の方も記事を書かれているので私が書く必要はないと思いますが……。

TextMeshProの表示


TextMeshProの表示は普通のテキストとほとんど似ています。
解説する必要もほとんど無いと思うのでUIでの表示のみ解説します。

挿入したいところに右クリック> UI>Text -TxteMeshProを選択

生成されたTxteMeshProのゲームオブジェクトのTxteMeshPro - Textコンポーネントを開き、MainSettings> Font Assetを生成したFont Assetに変更します。

すると表示されるはずです。
後はTextMeshProのコンポーネントの値をいじって楽しんでみてください。

縁取りの方法などは他の方の書かれた記事にも記載があるので、そちらの方が分かりやすいと思います。

アセットを更新しない限り、もうフォントは必要ないのでプロジェクトから消してしまっても構わないです。
注意事項としてはGitでこのアセットを管理するときにバイナリで管理されるため読み込みに失敗して、テクスチャアトラスの参照が剥がれてしまうことがあるようです。
あまりにも高頻度で参照が剥がれてしまうようでしたら今回解説したFallback Font Assets機能を使って、2048*2048のテクスチャ複数枚で管理するなどをおすすめします。

一旦これで以上になります。
閲覧ありがとうございました。

まとめ


以上でTextMeshProの使い方を終わりにします。
間違っていたら連絡ください手直しいたします。
今回はベイクするまでの設定を中心に教えましたが、TextMeshProはベイクする作業より色々装飾をつける作業のほうが楽しいので、ここで終わりにせず色々いじってみて欲しいです。
私としてはベイクについて中心的に述べてある記事が少ないことが気がかりだったために、この記事を書きました、装飾の仕方などは別の方の記事を参考にしていただければと思います。
(それはそれとして要望があれば書きます。Twitterなりでメンションくれれば即反応すると思います)
これからTextMeshProを始める人の手助けになれば幸いです。
以上、やまだたいし( やまだ たいし (@OrotiYamatano) / Twitter )でした。

TextMeshProの使い方【前編】

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) / Twitter )です。
以前、TextMeshProをStatic SDFであればフォントを使わなくて済むのでFonts66コンプリートパックを使うことができるという記事を書かせていただきました。

orotiyamatano.hatenablog.com

しかし、結構散見されるのは、「Static SDFの使い方が分からない!」、「TextMeshProの設定が分からない」という意見です。
確かにStatic SDFのやり方は一見分かりづらい&忘れがちのため、今回調べてメモをしておこうと思ったためこの記事を書くことにしました。

目次


TextMeshProとは?


通称TMPとも略されるTextMeshPro。
そもそもコレが何なのかというのを解説させてもらうと、
Textが使えるアセットでUnity 2018.2からUnity公式で使えるようになったアセットです。
2018.2以降であればデフォルトでUnityに組み込まれています。

テキスト表示であればコレまでも使えていたではないか!と思われる方もいると思いますが、
Text Mesh Pro の方が優秀だと断言しておきます。

何が嬉しいのか


何が嬉しいのか1つめキレイ。

普通のUI Textだとテキストがぼやけるデメリットがあります。
Text Mesh ProはSDF(Signed Distance Field)(日本語訳すると符号付き距離函数とかいうやつ)を使う。
「距離情報によるテクスチャ画像のベクトル化」を行いキレイにフォントを表示するのだ。
要するにフォントデータをテクスチャに焼き込んでおいてロジック側でにじみが無いように拡大させてあげることができるのが、このSDF形式という物らしいです。
だからキレイに出力されます。

じゃあ、なぜUI Textがぼやけるのかというと、
ココに関しては設定などによっては、ぼやけなくも出来るのだが、基本的にフォントはキャッシュ(一部情報として保存)されており、
テクスチャフォントアトラス(複数の画像を1つの画像データとして保存したデータのこと)化されソレを表示している。
そのためスケールを変えるなどを行うと元のテクスチャの大きさは変わらないため、ボヤケてしまうのです。

何が嬉しいのか2つめ容量が優しい。
先程もいったようにテクスチャフォントアトラスを作る必要があるのでUI Textはフォントをバイナリ(ココでは最終的なアプリの内部情報のことを指す)に含める必要があります。

今やフォントといえばベクターフォントが多いですが、このベクターフォントはドット情報を保存するのではなく、
このベクターフォントは線の長さや向きなどを記憶させておいて、どんなにフォントを大きくしても滲まないようにしてあげるものです。

しかし、いかんせんこれは容量が多いです。
また、フォントサイズを異なる大きさで保存する場合、その大きさごとにフォントアトラスが生成されます。

(↓こんな感じ)

TextMeshProはテクスチャを焼いて(焼く→生成すること。IT用語。語源は英語のBake.)おいてから、SDFで処理するため、フォントデータをバイナリに組み込む必要がアリません。
容量的に優しいですし、フォント規約にはバイナリにフォントデータを組み込んでは行けないというものもあるため、それを回避することが出来ます。

ちなみに焼き込んだデータはフォントサイズごとに用意する必要がないのでさほど重くないです。

(↓こんな感じ)

何が嬉しいのか3つめ色々装飾が出来るのに軽い。

UI Textでは出来ることが限られています。
アウトラインくらいならつけることが出来ますが、アウトラインをつける処理の特性上、Text Mesh Proの方が軽いです。

ざっと述べるとこんな感じです。
単に表示させるだけならUI Textでも十分ですが、細かい動きをつけたり、見た目をよくしようとすると断然Text Mesh Proにしておいたほうが良いです。

TextMeshProの利用方法


初期設定


TMP(TextMeshPro。以下TMP)の実行方法。

まずはフォントを使えるようにしてあげないといけないのでツールバー>Component>Mesh>TextMeshPro - Textを選択します。

すると、TMP Importerというウィンドウが表示されるのでImport TMP Essentialsをクリックします。

するとTMPのシェーダーなどが一通りインポートされ準備完了です。

フォント


上記の設定で使うことも出来るのですが、
先程もいったように一旦テクスチャを生成しなければいけません。
またデフォルト設定でいくつか既に出来上がっているのですが、英語文字しか含まれていません。
まぁ、生成しなくても日本語文字は動的に生成されるのですが、動的にテクスチャアトラスを生成する必要があるため、
フォントデータをバイナリに組み込む必要があります。TMPの利点を最大限に活かせません。もったいないですね。

それに貴方が使いたいであろうデフォルト以外のフォントはまだ使えません。
ココからは新しくフォントを設定する方法についてです。

まず、使用したいフォントをドロック&ドロップでUnityに取り込みます。(バイナリデータに含めたくないので後で消します)
(フォント名に日本語が含まれる場合は上手く使えないことがあるようなので、フォント名を日本語から英字に変えておいた方が良いかも?)
(ちなみに私が今回使うのは株式会社ネットユーコムさんのAFSまるご風書体)

右クリックをして
Create > TextMeshPro > FontAssetを指定

すると、テクスチャアトラスの設定されてないSDFアセットが生成されます。

生成されたSDFアセットを選択し、Inspecter Windowの設定を変更します。

何が設定できるのか項目を見ていきましょう。
まずはFace Infoから。

Face Infoにはフォント内に設定されている基本情報が格納されます。
基本的に値を変えた場合、最悪文字が崩れる場合があるため変更はしないほうが良いと思います。

各項目について解説しようと思って連番をつけましたが、
フォントをTextMeshProの機能でアトラス化するときに参照する情報みたいで変更すると上手く切り取られなくなるようです。
購入フォントでは設定情報が誤っているということは、ほとんど無いと思われるので、変えないほうが良いと思われます。
(切り抜くために↓こういう情報が入ってるらしい)

次はGeneration Settingについてです。

(多分)描画する上で参照情報です。

  1. SourceFontFile →フォントの参照データです。ない場合はエラーになります。
  2. Atlas Population Mode → 出力設定です。Dynamicになっている場合、アトラス化済み以外のデータは随時動的に生成されます。今回はアプリにフォントデータを含めたくないのでStaticにします。(Bake済みデータしか表示されなくなるので注意)
  3. Atlas Render Mode → アトラス画像を生成するときのレンダリングロジックの選択です。選択する物によって生成スピード、文字の綺麗さなどが変わります。
    英語のネット記事を読む限りAtlas Render ModeはSDFAAが高速で安定しているようです。
    1. SMOOTH_HINTED→スムージング表現(フォントヒンディング)グリフのアウトラインの8bitまたはアンチエイリアス処理された画像から、ヒントを使用してグリフのビットマップ表現をレンダリング
    2. SMOOTH→スムージング表現 グリフのアウトラインの8ビットまたはアンチエイリアス処理された画像から、ヒントなしでグリフのビットマップ表現をレンダリング
    3. RASTER_HINTED→ラスター表現(フォントヒンディング) グリフのアウトラインのバイナリ(1ビットモノクロ)画像からグリフのビットマップ表現をヒント付きでレンダリング
    4. RASTER→ラスター表現 グリフのアウトラインのバイナリ(1ビットモノクロ)画像から、ヒントなしでグリフのビットマップ表現をレンダリング
    5. SDF→ SDFシェーダを使用するために必要なテクスチャを作成する。 グリフのアウトラインのバイナリ(1ビットモノクロ)画像から、ヒントなしでグリフの符号付き距離フィールド(SDF)表現をレンダリング。 1.SDF8→SDFのグリフのサンプリングが8倍スケールアップ版 1.SDF16→SDFのグリフのサンプリングが16倍スケールアップ版 1.SDF32→SDFのグリフのサンプリングが32倍スケールアップ版 1.SDFAA_HINTED→グリフの8ビットまたはアンチエイリアス処理された画像からグリフの符号付き距離フィールド(SDF)表現を、ヒント付きでレンダリングします。このレンダリングモードは非常に高速だが、精度が少し低下します。
    6. SDFAA → グリフの8bitまたはアンチエイリアス処理された画像からグリフの符号付き距離フィールド(SDF)表現を、ヒントなしでレンダリングします。レンダリングモードは非常に高速だが、精度が少し低下 SDFは数が大きいほど正確になります。
  4. Sampling Point Size → どうやらサンプリング元になるデータのフォントサイズを指定するようです。大きければ大きいほどキレイ?まぁ焼くスピードも遅くなると思うのでデフォにしておくことにします。私の設定は90になっているけど90は少し大きいかも?
  5. Padding → 文字間ですね。
  6. Atlas Width → アトラス画像の横幅です。今回は常用漢字と英数字を入れたいので1024では収まらないと思うので、もっと大きい数値に8192にしておきます。
  7. Atlas Height → アトラス画像の縦幅です。今回は常用漢字と英数字を入れたいので1024では収まらないと思うので、もっと大きい数値に8192にしておきます。

前半はとりあえずここまで


意外と長くなったので、前編、後編で区切らせていただこうと思います。

一旦ココまでで失礼します!
次回は実際に書き出しと描画をします。後、アセットを管理する上で気をつけることなどを書けたら良いなと思います。
何か誤っているなどアリましたら教えて下さい!
↓続き
orotiyamatano.hatenablog.com

もうソレ何番煎じ?Unity、UEの比較

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
これまでUnity、UEの比較記事が大量がありました。
しかし、どちらも(平等ではなく)公平に語られてないような気がします。
なので、私が出来るだけ公平に答えたいと思います。
本当はTwitterに載せようと思ったのですが、思いの外、長くなったのでブログにしました。
思いついた順で書いてるので読みにくいかも知れませんが、ご承知ください。

目次


一番最初に言いたいこと


意見が私が偏ってないと言い張る理由は、業務で半年以上UEを使っていること、
Unityもゲームをリリースする程度には触ってきたことです。
強いて言うなら、UEよりUnityの方が触っている期間が長いので肩を持ってしまっています。
(後、Unityゲーム開発者ギルドに入ってしまっているので……)

しかしながら、UEも半年は触ってきたので色々述べられるところがあると思います。

後、これだけは言っておこうと思います。

そもそも、それぞれ利点欠点があるので比較するものではない!

では、早速つらつらと比較していこうと思います。

本文


まずUnity、マルチスレッド、イベントドリブンの考え方はUniRxなど外部ライブラリの力を使わないと使えないので、そういった実装がやりづらいのが難点ですね。
UnityではモノビヘイビアとC#の力で色々制御されているのでユーザー(エンジンの使用者。以下ユーザー)が色々違反したように書けないのが良いです。
(DOTS?しらんな……)

UEは自由に書けすぎてしまうのが難点ですね、イベントドリブン、マルチスレッドは出来ますが、全てをユーザー側で厳密に管理しないといけないです。
設計思考がチームごとに厳密にないプロジェクトも多いと思うのでカオスなコードが出来上がりやすいです。

UEは命名規則もあることから、ルールでなんとかしようという取り組みがあるのが伺えます。


グラフィックはUEに軍配。様々なツールとのやりとりが出来たり、確認がしやすいのが良いですね。
Unityも頑張ってきてるのでココはなんとも言えないです。
Unityは前まではパイプラインまわりがいじれなかったのにイジれるようになったりもしたので良いですね。
後はポストプロセスをもう少し頑張って。


システムについて
Unityはユーザー側で色々考えて実装したり、ライブラリを買ったりして実装することが多いですが、
UEはデバッグコマンドやベースシステムが組み込まれてるのが良いですね。
Unityだとデバッグコマンドなどは自前実装するか、ライブラリを買うしかありません。
UEは日本語資料を増やせとは言わないので、総量として英語だけでもドキュメントを頑張って欲しいです。

Unityのシステムは基礎しか無いので自分で考える必要がありますが、
逆にその基礎は色んな会社で触られるからか、ユーザーのWEB記事が充実してます。
UEはこんなの誰が使うんだ?という機能が多すぎます。
使えない機能はなくして……。


Unityはある程度のPCスペックでエンジンが動くのが良いです。
どの環境でもほぼ同じように動く!
UEは思い通りに動かないことが多いのでイライラします。
しかしながら、UEはエンジンからサワれるので色々カスタマイズしやすいのは利点ですね。
Unityのように色々組み込み方を覚えずに済む。


コードビルドはUnityが軍配ですかね。
IL2CPPだとUnityも時間がかかりますが、UEは通常のコードビルドも結構かかってしまうのが難点です。
ただ、UEはノードベースのBlueprintはビルドは爆速で確認のイテレーションが回しやすいので
BlueprintだけのプロトタイプならUEが早く作れて良いかも?
UEのパッケージング周りは私は、あまり触ったことがないのでソコに関しては割愛。


2D処理はUnityが軍配ですね。
3DはUE。
UEはアニメーション周りが充実していて使いやすいです。
Unityは結構ネイティブ的な使い方っぽい感じがしますが、私があまり機能を知らないだけの可能性もあるので、深くは語りません。
Unityは外部のアセットになるのですが、色々2Dのアセットが充実していて動きを作りやすいのがいいですね。


個人的には初心者にはUnityをおすすめします。
ポインタ周りは初心者には辛すぎる。
UEのBlueprintだけで実装する手もありですが、スケールアップしていく中で
UEは参考資料も少なかったり、色々弄りたくなると思います。
ゲームプログラマを目指すなら、そのぐらい頑張れ!」と言いたい方もいると思いますが、
本当の初心者例えば小学生や中学生がポインタの概念を理解するのは辛いと思います。
最初作ったそのままの感覚で進められるUnityの方が強い。

しかしながら、大規模開発はUEで行われることが今は多いので、大規模コンシューマーに関わりたいなら勉強していて損はなさそう。


後、最初から大規模予定ならUEでも良いかも。
大規模開発に向いてる。
Unityは小規模チーム開発に向いてるかも。
どちらも少人数、大規模が出来ないわけじゃないですが……
どちらも同程度に使えるならという前提なら私はそう思います。

まとめというか、感想というか一言


公平に語ったつもりですが、賛否両論あると思います。
どちらが優れているとかは無いと思います。

作るのは開発者であってエンジンは手段でしか無いです。
状況に応じて判断すべきで、どちらが良いかとは一概にいえないと思います。

Unityエンジニアだから、UEエンジニアだからって現状を嘆いたり、相手を悪く言う必要はないと思います。
というか、どちらも触っている私に精神衛生上に優しくないので、なかよくしてください。

以上です。

MVRP4Uリポジトリの解説

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
以前 UnityのMVP、MV(R)Pを調べたけど、どれが正しいんだ?という記事を書きましたが、
結構閲覧されている割に一番見て欲しいリポジトリの中身を見られていないようだったので、
今回はそのリポジトリの中身の解説をしていきます。
(合わせて2019verにバージョンアップをしました)

github.com

目次


中身について


中身はただボタンを押すと、カウントアップするものです。

f:id:OrotiYamatano:20200908020112g:plain
中身

使用ライブラリ


Zenject(Extenject)


ZenjectとはDIフレームワークです。
DI(Dependency Injection)は依存性の注入という意味ですが、かんたんに言うと、
半自動的にインスタンスを生成してくれる機能です。(ちょっと語弊があるケド……)

シングルトン(Singleton)とかは世界に一つだけで、グローバルにどこからでもアクセスできるという利点がありますが、
逆に言うと世界のどこからでもアクセスできてしまいます。

ゲーム業界では音声の管理やキー入力の管理など好んで使われますが、業界によってはグローバル変数と同じぐらい嫌われ者です。

そこで出てきたのがサービスロケーター(Service Locator)パターンと呼ばれるもので、
コレクションとして登録しておいて必要に応じてインスタンスを生成し取り出す形です。

もう少し分かりやすく言うと、「シングルトンなファクトリークラス(サービスロケーター)にインスタンスをキャッシュしておいて必要に応じて取り出す」って感じです。

手元のインスタンスに対して代入してから使うので、グローバル変数みたいに直接にアクセスするわけではないので、多少マシです。

しかしながら、サービスロケーターでは結局ずっとキャッシュを保持し続けることになり優しくありませんし、
消す仕組みをいれたとしても「シングルトンなファクトリークラス」を参照する必要があり密結合です。

そこで出てくるのがDIパターンです。
DIパターンは先にインスタンスを生成しておいて、生成しておいたクラスをインスタンスに代入してくれます。
使用側はあまり意識せずに利用できるような仕組みです。

詳細は割愛しますが、DIパターンを書くとコード量が多くなるので、DIのコードを書かず
いい感じにDIの良さを体験できるのがDIフレームワークです。

まぁかんたんに言えば、「シングルトンを寿命管理したりスコープ管理したり出来るいい感じのヤツ」がIDフレームワークと考えてくれれば良いです。

UnityのDIフレームワークで今回利用するのがZenject(Extenject)ってわけです。
ExtenjectとはZenjectの派生リポジトリなんですが、(権利周りで)なんやかんやあって
普通最新であるはずの本家リポジトリのZenjectより最新で使いやすいのでExtenjectの方を使いましょう。

assetstore.unity.com

UniRx


UniRxとは
Uni(Unity)Rx(Reactive Extensions)と名前の通り
Unityでリアクティブプログラミングをするためのライブラリです。

作者さんのオレオレライブラリって訳ではなく、ちゃんとした考えに則って作られてます。(本家Rx系から機能を移植されるかは作者の気分次第かも?)

Rxの強みは非同期処理とObservableパターンのプログラミングがしやすくなることだと私は考えます。
Observableパターンとは例えばボタンが押した時に処理が反応するという物を作りたい時、
普通ならボタンを押下という処理からボタンの処理という関数を呼び出すのが普通です。

ですが、Observableパターンは逆で、ボタンを押されたかを監視して、押されたら処理を実行するという処理になります。
ん?イベント駆動型とどう違うんだ?と思った貴方!鋭い!

実際にはイベント駆動型の場合別スレッドで受け取る側を用意していてボタンを押した次のフレームなどに処理されますが、
Observableパターンの場合は同一フレーム内で即実行される。とだけ、ここでは言っておきます。

詳しくは調べてください。
というかそんなことに気がつく貴方はここの記事読まなくても良いのでは?

とにかく、Observableパターンはそのようなイベント駆動型と似ていて、ライブラリを使えば、コードを短く書ける利点があるだけでなく、感覚的に処理が書けます。
もちろん学習コストはかかりますが、慣れれば手放したくないという人が、ほとんどです。

これまでゲームはシングルスレッドで、Update(もしくはTick)処理にて毎フレームゲームオブジェクトを更新したり更新しなかったりするのが普通でした。
しかし、ゲームのオブジェクトの巨大化やシミュレーションするオブジェクト数の増加により単純にUpdateの呼び出しにさえコストを計算する必要が出てきました。

そこで、マルチスレッド化をしたり、Updateを必要なときだけ呼び出して、他の場合は呼び出さないと言ったような処理をしたいとき、
UniRxのようなイベントベース、メッセージベースの機能は便利に働きます。
(Unityは早くシングルスレッドやめろ)

UniRxは必要なときにしかUpdateを呼び出さない処理が組み込まれています。(UpdateAsObservable)
また、頻繁に呼ばないと行けない時に呼び出す関数(Observable.EveryUpdate)などもあり、
使い分けをすることで普通にUnityでUpdate関数を呼び出すより高速化が測れます。

後、MonoBehaviour を継承していないクラスでUpdate出来たり……とにかく便利

qiita.com

作者の一言

前回紹介したMicroCoroutineを改良して、配列をお掃除しながら走査する(かつ配列走査速度は極力最高速を維持する)ようになった

neue.cc

どうやって早くしてるのかとかはUnity触ったことのある人なら一度はお世話になる
皆おなじみテラシュールブログの解説が分かりやすいらしいです。

tsubakit1.hateblo.jp

まぁ、早くなるし、短く書けるのでオススメです。

assetstore.unity.com

MV(R)Pとは


まず、リポジトリの中身を説明する前にレイヤ別けの考え方を説明しておきます。

というか、MVP(MVC)については私より説明がうまいページがあるので、ここを読んできてください。

developers.cyberagent.co.jp

MVPをリアクティブプログラミングでやるのがMV(R)Pです。
この上のリンクのページもキレイにプログラムを書けているのですが、ライブラリを用いていないためコード数が多く複雑です。

ライブラリを使うともっとスッキリ書くことが出来ます。

後、UnityはMonoBehaviourによって支えられていますが、MonoBehaviourを継承して使っているということはMonoBehaviourに依存しているということでもあります。
きれいなソースコードを目指す人はできる限りMonoBehaviourを継承しないマッサラなクラスがきれいなので出来るだけ依存しないようにしましょう。
MonoBehaviourに依存していないコード、イコールUnityにアタッチしていないコードとなるのでUnity上でのコンポーネントのアタッチの考慮をしなくていいのでキレイです。

ソースコード


では、コードの中身を解説していきたいと思います。

Model


まずは分かりやすいModel(TestModel.cs)から。

まずメンバ変数として2つ定義します。

        private readonly IntReactiveProperty num = new IntReactiveProperty();
        public IReadOnlyReactiveProperty<int> Num => num;

いきなり、マッサラなC#しか触ってこなかった人だと分からない型が、出てきましたね?
これはUniRxで定義されている変数の型で、IntReactiveProperty は値が変更した際にイベントのようなもの(OnNext)を発行する型です。
intの派生形だと思ってもらえばいいです。
IReadOnlyReactivePropertypablic で定義されていますが見ての通りReadOnlyで変数 num を参照しています。
つまり Num を外部から使えば num の変更したタイミングと値を取得することが出来ます。

次にコンストラクタの解説に進みます。

        private TestModel(){
            num.Value = 0; //値のリセット
        }

はい。ただnumを初期化してるだけです。

次はビジネスロジックです。
といってもただ加算するものですので、ただ足すだけです。

        public void CountUp()
        {
            num.Value++;
        }

はい。
これで終わりです。

以下がModelの全文。

using UniRx;

namespace Sample.Models
{
    /// <summary>
    /// Model
    /// ビジネスロジックはmodelに書く
    /// </summary>
    public class TestModel
    {
        private readonly IntReactiveProperty num = new IntReactiveProperty();
        public IReadOnlyReactiveProperty<int> Num => num;


        private TestModel(){
            num.Value = 0; //値のリセット
        }

        // カウントアップの処理(ビジネスロジック)
        public void CountUp()
        {
            num.Value++;
        }

    }
}

簡単ですね。
UniRxを使わなくてもModelはほぼ同じような中身になると思います。

View


次はView(TestView.cs)の解説です。
ViewはUnityのオブジェクトにアタッチします。

メンバ変数の解説をします。

        [SerializeField]
        private Button countButton = null;
        [SerializeField]
        private Text text = null;

        //ボタンがタッチされたらPresenterに通知
        public IObservable<Unit> PushButtonObservable => countButton.onClick.AsObservable();

Unityお馴染みの SerializeFieldButtonText を定義します。
もちろん、Unityエディタ側で対応するオブジェクトをアタッチしておきます。

f:id:OrotiYamatano:20200912015247p:plain
スクリプトに該当オブジェクトをアタッチ

次に IObservable<Unit> で定義してあるものですが、こちらもUniRxの機能を使っています。
IObservableと書いている通り監視者です。
ボタン( countButton )が押された時( onClick )に IObservable型にキャスト( AsObservable ) して返す処理を監視します。
publicになっていますので、外部からコレを参照すると、クリックしたタイミングを取得できます。

次に見た目への反映部分の処理です。

        //見た目へ変更を加える(Presenterから呼ばれる)
        public void TextMeshUguiSet(string str)
        {
            text.text = str;
        }

はい。
ただ、pubicメソッドでテキストを代入してるだけです。
これで終わりです。

以下がViewの全文。

using System;
using UniRx;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;

namespace Sample.Views
{
    /// <summary>
    /// Viewの設定
    /// </summary>
    public class TestView : MonoBehaviour
    {
        [SerializeField]
        private Button countButton = null;
        [SerializeField]
        private Text text = null;

        //ボタンがタッチされたらPresenterに通知
        public IObservable<Unit> PushButtonObservable => countButton.onClick.AsObservable();

        //見た目へ変更を加える(Presenterから呼ばれる)
        public void TextMeshUguiSet(string str)
        {
            text.text = str;
        }
        
    }
}

簡単ですね。
Unityにアタッチしてる部分なのでそんなに難しい処理は無いです。

Presenter


次に仲介者であるPresenter(TestPresenter.cs)を解説していきます。
まずは、メンバ変数の解説。

        //読み取りしかしない
        private readonly TestView testView;
        private readonly TestModel upButtonModel;

メンバ変数は、先程解説したTestViewとTestModelを変数として保持しています。
privateでreadonlyなので、ViewとModelは互いに存在を知りません。

コンストラクタの解説です。

        //Presenterの処理
        public TestPresenter(TestModel model, TestView view)
        {
            upButtonModel =  model ?? throw new ArgumentNullException(nameof(model));
            testView = view ? view : throw new ArgumentNullException(nameof(view));
            //ModelとViewが増えたら追記していく
            
            upButtonModel.Num.Subscribe(ViewNumUpdate); //Modelに変更があったらViewへ更新
            testView.PushButtonObservable.Subscribe(_=> CountUp());  //Viewからカウントアップ通知があったらModelを更新

        }

はい、ここでModelとViewの中身を入れてます。
こちらでZenjectの コンストラクタ/メソッドインジェクション を利用しています。
後でもう少し詳しく解説しますが、「先のModelとViewの中身を入れてるんだな~」ぐらいの認識でOKです。
ArgumentNullException は中身なければエラー吐くぐらいな感じです。
定義忘れとかのチェックのために念の為書いておきます。

で、 upButtonModel.Num.Subscribe(ViewNumUpdate); ですが、コメント文のとおりです。
Numの解説に「 Num を外部から使えば num の変更したタイミングと値を取得することが出来ます。」と書きましたが、ここで使用してます。
ViewNumUpdate はメソッドです。メソッド呼び出しをするということです。

要約するとSubscribeでupButtonModelのNumつまり、Modelのnumに変更があったら変更通知を取得でき、ViewNumUpdateメソッド呼び出しをしているということです。

testView.PushButtonObservable.Subscribe(_=> CountUp()); ですが、コチラもコメント文のとおりです。
PushButtonObservableの説明で「外部からコレを参照すると、クリックしたタイミングを取得できます。」と書きましたが、ここで使用してます。(なんせ仲介者ですからね)
こちらはCountUpメソッドを呼んでいます。

では、ViewNumUpdateメソッドとCountUpメソッドの解説です。
と言っても解説するほどの内容は無いです。

        /// <summary>
        /// Modelのカウントアップ処理を呼ぶ
        /// </summary>
        private void CountUp()
        {
            upButtonModel.CountUp();
        }

        /// <summary>
        /// ViewのTextMeshUguiSetを呼ぶ
        /// </summary>
        /// <param name="num"></param>
        private void ViewNumUpdate(int num)
        {
            testView.TextMeshUguiSet(num.ToString());
        }

はい、各Model,Viewの処理を引き継いで渡してる(仲介してる)だけです。
これで終わりです。

以下がPresenterの全文。

using System;
using Sample.Models;
using Sample.Views;
using UniRx;

namespace Sample.Presenter {
    /// <summary>
    /// Presenter
    /// Modelの変更をViewに反映し、ViewのアクションをModelへ反映
    /// </summary>
    public class TestPresenter
    {
        //読み取りしかしない
        private readonly TestView testView;
        private readonly TestModel upButtonModel;

        //Presenterの処理
        public TestPresenter(TestModel model, TestView view)
        {
            upButtonModel =  model ?? throw new ArgumentNullException(nameof(model));
            testView = view ? view : throw new ArgumentNullException(nameof(view));
            //ModelとViewが増えたら追記していく
            
            upButtonModel.Num.Subscribe(ViewNumUpdate); //Modelに変更があったらViewへ更新
            testView.PushButtonObservable.Subscribe(_=> CountUp());  //Viewからカウントアップ通知があったらModelを更新

        }
        
        /// <summary>
        /// Modelのカウントアップ処理を呼ぶ
        /// </summary>
        private void CountUp()
        {
            upButtonModel.CountUp();
        }

        /// <summary>
        /// ViewのTextMeshUguiSetを呼ぶ
        /// </summary>
        /// <param name="num"></param>
        private void ViewNumUpdate(int num)
        {
            testView.TextMeshUguiSet(num.ToString());
        }
    }
}

簡単ですね。
ViewとModelの仲介をしてるだけなので難しい処理は無いです。
見ての通りZenjectを使うとPresenterがMonoBehaviourを継承せずに済み、依存性が薄いクラスが出来上がります。

Zenjectinstaller


一気に説明します。
zenjectInstallerと名前にある通り、zenjectのインストール関連を管理しています。
Containerへのインストールですね。
今回はMonoInstallerを使用します。
定義したものはMonoBehaviourのように振る舞うようになります。
インストールの定義を書かれたものがInstallerのスクリプト

TestModelをAsCached( ContractTypeが要求されるたびにResultTypeの同じインスタンスを再利用。これは最初の使用時に遅れて生成)。
TestPresenterをAsCached( ContractTypeが要求されるたびにResultTypeの同じインスタンスを再利用。これは最初の使用時に遅れて生成)。
した上で、NonLazy(これを指定すると最初にインスタンスが生成される)

using Sample.Models;
using Sample.Presenter;
using Zenject;

namespace Sample.ZenjectInstaller
{
    public class SampleButtonInstaller : MonoInstaller
    {
        //zenjectでModelとPresenterのインストールする
        public override void InstallBindings()
        {
            Container.Bind<TestModel>().AsCached();
            Container.Bind<TestPresenter>().AsCached().NonLazy();
        }
    }
}

各オブジェクトの紐付け


TestModelとTestPresenterがMonoBehabiorのように振る舞うようになりましたが、肝心な部分を解説していません。
それぞれのオブジェクトをどうやってPresenterにつなぎ込んでいるのかです。

それはズバリ、SceneContextやZenjectBindingで解消されます!
後、マッサラなクラスやUnityにアタッチしているオブジェクト()をZenjectの SceneContextZenjectBinding に設定することで依存関係を直してれます。

f:id:OrotiYamatano:20200912022613p:plain

まとめ


ぶっちゃけ、勢いで書いてみたものの、Zenject周りの解説があってるのかとか、
サービスロケーターの説明がちゃんと正しいのかはちょっと不安です。

でも、アウトゲーム部分を作る分にはキレイなコードだと自負しています。
(インゲームに適用するのはおすすめしません)

こんな雑な説明をしていますが、一応私も業務でUnityを使ったことがある身です。
説明は下手ですが、コードは問題ないと思います。ある程度なら肥大化しても耐えうるでしょう。
Zenjectは生成時がちょっと処理負荷が重いですが、そこはちゃんと使いこなして生成タイミングをずらしたりすれば良いと思います。
疎結合のコードが出来る良いものです。
みなさんも色々設計考えながらコーディングしてみてはいかがでしょうか?
以上で解説を終わります。

おすすめの記事とか


ryo620.org

light11.hatenadiary.com

booth.pm

booth.pm

qiita.com

おまけ。


Editorフォルダ配下に途中まで、スクリプト自動生成スクリプトを書きました。
指定先のフォルダのtextを参照してクラスの雛形を作るだけです。

この辺を参考にすると作れます。

light11.hatenadiary.com

(Scriptが小文字になってるのが気に食わなくてソコだけはgifと変わってます)
(あ、削除する方は小文字で変え忘れてますね……ちゃんと消えない……まぁ、いいや)

f:id:OrotiYamatano:20200912025138p:plain

using UnityEngine;

using System.IO;
using System.Text;
using UnityEditor;

// コードの自動生成
public class MVPRU : EditorWindow
{

        private string _baseClassName = string.Empty;
        private string _sceneName = string.Empty;

        
        [MenuItem("Window/MVPRU")]
        private static void Open()
        {
            GetWindow<MVPRU>("MVPRU");
        }


        private void OnGUI()
        {
            EditorGUILayout.LabelField("SceneName");
            _sceneName = GUILayout.TextField(_sceneName);
            EditorGUILayout.LabelField("Create Base Class Name");
            _baseClassName = EditorGUILayout.TextField(_baseClassName);

            if (GUILayout.Button("CreateScript"))
            {
                string path = Application.dataPath;
                string namePath = "Scripts/" + _sceneName + "/";
                path += "/"+namePath;

                
                CreateScriptAsset(_sceneName+".Models", _baseClassName, "Model", path + "/Models",_sceneName);
                //CreateScriptAsset("Script."+_sceneName+".Presenters", _baseClassName, "Presenter", path+ "/Presenters",_sceneName);
                CreateScriptAsset(_sceneName+".Views", _baseClassName, "View", path + "/Views",_sceneName);
                
                Debug.Log($"Create Script Path : {path}");
            }


            if (GUILayout.Button("ClearScript"))
            {
                string path = Application.dataPath;
                string namePath = "script/" + _sceneName + "/";
                path += "/"+namePath;
                
                RemoveScriptAsset(_baseClassName, "Model", path + "/Models");
                RemoveScriptAsset(_baseClassName, "Presenter", path+ "/Presenters");
                RemoveScriptAsset(_baseClassName, "View", path + "/Views");
                SafeCreateDirectory(path + "/ZenjectInstaller/");
                Debug.Log($"Remove Script Path : {path}");
            }

        }

        private const string TemplateScriptFilePath = "ScriptTemplate/";

        private static void CreateScriptAsset(string nameSpace, string baseClassName, string domainName, string filePath,string sceneName)
        {
            string templateRawText = Resources.Load($"{TemplateScriptFilePath}{domainName}.cs").ToString();
            string replacedText = templateRawText.Replace("#SCRIPTNAME#", baseClassName).Replace("#NAMESPACE", nameSpace).Replace("#SCRIPTSCENENAME", sceneName);
            var encoding = new UTF8Encoding(true, false);

            if (Path.GetExtension(filePath) != "")
            {
                // If you select Non directory, then get parent directory.
                filePath = Directory.GetParent(filePath).FullName + "/";
            }

            SafeCreateDirectory(filePath);
            filePath += "/";

            string fileName = $"{baseClassName}{domainName}.cs";
            File.WriteAllText(filePath + fileName, replacedText, encoding);

            var createdScript = AssetDatabase.LoadAssetAtPath<MonoScript>(filePath + fileName);
            ProjectWindowUtil.ShowCreatedAsset(createdScript);
            AssetDatabase.Refresh();
        }

        private static void RemoveScriptAsset( string baseClassName, string domainName, string filePath)
        {
            if (Path.GetExtension(filePath) != "")
            {
                // If you select Non directory, then get parent directory.
                filePath = Directory.GetParent(filePath).FullName + "/";
            }
            filePath += "/";

            string fileName = $"{baseClassName}{domainName}.cs";

            File.Delete(filePath + fileName);

            AssetDatabase.Refresh();
            
        }
        
        
        private static DirectoryInfo SafeCreateDirectory( string path )
        {
            return Directory.Exists( path ) ? null : Directory.CreateDirectory( path );
        }
}

鈴木竹治氏のフォント「Fonts66コンプリートパック/109書体 -商用利用可」はUnityのSDFで使える?問い合わせてみた

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) / Twitter )です。
以前、Fonts66のセールがあり今回はUnityのTextMeshProでSDFを使いたいと思ったんですが、
使えるかどうかが不明だったので問い合わせをしてみました。

目次


なぜ問い合わせをしたの?


様々な用途で使えるようですが、ゲームで使う方法については具体的に記載されていません。
以前から私も気になっていましたし、今回セールがあり他にも気になる人が多く出てきそうなことや、Unityで使わず死蔵してる人も多いと思ったので問い合わせてみました。
以前ゲームない使用については、Unity AssetStoreさんが問い合わせをされていたようですが、SDFで使えるかどうかについては詳しく記載されていませんでした。

www.asset-sale.net

ズームしても滑らかに表示されるためNGだと思います

と書いてあるけど、具体的には不明?

今回のセール


nlab.itmedia.co.jp

27万円→2980円になるセールです。
半年に1回ぐらい(?)のペースでセールをされているようです。
とても安いです。

で、使えるの?


Dynamic SDFについては、もちろん使えません。
動的にフォントSDFを生成するためにゲーム内バイナリにフォントデータが含まれることになります。
ですので、PDFと同様に使用は出来ません。

しかし、Static SDFについては使えるようです!

問い合わせをした文章


ちょっと日本語が変ですが、私が問い合わせをした文章がこちら。

個人ゲーム開発者の山田大志です。
Fonts66の利用についての問い合わせです。
私はFonts66をゲーム内での利用を検討しています。
そこでゲームエンジンUnityのTextMeshPro機能にてSDF(Signed Distance Field)形式『ビットマップ画像だけれど、輪郭をきれいに出すプログラムを利用して表示させるもの』が利用可能かを伺いたいです。
以前Fonts66のゲーム内利用については、ビットマップでの利用ならOKとの回答をしていたようですが、( https://www.asset-sale.net/entry/Vector_Fonts66 )
SDF形式は「フォントデータを一部取り出しビットマップの画像形式で保存し表示させるもの」ではあるものの、「拡大縮小で品質が大きく変わるものでは無い」のですが、利用する場合は問題があるのでしょうか?
ご回答いただけますよう、よろしくお願いします。

そして、スキルインフォーメーションズさんの回答がこちら。
(念の為、担当者名は伏せます)

山田大志様
スキルインフォメーションズのxxと申します。
この度はFonts66についてのお問い合わせ
誠にありがとうございます。
ご質問いただきました内容についてですが、
ビットマップ化された画像データをゲームに搭載いただくことは
可能ですが、フォントデータそのものをゲームに実装、搭載する
ご使用方法は不可となります。

とのことです。

つまり今回は フォントデータそのもの ではないからOKだと思われます。

SDF形式を知らない可能性が高い気がしますが、一応、

SDF形式は「フォントデータを一部取り出しビットマップの画像形式で保存し表示させるもの」ではあるものの、「拡大縮小で品質が大きく変わるものでは無い」のですが、利用する場合は問題があるのでしょうか?

と私が軽く説明した上で返答が来ているので大丈夫だと思われます。
念押しとして本日(2020/08/24)以下の文章で返信をしておきました。

回答ありがとうございます。
フォントデータそのものが含まれないのであれば
SDF形式で利用が可能ということで実装させていただきたいと思います。

何か齟齬があれば返信が来るはずなので、その際はこの記事を編集しようと思います。

生のフォントデータそのものをゲームソフトに組み込むことについては、別途契約が必要らしいので、組み込まないようにだけ気をつけましょう!
またPC1台につき1ライセンスだということにも気をつけましょう。複数台で動作確認する際は台数分購入するようにしましょう!

Unityでstatic SDFで使う時に参考にできる記事


qiita.com

ということで、このあたりを参考にfontをベイクして使ってしまいましょう!

まとめ


Static SDFなら使える!ということで回答を得ることが出来ました。
万が一、齟齬があったら訂正記事を出そうと思います。

ということで皆、格安セールのうちに!Fonts66を買おうぜ!
TxetMeshProをStaticで使う方法についてはこちら↓
orotiyamatano.hatenablog.com