1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonのPanda3Dではじめる3Dゲーム開発初心者向け

Posted at

はじめに

Panda3Dは、Pythonで3Dゲームや視覚化アプリケーションを作成するための強力なオープンソースエンジンです。このエンジンは、初心者にも使いやすく、同時に高度な機能も備えています。この記事では、Panda3Dの基本から応用まで、15の章に分けて詳しく解説します。各章では、概念の説明とともに、実際のコード例を提供します。

第1章: Panda3Dの基本

Panda3Dは、3D空間を扱うためのシーングラフという概念を使用します。シーングラフは、3D世界の要素を階層的に管理する木構造です。

from direct.showbase.ShowBase import ShowBase

class MyGame(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # 3Dモデルをロード
        self.model = self.loader.loadModel("models/panda")
        # シーンに追加
        self.model.reparentTo(self.render)

game = MyGame()
game.run()

このコードは、Panda3Dの基本的な構造を示しています。ShowBaseクラスを継承してゲームを作成し、3Dモデルをロードしてシーンに追加しています。

第2章: カメラの制御

3D空間を見るためには、カメラの制御が重要です。Panda3Dでは、カメラの位置や向きを簡単に変更できます。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import Point3

class CameraControl(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        self.model = self.loader.loadModel("models/panda")
        self.model.reparentTo(self.render)
        
        # カメラの位置を設定
        self.camera.setPos(0, -10, 5)
        # カメラの向きを設定
        self.camera.lookAt(Point3(0, 0, 0))

game = CameraControl()
game.run()

このコードでは、カメラの位置を(0, -10, 5)に設定し、原点(0, 0, 0)を見るように向きを調整しています。

第3章: ライティング

3D空間の雰囲気を作り出すには、適切なライティングが欠かせません。Panda3Dでは、様々な種類のライトを使用できます。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import AmbientLight, DirectionalLight, Vec4

class Lighting(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        self.model = self.loader.loadModel("models/panda")
        self.model.reparentTo(self.render)
        
        # 環境光を追加
        ambientLight = AmbientLight("ambient light")
        ambientLight.setColor(Vec4(0.2, 0.2, 0.2, 1))
        self.render.setLight(self.render.attachNewNode(ambientLight))
        
        # 指向性光源を追加
        directionalLight = DirectionalLight("directional light")
        directionalLight.setDirection(Vec4(-1, -1, -1, 0))
        directionalLight.setColor(Vec4(0.8, 0.8, 0.8, 1))
        self.render.setLight(self.render.attachNewNode(directionalLight))

game = Lighting()
game.run()

このコードでは、環境光と指向性光源を追加しています。環境光は全体的な明るさを、指向性光源は影の付き方を制御します。

第4章: テクスチャとマテリアル

3Dモデルの見た目を向上させるには、テクスチャとマテリアルの適用が重要です。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import Texture, TextureStage

class TextureAndMaterial(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        self.model = self.loader.loadModel("models/panda")
        self.model.reparentTo(self.render)
        
        # テクスチャをロード
        texture = self.loader.loadTexture("textures/panda_texture.png")
        
        # テクスチャをモデルに適用
        self.model.setTexture(texture)
        
        # マテリアルを設定
        self.model.setMaterial(self.loader.loadMaterial("materials/panda_material.mat"))

game = TextureAndMaterial()
game.run()

このコードでは、テクスチャをロードしてモデルに適用し、さらにマテリアルを設定しています。

第5章: アニメーション

3Dモデルにアニメーションを適用することで、シーンに動きを加えることができます。

from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor

class Animation(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # アニメーション付きモデルをロード
        self.actor = Actor("models/panda-model", {"walk": "models/panda-walk"})
        self.actor.reparentTo(self.render)
        
        # アニメーションを再生
        self.actor.loop("walk")

game = Animation()
game.run()

このコードでは、アニメーション付きのモデルをロードし、「歩く」アニメーションをループ再生しています。

第6章: 物理シミュレーション

Panda3Dには物理エンジンが組み込まれており、リアルな物理シミュレーションを実現できます。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import Vec3
from panda3d.bullet import BulletWorld, BulletPlaneShape, BulletRigidBodyNode, BulletBoxShape

class Physics(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # 物理世界を作成
        self.world = BulletWorld()
        self.world.setGravity(Vec3(0, 0, -9.81))
        
        # 地面を作成
        shape = BulletPlaneShape(Vec3(0, 0, 1), 0)
        node = BulletRigidBodyNode('Ground')
        node.addShape(shape)
        np = self.render.attachNewNode(node)
        np.setPos(0, 0, 0)
        self.world.attachRigidBody(node)
        
        # 箱を作成
        shape = BulletBoxShape(Vec3(0.5, 0.5, 0.5))
        node = BulletRigidBodyNode('Box')
        node.setMass(1.0)
        node.addShape(shape)
        np = self.render.attachNewNode(node)
        np.setPos(0, 0, 2)
        self.world.attachRigidBody(node)
        
        # 更新タスクを追加
        self.taskMgr.add(self.update, 'update')
    
    def update(self, task):
        dt = globalClock.getDt()
        self.world.doPhysics(dt)
        return task.cont

game = Physics()
game.run()

このコードでは、物理世界を作成し、地面と箱を配置しています。箱は重力の影響を受けて落下します。

第7章: 衝突検出

ゲーム内のオブジェクト間の衝突を検出することは、多くのゲームメカニクスの基礎となります。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode, CollisionSphere
from panda3d.core import CollisionHandlerQueue

class Collision(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # 衝突検出システムを設定
        self.cTrav = CollisionTraverser()
        self.cHandler = CollisionHandlerQueue()
        
        # プレイヤーオブジェクトを作成
        self.player = self.loader.loadModel("models/panda")
        self.player.reparentTo(self.render)
        
        # プレイヤーに衝突球を追加
        cNode = CollisionNode('player')
        cNode.addSolid(CollisionSphere(0, 0, 0, 1))
        cNodePath = self.player.attachNewNode(cNode)
        self.cTrav.addCollider(cNodePath, self.cHandler)
        
        # 障害物を作成
        self.obstacle = self.loader.loadModel("models/box")
        self.obstacle.reparentTo(self.render)
        self.obstacle.setPos(5, 0, 0)
        
        # 障害物に衝突ノードを追加
        cNode = CollisionNode('obstacle')
        cNode.addSolid(CollisionSphere(0, 0, 0, 1))
        self.obstacle.attachNewNode(cNode)
        
        # 更新タスクを追加
        self.taskMgr.add(self.update, 'update')
    
    def update(self, task):
        self.cTrav.traverse(self.render)
        
        for i in range(self.cHandler.getNumEntries()):
            entry = self.cHandler.getEntry(i)
            print(f"Collision detected: {entry.getFromNodePath()} hit {entry.getIntoNodePath()}")
        
        return task.cont

game = Collision()
game.run()

このコードでは、プレイヤーと障害物に衝突球を設定し、衝突が発生したときにコンソールに出力します。

第8章: ユーザー入力の処理

ゲームを対話的にするには、キーボードやマウスからの入力を処理する必要があります。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import Vec3

class UserInput(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        self.player = self.loader.loadModel("models/panda")
        self.player.reparentTo(self.render)
        
        # キー入力のイベントを設定
        self.accept("arrow_left", self.moveLeft)
        self.accept("arrow_right", self.moveRight)
        self.accept("arrow_up", self.moveForward)
        self.accept("arrow_down", self.moveBackward)
        
        # マウス操作を有効化
        self.disableMouse()
        self.taskMgr.add(self.spinCameraTask, "SpinCameraTask")
    
    def moveLeft(self):
        self.player.setPos(self.player.getPos() + Vec3(-1, 0, 0))
    
    def moveRight(self):
        self.player.setPos(self.player.getPos() + Vec3(1, 0, 0))
    
    def moveForward(self):
        self.player.setPos(self.player.getPos() + Vec3(0, 1, 0))
    
    def moveBackward(self):
        self.player.setPos(self.player.getPos() + Vec3(0, -1, 0))
    
    def spinCameraTask(self, task):
        angleDegrees = task.time * 6.0
        angleRadians = angleDegrees * (pi / 180.0)
        self.camera.setPos(20 * sin(angleRadians), -20 * cos(angleRadians), 3)
        self.camera.lookAt(0, 0, 0)
        return task.cont

game = UserInput()
game.run()

このコードでは、矢印キーでプレイヤーを移動させ、マウスでカメラを回転させることができます。

第9章: サウンドとBGM

ゲームに音楽や効果音を追加することで、プレイヤーの没入感を高めることができます。

from direct.showbase.ShowBase import ShowBase
from direct.showbase import Audio3DManager

class Sound(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # 3Dオーディオマネージャーを作成
        self.audio3d = Audio3DManager.Audio3DManager(self.sfxManagerList[0], camera)
        
        # BGMを再生
        self.bgm = self.loader.loadMusic("music/background.ogg")
        self.bgm.setLoop(True)
        self.bgm.play()
        
        # 効果音をロード
        self.effect = self.audio3d.loadSfx("sounds/effect.wav")
        
        # 効果音を再生するオブジェクトを作成
        self.soundObject = self.loader.loadModel("models/panda")
        self.soundObject.reparentTo(self.render)
        self.soundObject.setPos(5, 0, 0)
        
        # 効果音を3D空間に配置
        self.audio3d.attachSoundToObject(self.effect, self.soundObject)
        
        # スペースキーで効果音を再生
        self.accept("space", self.playEffect)
    
    def playEffect(self):
        if self.effect.status() != self.effect.PLAYING:
            self.effect.play()

game = Sound()
game.run()

このコードでは、BGMをループ再生し、スペースキーを押すと3D効果音が再生されます。

第10章: パーティクルシステム

パーティクルシステムを使用することで、火や煙、雨などの複雑な視覚効果を作成できます。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import Filename
from direct.particles.ParticleEffect import ParticleEffect

class Particles(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # パーティクル効果を作成
        self.effect = ParticleEffect()
        self.effect.loadConfig(Filename("particles/fire.ptf"))
        
        # パーティクルをシーンに追加
        self.effect.start(parent=self.render, renderParent=self.render)
        self.effect.setPos(0, 0, 0)
        
        # カメラの位置を調整
        self.camera.setPos(0, -10, 5)
        self.camera.lookAt(0, 0, 0)

game = Particles()
game.run()

このコードでは、火のパーティクル効果をロードし、シーンに追加しています。パーティクルファイル(fire.ptf)は事前に作成しておく必要があります。

第11章: シェーダーの使用

シェーダーを使用することで、高度なグラフィック効果を実現できます。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import Shader

class ShaderExample(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # モデルをロード
        self.model = self.loader.loadModel("models/panda")
        self.model.reparentTo(self.render)
        
        # シェーダーをロード
        shader = Shader.load(Shader.SL_GLSL, 
                             vertex="shaders/vertex.glsl",
                             fragment="shaders/fragment.glsl")
        
        # シェーダーをモデルに適用
        self.model.setShader(shader)
        
        # シェーダー入力を設定
        self.model.setShaderInput("time", 0)
        
        # 更新タスクを追加
        self.taskMgr.add(self.update, "update")
    
    def update(self, task):
        # 時間を更新
        self.model.setShaderInput("time", task.time)
        return task.cont

game = ShaderExample()
game.run()

このコードでは、カスタムシェーダーをモデルに適用し、時間に基づいて変化するエフェクトを作成しています。vertex.glslとfragment.glslファイルは別途作成する必要があります。

第12章: GUIの作成

ゲーム内のメニューやHUD(ヘッドアップディスプレイ)を作成するには、GUIシステムを使用します。

from direct.showbase.ShowBase import ShowBase
from direct.gui.DirectGui import DirectButton, DirectFrame, DirectLabel
from panda3d.core import TextNode

class GUIExample(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # フレームを作成
        self.frame = DirectFrame(frameColor=(0, 0, 0, 0.5),
                                 frameSize=(-0.5, 0.5, -0.5, 0.5),
                                 pos=(0, 0, 0))
        
        # ラベルを作成
        self.label = DirectLabel(text="スコア: 0",
                                 parent=self.frame,
                                 scale=0.07,
                                 pos=(-0.4, 0, 0.4),
                                 text_align=TextNode.ALeft)
        
        # ボタンを作成
        self.button = DirectButton(text="クリック",
                                   parent=self.frame,
                                   scale=0.1,
                                   pos=(0, 0, -0.4),
                                   command=self.onButtonClick)
        
        self.score = 0
    
    def onButtonClick(self):
        self.score += 1
        self.label["text"] = f"スコア: {self.score}"

game = GUIExample()
game.run()

このコードでは、フレーム、ラベル、ボタンを含む簡単なGUIを作成しています。ボタンをクリックするとスコアが増加します。

第13章: マルチプレイヤーネットワーキング

ネットワークを介して複数のプレイヤーが参加できるゲームを作成するには、ネットワーキング機能を実装する必要があります。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import QueuedConnectionManager, QueuedConnectionListener
from panda3d.core import QueuedConnectionReader, ConnectionWriter
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.PyDatagramIterator import PyDatagramIterator

class NetworkingExample(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # ネットワークコンポーネントを初期化
        self.cManager = QueuedConnectionManager()
        self.cListener = QueuedConnectionListener(self.cManager, 0)
        self.cReader = QueuedConnectionReader(self.cManager, 0)
        self.cWriter = ConnectionWriter(self.cManager, 0)
        
        # サーバーを開始
        self.port = 9099
        self.socket = self.cManager.openTCPServerRendezvous(self.port, 1000)
        self.cListener.addConnection(self.socket)
        
        # タスクを追加
        self.taskMgr.add(self.tskListenerPolling, "Poll the connection listener", -39)
        self.taskMgr.add(self.tskReaderPolling, "Poll the connection reader", -40)
    
    def tskListenerPolling(self, task):
        if self.cListener.newConnectionAvailable():
            rendezvous = PointerToConnection()
            netAddress = NetAddress()
            newConnection = PointerToConnection()
            
            if self.cListener.getNewConnection(rendezvous, netAddress, newConnection):
                newConnection = newConnection.p()
                self.cReader.addConnection(newConnection)
                print(f"New connection from {netAddress.getIpString()}")
        
        return task.cont
    
    def tskReaderPolling(self, task):
        if self.cReader.dataAvailable():
            datagram = NetDatagram()
            if self.cReader.getData(datagram):
                self.processData(datagram)
        
        return task.cont
    
    def processData(self, datagram):
        iterator = PyDatagramIterator(datagram)
        message = iterator.getString()
        print(f"Received message: {message}")

game = NetworkingExample()
game.run()

このコードは、基本的なTCPサーバーを実装しています。クライアントからの接続を受け付け、メッセージを処理します。実際のゲームでは、このコードをさらに拡張して、ゲーム状態の同期などを行う必要があります。

第14章: AIと経路探索

ゲーム内のNPC(ノンプレイヤーキャラクター)にインテリジェントな行動をさせるには、AIと経路探索アルゴリズムを実装する必要があります。

from direct.showbase.ShowBase import ShowBase
from panda3d.core import NodePath, Vec3
from direct.task import Task
import random

class AIExample(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # NPCを作成
        self.npc = self.loader.loadModel("models/panda")
        self.npc.reparentTo(self.render)
        self.npc.setScale(0.5)
        
        # 目標地点のリスト
        self.waypoints = [
            Vec3(5, 5, 0),
            Vec3(-5, 5, 0),
            Vec3(-5, -5, 0),
            Vec3(5, -5, 0)
        ]
        
        self.current_waypoint = 0
        
        # 更新タスクを追加
        self.taskMgr.add(self.updateNPC, "UpdateNPC")
    
    def updateNPC(self, task):
        # 現在の目標地点
        target = self.waypoints[self.current_waypoint]
        
        # NPCの現在位置
        current_pos = self.npc.getPos()
        
        # 目標への方向ベクトル
        direction = target - current_pos
        
        # 一定の速度で移動
        speed = 0.1
        if direction.length() > speed:
            direction.normalize()
            new_pos = current_pos + direction * speed
            self.npc.setPos(new_pos)
        else:
            # 目標に到達したら次の目標へ
            self.current_waypoint = (self.current_waypoint + 1) % len(self.waypoints)
        
        # NPCの向きを進行方向に合わせる
        self.npc.lookAt(target)
        
        return Task.cont

game = AIExample()
game.run()

このコードでは、NPCが定義されたウェイポイント(目標地点)を順番に巡回します。実際のゲームでは、より複雑な経路探索アルゴリズム(例:A*アルゴリズム)を実装することで、障害物を避けながら目的地に到達するようなAIを作成できます。

第15章: ゲームステートとシーン管理

大規模なゲームでは、異なるゲーム状態(メニュー、ゲームプレイ、ゲームオーバーなど)を管理し、シーンを切り替える必要があります。

from direct.showbase.ShowBase import ShowBase
from direct.fsm.FSM import FSM
from direct.gui.DirectGui import DirectButton

class GameState(FSM):
    def __init__(self, game):
        FSM.__init__(self, "GameState")
        self.game = game
    
    def enterMenu(self):
        self.game.menu_scene.show()
        self.game.play_scene.hide()
        self.game.gameover_scene.hide()
    
    def exitMenu(self):
        self.game.menu_scene.hide()
    
    def enterPlay(self):
        self.game.play_scene.show()
    
    def exitPlay(self):
        self.game.play_scene.hide()
    
    def enterGameOver(self):
        self.game.gameover_scene.show()
    
    def exitGameOver(self):
        self.game.gameover_scene.hide()

class GameExample(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # シーンを作成
        self.menu_scene = self.loader.loadModel("models/menu")
        self.play_scene = self.loader.loadModel("models/level")
        self.gameover_scene = self.loader.loadModel("models/gameover")
        
        self.menu_scene.reparentTo(self.render)
        self.play_scene.reparentTo(self.render)
        self.gameover_scene.reparentTo(self.render)
        
        # ゲームステートを初期化
        self.game_state = GameState(self)
        
        # メニューボタンを作成
        self.start_button = DirectButton(text="ゲーム開始", scale=0.1, pos=(0, 0, 0.2),
                                         command=self.startGame)
        self.quit_button = DirectButton(text="終了", scale=0.1, pos=(0, 0, -0.2),
                                        command=self.quit)
        
        # 初期状態をメニューに設定
        self.game_state.request("Menu")
    
    def startGame(self):
        self.game_state.request("Play")
    
    def gameOver(self):
        self.game_state.request("GameOver")
    
    def quit(self):
        self.userExit()

game = GameExample()
game.run()

このコードでは、FSM(有限状態機械)を使用してゲームの状態を管理しています。メニュー、プレイ、ゲームオーバーの3つの状態があり、それぞれの状態に応じて適切なシーンを表示します。

以上で、Panda3Dを使用したPythonゲーム開発の15章にわたる詳細な解説が完了しました。これらの章を通じて、3Dゲーム開発の基本から応用まで幅広くカバーしています。実際のゲーム開発では、これらの要素を組み合わせて使用することになります。Panda3Dの豊富な機能を活用し、創造的なゲームを作成してください。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?