Panda3D 3次元ゲームエンジン入門

概要

Panda3Dはオープンソースの3次元ゲームエンジンである。本記事では、Pythonプログラムから3次元空間を生成し、モデルを配置し、照明を当て、キーボードやマウスで操作する流れを、10の演習を通して学ぶ。

【前提知識】Pythonの基本文法(クラス、継承、メソッド定義)。

【学修のねらい】3次元コンピュータグラフィックスの基本要素(座標系、視点、モデル、変換、照明、イベント、アニメーション、運動)を、演習を通して理解する。

目次

サイト内の関連情報

1. はじめに

Panda3Dプログラムは、次の4ステップで構成される。本記事の演習はすべてこの構成に沿う。

┌─────────────────────────────────────┐
│ 1. モジュールのインポート              │
│    from direct.showbase.ShowBase    │
│        import ShowBase              │
└─────────────────────────────────────┘
           ↓
┌─────────────────────────────────────┐
│ 2. アプリケーションクラスの定義        │
│    class MyApp(ShowBase)            │
└─────────────────────────────────────┘
           ↓
┌─────────────────────────────────────┐
│ 3. シーンの構築                       │
│    モデルの読み込み・配置・照明・       │
│    イベント登録など                   │
└─────────────────────────────────────┘
           ↓
┌─────────────────────────────────────┐
│ 4. アプリケーションの実行             │
│    app.run()                        │
└─────────────────────────────────────┘

ステップ3で行う内容は演習ごとに異なる。演習1ではシーンに何も構築しないが、演習が進むにつれてモデルの読み込み・配置・照明・イベント登録などを追加していく。

2. 本教材を読む前に:3つの基本概念

演習に繰り返し登場する3つの基本概念を最初にまとめる。各演習ではこれらを既知のものとして扱う。コードの中で疑問が生じたらこの節に戻って確認するとよい。

概念1:シーングラフ(ノードの木構造)

Panda3Dのシーンは、ノードと呼ばれる要素が親子関係でつながった木構造として管理される。これをシーングラフと呼ぶ。木の根(最上位)にあたるのがrenderという特別なノードである。

render(根:シーン全体の最上位ノード)
 ├─ environment(竹林のモデル)
 ├─ rgbCube(立方体のモデル)
 └─ ライト など

モデルを読み込んだだけでは、そのモデルはまだ木のどこにも属しておらず、画面に表示されない。reparentTo(self.render)は「このモデルをrenderの子として取り付ける」操作である。画面に表示されるのは、renderの子孫であるノードだけである。「親に取り付ける」操作が随所に出てくるのは、このためである。なお、シーングラフ上のノードを指す変数には、慣習として末尾にNp(NodePath、ノードへの参照を表す型の略)を付けることがある。

概念2:ShowBaseが用意する基本オブジェクト

Panda3Dでは、ShowBaseを初期化すると、シーン構築や時間管理に使う基本オブジェクトを利用できる。本教材で使うものは次の通りである。

self.loaderself.renderself.taskMgrShowBaseオブジェクトの属性としてself.を付けて使う。globalClockだけはself.を付けずに使う。コード中に定義が見当たらなくても誤りではない。

概念3:フレームとアニメーション

Panda3Dは1秒間に何回も画面を描き直している。その1回分を「フレーム」と呼ぶ。毎フレーム、モデルの状態(位置など)を少しずつ更新し続けると、人の目にはなめらかに動いて見える。これがアニメーションの基本原理である。

1フレームにかかる時間は一定とは限らない。パソコンの性能や処理の重さによって、フレームの間隔は変動する。そこで動かす量を計算するときは、移動の速さにデルタ時間(dt)を掛ける。フレームが速く描かれるとき(dtが小さい)は1回あたりの移動量を小さく、遅いとき(dtが大きい)は大きくすることで、実時間あたりの動く速さが一定に保たれる。この考え方は演習10で使う。

3. 前準備

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 ウィンドウは,マウスクリックでウィンドウをアクティブ(操作対象の状態)にしてからキーを押すと終了する

Panda3Dのインストール

本記事の演習はPanda3Dを使う。Panda3Dは3次元コンピュータグラフィックスのためのライブラリである。インストールは1回だけ行う。

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

pip install --no-user -U panda3d
Panda3Dのインストール画面

プログラムの実行

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

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

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

python a.py

以下、各演習では「コードを実行する」と記述する。方法AまたはBで実行する。プログラムを終了するときは、表示されたウィンドウ右上の「×」をクリックする。

4. 演習1: 画面を開く

最小構成のプログラムで画面を表示し、Panda3Dが動作することを確認する。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
    app = HelloWorld()
    app.run()
    
  2. 何も描かれていない灰色のウィンドウが表示されることを確認する。
    Panda3Dの初期画面(灰色の空の画面)

