このサイトは、只今WEB業界で活躍中のデザイナー、プログラマーの方々の情報を集めたweb統合情報サイトです。

Archives Details

ゲーム開発に必要な基本数学入門

Rust

2025.08.15

どもです。

ゲーム開発では、見た目の派手さやストーリーも大事ですが、裏側では数学が多くの表現や挙動を支えています。この記事では、特にゲームの基礎を形作る「線形補間」「三角関数」「行列」「回転」の基本と活用をまとめます。

1. 線形補間(Linear Interpolation)

概要

2つの値の間を一定割合で補う計算。
Lerp(Linear Interpolation)とも呼ばれます。

数式
Lerp(a, b, t) = a + (b - a) * t
  • a … 開始値

  • b … 終了値

  • t … 0.0~1.0 の割合

活用例
  • 物体の位置を滑らかに移動

  • アニメーションの中間フレーム生成

  • 色のフェードイン・フェードアウト

2. 三角関数(Trigonometry)

Angles(角度)
  • 度(degrees):0°〜360°

  • ラジアン(radians):0〜2π
    多くのゲームエンジンはラジアンを使用

変換式:

radians = degrees × π / 180
degrees = radians × 180 / π

 

Vectors(ベクトル)

ベクトルは位置や方向を表す基本要素。
2Dなら (x, y)、3Dなら (x, y, z) で表現。

  • 正規化(normalize):長さを1にすることで方向だけを保持

  • 内積(dot):角度や向きの類似度を求める

  • 外積(cross):3Dで垂直方向のベクトルを求める

活用例
  • 弾丸の進行方向計算

  • キャラクターが向く方向の計算

  • カメラの回転制御

3. 行列(Matrices)

概要

行列は位置・回転・拡縮をひとまとめに変換する数学的ツール。
ゲームでは特に「変換行列(Transformation Matrix)」がよく使われます。

代表的な行列
  • 平行移動行列:オブジェクトを移動

  • 回転行列:オブジェクトを回転

  • スケーリング行列:拡大・縮小

活用例
  • モデルの座標変換(ローカル座標→ワールド座標)

  • カメラ視点の変換

  • スプライトの回転やズーム

4. 回転(Rotations)

回転の表現方法は複数あり、それぞれ一長一短があります。

ジンバルロック(Gimbal Lock)
  • 原因:3軸のうち2軸が同じ方向を向き、自由度が失われる

  • 発生例:Euler角での回転中に特定の角度になると、回転が思った通りにならない

Euler Angles(オイラー角)
  • XYZ軸の回転を順番に適用

  • 人間に分かりやすい

  • ジンバルロックの影響を受けやすい

Matrices(行列による回転)
  • 安定している

  • 他の変換(移動・拡縮)とまとめて扱える

  • 直感的な編集はやや難しい

Quaternions(クォータニオン)
  • 構成:1つのスカラー値 + 3つの虚数成分(x, y, z)

  • ジンバルロックが起きない

  • 回転の補間(Slerp)が滑らか

  • 数式は複雑だが、ゲームエンジン側でサポートされていることが多い

活用例
  • 3Dキャラクターやカメラの回転

  • なめらかな方向転換

  • 物理シミュレーションの回転制御

まとめ

  • 線形補間:数値や位置をスムーズに変化させる

  • 三角関数:角度・方向・距離の計算

  • 行列:移動・回転・拡縮をまとめて処理

  • 回転の表現:Euler角・行列・クォータニオンの使い分けが重要

ゲーム開発では、これらの数学が組み合わさって自然で魅力的な動きを作り出します。
それでは、これらをRustで実装していきましょう。

 

セットアップ(Cargo)

# Cargo.toml
[package]
name = "game-math-basics"
version = "0.1.0"
edition = "2021"

[dependencies]
glam = "0.29"

 

1) 線形補間(Lerp)

素の Rust 実装
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
    a + (b - a) * t
}

#[test]
fn test_lerp() {
    assert!((lerp(0.0, 10.0, 0.25) - 2.5).abs() < 1e-6);
}


glam での利用例(色や位置の補間)
use glam::{Vec3, Vec4};

fn lerp_vec3(a: Vec3, b: Vec3, t: f32) -> Vec3 {
    a + (b - a) * t
}

