Upload
ryota-murohoshi
View
5.568
Download
2
Embed Size (px)
Citation preview
UnityでC#を学び始めた私の主張
@RyotaMurohoshi
2015/09/26(土)*Comm*Tech*Fes4val
はじめまして、こんにちはUnityセッションを担当させていただきます
@RyotaMurohoshi-と申します
ごめんなさい
UnityにおけるC#とか.NETの『なんかすんげぇこと』は話しません
(話せません。ごめんなさい)
お前だれよ• twi%er(:(@RyotaMurohoshi
• 投稿先(:(h%p://qiita.com/RyotaMurohoshi
• 所属(:(Fuller,(Inc.
• コミュニティ(:(Unity部
• その他(:(UniLinqっての作りました(いらない子になりました)
「UnityでC#を学び始めた私の主張」のタイトル通り、
ゲームエンジンUnityとの出会い、C#を触り始めました
今は業務でもUnityでゲームを作っています
今日のこのセッションの方向性と目的を
ちょっとお話させて下さい
以前こんなことがありました
あるUnityの勉強会の飛び入りLTにて
おれ:「LINQ知っている方~(みなさんしってるよねー)」会場:「しーん。。。」おれ:(大体一割くらいだと。。。)
おれ:(LINQを使わないとはもったいない!!!)
※ただし、プログラマのみが対象の勉強会ではないです
※当時は、LINQがiOSで落ちるとか問題もあった
デリゲートやラムダ式についてまとめ記事書いた際も
「わかってなかった」とか「これ、知らなかった」
とかという感想をいただきました。
【LINQの前に】ラムダ式?デリゲート?Func<T,,TResult>?な人へのまとめ【知ってほしい】
Unityやっている人の中にはC#のあれこれ
実は知らない人も以外といるのでは?
確かにインターフェースの明示的な実装や
型パラメータの制約とか知らなくても
ゲームが作れなくはないですが...
だからって知らなくていいわけではないと思うんですよ
今日はUnityを題材に
普段のUnityの勉強会では触れられることが少ない
C#の言語機能を紹介し、個人的な主張を行います
みなさんへのお願い!
知らなかったことがあった方へ
「ここ知らなかった」とか「ここためになった」
ってつぶやいてくれると嬉しいです
C#マスターなみなさんへ
今日の内容はみなさんには当たり前のことばかりだと思います
!どうか私の意見に、ツッコミや反論お願いします!
どんどんつぶやいてください
後日、みなさんのご意見やツッコミを参考にさせていただいて
別のブログとかでUnityコミュ二ティーに歓迎できたら最高!
(できたらいいな...)
#comuplus)#roomD
つけてどんどんつぶやいてくれると嬉しいです
まえおき終わり
本セッションの内容・意見は登壇者が所属する
企業・ユーザーグループの意見ではなく
登壇者個人のものです
【主張】
【主張】
インターフェースの明示的な実装を状況に応じて使おう
IDragHandler
void%OnDrag(PointerEventData)というメソッドをもつ
ドラッグ検知に関連するUnityのインターフェース
これを例にまず紹介します
IDragHandlerを『普通に』実装する例using UnityEngine;using UnityEngine.EventSystems;
public class DragSample : MonoBehaviour, IDragHandler {
public void OnDrag(PointerEventData pointerData { Debug.Log ("OnDrag"); }
}
IDragHandlerを『明示的に』実装する例using UnityEngine;using UnityEngine.EventSystems;
public class DragSample : MonoBehaviour, IDragHandler {
// publicがなくなってメソッド名にインターフェース名「IDragHandler.」がついた void IDragHandler.OnDrag(PointerEventData pointerData) { Debug.Log ("OnDrag"); }
}
違いは?
インターフェースを普通に実装した場合
// DragSample型の変数でもOnDragメソッドが呼べる// dragSampleはDragSample型dragSample.OnDrag(pointerData);
インターフェースを明示的に実装した場合
// DragSample型の変数ではOnDragメソッドが呼べない// dragSample.OnDrag(pointerData); 左はコンパイルエラー
// IDragHandler型の変数に代入すれば呼べるIDragHandler handler = dragSample;handler.OnDrag(pointerData);
嬉しいのか?自分は嬉しいと思う!
インターフェースの明示的な実装をしたメソッドは
その型の変数経由で『呼べなくなってしまう』
ではなくて、
『呼ばせたくない場合に、呼べなくすることができる!』
だと思う
例1
List<T>はICollec.on<T>.IsReadOnlyを明示的な実装している
例1
List<int> numList = new List<int> {0, 1, 2, 3};// 下記はコンパイルエラー//bool isReadOnly = numList.IsReadOnly;
// ICollection<int> 経由では呼び出せるICollection<int> numCollection = numList;bool isReadOnly = numCollection.IsReadOnly;
例1
List<T>はICollec.on<T>.IsReadOnlyを明示的な実装をしている
ICollec'on<T>型の変数では有用なプロパティだけど、
List<T>の変数では呼べないほうが嬉しい
(List<T>型ならば定義的falseなのは当たり前だから、IList.IsFixedSizeも同様)
例2
ReadOnlyCollec,on<T>の
ICollec'on<T>.ClearやICollec'on<T>.Addなど明示的な実装
ReadOnlyなんだから、要素操作はさせたくない。呼ばせたくない。
ICollec'on<T>型などの変数に入れて呼んでしまった場合、NotSupportedExcep'onを投げる
例3
ISerializableを実装するクラスのGetObjectDataメソッド
GetObjectDataメソッドは、シリアル化インフラストラクチャが呼び出すもので、
他のユーザー定義クラス内などで、実装したクラスの変数を介して呼ぶものではないため
h"ps://msdn.microso/.com/ja2jp/library/ms229034(v=vs.100).aspxAより
ではさっきのUnityの例に戻って
下記のOnDragは、UnityのUIイベントシステムが呼んでくれる
public class DragSample : MonoBehaviour, IDragHandler { void IDragHandler.OnDrag(PointerEventData pointerData) { Debug.Log ("OnDrag"); }}
ポイントは、OnDragメソッドをイベントシステム以外から呼ばせたいのかどうかだと思う
UnityのUIイベントシステムが呼んでくれる
ていうかむしろ他から呼ばせたくない、限定したい
なら、インターフェースの明示的な実装をした方が良くない?
という主張
もちろんOnDragメソッドをシステム以外から、
他のクラスなどから呼び出す必要があるならば別
【主張】
インターフェースの明示的な実装を状況に応じて使おう
どう思います?ご意見募集!
「積極的な理由がない場合は、インターフェイス!メンバーの明示的な実装を避けます」@MSDNだけど
別な話ですがインターフェースの明示的な実装、よくある説明だと
「二つのインターフェースのメソッドの引数と名前が同じ時、これを使えば大丈夫ー」
という紹介が多いのですが、「List<T>のIsReadOnly」とか
「共変戻り値型がないから明示的な実装が役立つ」とかの方が
大切さが・有用さが伝わると思うのは自分だけ?
【主張】
ObjectとObject
なぜその名前にしたの?
Objectクラス
Objectクラス• System.Objectクラス
• .NET0Framework0の全クラスの基本クラスで、型階層のルート
• キーワード「object」、でクラス名を表せる
Objectクラス
Objectクラス• UnityEngine.Objectクラス
• Unityで重要なGameObjectやComponentの親クラス
• なんでそんな名前つけちゃったのまじで!?
Debug.Logというログを吐くメソッドpublic static void Log(object message, Object context);
さー、どっちがどっちだ?
Debug.Logというログを吐くメソッドpublic static void Log(object message, Object context);
第一引数がSystem.Objectで、第二引数がUnityEngine.Objectで
紛らわしいったらありゃしない
【主張】
【主張】
クラス名のかぶりをusingエイリアスディレクティブで
いいかんじにする(?)
using UnityEngine;
public class Sample : MonoBehaviour{ void Start () { // 下記はUnityEngine.Object型 Object unityEngineObject;
// 下記はSystem.Object型 object systemObject; }}
using UnityEngine;using System; // <- new!
public class Sample : MonoBehaviour{ void Start () { // コンパイルエラー // 下記はSystem.ObjectかUnityEngine.Objectか曖昧 // Object ambiguousObject;
// 下記はSystem.Object型 object systemObject; }}
ここでusingエイリアスディレクティブを使ってみる
usingエイリアスディレクティブ
名前空間または型のエイリアスを作成できる機能
using UnityEngine;using System;using UnityObject = UnityEngine.Object; // <- usingエイリアスディレクティブ
public class Sample : MonoBehaviour{ void Start () { // 下記はUnityEngine.Object
UnityObject unityEngineObject;
// 下記はSystem.Object型 object systemObject;
// 残念ながら、Objectは曖昧なままでコンパイルエラー // Object ambiguousObject; }}
うーん。正直、微妙!
実はあんまりUnityEngine.Object型、コードで書かないし
ドキュメントで読む時は多いから、このかぶりマジで混乱するからつらい
もう一例
System.RandomとUnityEngine.Random
UnityEngine.Randomにエイリアスをありっちゃありかな?
using UnityEngine;using System;using UnityRandom = UnityEngine.Random;
public class Sample : MonoBehaviour{ void Start () { float randomValue = UnityRandom.value; }}
【主張】
クラス名のかぶりをのusingエイリアスディレクティブで
いいかんじにする(?)
のは、Objectは微妙だけれどもRandomはありっちゃありかも(個人の感想です)
【意見募集】
先ほどの二つの例、どう思われますか?
「こんなのもいいよ」とかあれば教えてください!
【主張】
【主張】
FindObjectOfType<T>でコンパイルエラー?
型パラメータの制約だぜ!
UnityEngine.Object.FindObjectOfType<T>メソッド型を指定して、ゲームシーン中のその型のオブジェトを取得できる
// GameManagerは自作のクラスで、MonoBehaviourを継承しているGameManager gameManager = FindObjectOfType<GameManager> ();gameManager.StartGameLoop ();
GameManager以外のクラスからでも、GameManagerのオブジェクトを取得できる
次のコードはすべてコンパイルエラー
string str = FindObjectOfType<string> ();
// MyClassは自作クラス、UnityEngine.Objectを継承していないMyClass myClass = FindObjectOfType<MyClass> ();
// ISampleは自作インターフェースISample sample = FindObjectOfType<ISample> ();
なぜか?
FindObjectOfType<T>は
UnityEngine.Object型に型パラメータの制約がかかっているため
TはUnityEngine.Objectかそのサブクラスでないといけない
型パラメータの制約を使うとジェネリックなクラスやメソッドに、
型引数に指定・使用できる型の種類に制限を加えることができる
ところで先ほどのGameManagerはMonoBehaviourを継承しているので
継承階層を辿るとUnityEngine.Objectを継承している
自作クラスだけでなく、ビルドインの剛体運動をつかさどるRigidBodyなどいろいろ取得できる
// GameManagerは自作のクラスで、MonoBehaviourを継承しているGameManager gameManager = FindObjectOfType<GameManager> ();gameManager.StartGameLoop ();
しかしstringやMyClass(UnityEngine.Objectを継承していない)はコンパイルエラーになる!
下記はコンパイルエラーFindObjectsOfType<T>もUnityEngine.Objectで型パラメータの制約がされている
これだとTには、UnityEngine.Object・そのサブクラス以外が来る可能性がある
public class Utility { // T型でゲームオブジェクトの名前がnameなものを取得 public static T[] FindObjectsOfType<T> (string name) {
return Object.FindObjectsOfType<T> () .Where (it => it.name == name) .ToArray (); }}
下記はOK
UnityEngine.Objectで型パラメータの制約がされているので
Tには、UnityEngine.Objectかそのサブクラスしかこない
public class Utility { // T型でゲームオブジェクトの名前がnameなものを取得 public static T[] FindObjectsOfType<T> (string name) where T : UnityEngine.Object { return Object.FindObjectsOfType<T> () .Where (it => it.name == name) .ToArray (); }}
下記はコンパイルエラーGenericなメソッド内で型パラメータの制約をTに行わないと
T型のインスタンスはSystem.Objectのメソッドしか呼べない
static T Min<T>(T a, T b) {
return a.CompareTo(b) < 0 ? a : b;}
下記はOK
TはIComparableに制約されているので、
IComparableがもつCompareToを呼べる
static T Min<T>(T a, T b) { where T : IComparable return a.CompareTo(b) < 0 ? a : b;}
型パラメータの制約、大切!
【ぼやき】
FindObjectOfType<T>の型パラメータの制約について、
Unityのドキュメントに載ってないorz
というかこのオーバーロード自体が載ってない
【ぼやき】
GetComponent<T>というよく使うメソッド
以前はTに対してComponentクラスで制約がかかっていました
けど仕様が変わっていたorz
今も前もドキュメントに記載なし、バージョンによる違いの記載ももちろんなしorz
つらたん
【意見募集】
型パラメータの制約、
こんなライブラリでこんな風に使っていて面白いぞ!
とかあったら教えてください
【主張】
【主張】
演算子のオーバーロードや型変換演算子が
裏側で使われていることを理解しよう
演算子のオーバーロードユーザー定義型でも+や"などの演算子が使えるようになるやつ
演算子のオーバーロードといえばDateTime型とTimeSpan型の+と,オペレータですが
(ですよね?もっといい例あったら教えてください。)
DateTime dateTime = DateTime.Now + TimeSpan.FromSeconds (30.0);
TimeSpan duration = DateTime.Now - GetStartedTime ();
Unityでも大活躍
位置等をあらわすVector3や回転を表すQuaternionで演算子のオーバーロードが定義されている
// Quaternionは四元数。回転・姿勢等で使うQuaternion rotation = Quaternion.Euler (0, 0, 90.0F);Vector3 vectorA = new Vector3 (1.0F, 1.0F, 0.0F);Vector3 vectorB = new Vector3 (1.0F, -1.0F, 0.0F);
Vector3 addedVector = vectorA + vectorB;Vector3 scaledVector = 2.0F * vectorA;Vector3 rotatedVector = rotation * vectorB;
書籍・ウェブサイトでも乱用禁止と紹介されているこの機能
新たに自作クラスで定義するならしっかりじっくり考えて
覚悟を持ってやるべきだと自分は思います
型変換演算子
明示的に型を指定すれば型変換を行える明示的型変換と
型を指定しなくても必要になったら変換してくれる暗黙的型変換
型変換演算子といえばSystem.Decimal型ですよね?
// 多くの数値型から暗黙的型変換decimal num1 = 1;decimal num2 = 2.0F;decimal num3 = 3.0;
// 多くの数値型への明示的型変換int numA = (int)num1;float numB = (float)num2;double numC = (double)num3;
Unityでも結構使われている
Color構造体からColor32構造体への暗黙的型変換Color構造体は色を表し各成分は[0.0F,1.0F]、Color32構造体も色を表し各成分はbyte型
デザイナーから色の成分を整数でもらった時、Color32構造体は各成分をそのままかける
// 初期化が楽なColor32構造体で初期化して// 暗黙的型変換をでよく使うColorに変換Color color = new Color32(255, 128, 255, 255);
コード中で色を定義することが多いなら結構便利だと思う
Vector3構造体からVector2構造体への暗黙的型変換
// z座標は無視されるVector2 vector2d = new Vector3 (1.0F, 1.0F, 0.0F);
// z座標は0.0Fが挿入されるVector3 vector3d = new Vector2 (1.0F, 1.0F);
結構便利!だと思う。自分は。
UnityEngine.Objectからboolへの型変換
// Rigidbodyは継承階層を辿るとUnityEngine.Objectにたどり着くRigidbody rigidbody = GetComponent<Rigidbody>();
// 下記はif (rigidbody != null) { と実質同じらしいif (rigidbody) { rigidbody.AddForce(10.0F * Vector3.forward);}
これは便利っちゃ便利だと思うかもしれないけれど、
そんなにうれしくない。どう思います?
【主張】
演算子のオーバーロードや型変換演算子が
裏側で使われていることを理解しよう
自分で無計画に定義するのは良くないと思うけれど、裏側でどうなっているのか把握するのは大切!
(あとVector3の==とかも大事)
【主張】
【主張】
var禁止とか言わないでお願い!var使おうぜ!
var好きです!var良くないですか?
Unityでも大活躍
// 右辺でRigidbody型って書いてあるじゃん!
// Rigidbody rigidbody = GetComponent<Rigidbody> ();var rigidbody = GetComponent<Rigidbody> ();
// 右辺でGameManager型って書いてあるじゃん!
// GameManager gameManager = FindObjectOfType<GameManager> ();var gameManager = FindObjectOfType<GameManager> ();
適材適所でvarを使う
// 戻り値型が伝わりズラい・自明でないメソッドでvar使おうとは言わない// var ambiguousTypeObject = GetAmbiguousTypeObject ():
// こういう場合、はっきり型を明示したほうがいいと思うSampleType ambiguousTypeObject = GetAmibigaousTypeObject ():
// でも右辺から型が自明ならvar使ったほうがすっきりする!// Dictionary<string, List<LongLongTypeName>> dictionary // = new Dictionary<string, List<LongLongTypeName>> ();var dictionary = new Dictionary<string, List<LongLongTypeName>> ();
悲しいかな
『var使うな』、『var使いたくない』という方がたまにいるorz
var使いたくない人の主張1
「型がないの気持ち悪いじゃん」
「型がないの気持ち悪いじゃん」への反論
型がないのではなくて、暗黙的に型が指定されているだけ
var使いたくない人の主張2
「型書いてないと分かりずらくない?」
「型書いてないと分かりずらくない?」への反論
無理にvar使うと分かりズラいときもあるから、
そういう時は使わなくてもいいと思う
けど明らかにわかる時は禁止する理由にはならないと思う!
var使いたくない人の主張3
「List<string>をIList<string>変数に入れたいけれど、
varはList<string>型になるじゃん」
「List<string>をIList<string>変数に入れたいけれど、varはList<string>型になるじゃん」への反論1
メソッドの返り値等で、適切な抽象的な型を返すの大事だよね!
けどvarはメソッドローカルスコープ限定だし影響範囲は
そんな大きくないよね?ローカルスコープではList<T>でもよくね?
まさかとは思うけれど100行1000行なメソッド書かないよね?
「List<string>をIList<string>変数に入れたいけれど、varはList<string>型になるじゃん」への反論2
IList<string>に入れたいっていうけれど、
本当にIList<string>で扱いたいの?
string[]もIList<string>実装しているんだけれど?
【意見募集】
var禁止って言われた時のいい感じの反論
【主張】
【主張】
省略可能な引数(オプション引数)、便利!
けれど個人的にはデフォルト引数って言わないでほしい!
省略可能な引数(オプション引数)がなんなのかは割愛
個人的にはデフォルト引数って言わないでほしい!
C++ではデフォルト引数っていうんですよね?
けれどC#では省略可能な引数(オプション引数)なんだから、C#の言葉を使った方がいいと思う
別にC++をディスる意図は全くないです
「Javaだとこう言う」「C++だとこう言う」ではなくて
郷に入っては郷に従え、C#の言葉を使った方がいいと思う
他の言語の流儀を入れるときは中途半端にやらず、しっかり徹底的にやるべきだと思う
【主張】
【主張】
名前付き引数便利!
メソッド定義するときは引数名も大切に!
名前付き引数がなんなのかは割愛
以前こんな辛いことがありました
UniLinqというライブラリをつくっていた際気づいた。
LINQのCountメソッド、Func<TSource,2Boolean>型のデリゲートの引数名は
正しくは、『predicate』なのだけれど!
// 下記はコンパイルエラー// int count = numList.Count(predicate: num => num > 90);
// 引数名が仕様と違うint count = numList.Count(selector: num => num > 90);
Unityが使っている古いmonoが間違っていたorz
つらい
名前付き引数があるC#では
引数名を変えると破壊的な変更になるし
適当な引数名つけちゃダメ
【主張】
【主張】
型の規定値を理解して変なバグを回避しようぜ
次のコードは問題があるFirstOrDefaultは、引数のデリゲートが表す条件を満たす最初の要素を取得する
『満たす要素がなかった場合』がここでのポイント
Vector3 playerPosition = GetPlayerPosition();List<Vector3> enemyPositions = LoadEnemyPositions();float attakRange = 5.0F;
Vector3 targetEnemyPosition = enemyPositions .FirstOrDefault(p => Vector3.Distance(playerPosition, p) <= attakRange);
満たす要素がなかった場合、FirstOrDefaultは型の規定値を返す!
nullを返すというのは間違い!FirstOrNullじゃないし
nullは参照型の規定値で、値型の規定値はnullじゃない
型の規定値• 参照型はnull
• 構造体はすべてのメンバを規定値で初期化したもの
• intは0
• floatは0.0F
• boolはfalse
(略)
Vector3は構造体なので規定値は
全部のメンバを規定値で初期化したもの
(x,$y,$zがすべて0なもの)
条件を満たす要素が存在しなかった場合
targetEnemyPosi.onはnew0Vector3(0.0F,00.0F,00.0F)という結果になる。(nullではない)
Vector3 playerPosition = GetPlayerPosition();List<Vector3> enemyPositions = LoadEnemyPositions();float attakRange = 5.0F;
Vector3 targetEnemyPosition = enemyPositions .FirstOrDefault(p => Vector3.Distance(playerPosition, p) <= attakRange);
『正しい値が入っていると思ったら、実は規定値が入っていたためバグ!』に気をよう
【主張】
【主張】
List<T>の代わりにReadOnlyCollec,on<T>を検討しよう
(例)
PlayerクラスはList<Item>型のItemsプロパティを持つ
このItemsをクラス外部に公開したい
けど中身の書き換えなどはクラス外部でされたくない
こうすれば安心?public class Player{ /*略*/
public List<Item> Items { get; private set; }}
ではない!
ダメな例Player player = GetPlayer();
// これはできないけれど// player.Items = new List<Item>();
// 普通に外部からList<T>のメソッド経由で中身をいじれるplayer.Items.Clear();
ではどうする?
案1"防御的コピーをする
public class Player { /*略*/
private List<Item> items; public List<Item> Items { get { return new List<Item>(items); } }}
内部のリストメンバの複製を外部に公開する
けれど、これだと「クラス内のアイテムのリストのデータはいじれなよ」「防御的コピーしたよ」
ってことがぱっと見で伝わらない。Playerクラスのコード'or'ドキュメントを読まないといけない
案2"プロパティをIEnumerable<T>型にする
public class Player { /*略*/
private List<Item> items; public IEnumerable<Item> Items { get { return items; } }}
「いじれないよ」ということは伝わるけれど、「確定しているデータだよ」が伝わらない
「遅延評価される値?」「内部ではリスト・配列で保持せずDBにいちいち問い合わせてる?」
って勘違いされる可能性あり。結局Playerクラスのコードかドキュメントを読まないといけない
案3"プロパティをIReadOnlyList<T>型にする
public class Player { /*略*/ private List<Item> items; public IReadOnlyList<Item> Items { get { return items; } }}
「外部からだといじれないよ」も「遅延評価じゃなくて確定しているデータだよ」も伝わる
【悲報】だけどUnityだと使えない【致命的】
.NET%4.5で登場したインターフェースだからorz
そこでReadOnlyCollec,on<T>ですよ!
ReadOnlyCollec,on<T>
指定したリストをラップする読み取り専用のラッパーで、ラップ対象の中身が変わるとこいつも変わる
IEnumerable<T>、ICollec1on<T>、IList<T>などなどを実装している。
IList<T>.Addとか要素を変更する系のメソッドはインターフェースの明示的な実装をしていて、
インターフェース型経由でそういうのを呼び出すとNotSupportedExcep-onを投げる
案4"プロパティをReadOnlyCollec.on<T>型にする
public class Player { /*略*/ private List<Item> items; public ReadOnlyCollection<Item> Items { get { return new ReadOnlyCollection<Item>(items); } }}
もしくは、(意味は変わってくるけれど)
public class Player { /*略*/
public ReadOnlyCollection<Item> Items { get ; private set; }}
「外部からだといじれないよ」も
「遅延評価じゃなくて確定しているデータだよ」も伝わる
Unityでも使える(<(ここ重要)
【質問】
.NET%Framework%4.5以上が使える場合、
IReadOnlyList<T>ってがっつり使います?
【主張】
主張
Transformと子供オブジェクトの列挙とGetEnumerator
Transformクラスゲーム中のオブジェクトの位置・大きさ・回転を保持するクラス
オブジェクト間の親子構造も司っている
対象Transoformの子オブジェクト(のTransform)の列挙
// 対象の子供オブジェクトの名前を表示foreach(Transform childTransform in transform) { Debug.Log(childTransform.name); // 名前を表示}
なぜこれができるのか?
List<T>や配列みたいなことができるのか?
TransformがGetEnumeratorメソッドを実装していて
それが子供オブジェクトを列挙するような仕様だからですよね。
(ダックタイピングなアレ)
まとめ
別にGetEnumerator知らなくても子要素の列挙できる
インターフェースの明示的な実装や型パラメータの制約
知らなくてもゲームは作れる
けれど知っていた方がいいと思う
困った時、いろいろ解決できる
C#らしいコードがいろいろ助けてくれると思う
プログラミング言語は道具だからこそ適当でいいわけはなく
しっかりと使いこなしたい
ちゃんと勉強したいぜC#!
つっこみお待ちしてます
UnityでC#を学び始めた私の主張
@RyotaMurohoshi
2015/09/26(土)*Comm*Tech*Fes4val