《ヒント》

《考察ポイント》

5. 演習2: マウスによる視点操作

3次元モデルを1つ読み込んで表示し、マウスで視点を動かす。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
    app = HelloWorld()
    app.run()
    

    補足:loadModel()はモデルファイルを読み込む。reparentTo(self.render)は、読み込んだモデルをrenderの子として取り付ける(概念1)。Panda3Dには標準モデルが同梱されており、"models/environment"のような相対指定で読み込める。

  2. 竹林のような3次元シーンが表示されることを確認する。
    環境モデルの表示結果(竹林のシーン)
  3. マウスで視点を動かす。Panda3Dの既定のカメラ操作は次のとおりである。
    • 左ドラッグ:視点を左右・上下に平行移動
    • 右ドラッグ:視点を前後に移動(ズームイン・ズームアウト)
    • 中ドラッグ:原点を中心に視点を回転

《ヒント》

《考察ポイント》

6. 演習3: 複数の3次元モデルの読み込み

2つのモデルを同じシーンに読み込む。

Panda3Dには複数の標準モデルが同梱されており、いずれも"models/..."という相対指定で読み込める。本演習で使う2つのモデルは次の通りである。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube = self.loader.loadModel("models/misc/rgbCube")
            self.cube.reparentTo(self.render)
            self.cube.setPos(0, 20, 0)
    
    app = HelloWorld()
    app.run()
    
  2. 竹林のシーンの中に立方体が表示されることを確認する。マウスで視点を変えて、立方体を複数の角度から観察する。
    竹林と立方体が表示された画面

《ヒント》

《考察ポイント》

7. 演習4: 3次元モデルの配置

モデルの位置・大きさ・向きを制御する。3次元コンピュータグラフィックスの基本変換である移動・スケール・回転を順に実施する。

演習4-1: 位置を指定して配置

Panda3Dの座標系:xは左右(右が+)、yは前奥(カメラから見て奥が+)、zは上下(上が+)。z-up(zを上方向とする)の右手座標系である。

Panda3Dの座標系(x:左右、y:前奥、z:上下)

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube1 = self.loader.loadModel("models/misc/rgbCube")
            self.cube1.reparentTo(self.render)
            self.cube1.setPos(-3, 20, 0)
    
            self.cube2 = self.loader.loadModel("models/misc/rgbCube")
            self.cube2.reparentTo(self.render)
            self.cube2.setPos(3, 20, 0)
    
    app = HelloWorld()
    app.run()
    
  2. 2つの立方体が画面の左右に離れて表示されることを確認する。マウスで視点を動かして位置関係を確かめる。
    異なる位置に配置された2つの立方体

《ヒント》

《考察ポイント》

演習4-2: setScale()で拡大縮小

setScale(値)でモデルそのものの大きさを変える。1.0が基準(等倍)である。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube1 = self.loader.loadModel("models/misc/rgbCube")
            self.cube1.reparentTo(self.render)
            self.cube1.setPos(0, 20, 0)
    
            self.cube2 = self.loader.loadModel("models/misc/rgbCube")
            self.cube2.reparentTo(self.render)
            self.cube2.setPos(5, 20, 0)
            self.cube2.setScale(2.4)
    
    app = HelloWorld()
    app.run()
    
  2. 右側の立方体が2.4倍に拡大されていることを確認する。
    右側の立方体が2.4倍に拡大された画面

《ヒント》

《考察ポイント》

演習4-3: setHpr()で回転

setHpr(H, P, R)でモデルの向き(回転)を変える。H・P・Rはオイラー角と呼ばれ、単位は度である。飛行機の姿勢にたとえると次のようになる。

setHpr(45, 0, 0)は「z軸まわりに45度回す」という意味になる。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube1 = self.loader.loadModel("models/misc/rgbCube")
            self.cube1.reparentTo(self.render)
            self.cube1.setPos(0, 20, 0)
    
            self.cube2 = self.loader.loadModel("models/misc/rgbCube")
            self.cube2.reparentTo(self.render)
            self.cube2.setPos(5, 20, 0)
            self.cube2.setHpr(45, 0, 0)
    
    app = HelloWorld()
    app.run()
    
  2. 左側の立方体(回転なし)と、右側の立方体(z軸まわりに45度回転)を見比べる。
    右側の立方体が回転した状態で表示された画面

《ヒント》

《考察ポイント》

8. 演習5: 照明を当てる

これまでの演習では、モデルは平坦に見えていた。Panda3Dでは初期状態で照明が当たっていないため、面の向きによる明暗が表現されない。本演習では照明を追加し、立体感を与える。

