Making a new mode:
A mode defines the specific reaction your game should have to various events that occur. If you want the player to receive 1000 points every time a specific shot is made, you will define that code within a mode.
The Mode is the basic building block of coding your own game; Modes can be as simple as defining a response to a single switch in your machine or they can handle every switch in your machine as the complexities of the sequences in which those switches might be hit. Modes might show something specific on the display or play background music while the mode is active, or they might be “invisible” as far as the player is concerned.
Modes in PyProcGame may:
- Respond to switch events
- Register/respond to timers
- Enable/disable/schedule lamps
- Enable/disable/schedule coils
- Play sounds
- Show animation/text on the display
When a mode is active it means it is present in the game’s ModeQueue (
self.game.modes). The ModeQueue is a priority-sorted list of the Modes in the game. Inactive modes do not receive notification of switch (or other event) notifications.
In SkeletonGame, each Mode is defined by the game programmer as a subclass of “AdvancedMode” –let’s explore the creation of a mode that will serve as a skillshot.
Defining a new Mode
To create a new
SkillshotMode class, we create a file called
my_modes/SkillshotMode.py with the following contents:
super(SkillshotMode,self).__init__(game=game, priority=20, mode_type=AdvancedMode.Game)
This file doesn’t define much functionality for the mode, but provides several key portions of the definition. At line 1 above, we see the name of the new class (
SkillshotMode) and its parent class (
AdvancedMode –from which our new class inheriets a lot of functionality).
Line 3 above defines the
__init__() function –the constructor method that is called whenever this class is created. This function must be included in your class definition, and line 4 includes the call to
super() which gives the
AdvancedMode class information about how your class should be treated.
priority is the numeric priority of this mode relative to others. Being higher priority means your mode will receive switch events before other events do. The
type indicates when the mode will be activated.
|The mode is activated when a ball starts and deactivated when the ball ends. In a typical 3-ball game, that means your mode is activated/started and deactivated/stopped 3-times per player.
|The mode is activated when a player starts a game and is deactivated when the (last) player’s game ends. In a typical 3-ball game, that means your mode is activated/started exactly once per game and deactivated/stopped exactly once per game, regardless of the number of players who are added into that game.
|The mode is activated when the game code is is initially launched and deactivated when the game code is quit/shutting down. The mode will remain active across multiple games, across multiple players.
|The mode is not auto-added or auto-removed. The programmer will take responsibility for activating the mode and deactivating the mode whenever it is appropriate to do so. NOTE: If a mode doesn’t specify a
mode_type in the call to
super(), this is the default/assumed type.
Because the mode is created with a type of Ball, the mode will be added/activated when each new balls starts. We will manually deactivate the mode early, once the skillshot phase of the ball is over.
Adding the mode to the Game class
We need to modify the
T2Game.py file to import the new mode. We will modify the modes import line as:
from my_modes import BaseGameMode, ExBlankMode, SkillshotMode
and futher on in the
__init__ method of the
T2Game we need to add an initializer for the
SkillshotMode (right after the other mode initializers). This line will be added:
self.skillshot_mode = SkillshotMode(game=self)
If we were to test the game code, start a game, and watch the console output, we would see the mode queue will contain our new
SkillshotMode. Unfortunately, the mode’s lack of interactivity with the player would make it otherwise impossible to verify its existence.
Enhancing the SkillshotMode functionality
Modes receive events as long as they are active. Specific function names within the Mode definition will automatically be called when these events occur. The following methods can be provided:
A mode that defines methods matching a speciifc naming convention indicates it wants the framework to call this method to “handle” the switch event (i.e., a switch handler method). When a Mode is created, its methods are scanned for any methods that match the naming pattern:
sw_<switch name>_<switch state>(self,sw) where
<switch name> is a valid named switch and
<switch state> is either
For example, the method:
def sw_gripTrigger_active(self, sw):
would be invoked by the framework when the switch corresponding with label
gripTrigger is activated (note that the binding of the identifier
gripTrigger and the specific machine switch location is defined in the machine.yaml).
Similarly, a method named
sw_startButton_inactive() would be called when the
startButton switch changes to an inactive state.
An optional state duration can be specified, in the case that you wish to respond to an event only after the switch has been in that state for a certain amount of time. Adding a
for_<time period> suffix to the normal switch handler naming convention enables this behavior:
sw_switchName_active_for_500ms(self, sw) | called once switchName is active for 500 milliseconds
sw_switchName_inactive_for_3s() | called once switchName is inactive for 3 seconds
sw_switchName_inactive_for_20ms() | called once switchName is inactive for 20 milliseconds
Mode life-cycle events
There are two standard method signatures which will be automatically invoked when a mode is activated (added to
game.modes) and a mode is deactivated (removed from
# called when the mode has been actived
# called when the mode is no longer active
Because these method names are tied to activation and deactivation, you should combine this information with the
mode_type (or your knowledge of when you manually add/remove this mode) to know when these methods will be called. A different approach would be to use SkeletonGame event handlers, as described below.
Additionally, PyProcGame provides a method signature that is called every “tick” for an active mode, called
tick is a single pass through
run_loop() as it completes one cycle of reading events from the P-ROC and processing them (by informing modes and running their responses). A game may have a tick rate anywhere from 300Hz to 7000Hz so the tick method would potentially be called 300 to 7000 times per second; in order to keep tick rates high, any mode that needs a
mode_tick handler should have that code be extremely brief in order to keep the run loop running quickly. If the tick rate drops too significantly, the
run_loop() will not tickle the watchdog circuit in the P-ROC hardware, and your machine will go dark. Don’t call
sleep() in code.
These are regular gameplay events that the SkeletonGame system tracks. Your mode will be informed of the game event occuring so you can run code to respond to the event. The following page details all the built-in events supported by SkeletonGame:
The SkeletonGame Event System
If your code defines the methods described above, the framework will call the method when that event occurs (provided your mode is active when the event occurs). Modes can also request to postpone the event propagation (i.e., the calling of the next event handler) by returning a number of seconds that the mode needs to ‘finish up’ (e.g., play a special animation or sound) or can delay and stop further propegation by returning a tuple of the delay in seconds and the second entry is True
Any method can be automatically called after a specified amount of time passes (i.e., delay) using the pyprocgame provided method
procgame.game.Mode.delay(). For example, suppose there is a mode that should only be active for 10.5 seconds. A mode can be “deactivated” (removed from the game’s mode queue) with the line
self.game.modes.remove(self), so we define a new method to contain that line, and use a delay to call that method later.
Delay also takes two optional arguments, a parameter to be passed to the delayed method (
param) and a name (
name) which is useful for canceling the delay before it fires. If a delay is not explicitly named, a name will be assigned and will be provided as the return value from the call to
The following is an example of canceling a delay before it fires, by storing the return value from
delay(). This example would give the player a bonus for hitting
target2 within 4 seconds of hitting
def evt_player_added(self, player):
def sw_target1_active(self, sw):
self.delayed_name = self.delay(delay=4.0, handler=self.reset_var)
def sw_target2_active(self, sw):
# cancel the delay:
if( self.game.getPlayerState('t1_hit')==True ):
# player gets a special bonus for hitting both
self.game.setPlayerState('t1_hit', False) # reset now, we've awarded
# the player only hit this one target
# too long has passed; reset the variable
# cancel the delay, the mode is over
Lighting Lights, Flashing Flashers
While the above example illustrates some advanced logic using timers, it’s not very exciting for the player, who receives zero feedback while playing the game. Flashing lights are a huge part of the pinball experience.
Lights are part of the physical machine. From Mode code, the identifier
self.game.lamps leads to the dictionary of lamps in this machine. The specific lamp to be controlled is an identifier defined in your machine yaml file. A lamp can be
enable()‘d (turned on),
disable()‘d (turned off), or
schedule()‘d to flash with a specific pattern. Examples of all three are given below, assuming the lamps named
startButton are defined in the
PRLamps: section of the machine yaml, and that
someButton is a switch defined in the
PRSwitches: section of the machine yaml:
def sw_someButton_active(self, sw):
# flash the start button ON/OFF, forever (until set otherwise):
# turn on the lamp at target1
# turn off the lamp at target2
Displaying content on the HD Display
SkeletonGame provides additional display functionality on top of PyProcGame’s existing Layer metaphor.
A mode with something to show will set its
self.layer to a specific instance of a Layer object (Note: Layer is actually the base-class, there are lots of specific sub-classes of Layer that provide additional functionality. The many Layer subclasses represent the various display types for content that might be shown on the DMD. Choose the right specific type of Layer based on what you want to display!)
Layers are like the Layer metaphor in Photoshop, etc. Layers are optional – if self.layer is not set, nothing is displayed by this Mode. The Z-Ordering of the Layers is dictated by the priority of the Modes highest toward the top of the display stack, lower priority mode’s appear below higher priority modes.
Because the Layer tools can be a little daunting at first, SkeletonGame provides a very powerful display helper method called
displayText(), which will get text on the display without the programmer needing to worry about layers.
The complete usage of
displayText will be documented in another entry, however the basic form is:
which would display “Message” in the default font on the HD display for the player to see. To provide a multiline message, use the python list syntax (
]) and each list entry will appear on its own line.
To display an animation (or single frame, etc) on the display, provide a second argument to displayText, which matches a valid key in the animations section of the
asset_list.yaml file. This example would display “Free” “Points!” on separate lines superimposed on a sequence of animated fire:
def sw_target2_active(self, sw):