Displays and Layers: basic concepts

Page content

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 128x32 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:

    # An example of loading a single frame from a DMD file      

    anim = dmd.Animation().load('filename.dmd')
    # 1. the dmd.Animation() class contains the helper method 'load', 
    #   which loads a DMD from a DMD file; the variable anim now holds that animation

    frame_layer = dmd.FrameLayer(opaque=False, frame=anim.frames[0])
    # 2. create a FrameLayer which contains a single frame, the first in the animation
    #   dmd.FrameLayer needs at least the frame to display; anim.frames[0] 
    #   is first frame (possibly the only frame) in that DMD file
    #   setting Opaque=False means layers below this layer may be visible

    self.layer = frame_layer 
    #3. sets this mode's layer to be the newly created frame layer.

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

    self.layer = dmd.FrameLayer(frame=animation.load('filename.dmd'))

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:

    
Animations:
- key: 'cows'
  file: 'cows_shuffling.dmd'  
- key: 'explosion'
  file: 'explosion.gif'
  frame_time : 4
  composite_op: 'magentasrc'  
- key: 'gameover'
  file: 'thumbsup.gif'
  frame_time : 2
- key: 'chrome'
  file: 'chrome.png'  

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

    self.layer = dmd.FrameLayer(frame=self.game.animations['chrome'].frames[0]) # access the 1st frame of the animation with the key 'chrome' and use it as a frame.

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

    self.layer = self.game.animations['chrome']

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

    # T: example of a text-layer
    txt_ayer = dmd.TextLayer(128/2, 14, self.game.fonts['Jazz18'], "center", opaque=False)
    # note the form of a textLayer call is x-location, y-location, font, justification, and then dictionary values
    #  .set_text(string) may be called on a textLayer to set the text; if this is called while the textLayer is 
    #   on screen, then the displayed text changes.

    txt_layer.set_text("Welcome")

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

    lyrHdTextExample = dmd.TextLayer(self.game.dmd.width/2, self.game.dmd.height/2, game.fonts['Jazz18'], "center", opaque=True, width=self.game.dmd.width, height=self.game.dmd.height, fill_color=(64,32,0))
    lyrHdTextExample.set_text("Hello, World!")

Panning Layer (HDVGA Extensions)

    t = dmd.PanningLayer(224,112, r, (0,0), (2,0), bounce=True, numFramesDrawnBetweenMovementUpdate=1)

Animated Layers

    # A: Example of an animated layer; the DMD file referenced here 
    anim = dmd.Animation().load(self.game.dmd_path + 'bikeacrosscity.dmd')
    animLayer = dmd.AnimatedLayer(frames=anim.frames, frame_time=3) # frametime is the duration each frame is shown

    self.layer = animLayer

    # after the animation has played (e.g., if a mode has been re-started) it may be useful to
    # reset the animation -- 
    lyrFutureWar = self.game.animations['t800-war']
    lyrFutureWar.reset()

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

    
Animations:
- key: 'cows'
  file: 'cows_shuffling.dmd'  
- key: 'explosion'
  file: 'explosion.gif'
  frame_time : 4
  composite_op: 'magentasrc'  
- key: 'gameover'
  file: 'thumbsup.gif'
  frame_time : 2
- key: 'chrome'
  file: 'chrome.png'  

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

    self.layer = self.game.animations['gameover']

Grouped Layers

    # G: example of Grouped Layers
    # specifically of placing text on top of a DMD image (again, from file)
    bgframe = dmd.FrameLayer(opaque=False, frame=dmd.Animation().load(self.game.dmd_path+'Splash.dmd').frames[0])

    textex.composite_op = "blacksrc" # cause black pixels to be transparent in the text layer
    # notice a single grouped layer is created, compositing the previous two layers
    grplayerex = dmd.GroupedLayer(128, 32, [bgframe, textex])

Real-world grouped layer

    # finally an example of a group layer to indicate multiple items to the player at once
    # notice these are declared at the scope of the class (self.) so we can use them later...
    self.info_layer_right = dmd.TextLayer(127, 1, self.game.fonts['Tiny7'], "right").set_text("B")
    self.info_layer_left = dmd.TextLayer(1, 1, self.game.fonts['Tiny7'], "left").set_text("A")
    self.info_layer_num = dmd.TextLayer(128/2, 10, self.game.fonts['Font_14x10'], "center").set_text("0")
    self.info_layer_msg = dmd.TextLayer(128/2, 26, self.game.fonts['Tiny7'], "center").set_text("C")
    self.composite_layer_example = dmd.GroupedLayer(128, 32, [self.info_layer_right, self.info_layer_left, self.info_layer_num, self.info_layer_msg])

    self.info_layer_left.set_text("Hurry")
    self.info_layer_msg.set_text("Hurry Up Mode!!")
    self.layer = self.composite_layer_example

    self.seconds_remaining = 30
    self.update_and_delay()

def update_and_delay(self):
    self.info_layer_right.set_text("%d seconds" % (self.seconds_remaining))
    self.delay(name='countdown', event_type=None, delay=1, handler=self.one_less_second)

def one_less_second(self):
    self.seconds_remaining -= 1
    if self.seconds_remaining >= 0:
        self.update_and_delay()
    else:
        # do something when the timer has expired!
        pass

Scripted Layer

    # for frame in highscore.generate_highscore_frames(self.game.highscore_categories, self.game.dmd.width, self.game.dmd.height):
    #     layer = dmd.FrameLayer(frame=frame)
    #     script.append({'seconds':2.0, 'layer':layer})

    ##self.layer = dmd.ScriptedLayer(224, 112, script)

    # example of a scripted layer; plays a sequence of layers for the times specified
    self.layer = dmd.ScriptedLayer(128, 32, [
        {'seconds':1.0, 'layer':layerex}, 
        {'seconds':1.0, 'layer':textex}, 
        {'seconds':1.0, 'layer':grplayerex}, 
        {'seconds':4.0, 'layer':animex}, 
        {'seconds':1.0, 'layer':ptex}, 
        {'seconds':2.0, 'layer':self.composite_layer_example}])

Layer from Text

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

    # Example creating a DMD image using plain-text characters
    # this lets you describe a graphic using plain-text, either 
    # in-place in your code or from an external file
    # (if you deal with loading that file yourself)

    ptframe = dmd.Frame.create_with_text(lines=[ \
        '                               ', \
        '            *****+*            ', \
        '            *****+*            ', \
        '    *****************++****    ', \
        '    *****************++****    ', \
        '    **                   **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **++++++++++  +  **    ', \
        '    **  **+++++++++++++  **    ', \
        '    **                   **    ', \
        '    ***********************    '], \
        palette={' ':0, '+':7, '*':15})

    # Make a Layer to contain the newly created frame
    ptex = dmd.FrameLayer(frame=ptframe)

Transitions

    # transitions work on layer change, such that prior to showing whatever layer is next, this transition will occur.
    layerex.transition = dmd.PushTransition(direction='east')

Advanced Layers for PyProcGameHD

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

Particle Layer

    from procgame.dmd.particle import ParticleLayer, ParticleEmitter, ParticleSystem, FireParticle, FireworkParticle

    ps1 = ParticleEmitter(32, 64, max_life=24, max_particles=300, particles_per_update=20, total_creations=None, particle_class=FireParticle)
    # ps2 = ParticleEmitter(64, 94, max_life=20, max_particles=300, particles_per_update=100, total_creations=300, particle_class=FireworkParticle, random_next=True)
    # ps3 = ParticleEmitter(118, 96, max_life=20, max_particles=300, particles_per_update=100, total_creations=300, particle_class=FireworkParticle, random_next=True)

    ps = ParticleLayer(64,96, emitters=[ps1]) #, ps2, ps3])

Rotation Layer

    r = dmd.RotationLayer(112,64, 2, ps)

HD Text Layer

Adds borders and interior fill colors

    # self.game.fonts['ex_font'] = dmd.hdfont_named("Courier",24)
    # lyrHdTextExample = dmd.HDTextLayer(self.game.dmd.width/2, self.game.dmd.height/2, game.fonts['ex_font'], "center", vert_justify="center", opaque=True, width=self.game.dmd.width, height=self.game.dmd.height,line_color=(255,128,0), line_width=1, interior_color=(192,96,0),fill_color=(64,32,0))
    # lyrHdTextExample.set_text("Hello, World!")

    # self.layer = lyrHdTextExample


    - or - 
    # lyrTxtPresents = dmd.HDTextLayer(224/2, 7, game.fonts['large'], "center", opaque=True, width=self.game.dmd.width, height=self.game.dmd.height,line_color=(132,32,132), line_width=1, interior_color=(155,155,255),fill_color=None).set_text("Presents")

Animated HD Text Layer

    # layer = dmd.AnimatedHDTextLayer(self.game.dmd.width/2, self.game.dmd.height/2, 
    #     self.font_for_score_single(score), "center", 
    #     line_color=(132,132,132), line_width=1, fill_color=None,
    #     line_anim=None, fill_anim=self.bgFire, width=self.game.dmd.width, height=self.game.dmd.height)

Transition Layer

    # lyrTransEC_Presents = TransitionLayer(layerA=lyrTxtEndicott, layerB=lyrTxtPresents, transitionType=Transition.TYPE_CROSSFADE, transitionParameter=None)

Script-less Layer

    sl = dmd.ScriptlessLayer(224,112)
    # sl.append(lyrHdTextExample,4.0)
    sl.append(t,6.0)
    sl.append(lyrTxtEndicott,2.0)
    sl.append(lyrFutureWar)
    sl.append(lyrTxtTerminator,2.0)
    sl.opaque=True
    self.layer = sl

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:

    [{'seconds':3.0, 'layer':self.game_over_layer}, {'seconds':3.0, 'layer':None, 'callback':self.next_lampshow()}]
    
  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.