Ursina Engine 3次元ゲームエンジン基礎

概要

本教材は、Python 用 3 次元ゲームエンジン Ursina Engine を用いた 10 項目のオンライン演習で構成される。座標系、カメラ、メッシュ・テクスチャ、ライティング、エンティティの階層、入力処理、アニメーションと物理、簡易ゲーム、データ可視化を体験的に扱い、3 次元コンピュータグラフィックス(3DCG)とゲームエンジンの基本概念を習得することを目的とする。各演習は《手順》《ヒント》《考察ポイント》で構成する。

目次



1. はじめに


予備知識


Ursina Engine は Python で 3 次元コンピュータグラフィックスとゲームを扱うためのエンジンである。少ないコードで 3D 空間にオブジェクトを配置し、動かし、ユーザー入力に反応させることができる。

用語解説


Ursina プログラムの基本構造は次のとおりである。


┌─────────────────────────────┐
│ 1. モジュールのインポート      │
│    from ursina import *      │
└─────────────────────────────┘
           ↓
┌─────────────────────────────┐
│ 2. アプリケーションの初期化    │
│    app = Ursina()           │
└─────────────────────────────┘
           ↓
┌─────────────────────────────┐
│ 3. エンティティの作成         │
│    Entity(...)              │
└─────────────────────────────┘
           ↓
┌─────────────────────────────┐
│ 4. アプリケーションの実行     │
│    app.run()                │
└─────────────────────────────┘

前準備

Python 3.12 のインストール

Pythonのインストールを行い、Pythonのプログラムを実行する環境を整える。扱う環境は、Windows搭載パソコンである。金子研究室では、Python 3.12.10を推奨する。

[Windows での Python 3.12 のインストール手順を見るには、ここをクリック]

Windows での Python 3.12 のインストール

以下のいずれかの方法でPython 3.12をインストールする。Pythonがインストール済みの場合、この手順は不要である。

方法 1:winget によるインストール

インストールコマンドの実行方法

管理者権限コマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして、コマンド全体をコマンドプロンプトにコピー&ペーストする。

--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動するとPATHが反映される。

REM Python 3.12 をシステム領域にインストール
winget install --id Python.Python.3.12 -e --scope machine --silent --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_pip=1 Include_launcher=1 InstallLauncherAllUsers=1 TargetDir=\"C:\Program Files\Python312\""

REM Python と Scripts を PATH 先頭に追加
powershell -NoProfile -Command "$p='C:\Program Files\Python312'; $s=\"$p\Scripts\"; $c=[Environment]::GetEnvironmentVariable('Path','Machine'); if((Test-Path $p) -and (';'+$c+';' -notlike \"*;$p;*\") -and (';'+$c+';' -notlike \"*;$s;*\")){[Environment]::SetEnvironmentVariable('Path',\"$p;$s;$c\",'Machine')}"

方法 2:インストーラーによるインストール

  1. Python公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンからWindows用インストーラーをダウンロードする。
  2. ダウンロードしたインストーラーを実行する。
  3. 初期画面の下部に表示される「Add python.exe to PATH」にチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから python コマンドを実行できない。
  4. 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。

インストールの確認

コマンドプロンプトで以下を実行する。

python --version

バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。

Python の開発環境 Visual Studio Code のインストールと Python 用の設定

Python の開発環境Visual Studio Code(プログラムを編集するソフトウェア。以下、VS Code)を整える。

[Windows での Visual Studio Code のインストールと Python 用の設定手順を見るには、ここをクリック]

Windows での Visual Studio Code のインストールと Python 用の設定手順

1. VS Code と拡張機能のインストール

以下のコマンドにより,既存の VS Code を削除し,全ユーザー共有の設定で再インストールしたうえで,拡張機能(VS Code に機能を追加するソフトウェア)をまとめて導入する.

インストールコマンドの実行方法

