The goal is to run P3SampleApp on the physical machine. P3SampleApp has some quirks that make it unpleasant or down right impossible to play on the physical machine. With just a few tweaks, it becomes possible to play a complete game until the last ball ends.
In this TechTip we will be using the Module IDs to refer to the various upper playfield modules. Here is a table of the known Module IDs so far:
Module ID | Module Name |
CL | Canon Lagoon |
LL-EE | Lexy Lightspeed – Escape from Earth |
CCR | Cosmic Cart Racing |
Heist | Heist |
WAMONH | Weird Al’s Museum of Natural Hilarity |
Drained | Drained |
FR | Final Resistance |
Copy the source code of P3SampleApp because we will be making small changes to it.
For example, copy C:\P3\P3_SDK_V0.8\P3SampleApp to C:\P3\P3SampleAppAgnostic
Before testing an application on the physical machine, it is often worthwhile to try it in the simulator first.
The module that is emulated when running in the simulator is specified in the file C:\P3\P3SampleAppAgnostic\Configuration\AppConfig.json
For example, you can change:
"ModuleID": "CCR",
to:
"ModuleID": "WAMONH",
The simulator will then emulate WAMONH instead of CCR. That also changes the LEDs displayed by the LEDSimulator. You don’t have to restart Unity for this change to take effect, but you will have to stop and restart Play mode if the application is already running.
You need the corresponding module driver installed in your %HOMEDRIVE%%HOMEPATH%\.multimorphic\P3\ModuleDrivers directory.
P3_SDK_V0.8 ships with the module drivers for CCR, CL, Heist, LL-EE and WAMONH. Hopefully, the module drivers for Drained and FR will ship in the next version of the SDK. You may not be able to run the simulator with Drained or FR, but the app can be run in the physical machine with those modules installed.
When you change the ModuleID, many of the key mappings become invalid in the AppConfig.json because these switches no longer exist for that module. This will not stop the application from running. You might want to remove the invalid mappings, or define the appropriate mappings for the new module. This is not strictly necessary.
The AppConfig.json file is used exclusively by the simulator and is not deployed on the machine with the app package.
WAMONH used a new syntax for the BallPaths CompletedEvent and that breaks the P3SampleApp application. See this P3TechTip for more explanation.
To add support for WAMONH in P3SampleApp, edit C:\P3\P3SampleAppAgnostic\Assets\Scripts\Modes\SceneModes\HomeMode.cs and change this code in the HomeMode constructor:
if (shot.ExitType == BallPathExitType.Target) { string[] strippedSwitchName = shot.StartedEvent.Split('_'); // format of switch event must be "sw_<switch name>_active" for started events string swName = strippedSwitchName[1]; add_switch_handler(swName, "active", 0, TargetHitEventHandler); } |
to:
if (shot.ExitType == BallPathExitType.Target) { // If the completed event looks like a standard switch handler definition, // parse and add a normal switch handler. Otherwise, use the completed event // with a mode2mode handler. if (shot.CompletedEvent.Contains("_inactive")) { string[] strippedSwitchName = shot.CompletedEvent.Split('_'); // format of switch event must be "sw_<switch name>_active" for started events string swName = strippedSwitchName[1]; add_switch_handler(swName, "active", 0, TargetHitEventHandler); } else { AddModeEventHandler(shot.CompletedEvent, TargetPathHitEventHandler, priority); } } |
Also add the implementation of TargetPathHitEventHandler() in HomeMode.cs right after TargetHitEventHandler():
private bool TargetPathHitEventHandler(string evtName, object evtData) { // Add target path hit logic here or move the subscription and this code into a relevant child mode to process target path hits // for your game. return SWITCH_CONTINUE; } |
If you play the game as it stands, you will quickly notice that balls entering a sink hole never reappear. Ball search activates but of course it cannot find the ball underneath the playfield. What we need to do is relaunch the ball whenever the ball enters a hole.
Edit HomeMode.cs and change the definition of HoleHitEventHandler() to:
private bool HoleHitEventHandler(string evtName, object evtData) { // Add hole hit logic here or move the subscription and this code into a relevant child mode to process hole hits // for your game. P3SABallLauncher.launch(); return SWITCH_CONTINUE; } |
P3SampleApp in P3_SDK_V0.8 can handle the ball launchers for all modules announced up to November 2023 except for Drained.
P3SampleApp defines two ball launchers from where the balls can be put in play. The application calls them VUK_LEFT and VUK_RIGHT. Each ball launcher consists of the primary launcher and one or more backup launchers in case the primary failed to serve the ball.
The original P3SampleApp strategy was to use the launcher feeding the left inlane for VUK_LEFT and the launcher feeding the right inlane for VUK_RIGHT, both serving as backups for each other. If the module could feed only one of the inlanes, then both VUK_LEFT and VUK_RIGHT were identical with no backup launcher.
LL-EE, CL, Heist and WAMONH have launchers feeding both inlanes. Note feeding the left inlane on Heist is not ideal because it needs to go through the staging area which slows down the launch.
CCR and FR can only feed one inlane, so all balls will come out from the same VUK.
Drained cannot feed the inlanes or the lower playfield. The ball will be fed to the upper playfield instead. This is safe because we know the module layout and we know the ball will always make its way to the lower playfield.
In case a new module is released later, we will feed to the inlanes if possible. If the module cannot feed the inlanes, then we will feed the ball to the lower playfield. It’s not safe to feed the ball to the upper playfield in this case because the module layout is unknown.
This code was intentionally kept simple. A production game would have more robust selection of the launchers. At a minimum, it should make sure to have a backup launcher to assist every primary launcher in case of failure.
To add support for Drained, edit C:\P3\P3SampleAppAgnostic\Assets\Scripts\Modes\P3SABallLauncher.cs
Add two using directives near the top of the file:
using System.Collections.Generic; using Multimorphic.P3App.Modes.PlayfieldModule; |
Replace the Initialize() method with this code:
public static void Initialize(Multimorphic.P3.P3Controller p3, DataManager Data) { data = Data; BallLauncher.AssignP3(p3); List<TroughLauncher> leftLaunchers = BallLauncher.GetTroughLaunchersForDestination(LaunchDestination.LeftInlane); List<TroughLauncher> rightLaunchers = BallLauncher.GetTroughLaunchersForDestination(LaunchDestination.RightInlane); if (leftLaunchers.Count == 0 && rightLaunchers.Count == 0) { LaunchDestination secondChoiceDestination = PlayfieldModuleManager.GetPlayfieldModuleId() == "Drained" ? LaunchDestination.UpperPlayfield : LaunchDestination.LowerPlayfield; leftLaunchers = BallLauncher.GetTroughLaunchersForDestination(secondChoiceDestination); if (leftLaunchers.Count == 0) { throw new System.Exception("Cannot find suitable TroughLaunchers"); } } // Assign primaries BallLauncher.AssignTroughLauncher(leftLaunchers, VUK_LEFT); BallLauncher.AssignTroughLauncher(rightLaunchers, VUK_RIGHT);
// Assign backups for each VUK. The backup is the opposite VUK. BallLauncher.AssignTroughLauncher(rightLaunchers, VUK_RIGHT); BallLauncher.AssignTroughLauncher(leftLaunchers, VUK_LEFT); } |
If you play the game now, you will notice the slingshots and pop bumpers are dead. To fix that, you need to add the BumpersMode to the mode queue. The statements are already in P3SampleApp but they are commented out.
To enable the pop bumpers, edit P3SABaseGameMode.cs and uncomment these 3 statements.
At the top of the class:
private BumpersMode bumpersMode; //!< Enables/Disables bumpers (slings, pops, etc) |
In the constructor:
bumpersMode = new BumpersMode (p3, Priorities.PRIORITY_UTILITIES); |
In mode_started():
p3.AddMode (bumpersMode); |
The pop bumpers are controlled by sending the Evt_EnableBumpers event, where the event data is: true to enable the pop bumpers, false to disable the pop bumpers. Now that we added the BumpersMode, this event will be handled.
The code to send Evt_EnableBumpers is already present in HomeMode. There is nothing more to do.
If you are curious, take a look in HomeMode StartPlaying() where the pop bumpers are enabled:
PostModeEventToModes ("Evt_EnableBumpers", true); |
And take a look in HomeMode DisableFeatures() where the pop bumpers are disabled:
PostModeEventToModes ("Evt_EnableBumpers", false); |
The strength of each pop bumper is determined by the settings declared in the Bumpers section of the module definition file. The GameAttributes implementing these settings are created in the module driver. The application inherits those GameAttributes. There is nothing special to do for these GameAttributes.
Here is a pop bumper definition found in the WAMONH module definition file. Notice the SettingName that specifies the GameAttribute name. WAMONH chose to share the same GameAttribute for its three pop bumpers.
{ "Name": "PopBumperR", "SwitchName": "PopBumperR", "CoilName": "PopBumperR", "SettingName": "PopBumperPulseTime", "SwitchPolarity": true, "AllowShotgunning": true, }, |
SideTargetMode keeps track of which targets were hit and remembers the state between balls played by the same player. Unfortunately, the side targets can only be completed once in P3SampleApp. That’s because the handler for the Evt_SideTargetComplete event is missing.
To handle the event, edit C:\P3\P3SampleAppAgnostic\Assets\Scripts\Modes\SceneModes\HomeMode.cs and change this code in the HomeMode constructor:
// AlienAttack only ends when it is completed. So use the event to call both Completed and Ended Handlers |
To:
AddModeEventHandler("Evt_SideTargetComplete", SideTargetCompleteEventHandler, Priority); |
Add the implementation of SideTargetCompleteEventHandler() somewhere below PopHitEventHandler():
public bool SideTargetCompleteEventHandler(string eventName, object eventData) { delay("SideTargetComplete", NetProc.EventType.None, 2, new Multimorphic.P3.VoidDelegateNoArgs(sideTargetMode.start)); return EVENT_CONTINUE; } |
The delay is needed to make the targets insensitive for a while to avoid hitting the same target twice with one shot.
Edit C:\P3\P3SampleAppAgnostic\Assets\Scripts\Modes\GameModes\SideTargetMode.cs and change this statement in LoadPlayerData():
complete = data.currentPlayer.GetData("SideTargetComplete", false); |
To:
complete = false; |
Also change this statement in SavePlayerData():
data.currentPlayer.SaveData("SideTargetComplete", complete); |
To:
if (complete) ResetStates(); |
Finally, change the start() method to remove the Difficulty argument, like this:
public void start() { complete = false; ResetStates (); RefreshVisuals(); } |
Those changes will make sure the side targets reset at the start of the next ball in case the Evt_SideTargetComplete handler did not cause a reset (for example because the ball drained before the delay timer in HomeMode).
A commercial game developed to run on multiple modules would need a smarter ball search than what is implemented in P3SABaseGameMode.InitBallSearchMode().
Notice the 4 switches explicitly listed in that method are part of the base machine: drain, moatInA, moatInB, moatExit. The current ball search might not be smart but it works on all modules, which is good enough for our purposes now.
P3SampleApp comes with a few modes that are tied to the LL-EE module because they use switch names that only exist on LL-EE. For example Multiball or TripleTargetsMode. That’s not an issue because these modes are permanently disabled in the application.
P3SampleApp comes with a couple extra Main icons that mess up the display of P3SampleApp in the app launcher carrousel. It is better to remove them.
del C:\P3\P3SampleAppAgnostic\LauncherMedia\Icons\Main\P3_LauncherIcons_Diagnostics0.png
del C:\P3\P3SampleAppAgnostic\LauncherMedia\Icons\Main\P3_LauncherIcons_Diagnostics1.png
You can install the application on the physical machine with a USB drive or through a network share.
Before we can do the installation, we need to build the application package.
From the top menu in Unity, select Multimorphic > Package App For Distribution
Enter these values:
Display Name: P3SampleApp
Version: 1.0.0.0
Description: Sample Application
Compatible Module Ids: Any
Leave Feature flags and Tags empty.
If deploying with a USB drive, select the option to “Copy the package to USB drive” and choose your Target drive. If installing through a network share, leave the option unselected.
Click Continue.
The package is saved in C:\P3\P3SampleAppAgnostic\Packages\P3SampleApp 1.0.0.0.p3p
If the USB drive option was selected, another copy is written on the target drive.
Remember to clean up the Packages directory if you deploy multiple versions over time. Same for the USB drive, if applicable.
Follow these instructions if you chose to install with a USB drive.
Select “Install or Update Software”.
Select “From USB Drive”.
Select the package. For example: P3SampleApp 1.0.0.0.p3p
Press the right flipper button for Yes.
Alternatively, you can choose to install using an SMB network share. This option is hidden by default to avoid confusing non-developers. For developers, it has the potential to really streamline the development process.
To use an SMB network share, you first need to enable the option and configure the login. This needs to be done only once. (Those instructions are for Windows 11. Adjust the instructions for your version of Windows accordingly.)
In the search bar, type cmd, right click on Command Prompt, select Run as administrator.
In the Terminal window, type: net user <username> <password> /add
For example: net user p3 changeit /add
Choose a better password than changeit.
In the Terminal window, type: ipconfig
Find the line that says: IPv4 Address
Write down the address, it will look like 192.168.2.10 but with different numbers.
Create a folder you want to share. For example: c:\P3\P3Share
Right click the folder in Windows Explorer, select Properties, select the Sharing tab, click Share…
Type the local user name in the text box. For example, type P3. Click Add. The default permission by is read only which works to install packages. Change the permission to read-write to allow copying logs to the computer later: click on Read and select Read/Write for the P3 user.
Click Share.
Click Done.
With System Manager running, open the coin door and press the Launch button. The Settings menu appears.
Select Developers.
Change “Allow installs from SMB” to Yes
Select Login.
Enter the Password.
Enter the computer IP address.
Enter the network share name. For example: P3Share
Enter the user name. For example: P3
Exit System Manager. This is important. The settings will take effect the next time you start System Manager.
Now that this is done, it is simpler to install new packages.
For example:
copy "C:\P3\P3SampleAppAgnostic\Packages\P3SampleApp 1.0.0.0.p3p" c:\P3\P3Share
Select “Install or Update Software”
Select “From LAN Drive”. You will see a red message if your network share configuration is incorrect or your shared folder is empty. In that case, fix the problem and restart System Manager.
Select the package. For example: P3SampleApp 1.0.0.0.p3p
Press the right flipper button for Yes.
If the network share suddenly stops working after working for several hours or days, verify if the IP address of the computer has changed. This can happen if the lease on the address has expired and the router assigned a different address.