見出し画像

Pythonでマイクラを作る ⑧カメラ機能を拡張する

Pythonでマイクラを作る 第8回目です。今回はカメラ機能を拡張して、斜め上から見下ろすカメラ(TPSカメラ)、プレイヤー視点のカメラ(FPSカメラ)、そしてミラーカメラを実装します。この3つのカメラを切り替えて、マイクラクローンの世界を探索することができるようになります。FPS視点では、プレイヤーの視点になって、マイクラクローンの世界に入り込むことができます。

ゲーム用語の説明 
プレイヤーとカメラの位置により対戦型ゲームを分類する
TPS = Third Person Shooting game の略(CoDシリーズ、レインボーシックスシージ、APEX など)
FPS = First Person Shooting game の略(フォートナイト、PUBG、スプラトゥーンシリーズなど)

まずカメラレンズの説明から入ります。カメラレンズには、透視投影用レンズ(Perspective Lens)と平行投影用レンズ(Orthographic Lens)があります。

透視投影用レンズ

perspective lens

Panda3D では、デフォルトのカメラに透視投影用レンズが使われています。透視投影では近くのものは大きく、遠くのものは小さく見えます。自然な見え方に近いレンズです。
視野角を変更することで、見える範囲を調整できます。視野角を大きくすると広い範囲(広角レンズ)が、小さくすると狭い範囲(望遠レンズ)を捉えることができます。デフォルトは水平視野角 40度です。
Near平面と Far平面の間にあるオブジェクトが描画されます。デフォルトの数値は 1 から 100,000 になっています。さらに近くのものを描画したいときは Near平面の値を 1 より小さくします。Far平面の値を小さくすると、遠くのものを描画対象から外すことができます。描画の負荷が下げ、プレイヤーの動きを滑らかにできる場合があります。

平行投影用レンズ

orthographic lens

平行投影レンズは、近くのものも遠くのものも同じ大きさで描画します。遠近感がなくなってしまうので、3Dモデルを厳密に表示したいなど特別な場合に使うレンズです
平行投影では視野角は存在しないので、描画範囲をフィルムサイズで設定します。フィルムサイズの縦横比と画面の縦横比は同じにしましょう。もし縦横比が違うときは画像が引き伸ばされて描画されてしまうので注意が必要です。
Near平面、Far平面は透視投影レンズと同じように設定できますが、Near平面の値はマイナスにできるところが違います。Near平面をマイナスにすると、カメラ(視点)より後ろに下がって、より広い範囲を描画することができます。

デフォルトのカメラを設定変更する

"""src/camera.py"""
from math import *
from panda3d.core import *


class Camera:
    far_length = 50
    base_camera_back = 5
    base_camera_height = 3
    base_camera_forward = 6
    base_cam_fov = 90
    player_cam_height = 1.5
    player_cam_fov = 100
    player_head_height = 0.8
    mirror_cam_radius = 5
    mirror_cam_film_size = (8, 6)

    def __init__(self):

        # マウス操作を禁止
        self.base.disableMouse()

        # base cam
        self.base.cam.reparentTo(self.base.player_head_node)
        self.base.camLens.setFov(Camera.base_cam_fov)
        # self.base.camLens.setFar(Camera.far_length)
        self.base.cam.setPos(
            Vec3(0, -Camera.base_camera_back, Camera.base_camera_height)
        )
        self.base.cam.lookAt(
            Vec3(0, Camera.base_camera_forward, 0)
        )

デフォルトのカメラをプレイヤーの動きと連動して動かします。デフォルトのカメラはインスタンス変数cam からアクセスできます。reparentTo(self.base.player_head_node) により、デフォルトカメラの所属を render からプレイヤーの頭ノードに変更しました。setFov(Camera.base_cam_fov) により水平視野角を 40 → 90 に変更しました。これでより広い範囲を捉えることができるようになります(広角レンズになるので画面端が歪んで見える副作用があります)。
setFar(Camera.far_length) により、50メートルより先は描画しないように設定しました。3Dモデルをリアルタイムで動かすには、強力な処理能力が必要となります。パソコンの負荷を下げるための工夫の一つです。(お使いのパソコンにもよりますが、画面がカクツクなど処理が追いつかないときは、もっと数字を小さくしてみましょう。)

default camera

プレイヤーとカメラの位置関係は上図を参照してください。
カメラは、プレイヤー頭の中心から後ろに 5メートル、上に 3メートルの位置に設置しました。カメラの注視点は、プレイヤー頭の中心から前に 6メートル進んだところです。

"""08_01_main.py"""
from math import *
from src import MC


