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:
self.layer = self.game.dmdHelper.generateLayerFromYaml(yaml_struct)
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:
Combo:
Text:
- "My Game"
- ""
- "v1.0"
Font: med
# FontStyle: None # this line is commented out so it will use default style
Animation: dark_chrome
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:
Combo:
Text:
- "My Game"
- ""
- "v1.0"
Font: med
FontStyle:
interior_color: [0,0,255] # R,G,B, each 0..255 --fill the text blue
line_width: 1 # a one "dot" border will surround the text
line_color: [255,0,0] # that border will be red
Animation: dark_chrome
Animation
Super simple. Show a single frame or animation with a key found in the asset_list
. Example:
Animation:
Name: t800-war
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)
RandomText:
Header: Instructions ## omit line completely to specify 'None'
TextOptions:
- Text:
- ""
- do not drink
- gasoline
- ""
- Text: ["", "", "do not swim in", "the ocean", ""]
- Text: don't panic
Font: small
Animation: dark_chrome
FontStyle: weird
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.
HighScores:
Font: tiny
FontStyle: weird
Background: dark_chrome
Order:
- player
- category
- score
duration: 4.0
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.
LastScores:
Font: large
FontStyle:
interior_color: [130,230,130]
line_width: 1
line_color: [60,60,60]
Background: dark_chrome
duration: 2.0
“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
orwidth
, 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 ofwidth
orx
) - if a string value matching the key is specified, the relative value is returned (so specifying
width: width
has the same effect as specifying1.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
: IfTrue
, 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 theasset_list
-
[Optional]
opaque
: WhenTrue
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 theasset_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
:
animation_layer:
Name: intro
frame_list: [-1]
hold_last_frame: True
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
markup_layer:
width: 450
Bold:
Font: med
FontStyle: blueish
Normal:
Font: small
FontStyle: blueish
Message:
- "#Gerry Stellenberg#"
- "#Adam Preble"
- "Josh (Rosh)#"
- "[Scott Danesi"
- "Matt Bonemma]"
- "{some-logo}"
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.
panning_layer:
width: 500
height: 500
origin_x: 0
origin_y: -130
scroll_x: 0
scroll_y: 2
frames_per_movement: 2
bounce: False
contents:
markup_layer:
width: ...
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)
group_layer:
width: 500
height: 500
contents:
- animation_layer:
name: dark_chrome
- markup_layer:
width: 450
Bold:
Font: med
FontStyle: blueish
Normal:
Font: small
FontStyle: blueish
Message:
- "#Gerry Stellenberg#"
- "#Adam Preble"
- "Josh (Rosh)#"
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:
text_layer:
x: width
y: height
h_justify: right
v_justify: bottom
Text: "Look down here"
Font: med
duration: 1.0
Another:
text_layer:
x: 0
y: 0
h_justify: left
v_justify: top
Text: "Look up here"
Font: med
duration: 1.0
Another:
text_layer:
x: .50 # 50% of the display width
y: -5 # 5 "dots" up from the bottom
h_justify: center
v_justify: bottom
Text: "Look over here"
Font: med
duration: 1.0