All posts by mocean

Using the GUI Tool to make RGB Lamp shows

Preparing to use the GUI Tool (for RGB LampShows, etc)

To start, you need an image of the playfield and the resolution matters because that’s how big the window will be when you use the tool later. At the time of writing resizing the window isn’t supported, which is lame, I know. The playfield image is better if it’s easy to somewhat ignore. I like to wash these images out (make them grayscale) so I can clearly see switches and lamps on top of the PF image. It is also crucial that the image is as “normal” (not distorted and directly overhead) as possible. A scan would be best, but then you lack ramp placement.

Below are three example images that work well:

The first is just lines, taken from JD’s manual page.  Very clear, very easy to see, but a bit hard to relate to the artwork.

The next example is of the Buffy playfield; line drawing super imposed on a washed out version of the playfield art, making it a bit easier to orient the user of the tool:

Last is an example of washed out playfield art under line drawings, complete with extra space on the right hand side with text based labels as placeholders for placing the coin door switches within the tool.

The command line example below supposes the filename is playfield.png.

Run the tool

You run the OSC tool using an extra command line argument, so such:
python switchMatrixClient.py -y -i playfield.png

Once you’ve run the program this way once, in the GUI’s menus, save a layout file, so you can keep your layout file (which includes lamp and switch placement).

Laying out switches and lamps:

In the GUI menus select “Layout Mode” which pops up two windows. One contains a button for each switch the other a button for each lamp. To place a lamp (or switch), left-click the lamp button, then click the playfield to place the lamp. The button stays an alternate color so you know which ones you’ve already placed.

(Notice in the screenshot above, the playfield image has not been washed out and as a result the interface is very hard to read).

For any switch or lamp you don’t want to place (and don’t want to see), the GUI provides an option to hide specific switches/lamps in the menus.

You’ll want to align your lamps as accurately to the inserts (or GI) locations as possible. When you make an RGB show, the position of the lamps (as you’ve placed them) will be used to determine the colors. Proper alignment will be key.

Creating an RGB light show

RGB Lamp show animation works by importing a bunch of images into the tool. Each image represents one time step in the RGB show. The tool then overlays each image over the playfield, and detects the pixel color that overlaps each lamp. Ideally, the images are the same size as the playfield image, so it’s easy to color the lamps.

The RGB lamp show tool is in the menu under Lampshows->Create an RGB Show (experimental) and the resulting pop-up is a prompt to open a sequence of images. Once you’ve opened the sequence you can rearrange the individual frames, add/remove frames, and save the sequence.

See the examples below to understand the contents of a generated file.

Recommended “manual” workflow:

Take a screenshot of the tool, with the lamp locations visible. Save the screenshot as an image, and use it as a base-layer in a paint program. Create more layers and “paint on top of” the playfield image. Save the layers as individual images. Each layer/image/frame is one moment in time/step in the animation.

Better workflow:

Even better is to use an animation tool. I use synfig which is a good, free 2D animation suite. Take your playfield screenshot as the background and animate different colored shapes moving over the playfield. Export the individual frames of the animation (as pngs). Bring the frames into the RGB maker so you can generate an RgbShow.

Usage/Playback:

The RgbShow file format is supported as a valid lampshow format in attract.yaml, provided they are defined in the asset_list.yaml under an RGBShows: section, similar to the LampShows section, as so:

They can be manually played by the rgbshow_player object that exists in the game. For example:

And technically they can be loaded by that object as well (e.g., if you prefer not to use the asset manager) as such:

What’s in a .RGBShow file?

The RgbShow file format contain a dictionary at the top that defines mappings of human readable ASCII characters to specific hexadecimal color values (as hex: rrggbb). The body of the lampshow contains the names of the lamps and a sequence of colors — each column entry is that lamp’s color per time unit (the color is the defined in the dictionary above).

An example is provided below to show how the files are internally structured. Notice that lines that start with # are comments. Lines that start with ! come in two forms: the first are directives and can be overridden when played via play_show(), the second are the mappings of characters to colors. Note that the specific mapping syntax used => vs. ~> adjusts the color cycling behavior as indicated in the comments of the example. The mappings map from a single ASCII character to the specific 24bit color for the lamp in hex.

The body of the show ignores leading whitespace and defines what a named lamp/led should be doing over the course of the show playback.

  • Caveats as of 2/5/2018 on the dev branch*

Gradients are okay but if you use more than 64 unique colors, this version of the tool gets unhappy –I may patch you a full color version in the future, but the RgbShow output is pretty gross/unreadable if we store full color entries for each step in the show.

The SkeletonGame Event System

A major goal of SkeletonGame is to keep you from having to wrestle with implementing the logic that controls the typical pinball game state stuff (e.g., is a ball starting? stopping? drained? tilting?). The idea is that the majority of your code can live in “modes” and your modes will be notified about these game-play events firing. To be notified of these events, your mode should provide a method with the same name as the event, and your mode needs to be active (unless otherwise noted in the table). Your mode will be notified of the event by the framework calling your method. Modes with higher priority will receive the event first. Returning a number (of seconds) will slow the event propagation so that your mode can do whatever it needs to without interruption from a lower event. If an argument is specified, your method can receive that argument and use it to gain additional information about the specific event.

The following table summarizes the current events supported in SkeletonGame (dev branch) as of 3/18/2017:

