0.9K Views
November 24, 18
スライド概要
2018/11/24に開催されたUnity道場 京都スペシャル3の講演スライドです。
講師: 安原 祐二(ユニティ・テクノロジーズ・ジャパン合同会社)
Unityのイベント資料はこちらから:
https://www.slideshare.net/UnityTechnologiesJapan/clipboards
リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。
プロなら当然! プログラミング技能解説 ユニティ・テクノロジーズ・ジャパン 安原 祐二
長い歴史 C# Rust Java Haskell 関数型 C++ Perl オブジェクト指向 C 構造化 迷って当然
1. プログラム言語 2. 参照と実体 3. プッシュとプル 4. 再帰
Part 1 プログラム言語
自然言語 美しい王国の姫 ハッキリ しなさいよ 美しいのは王国?姫?
自然言語 美しい王国の姫 プログラム言語 ハッキリ しなさいよ int a = 1 + 2 * 3; 2*3が先に計算される int a = (1 + 2) * 3; 1+2が先に計算される 美しいのは王国?姫? 曖昧さがない
自然言語 •話す •聞く •聞く •話す •書く •読む •読む •書く
自然言語 プログラム言語 •話す •聞く •話す •聞く •聞く •話す •聞く •話す •書く •読む •書く •読む •読む •書く •読む •書く
コンピューターはこれが読めるの?? 読んで理解する仕組みがある!
1.字句解析 2.構文解析
1.字句解析 public class A { int c = 0; • スペースは単語の区切り • 改行も単語の区切り • ; は式の区切り public class A { int c = 0; • {} は始まりと終わり 単語や記号の区切りを検出
字句解析を 理解すれば public class Test : MonoBehaviour { void Start() { } void Update() { } } public class Test : MonoBehaviour { void Start(){} void Update(){} } 一行にまとめても同じ
この字下げ(indent)は必要なの? if (a == 10) { a = 0; } 必要ない! 字句解析に必要なのか 人間が見やすいためなのか 区別しよう
2.構文解析 public class A public class A { int c = 0; int 意味の構成を検出 c = 0
構文解析を 理解すれば void Start() { int a = 10; } void Start() { int a = ((((10)))); } ()をたくさん書いても問題なし
参考:IOCCC(国際難読化Cコードコンテスト)
#define/*__Int3rn^ti[]n/l_()I3fusc^t3|]_C_C<>I7E_C[]nt3st__*/L/*__MMXVIII__*/for
#include/*!"'()*+,-./12357:;<=>?CEFGHIJKLMNSTUVWXYZ[]^_`cfhijklmnrstuvwxyz{|}*/<stdio.h>
char*r,F[1<<21]="~T/}3(|+G{>/zUhy;Jx+5wG<v>>u55t.?sIZrC]n.;m+:l+Hk]WjNJi/Sh+2f1>c2H`)(_2(^L\
-]=([1/Z<2Y7/X12W:.VFFU1,T77S+;N?;M/>L..K1+JCCI<<H:(G*5F--E11C=5?.(>+(=3)Z-;*(:*.Y/5(-=)2*-U,\
/+-?5'(,+++***''EE>T,215IEUF:N`2`:?GK;+^`+?>)5?>U>_)5GxG).2K.2};}_235(]:5,S7E1(vTSS,-SSTvU(<-HG\
-2E2/2L2/EE->E:?EE,2XMMMM1Hy`)5rHK;+.T+?[n2/_2{LKN2/_|cK2+.2`;}:?{KL57?|cK:2{NrHKtMMMK2nrH;rH[n"
"CkM_E21-E,-1->E(_:mSE/LhLE/mm:2Ul;2M>,2KW-+.-u).5Lm?fM`2`2nZXjj?[n<YcK?2}yC}H[^7N7LX^7N7UN</:-\
ZWXI<^I2K?>T+?KH~-?f<;G_x2;;2XT7LXIuuVF2X(G(GVV-:-:KjJ]HKLyN7UjJ3.WXjNI2KN<l|cKt2~[IsHfI2w{[<VV"
"GIfZG>x#&#&&$#$;ZXIc###$&$$#>7[LMv{&&&&#&##L,l2TY.&$#$#&&$,(iiii,#&&&#$#$?TY2.$#$1(x###;2EE[t,\
SSEz.SW-k,T&&jC?E-.$##
&#&57+$$#
&&&W1-&$$7W
-J$#$kEN&#&
$##C^+$##W,h###n/+L2YE"
&&G_x
,mT&$YE-#&
5G
$#VVF$#&zNs$$&Ej]HELy\
G>x;
2zsW/$$#HKt&$$v>+t1(>"
"2nJk/H;YNs#$[,:TU(#$
,:
&&~H>&#
Y;
CN/U^Jk71<(#&:G7E+^&#
l|?1
$$Y.2$$
7lzs
WzZw>&$E
-<V-wE(2$$
"7>S7S,;TT,&$;S7S>7&#>E_::U
$$'",op
,*G=
F,*I=957+F
;int*t,k,O,
m%(0+nop
);;}
int*tOo,w,
h,z,W;void(C)
;void(E/*d
*/)(
int/*RP*/n
){L(Z=k+00;
Z;
|M(n,2)<<f,pq=2,f=+06
<f?++pq,++pq
,G++
,z:f+001,n
/=2;;}void
(V)(
int/*opqrstabd*/n){C(n
%Y);;C(n/Y+00);;}void
J(){L(pq--,pq
=j
=O=-1+0;++
j<240;I[6+
(h
+6+j/12/2*2+M(j/2,2))*
int m,int nop){;;;return+
f,c,H=11,Y=64<<2,Z,pq,X
W+M(j/2/2,+06)*2+w*014
+00+M(00+
000+j,002
+00)]=000
i,
j,T[+060<<+020];int M(
(int n){n=putchar(n);}int
Z/=+2+000)G[000]=*G*!!f
+00+k)k=M(G[j/2/2+(*r-+
32)**"<nopqabdeg"],/*4649&96#*/3);/*&oaogoqo*/;}/*xD%P$Q#Rq*/int/*dbqpdbqpxyzzyboo3570OQ*/main()
{L(X=Y-1;i<21*3;i++,I++)L(r=G,G+=2;*G++;)*G>=13*3?*G-*r?*I++=*G:(*I++=r[1],*I++=r[2]):1;L(j=12,r
=I;(*I=i=getchar())>-1;I++)i-7-3?I-=i<32||127<=i,j+=12:(H+=17+3,W=W<j?j:W,j=12);L(;*r>-1;r++)*r7-3?J(),w++:(w=z,h+=17+3);C(71);C(73);V('*'*'1'*7);C(57);C(32*3+1);V(W);V(H);C(122*2);L(V(i=z);i
<32*3;)C(i++/3*X/31);C(33);C(X);C(11);L(G="SJYXHFUJ735";*G;)C(*G++-5);C(3);V(1);L(V(j=z);j<21*3;
j++){k=257;V(63777);V(k<<2);V(M(j,32)?11:511);V(z);C(22*2);V(i=f=z);V(z);V(W);V(H);V(1<<11);r=
G=I+W*H;L(t=T;i<1<<21;i++)T[i]=i<Y?i:-1;E(Y);L(i=-1;++i<W*H;t=T+Z*Y+Y)c=I[i]?I[i]*31-31:(31<
j?j-31:31-j),Z=c[t[c]<z?E(Z),k<(1<<12)-2?t[c]=++k,T:T:t];E(Z);E(257);L(G++;k=G-r>X?X:G-r
,C(k),k;)L(;k--;C(*r++/*---#$%&04689@ABDOPQRabdegopq---*/));}C(53+6);return(z);}
https://www.ioccc.org/2018/endoh1/prog.c
例題: この()はなんなの?なくてもいいの? void Start() {
例題: この()はなんなの?なくてもいいの? void Start() { 必要。この()は「関数であること」を 構文解析器に伝える
このvoidはなんなの? void Start() {
このvoidはなんなの? void Start() { そのまえに・・・ 数学の関数を思い出そう 2 f(x) = x − 1 y = f(x)
数学 f (x) 2 =x プログラム −1 y = f (10 ) int f(int x) { 関数の定義 return x*x - 1; } 関数の評価 (呼び出し) y = f(10);
数学 f (x) 2 =x プログラム −1 y = f (10 ) int f(int x) { 関数の定義 return x*x - 1; } 関数の評価 (呼び出し) 関数は必ず結果を返す y = f(10);
そもそも関数は 函数 つまり 入れたら取り出す函(はこ) のこと
数学 f (x) 2 =x プログラム 返り値が 整数 −1 y = f (10 ) int f(int x) { 関数の定義 引数が 整数 return x*x - 1; } 関数の評価 (呼び出し) 関数は引数と返り値がある y = f(10);
プログラムでは、値を返さないこともある ??? f(int x) { 処理 return; } 値を返さない!
プログラムでは、値を返さないこともある ??? f(int x) { 処理 return; } 値を返さない! 構文解析しやすいように ??? はvoid と書くことにしよう ・・・ということでした
まとめ&今後の指針 • プログラムを解釈する機構を意識しよう • 記述には自由があることを知ろう • 自分が書いたコードをもういちど読んでみよう • いろんな言語の入門書を読んでみよう
Part 2 参照と実体
参照 実体 •預金通帳 •現金 •URL(リンク) •web魚拓 •クレジットカード •切符
広大なメモリ空間
広大なメモリ空間
2018 11 24
アドレス 中身 2148685152 2018 2148685160 11 2148685168 24 アドレスがあれば 中身にアクセスできる
ちなみに・・・ アドレス 中身 2148685152 2018 2148685160 11 2148685168 24 2148685176 2148685152 中身にアドレスも入れられる
class Date { 2148685128 long year; 2148685136 long month; 2148685144 long day; 2148685152 } 2148685160 この時点では何も起きない 2148685168 2148685176 2148685184 2148685192 2148685200 2148685208
2148685128 class Date { long year; 2148685136 long month; 2148685144 long day; } Date d = new Date(); メモリが確保される 2148685152 2018 2148685160 11 2148685168 24 2148685176 2148685184 2148685192 2148685200 2148685208
2148685128 class Date { long year; 2148685136 long month; 2148685144 long day; Date d = new Date(); } 2148685152 2018 2148685160 11 2148685168 24 2148685176 2148685184 d 2148685192 2148685200 dにはアドレスが入る 2148685208
2148685128 class Date { long year; 2148685136 long month; 2148685144 Date d = new Date(); long day; } Date d2 = d; 2148685152 2018 2148685160 11 2148685168 24 2148685176 d 2148685184 d2 2148685192 代入するとアドレスが 複製される 2148685200 2148685208
2148685128 class Date { long year; 2148685136 long month; 2148685144 Date d = new Date(); long day; } Date d2 = d; 2148685152 2018 2148685160 11 2148685168 25 2148685176 d なので d2 d2.day = 25; とするとdにも影響する 2148685184 2148685192 2148685200 2148685208
struct Date { long year; } classを structに変える 2148685128 2148685136 long month; 2148685144 long day; 2148685152 2148685160 2148685168 2148685176 2148685184 2148685192 2148685200 2148685208
struct Date { 2148685128 long year; 2148685136 long month; 2148685144 long day; Date d = new Date(); } d 2148685152 2018 2148685160 11 2148685168 24 2148685176 d 2148685184 2148685192 2148685200 中身がまるごと入る 2148685208
struct Date { 2148685128 long year; 2148685136 long month; 2148685144 Date d = new Date(); long day; } Date d2 = d; d 2148685152 2018 2148685160 11 2148685168 24 2148685176 d2 代入すると内容が 複製される 2148685184 2018 2148685192 11 2148685200 24 2148685208
struct Date { 2148685128 long year; 2148685136 long month; 2148685144 Date d = new Date(); long day; } Date d2 = d; d d2 なので d2.day = 25; としてもdに影響しない 2148685152 2018 2148685160 11 2148685168 24 2148685176 2148685184 2018 2148685192 11 2148685200 25 2148685208
参照 実体 •勝手に中身が変わる •中身は固定 •軽い(IDだけ) •重い(まるごと扱う) •複数から中身を閲覧できる •中身を知るのは自分のみ
一時変数を使う記述 int a = b + c; int d = a * 2; 一時変数を使わない記述 int d = (b+c) * 2;
一時変数を使う記述 Rigidbody rb = GetComponent<Rigidbody>(); rb.AddForce(f); 一時変数を使わない記述 GetComponent<Rigidbody>().AddForce(f);
エラーが出ます。なぜでしょう? transform.position.x = 10;
エラーが出ます。なぜでしょう? transform.position.x = 10; transform.position 実体を返す 複製
エラーが出ます。なぜでしょう? transform.position.x = 10; transform.position 実体を返す 複製.x = 10; 複製のxを更新している
エラーが出ます。なぜでしょう? transform.position.x = 10; transform.position 実体を返す 複製.x = 10; 複製のxを更新している 複製は破棄 元の transform.position には変化なし! 意味のない操作なのでエラーにしてくれる親切
正しい書き方 Vector3 pos = transform.position; 複製にposという名前を与える pos.x = 10; transform.position = pos; 複製のposを代入する transform.position は 実体返しだから
引数の値を変更したら? void func(int x) { x = 10; } int x = 5; func(x); Debug.Log(x); Debug.Logに渡されるのは 5? 10?
引数の値を変更したら? 実体を 更新している void func(int x) { x = 10; } この関数は 無意味 int x = 5; func(x); Debug.Log(x); Debug.Logに渡されるのは 5? 10?
引数の値を変更したら? void func(int[] x) { x[0] = 10; } int[] x = {5}; func(x); Debug.Log(x[0]); Debug.Logに渡されるのは 5? 10?
引数の値を変更したら? 参照を 更新している void func(int[] x) { x[0] = 10; } この関数は 狙い通り int[] x = {5}; func(x); Debug.Log(x[0]); Debug.Logに渡されるのは 5? 10?
エラーすら出てくれない例 Quaternion[] rots = new Quaternion[10]; rots[0]. SetLookRotation(Vector3.up); 期待通りに動作 List<Quaternion> rots = new List<Quaternion>(); rots[0]. SetLookRotation(Vector3.up); rots[0]に変化なし!(実体返しなので)
まとめ&今後の指針 • 言語を学ぶ際は、まず参照と実体の書き分けを確認しよう • C#の「値型(Value Type)」「参照型(Reference Type)」で調べてみよう • https://msdn.microsoft.com/ja-jp/library/cc406735.aspx • ref キーワードについて調べてみよう
Part 3 プッシュとプル ※確立したプログラミング用語ではなく、説明のための言葉です
プッシュ型 早く出なさいよ 主導権は送信側
プル型 来てるかな 主導権は受信側
プッシュ型の例 void OnCollisionEnter(Collision c) { … } 誰かがこの関数を呼んでいる 関連用語:コールバック
プル型の例 if (Input.GetMouseButton(0)) { … } 自分で関数を呼ぶ 関連用語:ポーリング
重大な事実 プルとプッシュは 受け手の自由
ただいま電話に 出られません。 発信音のあとに… プッシュ型を プル型に変換 留守電は あとでチェックしよう ! 送信側に変更なし 受信側で工夫
プル型を プッシュ型に変換 送信側に変更なし 受信したら 電話をかける装置 受信側で工夫
プッシュ型を プル型に変換 void OnCollisionEnter(Collision c) { hit = true; } void Update() { if (hit) { … } } 留守電は あとでチェックしよう
プル型を プッシュ型に変換 少々ややこしい
関数もメモリに置かれる 2148685128 2148685136 2148685144 void f() { 2148685152 void f() { 処理 2148685160 処理 return; 2148685168 return; } 2148685176 2148685184 2148685192 2148685200 2148685208 }
関数のアドレスを実行 2148685128 2148685136 2148685144 f(); 2148685152 を実行 2148685152 void f() { 2148685160 処理 2148685168 return; 2148685176 2148685184 2148685192 2148685200 2148685208 }
誰かに関数を実行してもらいたい 2148685128 2148685136 2148685144 2148685152 void f() { 2148685160 処理 2148685168 return; 2148685176 2148685184 2148685192 2148685200 2148685208 }
誰かに関数を実行してもらいたい 2148685128 2148685136 2148685144 変数に関数を入れられればいい 2148685152 void f() { 2148685160 処理 2148685168 return; 2148685176 } 2148685184 2148685192 2148685200 2148685208 2148685152
誰かに関数を実行してもらいたい 2148685128 2148685136 2148685144 変数に関数を入れられればいい 2148685152 void f() { 2148685160 処理 2148685168 return; 2148685176 } 2148685184 ここにある関数を実行するよ 2148685192 2148685200 C#ではdelegateを使う 2148685208 2148685152
void f() { … } 関数fを変数に入れたい
void f() { 関数fを変数に入れたい … } 関数fと同じ返り値と引数で delegate宣言 この段階では何も起きない delegate void MyFunc();
void f() { 関数fを変数に入れたい … } 関数fと同じ返り値と引数で delegate宣言 MyFuncで定義した 変数hにfを代入 delegate void MyFunc(); void Update() { MyFunc h = f;
void f() { 関数fを変数に入れたい … } 関数fと同じ返り値と引数で delegate宣言 MyFuncで定義した 変数hにfを代入 delegate void MyFunc(); void Update() { MyFunc h = f; h(); 変数hを実行 }
プル型を プッシュ型に変換 呼んで欲しい関数 static void OnMouseButton0() { … } 装置 delegate void MyFunc(); static MyFunc cb; static void set(MyFunc f) { cb = f; } 呼んで欲しい関数をセット void Update() { if (Input.GetMouseButton(0)) { void Start() { cb(); set(OnMouseButton0); } } }
delegateを使うと・・・ switch (n) { case 0: func0(); break; case 1: func1(); break; case 2: func2(); break; case 3: func3(); break; case 4: func4(); break; case 5: func5(); break; case 6: func6(); break; case 7: func7(); break; } func[n]();
まとめ&今後の指針 • 身の回りの通信がプッシュ型かプル型か意識しよう • delegateを使いこなそう • コールバック・ポーリングについて調べよう
Part 4 再帰
関数の中でその関数を 呼んだらどうなる? void f() { … f(); }
関数の中でその関数を 呼んだらどうなる? void f() { … f(); } 無限ループ!
引数を減らしながら呼ぶ void f(int n) { f(n-1); } nは減っていく でも無限ループ!
返り値を用意する int f(int n) { return f(n-1); } まだ無限ループ!
返り値を用意する int f(int n) { return f(n-1); } まだ無限ループ! 漸化式 f(n) = f(n −1) 記述は正しいが数学的には意味をなさない
漸化式には必ず条件がある n ≤ 0 のとき f(n) = 0 n > 0 のとき f(n) = f(n −1) プログラムも同様に条件を与える
数式 n ≤ 0 のとき f(n) = 0 n > 0 のとき f(n) = f(n −1) プログラム int f(int n) { if (n <= 0) return 0; else return f(n-1); }
応用例 n ≤ 0 のとき f(n) = 0 n > 0 のとき f(n) = f(n −1)+ n int f(int n) { if (n <= 0) return 0; else return f(n-1)+n; } f(4); で4+3+2+1を得る
親子構造のデータ © UTJ/UCL
動画
[MenuItem("GameObject/DumpName", false, 0)] static void DumpName() { var obj = Selection.activeGameObject; string str = createString(obj.transform); Debug.Log(str); } static string createString(Transform tfm) { string name = ""; name += tfm.name + "\n"; foreach(Transform child in tfm) { name += createString(child); } return name; }
[MenuItem("GameObject/DumpName(Indent)", false, 0)]
static void DumpNameIndent() {
var obj = Selection.activeGameObject;
string str = createString(obj.transform, 0);
Debug.Log(str);
}
static string createString(Transform tfm, int indent) {
string name = "";
for (int i = 0; i < indent; ++i) name += " ";
name += tfm.name + "\n";
foreach(Transform child in tfm) {
name += createString(child, indent+4);
}
return name;
}
再帰が役に立つシーン • 親子構造のデータを扱う • パズルの解を出すとき • 対戦ボードゲームのAIを作るとき
AddComponentすると
var g = new GameObject();
g.AddComponent<MyScript>();
Startが呼ばれる
class MyScript: MonoBehaviour {
void Start() {
}
}
Start で AddComponent すると・・・?
これを利用して再帰プログラムを書いてみよう
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fractal : MonoBehaviour {
[SerializeField] Mesh mesh_;
[SerializeField] Material material_;
const int MAX_DEPTH = 7;
Vector3 start_ = Vector3.zero;
Vector3 end_ = Vector3.up;
float width_ = 0.1f;
int depth_ = 0;
void initialize(Fractal parent, float given_length, float given_width,
Quaternion given_rotation, int depth)
{
mesh_ = parent.mesh_;
material_ = parent.material_;
transform.position = parent.transform.position + Vector3.up;
start_ = parent.end_;
end_ = start_ + given_rotation * (Vector3.up * given_length);
width_ = given_width;
depth_ = depth;
}
IEnumerator Start()
{
gameObject.AddComponent<MeshFilter>().mesh = mesh_;
gameObject.AddComponent<MeshRenderer>().material = material_;
transform.position = (start_ + end_) * 0.5f;
var diff = end_ - start_;
var length = diff.magnitude;
transform.rotation = Quaternion.LookRotation(diff) *
Quaternion.Euler(90f, 0f, 0f);
transform.localScale = new Vector3(width_, length * 1f, width_);
if (depth_ >= MAX_DEPTH)
yield break;
float roty = Random.Range(0f, 360f);
for (var i = 0; i < 5; ++i) {
if (Random.Range(0, 2) != 0)
continue;
yield return new WaitForSeconds(0.1f);
var child = new GameObject();
child.name = gameObject.name;
var fractal = child.AddComponent<Fractal>();
fractal.initialize(this,
length * 0.75f,
width_ * 0.75f,
transform.rotation * Quaternion.Euler(30f, roty, 0f),
depth_+1);
roty += 137.5f;
動画
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fractal : MonoBehaviour {
[SerializeField] Mesh mesh_;
[SerializeField] Material material_;
const int MAX_DEPTH = 7;
Vector3 start_ = Vector3.zero;
Vector3 end_ = Vector3.up;
float width_ = 0.1f;
int depth_ = 0;
void initialize(Fractal parent, float given_length, float given_width,
Quaternion given_rotation, int depth)
{
mesh_ = parent.mesh_;
material_ = parent.material_;
transform.position = parent.transform.position + Vector3.up;
start_ = parent.end_;
end_ = start_ + given_rotation * (Vector3.up * given_length);
width_ = given_width;
depth_ = depth;
}
再掲
IEnumerator Start()
{
gameObject.AddComponent<MeshFilter>().mesh = mesh_;
gameObject.AddComponent<MeshRenderer>().material = material_;
transform.position = (start_ + end_) * 0.5f;
var diff = end_ - start_;
var length = diff.magnitude;
transform.rotation = Quaternion.LookRotation(diff) *
Quaternion.Euler(90f, 0f, 0f);
transform.localScale = new Vector3(width_, length * 1f, width_);
if (depth_ >= MAX_DEPTH)
yield break;
float roty = Random.Range(0f, 360f);
for (var i = 0; i < 5; ++i) {
if (Random.Range(0, 2) != 0)
continue;
yield return new WaitForSeconds(0.1f);
var child = new GameObject();
child.name = gameObject.name;
var fractal = child.AddComponent<Fractal>();
fractal.initialize(this,
length * 0.75f,
width_ * 0.75f,
transform.rotation * Quaternion.Euler(30f, roty, 0f),
depth_+1);
roty += 137.5f;
まとめ&今後の指針 • 樹木生成のコードを変更して遊んでみよう • 「再帰は使うべきでない」とする説の意味を調べよう • スタックの動作を理解しよう
本日のお話 1. プログラム言語 C# 2. 参照と実体 Java 3. プッシュ/プル 4. 再帰 Rust Haskell 関数型 C++ Perl オブジェクト指向 C 構造化
永遠に続きます。
おしまい