C#
あくまでこの記事では、Unityをベースとした前提で話す。 Visual Studioのホットキーなど
ctrl + R + R
変数の名前などを一括で変更する
ctrl + shift + s
全ファイル保存
ctrl + .
コンストラクタなど不足分がある場合に生成する
選択後、alt + enter
→extract method(メソッドとして抽出) で、選択範囲を関数化する
命名規則
※Unityで使うC#の命名や、オブジェクトの命名など。
C#
Classファイル名
アッパーで書くが、_を挟むこともある。
自分の学習している教材では少なくとも混在している。
Player.cs, Player_IdleState.cs
メソッド名
大文字から始める。
code:c#
public virtual void Enter()
{
...
enter()とはしない。
変数名
小文字でキャメルで繋げる。
player, stateMachine, ....
定数名
大文字ではじめて、キャメルで繋げる。
private const int FirstComboIndex = 1;
Unity
フォルダの名称
大文字で書けばよい。
Animation, InputSystem
オブジェクト
大文字で書けばよい。
Player, Animator, AttackPoint
アニメーター関連
Controller
Playerと大文字でよい
Clip(.anim)
ローワーキャメル。playerIdle, playerMove
パラメータなど
ローワーキャメル。idle, isGrounded, yVelocity, xVelocity
C#
変数の定義
code:c#
using UnityEngine;
public class Player : MonoBehaviour
{
public string playerName = "Bob the hero"; // This box (variable) called "playerName" holds the text "Bob".
public int age = 25; // This box (variable) called "age" holds the number 25.
// floatを定義するときは、2.5fのように、fをつける
public float moveSpeed = 2.5f; // This box (variable) called "moveSpeed" holds the decimal number 2.5.
public bool gameOver = true; // This box (variable) called "gameOver" holds the value true.
public Rigidbody2D rb;
private void Start()
{
}
}
クラスなど
code:c#
using UnityEngine;
public class Example : MonoBehaviour
{
private void Awake()
{
Debug.Log("Awake called");
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
Debug.Log("Start called");
}
// Update is called once per frame
// ex: Skill cooldowns, checking for input, etc.
void Update()
{
Debug.Log("Update called");
}
// FixedUpdate is called on a fixed timer, independent of frame rate
// Defalt is 0.02 seconds (50 times per second)
// It can be changed in Edit > Project Settings > Time > Fixed Timestep
private void FixedUpdate()
{
Debug.Log("FixedUpdate called");
}
}
}
Start, UpdateなどはMonoBehaviorに用意されたメソッド
MonoBehaviorは、ゲームの基礎機能が用意されたクラス
実際にこの部分をコメントアウトすると、Hierarchy > Inspectorのコンポーネントとして追加することができない
なので、Unityの要素として紐づける場合は基本的に必要となる
ライフサイクル
Update(), FixedUpdate()の違い
フレーム単位で呼ぶか、時間単位で呼ぶか。
例えば全体の挙動をフレームで設定すると、240fpsの環境と60fpsの環境で違いが出る。
そのため、そのあたりを設定できるゲームなら秒単位、つまりFixedUpdate()で定義するべき。
コンテキストメニュー
code:c#
private void Flip()
{
transform.Rotate(0, 180, 0);
facingRight = !facingRight; // 呼ばれるたび反転させる
}
https://scrapbox.io/files/68f194ea2acd03b5bbcea8d1.png
こんな感じで、関数を呼べるようになる
SerializeField
エディタ上で値の設定などができるようにする
デバッグ時につけて、問題なければ外すこともある
Header
インスペクタでグループに見出しをつけるための装飾。
https://scrapbox.io/files/68f1a77b0c514bd82693f7cf.png
こういうこと
ほかにも[]の付属するものはあるが、インスペクタ上で便利にするための設定であることが多い。
衝突判定
code:c#
private void OnDrawGizmos()
{
Gizmos.DrawLine(transform.position, transform.position + new Vector3(0, -groundCheckDistance));
}
Gizmos
デバッグ用の描画ツール。これで地面への距離などを測る。ゲームには影響しない。
DrawLine
transform.position
キャラの現在位置(transformのインスペクタの値)
new Vector3(0, -groundCheckDistance)
下方向に、groundCheckDistance分だけ進んだベクトル
に対して、直線を書くという意味になる
code:c#
private void HandleCollision()
{
isGrounded = Physics2D.Raycast(transform.position, Vector2.down, groundCheckDistance, whatIsGround);
}
これも同じで、GizmosのようなRaycastを指定の方向に放ち、特定のレイヤーに当たったら感知させるということ
キャラの現在位置から
下向きに
groundCheckDistanceの距離だけ。
whatIsGroundで指定したレイヤーに当たったら。
今回の場合、Unity側で
PlayerのWhat is GroundにGroundのレイヤーを割り当てる(ないので作った)
https://scrapbox.io/files/68f1acf8902b3e254cdca89b.png
GroundというレイヤーをPlatformに割り当てて、何がGroundなのかをはっきりさせる
(Platformに指定のRaycastが当たると、isGroundedがtrueになるようになった
https://scrapbox.io/files/68f1acba8585d06e31834fc0.png
という処理が実現できている。
敵へのダメージ判定
code:player.cs
public void DamageEnemies()
{
Collider2D[] enemyColliders;
enemyColliders = Physics2D.OverlapCircleAll(attackPoint.position, attackRadius, whatIsEnemy);
foreach (Collider2D enemy in enemyColliders)
{
enemy.GetComponent<Enemy>().TakeDamage();
}
}
ダメージを与える関数
これは、Animationに紐づけたスクリプトのPlayerAnimationEvents.csから呼ぶ。
中心座標から, Radius(直径)に、whatIsEnemy(レイヤー)を対象に触れたものを配列に入れている
enemy.GetComponent<Enemy>().TakeDamage();
触れたColliderコンポーネントの、Enemyスクリプトを取得し、TakeDamage()を呼んでいる
code:PlayerAnimationEvents.cs
public class PlayerAnimationEvents : MonoBehaviour
{
private Player player;
private void Awake()
{
player = GetComponentInParent<Player>();
}
public void DamageEnemies() => player.DamageEnemies();
public void DamageEnemies() => player.DamageEnemies();という形で呼び出す
このメソッドを、攻撃アニメーションの攻撃判定を出したいタイミングでイベントとして呼べばよい
https://scrapbox.io/files/68f21b362dea63d02a7c4157.png
という感じ
クールダウンタイムの考え方
1つめ
code:c#
private void Update()
{
timer -= Time.deltaTime;
if(timer < 0 && sr.color != Color.white)
sr.color = Color.white;
}
private void UpdateTimer() => timer = redColorDuration;
public void TakeDamage()
{
Debug.Log(gameObject.name + " took some damage!");
sr.color = Color.red;
timer = redColorDuration;
}
Update()
Time.deltaTime
直前Fと現在のFで経過した時間を返す
なので、フレームが経過するほどtimer変数に時間が蓄積されていくことになる動きになる
今回、timerが0より下で、対象オブジェクトが白でなかったら城にするという処理
UpdateTimer()
コンテクストメニュー用
(Unityの画面でリセットする用のメソッドということ)
呼ばれたとき、timerの値をその変数の値に変える
ようするに、timerがリセットされて、またTime.deltaTimeが減算されていく
TakeDamage()
呼ばれたとき、色を赤に変える
そして、timerの値をその変数の値に変える
つまり
redColorDurationが5だったとき、敵に当たったら5秒間赤色になる
減算されて0秒を切ると白色になるということ
2つめ
code:c#
private void Update()
{
currentTimeInGame = Time.time; // アプリケーションを起動してからの現在のフレームを秒単位で出す
if (currentTimeInGame > lastTimeWasDamaged + redColorDuration)
{
if (sr.color != Color.white)
sr.color = Color.white;
}
}
public void TakeDamage()
{
sr.color = Color.red;
lastTimeWasDamaged = Time.time;
}
同じ考え方
クールダウン3秒のスキルがあったとしたら、
現在は8秒 > 6秒時点でダメージ + クールダウン3秒
この場合は9秒時点までクールダウンが必要なので、まだ動作させないというif文になる
コルーチン(Coroutine)
code:c#
using System.Collections;
using UnityEngine;
public class Test : MonoBehaviour
{
private Coroutine sayMessageCo;
private IEnumerator SayMessageCo()
{
Debug.Log("Hello world.");
yield return new WaitForSeconds(2);
Debug.Log("Hey. Are you still there?");
yield return new WaitForSeconds(3);
Debug.Log("HEY I M TALIKNG TO YOU");
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
if (sayMessageCo != null)
StopCoroutine(sayMessageCo);
sayMessageCo = StartCoroutine(SayMessageCo());
}
}
}
この処理の場合、
Fを押すと、コルーチン関数sayMessageCoが始まる
すでに押されていたら、ストップしたのち実行
そうでなければ、ただ実行(StartCoroutine
特徴
変数に格納できる
private Coroutine sayMessageCo
定義するとき
private IEnumerator SayMessageCo()
呼び出すとき
通常のメソッドのようには呼べない。
StartCoroutine(SayMessageCo())というように呼び出す。
⭐
MonoBehaviorから継承する必要があるので、それ以外のクラスからは呼べないので注意。
OOPの話
Object Oriented Programming
PHPなどにも通づる話なので、気になったとこだけ書く。
継承Inheritanceの話
Entityという基底クラスを作り、そこにFlip()やhealthなどの要素を持たせる
そこからPlayer, Enemyなどに小分けしていく作り方がベースになる
code:c#
// 親
private float moveSpeed;
// 継承先
public class Player : Entity
{
private void Update()
{
moveSpeed = 10; // 子でも書き換えようと思ったら、privateでなくprotectedで定義
// メソッドなども同様。
}
多様性 Polymorphism
親クラスのメソッドを子クラスで子クラスなりの振る舞いにさせるということ
C#のoverrideはこんな感じで書ける
code:c#
protected virtual void Attack()
{
Debug.Log(enemyName + " attacks!");
}
----------
protected override void Attack()
{
base.Attack();
StealMoney();
}
カプセル化 Encapsulation
例えば、Player.cs側のデバッグログなどでEnemyのprotected string enemyNameを参照したいとき
code:c#
enemy.GetComponent(<Enemy>().enemyName
としても、protectedなので、保護レベルの問題上参照できない(PlayerはEnemyを継承しているわけではないので)
ここで、getter / setterを用意してやるという話になる。
code:c#
public string GetEnemyName()
{
return enemyName;
}
-----------
Enemy enemyScript = enemy.GetComponent<Enemy>();
enemyScript.TakeDamage();
string enemyName = enemyScript.GetEnemyName();
Debug.Log("I damaged enemy : " + enemyName);
これは、もっと明快に書ける
code:Enemy.cs
public string enemyName { get; private set; }
とすると
ほかのスクリプトからもenemyNameは呼び出せる(getできる)
ただし、 enemy.GetComponent<Enemy>.enemyName = "aaa"というようにsetはできない
エラー1
Library\PackageCache\com.unity.visualscripting@6279e2b7c485\Runtime\VisualScripting.Flow\Unit.cs(30,29): error CS0246: The type or namespace name 'ConnectionCollection<,,>' could not be found (are you missing a using directive or an assembly reference?)
Unityを無理やり落とした(PCがとまった)のが原因っぽい。パッケージの破損
Package Managerを開く
Window → Package Management → Package Manager
今回、VisualScriptingなのでそれをdelete
Unity再起動
必要なら、safe modeで起動
Package Managerを開く
同じようにVisualScriptingをインストール
動作確認で治った(と思う)
エラー2
NullReferenceException: Object reference not set to an instance of an object
Player.DamageEnemies () (at Assets/Player.cs:60)
PlayerAnimationEvents.DamageEnemies () (at Assets/PlayerAnimationEvents.cs:12)
enemy.GetComponent<Cooldown_Example>().TakeDamage();の処理
Enemyのオブジェクトに、Cooldown_Exampleのコンポーネントが設定されていないというエラー