event name fired when… return value delays…
evt_balls_missing the trough reports insufficient balls, blocking the start of a game no next function, so return value is irrelevant
evt_balls_found a previous search for balls (that had blocked game start) has been resolved nothing to delay
evt_game_starting the user has pressed start from attract mode return value delays the actual start of the game
evt_player_added$ called every time a player is added to the game. Will be called at least once (after evt_game_starting); argument is the newly created player no delay
evt_ball_starting called when a ball is about to start for a player (prior to the ball being placed in the shooter-lane, flipers being enabled, etc) return value delays the actual start of the ball
evt_ball_saved The moment a ball has been saved by the ball saver no delay
evt_shoot_again$ a ball has drained but an extra ball is available; NOTE: evt_ball_starting WILL fire after this as a ball will be starting after this delays the start of the ball
evt_ball_ending the final “in-play” ball has drained; args=(shoot_again,last_ball) to indicate if the player has any extra balls, and/or if this is the player’s last ball. the end ball process, which will initiate a bonus tally and eventually start the next ball
evt_tilt_ball_ending ALTERNATIVE to evt_ball_ending when the game has been tilted. evt_ball_ending will NOT fire. This event indicates all balls are back. Good place to admonish the player (note that evt_tilt will have fired before this, if not also evt_tilt_warning) either end of this ball or slam_tilt complete (game reset)
evt_single_ball_play called when the number of balls in play decreases to 1. Indicates the game is no longer in a multiball mode No return value
evt_tilt_warning tilt switch has been tripped; arg is number of times the player has been warned nothing
evt_game_ending the game has ended. All balls are done. Will also fire following a slam-tilt the game ending sequence follows; fire high score entry, then game ended
evt_initial_entry if the player has been awarded a high score of some sort; this event will fire before each individual high score entry prompt. Arg is the category record of the specific award. This event may potentially multiple times after each entry has been entered. blocks the actual entry of initials
evt_game_ended after high score entry is complete (will fire even if high score entry does not occur) this delays the call to reset() that puts the machine in a clean state before the start of the next game
evt_tilt if the player has tilted the machine. the arg is a boolean indicating if the machine was slam tilted (True) or not (False) n/a
evt_volume_up the volume has been changed. Use this event to show something custom to the player. The arg is the system volume as an integer (0..10). n/a
evt_volume_down the volume has been changed. Use this event to show something custom to the player. The arg is the system volume as an integer (0..10). n/a

$ –> means even inactive modes will receive this event.

Step 5: Making a Mode

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:

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.

The 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.

mode_type = which means…
AdvancedMode.Ball 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.
AdvancedMode.Game 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.
AdvancedMode.System 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.
AdvancedMode.Manual 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:

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:

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:

Switch events

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 active or inactive.

For example, the method:

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:

Examples:
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 game.modes), respectively.

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 mode_tick(). A 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.

SkeletonGame events

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

Timers

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 delay().

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 target1

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 target1, target2, and 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:

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:

Step 1: (Alternate) Manual Installation for OSX/Linux