管理者権限コマンドプロンプトを起動する(手順:Windows キーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして,コマンド全体をコマンドプロンプトにコピー&ペーストする。

インストールコマンド


REM ============================================================
REM Microsoft Visual Studio Code
REM ============================================================
winget uninstall -e --id Microsoft.VisualStudioCode --silent --disable-interactivity --accept-source-agreements
rmdir /s /q C:\ProgramData\vscode-extensions 2>nul
rmdir /s /q "%APPDATA%\Code" 2>nul
rmdir /s /q "%USERPROFILE%\.vscode" 2>nul
rmdir /s /q "%LOCALAPPDATA%\Microsoft\vscode-update" 2>nul

REM VS Code をシステム領域に新規インストール
winget install --scope machine --id Microsoft.VisualStudioCode -e --silent --accept-source-agreements --accept-package-agreements

REM 全ユーザー共有の拡張機能フォルダ
mkdir C:\ProgramData\vscode-extensions 2>nul
icacls "C:\ProgramData\vscode-extensions" /grant "Everyone:(OI)(CI)M" /T

REM スタートメニューのショートカットを --extensions-dir 付きで再作成
rmdir /s /q "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code" 2>nul
del "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk" 2>nul
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); $lnk.TargetPath='C:\Program Files\Microsoft VS Code\Code.exe'; $lnk.Arguments='--extensions-dir \"C:\ProgramData\vscode-extensions\"'; $lnk.Save()"
REM ショートカットの検証
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); Write-Host 'TargetPath:' $lnk.TargetPath; Write-Host 'Arguments:' $lnk.Arguments"

REM ファイル / フォルダ右クリックの「Code で開く」を登録
reg add "HKLM\SOFTWARE\Classes\*\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\Background\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%V\"" /f

REM --extensions-dir 付きで起動する code.cmd ラッパを作成
REM (%* を echo で書くと対話的 cmd で失われるため、PowerShell で [char]37+'*' を書き出す)
powershell -NoProfile -Command "$pct=[char]37; $q=[char]34; $c='@echo off'+[char]13+[char]10+$q+'C:\Program Files\Microsoft VS Code\bin\code.cmd'+$q+' --extensions-dir '+$q+'C:\ProgramData\vscode-extensions'+$q+' '+$pct+'*'+[char]13+[char]10; [IO.File]::WriteAllText('C:\ProgramData\vscode-extensions\vscode.cmd',$c,[Text.Encoding]::ASCII)"

REM 拡張機能のインストール
set "CODE=C:\Program Files\Microsoft VS Code\bin\code.cmd"
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot-chat
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.python
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.vscode-pylance
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.debugpy
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension MS-CEINTL.vscode-language-pack-ja
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension saoudrizwan.claude-dev
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension rust-lang.rust-analyzer
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension tamasfe.even-better-toml
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension anthropic.claude-code
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension almenon.arepl
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --list-extensions --show-versions
echo === セットアップ完了 ===

2. Python インタプリタの選択

同一マシンに複数の Python がインストールされている場合,VS Code で使用する Python 本体(インタプリタ:Python プログラムを解釈・実行するソフトウェア)を選択する必要がある.

  1. コマンドパレット(コマンド名で機能を呼び出す VS Code の入力欄)を開く(Ctrl+Shift+P
  2. Python: Select Interpreter と入力する
  3. 表示される一覧から,使用する Python(例:C:\Program Files\Python312\python.exe)を選択する.

Python プログラム実行手順

[Windows での Python プログラム実行手順を見るには、ここをクリック]

Windows での Python 実行手順(Visual Studio Codeを使用)

プログラムファイルの作成と保存

  1. 左サイドバーの「エクスプローラー」アイコン(Ctrl+Shift+E)をクリックする
  2. 「NO FOLDER OPENED」(作業対象フォルダが未選択の状態)と表示される場合は,「Open Folder」をクリックし,プログラムを保存するフォルダを選択する

    続いて「フォルダを信用するか」を確認する画面(フォルダ内のコードを実行してよいか確認する VS Code の仕組み)が表示されるので,チェックして Yes を選択する

  3. フォルダ名の右側に表示される「新しいファイル」アイコンをクリックする
  4. ファイル名(例:aitask.py.ファイル名は何でも良い)を入力し Enter を押す.拡張子は .py(Python ファイルを示す拡張子)とする
  5. 実行したいコードを選択し,Ctrl+C でコピーする.VS Code のエディタ領域に Ctrl+V で貼り付ける
  6. Ctrl+S で保存する

プログラムの実行

  1. エディタ右上の三角形「▷」アイコン(Run Python File:現在開いている Python ファイルを実行するボタン)をクリックする.または,エディタ上で右クリックし「ターミナルで Python ファイルを実行」を選択する
  2. VS Code 下部のターミナル(コマンドの入出力を表示する画面)に,実行結果(print 関数の出力等)が表示される
  3. tkinter(Python 標準の GUI ライブラリ)のファイル選択ダイアログを使うプログラムを実行した場合は,ダイアログが開くので対象画像を選択する
  4. VS Code 下部のターミナルで実行結果を確認する.OpenCV ウィンドウ(OpenCV が画像を表示するために開く専用ウィンドウ)が開いた場合はそちらも確認する.OpenCV ウィンドウは,マウスクリックでウィンドウをアクティブ(操作対象の状態)にしてからキーを押すと終了する

Ursina のインストール

10 個の演習はすべて Ursina Engine を使う。Ursina は Python 用の 3 次元ゲームエンジンである。インストールは 1 回だけ行えばよい。

管理者権限コマンドプロンプトを起動する (手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。起動したコマンドプロンプトに次を入力して実行する。

pip install --no-user ursina

演習1. Ursina プログラムの実行

各演習のコードを実行する方法は 2 通りある。どちらでも動作する。

方法A:Visual Studio Code で実行する。Visual Studio Code がインストール・設定済みであれば、本ページのコードを編集画面にコピー&ペーストし、そのまま実行する。

方法B:ファイルに保存して実行する。メモ帳などのテキストエディタを開き、本ページのコードを貼り付ける。a.py のようなファイル名で保存する。コマンドプロンプトでそのファイルがあるフォルダに移動し、次を入力して実行する。

python a.py

演習1-1:最小プログラムの実行

《手順》

  1. 下記のコードを上記のいずれかの方法で実行する。
  2. 立方体が回転していることを確認する。ウィンドウを閉じるには、Alt+F4 キーを押す。
from ursina import *

# アプリケーションの初期化
app = Ursina()

# 回転する立方体
cube = Entity(
    model='cube',
    color=color.orange,
    position=(0, 0, 5)
)

# 毎フレーム実行される関数
def update():
    cube.rotation_y += 50 * time.dt
    cube.rotation_x += 30 * time.dt

# アプリケーションの実行
app.run()
実行結果:オレンジ色の立方体が画面中央で回転する。

《ヒント》

《考察ポイント》


演習1-2:シーンの構成要素を確認する

《手順》

  1. 下記のコードを実行し、地面・複数オブジェクト・テキストが同時に表示されることを確認する。
  2. カメラの位置や色相(色合い)の値を変えて再実行し、見え方の違いを観察する。
from ursina import *

app = Ursina()

# 地面
ground = Entity(
    model='plane',
    scale=(10, 1, 10),
    color=color.green,
    texture='grass'
)

# 複数のオブジェクト
for i in range(3):
    Entity(
        model='cube',
        color=color.hsv(i * 120, 1, 1),
        position=(i * 2 - 2, 0.5, 3)
    )

# テキスト表示
Text(
    text='Ursina Engine Test',
    position=(-0.5, 0.4),
    scale=2
)

camera.position = (0, 5, -8)
camera.look_at((0, 0, 3))

app.run()

実行結果:緑の地面の上に 3 つの異なる色の立方体が並び、画面上部にテキストが表示される。

《ヒント》

《考察ポイント》


3D座標系とトランスフォーム(位置、回転、スケール)


予備知識


3 次元空間における点の位置は、互いに直交する X 軸、Y 軸、Z 軸の 3 つの数値の組 (x, y, z) で一意に表される。これを直交座標系という。


用語解説


Ursina は Y 軸が上、Z 軸が前(画面奥)を向く左手座標系を採用する(Unity と同じ規約)。


        Y (上)
        |
        |
        +---- Z (前)
       /
      /
     X (右)

正の方向用途
X 軸左右の位置
Y 軸高さ
Z 軸前(画面奥)前後の位置

演習2:位置・回転・スケールを操作する

《手順》

  1. 下記のコードを実行し、立方体の位置・回転・スケールを確認する。
  2. positionrotation_yscale の値をそれぞれ別個に変更し、変化を観察する。
  3. camera.position も変えて、見え方の違いを比較する。
from ursina import *

app = Ursina()

# エンティティの作成と配置
box = Entity(
    model='cube',
    color=color.orange,
    position=(0, 1, 5)  # X, Y, Z座標
)

# 回転の設定
box.rotation_y = 45  # Y軸周りに45度回転

# スケールの設定
box.scale = (2, 1, 1)  # X方向に2倍

# 相対移動
box.position += Vec3(2, 0, 0)  # 右に2単位移動

# カメラ設定
camera.position = (5, 3, -10)
camera.look_at(box)

app.run()

実行結果:立方体が右に 2 単位、前方 5 単位、高さ 1 の位置に配置され、Y 軸周りに 45 度回転し、X 方向に 2 倍に拡大されて表示される。

《ヒント》

《考察ポイント》


カメラとビューポート


予備知識


用語解説


演習3:一人称視点でシーンを歩く

《手順》

  1. 下記のコードを実行する。
  2. WASD キーで移動、マウスで視点回転を行い、空間を歩き回る。
  3. ウィンドウを閉じるには、Alt+F4 キーを押す。
from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController

app = Ursina()

# テスト用のオブジェクト配置
for i in range(5):
    Entity(
        model='cube',
        color=color.random_color(),
        position=(i * 3, 1, 0),
        collider='box'
    )

# 地面(プレイヤーの初期位置が縁に来ないよう十分な大きさを確保する)
ground = Entity(
    model='plane',
    scale=(40, 1, 40),
    color=color.gray,
    collider='box'
)

# 一人称視点コントローラー(初期位置はコンストラクタで指定し,生成時の接地補正を機能させる)
player = FirstPersonController(position=(0, 2, -10))

app.run()

実行結果:WASD キーでカメラを移動、マウスで視点を回転できる。FirstPersonController がカメラ制御を提供する。

《ヒント》

《考察ポイント》


メッシュ、マテリアル、テクスチャ


予備知識


用語解説


Ursina に組み込まれている基本モデルは次のとおりである。


モデル名形状用途
'cube'立方体建物、箱、障害物
'sphere'球体ボール、惑星、収集アイテム
'plane'平面地面、壁、床
'cylinder'円柱柱、木、タワー
'quad'四角形看板、パネル、UI

演習4:テクスチャと色の組み合わせ

《手順》

  1. 下記のコードを実行する。
  2. テクスチャ付き立方体・色付き球体・テクスチャ付き地面の見え方を確認する。
  3. 球体に texture='white_cube' を追加して、color と texture を併用したとき(テクスチャの上から色が乗算される様子)の見え方を確かめる。
from ursina import *

app = Ursina()

# テクスチャ付き立方体
textured_cube = Entity(
    model='cube',
    texture='white_cube',
    position=(-3, 1, 0)
)

# 色付き球体
colored_sphere = Entity(
    model='sphere',
    color=color.orange,
    position=(0, 1, 0)
)

# 回転アニメーション
def update():
    textured_cube.rotation_y += 30 * time.dt
    colored_sphere.rotation_x += 50 * time.dt

# 地面
Entity(
    model='plane',
    texture='grass',
    scale=(10, 1, 10)
)

camera.position = (0, 5, -10)
camera.look_at((0, 1, 0))

app.run()

実行結果:テクスチャ付きの立方体、色付きの球体、テクスチャ付きの平面が表示される。立方体が回転しながらテクスチャの効果を確認できる。

《ヒント》

《考察ポイント》


ライティングとシェーディング


予備知識


用語解説


光源の種類特徴用途
AmbientLight全方向を均一に照らす全体の明るさ確保
DirectionalLight平行光線(太陽光)主光源
PointLight点から全方向に放射電球、松明

演習5:環境光と指向性光源の効果

《手順》

  1. 下記のコードを実行する。
  2. AmbientLightcolor 値を color.rgba32(50, 50, 50, 255)color.rgba32(200, 200, 200, 255) に変えて違いを確認する(4 つの数値は順に R・G・B・A、各 0〜255。値が大きいほど明るい)。
  3. sun.look_at(Vec3(...)) の方向ベクトル(光が進む向き)を変え、影の付き方を比較する。
from ursina import *
from ursina.shaders import lit_with_shadows_shader

app = Ursina()

# 以降に作成するエンティティに、光源の影響を受けるシェーダーを既定で適用する
Entity.default_shader = lit_with_shadows_shader

# 環境光(0〜255の整数値はrgba32で指定する)
AmbientLight(color=color.rgba32(100, 100, 100, 255))

# 指向性光源
sun = DirectionalLight()
sun.look_at(Vec3(1, -1, 1))

# 地面
Entity(
    model='plane',
    scale=(20, 1, 20),
    color=color.gray,
    texture='white_cube'
)

# オブジェクトの配置
for i in range(5):
    Entity(
        model='sphere',
        position=(i * 3 - 6, 1, 0),
        color=color.random_color()
    )

camera.position = (0, 10, -20)
camera.look_at((0, 0, 0))

app.run()

実行結果:環境光で全体が明るくなり、指向性光源が陰影を作る。複数の球体がライティングの効果で立体的に見える。

《ヒント》

《考察ポイント》


エンティティ(Entity)の生成と制御


予備知識


用語解説


階層構造の例:

vehicle (親)
  |
  +-- body (車体)
  |
  +-- wheel_fl (前左車輪)
  |
  +-- wheel_fr (前右車輪)
  |
  +-- wheel_rl (後左車輪)
  |
  +-- wheel_rr (後右車輪)

親を移動 → 全ての子も移動
子を移動 → 親は影響を受けない

演習6:親子関係を使った車両モデル

《手順》

  1. 下記のコードを実行し、車体と 4 輪が一体で前進することを確認する。
  2. 4 輪の 1 つだけを parent=vehicle から外し、再実行する。挙動の違いを観察する。
  3. vehicle.rotation_yupdate() 内で変化させ、車体ごとの回転を試す。
from ursina import *

app = Ursina()

# 親エンティティ(車体)
vehicle = Entity()

# 車体本体
body = Entity(
    model='cube',
    color=color.red,
    scale=(2, 1, 3),
    parent=vehicle
)

# 車輪の作成(4つ)
wheel_positions = [
    (-1, -0.5, 1),   # 前左
    (1, -0.5, 1),    # 前右
    (-1, -0.5, -1),  # 後左
    (1, -0.5, -1)    # 後右
]

for pos in wheel_positions:
    Entity(
        model='sphere',
        color=color.black,
        scale=0.5,
        position=pos,
        parent=vehicle
    )

# 親を移動すると全ての子も移動する
def update():
    vehicle.z += time.dt * 3
    if vehicle.z > 10:
        vehicle.z = -10

camera.position = (0, 5, -15)
camera.look_at(vehicle)

app.run()

実行結果:車体と 4 つの車輪が一体となって前方に移動する。車輪の位置は車体からの相対位置で管理されている。

《ヒント》

《考察ポイント》


入力処理(キーボード、マウス)


予備知識


Ursina では、押し続けの入力を held_keys、押した瞬間の入力を input() 関数で扱う。


入力方法用途
held_keys継続的な入力(押し続ける)移動、回転
input() 関数単発の入力(押した瞬間)ジャンプ、攻撃

キー記述用途
W/A/S/D'w', 'a', 's', 'd'移動
スペース'space'ジャンプ
マウス左'left mouse down'選択、攻撃
ESC'escape'ポーズメニューなど、スクリプト側で処理を定義したときの終了・メニュー用キー

演習7:移動とジャンプの実装

《手順》

  1. 下記のコードを実行する。
  2. WASD で移動、Space でジャンプの動作を確認する。
  3. jump_force = 5 の値(ジャンプ初速度)や重力 -20(重力加速度)を変更し、跳躍と落下の挙動の違いを観察する。
from ursina import *

app = Ursina()

# プレイヤー
player = Entity(
    model='cube',
    color=color.azure,
    position=(0, 1, 0)
)

# 地面
Entity(
    model='plane',
    scale=(20, 1, 20),
    color=color.green
)

# 移動速度とジャンプ
speed = 5
jump_force = 0
is_jumping = False

def input(key):
    global jump_force, is_jumping
    if key == 'space' and not is_jumping:
        jump_force = 5
        is_jumping = True

def update():
    global jump_force, is_jumping

    # キーボードによる移動
    if held_keys['w']:
        player.z += speed * time.dt
    if held_keys['s']:
        player.z -= speed * time.dt
    if held_keys['a']:
        player.x -= speed * time.dt
    if held_keys['d']:
        player.x += speed * time.dt

    # ジャンプと重力
    jump_force += -20 * time.dt
    player.y += jump_force * time.dt

    if player.y <= 1:
        player.y = 1
        jump_force = 0
        is_jumping = False

camera.position = (0, 5, -15)
camera.look_at(player)

app.run()

実行結果:WASD キーでプレイヤーを移動、スペースキーでジャンプできる。重力により落下する。

《ヒント》

《考察ポイント》


アニメーションと物理演算


予備知識


用語解説


物理シミュレーションの基本的な流れは次のとおりである(dt はデルタ時間)。

1. 加速度を速度に加算
   velocity += acceleration * dt

2. 速度を位置に加算
   position += velocity * dt

3. 衝突判定
   if 衝突:
       位置補正
       速度反転(反発係数を乗算)

演習8-1:時間関数を使った周期アニメーション

《手順》

  1. 下記のコードを実行する。
  2. 3 種類のアニメーション(回転・上下移動・拡大縮小)を観察する。
  3. math.sin の引数の係数(3、2)と振幅(0.5、0.3)を変更し、動きの違いを確認する。
from ursina import *
import math

app = Ursina()

# 地面
Entity(
    model='plane',
    scale=(20, 1, 20),
    color=color.gray
)

# 回転するキューブ
rotating_cube = Entity(
    model='cube',
    color=color.red,
    position=(-4, 1, 0)
)

# 上下移動する球体
bouncing_sphere = Entity(
    model='sphere',
    color=color.blue,
    position=(0, 1, 0)
)

# 拡大縮小する円柱
scaling_cylinder = Entity(
    model='cylinder',
    color=color.green,
    position=(4, 1, 0)
)

def update():
    # 回転アニメーション
    rotating_cube.rotation_y += 100 * time.dt

    # 上下移動アニメーション
    bouncing_sphere.y = 1 + math.sin(time.time() * 3) * 0.5

    # 拡大縮小アニメーション
    scale_factor = 1 + math.sin(time.time() * 2) * 0.3
    scaling_cylinder.scale_y = scale_factor

camera.position = (0, 5, -12)
camera.look_at((0, 1, 0))

app.run()

実行結果:赤い立方体が回転し、青い球体が上下に移動し、緑の円柱が拡大縮小する。

《ヒント》

《考察ポイント》


演習8-2:重力と反発による落下

《手順》

  1. 下記のコードを実行する。
  2. 箱が落下し、地面でバウンドして徐々に止まる様子を観察する。
  3. 反発係数 0.5(コード中の * 0.5)を 0.9、0.1 に変えて挙動を比較する。
from ursina import *

app = Ursina()

# 背景とライト(箱を立体的に見せる)
Sky()
DirectionalLight(rotation=(45, -45, 0))
AmbientLight(color=color.rgba(120, 120, 120, 0.15))

# 地面(グリッド柄で奥行きを分かりやすく)
ground = Entity(
    model='plane',
    scale=(20, 1, 20),
    color=color.green,
    texture='white_cube',
    texture_scale=(20, 20),
    collider='box'
)

# 落下する箱
boxes = []
for i in range(5):
    box = Entity(
        model='cube',
        color=color.random_color(),
        position=(i * 2 - 4, 5 + i, 0),  # y=5〜9(画角内に収まる高さ)
        collider='box'
    )
    box.velocity = Vec3(0, 0, 0)  # カスタム属性として速度を追加
    boxes.append(box)

gravity = -9.8

def update():
    for box in boxes:
        # 重力を適用
        box.velocity.y += gravity * time.dt
        box.position += box.velocity * time.dt

        # 地面との衝突判定(箱の底=地面上面 y=0、立方体の半分=0.5 で停止)
        if box.y < 0.5:
            box.y = 0.5
            if box.velocity.y < 0:
                box.velocity.y = -box.velocity.y * 0.5  # 反発(減衰)
            # 十分遅くなったら停止
            if abs(box.velocity.y) < 1:
                box.velocity.y = 0

# カメラ(落下開始から着地まで全体が収まる位置・向き)
camera.position = (0, 7, -22)
camera.rotation_x = 13
# 自由に視点を動かしたい場合は上の2行を消して次を有効化:
# EditorCamera()

app.run()

実行結果:複数の箱が重力により落下し、地面に着地してバウンドする。徐々に跳ねる高さが低くなり、最終的に停止する。

《ヒント》

《考察ポイント》


ゲーム制作(簡易的な3Dアクションゲーム)


予備知識


3D ゲームは、プレイヤー、環境(地面・障害物)、ゲームロジック(スコア・クリア条件)、入出力(操作・表示)を組み合わせて構成される。本演習ではアイテム収集型の最小構成を扱う。


演習9:アイテム収集ゲーム

《手順》

  1. 下記のコードを実行する。
  2. WASD でプレイヤーを動かし、金色の球体を集めてスコアを増やす。
  3. 全アイテムを取ると「Game Clear!」が表示されることを確認する。
from ursina import *
from random import uniform

app = Ursina()

# プレイヤー
player = Entity(
    model='cube',
    color=color.azure,
    position=(0, 1, 0),
    collider='box'
)

# 地面
Entity(
    model='plane',
    scale=(50, 1, 50),
    color=color.green,
    texture='grass',
    collider='box'
)

# 収集アイテム
collectibles = []
for i in range(10):
    item = Entity(
        model='sphere',
        color=color.gold,
        position=(uniform(-20, 20), 0.5, uniform(-20, 20)),
        scale=0.5,
        collider='sphere'
    )
    collectibles.append(item)

# スコア表示
score = 0
score_text = Text(
    text=f'Score: {score}',
    position=(-0.85, 0.45),
    scale=2
)

# ゲームクリア判定用フラグ
game_clear = False

speed = 5

# カメラの初期設定(プレイヤーを見下ろす三人称視点)
camera.position = player.position + Vec3(0, 10, -15)
camera.look_at(player)

def update():
    global score, game_clear

    # クリア後は処理を止める
    if game_clear:
        return

    # プレイヤー移動
    if held_keys['w']:
        player.z += speed * time.dt
    if held_keys['s']:
        player.z -= speed * time.dt
    if held_keys['a']:
        player.x -= speed * time.dt
    if held_keys['d']:
        player.x += speed * time.dt

    # アイテム収集判定
    for item in collectibles[:]:
        if player.intersects(item).hit:
            destroy(item)
            collectibles.remove(item)
            score += 10
            score_text.text = f'Score: {score}'

    # ゲームクリア判定
    if len(collectibles) == 0:
        score_text.text = 'Game Clear!'
        game_clear = True

    # カメラがプレイヤーを追従(毎フレーム位置と向きを更新)
    camera.position = player.position + Vec3(0, 10, -15)
    camera.look_at(player)

app.run()

実行結果:プレイヤーを WASD キーで操作し、金色の球体を収集する。全てのアイテムを収集するとゲームクリアとなる。

《ヒント》

《考察ポイント》


ビジュアライゼーション(データの3D可視化)


予備知識


3D 空間で数値を表現すると、大小関係や分布を視覚的に把握できる。代表的な手法は次のとおりである。


可視化方法適したデータ表現方法
棒グラフ離散的な数値高さで値を表現
散布図3 次元座標データ位置と色で表現
ヒートマップ格子状のデータ色の濃淡で表現
ネットワーク図関係性データノードと線で表現

演習10:3D 棒グラフによるデータ可視化

《手順》

  1. 下記のコードを実行する。
  2. 10 個の値が高さの異なる棒として表示されることを確認する。
  3. リスト data の値を自分のデータに置き換えて再実行する。
from ursina import *

app = Ursina()

# データの可視化(棒グラフ)
data = [3, 7, 5, 9, 4, 6, 8, 2, 10, 6]
bars = []

for i, value in enumerate(data):
    bar = Entity(
        model='cube',
        color=color.hsv(i * 36, 1, 1),
        position=(i * 1.5 - 6.75, value / 2, 0),
        scale=(1, value, 1)
    )

    # ラベル(3D空間内に配置するため、既定のUI空間ではなくシーン空間を親にする)
    Text(
        text=str(value),
        parent=scene,
        position=(bar.x, value + 0.5, 0),
        scale=3,
        billboard=True
    )

    bars.append(bar)

# グリッド(基準面)
Entity(
    model='plane',
    scale=(20, 1, 10),
    color=color.dark_gray,
    texture='white_cube',
    alpha=0.3
)

# アニメーション
def update():
    for i, bar in enumerate(bars):
        bar.rotation_y += (i + 1) * 10 * time.dt

camera.position = (0, 8, -15)
camera.look_at((0, 5, 0))

app.run()

実行結果:データが 3D 棒グラフとして可視化される。各棒は異なる色で表示され、回転しながらデータの大小を視覚的に表現する。

《ヒント》

《考察ポイント》