fn main() {
    let p0 = Vec3::new(0.0, 0.0, 0.0);
    let p1 = Vec3::new(10.0, 5.0, 0.0);
    let p = lerp_vec3(p0, p1, 0.3);
    println!("Position: {p:?}"); // (3.0, 1.5, 0.0)

    let c0 = Vec4::new(0.0, 0.0, 0.0, 1.0); // 黒
    let c1 = Vec4::new(1.0, 1.0, 1.0, 1.0); // 白
    let c  = c0.lerp(c1, 0.5);
    println!("Color: {c:?}"); // (0.5, 0.5, 0.5, 1.0)
}

 

2) 三角関数(Angles / Vectors)

角度(度↔ラジアン)
pub fn deg2rad(deg: f32) -> f32 { deg.to_radians() }
pub fn rad2deg(rad: f32) -> f32 { rad.to_degrees() }

fn main() {
    println!("180° = {} rad", deg2rad(180.0));
    println!("π   = {} °", rad2deg(std::f32::consts::PI));
}

 

ベクトルの基本(正規化・内積・外積・射影)
use glam::Vec3;

fn main() {
    let v = Vec3::new(3.0, 4.0, 0.0);
    let n = v.normalize();
    println!("len={} -> normalize={:?}", v.length(), n); // len=5 → (0.6, 0.8, 0)

    let a = Vec3::new(1.0, 0.0, 0.0);
    let b = Vec3::new(0.0, 1.0, 0.0);
    println!("dot(a,b)={}", a.dot(b)); // 0(直交)
    println!("cross(a,b)={:?}", a.cross(b)); // (0,0,1)

    // a 方向への b の射影
    let proj = a * (b.dot(a) / a.length_squared());
    println!("projection of b on a = {:?}", proj); // (0,0,0)
}

 

よくある用途

  • 角度の計算:cosθ = a·b / (|a||b|) → 方向の類似度

  • 2D/3D の向き操作、視線ベクトル、移動入力の正規化など

 

3) 行列(Matrices)

2D:平行移動+回転+拡縮の合成
use glam::{Mat3, Vec2};

fn main() {
    let translation = Mat3::from_translation(Vec2::new(100.0, 50.0));
    let rotation    = Mat3::from_angle(std::f32::consts::FRAC_PI_4); // 45°
    let scale       = Mat3::from_scale(Vec2::new(2.0, 1.5));

    // 注意:適用順を意識(右から左へ適用される)
    let model = translation * rotation * scale;

    let p_local = Vec2::new(1.0, 0.0);
    let p_world = model.transform_point2(p_local);
    println!("2D transformed = {:?}", p_world);
}
 
3D:ローカル→ワールド、ビュー、プロジェクション
use glam::{Mat4, Vec3, Quat};

fn main() {
    // モデル行列(平行移動×回転×拡縮)
    let t = Mat4::from_translation(Vec3::new(0.0, 1.0, 5.0));
    let r = Mat4::from_quat(Quat::from_rotation_y(std::f32::consts::PI * 0.25)); // Y軸45°
    let s = Mat4::from_scale(Vec3::splat(2.0));
    let model = t * r * s;

    // ビュー行列(カメラ)
    let eye    = Vec3::new(0.0, 2.0, 8.0);
    let target = Vec3::ZERO;
    let up     = Vec3::Y;
    let view   = Mat4::look_at_rh(eye, target, up);

    // 透視投影
    let proj = Mat4::perspective_rh_gl(45f32.to_radians(), 16.0/9.0, 0.1, 1000.0);

    // 最終クリップ空間
    let mvp = proj * view * model;
    println!("MVP=\n{mvp:?}");
}

 

4) 回転(Rotations)

ジンバルロックとは
  • XYZ の オイラー角で回転を順に掛けると、特定の姿勢(例:ピッチ=±90°)で 2 軸が重なり、自由度が 3→2 に落ちてしまう現象。

  • カメラやキャラの急なねじれ、意図しない回転制限の原因に。

オイラー角(Euler Angles)
use glam::{Mat3, Vec3};

