Skip to content

Commit dbfa9d1

Browse files
committed
plunger: Add "fire and pull back" mode for kickback.
1 parent 37f0565 commit dbfa9d1

5 files changed

Lines changed: 124 additions & 34 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
uid: plungers
3+
title: Plungers
4+
description: VPE supports manual plungers, auto plungers, and ROM-controlled kickback-style plungers.
5+
---
6+
7+
# Plungers
8+
9+
Plungers are physics objects that can launch a ball by moving along their stroke. They are commonly used for manual ball launchers, auto-launchers, and kickback mechanisms such as an outlane ball saver.
10+
11+
## Setup
12+
13+
Add a *Plunger Collider* to the plunger object to make it part of the physics simulation. The plunger can then be wired through the [Coil Manager](xref:coil_manager) or [Wire Manager](xref:wire_manager), depending on whether it is controlled by game logic or player input.
14+
15+
The most important plunger settings for gameplay are:
16+
17+
- *Stroke*: total travel distance of the plunger tip.
18+
- *Park Position*: normalized rest position from `0` to `1`, where `0` is fully forward and `1` is fully retracted.
19+
- *Speed Fire*: release speed used when the plunger fires.
20+
- *Speed Pull*: pull-back speed used when the plunger is pulled back by a coil or input.
21+
- *Mech Plunger*: enables synchronization with analog plunger input.
22+
- *Auto Plunger*: makes the plunger rest at *Park Position* and fire from full retraction when triggered.
23+
24+
## Coil Modes
25+
26+
The plunger exposes multiple coil items. Pick the one that matches the real mechanism or the original VPX script behavior.
27+
28+
| Coil item | Inspector label | Coil enabled | Coil disabled | Typical use |
29+
| --- | --- | --- | --- | --- |
30+
| `c_pull` | Pull back | Pulls the plunger back | Fires from the current position | Manual launch plunger wired to a button or input |
31+
| `c_autofire` | Auto-fire | Fires the plunger | No action | ROM-controlled auto-launchers where the plunger returns to rest naturally |
32+
| `c_fire_pullback` | Fire and pull back | Fires from full retraction | Pulls the plunger back | Kickbacks that call `Fire` on solenoid on and `PullBack` on solenoid off |
33+
34+
## Manual Launchers
35+
36+
For a normal shooter lane plunger, map player input to `c_pull`. Pressing the input pulls the plunger back, and releasing it fires from the current position. If the table uses analog plunger input, enable *Mech Plunger* so the physics plunger follows the analog input position.
37+
38+
## Auto-Launchers
39+
40+
For ROM-controlled launch buttons, enable *Auto Plunger* and map the game logic coil to `c_autofire`. In this mode the plunger rests at *Park Position*. When the coil fires, VPE launches from full retraction, which gives a consistent launch impulse.
41+
42+
## Kickbacks
43+
44+
Some VPX tables implement kickbacks with a plunger object and script the solenoid like this:
45+
46+
```vb
47+
Sub SolKickback(enabled)
48+
If enabled Then
49+
Plunger1.Fire
50+
Else
51+
Plunger1.PullBack
52+
End If
53+
End Sub
54+
```
55+
56+
Use `c_fire_pullback` for this pattern. It fires from full retraction when the ROM enables the coil, and pulls the plunger back when the ROM disables the coil again.
57+
58+
VPE also applies the disabled state once when this coil mode is mapped at startup. This mirrors VPX tables that call `PullBack` during table initialization, and ensures a kickback starts clear of the ball path before the ROM has emitted its first coil event.
59+
60+
For kickbacks that should not block the outlane while idle, enable *Auto Plunger* and set *Park Position* to the clear/resting position. This keeps the physics target at the parked position while idle. If the original VPX table uses a small park position such as `0.1666`, start with the same value and tune only if the VPE geometry needs it.
61+
62+
Avoid enabling *Mech Plunger* on kickbacks unless an analog input should control that specific plunger. A mechanical plunger with no analog input targets position `0`, which is fully forward and can make the plunger block the lane during gameplay.

VisualPinball.Unity/Documentation~/creators-guide/toc.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,12 @@
9999
items:
100100
- name: Troughs / Ball Drains
101101
href: manual/mechanisms/troughs.md
102-
- name: Flippers
103-
href: manual/mechanisms/flippers.md
104-
- name: Slingshots
105-
href: manual/mechanisms/slingshots.md
102+
- name: Flippers
103+
href: manual/mechanisms/flippers.md
104+
- name: Plungers
105+
href: manual/mechanisms/plungers.md
106+
- name: Slingshots
107+
href: manual/mechanisms/slingshots.md
106108
- name: Light Groups
107109
href: manual/mechanisms/light-groups.md
108110
- name: Teleporters

VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,18 @@ public void OnStart()
117117
/// </summary>
118118
/// <param name="coilMapping">Mapping to assign</param>
119119
/// <param name="isLampCoil">If it's a flasher</param>
120-
private void AssignCoilMapping(CoilMapping coilMapping, bool isLampCoil)
121-
{
122-
AssignCoilMapping(coilMapping.Id, coilMapping, isLampCoil);
123-
if (int.TryParse(coilMapping.Id, out var id) && id.ToString() != coilMapping.Id) {
124-
AssignCoilMapping(id.ToString(), coilMapping, isLampCoil);
125-
}
126-
}
120+
private void AssignCoilMapping(CoilMapping coilMapping, bool isLampCoil)
121+
{
122+
AssignCoilMapping(coilMapping.Id, coilMapping, isLampCoil);
123+
if (int.TryParse(coilMapping.Id, out var id) && id.ToString() != coilMapping.Id) {
124+
AssignCoilMapping(id.ToString(), coilMapping, isLampCoil);
125+
}
126+
127+
if (!isLampCoil && coilMapping.Device != null && coilMapping.DeviceItem == PlungerComponent.FireAndPullBackCoilId) {
128+
// This mode's inactive state is actively pulled back, matching VPX scripts that call PullBack at table init.
129+
_coilDevices[coilMapping.Device].Coil(coilMapping.DeviceItem)?.OnCoil(false);
130+
}
131+
}
127132

