The P3 SDK can keep track of tilt warnings and notify when the number of tilt warnings is exceeded. The actual tilt processing is the responsibility of the application.
Tilt handling is absent from P3SampleApp. This tech tip shows how to implement tilt in P3SampleApp.
For more information on fixing a sensitive tilt, consult this other tech tip in the owner’s series.
Tilt handling is documented in file:///C:/p3/P3_SDK_V0.8/P3SampleApp/Documentation/html/_customizing_your_game.html#Tilt
Some tilt options are documented in file:///C:/p3/P3_SDK_V0.8/P3SampleApp/Documentation/html/_game_management.html#DisablingTiltHandling
The Tilt settings in the service menu are documented in file:///C:/p3/P3_SDK_V0.8/P3SampleApp/Documentation/html/_persistent_data.html#TiltGameAttributes
There are many requirements when implementing tilt. Some are easy, some can be quite complex. From experience, the interaction between tilt drainage and ball search can be tricky.
Tilt warnings must give feedback:
Tilt needs to stop all interactions:
Tilt needs to give feedback the player has tilted:
Tilt needs to advance the game to the next ball:
A tilt is detected by the tilt bob when it contacts the ring surrounding it. This corresponds to the tilt switch becoming active.
public bool sw_tilt_active(Switch sw)
{
…
return SWITCH_CONTINUE;
}
You don’t need to implement this switch event handler since the P3 SDK takes care of it.
In theory, a tilt could also be detected by the accelerometer but that’s not readily available in the P3 SDK.
The P3 SDK implements 5 tilt settings that are inherited by all applications.
To access the tilt settings, make sure your application is running, open the coin door and press the Launch button. Close the coin door, then select Settings > Mechs > Tilt.
To enable tilt detection, set both “Tilt Detection Enabled” and “Use tilt bob for tilt detection” to true.
To disable tilt detection, set “Tilt Detection Enabled” to false.
The reason why there are two settings is because there are two ways to detect a tilt:
In practice, the current applications do not send custom tilt events, so the two settings are somewhat redundant.
Setting | Description |
Tilt Detection Enabled | when true: enables custom programmatic tilt events, also allows “Use tilt bob for tilt detection” to control tilt bob detection; when false: disables all tilt detection |
Use tilt bob for tilt detection | when true: enables tilt bob detection if and only if “Tilt Detection Enabled” is also true; when false: disables tilt bob detection |
Tilt Warnings | Number of tilt warnings before a tilt is detected |
Time (in seconds) between warning | Period of time after a tilt warning during which tilt handling is temporarily disabled |
Settle time (in seconds) after tilt | Period of time after tilt before the next player can start his ball, this gives time for the tilt bob to settle. |
There is also the setting “Tilt Sensitivity Popups” under Settings > Gameplay > Debug. The SDK does not use that setting, it is up to the application to do something with it. Heist and WAMONH ignore it, so you can ignore it too.
TripleTargetsMode is worth mentioning here because it shows an anti-pattern. It implements a switch handler directly instead of relying on mode events sent by the module driver.
The big advantage with events is the application no longer has to figure out which switches can be ignored versus those that must be processed during tilt drainage (for mechs and ball search).
This advice applies to switches in the module definition file. The application might still have to implement switch event handlers for some switches in the base machine. The drain switch is a good example of that.
We will not show how to fix TripleTargetsMode because it is not used in P3SampleApp. It is simpler to just delete the source file.
TiltMode is part of the SDK. This mode keeps track of the number of tilt warnings. An instance of TiltMode is created by BaseGameMode.
By default, TiltMode is added to the ModeQueue in StartNewBall(). To override this, set the bool member variable tiltModeEnabled to false in your BaseGameMode subclass. This will let you create your own implementation of TiltMode as well as control when TiltMode is added to the ModeQueue. Typically, the BaseGameMode subclass does not modify tiltModeEnabled because TiltMode works fine.
TiltMode is designed to remain in the ModeQueue until the next ball starts. The application controls when tilt handling is enabled during that time. The application programmatically enables TiltMode when the ball starts, and disables TiltMode when the bonus is displayed. To accomplish this, it sends the Evt_EnableTilt event.
PostModeEventToModes("Evt_EnableTilt", true); // enables tilt detection if the settings allow it
PostModeEventToModes("Evt_EnableTilt", false); // disables tilt detection
If a tilt is detected and the number of tilt warnings was not exceeded, then TiltMode sends Evt_TiltWarning to the GUI and the modes. Tilt detection is temporarily disabled for “Time (in seconds) between warnings”. The event data is the number of warnings detected, so this number goes up with each warning.
PostModeEventToGUI("Evt_TiltWarning", warningCount);
PostModeEventToModes("Evt_TiltWarning", warningCount);
Let’s put it all together: a tilt is detected if the tilt switch is active, both “Tilt Detection Enabled” and “Use tilt bob for tilt detection” are enabled, the application enabled TiltMode programmatically and there was no recent tilt warning (less than “Time (in seconds) between warnings” ago).
If a tilt is detected and the number of tilt warnings is exceeded, then TiltMode sends Evt_Tilted to the modes and it immediately disables itself. The event data is always 0 so it can be ignored. The application is responsible to finish tilt processing, including the tilt settle time. The Evt_Tilted event is not sent to the GUI, that’s also the responsibility of the application if it wants some visual feedback.
PostModeEventToModes("Evt_Tilted", 0);
When we say P3SampleApp does not implement tilt processing, we mean it does not handle the Evt_TiltWarning and Evt_Tilted events.
The application can send the event Evt_ResetTiltWarnings to reset the tilt warning count back to zero. This is rarely used since the warning count is reset to 0 when TiltMode starts, i.e. with every new ball. By definition, a tilt ends the ball and that leads to a new ball. I wish the event data was the new tilt warning count, but unfortunately, the event data is ignored.
PostModeEventToModes("Evt_ResetTiltWarnings", 0);
TiltMode also handles custom tilt events sent by the application. It’s unlikely your application will need these events. We only mention them for completion and to justify the two tilt settings.
Sending the event Evt_NonStandardTiltWarning causes a tilt detection similar to the tilt bob hitting the ring.
Sending the event Evt_NonStandardTilt causes an immediate tilt similar to a tilt detection exceeding the number of tilt warnings.
The event data is ignored for both of these events.
BaseGameMode is also part of the SDK. This mode implements an event handler for Evt_Tilted. The handler simply sets a flag to remember the ball is tilting. This will block the display of the bonus in the Evt_BallEnded event handler. See the section “showBonusIfTiltActive” below if you want to show the bonus even if the ball has tilted.
P3SABaseGameMode is the mode that enables and disables TiltMode programmatically.
Edit Assets\Scripts\Modes\P3SABaseGameMode.cs and insert the lines in bold:
protected override void StartNewBall() { base.StartNewBall(); p3.AddMode(homeMode); PostModeEventToModes("Evt_EnableTilt", true); } // ########################################## // # Ball End // ########################################## // Wait for an event from the active game mode signalling the ball is over. protected override bool BallEndedEventHandler(string eventName, object eventData) { PostModeEventToModes("Evt_EnableTilt", false); if (!forcePlayerChangeActive) { // Reset scoring multiplier before applying bonus. ScoreManager.SetX (1); bonusInfo = homeMode.getBonusInfo(); } if (tilting) { PostModeEventToGUI("Evt_TiltComplete", null); } return base.BallEndedEventHandler(eventName, eventData); } |
It is now possible to tilt when the ball is waiting to be launched or during play, whereas it is not possible to tilt during the bonus display.
The Evt_TiltComplete event is sent to the GUI to remove tilt feedback on the screen. This event is sent when all balls have drained but before the bonus would be displayed.
Edit Assets\Scripts\Modes\P3SAPriorities.cs to add a constant required in the next section.
... public const int PRIORITY_MOVING_TARGET = PRIORITY_HOME + 60; public const int PRIORITY_BLACKOUT_LED_SHOW = PRIORITY_HOME + 399; public const int PRIORITY_TILTED = PRIORITY_HOME + 1000; |
TiltedMode is the new mode that disables everything when a tilt is detected. Do not confuse the application’s TiltedMode with the SDK’s TiltMode.
Create the file Assets\Scripts\Modes\Mechs\TiltedMode.cs with this content:
using Multimorphic.NetProc; using Multimorphic.NetProcMachine.Config; using Multimorphic.NetProcMachine.Machine; using Multimorphic.P3; using Multimorphic.P3.Colors; using Multimorphic.P3.Mechs; using Multimorphic.P3App.Logging; using Multimorphic.P3App.Modes; namespace Multimorphic.P3SA.Modes { public class TiltedMode : P3SAGameMode { private static string[] switches = { "slingL", "slingR", "buttonLeft0", "buttonLeft1", "buttonLeft2", "buttonRight0", "buttonRight1", "buttonRight2" }; public TiltedMode(P3Controller controller, int priority) : base(controller, priority) { } public override void mode_started() { base.mode_started(); for (int i = 0; i < LEDScripts.Count; i++) { LEDScripts[i] = LEDHelpers.OnLED(p3, LEDScripts[i], Color.black); } for (int j = 0; j < GUIInsertScripts.Count; j++) { GUIInsertScripts[j] = GUIInsertHelpers.OnInsert(p3, GUIInsertScripts[j], Color.black); } PostModeEventToModes("Evt_EnableLanes", false); PostModeEventToModes("Evt_EnableFlippers", false); PostModeEventToModes("Evt_EnableBumpers", false); PostModeEventToModes("Evt_BallSaveStop", null); PostModeEventToModes("Evt_RespawnEnable", false); PostModeEventToModes("Evt_AlTopperStop", 0); RequestPlaylist("Silence"); RaiseUpperFlippers(); p3.wallScoopMode.LowerWalls(WallScoopSequence.Simultaneously); p3.wallScoopMode.LowerScoops(WallScoopSequence.Simultaneously); AddModeEventHandler("Evt_ScoopHit", HoleEventHandler, Priority); AddModeEventHandler("Grid Event", BlockEventHandler, Priority); foreach (string switchName in switches) { add_switch_handler(switchName, "inactive", 0, BlockSwitchHandler); add_switch_handler(switchName, "active", 0, BlockSwitchHandler); } foreach (BallPathDefinition ballPath in p3.BallPaths.Values) { if (ballPath.ExitType == BallPathExitType.Target && ballPath.CompletedEvent.Contains("_inactive")) { // format of switch event must be "sw_<switch name>_active" for started events string[] strippedSwitchName = ballPath.CompletedEvent.Split('_'); string swName = strippedSwitchName[1]; add_switch_handler(swName, "inactive", 0, BlockSwitchHandler); add_switch_handler(swName, "active", 0, BlockSwitchHandler); // also register event handlers below } AddModeEventHandler(ballPath.StartedEvent, BlockEventHandler, Priority); if (ballPath.ExitType == BallPathExitType.Hole) { AddModeEventHandler(ballPath.CompletedEvent, HoleEventHandler, Priority); } else { AddModeEventHandler(ballPath.CompletedEvent, BlockEventHandler, Priority); } } } public override void mode_stopped() { RemovePlaylistRequests(); DropUpperFlippers(); base.mode_stopped(); } private bool BlockSwitchHandler(Switch sw) { return SWITCH_STOP; } private bool BlockEventHandler(string evtName, object evtData) { return EVENT_STOP; } private bool HoleEventHandler(string evtName, object evtData) { P3SABallLauncher.launch(); return EVENT_STOP; } private void RaiseUpperFlippers() { RaiseUpperLeftFlipper(); // Raise Upper Right Flipper after a small delay delay("RaiseUpperRightFlipper", EventType.None, 0.25, new Multimorphic.P3.VoidDelegateNoArgs(RaiseUpperRightFlipper)); } private void RaiseUpperLeftFlipper() { p3.Coils["auxLeft0"].Pulse(30); p3.Coils["auxLeft1"].Pulse(0); } private void RaiseUpperRightFlipper() { p3.Coils["auxRight0"].Pulse(30); p3.Coils["auxRight1"].Pulse(0); } private void DropUpperFlippers() { cancel_delayed("RaiseUpperRightFlipper"); p3.Coils["auxLeft1"].Disable(); p3.Coils["auxRight1"].Disable(); } } } |
Edit Assets\Scripts\Modes\SceneModes\HomeMode.cs
Add these declarations at the top of the class:
... private LanesMode lanesMode; private P3SAGameMode modeSummaryPendingMode; private TiltedMode tiltedMode; // Declare TwitchControlMode to handle twitch viewer interactions private TwitchControlMode twitchControlMode; private bool firstTimePerBall; private int scoreX; private int lastBallNum; private bool ballStarted; private bool isBallDrained; private bool isTiltActive; private bool isTiltSettled; ... |
Create the tiltedMode instance in the constructor:
... movingTargetMode = new MovingTargetMode (p3, P3SAPriorities.PRIORITY_MOVING_TARGET); sideTargetMode = new SideTargetMode (p3, P3SAPriorities.PRIORITY_SIDE_TARGET); tiltedMode = new TiltedMode(p3, P3SAPriorities.PRIORITY_TILTED); ... |
Add some event handlers in the constructor:
... AddModeEventHandler("Evt_ScoopHit", ScoopEventHandler, Priority); AddModeEventHandler("Evt_BallSearchBallLaunchRequest", BallSearchBallLaunchRequestEventHandler, Priority); AddModeEventHandler("Evt_TiltWarning", TiltWarningEventHandler, Priority); AddModeEventHandler("Evt_Tilted", TiltedEventHandler, Priority); ... |
Initialize some variables in mode_started():
... firstTimePerBall = true; ballStarted = false; isBallDrained = false; isTiltActive = false; isTiltSettled = true; ... |
Remove tiltedMode in mode_stopped():
... p3.RemoveMode (movingTargetMode); p3.RemoveMode (sideTargetMode); p3.RemoveMode (tiltedMode); ... |
Edit the sw_drain_active() method like this:
public bool sw_drain_active(Switch sw) { Multimorphic.P3App.Logging.Logger.Log(Multimorphic.P3App.Logging.LogCategories.Game, "HomeMode has received a drain event."); if (ballStarted || isTiltActive) { isBallDrained = true; if (isTiltSettled) { EndOfBall(); } } else { Multimorphic.P3App.Logging.Logger.Log( Multimorphic.P3App.Logging.LogCategories.Game, "Ball drained, but we're not processing it because ballStarted=" + ballStarted + " and isTiltActive=" + isTiltActive); } return SWITCH_CONTINUE; } |
Notice how sw_drain_active() is careful not to end the ball right away if the ball has tilted and the tilt bob has not settled yet. We don’t want to penalize the next player for a violent tilt by the preceding player.
Send Evt_BallDrained in EndOfBall():
private void EndOfBall() { PostModeEventToModes("Evt_BallDrained", 0); ballStarted = false; ... |
Add some new event handler methods at the end of the class. Since TiltMode does not send the Evt_Tilted event to the GUI, the application reposts the event to the GUI in TiltedEventHandler.
private bool TiltWarningEventHandler(string eventName, object eventData) { // int warningNumber = (int)eventData; // PlaySound("TiltWarningSound"); // PlaySound("TiltWarningVoiceMessage"); return EVENT_CONTINUE; } private bool TiltedEventHandler(string eventName, object eventData) { isTiltActive = true; isTiltSettled = false; AddModeEventHandler("Evt_TiltSettled", TiltSettledEventHandler, Priority); PostModeEventToGUI("Evt_Tilted", null); // PlaySound("TiltSound"); // PlaySound("TiltVoiceMessage"); base.p3.AddMode(tiltedMode); if (!ballStarted) { PostModeEventToGUI("Evt_HideButtonLegend", 0); ballStartMode.Disable(); P3SABallLauncher.launch(); } PostModeEventToModes("Evt_TiltProcess", null); return EVENT_CONTINUE; } private bool TiltSettledEventHandler(string eventName, object eventData) { isTiltSettled = true; RemoveModeEventHandler("Evt_TiltSettled", TiltSettledEventHandler, Priority); if (isBallDrained) { EndOfBall(); } return EVENT_CONTINUE; } |
In TiltedEventHandler, we launch the ball if a tilt is detected and the ball had not been launched yet. This gives good feedback the ball has been forfeited. The drain will also trigger the end of ball normally.
Notice how TiltSettledEventHandler is registered dynamically when the ball has tilted. We don’t want to register that handler in the constructor because Evt_TiltSettled is also sent after a tilt warning and that would confuse the end of ball processing.
After a tilt is detected, TiltedMode will block any target switch events and BallPath events. For some game modes, this is all the tilt processing that we need: these modes will no longer receive input and will lay dormant until the ball ends.
More generally, a game mode needs to know if the ball has tilted to abstain from reinstating features disabled by TiltedMode. Any input not blocked by TiltedMode is suspicious, for example: delayed handlers, custom mode events, or internet messages.
We want the modes to stop drawing on the screen. Most events destined to GUI should no longer be sent. If the mode has automatic GUI updates running on its behalf, it will have to instruct the SceneController to stop the updates. On the other hand, GUIInsert updates don’t really matter because they will be overridden by TiltedMode’s higher priority.
Some modes hold the ball captive while they show some information on the screen. WAMONH’s Supply Closet is a good example. These modes must implement tilt processing to launch the ball if tilt is detected when the ball is held. This gives good feedback to the player that the ball has tilted. It is also easier to implement since the ball drain will trigger the end of ball processing as usual. We did something in HomeMode if you remember.
A multiball mode is often a special case of a mode that holds the ball. The multiball mode should go ahead and launch the ball if the ball is held with no other balls in play. The remaining pending launches should be discarded to speed up the end of the ball.
You can open the profile menu by holding a flipper button and pressing start when the ball is pending launch. If you tilt the ball when the profile menu is still displayed, the profile menu continues to respond to the flipper buttons. This may or may not be a bug because the profile menu stays open and remains functional while the next ball is pending launch.
Finally, there is no need to implement tilt processing when tilt detection is disabled:
A game mode implements tilt processing by listening for the Evt_TiltProcess event. This event is sent at the end of TiltedEventHandler in HomeMode. Using a different event than Evt_Tilted guarantees HomeMode has time to initialize TiltedMode.
One strategy for the Evt_TiltProcess event handler is to set a flag that can be tested later.
The same code will be shared by many game modes, so it makes sense to move it to a common base class like P3SAGameMode. (Note P3SABaseGameMode is not applicable because it is the base mode of the game, not the base class of game modes). The code is not obtrusive so it can indeed be shared by all game modes.
Edit Assets\Scripts\Modes\GameModes\P3SAGameMode.cs
... protected bool isTilted; ... public P3SAGameMode (P3Controller controller, int priority) : base(controller, priority) { AddModeEventHandler("Evt_TiltProcess", TiltProcessEventHandler, base.Priority); } public override void mode_started() { ... isTilted = false; } ... protected virtual bool TiltProcessEventHandler(string eventName, object eventData) { isTilted = true; return EVENT_CONTINUE; } |
A specific game mode needs to check the isTested flag before executing problematic code:
... if (!isTilted) { // problematic code here } ... |
A mode that holds the ball must override TiltProcessEventHandler to launch the dead ball:
... private bool isBallHeld = false; ... isBallHeld = true; ... protected override bool TiltProcessEventHandler(string eventName, object eventData) { if (isBallHeld) { P3SABallLauncher.launch(); isBallHeld = false; } return base.TiltProcessEventHandler(eventName, eventData); } |
For many modes, it is easier to override TiltProcessEventHandler to immediately remove the mode. Clearly, the mode will not run problematic code anymore if it is stopped. This strategy does not work for all modes, so don’t do this in P3SAGameMode. For example, removing HomeMode too early breaks the game.
For the modes that can be removed immediately, you can add this event handler within the class:
protected override bool TiltProcessEventHandler(string eventName, object eventData) { p3.RemoveMode(this); return EVENT_CONTINUE; } |
This section shows how to implement tilt processing in each game mode.
Start by deleting the source files of these unused modes. There is no point in fixing unused code.
del Assets\Scripts\Modes\GameModes\HomeIntroMode.cs
del Assets\Scripts\Modes\GameModes\HomeIntroMode.cs.meta
del Assets\Scripts\Modes\GameModes\LiteLockMode.cs
del Assets\Scripts\Modes\GameModes\LiteLockMode.cs.meta
del Assets\Scripts\Modes\GameModes\Multiball.cs
del Assets\Scripts\Modes\GameModes\Multiball.cs.meta
del Assets\Scripts\Modes\GameModes\TargetCompletionMode.cs
del Assets\Scripts\Modes\GameModes\TargetCompletionMode.cs.meta
del Assets\Scripts\Modes\GameModes\TargetSaveMode.cs
del Assets\Scripts\Modes\GameModes\TargetSaveMode.cs.meta
del Assets\Scripts\Modes\GameModes\TargetScoresMode.cs
del Assets\Scripts\Modes\GameModes\TargetScoresMode.cs.meta
del Assets\Scripts\Modes\GameModes\TimerMode.cs
del Assets\Scripts\Modes\GameModes\TimerMode.cs.meta
del Assets\Scripts\Modes\GameModes\TripleTargetsMode.cs
del Assets\Scripts\Modes\GameModes\TripleTargetsMode.cs.meta
del Assets\Scripts\Modes\LightShows\RotatingSmoothFadeMode.cs
del Assets\Scripts\Modes\LightShows\RotatingSmoothFadeMode.cs.meta
del Assets\Scripts\Modes\LightShows\SirenMode.cs
del Assets\Scripts\Modes\LightShows\SirenMode.cs.meta
del Assets\Scripts\Modes\Mechs\CheatDetectorMode.cs
del Assets\Scripts\Modes\Mechs\CheatDetectorMode.cs.meta
Edit these game modes to add the following TiltProcessEventHandler method:
Assets\Scripts\Modes\GameModes\LanesMode.cs
Assets\Scripts\Modes\GameModes\RespawnMode.cs
Assets\Scripts\Modes\GameModes\ShotCounter.cs
Assets\Scripts\Modes\GameModes\SideTargetMode.cs
protected override bool TiltProcessEventHandler(string eventName, object eventData) { p3.RemoveMode(this); return EVENT_CONTINUE; } |
Edit these modes to handle the Evt_TiltProcess event. The code is different because these modes are not P3SAGameMode subclasses:
Assets\Scripts\Modes\GameModes\TwitchControlMode.cs
Assets\Scripts\Modes\GameModes\MovingTargetMode.cs
Assets\Scripts\Modes\Mechs\ShotsMode.cs
Register the Evt_TiltProcess event handler in the constructor:
AddModeEventHandler("Evt_TiltProcess", TiltProcessEventHandler, base.Priority); |
Add the non-virtual TiltProcessEventHandler method:
private bool TiltProcessEventHandler(string eventName, object eventData) { p3.RemoveMode(this); return EVENT_CONTINUE; } |
See the next section for the GUI side of MovingTargetMode.
Edit Assets\Scripts\Modes\SceneModes\IntroVideoMode.cs to add this TiltProcessEventHandler method. The Abort() call tells the GUI to stop playing the video when tilting:
protected override bool TiltProcessEventHandler(string eventName, object eventData) { Abort(); p3.RemoveMode(this); return EVENT_CONTINUE; } |
I have decided not to change HUDMode. The idea is to stop other modes from sending those events that need to be forwarded to the GUI.
I have also decided to leave P3SAButtonCombos the way it is because it is mostly for system debugging. For a production game, I would rather remove this source file from the build.
There is no need to implement tilt processing in these classes because they are not modes:
Assets\Scripts\Modes\GameModes\ModeSummary.cs
Assets\Scripts\Modes\Mechs\P3SABallLauncher.cs
When tilt is detected, MovingTargetMode removes itself but the cube continues to rotate and change color. To stop the cube immediately, you need to change the MovingTarget component.
Edit Assets\Scripts\GUI\Home\MovingTarget.cs like this:
... public class MovingTarget : P3Aware { ... private bool isTilted; ... public override void Start () { ... isTilted = false; } protected override void CreateEventHandlers() { ... AddModeEventHandler("Evt_Tilted", TiltedEventHandler); } private void TiltedEventHandler(string eventName, object eventData) { isTilted = true; } ... public void OnTriggerEnter(Collider other) { if (!isTilted && disabledCountdown < 0) // if we're not disabled ... } public override void Update () { if (!isTilted) { ... original Update method body here ... } } |
We will create a prefab to show a tilt message on the screen. The next section will show how the prefab is used.
The prefab is
In the Unity Editor, select the Home scene.
In the Hierarchy Window,
Within the “Canvas Scaler”, change the “UI Scale Mode” to “Scale With Screen Size”
Change “Reference Resolution” X to 1080 and Y to 1920.
Change Anchor Min X to 0, Anchor Min Y to 0.
Change Anchor Max X to 1, Anchor Max Y to 1.
Change Left to 0, Top to 0, Z to 0, Right to 0, Bottom to 0.
Change the color to 255,0,0,96
Make sure Pos X is 0, Pos Y is 0, Pos Z is 0.
Change Width to 1080, Height to 1920
Change Text to Warning.
Change Font Style to Bold.
Change Font Size to 200.
Change the alignment to Horizontal Center and Vertical Center.
Change the color to opaque white (255, 255, 255, 255)
In the Project window, expand Assets > Resources and select Prefabs.
Drag TiltOverlay from the Hierarchy window to the Assets > Resources > Prefabs Window.
This will remove TiltOverlay from the Home scene and create a prefab called TiltOverlay.
Let us add feedback on the playfield’s screen when a tilt warning or full tilt is detected. In your application, you should also add feedback on the backbox screen.
Edit Assets\Scripts\GUI\Home\HomeSceneController.cs like this:
... private float VUKStageTimer; private float tiltOverlayTimer; private ModeSummaryDisplay modeSummaryDisplay; private GameObject tiltOverlay; ... protected override void CreateEventHandlers() { ... AddModeEventHandler("Evt_TiltWarning", TiltWarningEventHandler); AddModeEventHandler("Evt_Tilted", TiltedEventHandler); AddModeEventHandler("Evt_TiltComplete", TiltCompleteEventHandler); } private void TiltWarningEventHandler(string eventName, object eventData) { ShowTiltOverlay("Warning", 2f); } private void TiltedEventHandler(string eventName, object eventData) { ShowTiltOverlay("Tilt", 0f); } private void TiltCompleteEventHandler(string eventName, object eventData) { if (tiltOverlay != null) { Destroy(tiltOverlay); } tiltOverlayTimer = 0f; } private void ShowTiltOverlay(string tiltMessage, float timer) { if (tiltOverlay == null) { tiltOverlay = (GameObject)Instantiate(Resources.Load("Prefabs/TiltOverlay")); } Transform tiltTextTransform = tiltOverlay.transform.Find("TiltText"); if (tiltTextTransform != null) { Text tiltText = tiltTextTransform.GetComponent<Text>(); if (tiltText != null) { tiltText.text = tiltMessage; } } tiltOverlayTimer = timer; } // Update is called once per frame public override void Update () { ... if (tiltOverlayTimer > 0) { tiltOverlayTimer -= Time.deltaTime; if (tiltOverlayTimer <= 0) { Destroy(tiltOverlay); } } } |
The default value of the settings “Tilt Detection Enable” is false. If you want your application to be sensitive to the tilt bob by default, you will need to override the default for the TiltEnable GameAttribute.
The default value of the settings “Tilt Bob Enable” is true. There is no need to override that default.
Edit Assets\Scripts\Modes\DataManagement\P3SASettingsMode.cs to modify the CustomizeGameAttribute() method after the comment:
protected override GameAttribute CustomizeGameAttribute(GameAttribute attr) { ... // Set the following values based on what's appropriate for your game else if (attr.item == "TiltEnable") { attr.value.Set(true); attr.defaultValue.Set(true); attr.compareOptions = GameAttributeCompareOptions.CompareAllExceptValue | GameAttributeCompareOptions.UpdateValueIfDefaultChanged; } ... |
By default, tilting sets the bonus to 0 and the bonus sequence is not shown on the screen.
If you play WAMONH, you will notice the bonus is shown even if the ball has tilted. This section shows how to implement this feature. This is optional.
Edit Assets\Scripts\Modes\P3SABaseGameMode.cs to set the flag showBonusIfTiltActive in the constructor. This tells BaseGameMode to show the bonus even if the ball has tilted.
... public P3SABaseGameMode (P3Controller controller) : base(controller) { ... enableTwitchIntegration = true; showBonusIfTiltActive = true; ... } ... |
Edit Assets\Scripts\Modes\SceneModes\HomeMode.cs to
... public HomeMode (P3Controller controller, int priority, string SceneName) : base(controller, priority, SceneName) { ... AddModeEventHandler("Evt_BallSearchBallLaunchRequest", BallSearchBallLaunchRequestEventHandler, Priority); AddModeEventHandler("Evt_TiltWarning", TiltWarningEventHandler, Priority); AddModeEventHandler("Evt_Tilted", TiltedEventHandler, Priority); AddModeEventHandler("Evt_TiltProcessingComplete", TiltProcessingCompleteEventHandler, Priority); ... } ... public virtual BonusInfo getBonusInfo() { BonusInfo info = new BonusInfo(); info.Append (getBonusItems()); if (isTiltActive) { info.AddItem(new BonusItem("TILT", (long)(-info.GetTotal() * ScoreManager.GetBonusX()))); info.SetMultiplier(0f); } else { info.SetMultiplier(ScoreManager.GetBonusX()); } return info; } ... private bool TiltedEventHandler(string eventName, object eventData) { isTiltActive = true; isTiltSettled = false; AddModeEventHandler("Evt_TiltSettled", TiltSettledEventHandler, Priority); PostModeEventToGUI("Evt_Tilted", null); // PlaySound("TiltSound"); // PlaySound("TiltVoiceMessage"); PostModeEventToGUI("Evt_DefineBonusTitle", "NO BONUS"); base.p3.AddMode(tiltedMode); if (!ballStarted) { PostModeEventToGUI("Evt_HideButtonLegend", 0); ballStartMode.Disable(); P3SABallLauncher.launch(); } PostModeEventToModes("Evt_TiltProcess", null); return EVENT_CONTINUE; } ... private bool TiltProcessingCompleteEventHandler(string eventName, object eventData) { if (isTiltActive) { PostModeEventToGUI("Evt_DefineBonusTitle", "BONUS"); }
return EVENT_CONTINUE; } |
Evt_TiltProcessingComplete is sent when TiltMode is removed which happens when resetting to a new ball but before HomeMode is removed. Clearly the bonus is no longer displayed at that time.
To help testing tilt in the simulator, edit Configuration\AppConfig.json and add this key mapping:
{"Key":"T", "Switch":"tilt"}, |
Now when running the application within the simulator, pressing the letter T will activate the tilt switch.
To unblock the application when a full tilt is detected, press 0 (zero). This simulates a ball drain and triggers the end of ball processing.
Make sure to enable both “Tilt Detection Enabled” and “Use tilt bob for tilt detection” under Settings/Mechs/Tilt within the service menu.
Don’t forget to also set both settings when deploying the application. The settings on the machine are independent of the settings in the simulator.