fn euler_xyz_to_mat3(euler_deg: Vec3) -> Mat3 {
    let (rx, ry, rz) = (
        euler_deg.x.to_radians(),
        euler_deg.y.to_radians(),
        euler_deg.z.to_radians(),
    );
    let rxm = Mat3::from_rotation_x(rx);
    let rym = Mat3::from_rotation_y(ry);
    let rzm = Mat3::from_rotation_z(rz);
    // XYZ の順で適用
    rzm * rym * rxm
}

fn main() {
    let m = euler_xyz_to_mat3(Vec3::new(90.0, 0.0, 0.0)); // ピッチ90°
    println!("{m:?}");
}

実運用では、内部表現はクォータニオン、UI 入力はオイラー角で受ける(必要に応じて変換)という構成が扱いやすいです。

行列での回転
  • 変換の合成(拡縮・移動)と一緒に扱える

  • 数値誤差の蓄積回避には正規直交化(再直交化)を挟むことも

use glam::{Mat3, Vec3};
fn rotate_vec(m: Mat3, v: Vec3) -> Vec3 { m * v }
クォータニオン(Quaternions)
  • 構成:w(スカラー)+ x,y,z(虚数成分)

  • ジンバルロックを回避、回転補間(Slerp)が滑らか

  • ゲームエンジン/ライブラリでの標準的な回転表現

use glam::{Quat, Vec3, Mat3, Mat4};

fn main() {
    // 1) 軸と角度から作る
    let axis = Vec3::Y; // Y 軸
    let angle = 45f32.to_radians();
    let q = Quat::from_axis_angle(axis, angle);

    // 2) オイラー角から作る(順序に注意)
    let q_euler = Quat::from_euler(glam::EulerRot::XYZ, 0.0, angle, 0.0);

    // 3) ベクトルを回す
    let v = Vec3::X;
    let v_rot = q * v; // Quat * Vec3 で回転
    println!("rotated vec = {:?}", v_rot);

    // 4) 行列へ変換(必要に応じて)
    let rot3: Mat3 = Mat3::from_quat(q);
    let rot4: Mat4 = Mat4::from_quat(q);
    println!("Mat3 = {rot3:?}, Mat4 = {rot4:?}");
}

Slerp(球面線形補間)で滑らかな回転

use glam::{Quat, Vec3};

fn main() {
    let q0 = Quat::from_axis_angle(Vec3::Y, 0.0);
    let q1 = Quat::from_axis_angle(Vec3::Y, 180f32.to_radians());

    for i in 0..=5 {
        let t = i as f32 / 5.0;
        let q = q0.slerp(q1, t);
        let forward = q * Vec3::Z; // 前方向ベクトル
        println!("t={t:.2} forward={forward:?}");
    }
}

Look Rotation(方向ベクトルから回転を作る)

use glam::{Quat, Vec3};

/// forward と up から、姿勢(クォータニオン)を構築
fn look_rotation(forward: Vec3, up: Vec3) -> Quat {
    Quat::from_mat3(&glam::Mat3::look_to_rh(Vec3::ZERO, forward.normalize(), up.normalize()))
}

fn main() {
    let target_dir = Vec3::new(1.0, 0.5, 0.0);
    let q = look_rotation(target_dir, Vec3::Y);
    println!("look quat = {q:?}");
}

 

まとめ(使い分けの指針)

  • Lerp:数値や位置・色の滑らかな遷移に(0..1 の t を制御)

  • 三角関数/ベクトル:角度・方向・射影・正規化は頻出

  • 行列:移動・回転・拡縮の合成、座標空間の変換(MVP)

  • 回転表現

    • 入力/UI:オイラー角(わかりやすい)

    • 内部/演算:クォータニオン(ジンバルロック回避、補間が強い)

    • 変換パイプライン:行列(最終的な合成に便利)

 

おまけ:最小デモ(1ファイルに集約)

// src/main.rs
use glam::{Vec2, Vec3, Vec4, Mat3, Mat4, Quat};

fn lerp(a: f32, b: f32, t: f32) -> f32 { a + (b - a) * t }