本演習で使う2種類の光:

光は次の3ステップで適用する。光のオブジェクトを作り、render.attachNewNode()でシーングラフに取り付け、render.setLight()でシーン全体に適用する。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    from panda3d.core import AmbientLight, DirectionalLight
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube = self.loader.loadModel("models/misc/rgbCube")
            self.cube.reparentTo(self.render)
            self.cube.setPos(0, 20, 0)
            self.cube.setHpr(45, 0, 0)
    
            ambient = AmbientLight("ambient")
            ambient.setColor((0.3, 0.3, 0.3, 1))
            ambientNp = self.render.attachNewNode(ambient)
            self.render.setLight(ambientNp)
    
            directional = DirectionalLight("directional")
            directional.setColor((0.8, 0.8, 0.8, 1))
            directionalNp = self.render.attachNewNode(directional)
            directionalNp.setHpr(-45, -60, 0)
            self.render.setLight(directionalNp)
    
    app = HelloWorld()
    app.run()
    
    照明を当てたシーンの表示結果

    補足:立方体にsetHpr(45, 0, 0)を付けているのは、複数の面が見えるよう傾け、照明による明暗を観察しやすくするためである(演習4-3を参照)。以降の演習でもこの目的で傾けて表示する。

  2. 竹林のシーンに明暗がつき、立体的に見えることを確認する。照明を入れる前(演習4-3)と見比べる。

《ヒント》

《考察ポイント》

9. 演習6: キーボードへの反応

ユーザーの入力に反応するシーンを作る。まずキーボード入力に対応させる。

self.accept(キー名, 関数)は、指定したキーが押されたときに、その関数を呼び出すよう登録する(イベントハンドラの登録)。

キー名の例:"a", "space", "enter", "escape", "arrow_left", "arrow_up", "arrow_down", "arrow_right"

本演習からは、Escキーでプログラムを終了できるようにする(self.userExitはアプリケーションを終了するメソッドである)。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube = self.loader.loadModel("models/misc/rgbCube")
            self.cube.reparentTo(self.render)
            self.cube.setPos(0, 20, 0)
            self.cube.setHpr(45, 0, 0)
    
            self.accept("a", self.a_key)
            self.accept("escape", self.userExit)
    
        def a_key(self):
            self.cube.setScale(2)
    
    app = HelloWorld()
    app.run()
    
  2. 立方体が表示されることを確認する。
    立方体の初期表示状態
  3. キーボードの「A」キーを押すと、立方体が拡大されることを確認する。
    Aキー押下後に拡大された立方体
  4. Escキーを押すと、プログラムが終了することを確認する。

《ヒント》

《考察ポイント》

10. 演習7: マウスクリックへの反応

マウスクリックで立方体を変化させる。

マウスボタンの名前:"mouse1"(左)、"mouse2"(中)、"mouse3"(右)

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube = self.loader.loadModel("models/misc/rgbCube")
            self.cube.reparentTo(self.render)
            self.cube.setPos(0, 20, 0)
            self.cube.setHpr(45, 0, 0)
    
            self.accept("mouse1", self.mouse1)
            self.accept("mouse3", self.mouse3)
            self.accept("escape", self.userExit)
    
        def mouse1(self):
            self.cube.setScale(2)
    
        def mouse3(self):
            self.cube.setScale(0.5)
    
    app = HelloWorld()
    app.run()
    
  2. 立方体が表示されることを確認する。
    立方体の初期表示状態
  3. マウスの右ボタンをクリックすると、立方体が縮小されることを確認する。
    右クリック後に縮小された立方体
  4. マウスの左ボタンをクリックすると、立方体が拡大されることを確認する。
    左クリック後に拡大された立方体

《ヒント》

《考察ポイント》

11. 演習8: 矢印キーで立方体を動かす

現在の位置を取り出し、値をずらして、書き戻す処理を繰り返すことで、立方体を移動させる。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube = self.loader.loadModel("models/misc/rgbCube")
            self.cube.reparentTo(self.render)
            self.cube.setPos(0, 20, 0)
            self.cube.setHpr(45, 0, 0)
    
            self.accept("arrow_up", self.up_key)
            self.accept("arrow_down", self.down_key)
            self.accept("arrow_right", self.right_key)
            self.accept("arrow_left", self.left_key)
            self.accept("escape", self.userExit)
    
        def up_key(self):
            self.cube.setZ(self.cube.getZ() + 1)
        def down_key(self):
            self.cube.setZ(self.cube.getZ() - 1)
        def right_key(self):
            self.cube.setX(self.cube.getX() + 1)
        def left_key(self):
            self.cube.setX(self.cube.getX() - 1)
    
    app = HelloWorld()
    app.run()
    
  2. 立方体が表示されることを確認する。
  3. 矢印キー(上、下、右、左)を押すと、立方体が1単位ずつ移動することを確認する。
    矢印キーで移動した立方体

