それとなく学ぶ�プログラミング
もっと詳しく上級生のために
このスライドには以下の要素が含まれます
(連続で教えた方がいい内容がある)
JAVAとC#の�間違い探し
今更ですが
微妙な違いメモ
多重配列(長方形)
Java
int [][] a = new int[5][3];
a.length;
for(int i=0;i<a.length;i++){
for(int j=0;j<a[i].length;j++);
}
C#
int [,] a = new int[5,3];
a.Length;
for(int i=0;i<a.GetLength(0); i++){
for(int j=0;j<a.GetLength(1); j++);
}
多重配列(ガタガタの長さ)
C# 宣言の書き方はJavaと同じ
int[][] jaggedArray = {
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
new int[] { 11, 22 }
};
いわゆるジャグ配列は初期化すれば使える
拡張for文
Java
int[] a = new int[5];
for(int b : a){
}
C#
int[] a = new int[5];
foreach(int b in a){
}
継承とコンストラクタ
Java
public class A extend B{
int a;
public A (int i) {
a = i;
}
public A (){
this(0);
}
}
C#
public class A : B{
int a;
public A (int i) {
a = i;
}
public A () : this(0) {
}
}
getterとsetter (C#ではプロパティ)
Java
public class {
private int life = 100;
public void SetLife(int life){
this.life = life;
}
public int GetLife(){
return life;
}
}
C#
public class {
private int life = 100;
public int Life {
set { life = value; }
get { return life; }
}
}
public class {
private int life = 100;
public int Pow {
get { return life * life ; }
}
}
public class {
private int life = 100;
public int Life {
protected set { life = value ; }
get { return life ; }
} }
public class {
public int AP {
private set ;
get ;
}
}
get だけ外部
から使える
Setのカプセル化をprotected
変数を利用しない
//使用時
int powLife= Pow ;
Life = 100 ;
AP = powLife ;
変数のように
使用できる
※詳しくは後述
構造体
クラスみたいな構造しただけの構造体
定義例
public struct SampleStruct{
public int value;
public string str;
public SampleStruct(int v = 0,string s = “”){ //コンストラクタ
value = v;
str = s;
}
public override string ToString(){ //ToString関数
return value + “ : ” + str;
}
}
引数なしのコンストラクタは不可
クラスと違って値型
SampleStruct a = new SampleStruct(0,”A”);
SampleStruct b = a;
b.value = 10;
b.str = “B”;
print(“a→” + a);
print(“b→”+ b);
実行結果(Bでの変更はAに反映されない)
a→0 : A
b→10 : B
(UnityではVector3とかが構造体)
オプション引数
知識のオプション
引数の数が増減できる
struct Structer {
float time;
int mode;
Structer(float t = 0, int m) { time = t; mode = m; }
void Method(int a = 0, int b=10) { }
}
Structer str;
str = new Structer(1.5f, 0);
str.Method(10, 2);
str = new Structer();
str.Method();
値を代入しない場合のデフォルト値を設定できる
→new Vector3();で引数なしが使える
3項演算子
参考までに
簡易的なif文
if(a > 0){
print(“P”);
}
else{
print(“M”);
}
( a > 0 ? print(“P”) : print(“M”) ) ;
↓
print( ( a > 0 ? ”P” : ”M” ) ) ;
( 条件 ? 真の時の処理 : 偽の時の処理 )
? は処理の優先順位が低いため
()で囲って使うのが基本
条件次第で代入とか
//X座標次第で0か1を代入
float alpha = (transform.position.x >= 0 ? 1 : 0);
//三項演算子in三項演算子
float sign = (transform.position.x > 0 ? 1 :
(transform.position.x == 0 ? 0 : -1));
代入のすゝめ
もしtrueならtrueって、それもうtrue
Q. Debug.log(10 >= 0); どう表示される
A . true
つまり 条件 if()の中は true や false になる
ということを踏まえて↓
void DrawPics(){
if(a >= 0){
pic1.enabled = true;
pic2.enabled = true;
}
else{
pic1.enabled = false;
pic2.enabled = false;
}
}
あるところにこんな関数がありました
もし0以上ならば
画像の表示、非表示を
true,falseにする関数
void DrawPics(){
pic1.enabled = a >= 0;
pic2.enabled = a >= 0;
}
もし0以上ならば
trueを代入
void DrawPics(){
pic1.enabled = pic2.enabled = a >= 0;
}
さらに省略
void DrawPics(){
if(a >= 0){
pic1.enabled = true; Instantiate(pic1.gameObject);
}
else{
pic1.enabled = false; Destroy(pic1.gameObject);
}
}
では、条件ごとに処理が違ったら?
もし0以上ならば
画像の表示、非表示を
true,falseにして
それぞれ別の処理を
する関数
void DrawPics(){
if(pic1.enabled = a >= 0)
Instantiate(pic1.gameObject);
else Destroy(pic1.gameObject);
}
代入してif文の条件に使うとかもできる
同じ処理でも
かなりコンパクトに
なにを処理しているかを把握することが大事
※見やすさを失いすぎないように
SWITCH文
Switch文に見た目をスイッチ
Switch文・・・見た目がスッキリする以外特に…
if(a == 0){
} else if(a == 1){
} else if(a == 2){
} else {
}
switch ( a ){
case 0:
break;
case 1:
break;
case 2;
break;
default:
break;
}
if( a > 10)
比較のifは
不可
細かな場合分けに向いてます
インデントが かさばらない
列挙型 ENUM
別の記述方法を列挙します
(public) enum Animation{� Stop , Walk , Run , Jump
}
int a = 0;
Animation anim = Animation.Stop;
[Serialized]Animation = anim2;
列挙型・・・値が限定された型を作れる
Animationという型を作り
その中のどれかしか入らない
SelializeFieldで外部入力も可能
Unityは日本語にも対応
Switch文との相性がいい
Switch(anim){
case Animation.Stop:
break;
case Animation.Walk:
break;
case Animation.Run:
break;
case Animation.Jump:
break;
}
Defaultがいらない
→想定外の処理はありえない
処理が見やすい
→見返しや修正が楽
オブジェクト指向�継承
継承について継承します
継承
いいこと
class Enemy : Character
{ //コロン ↑ で継承
[Serialized] protected int HP;
protected int AP;
public int Attack{
get { return AP; }
}
protected virtual void Move() { }
}
[Serialized]も継承
継承先でoverrideさせたい場合は「virtual」で宣言だけしておく
ぽりもーふぃずむ�多態性
多態性をたたえよ
class Zombie : Enemy
クラス Player
OnCollisionEnter(Collision coll){
if(coll.gameObjct.tag == “Enemy”){
coll.GetComponent<?クラス名?>();
Damage( ??? );
}
}
//オーバーロードを利用した場合
void Damage(Zombie zom){}
void Damage(Pumpkin pum){}
void Damage(Boss bos){}
class Pumpkin : Enemy
class Boss : Enemy
クラス名の判定ができない
クラス Player
OnCollisionEnter(Collision coll){
if(coll.gameObjct.tag == “Enemy”){
Damage(coll.GetComponent<Enemy>());
}
}
//多態性を利用
void Damage(Enemy enemy){
HP -= enemy.Attack;
}
class Zombie : Enemy
class Pumpkin : Enemy
class Boss : Enemy
多態性で複数のクラスに対応
動的配列�LIST
あなたの知識にリストをadd
突然始まる
こともある
アルゴリズムを
考えてみよう
public class Node{
public Node Next;
//あとなんかint とかデータ
}
public class Node{
public Node Next;
//あとなんかint とかデータ
}
public class Node{
public Node Next;
//あとなんかint とかデータ
}
リスト・・・長さが可変長な配列
→配列のように長さが決まってない
List<クラス型> a = new List<クラス型>();
例)
List<int> data = new List<int>();
data.add(5); //一番後ろに追加
for(int i=0; I < data.Count() ;i++){
print( data[i] ); //配列と同じ使い方
}
data.insert(3,25); //3番目の後ろに追加
Vector2型だとこんな
data.add(new Vector2(2,3));
途中にデータを追加できる
1つ1つの要素の型が違ってもいいリストも作れる
GetComponent<○○>();
プロパティ
Getter Setterはもういらない
C#にGetterSetter関数はいらない
int a = 0;
public int GetA(){ return a; }
public void SetA(int A){ a = A; }
void Start(){
int b = GetA();
SetA(b);
}
int a = 0;
public int A{
get { return a; }
set { a = value; }
}
void Start(){
int b = A;
A = b;
}
片方だけprivateっていうのにも対応可
float a = 0;
public int GetA(){ return a; }
private void SetA(float A){ a = A; }
~~他クラス~~
void Start(){
float b = GetComponet<A>().GetA();
}
public float A{
get { return a; }
private set { a = value; }
}
~~他クラス~~
void Start(){
float b = GetComponet<A>(). A;
}
小さい関数だと思っていただいて
int a = 0;
public int GetAA(){
return a * a;
}
int a = 0;
public int AA{
get { return a * a; }
}
int a = 0;
private void SetA(int A){
a = A * A;
}
private int A{
set { a = value * value; }
}
そのまま変数みたいに使えたりも
public float Ab{ get; set; }
public int Bc{ get; private set; }
void Start(){
Ab = 1.5f;
Bc = Ab;
int bc = Bc;
}
ifの中を省略するためになど、割と便利
bool CanJamp{
get {
return isGround && jampTime < 0.5f
}
}
インデクサー
インデクサーってなんですかー
見た目は()が[ ]になった関数で、中身はプロパティみたいな
class Indexer {
public 型 this[引数,…] {
get;
set;
}
public 型 this[引数] {
get;
set;
}
}
Indexer index = new Indexer();
型 value = new 型();
index[引数] = value;
型 value2 = index[引数];
名前はthis
引数は型も数も任意
オーバーロードも可
Indexerを実装したクラスの変数index
変数名[引数]で使う
代入と利用はプロパティと同じ
プロパティと同様の機能
class Official { string pos; string name;}
public class OfficerList {
private List< Official > data = new List< Official >();
public string this[string key] {
set {
int i=0; for(;i < data.Count;i++)if(data[i].pos==key) data[i].name = value;
if(i==data.Count)data.Add(new Official(key,value));� }
get {
for(int i=0; i < data.Count;i++)
if(data[i].pos == key) return data[i].name;
return null;
}
} }
OfficerList dic = new OfficerList();
dic[“Leader”] = “Nekocan”;
dic[“SubLeader”] = “Wingman”;
dic[“Leader”] = “Hilljin”;
print(dic[“Leader”] dic[“SubLeader”]);
実行結果
HilljinWingman
探索に使う引数と代入に使う値で2つ1組
役職名で名前を管理する例
リスト内の役職を探索して名前を上書き
未登録の役職ならリストにデータ追加
JSON
金曜日の文字列
文字列に変換する機能
変数の名前とその中身を文字列にする
string name = “Kusa”;
int HP = 100;
float Speed = 7.5f;
一つの文字列に変換
{ name : Kusa , HP : 100 , Speed : 7.5f }
Json
クラスを文字列に変換する機能
class Character {
string name = “Kusa”;
int HP = 100;
float Speed 7.5f;
}
Character chara = new Character();
string s = JsonUtility.ToJson(chara);
Debug.log(“s = ” + s); //右上に続く
実行結果 Json形式
s = {“name”:”Kusa”,”HP”:100,”Speed”:7.5}
//変数と中身を文字列に出来る
また、文字列からクラスに戻すことも可能
Character c = JsonUtility.FromJson<Characer>(chara);
ぷれいやーぷれふす�PLAYERPREFS
保存します。データと知識。
ゲームに保存機能をつけられます
保存できるのは int型 float型 string型 の3種類
PlayerPrefs.Set〇〇(string key , 〇〇型); //keyには保存データ名を入れる
例)
PlayerPrefs.SetString(“name”, name);
PlayerPrefs.SetInt(“HP”, HP);
PlayerPrefs.SetFloat(“speed”, Speed);
PlayerPrefs.Save(); //ディスクに書き込む処理
ゲームに保存機能をつけられます
取得できるのも int型 float型 string型 の3種類
PlayerPrefs.Get〇〇(string key , default);
//keyには保存データ名を入れる
//defaultにはデータが保存されてない場合に代入する値
例)
PlayerPrefs.GetString(“name”, ”default name”);
PlayerPrefs.GetInt(“HP”, 100);
PlayerPrefs.GetFloat(“speed”, 10);
じぇいそん せぇ~ぶ�JSON SAVE
複合錬金術
Json と PlayerPrefs でクラスを保存
保存したいデータをJsonで文字列に変換
Character player = new Character();
string player_Data_Str = JsonUtility.ToJson(player);
PlayerPrefs で保存
PlayerPrefs.SetString(“Data_Of_Player”,player_Data_Str);
PlayerPrefs.Save();
保存したデータを取得する
予め保存したデータを文字列に入れる
string player_Data = PlayerPrefs.GetString(“Data_Of_Player”);
player_Dataには、Json形式の文字列が入る
{“name”:”Kusa”,”HP”:100,”Speed”:7.5}
Json形式の文字列をクラスに代入
player = JsonUtility.FromJson<Character>(player_Data);
以前セーブしたデータが player に復元される
←インデクサを利用したセーブ用クラスの例
public class SaveData {
public int num; public SaveData(int n = 0){ num = n; }
}
public class SaveManager : MonoBeaviour {
public SaveData data;
public SaveData this[string key] {
set {
PlayerPrefs.SetString(key, JsonUtility.ToJson(value));
}
get {
string json = PlayerPrefs.GetString(key, ””);
if(json.Length == 0) return new SaveData();
return JsonUtility.FromJson<SaveData>(json);
}
}
public void Save() { PlayerPrefs.Save(); } }
SaveManager saveManager ;
string saveKey = “player1” ;
SaveData saveData ;
void Start() {
saveManager = new SaveManager();
saveData = saveManager[saveKey];
}
public void Save() {
saveManager[saveKey] = saveData;
}
public void CloseMenuWindow() {
saveManager.Save();
}
「key」の保存データがない場合「””」が代入される
コルーチン�IENUMERATOR
中断します。関数の処理と ついでに思考。
コルーチン・・・処理を中断→再開
IEnumerator Timer(float t){
print(“0”);
yield return WaitForSeconds (t);
print(“Waited : ” + t);
}
引数に5を入れた場合の実行結果
0
Waited : 5
5秒後に
表示される
引数の秒数だけ待機する
関数
(ちなみに)次のフレームで再開
yield return null;
・クセが強い
・返り値が利用出来なくなる
・使いこなせば便利
・C#独自の処理
・関数ごとにタイマーを作れる
→イベントに対するタイマーと
して重宝される
ラムダ式を用いた複数OR演算を可能にする関数
何個でもイコール判定先輩
何個でもOR判定できる
事例) 複数のタグに対して処理したい場合など
void OnTriggerEnter(Collider other){
if(other.tag == “A” || other.tag == “B” || other.tag == “C”)
↓
if(other.tag.AnyEquale(“A”,”B”,”C”)) //カンマ区切りでOR演算が出来る
}
参考文献
【C#】1つのオブジェクトが複数のオブジェクトのいずれかと等しいか判定する拡張メソッド - コガネブログ
かきかた(要コピペ)
どこでもいいので以下のクラスを記述する
using System.Linq; //必須
//非ジェネリックで静的なクラス(関数名は自由でいい)
public static class GenericExtensions
{
public static bool AnyEquale<E>(this E self, params E[ ] data)
{
return data.Any( c => c.Equals(self) );
}
}
間違ったFOR文の�正しい使い方
プレゼン for For
√(ルート)を1行で書ける
int r = Random.Range(1,101);
int i ;
for (i = 1 ; i * i < r ; i++); //※正確にはこの後 i--;しないといけない
正確な値を求めるなら
float I = 0;
float prevI = ram = Random.Range(1,101);
for( I = 1;( I = ( I + ram / I ) / 2) != prevI ; prevI = I );
乱数に制限を
-2~2以外の乱数が必要なとき
int ram;
for (ram = 0; Abs(ram) < 2 ; ram = Random.Range(-10,10) );
使うタイミングはそうそうない
乱数調整
ランダムじゃない乱数
ランダムに網羅してほしい時って�あるじゃん?
→袋の中からボールを取り出していくのと同じ概念
1
2
3
2
1
3
1
3
1
網羅した乱数
かきかたの例(一番シンプルな例)
public GameObject[ ] Blocks = null; //インスペクタでプレハブを代入
List<GameObject> list = new List<GameObject>(); //袋の役目
void InstRandom(){ //リストが空なら全部追加↓
if(list.Count == 0) foreach(GameObject obj in Blocks) list.Add(obj);
int ram = Random.Range(0 , list.Count);
Instantiate(list[ram]); // list の ram 番目を生成
list.RemoveAt(ram); // list から ram 番目の要素を削除
}
エディタ拡張
見やすさの可能性を拡張します
エディタ拡張
→なんかちょっと見やすくするやつ(ゲームに影響なし)
[SerializeField] private int HP; //privateでも外部入力を可能に
[SerializeField,Tooltip(“説明”)] int AP; //簡易的な説明文の追加
[SerializeField ,Range(-10,10)] //スライダーを表示
float Speed 0;
[SerializeField ,Tooltip(“A”),Range(0,5)] int a; //カンマ区切りで複数も可能
PublicならSerializeField
なしでも可
より高度なエディタ拡張
パラメーター修飾子
あたいも値を参照したい
パラメーター修飾子
関数の引数「intやfloatや構造体」などの値型を参照渡しができる
(返り値を使わずに結果を出力する)(関数内で値を入力する)
初期値なし
初期値あり
初期値あり、値の変更不可(参照渡しにすることでメモリ削減で高速化)
https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/out-parameter-modifier
https://www.atmarkit.co.jp/ait/articles/1804/25/news021.html
struct Str {
float s,a,b,c,f;
//コンストラクタ 略
}
void ParamMethod(out int age,
ref float height, in Str str) {
age = 10;
height += 1.5f;
print(“str.a:”+str.a);
}
Str str = new Str(2,3,4,1,0);
int Age;
float Height = 160f;
ParamMethod(out Age, ref Height, in str);
print(“Age:”+Age);
print(“Height:”+Height);
実行結果
str.a:2
Age:10
Height:161.5
命名規則
君の名は。
名前は名詞、処理は動詞
効率化の話
効率と意識の向上を
疑問に思いませんか?transform
Q.
Rigidbody などのコンポーネントは
GetConponent<>() で取得するのに
なぜ transform は使わなくていい?
A.実は毎回やっているから
Unity側で毎回GetComponentレベルの処理をしていた
→頻繁に使うなら保持して負荷軽減するのもアリ
(今更気にするほど負荷が高いわけでもない)
いつも通りtransformを使う例
new Transform transform;
void Start() {
transform = GetComponent<Transform>();
}
オブジェクトプール
Destroyせずにとっておく手法
→Destroy()とInstantiate()の回数が減り、負荷が減る
次ページ 記述例
Transform pool; //オブジェクトを保存する空オブジェクトのtransform
void Start(){
pool = new GameObject(“名前”).transform;
}
void GetObject(GameObject obj , Vector3 pos , Quaternion qua){
foreach(Transform t in pool) {
if( ! t.gameObject.activeSelf){ //オブジェが非アクティブなら使い回し
t.SetPositionAndRotation(pos,qua); t.gameObject.SetActive(true);
} //位置と回転を設定後、アクティブにする
} //非アクティブなオブジェクトがないなら生成
Instantiate(obj , pos , qua , pool);//生成と同時にpoolを親に設定
} //ギリギリ入りきってよかった
※オブジェクトプールはデータを保存するため
メモリを圧迫します
大量にプールしすぎても重くなるので注意
「スクリプトの最適化 – Unity マニュアル」
https://docs.unity3d.com/ja/current/Manual/MobileOptimizationPracticalScriptingOptimizations.html
transformが実は重いって告白
計算量を減らす
ベクトルの長さを得る「magnitude」→ √(ルート)を使うため重い
→sqrMagnitudeを使う
→ルートする前の値なので、比較対象を2乗して同等の比較が行える
例)
if( (target.position – transform.position).magnitude < 50)
↓
if( (target.position – transform.position).sqrMagnitude < 50 * 50)
同等の比較になる
FixedUpdate()の利用
FixedUpdate ・・・ 定期的に呼ばれる関数
物理演算するならこの関数内に記述するのが良い
呼ばれる頻度は 0.2f秒 で、Updateの時と同じ記述で問題ないが
時間計測の変数が違う
Time.FixedDeltaTime
関数を使わないという提案
void Method(int a) ←引数にメモリを使用する
同時に大量にメソッドを使用するとメモリの圧迫になり重くなる
再帰なんて使わない → for文の方が軽い
Mathf.Abs(X) → (X>0 ? X : -X)
Mathf.Sign(X) → (X>0 ? 1 : -1)
Mathf.Pow(X,2) → (X * X)
プログラマの話
最優秀不健康賞
寝ないでください
明日が納期ですって!?
→寝ないでください
→とりあえず、システムの完成だけを考えてください
明日までに機能の追加をしたい!?
→寝ないでください
→不眠による思考力の低下を加味した上で、それは可能か適切 であるか、判断してください
寝てください
眠い状態で考えても無駄な時間を過ごします
→下手な考え眠るに同じ ということわざもあるし
→早く寝て、朝一番でプログラムの見直しをした方が効率的です
(経験談)
どうせ考え事してる時に力尽きるだけです
寝かしましょう
作ったゲームやプログラミング、レポート等を一旦忘れて
翌日見直してみましょう
明らかに修正すべき点が見つかりやすいです
あと、製作者も寝かしましょう