Pythonでマイクラを作る ⑪起動画面を実装する
Pythonでマイクラを作る 第11回目です。今回はユーザーインターフェースの実装を続けます。まず画面上にテキストを表示できるようにします。デバッグモードでは、プレイヤーの情報(位置や向きなど)を画面上で確認できるようにします。最後、起動画面(スプラッシュスクリーン)を表示して、本格的なゲームに近づける方法を学びます。
フォントの準備
フォントとはコンピューターで使われる文字の書体のことです。Panda3D のデフォルトのフォントは日本語に対応していないため、自分で日本語対応フォントを準備します。
PixelMplus(ピクセル・エムプラス)は、8bitゲーム機のビットマップフォント風のフリーのフォント(True Type Font)です。こちらを使わせていただきます。
# ディレクトリ構造
Documents/
├ pynecrafter/
│ ├ fonts/
│ │ ├ PixelMplus10-Regular.ttf
│ │ ├ PixelMplus12-Regular.ttf
│ │
ダウンロードしたフォントは、fontsフォルダを作って、その中に保存してください。これでフォントの準備は完了です。
画面にテキストを表示する
"""src/utils.py"""
from direct.gui.DirectGui import *
from panda3d.core import *
class DrawImage(OnscreenImage):
def __init__(self, parent=None, image=None, scale=(1, 1, 1), pos=(0, 0, 0)):
super().__init__(
parent=parent,
image=image,
pos=pos,
scale=scale,
)
self.setName(image)
self.setTransparency(TransparencyAttrib.M_alpha)
class DrawText(OnscreenText):
def __init__(self, parent=None, text='', font=None, scale=0.07, pos=(0.05, -0.1), fg=(0, 0, 0, 1), bg=(0, 0, 0, 0.1)):
super().__init__(
parent=parent,
text=text,
align=TextNode.ALeft,
pos=pos,
scale=scale,
font=font,
fg=fg,
bg=bg,
mayChange=True,
)
self.start_time = None
utils.py ファイルの中に、DrawTextクラスを追記します。Panda3D の文字表示を操作する OnscreenTextクラスを継承して、super().__init__()により、OnscreenTextクラスのプロパティーを初期化します。
引数 scale は文字サイズを指定します。引数 fg は前景(ForeGround)を意味し、文字色(r、g、b、a)を指定します。引数 bg は背景(BackGround)を意味し、背景色(r、g、b、a)を指定します(デフォルト値は薄いグレー (0, 0, 0, 0.1) に指定した)。引数mayChange は文字を変更する場合に True を指定します。
インスタンス変数start_time は既定時間で文字が消える実装のときに使うので、この時点で指定しておきましょう。
"""src/mc.py"""
from direct.showbase.ShowBase import ShowBase
from panda3d.core import *
from . import *
class MC(ShowBase, UserInterface):
def __init__(self, ground_size=128):
# ShowBaseを継承する
ShowBase.__init__(self)
self.font = self.loader.loadFont('fonts/PixelMplus12-Regular.ttf') # 追記
UserInterface.__init__(self)
mc.py を修正します。
インスタンス変数font に使用するフォントを代入します。self.loader.loadFontメソッドでフォントのパスを指定して、PixelMplus(ピクセル・エムプラス)フォントを読み込みます。ここでフォントを指定しておくと、UserInterface クラスでフォントを利用できるようになります。
"""src/user_interface.py"""
from time import time
from math import *
from panda3d.core import *
from .utils import * # 修正
class UserInterface:
hotbar_blocks = [
['stone', ['0-1']],
['grass_block', ['0-3', '0-0', '0-2']],
['dirt', ['0-2']],
['white_wool', ['4-0']],
['blue_wool', ['11-1']],
['red_wool', ['8-1']],
['glass', ['3-1']],
['gold_block', ['1-7']],
['bricks', ['0-7']],
]
def __init__(self):
self.selected_hotbar_num = 0
self.selected_block = UserInterface.hotbar_blocks[0]
# draw hotbar
for i, block in enumerate(UserInterface.hotbar_blocks):
block_image_name = block[1][0]
image = DrawImage(
parent=self.a2dBottomCenter,
image=f'textures/{block_image_name}.png',
scale=(16 / 164, 16 / 164, 16 / 164),
pos=((i - 4) * 0.22, 0, 20 / 164)
)
self.set(f'bar{i + 1}', image)
self.hotbar = DrawImage(
parent=self.a2dBottomCenter,
image='images/hotbar1.png',
scale=(1, 1, 20 / 164),
pos=(0, 0, 20 / 164)
)
# ---ここから追記---
# text window
how_to_use = \
'移動: W A S D\n' \
'アイテム選択: 123456789\n' \
'ブロックを置く: 左クリック\n' \
'ブロックを壊す: 右クリック\n' \
'カメラの切り替え: T\n' \
'操作説明を表示/非表示: X'
self.text_window = DrawText(
parent=self.a2dTopLeft,
text=how_to_use,
font=self.font,
)
# console window
wellcome_text = 'ようこそ Pynecrafter!'
self.console_window = DrawText(
parent=self.a2dTopLeft,
text=wellcome_text,
font=self.font,
pos=(0.05, -1.5)
)
self.console_window.start_time = time() # 実行した時間を start_time に記録
# ---ここまで追記---
# select hotabar
self.accept('1', self.select_hotbar, [1])
self.accept('2', self.select_hotbar, [2])
self.accept('3', self.select_hotbar, [3])
self.accept('4', self.select_hotbar, [4])
self.accept('5', self.select_hotbar, [5])
self.accept('6', self.select_hotbar, [6])
self.accept('7', self.select_hotbar, [7])
self.accept('8', self.select_hotbar, [8])
self.accept('9', self.select_hotbar, [9])
def select_hotbar(self, i):
self.selected_hotbar_num = i - 1
self.selected_block = UserInterface.hotbar_blocks[i - 1]
self.hotbar.setImage(f'images/hotbar{i}.png')
self.hotbar.setTransparency(TransparencyAttrib.M_alpha)
user_interface.py にテキストを表示するコードを追記します。DrawTextクラスのインスタンスを生成することで、右上の操作説明と左下のコンソールを表示します。追記部分を詳しく見ていきましょう。
# ---ここから追記---
# text window
how_to_use = \
'移動: W A S D\n' \
'アイテム選択: 123456789\n' \
'ブロックを置く: 左クリック\n' \
'ブロックを壊す: 右クリック\n' \
'インベントリの表示/非表示: E\n' \
'操作説明を表示/非表示: X'
self.text_window = DrawText(
parent=self.a2dTopLeft,
text=how_to_use,
font=self.font,
)
# console window
wellcome_text = 'ようこそ Pynecrafter!'
self.console_window = DrawText(
parent=self.a2dTopLeft,
text=wellcome_text,
font=self.font,
pos=(0.05, -1.5)
)
self.console_window.start_time = time() # 実行した時間を start_time に記録
# ---ここまで追記---
上記のコードがテキストを表示するコードです。
DrawTextクラスに、表示したい文字列、表示したいノード、フォントを指定してインスタンスを生成します。self.a2dTopLeft は2次元ノード(aspect2)の左上を表します。 引数pos=(0.05, -1.5)により、self.console_window を右に 0.05、下に 1.5 動かします。
self.console_window.start_time = time()により、実行した時間をインスタンス変数start_time に記録しておきます。コンソールのテキストを指定した時間で消すときに使います。
08_01_main.py を実行して、テキスト表示を確認します。左上に操作説明が、左下にコンソール文字列が表示できたら成功です。
次に、文字を消す方法を実装します。操作説明は常に表示されていると邪魔なので、ユーザーが 「x」キーを押すと、表示/非表示を切り替えられるようにします。そしてコンソールの文字列は 3秒経ったら自動で消えるようにしましょう。
テキストを消す
"""src/user_interface.py"""
コンストクタの最後に追記
# テキストウインドウを表示/ 非表示
self.accept('x', self.toggle_text_window)
# スクリーンを更新
self.taskMgr.add(self.screen_update, "screen_update")
ユーザーが「x」キーを押したとき、操作説明文を消すコードを実装します。
self.accept('x', self.toggle_text_window)により、「x」キーを押したとき toggle_text_windowメソッドが実行されます。
コンソールの文字列が3秒後に消える実装のため、タスクマネージャーのtaskMgr.addメソッドで screen_updateメソッドを指定します。
"""src/user_interface.py"""
メソッドの追加
def toggle_text_window(self):
if self.text_window.isHidden():
self.text_window.show()
else:
self.text_window.hide()
def screen_update(self, task):
# 3秒でコンソールの文字を消す
if self.console_window.getText() and \
self.console_window.start_time and time() - self.console_window.start_time > 3:
self.console_window.setText('')
return task.cont
toggle_text_windowメソッドは、テキストウインドウの表示/非表示を管理します。isHiddenメソッドで、テキストウインドウが非表示(hide)であるか確認して、表示⇄非表示を切り替えます。
screen_updateメソッドは、フレームごとに画面を更新します。
time() - self.console_window.start_time はコンソール文字列が表示されてからの経過時間を表し、経過時間が3秒以上になったら、self.console_window.setText('')により、テキストを消します。
08_01_main.py を実行して、所定の動作が行われるか確認してください。
次にデバッグモードを実装します。操作説明の代わりにプレイヤーの情報を画面上で確認できるようにします。
プレイヤーの情報を表示する
"""src/user_interface.py"""
def screen_update(self, task):
# 3秒でコンソールの文字を消す
if self.console_window.getText() and \
self.console_window.start_time and time() - self.console_window.start_time > 3:
self.console_window.setText('')
# デバッグモード
if self.mode == 'debug':
position = self.player.position
direction = self.player.direction
velocity = self.player.velocity
text = f'player x: {round(position[0], 1)}\n' \
f'player y: {round(position[1], 1)}\n' \
f'player z: {round(position[2], 1)}\n' \
f'player heading: {int(direction[0])}\n' \
f'player pitch: {int(direction[1])}\n' \
f'player roll: {int(direction[2])}\n' \
f'player velocity x: {round(velocity[0], 1)}\n' \
f'player velocity y: {round(velocity[1], 1)}\n' \
f'player velocity z: {round(velocity[2], 1)}\n'
self.text_window.setText(text)
return task.cont
プレイヤーの位置、方向、速度を画面に表示するために screen_updateメソッドにコードを追記します。
フレームごとに、position、direction、velocity を取得して、テキストを書き換えてやります。
"""src/mc.py"""
from direct.showbase.ShowBase import ShowBase
from panda3d.core import *
from . import *
class MC(ShowBase, UserInterface):
def __init__(self, ground_size=128, mode='normal'): # 追記
self.mode = mode # 追記
# ShowBaseを継承する
ShowBase.__init__(self)
self.font = self.loader.loadFont('fonts/PixelMplus12-Regular.ttf')
UserInterface.__init__(self)
# ウインドウの設定
self.properties = WindowProperties()
self.properties.setTitle('Pynecrafter')
self.properties.setSize(1200, 800)
self.win.requestProperties(self.properties)
self.setBackgroundColor(0, 1, 1)
# ブロック
self.block = Block(self, ground_size)
# プレイヤー
self.player = Player(self)
# ゲーム終了
self.accept('escape', exit)
def get(self, var):
try:
return getattr(self, var)
except AttributeError:
return None
def set(self, var, val):
setattr(self, var, val)
MCクラスを修正します。
コンストラクターの引数に mode='normal' を追記します。これでモードを指定してゲームを実行できるようになります。normal(通常モード)、debug(デバッグモード)の2つのモードを切り替えることができる設計にしました。
"""11_01_main.py"""
from math import *
from src import MC
class Game(MC):
def __init__(self):
# MCを継承する
MC.__init__(self, ground_size=32, mode='debug') # 追記
# 座標軸
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()
08_01_main.py をコピーして、11_01_main.py を作成します。
MCクラスのコンストラクタを実行するコードに、引数mode='debug' を指定します。これでデバッグモードでゲームを実行できます。
11_01_main.py を実行すると、デバッグモードでゲームが開始されます。プレイヤーの情報が画面左上に表示されます。プレイヤーを動かすと、プレイヤー情報が更新されます。今後は、このデバッグモードでプレイヤー情報を確認しながら開発を進めていきましょう。
最後に起動画面(スプラッシュスクリーン)を実装します。起動画面はゲームの演出として利用されます。起動画面が表示された 5秒後にゲームが開始されるように実装していきます。
起動画面の画像を作成
ドット絵エディターで起動画面に表示する画像を作成します。
上図は Aseprite で起動画面を作成中のスクリーンショットです。サイズは 120x80で作成し、出力時に 1200x800 にリサイズしました。右上のキャラクターはパンダのアバターで、このゲームが Panda3D で作られていることからデザインしました。
ご自分で作成されない方は以下のファイルをダンロードしてお使いください。imagesディレクトリに、pynecrafter_splash.png の名前をつけて保存してください。
aspect2d(2次元の専用ノード)
シーングラフについて説明します。
Panda3D では2次元のオブジェクトを render2dをルートとして、シーングラフで管理します。通常の2Dオブジェクトは aspect2dノードの下に配置します。2Dオブジェクトを操作(移動したり、消したり)するには、aspect2dノードの下に新しいノードを配置して、その中に2Dオブジェクトを配置します。
起動画面を表示し、5秒後に消す
"""src/user_interface.py"""
コンストラクタの最後に追記
# スプラッシュスクリーン
self.splash_screen_node = self.aspect2d.attachNewNode("splash_screen_node")
self.splash_image = DrawImage(
parent=self.splash_screen_node,
image='images/pynecrafter_splash.png',
scale=(3 / 2, 1, 1),
pos=(0, 0, 0),
)
loading_text = 'creating a new world...'
loading_text = '新しい世界を創造しています...'
self.loading_text = DrawText(
parent=self.splash_screen_node,
text=loading_text,
font=self.font,
pos=(-.2, -.5, 0),
scale=0.1,
)
self.taskMgr.doMethodLater(3, self.close_splash_screen, "close_splash_screen")
メソッドを追加
def close_splash_screen(self, task):
self.splash_screen_node.detachNode()
self.console_window.start_time = time() # 実行した時間を start_time に記録
return task.done
起動画面を表示・非表示するコードを追記します。
self.aspect2d.attachNewNode("splash_screen_node")により、aspect2dノードの下に新しいノード(splash_screen_nodeノード)を作成します。DrawImageクラスを使って、スプラッシュスクリーンの画像を splash_screen_nodeノードに表示します。起動画面に表示するテキストは、DrawTextクラスを使って、splash_screen_nodeノードに表示します。
taskMgr.doMethodLater は遅延実行を行うメソッドです。第一引数で指定した数字の秒数が経過すると、第2引数のメソッドが実行されます。
close_splash_screenメソッドは、splash_screen_nodeノードをデタッチ(再利用しないノードを完全に削除)します。起動画面が消えてから 3秒後にコンソールの文字列が消えるように、self.console_window.start_time = time()を記載します。
以上で、今回のプログラミングは完成です。
11_01_main.py を実行して、ゲームを開始します。起動画面が表示され、5秒後に消えることを確認してください。起動画面が実装されると、ゲームらしくなりましたね。
次回はインベントリ(アイテム一覧のホップアップ画面)を実装します。ユーザーが「e」キーを押すとインベントリが表示されます。そしてインベントリからホットバーに表示されているブロックを変更できるようにします。お楽しみに。
前の記事
Pythonでマイクラを作る ⑩設置するブロックを選択する
次の記事
Pythonでマイクラを作る ⑫インベントリからブロックを選択する
その他のタイトルはこちら
この記事が気に入ったらサポートをしてみませんか?