《ヒント》

《考察ポイント》

12. 演習9: 立方体を自動で動かす

キー入力を待たずに、毎フレーム自動で位置を更新する。

self.taskMgr.add(関数, 名前)で関数を登録すると、その関数が毎フレーム自動的に呼び出される(このような関数を「タスク」と呼ぶ)。タスク関数の引数taskには現在のタスクの状態が入っており、task.timeはタスクが最初に実行されてからの経過秒数である(本演習では起動直後にタスクを登録するため、アプリ起動からの経過秒数とみなしてよい)。task.contは「次のフレームも継続して呼ぶ」ことを示す戻り値である(フレームについては概念3を参照)。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube = self.loader.loadModel("models/misc/rgbCube")
            self.cube.reparentTo(self.render)
            self.cube.setPos(0, 20, 0)
            self.cube.setHpr(45, 0, 0)
    
            self.accept("escape", self.userExit)
            self.taskMgr.add(self.cubeMotionTask, "CubeMotionTask")
    
        def cubeMotionTask(self, task):
            self.cube.setPos(0, 20, task.time * 0.2)
            return task.cont
    
    app = HelloWorld()
    app.run()
    
  2. 立方体が自動で上方向に動き続けることを確認する。
    自動移動開始時の立方体の位置
    自動移動後の立方体の位置

《ヒント》

《考察ポイント》

13. 演習10: 速度を持たせて動かす

位置を直接書き換えるのではなく、速度を持たせて、フレームごとに位置を進める。物理シミュレーションやゲーム制御の出発点である。

本演習では「位置 += 速度 × デルタ時間」という式で立方体を動かす(+=はPythonの加算代入演算子で、左辺の値に右辺を加えて左辺に戻す)。速度(vx, vy, vz)のうち、vxvzを矢印キーで増減させ、vyは0のままとする。位置(x, y, z)は毎フレーム自動更新する。デルタ時間はglobalClock.getDt()で取得する(概念2、概念3を参照)。

設計の考え方:位置(x, y, z)と速度(vx, vy, vz)をPython側の変数として保持する。毎フレーム、これらの変数で次の位置を計算し、その結果をsetPos()でモデルに反映する。setPos(0, 20, 0)で与えた初期位置と、変数self.x, self.y, self.zの初期値(0, 20, 0)が一致しているのはこのためである。計算はPython側の変数で行い、モデルへの反映は計算結果を渡すだけ、という役割分担になっている。

注意:同じキーを複数回押すと加速する。反対方向のキーを押すと減速し、さらに押すと逆向きに動く。

《手順》

  1. 次のコードを実行する。
    from direct.showbase.ShowBase import ShowBase
    
    class HelloWorld(ShowBase):
    
        def __init__(self):
            ShowBase.__init__(self)
    
            self.scene = self.loader.loadModel("models/environment")
            self.scene.reparentTo(self.render)
    
            self.cube = self.loader.loadModel("models/misc/rgbCube")
            self.cube.reparentTo(self.render)
            self.cube.setPos(0, 20, 0)
            self.cube.setHpr(45, 0, 0)
            self.x = 0
            self.y = 20
            self.z = 0
            self.vx = 0
            self.vy = 0
            self.vz = 0
    
            self.accept("arrow_up", self.up_key)
            self.accept("arrow_down", self.down_key)
            self.accept("arrow_right", self.right_key)
            self.accept("arrow_left", self.left_key)
            self.accept("escape", self.userExit)
            self.taskMgr.add(self.cubeMotionTask, "CubeMotionTask")
    
        def cubeMotionTask(self, task):
            dt = globalClock.getDt()
            self.x += self.vx * dt
            self.y += self.vy * dt
            self.z += self.vz * dt
            self.cube.setPos(self.x, self.y, self.z)
            return task.cont
    
        def up_key(self):
            self.vz = self.vz + 1
        def down_key(self):
            self.vz = self.vz - 1
        def right_key(self):
            self.vx = self.vx + 1
        def left_key(self):
            self.vx = self.vx - 1
    
    app = HelloWorld()
    app.run()
    
  2. 立方体が表示されることを確認する。最初は速度がゼロのため停止している。
    立方体の初期表示状態
  3. 矢印キーを押すと立方体が動き出し、キーから手を離しても動き続けることを確認する。同じキーをもう一度押すと加速し、反対のキーを押すと減速する。
    矢印キーで速度を与えて移動した立方体

《ヒント》

《考察ポイント》