There is no one-click installer for OSX at this time. These steps have been tested to work on OSX 10.10; if you’re on Linux, I’m sure you can figure out how to adjust these steps accordingly.

  1. Head into System Preferences->Security & Privacy->Unlock and select: ‘Allow apps from: Anywhere’; When you’re done with this process you can set that back to its previous setting.
    Similarly, you need to disable “System Integrity Protection” in order to get past the unsafe use of relative path libftdi1.2.dynlib in /Library/Python/2.7/site-packages/pinproc.so with restricted binary –Instructions to disable System Integrity Protection (which isn’t necessarily a great thing to do: apple SIP instructions except you are doing to use the disable argument instead of  enable.
  2. Install XCode from the Mac App Store (note: you need to launch it once after installing it, in order to complete the installation–it can then be closed)
  3. Download libusb-1.0.20
    Create a new folder in your user folder called proc and unzip this into that new folder.
    FYI: ~ is short for the current user’s home folder, we we call this target folder “~/proc” from here on out.

Open a terminal window and change to the ~/proc/libusb-1.0.20 folder and run:

(Note: on Linux, the preceding requires libudev, which is available for ubuntu in the apt packages libudev1 and libudev-dev)

  1. Download libusb-compat
    Unzip to /proc and change to that folder (~/proc/libusb-compat-0.1.5/)

  1. download cmake 2.8.12.2 universal dmg
    install the cmake package by double clicking the pkg file
    Choose the default /usr/bin location and choose to install the command line tools (On ubuntu 16.04 sudo apt-get install cmake appears to install a usable cmake for our purposes)On OSX, newer installs of CMake include instructions to install the command line tools. This is done from terminal as:
  1. Download libftdi1-1.1

unzip into ~/proc folder
change to the newly created libftdi folder with cd

  1. download yaml-cpp 0.2.6
    unzip and copy to ~/proc
    change to that newly created yaml-cpp folder with cd

  1. download libpinproc via git clone, as described below (linux users can use sudo apt-get install git; OSX users can navigate to the github url and download the ZIP.

(take note that the cmake line ends with ..)

Change the include lines in the file proc/libpinproc/CMakeLists.txt to the following:

include_directories(${PINPROC_SOURCE_DIR}/include ${EXTRA_INC} /usr/local/include ${FTDI_INC} /usr/local/include/libftdi1 /usr/local/include/libusb-1.0)

Add the following line with the others below it:
set(lib_ftdi_usb usb-1.0 ftdi1)

I also comment out the build of pinproctest. It’s finicky to build and not strictly necessary. This changes lines in that file to reflect the following:

#add_executable(pinproctest
# examples/pinproctest/pinproctest.cpp
# examples/pinproctest/drivers.cpp
# examples/pinproctest/dmd.cpp
# examples/pinproctest/switches.cpp
# examples/pinproctest/alphanumeric.cpp
#)
target_link_libraries(#pinproctest

Save the file

  1. download pypinproc (dev branch) via git clone, as described below:

(Linux note: Ubuntu 16.04 has Python 2.7 installed by default; I needed to apt-get install python-dev in order for the following to work)

  1. download pyYAML 3.12
    unzip and copy that folder to ~/proc
    change to that new pyYaml folder (cd ~/proc/PyYAML-3.12)

  1. Download pygame. Pick the “Lion apple supplied python” pygame-1.9.2pre-py2.7-macosx10.7.mpkg.zip
    Install the package(Linux: sudo apt-get install python-pygame)
  2. Download & install xquartz for mac (Linux users:skip)
  3. Install Python Pillow or Python Imaging Library (PIL):
13. For OSX (Pillow):

13. For Linux (PIL 1.1.7 or Pillow, instructions are for PIP):

Download PIL 1.1.7:(PIL) 1.1.7 source kit
unzip and copy to ~/proc
take a note of the folder name so you can cd to it

  1. PyProcGameHD-SkeletonGame (dev branch, for the latest)

This will install PyProcGame and SkeletonGame in ‘develop’ mode, such that when you import pyprocgame from any python script, it will use these files, from the current location (~/proc/skeletongame).

Install the PyProcGameHD SDL2 dependencies.

  1. Download and install SDL + PySDL2 0.9.5 for your platform:
    • PySDL2 0.9.5
      cd ~proc/PySDL2-0.9.5/
      sudo python setup.py install
    • PyOSC PyOSC 0.3.5b
    • Install the appropriate versions of the SDL2 and the SDL2_TTF libraries:
    • sdl2
    • sdl2_ttf
    • for many linux distros, SDL2 is installed via: sudo apt-get install libsdl2-2.0-0 libsdl2-image-2.0-0 libsdl2-ttf-2.0-0
    • Either set the PYSDL2_DLL_PATH (as per the instructions, here or add a PYSDL2_DLL_PATH in the game’s config.yaml.
    • Optional: For MP4 support install OpenCV and libffmpeg
      linux:https://github.com/BVLC/caffe/wiki/OpenCV-3.1-Installation-Guide-on-Ubuntu-16.04

to test, in python run:

The first three lines should return the prompt with no feedback. The last should print 6. This is how you know it worked.

This also updates the “procgame” command line tool, and the updated version works with the color file formats. From the command line running

procgame

Will show a list of available arguments/commands. For example, procgame dmdimage <file.dmd> <file.png> will convert a standard .dmd file into a single or series of png files.


Last but not least, if you want to use the GUI switch tester tool, you can get it from GitHub, here: OscSwitchMatrixGUI, which will require wxPython to be installed. –note, if you are in my class, I have posted a zip which includes the playfield image and the tool to Canvas, but you need to install wxPython before you can run it.


Many thanks to Brian Madden for his original work to record the PyProcGame install steps on OSX from his guide, here.

Step 1: Installation and Testing the Install (Windows)

Step 1: Install the environment.

Installing the PyProcGameHD/SkeletonGame environment requires installing a number of packages. The easiest way to get up and running is to use the Windows one-click installer. You can download the Windows All-in-One Installer here.

This will install P-ROC/P3-ROC Drivers, Python 2.7, SDL2, PySDL2, PyProcGame, PyProcGameHD, SkeletonGame, and so on.

I’m working on one-click installers for other platforms but they are far from done. Manual installation on OSX is described here.

Note: If you have previously run the installer, as of 6/6/2017, the new installer will install Python 2.7.13 and the dependencies required to run on Python 2.7; you should remove c:\Python26\ from your path before running the new installer.

Step 2: Run the sample game.

2a) Unpack the assets

Once the environment is installed you should confirm that everything is working. A sample game is included in:
C:\P-ROC\PyProcGameHD-SkeletonGame-master\SampleGame –assets for that project are located in a file in that folder called
UNZIP ME.zip; if you intend to run the sample you should unzip the folder.

2b) Run the sample game

Open a windows command-prompt (Win-R and type CMD.exe), switch to the folder that contains the game, and run it. To do that, after opening the command prompt, type the following:

These two lines will change the current working directory (i.e., folder), and then run the python file named T2Game.py in that folder.

If this works, you should see the progress bar pre-load the assets (images, animations and sounds), and then the game should start showing the “attract mode”

2c) Simulate play

To simulate game play:
Pressing ‘S’ will simulate pressing the Start Button on the cabinet and should start the game.
–press the ‘d’ key briefly to simulate the ball hitting the shooter lane switch
–press keys like ‘7’ ‘8’ ‘9’ to simulate hitting playfield switches
–Hold down the ‘1’ key to simulate closing the leftmost switch in the ball trough –the game will think all three balls are in the trough and end the ball–

If this worked, your environment is set and the simulator is working.

Step 2d) Optional: the graphical switch tester:

The graphical switch tester allows you to use an image of your playfield, layout the buttons and lamps on that image, and then test your machine by interacting with a GUI on top of that image.

If you have installed the GUI tool from the installer, a run_gui_SampleGame.bat file was created in c:\P-ROC\GUITool. Double-click that to run.

The contents of that file are, essentially:

where:
* config/t2.yaml is the path to the machine configuration file
* playfield.jpg is a playfield image shrunk to fit the screen.
* t2.layout is a layout file saved from this tool (a first run will not include this -l t2.layout argument)

Step 2: Config and Machine YAML files

Understanding the config.yaml:

A PyProcGameHD/SkeletonGame ‘game’ will look for a file called ‘config.yaml’ when it launches to try to load some environment-wide and game speciifc settings. Your ‘config.yaml’ can exist in the same folder as your game code, if it isn’t found there the system will check for it in a folder called ‘.pyprocgame’ in your user folder. Chances are, if you are running your game from a different folder than the one your game is in, it will rely on that fail-safe location.

It defines the paths to the asset files your game will need (images, lampshows, sounds), SkeletonGame specific settings (which modes to enable or disable), DMD specific settings (resolution, fps), window settings (border, scale, dot-effect), whether to connect to the real P-ROC hardware or not (‘FakePinProc’), and finally a mapping of keyboard keys to switch numbers (for testing when the real P-ROC is not connected).

If you open up your config.yaml file, you will see a very specifically formatted file that defines key/value pairs. The PyProcGame/PyProcGameHD/SkeletonGame code looks for the values of specific keys to set defaults for your game. YAML is a whitespace sensitive format, so the presence or absence of spaces may cause the file to no longer load properly. There’s a good, high-level overview of YAML here: http://docs.ansible.com/ansible/YAMLSyntax.html