128133
private void AssignCoilMapping(string id, CoilMapping coilMapping, bool isLampCoil)
129134
{

VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,26 @@ public class PlungerApi : CollidableApi<PlungerComponent, PlungerColliderCompone
5252
public DeviceCoil PullCoil;
5353

5454
/// <summary>
55-
/// Auto-fires the plunger.
56-
/// </summary>
57-
public DeviceCoil FireCoil;
58-
59-
// todo
60-
public event EventHandler Timer;
55+
/// Auto-fires the plunger.
56+
/// </summary>
57+
public DeviceCoil FireCoil;
58+
59+
/// <summary>
60+
/// Fires the plunger when enabled, and pulls it back when disabled.
61+
/// </summary>
62+
public DeviceCoil FireAndPullBackCoil;
63+
64+
// todo
65+
public event EventHandler Timer;
6166

6267
public bool DoRetract { get; set; } = true;
6368

6469
internal PlungerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine)
65-
{
66-
PullCoil = new DeviceCoil(Player, PullBack, Fire);
67-
FireCoil = new DeviceCoil(Player, Fire);
68-
}
70+
{
71+
PullCoil = new DeviceCoil(Player, PullBack, Fire);
72+
FireCoil = new DeviceCoil(Player, Fire);
73+
FireAndPullBackCoil = new DeviceCoil(Player, FireFromFullRetract, PullBack);
74+
}
6975

7076
internal void OnAnalogPlunge(InputAction.CallbackContext ctx)
7177
{
@@ -106,6 +112,16 @@ public void PullBack()
106112
}
107113

108114
public void Fire()
115+
{
116+
Fire(null);
117+
}
118+
119+
public void FireFromFullRetract()
120+
{
121+
Fire(1f);
122+
}
123+
124+
private void Fire(float? startPositionOverride)
109125
{
110126
var collComponent = GameObject.GetComponent<PlungerColliderComponent>();
111127
if (!collComponent) {
@@ -117,7 +133,9 @@ public void Fire()
117133
ref var plungerState = ref state.PlungerStates.GetValueByRef(ItemId);
118134

119135
// check for an auto plunger
120-
if (isAutoPlunger) {
136+
if (startPositionOverride.HasValue) {
137+
PlungerCommands.Fire(startPositionOverride.Value, ref plungerState.Velocity, ref plungerState.Movement, in plungerState.Static);
138+
} else if (isAutoPlunger) {
121139
// Auto Plunger - this models a "Launch Ball" button or a
122140
// ROM-controlled launcher, rather than a player-operated
123141
// spring plunger. In a physical machine, this would be
@@ -141,12 +159,13 @@ public void Fire()
141159
private IApiCoil Coil(string deviceItem)
142160
{
143161
return deviceItem switch
144-
{
145-
PlungerComponent.FireCoilId => FireCoil,
146-
PlungerComponent.PullCoilId => PullCoil,
147-
_ => throw new ArgumentException($"Unknown plunger coil \"{deviceItem}\". Valid names are: [ \"{PlungerComponent.FireCoilId}\", \"{PlungerComponent.PullCoilId}\" ].")
148-
};
149-
}
162+
{
163+
PlungerComponent.FireCoilId => FireCoil,
164+
PlungerComponent.PullCoilId => PullCoil,
165+
PlungerComponent.FireAndPullBackCoilId => FireAndPullBackCoil,
166+
_ => throw new ArgumentException($"Unknown plunger coil \"{deviceItem}\". Valid names are: [ \"{PlungerComponent.FireCoilId}\", \"{PlungerComponent.PullCoilId}\", \"{PlungerComponent.FireAndPullBackCoilId}\" ].")
167+
};
168+
}
150169

151170
#region Collider Generation
152171

VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ public void UnpackReferences(byte[] data, Transform root, PackagedRefs refs, Pac
7373
protected override Type MeshComponentType { get; } = typeof(MeshComponent<PlungerData, PlungerComponent>);
7474
protected override Type ColliderComponentType { get; } = typeof(ColliderComponent<PlungerData, PlungerComponent>);
7575

76-
public const string PullCoilId = "c_pull";
77-
public const string FireCoilId = "c_autofire";
76+
public const string PullCoilId = "c_pull";
77+
public const string FireCoilId = "c_autofire";
78+
public const string FireAndPullBackCoilId = "c_fire_pullback";
7879
#endregion
7980

8081
#region Runtime
@@ -97,10 +98,11 @@ private void Awake()
9798

9899
#region Wiring
99100

100-
public IEnumerable<GamelogicEngineCoil> AvailableCoils => new[] {
101-
new GamelogicEngineCoil(PullCoilId) { Description = "Pull back" },
102-
new GamelogicEngineCoil(FireCoilId) { Description = "Auto-fire" },
103-
};
101+
public IEnumerable<GamelogicEngineCoil> AvailableCoils => new[] {
102+
new GamelogicEngineCoil(PullCoilId) { Description = "Pull back" },
103+
new GamelogicEngineCoil(FireCoilId) { Description = "Auto-fire" },
104+
new GamelogicEngineCoil(FireAndPullBackCoilId) { Description = "Fire and pull back" },
105+
};
104106

105107
IApiCoil ICoilDeviceComponent.CoilDevice(string deviceId) => ((IApiCoilDevice)PlungerApi).Coil(deviceId);
106108

0 commit comments

Comments
 (0)