線型代数学
ベクトル
ベクトルとは、一般的には「大きさと向きをもつ量」であり「矢印で表すことのできる量」である。

2次元のベクトルは2つの成分(x, y)があり、3次元の場合は3の成分(x, y, z)がある。Unityでは Vector2 と Vector3 の構造体があり、それぞれのベクトルを作成ができる。
Vector2 vec2D = new Vector2(vx, vy); // vxとvyはfloatである
Vector3 vec3D = new Vector3(vx, vy, vz); // vzもfloatである
ゲームの中で、ベクトルをよく使うものである。例えば、3D空間で、キャラクターの速度が「左向き:秒速2メートル、前方:秒速10メートル」であるとしたら…
// 3D空間だと、x:左右、y:上下、z:前後
Vector3 velocity = new Vector3(-2, 0, 10);
また、ゲームの場合は、向きと量はもちろんそうであるが、その他に「位置」もベクトルで表現する。
// プレーヤーはシーンの中心点から前方100メートルにいる
Vector3 playerPosition = new Vector3(0, 0, 100);
演算
やはり、ベクトルは重要なので、ベクトルの演算を学ばないといけない。
加算
2つのベクトルを足すことができる。その結果は各成分の足し算である:
2次元の場合:
|
|
3次元の場合:
|
|
Unityでは「+」の演算子で2つのベクトルを足すことができる
Vector3 v = new Vector3(vx, vy, vz); // vx, vyとvzは何かのfloat型の数値
Vector3 w = new Vector3(wx, wy, wz); // wx, wyとwzは何かのfloat型の数値
Vector3 vw = v + w;
幾何学的に、足し算は2つの矢印を連続し、1つ目のベクトルの始点から2つ目のベクトルの終点までに新しいベクトルを作成するイメージ:

減算
引き算は同じ形で行う:
Vector3 v = new Vector3(vx, vy, vz); // vx, vyとvzは何かのfloat型の数値
Vector3 w = new Vector3(wx, wy, wz); // wx, wyとwzは何かのfloat型の数値
Vector3 vw = v - w;
幾何学的には、引くベクトルの向きを反転してから連続に繋がる
練習
加算と減算を使い、2つのオブジェクトを相対的に移動
- シーンに2つの3Dゲームオブジェクトを追加し、距離的に放してください。
- 1つ目の名前は「Control」
- 2つ目の名前は「Follower」
- スクリプト「VectorTest」を作成し…
- Follower と Control の相対位置を求める(減算)
- Control をシーン内で動かしたら、Follower が移動を真似する(加算)
スクリプト:VectorTest
// ベクトル演算の練習」
public class VectorTest : MonoBehaviour
{
// 移動すべきのオブジェクト
[SerializeField] private GameObject control;
// controlを追跡するオブジェクト
[SerializeField] private GameObject follower;
// controlとfollowerの相対位置
private Vector3 relativePosition;
private void Start()
{
// 初期位置を取得
var v = control.transform.position;
var w = follower.transform.position;
// 相対位置を計算(減算で)
relativePosition = w - v;
}
private void Update()
{
// followerの位置が、controlの位置 + 相対位置
follower.transform.position =
control.transform.position + relativePosition;
}
}
実数との掛け算、割り算
ベクトルを実数との掛け算ができる。各成分との掛け算になる:
|
|
Unityでは、普通の掛け算演算子「*」で計算できる:
Vector3 v = new Vector3(vx, vy, vz); // vx, vyとvzは何かのfloat型の数値
Vector3 nv = v * n; // nは何かの実数(float)である
練習
先ほど作った「VectorTest」を更新し、相対位置の「倍率(scale)」を Inspector で変えられるようにする。
スクリプト:VectorTest
// ベクトル演算の練習」
public class VectorTest : MonoBehaviour
{
// 移動すべきのオブジェクト
[SerializeField] private GameObject control;
// controlを追跡するオブジェクト
[SerializeField] private GameObject follower;
// 相対位置の倍率
[SerializeField] private float scale = 1;
// controlとfollowerの相対位置
private Vector3 relativePosition;
private void Start()
{
// 初期位置を取得
var v = control.transform.position;
var w = follower.transform.position;
// 相対位置を計算(減算で)
relativePosition = w - v;
}
private void Update()
{
// 倍率を適用する
Vector3 scalePos = relativePosition * scale;
// followerの位置が、controlの位置 + 相対位置
follower.transform.position =
control.transform.position + scalePos;
}
}
ベクトルの長さ
場合により、ベクトルの長さを求めたいときもある。これには「ピタゴラスの定理」(三平方の定理)を利用する。ピタゴラスの定理を思い出しましょう。直角三角形があれば…