The most important line to notice is the line that starts:
pinproc_class: –if this line is present and has a value of procgame.fakepinproc.FakePinPROC, then the simulator will be used and it will NOT connect to a real P-ROC/P3-ROC. If you place a # at the start of this line, it will be “commented out” so that the YAML processor ignores it. It is like deleting the line without actually deleting it. When you are ready to connect to a real P-ROC on a real pinball machine, you’ll want to comment out that line.

Understanding the machine yaml file:

Once you have your config.yaml the way you like it –that is, the display resolution has been set, the dot scale, dot effect, folders in which to find sounds and images and things– you need to define a machine configuration yaml file (or machine yaml file).

Your machine yaml is different from your config.yaml. A pinball machine is filled with switches, lamps, and coils, and we need a way to refer to them in the code. Identifying them based on their connection to the machine hardware (e.g., location in the lamp matrix, location in the switch matrix or the sw-16 board address and switch number) works, but it would be really cumbersome to write and to later debug. Who want’s to see things expressed in terms of S13, or C04?

This is the primary problem solved by the machine yaml file: you use it to define what components are attached to your machine and the yaml allows you to provide names (aliases) for each of the devices in your machine.

Below is an excerpt of the machine yaml file from the SampleGame provided with SkeletonGame. The file config\T2.yaml contains the entire configuration for this machine, which is a Williams Terminator 2. Looking at the excerpt you will see listings for coils, lamps, and switches. Each entry in the list (starting with a dash) is the friendly name for the device found at the corresponding number.

In SkeletonGame, it’s recommended that you store your machine config in the config folder.

Later, when you are writing code, you will be able to refer to the machine’s elements by these names. For example: self.game.lamps.mult2x.enable()

SkeletonGame adds some additional functionality to the machine yaml when compared to the PyProcGame format, specifically SkeletonGame adds support for the tags on switches/coils related to ball search. In addition, in order to make your life easier, some of the helper modes provided in SkeletonGame require certain switches to have specific names (eg, your ball trough switches must be names trough1 through troughN where N is the number of balls held in your trough, and trough1 is the leftmost switch —troughN is the switch closest to the shooter lane).

  1. config/machine.yaml

This is the machine definition file. It maps all the switches, lamps and coils in your machine to easier to use logical names. Some samples are available, including the T2.yaml example in the Sample Game

You must adhere to a few naming conventions:

  • your trough switches should be named trough1, trough2, .. troughN where N==num trough switches. OR your switches should be tagged as ‘trough’ as shown here tags: trough
  • your start button should be called startButton or tagged start_buton
  • your shooter lane switch should be called shooter
  • your (bob) tilt switch should be called tilt
  • your slam-tilt switch should be called slamTilt
  • ball search is handled differently! switches and coils should be tagged for ball search, as shown in the example.

defining your own machine yaml

If your game is a retheme/continued, based on an existing Williams or Stern boardset, someone may have already created a yaml file for you. You can check in shared/config for some existing yaml files. These files don’t conform to all of the naming requirements of the SkeletonGame naming convention, but starting from these files will likely make your job much easier. Also bear in mind that you might not like the names defined by someone else, and those names (except for those required by SkeletonGame) are totally at your discretion. If it seems like the name should be something else to you, go for it.

If your machine’s yaml doesn’t exist, you’ll need to grab a copy of the operator’s manual and generate entries for each of the lamps, switches, and coils. It can be helpful to use the machine yaml for another machine from the same manufacturer and vintage (and that machine’s manual) as a reference to get your machine’s yaml started.

For completely custom games running on the PinballControllers controller boards (PDB16, 8×8, etc), you can use the example pdb.yaml to get you started. Also, forum user JoeShabadu has written an amazing getting started guide that includes a post getting your machine yaml right for custom machines. Honestly, the whole thread is a good read if you’re starting from scratch on a custom machine.

sanity checking your machine yaml

This is optional, but a nice progress milestone for the impatient who want their game flipping now🙂

It can be a bit rough to try to get your game code up and running as the first thing you do, since you need to deal with your assets, etc. a good strategy is to use this code for a stripped down “first flips” game. It does nothing but load the machine yaml, initialize some things under the covers, and enable the flippers.