fn main() {
    // --- Lerp
    println!("lerp(0, 10, 0.3) = {}", lerp(0.0, 10.0, 0.3));

    // --- Angles
    println!("90° = {} rad", 90f32.to_radians());

    // --- Vectors
    let v = Vec3::new(3.0, 4.0, 0.0);
    println!("len={}, norm={:?}", v.length(), v.normalize());
    println!("dot(X, Y)={}", Vec3::X.dot(Vec3::Y));
    println!("cross(X, Y)={:?}", Vec3::X.cross(Vec3::Y));

    // --- Matrices (2D)
    let m2 = Mat3::from_translation(Vec2::new(100.0, 50.0))
        * Mat3::from_angle(std::f32::consts::FRAC_PI_4)
        * Mat3::from_scale(Vec2::new(2.0, 1.5));
    println!("2D point -> {:?}", m2.transform_point2(Vec2::new(1.0, 0.0)));

    // --- Matrices (3D)
    let model = Mat4::from_translation(Vec3::new(0.0, 1.0, 5.0))
        * Mat4::from_quat(Quat::from_rotation_y(std::f32::consts::PI * 0.25))
        * Mat4::from_scale(Vec3::splat(2.0));
    let view  = Mat4::look_at_rh(Vec3::new(0.0, 2.0, 8.0), Vec3::ZERO, Vec3::Y);
    let proj  = Mat4::perspective_rh_gl(45f32.to_radians(), 16.0/9.0, 0.1, 1000.0);
    let _mvp  = proj * view * model;

    // --- Quaternions
    let q0 = Quat::from_axis_angle(Vec3::Y, 0.0);
    let q1 = Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI);
    let mid = q0.slerp(q1, 0.5);
    let forward = mid * Vec3::Z;
    println!("slerp forward = {:?}", forward);

    // --- Color lerp
    let c0 = Vec4::new(0.0, 0.0, 0.0, 1.0);
    let c1 = Vec4::new(1.0, 1.0, 1.0, 1.0);
    println!("color = {:?}", c0.lerp(c1, 0.25));
}

 

というのも、ChatGPTが一瞬で吐き出せる世の中になったわけだ。
 
画像も含め(行列おかしくない?)。非常に便利だが、末恐ろしい世の中になったもんだ。
そう思った今日このごろでした。
 

Comment

Related Article

ゲーム開発に必要な基本数学入門

2025.08.15

Godot 4 & Rust 始め方

2025.06.19

Rustでつくる ふっかつのじゅもん

2025.04.29

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その5

2025.04.14

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その4

2025.04.10

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その3

2025.04.08

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その2

2025.04.07

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その1

2025.04.05

keyring-rsで、Macのキーチェーンに登録する。

2025.04.04

RustとWebAssemblyによるゲーム開発 Webpack5対応

2025.03.27

CATEGORY LIST

LATEST NEWS

ゲーム開発に必要な基本数学入門

Rust

2025.08.15

Godot 4 & WebAssemblyで様々なデータフォーマットを処理

Godot

2025.07.06

Godot 4 & WebAssemblyで、Hello WebAssembly! - godot-wasm

Godot

2025.06.21

Godot 4 & Rust 始め方

Rust

2025.06.19

ご、ごめん。。今はFlutterの気分じゃないんだ。。

Flutter

2025.05.30

AIのために働け。AI リーダブルコーディングな未来。

AI・Bot・algorithm

2025.05.21

Rustでつくる ふっかつのじゅもん

Rust

2025.04.29

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その5

Rust

2025.04.14

う、動くぞ! Mac mini Apple M4 Pro で PS3ソフトを遊ぶ。RPCS3 Mac版を起動

Game

2025.04.12

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その4

Rust

2025.04.10

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その3

Rust

2025.04.08

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その2

Rust

2025.04.07

RANKING

Follow

SPONSOR

現在、掲載募集中です。



Links

About Us

WEBデザイナーの、WEBデザイナーによる、WEBデザイナーの為のサイト。「みんなで書こう!」と仲間を募ってみたが、結局書くのは自分だけとなってしまいました。日々のメモを綴っていきます。

Entry Profile

Graphical FrontEnd Engineer
- Daisuke Takayama

MAD CITY 北九州市で生まれ育つ。20代はバンド活動に明け暮れ、ふと「webデザイナーになりたい。」と思い、デジタルハリウッド福岡校入学。卒業後、数々の賞を受賞、web業界をざわつかせる。
現在、主に、ゲーム制作中心に港区六本木界隈で活動中。

FOLLOW US