"Seamless" Game Launch

An enhancement request on the github issue tracker proposed adding an option for "seamless launching". The idea is to get rid of the blank black background that PinballY normally shows on the playfield screen while waiting for the game program to start up, and instead continue showing the playfield video as normal. The status messages about the progress of the launch would still appear ("Loading... Running..."), but superimposed on the background video instead of on a boring black background.

The new Javascript features in 1.0 Beta 17 make this pretty easy to implement. They also make it easy to add custom touches, such as dimming the background video a bit, or superimposing some animated graphics to make the transition more dynamic looking.

Launch Overlay events

The first system feature we can bring to bear on this problem is the Launch Overlay event group, which gives us a natural place to put the code that will orchestrate the seamless loading UI.

The black background that the system displays during the launch process is actually an overlay layer that's drawn in front of the normal background. Another layer on top of this holds the status text with the "Loading... Running..." messages.

When the system is about to display these overlay layers at the start of a launch, it fires a launchoverlayshow event. This is where we can intervene to prevent the normal black background from coming up, which we can do simply by blocking the default event processing, via the event object's preventDefault() method.

mainWindow.on("launchoverlayshow", ev => { ev.preventDefault(); });

Tidying up the UI

There are two other little details in the UI that we should take care of: hide the wheel, and hide the underlay. These are both simple operations that we can add to our event handler:

mainWindow.on("launchoverlayshow", ev => { mainWindow.showWheel(false); mainWindow.setOverlay(); ev.preventDefault(); });

Having made those changes, we'll need to undo them when the game exits. We actually don't have to worry about undoing the underlay removal, since that will come back on its own; the system pretends that you've selected a new game when it returns to the wheel UI after a game session has ended, which reloads all of its media, including any underlay. But we do have to restore the wheel to visibility explicitly. The system won't presume to override our wishes on the wheel hiding until we say otherwise.

The place to take care of these end-of-launch cleanup tasks is in the launchoverlayhide event, which the system fires when it's removing the launch overlay at the end of a game launch session. This event is the bookend to launchoverlayshow, so it's the place to undo changes we make there.

mainWindow.on("launchoverlayhide", ev => { mainWindow.showWheel(true); });

Launch overlay drawing layers

Preventing the default system processing on a launchoverlyshow event is actually all we have to do to leave the playfield video showing throughout the launch. That's the only point at which the system will try to cover up the background video. But I also want to point out that we can do more than just blocking the system's default black background. We can actually take it over and provide a different background of our own!

The way we can draw our own background is to use the mainWindow.launchOverlay objects. That property gives us an object, which in turn contains two properties, fg and bg, giving us two more objects. mainWindow.launchOverlay.fg is the "foreground" layer of the launch overlay, which is where the system draws the "Running... Loading..." text message updates. And mainWindow.launchOverlay.bg is the background layer, which the system normally fills with an opaque dark gray.

Those two objects are Drawing Layer objects, so they offer flexible graphics capabilities: you can use them to display still image files, videos, or your own custom graphics, using the Custom Drawing facility.

As a simple example, let's place a thin veil over the playfield video to emphasize that we're not in the normal PinballY interactive mode while we're waiting for the launch. We'll still make the video visible, but we'll dim it out a little and give it a purple tint. We can do this simply by filling the overlay background layer with a partially transparent fill color:

mainWindow.on("launchoverlayshow", ev => { mainWindow.launchOverlay.bg.clear(0x20FF00FF); ev.preventDefault(); });

This fills the background layer with color code 0x20FF00FF. That parses as 20 (hex) for the alpha value, which is about 12% opacity, meaning it's mostly but not entirely transparent. The rest is RGB for bright purple, the same as HTML code #ff00ff, which looks like this:

When that's combined at 12% with the background playfield video, it'll dim the background slightly and give it a tinge of the foreground color.

Note that we don't have to worry about undoing our custom background the game launch ends, since the system will automatically remove the launch overlay on its own. The same goes for any other custom drawing we do, or for loading an image or video into the overlay layer.

Moving the progress messages to the DMD

Another little tweak you might want to make to the launch UI is to move the status messages out of the playfield window and into the DMD window instead. The DMD screen is, after all, the place where the newly launched game will display its own messages, so it's natural to move the launch messages there as well.

Note that this is only possible with a video DMD screen. If you have a real DMD device - not a video screen, but a plasma or LED score display like on a real pinball machine - we unfortunately can't use it for launch status messages. The reason is that the real DMD hardware devices can't be shared by two programs at once, which makes it critical for PinballY to disconnect itself from the DMD hardware before it launches the game. If it doesn't do that, the game program might encounter an error trying to connect to the device, because of the one-program-at-a-time limitation. So it's really important for PinballY to release the DMD before the launch process even begins, thus making it impossible to display any progress messages there during the launch.