If you save the following into a file called ‘first_flips.py‘, copy your machine yaml into the same folder as this file (call it machine.yaml), and ensure that one of your lamps is actually called somelamp, then running this file (literally just double-clicking ‘first_flips.py’ and running it with Python or the better way, running it from the command prompt via python first_flips.py then your game should be up and flipping.

This is NOT a SkeletonGame game, and as such you don’t have any trough support, ball counting, or anything of the sort. It’s just a nice quick way to get your flippers active. If the game won’t run, it’s because your machine yaml is malformed.

If you are having problems with your yaml file, there are several good online yaml file validation sites (like YAMLlint) that can help you find spacing, or other syntax problems. These resources don’t know about the meaning of the specific tags of the machine yaml, so they can’t help you with those things, but for these basic problems (that can be very hard to see), these tools can be indispensable.

Step 3: Adding Assets (images, sound, etc)

Adding sound, video, fonts, etc. to your game

SkeletonGame provides an built-in asset manager that is driven by a simple YAML file format. On game launch, the asset manager will pre-load all the assets defined in config/asset_list.yaml. The asset_list.yaml, associates files (images, animations, sounds, music, fonts, and lampshows) with “friendly” names, and allows for custom settings for each of these items (e.g., change the default volume of a sound, change the x/y position of a sprite). Once these assets have been loaded, they can be used in code or in other locations by referring to their key (alias).

Looking at an example asset_list.yaml (this is the example that comes with SampleGame), we can disect the components expected to be present:

  1. Loader User Interface: The visual layout of the asset loading screen that will be shown while your assets are being pre-loaded (on game launch)
  2. LampShows: a mapping of lamp show file names and logical names (i.e., keys) by which they will be defined. Defining a lampshow in this file causes the lampshow to be parsed and validated during asset loading, so if there is in error in the lampshow you will find out immediately instead of when your code eventually goes to play the lampshow.
  3. Animations: a list of images and animations to be used in your game; minimally their file names and their logical names (i.e., keys). By including an entry the image or animation will be pre-loaded, ready for use in your game’s mode code. Supported formats include the ‘dmd’ format, many common single frame formats: jpg, gif, png (per pixel alpha supported), and some animation formats such as gif (for animated gifs) and single frame file sequences (e.g., a file range from my_image000.png through my_image029.png would be specified with the file name my_image0%3d.png and all 30 would be loaded as the frames of a single animation).
  4. HDFonts: Native system fonts (TTF) and aliases for specific sizes of those fonts
  5. Fonts: Bitmap fonts, based on the PyProcGame font format
  6. Audio, Music, Voice: Audio files named via key (wav or ogg format).

UserInterface — what the loading bar looks like

Settings in this section modify what the user sees when the game is pre-loading assets.

The splash_screen is an image file to be shown underneath the information displayed during pre-load. progress_bar defines the size and position of the progress_bar relative to the overall screen size. In the example above the full-width of the bar is 90% (width: 0.9) of the window, and 10% of the height of the window (height: 0.10). It is centered on the X-axis (0.5) and appears in the top 1/4 of the display. The border color of the bar is a bright magenta color (border: [255, 0, 255] –> means 255 of a possible 255 Red, 0 of a possible 255 Green, and 255 of a possible 255 Blue). The filled portion of the bar as loading progresses is shown as Red.

The text portion defines the color of the text used to indicate which file is being loaded, and the y_center defines the height at which the text will appear.

Those settings together produce the following loading interface for the user; while the text is clearly cut off, thankfully the user only sees this briefly. Note to self: add a font size option!!
preview of loading screen

Lampshows

lampshow files can be pre-loaded on asset load. The key is name by which you will refer to the lampshow files later when you attempt to play them, and the file is the filename, assumed to be located in the path ./assets/lampshows/

The above example loads two lampshow files, each of which has a key that is simply the name of the file without the .lampshow extension. You can find more information about the lampshow format, here

Animations (and Images)

Both single images and animations are defined in a section called Animations: The minimal information for an entry is typically a filename (file:) and an alias (key:). Code can then refer to the asset by the given alias.

file: can be a single .gif, .png, .jpg, or .dmd file, or can refer to a numeric range of files using the %d operator. Three simple examples are given below:

Notice on the third entry (robot) the file there is an additional parameter x_loc which will move the image over 50 ‘dots’ when it is displayed. (y_loc would move the image down, and negative values can be used to move objects in the opposite direction)

Animations as sequences of images:

For example, suppose there are 91 images called explosion1_0000.png through explosion1_0090.png and these images should be treated as consecutive frames of an animation. If all these files are placed in ./assets/dmd/explosion1_64x48/ (just to reduce clutter), specifying the file: 'explosion1_%04d.png' will load a file ./assets/dmd/explosion1_64x48/explosion1_0000.png as the first frame, ./assets/dmd/explosion1_64x48/explosion1_0001.png and so on until the first consecutive file number is not found. You can’t skip numbers in the middle, and you cannot skip the first (0) filename. The %04d means 4 digits, padded with zeros to the left (so 0000, 0001, …). %2d means numbers up to two digits, but without the zero padding, so 1, 2, 3, ..9, 10.

You can also use .mp4 files that contain animations. More documentation about their use is forthcoming.

frame_time: indicates how many times the frame will be shown before it advances to the next frame (note the default system framerate is 30 frames per second, and this can be changed in your config.yaml file). By default, the frame_time is 1.

repeatAnim: indicates whether or not the animation should loop, playing over and over again. If omitted, the default is False. (Notice that True and False are indicated with a leading capital letters, as in Python itself).

compositeOp: 'blacksrc' can be indicated to cause the framework to treat black (0,0,0) pixels as transparent. There is also a 'magentasrc' (255,0,255) and 'greensrc' (0,255,0). For PNG files, per-pixel alpha transparency and whole image alpha transparency is supported, so saving images as PNG files with per-pixel alpha (RGBA/32bpp) eliminates the need for using compositeOp.

Sounds

The Audio: category allows the definition of music and sounds. Use .wav or .ogg (Ogg Vorbis) file formats. MP3 is not recommended at this time.

The following example should be straight forward.

  • Music files are expected to be found in ./assets/sound/music
  • Effects (sound effects) are expected in ./assets/sound/sfx
  • Voice (call-outs) are expected in ./assets/sound/voice

Fonts

True Type and Open Type Fonts (ttf and otf) can be used in the framework. The list of the entries of “HD” fonts should occur under the Font category

The key defines an alias for the specific type face and the size of the font when rendered. The font may be specified as a filename or a system name, however using systemName will require a user to install a font onto his/her machine before use, which is far more intrusive. If using filename, the path is assumed to be ./assets/fonts/

Font “Styles”

Font styles may be defined within the font category as well, and should appear at the same depth as the HDFonts category

In the example above there are two styles defined: weird will fill the text rendered in a medium blue ([0,0,128] as R,G,B), has a line_width (aka border) of 1 and the line_color is a reddish purple ([255,0,128]).

SkeletonGame Requirements:

SkeletonGame requires a few conventions in your asset_list, that allow you to tweak/customize certain properties. You must be sure you define a few things:

  • A Font or HDFont named: tilt-font-big, tilt-font-small, settings-font-small, high_score_entry_inits, high_score_entry_msg high_score_entry_letters
  • TODO: Flesh out the complete list, for here!
  • something else that’s vital…

Step 4: Making a Game Class

You will need to make a Game class. Yours can look a lot like the one that comes with SampleGame called T2Game.py

In this entry we will dissect the example given game class, T2Game.py, in order to better understand what’s required in a SkeletonGame’s main class. Before we get too deep, it’s worth noting that a familiarity with Object Oriented concepts is very valuable here, as is some experience with Python. You can pick up everything you need from a few nights going through the Python tracks on Code Academy.

If you investigate the SampleGame folder you will see two files: config.yaml (discussed in a previous entry) and T2Game.py –the latter defines the “main” class of the Sample Game, and is the place to start in understanding how this game works and what is expected in your own game. Some good additional Python resources are this wikibook, and this overview of object orientation in Python.

Imports

The first few lines are imports; they include code written by others for use into your program. Some are included with python (e.g., logging) and others are part of the PyProcGame/PyProcGameHD/SkeletonGame framework (e.g., anything starting with procgame)

Nothing is wildly special in the above. In the next block, you’ll find the imports of python modules that define “Modes” –Modes are important, but let’s table that until we get through this file.

The above imports do a few things: the first imports my_modes which is a folder under SampleGame, and ultimately a package in Python lingo. In Python, the execution of this line causes Python to look a definition of a module of that name, and that search starts from the current folder, looking for a file of that name, or a package of that name. What python finds is a folder, so within that folder, Python is looking for a file called __init__.py which will be executed when that package is imported. That file can include seperate import lines to define “All” the files in that sub-folder, but importing all the files in that folder is not automatic.

The next line imports two specific classes from my_modes –those examples will be the ones we dissect in the next entry.

The next two lines give examples of how to import a new Mode that you create if you do not wish to modify the __init__.py; the general for there is:

from <folder>.<file> import <class_name>

Reading the Python docs about modules and packages should shed more light here, if you are so inclined.

Logging

The next line sets up the logging level and format.

Logging level INFO produces a considerable amount of output to the console, but actually not as verbose as is possible. The levels are listed here, and a good tutorial on using Logging (which is worlds better than just using print is availble here).

The curr_file_path line simply lets the SkeletonGame code know where in the system the current file is located for relative path loading.

The Game Class

Now we get to the meat of things. The Game class is the main game object. It “owns” all the attributes of the physical machine (.coils, .lamps, .switches) and can communicate with these components via the P-ROC interface. In addition the Game class owns the state of the active game, including (among other things) the “Player” (.current_player()), the list of players (.players), the ball number (.ball), and the Mode Queue (.modes). The Game class also is the home of the display controller (which we typically don’t access directly), and the sound controller (.sound),

Some understanding of Object Orientation goes a long way. Our game is a sub-class of SkeletonGame, which means it includes all the fields (i.e., variables) and methods (i.e., functions) of the SkeletonGame class. SkeletonGame is a subclass of BasicGame, which is in turn a sub-class of GameController.

In the code above, the __init__(self): method is defined, which is the T2Game class constructor –this is the function that gets called when a new instance of the T2Game class is created. That means, typically, this function is run once, when your game is executed. If you “peek ahead” to the bottom of the file, you will see where your game is constructed.

The rest of the constructor defines the switches that will start out as “automatically closed”, and invokes the __init__() method of the super-class (that is, calls the __init__ method defined in SkeletonGame); that code sets up a lot of stuff for us, and it’s nice that you don’t have to write that code. 🙂

The next few lines instantiate the Modes that manage the behavior of the game. When you add a new mode, you’ll need to add a line like the following:

If you’d like to add any global helpers, notice that the leftTargetLamps is defined just to be helpful, creating an array of the lamps corresponding to the left five targets, accessible through the game instance.

The last thing the game __init__() needs to do is call .reset(); the definition of reset immediately follows.

The .do_ball_search() method, shown below, is called by the framework when a ball search is required. Your methods should actually pulse coils in order to release stuck balls. This example is far from ideal since it doesn’t have any delay

Last, and certainly not least, at the bottom of the file:

That is what is actually run when you type python T2Game.py, and it passes the class into the run_proc_game() function (defined in SkeletonGame) to create an instance of your game and run it.

Displays and Layers: basic concepts

PyProcGame display basics

The Mode architecture of PyProcGame allows every mode to provide a display ‘Layer’ to be shown on the DMD. The Layer should be thought of exactly as the Layer metaphor in Photoshop or other image editing software. The Layer family of classes in PyProcGame represent the various container classes for types of content to be displayed on the DMD, and The ordering of the Layers is dictated by the priority of the Modes, with the highest priority mode (largest number) being the top-most layer and the lowest priority mode would be the bottom-most layer (assuming all layers are opaque).

Modes do not always have a visible component, and do not by default. A mode that does have something to show would create a layer by assigning it’s layer attribute (i.e., self.layer) to some specific instance of a Layer object. The type of layer object you create depends on the type of data you want to place in the Layer.

What’s different in the display framework between PyProcGame and PyProGameHD ??

In PyProcGameHD, traditional DMD support has been removed. If you are running a P3-ROC, you didn’t have it anyway, but if you are a P-ROC user, this means you cannot output to traditional 128×32 DMD displays. That said, you can now very easily output full color graphics to a regular video display device (a 1080p monitor, for example).

SkeletonGame specific conveniences (like using YAML to describe fancy layers with minimal code) is described [on this page].

You can use this document to learn the display in “vanilla” PyProcGame –PyProcGameHD “exclusive” layers are indicated where appropriate. Similarly changes to the functionality of some layers was introduced and those are also indicated here. The biggest changes (i.e., things that might cause existing code to not work or behave differently than expected under the new framework) are:

  1. Alpha support: composite_op='blacksrc' is deprecated at best. Instead, every new layer created (Grouped, Frame, etc) is filled with a transparent color –i.e., (0,0,0,0) of the form (R,G,B,Alpha) where Alpha ranges from 0 (invisible) to 255 (opaque). Before, if you created a layer and did not indicate the blacksrc composite_op you would have expected it to be visibly opaque (though not the same as the flag opaque=True which carries a very different meaning for layers). If you’re wondering why you can now see through your GroupedLayer, that’s why. It also means you should probably set that GroupedLayer to have it’s .opaque property set to True which will both stop the lower layers from being visible by preventing them from even being rendered (saving CPU cycles).

  2. Graphical (animations, frames, fonts) assets must be loaded after the display framework has been initialized. What this means, is that any code that initializes a font at ‘global scope’ (i.e., outside a class body) will cause the code to “blow up” –instead, we provide an assetmanager class (defined in assetmanager.py) and all you have to do is provide a list of assets in the file config/asset_list.yaml and they will be pre-loaded for you when the game is loaded (a graphical progress bar is shown).

  3. Using the assetmanager means that you can refer to an animation (or single frame) by its key via game’s animation dictionary.

Look at the sample asset_list.yaml in the SampleGame/config/ folder. The assetmanager should hopefully make things easier.

Types of Layers and examples of their use:

FrameLayer

The simplest of Layers (aside from ‘Layer’ itself) is the Frame layer. This layer is used when you want a mode to show a single image on the dmd. Assuming you already have the image in the correct .dmd format (which can be done using the procgame command-line tools to convert an image to a .dmd, or can be converted from other image formats at runtime). You will need three lines:

Of course, you can consolidate into a single line, if you are so inclined:

In PyProcGameHD you would define the file in the asset_list.yaml and simply refer to the file’s key.
Also, in PyProcGameHD, there is no need to convert animations to DMD format.

The asset_list.yaml might include a few entries. In the following example, an chrome.png is a single image and could be used as the source frame in a frame layer:

The corresponding ‘mode code’ to use chrome as a Frame Layer is:

This is far more complicated than just using the AnimatedLayer object that the assetmanager creates when it pre-loads the content.

TextLayer

The text layer will be used a lot in your games and is the same in any DMD based PyProcGame game. The PyProcGameHD version adds HDTextLayer, and it isn’t drastically different, so we will start with this one.

Typically you will create a textLayer

Many layers support a ‘fill_color’ argument which specifies the background fill to be placed within the
larger layer that contains the desired content. In the regular DMD framework those colors are 0..15,
in the PyProcGameHD framework they are (RED,GREEN,BLUE) tuples with values ranging from 0..255 for each RED,GREEN, and BLUE

There is also a width and height argument that lets you specify the width and height of the layer to be
created. The following example uses width, height and fill_color

Panning Layer (HDVGA Extensions)

Animated Layers

Again, PyProcGameHD and it’s assetmanager should make life easier. It will load the animated gifs indicated in the following examples as animations.

The corresponding ‘mode code’ to use a pre-loaded animation as such, is:

Grouped Layers

Real-world grouped layer

Scripted Layer

Layer from Text

Note: this is NOT supported in the PyProcGameHD Framework!!

Transitions

Advanced Layers for PyProcGameHD

The following are Layers that are only present in the PyProcGameHD Framework only:

Particle Layer

Rotation Layer

HD Text Layer

Adds borders and interior fill colors

Animated HD Text Layer

Transition Layer

Script-less Layer

Assorted Changes in PyProcGameHD

  1. Added some fixes for when frame_sequences are used. My frame_listeners were not firing when I used frame_sequences, so I added a fix for that.
  2. Added a helper called play_sequence that would play a specific frame_sequence and then revert back to the prior sequence when complete. It makes it more like a traditional animation interface. I also added the ability to send an arg to the listener because I needed that for resuming sequences

  3. Fixed the movie (mp4) layer to work with the new framework. It does now; I’ve tested it.

  4. Added a callback for scripted_layers, so a specified method will be called when that script section is encountered. Example:

  5. Scriptless layer supports callbacks, too

  6. GroupedLayers support a fill_color so that if you want to explicitly black_fill (or something else) you can just specify it here. You can also omit it for the default which is a transparent fill.

  7. PanningLayer supports setting the ‘frame’ content to be any type of layer, so anything can be panned. If you specify a regular frame, well, that still works too. I also fixed some implementation bugs that I encountered with bounce. A ton of testing went on there, so I’d be surprised if any part of that didn’t work.

  8. RotationLayer: a cute toy. Lets you rotate any type of layer. It’s cute.. not very useful but I thought, why not?

  9. I fixed some major placement bugs in AnimatedHDTextLayer that caused them to be positioned incorrectly.

SG: Display Markup Language

SkeletonGame’s display markup language:

SkeletonGame exists to make it easier for you to get your game going. The YAML based display layer markup syntax was intended to make it “easier” to describe complex layer compositions for display on the PyProcGameHD framework, without requiring the programmer to generate code to do so. It was intended just to provide a short hand for a few Layer types. I use it in the attract mode, and it obviously has uses elsewhere too.

It was never intended to become an interface to the entirety of the display power of the framework … but there has been feature creep.

Can I use this in mode code??

Absolutely. Your mode has a layer. That layer is accessible via self.layer –the yaml parser is in the game’s dmdHelper so code such as:

will work, provided yaml_struct contains a valid format, as described below.

The YAML format

Let’s start by taking the canonical example of where this is used and likely to be first encountered, the attract.yaml file. This file starts with a Sequence tag that indicates what follows is a list of layer types that are to be played in, get this: a sequence!

What makes the sequence type “special” is that it augments each of the layer that it contains with three additional tags:
* [Required] duration: –a number of seconds that this layer should be shown.
* [Optional]lampshow: –the name of a lampshow to be played when this part of the sequence is shown on the display.
* [Optional] sound: –the name of a sound to be played when this part of the sequence is shown on the display.

The values for lampshow and sound (if present) should correspond to keys that can be found in the asset_list.yaml file if you expect it to work. Again, every list entry in the Sequence should define a duration

The Sequence contains a list of layers. The following documents the syntax for the layers:

Types of Layers:

Combo: a combination of simple text, shown in front of an image or animation

The Combo layer is the simplest way to get text on the screen The line (or lines) given
to the combo layer will be centered on the display. If multiple lines are specified,
then they will be centered vertically.

Example:

In the example above, this entry will show three lines of text (the middle line being blank) drawn
in a Font named med (as defined in the asset_list.yaml). No FontStyle has been specified
(notice the line starts with a # which comments out the line). This text will be shown on top
of some image or animation named dark_chrome which is presumably defined in the asset_list.yaml.

A FontStyle can be defined in two different ways: one, as a string that maps to a key in the
asset_list.yaml –very useful if you plan to use the same style over and over again. The other
way is with an in-line definition. That looks something like this:

Animation

Super simple. Show a single frame or animation with a key found in the asset_list. Example:

So we expect to find an entry keyed t800-war in the asset_list.

If you want more control/power, see the #animation# object, later in this document.

RandomText

An idea from scottdanesi, this is a Combo layer that allows you to specify multiple options
for the text and, at playback, one will be selected at random each time the layer is shown.

Like Combo, it shares Font, FontStyle and Animation tags. What is different is the
Header tag which, if present, will add a line of text at the top of each of the options when
displayed. The list of options are tagged TextOptions which is a list of Text entries,
each of which is a list, of multiple lines of text which might be shown (as in the Combo layer)

This layer has three different options that will be chosen at random, but all
would be rendered in front of the dark_chrome asset, in the small Font, in the
weird FontStyle.

HighScores

Multiple frames will be generated (a sequence of frames) for each of the system’s recorded
high scores. You can control the Font, FontStyle, a background Image/Animation, and the
order in which the stats appear on the display. Of note, the duration tag here is required
and each of the frames will be shown for the given duration

For example, if there are four high scores and a duration of 4.0, then the entire high score sequence will take 16 seconds.

LastScores

Multiple frames will be generated (a sequence of frames), one for each of the player’s score from the last game played. You can control the Font, FontStyle, a background Image/Animation. The duration tag again is used to indicate how long each frame is shown. If this is the first launch of the game and there was no immediate prior play, then this frame will not be generated.

“power” layers

The following layers are actually quite powerful and flexible. They are indicated by lower case layer names and the _layer suffix naming convention.

All of the _layers that follow provide the user with the optional ability to specify the x and y location (relative to the top left corner of the display and the image) as well as a width and height.

These values are specified in a fairly flexible format:

  • if a positive int is specified, the value is taken as-is
  • if a negative int is specified, the value is taken as offset from the relative value (e.g., for x or width, this is the width of the DMD display)
  • if a float is specified the value is taken as a percentage of the relative value (e.g., 0.25
    is 25% of the display width, in the case of width or x)
  • if a string value matching the key is specified, the relative value is returned
    (so specifying width: width has the same effect as specifying 1.0 as 100%)

When width and height are omitted, the framework will try to compute the values on its own. This may be incorrect in certain pathological cases. When something seems amiss and you cannot see the entirety of your layer content, you may wish to set these values as width:width and height:height just to check.

animation_layer

The Name: tag remains from the Animation layer, however the animation_layer provides quite a bit more control over the animation playback.

The following tags are supported to adjust the specific settings for this play-through of the animation:

  • [Optional] hold_last_frame: If True, the last frame will be held if the animation is shown for longer than the time it takes to play all of the frames. The alternative (i.e., False) is that a blank frame is shown. If unspecified, the default value is the opacity set in the asset_list

  • [Optional] opaque: When True the player can see through this frame (not terribly useful unless the animation has transparent pixels and is part of a group). If unspecified, the default value is the opacity set in the asset_list

  • [Optional] frame_list: A list of frame indices to be shown. For example a value of [0,1,2] would only show the first three frames of the named animation. A value of [0] would only show the first frame. A value of [-1] would only show the last frame.

The following example shows only the final frame of the animation found in the asset_list with key intro:

markup_layer

This layer exposes the ‘MarkupLayer’ provided in PyProcGame, however this has been updated
for HD functionality. This layer is commonly used as a “Special Thanks To:” layer, or to provide a large amount of text, as it supports multi-line text and some basic formatting (two different font styles, left justify, right justify, and centered text, all on the same layer). This does
not support an animation tag for a background. If you want one, you’ll have to provide your own
through a group_layer. Similarly, it does not scroll/pan. If you want panning, you will need to enclose this layer in a panning_layer

In the example above, notice that two Fonts are defined: Bold and Normal
Lines with a [ are rendered in Normal Font & FontStyle, and those with a # are considered Bold. Lines that have style indicators at the start and end of the line of text are
centered. An indicator only on the left indicates the line is left justified. An indicator
only on the right means the line will be right justified. width can be used to control
the width of the rendered frame.

The final entry in the list, is surrounded with curly-braces. The markup layer will replace that element with an inline drawing of the first frame of the animation/image with key matching some-logo in the asset_list.

panning_layer

This layer exposes the PanningLayer provided in PyProcGame/HD. It provides the ability to
scroll/pan the layer that it contains.

The origin_x and origin_y represent where the layer will start on
the display. This allows the layer to “scroll into view” starting from off screen. Similarly scroll_x or scroll_y can be specified as zero to not move or negative to scroll left or up. frames_per_movement is how many frames need to pass (w.r.t. frames per second) before the next scroll_x and scroll_y are added to the layer’s current location. If True, bounce indicates that the layer will start moving in the opposite direction if it hits the edge of the display.

group_layer

This layer allows you to group multiple layers just as the PyProcGame GroupLayer does in code.
The contents are a list of other layers (potentially including other group_layers if you really want to)

Items in the content list are added “bottom up” so the first item will appear beneath the next item, which appears beneath the next and so on…

text_layer

About as fine-grained as you can get, the text_layer is the mechanism to describe a single
line of text for the display. Sure, it supports Font and FontStyle, and there is a slight
change in the use of Text to describe a single lines worth of text, however you can also
specify relative x, y, v_justify (vertical justification, either top, bottom, or center), h_justify (horizontal justification, either: left, right, or center), width.

In addition, a blink_frames tag can be added to specify a periodicity at which the text will appear/disappear. For example, blink_frames:15 would cause the text to appear for 15 frames then disappear for 15 frames (and that cycle would repeat). At 30fps (depending on your config.yaml) this is half a second on, half a second off.

Examples go a long way:

Another:

Another: