With very few exceptions, nearly every game features some kind of menu-based user interaction for selecting game modes, browsing servers, setting game, and graphics options, or chatting with other players. Most likely, the games you are going to create with Panda3D will also have such requirements and you will need to create buttons, text input fields, loading bars, or whatever controls suit your needs. To make things easier for you, the Panda3D engine comes with a set of user interface classes that make it very easy to place controls on the screen and make them react to the players' actions.
Go back to Chapter 1 and follow the steps of the recipe Setting up the game structure if you haven't yet and you are set to go on with the following tasks.
Let's create a user interface:
Application.py:
from direct.showbase.ShowBase import ShowBase from direct.gui.DirectGui import * from direct.interval.IntervalGlobal import * from panda3d.core import * class Application(ShowBase): def __init__(self): ShowBase.__init__(self) self.nameEnt = DirectEntry(scale = 0.08, pos = Vec3(-0.4, 0, 0.15), width = 10) self.nameLbl = DirectLabel(text = "Hi, what's your name?", pos = Vec3(0, 0, 0.4), scale = 0.1, textMayChange = 1, frameColor = Vec4(0, 0, 0, 0)) helloBtn = DirectButton(text = "Say Hello!", scale = 0.1, command = self.setName, pos = Vec3(0, 0, -0.1)) self.gender = [0] genderRdos = [DirectRadioButton(text = "Female", variable = self.gender, value = [0], scale = 0.05, pos = Vec3(-0.08, 0, 0.05)), DirectRadioButton(text = "Male", variable =self.gender, value = [1], scale = 0.05, pos = Vec3(0.16, 0, 0.05))] for btn in genderRdos: btn.setOthers(genderRdos)
Application
class:def setName(self): self.acceptDlg = YesNoDialog(text = "Are you sure?", command = self.acceptName) def acceptName(self, clickedYes): self.acceptDlg.cleanup() if clickedYes: self.loadName()
def loadName(self): self.waitBar = DirectWaitBar(text = "Loading", range = 100, value = 0, pos = Vec3(0, 0, -0.3)) inc = Func(self.loadStep) show = Func(self.setNameLabel) load = Sequence(Wait(1), inc, Wait(2), inc, Wait(1), inc, Wait(3), inc, show) load.start() def loadStep(self): self.waitBar["value"] += 25
Application.py:
def setNameLabel(self): title = "" if self.gender[0]: title = "Mister" else: title = "Miss" self.nameLbl["text"] = "Hello " + title + " " + self.nameEnt.get() + "!"
After importing the packages we need for this recipe—most notably, direct.gui.DirectGui
for the user interface functionality—we go on to add a text input field, a label, a set of radio buttons and a button to confirm the name entry. We also use the pos, scale
, and text
parameters to define the positions, scales, and the texts of the controls and hint the label that its text will change by setting textMayChange
to 1. Also note the variable
and value
parameters used when setting up the radio buttons. The former defines the variable that value
will be stored into when the radio button is active. Don't forget to pass data to value
using a list! Further, we pass the method to call when being clicked by using the command
parameter of our confirmation button. To close step 1 of this sample, we iterate over the radio buttons, informing each button about the other buttons in the radio group so that only one option can be selected at a time.
In the next step, we add the setName()
and acceptName()
methods, which cause the application to display a confirmation popup after the user clicks the Say Hello! button. If the user clicks Yes, the program proceeds, otherwise the popup is hidden and nothing whatsoever happens.
We then proceed to step 3, where we add a progress bar to our user interface. The range
parameter sets the maximum value for the progress bar, whereas value
defines the initial progress shown after the control is added to the screen. We want to slow things down for this sample to see the progress bar in action. Therefore, we call loadStep()
in a Sequence
that waits a short moment between each step. This also allows us to see how we are able to update the progress bar's current value
.
In the last method we add to the Application
class, setNameLabel()
, which is called last by the sequence that controls our progress bar. Here we finally modify the text
attribute of the label control to display the name that is entered into the text input field.
The GUI features of Panda3D go beyond what you have seen in this recipe so far, of course. The following section is meant to give you an idea about what else you can do with the engine's DirectGui
library.
Besides the DirectEntry, DirectLabel, DirectButton, DirectRadioButton, YesNoDialog
, and DirectWaitBar
controls we used so far in this recipe, there are several more that might be useful for your purposes:
DirectCheckButton
is a button control that toggles between a checked and unchecked state every time it is clicked.DirectDialog
is used for building popup dialogs like the confirmation used in the sample code. There are several pre-built default dialogs included in the DirectGui
library like YesNoDialog, OkDialog
, or RetryCancelDialog
.DirectFrame
acts as a container for controls. This allows us to group multiple controls and positions them relative to the frame.DirectOptionMenu
works like a drop-down menu. When clicked, a menu opens and the control state is set to the selected item.DirectScrolledList
is a container control similar to DirectFrame
, with the difference that items are being placed in a scrollable list.DirectSlider
is a control that allows the user to select an arbitrary value between two boundaries.DirectScrollBar
allows you to build controls similar to DirectScrolledList
by hand.DirectScrolledFrame
works just like a DirectFrame
, but it allows objects that are positioned outside of the container's boundaries to be reached using scroll bars.Apart from the parameters we used for setting up our sample GUI, there are some more common options that allow customizing Panda3D's user interface controls:
frameSize
makes it possible to set the measures of the control using a four-component vector that specifies the left, right, bottom, and top positions of its frame.frameColor
sets the color of the user interface control element.image
sets the texture that is rendered on the control.geom
allows to set a Geom
object that is rendered in place of the control.suppressKeys
turns off global keyboard events when set to 1. This is useful for implementing a menu for the pause state of a game, as it makes sure that the game logic is not notified about any keys being pressed when interacting with the user interface.suppressMouse
works like suppressKeys
, but for the mouse.