class Game(MC):
    def __init__(self):
        # MCを継承する
        MC.__init__(self, ground_size=32)

        # 座標軸
        self.axis = self.loader.loadModel('models/zup-axis')
        self.axis.setPos(0, 0, 0)
        self.axis.setScale(1.5)
        self.axis.reparentTo(self.render)

        # 壁
        for i in range(5):
            for j in range(3):
                self.block.add_block(i, 5, j, 'gold_block')


game = Game()
game.run()
TPS camera

08_01_main.py はゲームを起動するモジュールです。
MCクラスの継承で、引数ground_size=32 を設定しました。地面のサイズを 32x32 に制限して、パソコンの処理を下げて画面のカクツキを防止しています。金ブロックで壁を作って目印にしました。
実行すると、TPS視点での世界が現れます。プレイヤーを動かすとカメラも一緒に移動することを確認しておいてください。これが TPS(Third Person Shooting)と言われる見え方になります。

プレイヤー視点のカメラを追加する

"""src/camera.py"""

コンストラクタに追記

        # player cam
        player_cam_lens = PerspectiveLens()
        player_cam_lens.setFov(Camera.player_cam_fov)
        player_cam_lens.setNear(0.1)
        player_cam_lens.setFar(Camera.far_length)
        self.player_cam = self.base.makeCamera(self.base.win)
        self.player_cam.node().setLens(player_cam_lens)
        self.player_cam.reparentTo(self.base.player_head_node)
        self.player_cam.setPos(
            Vec3(0, Camera.player_head_height / 2, 0)
        )

camera.py を修正して、プレイヤー視点のカメラを追加します。
player_cam_lens = PerspectiveLens()により、透視投影レンズを準備します。水平視野角 100度、Near平面 0.1、Far平面 50 に設定しました。
self.base.makeCamera(self.base.win)により新しいカメラを作成できます。node().setLens(player_cam_lens)により、先ほど作成した透視投影レンズをカメラに装着します。
プレイヤーの頭ノードの中心より 0.4 前にカメラを追加しました。

"""src/camera.py"""

コンストラクタに追記

        # カメラ設定
        self.base.cameras = [self.base.cam, self.player_cam]
        self.base.active_cam = 0
        self.base.cameras[1].node().getDisplayRegion(0).setActive(0)

        # カメラを切り替え
        self.base.accept('t', self.toggle_cam)

    def toggle_cam(self):
        self.base.cameras[self.base.activeCam].node().getDisplayRegion(0).setActive(0)
        self.base.activeCam = (self.base.activeCam + 1) % len(self.base.cameras)
        self.base.cameras[self.base.activeCam].node().getDisplayRegion(0).setActive(1)

ユーザーが「t」キーを押すと、カメラを切り替えることができるコードを追記します。
インスタンス変数cameras は使用できるカメラのリストです。デフォルトのカメラとFPSカメラを要素として含みます。インスタンス変数active_cam は現在選択中のカメラ番号(0)を表します。そして self.base.cameras[1].node().getDisplayRegion(0).setActive(0)により、FPSカメラを非表示(setActive(0))にします。これでデフォルトのカメラのみ表示できます。
toggle_camメソッドはカメラの切り替えを担当します。ユーザーが「t」キーを押すと、現在選択中のカメラを非表示(setActive(0))にして、カメラ番号が次のカメラを表示(setActive(1))します。
(self.base.active_cam + 1) % len(self.base.cameras)のコードの意味は、変数active_cam がリストcameras の長さを超えたときは、先頭の要素に戻るということです。つまりリストcameras を循環的に選ぶことができます。

FPS camera

08_01_main.py を実行してください。「t」キーを押して、カメラが切り替えられることを確認します。プレイヤー視点のことを FPS(First Person Shooting)と言います。ゲームの世界に入り込んで、世界を探索できるようになりました。

ミラーカメラを追加する

"""src/camera.py"""
from math import *
from panda3d.core import *


class Camera:
    far_length = 50
    base_camera_back = 5
    base_camera_height = 3
    base_camera_forward = 6
    base_cam_fov = 90
    player_cam_height = 1.5
    player_cam_fov = 100
    player_head_height = 0.8
    mirror_cam_radius = 5
    mirror_cam_film_size = (8, 6)

    def __init__(self):

        # マウス操作を禁止
        self.base.disableMouse()

        # base cam
        self.base.cam.reparentTo(self.base.player_head_node)
        self.base.camLens.setFov(Camera.base_cam_fov)
        # self.base.camLens.setFar(Camera.far_length)
        self.base.cam.setPos(
            Vec3(0, -Camera.base_camera_back, Camera.base_camera_height)
        )
        self.base.cam.lookAt(
            Vec3(0, Camera.base_camera_forward, 0)
        )

        # player cam
        player_cam_lens = PerspectiveLens()
        player_cam_lens.setFov(Camera.player_cam_fov)
        player_cam_lens.setNear(0.1)
        player_cam_lens.setFar(Camera.far_length)
        self.player_cam = self.base.makeCamera(self.base.win)
        self.player_cam.node().setLens(player_cam_lens)
        self.player_cam.reparentTo(self.base.player_head_node)
        self.player_cam.setPos(
            Vec3(0, Camera.player_head_height / 2, 0)
        )

        # mirror cam
        mirror_cam_lens = OrthographicLens()
        mirror_cam_lens.setFilmSize(*Camera.mirror_cam_film_size)
        mirror_cam_lens.setFar(Camera.far_length)
        mirror_cam_lens.setNear(-100)
        self.mirror_cam = self.base.makeCamera(self.base.win)
        self.mirror_cam.node().setLens(mirror_cam_lens)
        self.mirror_cam.reparentTo(self.base.player_head_node)
        self.mirror_cam.setPos(
            Vec3(0, Camera.mirror_cam_radius, 0)
        )
        self.mirror_cam.setH(180)

        # カメラ設定
        # self.base.cameras = [self.base.cam, self.player_cam]
        self.base.cameras = [self.base.cam, self.player_cam, self.mirror_cam]
        self.base.activeCam = 0
        self.base.cameras[1].node().getDisplayRegion(0).setActive(0)
        self.base.cameras[2].node().getDisplayRegion(0).setActive(0)

        # カメラを切り替え
        self.base.accept('t', self.toggle_cam)

    def toggle_cam(self):
        self.base.cameras[self.base.activeCam].node().getDisplayRegion(0).setActive(0)
        self.base.activeCam = (self.base.activeCam + 1) % len(self.base.cameras)
        self.base.cameras[self.base.activeCam].node().getDisplayRegion(0).setActive(1)

上のコードが今回の完成形です。
ミラーカメラを追加しました。ミラーカメラはプレイヤー自身の姿を正面から捉えます。プレイヤーの姿を確認したり、後ろに敵がいないか確認したりするときに使用します。
追加したコードを説明します。


        # mirror cam
        mirror_cam_lens = OrthographicLens()
        mirror_cam_lens.setFilmSize(*Camera.mirror_cam_film_size)
        mirror_cam_lens.setFar(Camera.far_length)
        mirror_cam_lens.setNear(-100)
        self.mirror_cam = self.base.makeCamera(self.base.win)
        self.mirror_cam.node().setLens(mirror_cam_lens)
        self.mirror_cam.reparentTo(self.base.player_head_node)
        self.mirror_cam.setPos(
            Vec3(0, Camera.mirror_cam_radius, 0)
        )
        self.mirror_cam.setH(180)

ミラーカメラとして、平行投影レンズを採用しました。
mirror_cam_lens = OrthographicLens()により、平行投影レンズを準備して、
setFilmSize(*Camera.mirror_cam_film_size)によりフィルムサイズを 8x6 に設定しました。setNear(-100)を設定したのは、地面を切り取って表示してしまうバグを防止するためです。
self.base.makeCamera(self.base.win)は新しくカメラを作ることができます。新しいカメラに平行投影レンズを装着して、プレイヤーの頭ノードの中心より 5 前にカメラを設置しました。setH(180)によりカメラを反転させて、プレイヤー自身を写すことができるようにします。


        # カメラ設定
        # self.base.cameras = [self.base.cam, self.player_cam]
        self.base.cameras = [self.base.cam, self.player_cam, self.mirror_cam]
        self.base.activeCam = 0
        self.base.cameras[1].node().getDisplayRegion(0).setActive(0)
        self.base.cameras[2].node().getDisplayRegion(0).setActive(0)

カメラ設定を修正します。リストcameras にミラーカメラを追加します。そして、ミラーカメラを非表示にして完了です。

MIRROR camera

08_01_main.py を実行して、ゲームを起動します。「t」キーでカメラを切り替えて、ミラーカメラの見え方を確認してください。平行投影レンズにしたので、遠近感がなくなっています。

今回はカメラのアップデートを行いました。カメラの位置や視野角は自分の好みで調整して、自分だけのマイクラクローンを作ってください。
次回はプレイヤーの操作でブロックを設置、破壊ができる機能を追加します。これでよりマイクラに近づくことができるでしょう。お楽しみに。


前の記事
Pythonでマイクラを作る ⑦プレイヤーモデルを作成
次の記事
Pythonでマイクラを作る ⑨ブロックの設置と破壊を実装する

その他のタイトルはこちら


いいなと思ったら応援しよう!