Lit Lane Rotation
Related Config File Sections:
In this How To guide, we're going to look at how you can set up a series of lanes with lights (or standup targets) which you can rotate with the flipper buttons. We'll also look at how you can play a light show when they're complete and assign scoring. "Lane change" is a fairly popular thing in pinball machines, typically with a set of lanes at the top of the machine. They start all off, and then as you roll over them they light up. You can use the flippers to cycle through which lanes are lit, and when they're all lit, you get a score (or increase the bonus multiplier, etc.) For this how to guide we'll use a Williams Indiana Jones machine. Here's a video that shows the final result of building everything we outline in this guide.
See it in action:
Let's begin!
(A) Configure your devices
We'll assume that you already have your switches and lights defined in
the switches:
and lights:
section of your machine-wide config. (If
you have RGB LEDs, you can follow this tutorial also---just substitute
leds:
for lights:
.)
Next you need to define your shots
, which is where you pair your
switches and lights so you know that Switch A is associated with Light
B, and so on.
Do this in your base mode configuration (in
/modes/base/config/base.yaml
), following the documentation for the
shots:
section in the configuration file reference. In Indiana Jones,
we've given the lights and switches the same names (which is ok since
they're different types of devices), so our shots:
section looks like
this:
#! switches:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
#! lights:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
##! mode: base
shots:
indy_i:
switch: indy_i
show_tokens:
light: indy_i
indy_n:
switch: indy_n
show_tokens:
light: indy_n
indy_d:
switch: indy_d
show_tokens:
light: indy_d
indy_y:
switch: indy_y
show_tokens:
light: indy_y
Next, configure a shot group
, which is where you can group individual
shots together so you can interact with as a single group, like this:
#! switches:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
#! lights:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
##! mode: base
#! shots:
#! indy_i:
#! switch: indy_i
#! show_tokens:
#! light: indy_i
#! indy_n:
#! switch: indy_n
#! show_tokens:
#! light: indy_n
#! indy_d:
#! switch: indy_d
#! show_tokens:
#! light: indy_d
#! indy_y:
#! switch: indy_y
#! show_tokens:
#! light: indy_y
shot_groups:
indy_lanes:
shots: indy_i, indy_n, indy_d, indy_y
Note that the order of your shots is important since that's how MPF
knows the order of them in order to do shot rotation (more on that
later.) At this point if you run MPF and start a game, if you hit one of
your shots then you should see the light turn on. (How does MPF know
this? Because you haven't specified a shot profile for these shots, so
MPF uses the default shot profile
which has them in an unlit state at
first and then lights them once they're hit.) Notice that if you hit
the flippers they don't rotate, and once you light all the shots they
just stay on. We'll change both those behaviors next! Also notice that
the states of the shots are stored per-player. If you play and drain a
ball, when you start the next ball, the shots will be in the same state
before they drained. Also note that if you start a multi-player game,
the shots will reset when the second player starts since that player
hasn't hit any yet, and when the first player goes to Ball 2, MPF will
reset the shots back to what the first player had.
(B) Configure shot rotation
Next, let's configure the shots so that their lit/unlit states rotate (or shift) to the left or right when the player hits the flipper. This step is optional of course. In some situations you might not want your shots to rotate (like the ADVENTURE standups in Indiana Jones where the player has to hit all the shots to light the Path of Adventure). To do this, we have to configure the shot group for rotation events. We configure two different events---one to rotate left and one to rotate right. You can actually configure rotation events in either your machine-wide config or in a mode-specific config. If you do it machine-wide, then the rotation events will always be active. If you configure it in a mode config, then they're only active as long as that mode's active. In this tutorial we're going to configure them in the base mode as well but you could put that group in any other mode and load/unload it as you need it.
#! switches:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
#! lights:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
##! mode: base
#! shots:
#! indy_i:
#! switch: indy_i
#! show_tokens:
#! light: indy_i
#! indy_n:
#! switch: indy_n
#! show_tokens:
#! light: indy_n
#! indy_d:
#! switch: indy_d
#! show_tokens:
#! light: indy_d
#! indy_y:
#! switch: indy_y
#! show_tokens:
#! light: indy_y
shot_groups:
indy_lanes:
shots: indy_i, indy_n, indy_d, indy_y
rotate_left_events: left_flipper_active
rotate_right_events: right_flipper_active
You can specify whatever event name(s) you want for your rotation
events. By default, MPF will post
(switch_name)_active when every switch in the game activates. So in our case, our
flipper buttons from the machine-wide switches: section are named
left_flipper
and right_flipper
. If you named your switch
s_lower_left_flipper_button
, then your event name would be
s_lower_left_flipper_button_active
. Some older pinball machines only
rotate lane shots to the right, regardless of which flipper button is
pressed. In that case you'd only have an entry for rotate_right_events,
but you'd add both the left and right flipper events, like this:
#! switches:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
#! lights:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
##! mode: base
#! shots:
#! indy_i:
#! switch: indy_i
#! show_tokens:
#! light: indy_i
#! indy_n:
#! switch: indy_n
#! show_tokens:
#! light: indy_n
#! indy_d:
#! switch: indy_d
#! show_tokens:
#! light: indy_d
#! indy_y:
#! switch: indy_y
#! show_tokens:
#! light: indy_y
shot_groups:
indy_lanes:
shots: indy_i, indy_n, indy_d, indy_y
rotate_right_events: left_flipper_active, right_flipper_active
Of course you can use whatever event(s) you want to rotate the shots.
Many System 11 machines had lit shots in the inlanes and outlanes that
rotate based on slingshot hits, so in that case you'd set them up and
then use left_slingshot_active
and right_slingshot_active
as your
events (changed based on your actual switch names, of course). Now if
you run MPF and start a game, you should be able to light a shot by
hitting it and then see it rotate when you hit the flippers. (Note that
you have to actually start a game. shots are not active when a game is
not in progress.)
(C) Configure your shots to reset when they're complete
If you played with this, you most likely noticed that the shots didn't actually reset once they were all complete. So that's what we'll do in this step. The way we'll do that is to add an entry for reset_events: which specifies what events will cause the shots to reset. To do that, go back into your base.yaml file and add another setting to your indy_lanes shot group for reset_events:, like this:
#! switches:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
#! lights:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
##! mode: base
#! mode:
#! start_events: ball_started
#! shots:
#! indy_i:
#! switch: indy_i
#! show_tokens:
#! light: indy_i
#! indy_n:
#! switch: indy_n
#! show_tokens:
#! light: indy_n
#! indy_d:
#! switch: indy_d
#! show_tokens:
#! light: indy_d
#! indy_y:
#! switch: indy_y
#! show_tokens:
#! light: indy_y
shot_groups:
indy_lanes:
shots: indy_i, indy_n, indy_d, indy_y
rotate_left_events: left_flipper_active
rotate_right_events: right_flipper_active
reset_events:
indy_lanes_lit_complete: 1s
##! test
#! start_game
#! assert_str_condition unlit device.shot_groups.indy_lanes.common_state
#! hit_and_release_switch indy_i
#! hit_and_release_switch indy_n
#! hit_and_release_switch indy_d
#! hit_and_release_switch indy_y
#! advance_time_and_run .1
#! assert_str_condition lit device.shot_groups.indy_lanes.common_state
#! advance_time_and_run 1
#! assert_str_condition unlit device.shot_groups.indy_lanes.common_state
There are a few things going on here. First, notice that the name of our
event is indy_lanes_default_lit_complete
. That seems like a mouthful,
but it's logical if you break it down! MPF automatically posts events
from shot groups based on what's happening in that group. What happens
is that every time a shot changes state, the shot group it belongs to
checks the state of all the shots in the group. If they are all the
same, then it posts a "complete" event which we can use to assign
scores, trigger effects, and reset the group. The format of that event
is (name)_(state)_complete. In
our case, our shot group name is indy_lanes
, and the state of the
shots that we're interested in is called lit. Also notice that
instead of adding indy_lanes_lit_complete
to the same line as
reset_events
, we put it on its own line along with a time entry of
1s
. This format is available for every device configuration setting
where we specify events, and it means that when that event is posted, it
will wait for the specified time to pass before actually performing its
action. The reason we did this is because without it, the shots will
reset themselves instantly when they complete, which might be confusing
to the player since it will look like they have 3 of the 4 shots
complete, they hit the 4th one, and then they all go out. The player
will think, "Wait, what just happened? Did I get it?" So by adding
this delay, we wait 1 second after completing all the shots before
they're reset. At this point you should be able to launch MPF, start a
game, hit a shot, rotate it with the flippers, and when you complete all
the shots, they should wait a second and then reset. Cool!
(D) Add some scoring
Next lets add some scoring to your shots. We're going to make it so the player gets 5,000 points if they hit and unlit shot (which will then light), 100 points if they hit a shot that's already lit (since they failed to rotate or nudge the ball into an unlit lane), and 10,000 points when they complete all the shots in the group. To do that, add a scoring section to your base.yaml mode configuration. (Or you can add it to your machine-wide config if you want to keep all your scoring entries in one place.) It should look like this:
#! switches:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
#! lights:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
##! mode: base
#! mode:
#! start_events: ball_started
#! shots:
#! indy_i:
#! switch: indy_i
#! show_tokens:
#! light: indy_i
#! indy_n:
#! switch: indy_n
#! show_tokens:
#! light: indy_n
#! indy_d:
#! switch: indy_d
#! show_tokens:
#! light: indy_d
#! indy_y:
#! switch: indy_y
#! show_tokens:
#! light: indy_y
#! shot_groups:
#! indy_lanes:
#! shots: indy_i, indy_n, indy_d, indy_y
#! rotate_left_events: left_flipper_active
#! rotate_right_events: right_flipper_active
#! reset_events:
#! indy_lanes_lit_complete: 1s
variable_player:
indy_lanes_unlit_hit:
score: 5000
indy_lanes_lit_hit:
score: 100
indy_lanes_lit_complete:
score: 10000
##! test
#! start_game
#! assert_str_condition unlit device.shot_groups.indy_lanes.common_state
#! hit_and_release_switch indy_i
#! hit_and_release_switch indy_n
#! hit_and_release_switch indy_d
#! hit_and_release_switch indy_y
#! advance_time_and_run .1
#! assert_str_condition lit device.shot_groups.indy_lanes.common_state
#! advance_time_and_run 1
#! assert_str_condition unlit device.shot_groups.indy_lanes.common_state
#! assert_player_variable 30000 score
Again, these event names might seem crazy, but they're all very logical
if you break them down. The shot group will post events any time one of
its member shots is hit. This is similar to the complete event from
the previous step, except the
hit event
ends in _hit
and is posted with every hit to any shot versus the
_complete event which is only posted when all the shots in the group
have made it to the same state. Remember that since we haven't assigned
any shot profiles (nor will we), we're using the default shot profile
which has two steps: unlit
and lit
, with the unlit
step running a
light script that turns off the associated light or LED and the lit
step running a light script that turns on the light. One anomaly with
the scoring is that when you hit the last shot to complete the group,
you'll actually get 15,000 points instead of 10,000. That's because
when you hit that final unlit shot, you get 5,000 points for hitting an
unlit shot plus the 10,000 points for completing the group. If you
really only want 10,000 points total on the last hit, then you could
just change the complete
event to 5,000 points, or setup a logic block
to track the count and trigger the scoring.
(E) Add a light show to play a cool effect on completion
As it is now, when you complete the lanes, you get the points which is cool, but after 1 second the lights just sort of unceremoniously reset. Boring! So let's create a light show that flashes the lane lights when you complete the lanes. To do this, let's first create a light show (details in Steps A and B here) called `indy_lanes_complete.yaml`:
##! show: indy_lanes_complete
- duration: 1
lights:
indy_i: ff
indy_n: 00
indy_d: ff
indy_y: 00
- duration: 1
lights:
indy_i: 00
indy_n: ff
indy_d: 00
indy_y: ff
Obviously you can make this show do whatever you want; I opted for a simple one that sort of alternates the lights. Then to run the light show, go back to your base.yaml mode config and add a light_player: entry which plays this show when the lanes are complete, like this:
#! switches:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
#! lights:
#! indy_i:
#! number: 1
#! indy_n:
#! number: 2
#! indy_d:
#! number: 3
#! indy_y:
#! number: 4
##! show: indy_lanes_complete
#! - duration: 1
#! lights:
#! indy_i: ff
#! indy_n: 00
#! indy_d: ff
#! indy_y: 00
#! - duration: 1
#! lights:
#! indy_i: 00
#! indy_n: ff
#! indy_d: 00
#! indy_y: ff
##! mode: base
#! mode:
#! start_events: ball_started
#! shots:
#! indy_i:
#! switch: indy_i
#! show_tokens:
#! light: indy_i
#! indy_n:
#! switch: indy_n
#! show_tokens:
#! light: indy_n
#! indy_d:
#! switch: indy_d
#! show_tokens:
#! light: indy_d
#! indy_y:
#! switch: indy_y
#! show_tokens:
#! light: indy_y
#! shot_groups:
#! indy_lanes:
#! shots: indy_i, indy_n, indy_d, indy_y
#! rotate_left_events: left_flipper_active
#! rotate_right_events: right_flipper_active
#! reset_events:
#! indy_lanes_lit_complete: 1s
#! variable_player:
#! indy_lanes_unlit_hit:
#! score: 5000
#! indy_lanes_lit_hit:
#! score: 100
#! indy_lanes_lit_complete:
#! score: 10000
show_player:
indy_lanes_default_lit_complete:
indy_lanes_complete:
speed: 20
loops: 10
priority: 1
##! test
#! start_game
#! assert_str_condition unlit device.shot_groups.indy_lanes.common_state
#! hit_and_release_switch indy_i
#! hit_and_release_switch indy_n
#! hit_and_release_switch indy_d
#! hit_and_release_switch indy_y
#! advance_time_and_run .1
#! assert_str_condition lit device.shot_groups.indy_lanes.common_state
#! advance_time_and_run 1
#! assert_str_condition unlit device.shot_groups.indy_lanes.common_state
#! assert_player_variable 30000 score
If you've worked with shows before, these settings should be pretty
straightforward. Running this show at 20x the speed means that it runs
really fast. We set loops: 10
so it loops 10 times and then stops. The
only slightly confusing thing might be the priority: 1
setting. Any
time priority settings are added to mode config files, the setting is
added to the priority of the mode. For example, if you configure your
base mode to run at priority 100, that means that everything it does has
a priority of 100---slide shows, lights, sounds, etc. Adding
priority: 1
to this light_player entry just means that this light show
will run with a priority of 101 instead of 100, ensuring that it shows
up "on top" of anything else this mode is doing with those lights.
(F) Revisit your reset delay
At this point you should be all set and your machine's shots should
work like the shots in the video at the beginning of this guide. The
only loose end to tie up is reset_events
entry of
indy_lanes_lit_complete: 1s
. As it is now, when the lanes complete
(and while the light show is playing), your lanes will still be in their
"lit complete" state, meaning if the ball hits a lane within that
first second, the player won't get credit for it towards the second
round of lighting the lanes. You might want to remove the 1s and just
change that entry to reset_events: indy_lanes_lit_complete
. If you do
that and the player's ball hits a lane while the show is playing, then
they will get the score and credit towards the next round of lighting
the lanes (even though they won't see the lane light until after the
show stops since the show is running at a higher priority). Whether you
do this is a matter of personal taste. You could also set a stop event
for the light show and cancel it right away if the lane is hit again, or
you could not have a priority
entry in the light_player entry so
lighting the lane shows up while the show plays around it. Really there
are lots of options you can play with.
This is a full example:
# switches and lights in your machine config
switches:
indy_i:
number: 1
indy_n:
number: 2
indy_d:
number: 3
indy_y:
number: 4
lights:
indy_i:
number: 1
indy_n:
number: 2
indy_d:
number: 3
indy_y:
number: 4
##! show: indy_lanes_complete
# the show on complete
- duration: 1
lights:
indy_i: ff
indy_n: 00
indy_d: ff
indy_y: 00
- duration: 1
lights:
indy_i: 00
indy_n: ff
indy_d: 00
indy_y: ff
##! mode: base
# your base mode
mode:
start_events: ball_started
shots:
indy_i:
switch: indy_i
show_tokens:
light: indy_i
indy_n:
switch: indy_n
show_tokens:
light: indy_n
indy_d:
switch: indy_d
show_tokens:
light: indy_d
indy_y:
switch: indy_y
show_tokens:
light: indy_y
shot_groups:
indy_lanes:
shots: indy_i, indy_n, indy_d, indy_y
rotate_left_events: left_flipper_active
rotate_right_events: right_flipper_active
reset_events: indy_lanes_lit_complete
variable_player:
indy_lanes_unlit_hit:
score: 5000
indy_lanes_lit_hit:
score: 100
indy_lanes_lit_complete:
score: 10000
show_player:
indy_lanes_default_lit_complete:
indy_lanes_complete:
speed: 20
loops: 10
priority: 1
##! test
#! start_game
#! assert_str_condition unlit device.shot_groups.indy_lanes.common_state
#! hit_and_release_switch indy_i
#! hit_and_release_switch indy_n
#! hit_and_release_switch indy_d
#! hit_and_release_switch indy_y
#! advance_time_and_run .1
#! assert_str_condition unlit device.shot_groups.indy_lanes.common_state
#! assert_player_variable 30000 score
Something missing or wrong? You can fix it!
This website is edited by people like you! Is something wrong or missing? Is something out of date, or can you explain it better?
Please help us! You can fix it yourself and be an official "open source" contributor!
It's easy! See our Beginner's guide to editing the docs.
Page navigation via the keyboard: < >
You can navigate this site via the keyboard. There are two modes:
General navigation, when search is not focused:
- F , S , / : open search dialog
- P , , : go to previous page
- N , . : go to next page
While using the search function:
- Down , Up : select next / previous result
- Esc , Tab : close search
- Enter : go to highlighted page in the results