We can display the launch messages on the DMD by using a custom drawing layer in the DMD window. And we can use the drawDMDText() method on the drawing layer to make the message look like an actual DMD message.

The way to intercept the status update messages is via the launchoverlaymessage event. That event fires each time the system updates the progress message. Left to its own devices, the system will display the message as text in the foreground layer of the launch overlay. We can intercept the event, display the message in the DMD window instead, and then cancel the event (via preventDefault()) to keep the system from displaying its own message in the playfield window.

// Create a drawing layer in the DMD window. This is just // a global variable, so the layer will always be around; // it'll be invisible most of the time, since we'll clear // it to all transparent pixels when it's not in use. let dmdLaunchMessageLayer = dmdWindow.createDrawingLayer(1000); // now intercept launch message updates to display the // messages on the DMD overlay instead of the main window mainWindow.on("launchoverlaymessage", ev => { dmdLaunchMessageLayer.drawDMDText(ev.message || "", { font: "12px"; }); ev.preventDefault(); });

The reason that we wrote (ev.message || "") rather than just using the message directly is that the message can be null in some cases, and if you use a null value where a string is expected, Javascript converts the null to the literal string "null". For our purposes here, we'd prefer to render these null messages as no text at all, which the "or" syntax accomplishes.

Note that you can specify some other options in drawDMDText() to control the display style, but we're intentionally letting the system choose the defaults based on the current game, so that our display harmonizes the with the game's normal DMD-window appearance. The one option we're customizing is the DMD font, which we've set to "12px". If we let the system choose the DMD font, it'll pick the biggest font that will make the text fit, so successive messages might be drawn in different fonts. That looks a little awkward, so I prefer to set a fixed font; the "12px" font is a good all-purpose size for the launch messages.

There's one more little detail (isn't there always?). When the launch process is over, we'll want to get rid of the overlay, so that we return to normal media and high score displays in the DMD window. launchoverlayhide is once again the place for these end-of-launch cleanup tasks. We can effectively hide the layer by filling it with a fully transparent (zero alpha) background.

mainWindow.on("launchoverlayhide", ev => { dmdLaunchMessageOverlay.clear(0x00000000); });

Playing an animated overlay

A neat thing you can do with the launch overlay is to display a video or animated GIF that plays during the launch process. There are lots of little animated GIF snippets on the Web with special effects loops that could make nice decorative effects during a launch. Here's how you could load such an animation into the launch overlay background layer, when the launch process first starts up:

mainWindow.on("launchoverlayshow", ev => { let animation = gameList.resolveMedia("Images", "launch.gif"); mainWindow.launchOverlay.bg.loadImage(animation); });

Here we've assumed that you've found a suitable animated GIF file, named it launch.gif, and placed it in the PinballY Media\Images folder. Remember that we're talking about customization here, so this GIF isn't a file that comes with PinballY - it's just something that we made up for the sake of this example, and we leave it as an exercise to find an animation clip you like for this.

You could just as well use a video file, replacing the call to loadImage() with a call to loadVideo(). We went with the GIF for this example just because it seems easier to find little effect-loop clips in animated GIF format than in video formats.

We'll want to hide the animated image as soon as the game has finished loading, both because it'll stop being appropriate visually at that point, and because we don't want the animation taking up system resources while the game is running. To hide the image, we can use the gamestarted event (see LaunchEvent):

mainWindow.on("gamestarted", ev => { mainWindow.launchOverlay.bg.clear(0xff404040); });

That fills the launch overlay background layer with an opaque dark gray - basically the same thing the system would have done on its own at the start of the launch, if we hadn't intervened. Clearing the drawing layer removes any video or image that was displayed in the layer, so this will tidy up the UI and reduce our performance impact, just as we wanted.

The original motivation of this chapter was to make the launch process seamless, by not covering up the playfield image or video, so this idea of loading an animation or video into the overlay might not quite fit the original plan. Even so, I wanted to mention it, since some people might prefer it as a third option that's even better than leaving the playfield video playing. There's also a best-of-both-worlds option, which is that GIF animations can contain transparent areas. A transparent GIF would superimpose an effect animation on top of the playfield background, so you'd have both the "seamless" part and the special effect. There are even a few video formats that support partial transparency: see Alpha transparency in videos for more on that.