c 辺の長さは:
|
|
つまり…
|
|
これはベクトルの成分とよく似ているので:

これができる!
|
|
ここで || v || はベクトルの「ノルム」といわれ、ベクトルの長さを表している。Unityでは、Vectorの便利な「magnitude」がある:
Vector2 p = new Vector2(3, 4);
float len = p.magnitude; // len は p の長さ(5)である
単位ベクトル
ベクトルの長さを「1」にすると、そのベクトルは単位円の中に入り、各成分がsinとcosを一致させることができる。その他にも、ベクトルの長さを調整する前に、まず長さを1に戻さないといけないが必要である。その場合は単位ベクトルを求めないといけない。

単位ベクトルを求めるのは、ベクトルをノルムで割るだけ:
|
|
Unityでは「normalized」というプロパティーで単位ベクトルを求めることができる。
// 任意の長さのベクトル v があるとして:
Vector3 v = new Vector3(30, 12, -15);
// uはvと同じ向き(方向)であるが、長さは「1」である(単位ベクトル)
Vector3 u = v.normalized;
ベクトル同士の”掛け算”(内積と外積)
ベクトル同士の掛け算はできないが、2つの特別な関数がある。内積(dot product)と外積(cross product)。それぞれは限られた使い方があるが、必要な時にとても便利である。
内積
内積の定義は以下の通りになる:

|
|
ある2つのベクトル「v」と「w」の内積は、それぞれのベクトルのノルム(長さ)とその2つのベクトルの間の角度のコサインの掛け算である。答えは実数である。
これは何に便利かというと、2つのベクトルの間の角度を求めたいなら、内積を使えば良いでしょう。まず、vとwを単位ベクトルにすると、長さは1になるので、式は単純になる:
|
|
そして、角度を求めるのは:
|
|
Unityで内積の計算を実装されている:
Vector3 v = new Vector3(vx, vy, vz);
Vector3 w = new Vector3(wx, wy, wz);
float dot = Vector3.Dot(v, w); // 内積
// 角度を求めたいなら
float radians = Mathf.Acos(dot);
// そして
float angle = radians * Mathf.Rad2Deg;
練習
警備員の視野を利用するゲーム
- 警備員は「視野」があり、前方±角度しか見えない
- 泥棒が視野の中に入ってれば→発見!
警備員のスクリプト
// 警備員の前に泥棒がいるかいないか確認する
public class GuardMan : MonoBehaviour
{
// 泥棒への向きが欲しいので、泥棒が必要
[SerializeField]
private GameObject thief;
// 視野(度)
[SerializeField]
private float viewAngle;
// 色を変えるため
private MeshRenderer rend;
private void Start()
{
rend = GetComponent<MeshRenderer>();
}
private void Update()
{
// 警備員の前向きのベクトル
Vector3 v = transform.forward; // 青い矢印のこと
// 泥棒への向き
Vector3 w = thief.transform.position - transform.position;
// 単位ベクトルにする
w.Normalize();
// 内積で角度のコサインを求める
float dot = Vector3.Dot(v, w);
// 内積からラジアン角度を求める
float radian = Mathf.Acos(dot);
// 度に変換
float angle = radian * Mathf.Rad2Deg;
// 警備員と泥棒の角度が視野よりも小さければ、発見!
if (angle < viewAngle)
rend.material.color = Color.red;
// 見えない
else
rend.material.color = Color.green;
}
}
外積
2つのベクトル外積の式は以下の通りである:
|
|
そして、Unityでも計算を実装されている:
Vector3 v = new Vector3(vx, vy, vz);
Vector3 w = new Vector3(wx, wy, wz);
Vector3 cross = Vector3.Cross(v, w); // 外積の結果は新しいベクトルである
式よりも、外積の幾何学的な意味は重要。2つの3Dベクトル「v」と「w」は平面を定義すし、それで「v」と「w」の外積の結果は:
- 新しい3Dベクトルである
- このベクトル「v」と「w」で定義された平面を直交する
- また、長さは
|
|

従って、2つのベクトルがあれば、そのベクトルで作られた「面」を直交するベクトルを求めることができる。面の「法線ベクトル」、または3D空間の傾きの方向を求めることができる!
行列
数を縦と横に矩形状に配列したものである。横に並んだ一筋を行(row)、縦に並んだ一筋を列(column)と呼ぶ。例えば、下記のような行列:
|
|
は2つの行と3つの列によって構成されているため、(2,3)型または2×3型の行列と呼ばれる。Unity は行列のクラスがないため、自分で作ってみましょう。
練習
「Matrix」クラスを実装して:
- サイズ(行数、列数)を設定できるようにする
- 各要素を(行:r、 列 c)
float Get(int r, int c)で取得できるようにする。void Set(int r, int c, float value)で代入できるようにする。
string ToString();のメソッドを実装し、中身を文字列として返すようにする
最後に、TestMatrix スクリプトを作成し、操作を確認してください。
補足:C#の1次元の配列を2次元の行列へ
行列は「行」と「列」2次元があるが、C#の配列は1次元しかない。2次元の行列を作成するため、以下の変換を行わないといけない:int i = r * cols + c;
ここで、
- i = 1次元(配列)の指数
- r = 行列の「行」
- c = 行列の「列」
- cols = 行列の列数
例えば、4×3の行列で「 列:3、行:2」の要素を求めたい。ならば…
- c = 2
- r = 1
- cols = 4
- i = r * cols + c = 1 * 4 + 2 = 6
Matrix クラス
// 行列クラス
public class Matrix
{
// 列の数
private int rows;
// 行の数
private int cols;
// 行列の数値(配列)
private float[] data;
// コンストラクタ(インスタンスする時に呼び出す)
// rows: 行の数
// cols: 列の数
public Matrix(int rows, int cols)
{
this.cols = cols;
this.rows = rows;
data = new float[cols * rows];
}
// 数値を読む
// r: 行
// c: 列
// 行列の数値を返す
public float Get(int r, int c)
{
// 配列の位置(指数)を計算
int i = r * cols + c;;
return data[i];
}
// 数値を代入
// r: 行
// c: 列
// value:代入したい数値
public void Set(int r, int c, float value)
{
// 配列の位置(指数)を計算
int i = r * cols + c;
// 代入
data[i] = value;
}
// 文字列にする
public override string ToString()
{
// 表示用の文字列
string s = "";
// 1行ずつ
for (int j = 0; j < rows; j++)
{
// 1列ずつ
for (int i = 0; i < cols; i++)
{
// 数値を取得
float v = Get(j, i);
// 数値を文字列にして、連結する
s += $"{v:0.00} ";
}
// 改行
s += "\n";
}
// 返す
return s;
}
}
TestMatrix スクリプト
// 行列を確認するため
public class MatrixTest : MonoBehaviour
{
private void Start()
{
PrintMatrix();
}
private void PrintMatrix()
{
Matrix m = new Matrix(2, 3);
m.Set(0, 0, 1);
m.Set(0, 1, 9);
m.Set(0, 2, -13);
m.Set(1, 0, 20);
m.Set(1, 1, 5);
m.Set(1, 2, -6);
// ToString() を実装したので、自動的に文字列に変換される
Debug.Log(m);
}
}
足し算(加法)
2つの行列「A」と「B」、「A」と「B」のサイズが「m × n」である場合、A+Bの結果行列「C」は、各要素の足し算である。
したがって、「C」も「m × n」の行列である。
練習
「Matrix」クラスを拡張して:
Matrix Sum (Matrix b) を追加し、加法を実装する
自分と渡された行列「b」を足し、新しい行列を返す
行数、または列数が合わない場合は Debug.LogError でエラーを表示し、null を返す
TestMatrix を更新し、以下の2つの行列を足してください。
Sum メソッド
// 行列の加算
public Matrix Sum(Matrix b)
{
// 同じサイズかを確認する
if (cols != b.cols || rows != b.rows)
{
Debug.LogError("行列のサイズが合わない");
return null;
}
// 結果の行列
Matrix result = new Matrix(rows, cols);
// 1行ずつ
for (int r = 0; r < rows; r++)
{
// 1列ずつ
for (int c = 0; c < cols; c++)
{
float va = Get(r, c); // 自分の数値を求める
float vb = b.Get(r, c); // 相手の数値を求める
result.Set(r, c, va + vb); // 足して、代入
}
}
// 返すだけ
return result;
}
TestSum メソッド
void TestSum()
{
// 3行2列の行列を作成
Matrix a = new Matrix(3, 2);
// 数値を代入
a.Set(0, 0, 4);
a.Set(0, 1, 6);
a.Set(1, 0, -1);
a.Set(1, 1, 0);
a.Set(2, 0, 8);
a.Set(2, 1, -3);
Debug.Log(a);
Matrix b = new Matrix(3, 2);
b.Set(0, 0, 5);
b.Set(0, 1, 1);
b.Set(1, 0, -2);
b.Set(1, 1, 2);
b.Set(2, 0, 2);
b.Set(2, 1, 3);
Debug.Log(b);
// 行列の加算
Matrix c = a.Sum(b);
Debug.Log(c);
}

















