Skip to content

Commit

Permalink
Merge branch 'main' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
avanwinkle authored Jun 4, 2024
2 parents 428f763 + ab354cd commit 5c39f64
Show file tree
Hide file tree
Showing 81 changed files with 3,118 additions and 60 deletions.
14 changes: 14 additions & 0 deletions docs/config/sound_player.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ sound_player:
sw_jet_bumper_active: super_jet_bumper_sound|block
```

### bus:

!!! info ""

New in MPF 0.80

Single value, type: `string`. Defaults to empty.

Specifies an audio bus on which this sound should be played. Overrides the sound setting if one exists.

### delay:

Single value, type: `time string (secs)`
Expand Down Expand Up @@ -238,6 +248,10 @@ overwrites the setting in your sound.

### track:

!!! warning ""

Deprecated in MPF 0.80. Use `bus` instead.

Single value, type: `string`. Defaults to empty.

Please refer to the [sounds:](sounds.md) documentation for details about this setting as it just
Expand Down
17 changes: 16 additions & 1 deletion docs/config/sounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ sounds:
file: extra_ball_12753.wav
events_when_stopped: extra_ball_callout_finished
streaming: false
track: voice
bus: voice
volume: 0.5
priority: 50
max_queue_time: None
Expand Down Expand Up @@ -85,6 +85,17 @@ duration of the sound, the event(s) will be posted as soon as the sound
begins playback. This value is specified as a
[time string](instructions/time_strings.md).


### bus:

!!! info ""

New in MPF 0.80

Single value, type: `string`. Defaults to empty.

Specifies an audio bus on which this sound should be played. If none is provided, the default bus will be used.

### ducking:

Single value, type: [sound_ducking:](sound_ducking.md). Defaults to empty.
Expand Down Expand Up @@ -365,6 +376,10 @@ particular sound. When `streaming` is set to `True`, the

### track:

!!! warning ""

Deprecated in MPF 0.80. Use `bus` instead.

Single value, type: `string`. Defaults to empty.

This is the name of the track this sound will play on. (You configure
Expand Down
4 changes: 2 additions & 2 deletions docs/config/switches.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ Special-purpose tags for switches include:
## Monitorable Properties

For
[dynamic values](../../config/instructions/dynamic_values.md) and
[conditional events](../../events/overview/conditional.md), the syntax for switches is `device.switches.(name).state`.
[dynamic values](instructions/dynamic_values.md) and
[conditional events](../events/overview/conditional.md), the syntax for switches is `device.switches.(name).state`.
Though uncommon, it is possible to query the current state of a switch in this way.

## Related How To guides
Expand Down
6 changes: 3 additions & 3 deletions docs/game_logic/logic_blocks/state_machines.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ title: State Machine Logic Block

Related Config File Sections:

* [integrating_logic_blocks_and_shows](../../config/state_machines.md)
* [integrating_logic_blocks_and_shows](../../config/state_machine_states.md)
* [integrating_logic_blocks_and_shows](../../config/state_machine_transitions.md)
* [state_machines:](../../config/state_machines.md)
* [state_machine_states:](../../config/state_machine_states.md)
* [state_machine_transitions:](../../config/state_machine_transitions.md)

Related How To Guides

Expand Down
9 changes: 4 additions & 5 deletions docs/game_logic/multiballs/multiball_locks.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,10 @@ For

## Related How To guides

* [/events/multiball_lock_multiball_lock_full](multiball_with_traditional_ball_lock.md)
* [/events/multiball_lock_multiball_lock_full](multiball_with_virtual_ball_lock.md)
* [/events/multiball_lock_multiball_lock_full](add_a_ball_multiball.md)
* [/events/multiball_lock_multiball_lock_full](multiball_with_virtual_ball_lock.md)
* [/events/multiball_lock_multiball_lock_full](multiball_with_multiple_lock_devices.md)
* [Multiball with a traditional ball lock](multiball_with_traditional_ball_lock.md)
* [Multiball with a virtual ball lock](multiball_with_virtual_ball_lock.md)
* [Multiball with multiple lock devices](multiball_with_multiple_lock_devices.md)
* [Add a ball multiball](add_a_ball_multiball.md)

## Related Events

Expand Down
55 changes: 55 additions & 0 deletions docs/gmc/guides/advanced-custom-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Advanced Custom Code
---

# Advanced Custom Code

For experienced developers who want complete control over how the GMC behaves, there are advanced options for hooking into, extending, and overriding all GMC behavior.

## Extending Core Script Classes

Each of the core scripts of GMC ([outlined here](../reference/mpf-gmc.md)) has a registered class name that allows the script to be extended or overridden. Many of these classes have virtual methods for code paths that would be common use cases, and *any* method on the class can be extended or overridden.

### Create a Custom Core Script

To insert your own code into the core GMC scripts, create a new GDScript file that `extends` from the core script class you want.

You can then create your own logic for virtual methods, extend existing methods with `super()`, or overwrite them entirely.

``` code
# /custom_code/my_custom_bcp.gd
extends GMCServer
# Override a virtual method
func on_connect():
print("Connection established, running custom startup flow.")
# Extend an existing method by calling super() and adding custom logic
func set_machine_var(name: String, value) -> void:
super()
print("Machine var %s updated to value: %s" % [name, value])
```

### Inject Custom Script into GMC

Now that your script is written, you can instruct GMC to use your script instead of the core script by configuring it in *gmc.cfg*.

In your *gmc.cfg* file, create a new section named `[gmc]` and enter a key of the class name you're overriding and a value of the path to your custom script.

``` ini

[gmc]
GMCServer="custom_code/my_custom_bcp.gd"
```

The available core scripts to override are:

1. `GMCUtil` - Utility functions for common scenarios
1. `GMCLogger` - Logging handler for MPF-style logging
1. `GMCGame` - Game data from MPF (player and machine vars, modes, settings)
1. `GMCServer` - The BCP server (sending, receiving, and processing events)
1. `GMCMedia` - The media controller for slides, widgets, and sounds

Note that the core scripts are loaded **in the above order** and earlier scripts cannot reference later ones.
65 changes: 65 additions & 0 deletions docs/gmc/guides/animating-slides-widgets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: Animating Slides and Widgets
---

# Animating Slides and Widgets

Godot offers a robust animation system that you can use for any purpose on your slides and widgets. This document covers the built-in animation support for *created*, *active*, and *removed* cases. For implementing arbitrary animations from custom events, see the [Slides Custom Methods](../slides.md#slide-custom-methods) documentation.

## Understanding Slide/Widget Lifecycle

Slides and widgets have a lifecycle of four possible states:

* **CREATED** when the slide or widget is instantiated and added to the display
* **ACTIVE** when the slide is placed at the top of the stack
* **INACTIVE** when the previously-active slide is removed from the top of the stack
* **REMOVED** when the slide or widget is removed from the display

GMC offers a convenient mechanism for triggering animations for these state changes, ideal for animating slides and widgets on to and off of the screen.

## Attach an AnimationPlayer Node

To use the built-in animations, you will need to add an `AnimationPlayer` Node to your scene. Then select the root node of your scene in the Godot Editor *Scene* panel (an `MPFSlide` or `MPFWidget` node) and in the *Inspector* panel, find the *Animation Player* property. Click on the *Assign...* button and select your AnimationPlayer node, or drag the AnimationPlayer node from the *Scene* panel onto the *Assign...* button.

## Create Animations

!!! note "See the Godot Docs"

The Godot AnimationPlayer is a powerful and robust tool with dozens of features
and options. It's highly recommended to review the [Introduction to Godot Animation Features](https://docs.godotengine.org/en/stable/tutorials/animation/introduction.html) guide before proceeding.

When you select the `AnimationPlayer` node in the *Scene* panel, the *Animation* panel will appear at the bottom of the editor. Click on the *Animation* button to create a new animation. You can name the animation "created", "active", "inactive", or "removed" depending on which lifecycle state you want to animate.

You can then create keyframes in the animation. For example, to have a slide fade-in when it's created, make an animation named "created".

The slide is currently 100% opaque which is what we want the *end* of the animation to be, so in the *Animation* panel drag the time slider to the end of the animation (by default, 1 second long).

In the *Scene* panel select the root node. In the *Inspector* panel find the *Visibility > Modulate* property, which should be a white box. Click on the key icon to create a keyframe of the Modulate property, defining that at this point of the animation (the end) the Modulate property should be 100% opaque.

Now drag the animation time slider to the beginning of the animation. Click on the white box of the Modulate property and find the `A` slider (for alpha channel, aka opacity). Slide the `A` value down to zero and click away from the color panel to dismiss it. Now click on the key icon again to create another keyframe in the animation, defining that at this point of the animation (the beginning) the Modulate property should be 0% opaque.

And that's it! When the slide is created, GMC will look at the `AnimationPlayer` attached to the root node and see an animation named "created", and therefore play the animation.

## When Animations Will and Will Not Play

There are some nuances to the lifecycle animations that need to be clarified to help avoid confusion and unexpected behavior.

### Created vs Active
When a slide is created, it will always have a `CREATED` state, but it will only get an `ACTIVE` state if it is the highest-priority slide on the stack. If a slide is simultaneously created and put on top of the stack, the `ACTIVE` animation will take precedence over the `CREATED` animation (if both are defined).

### Removal and Priority
An active slide may be removed and a new or underlying slide will become active, and both may have respective animations for those states.

#### If the new active slide has a *higher priority* than the old one:

* The newly-active slide's `CREATED` or `ACTIVE` animation will play and the old slide will wait until the animation finishes before being removed. This allows a new slide to transition in overtop of the previous slide.

* The outgoing `REMOVED` animation **will not play**. Because the new slide is on top of the old slide, there is no need to play the removal animation.

#### If the new active slide has a *lower priority* than the old one:

* The newly-active slide's `CREATED` or `ACTIVE`animation **will not play**. Because the new slide is below the old slide, it must be present for when the old slide is removed.

These rules are based on expected use cases. If you need to fine-tune the behavior of slides coming in and out, GMC will post events that can be used to trigger subsequent `action: play` and `action: remove` behaviors.

Each slide will post the events *slide_(name)_created*, *slide_(name)_active*, *slide_(name)_inactive*, and *slide_(name)_removed*. Of note is that the *slide_(name)_removed* event will not be posted until after the removal animation plays, so you can use this event to delay the playing of a new lower-priority slide and see its created/active animation.
80 changes: 80 additions & 0 deletions docs/gmc/guides/base-slide-with-score.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: "GMC Example: Slide with Score and Player"
---

# Tutorial: Slide with Score, Player, and Ball

In this GMC tutorial we'll create a base slide for our game that includes the player score, ball number, and player number (when a multiplayer game is active).

## Configure the Slide Player

In your MPF config for your base mode, set the following:

``` yaml

slide_player:
mode_base_started: base
```
This will trigger the slide with a file name *base.tscn* when the base mode starts.
## Create a Base Slide
In the */modes/base* folder of your project, create a *slides* folder and in that folder create a new scene (*Create New > Scene*). Set the Root Type to be `MPFSlide` and save the file as *base.tscn*

Open the slide in the editor and add a new Node of type `Sprite2D`. Name this node "background". In the *Inspector* panel, under *Texture* choose *Quick Load* and select a nice background image. Drag the corners of the image node until it fills the dimensions of the slide.

## Add a Score
Add a new node of type `MPFVariable`, name it "score", drag the corners until it fills the full width of the slide, and move it down to the middle. In the *Label > Text* field enter "1,000,000" so you can visualize what the text will look like.

Lower in the *Inspector* panel, set *Label > Horizontal Alignment* to "Center". Expand *Theme Overrides > Font Sizes* and set the font size to be a large number, like 150px. Under *Colors* enable the *Font Outline Color* (the default black is fine), and under *Constants* set the *Outline Size* to 6. If you have a custom font you'd like to use, you can set that in the *Fonts* section.

You should now see "1,000,000" large in the middle of the slide. Go to the top of the *Inspector* panel and set the following values:

* *Variable Type*: Current Player
* *Variable Name*: score
* *Comma Separate*: enabled
* *Min Digits*: 2

![image](../images/score_variable.png)

You can now play the project, start MPF, and start a game. You'll see `00` appear at the beginning, and as you press keyboard inputs to score points, the score value will update automatically. Neat!

## Add a Ball Number: Theme Variants

Add another `MPFVariable` node, name it "ball", and position it in the upper-left corner. In the *Inspector* panel set the following values:

* *Variable Type*: Current Player
* *Variable Name*: ball
* *Template String*: "Ball %s"
* *Label > Text*: "Ball 1"

![image](../images/ball_variable.png)

Rather than setting the font details for this variable explicitly, we will create a Theme that can be re-used throughout the project. In the *Inspector* panel under *Theme*, click the dropdown that says `<empty>` and select *New Theme*. A theme icon will appear in the dropdown, click it again to open the Theme Editor at the bottom of the screen.

On the right side of the Theme Editor next to *Type* click the plus icon and add a `Label` type. The Font tab will open below, click the plus icon to enable a theme font and then click on `<empty>` and *Quick Load* to select a font file from your fonts folder. The next tab is for font size, click the plus icon to enable a size and set the value to 40px.

![image](../images/font_theme.png)

At the top of the Theme Editor select *Save As...* and save the theme in your fonts folder (or a theme folder if you'd prefer) with a name for the font style, e.g. *body_md.tres*. In the *Inspector* panel, you should see your ball number label now has the theme *body_md*.

## Add a Player Number: Multiplayer-Only Variables

Create another `MPFVariable` like above, name it "player" set the *Variable Name* to `number`, then click the *Theme* `<empty>` dropdown and *Quick Load* to select the *body_md.tres* theme. Then set the following properties:

* *Variable Type*: Current Player
* *Variable Name*: number
* *Template String*: "Player %s "
* *Min Players*: 2
* *Label > Text*: "Player 1"

Note there are two extra spaces after the `%s` in the *Template String*, this is to create some extra space between the player and ball texts that only appears when the player text does. And the *Min Players* value means that the player text will only appear in a multiplayer game.

## Dynamic Variable Positioning with Containers

When these variables appear onscreen, we want them to be left aligned and the player number to be first (if present), so we can use Godot's built-in layout tools to automatically position them. In the *Scene* panel create a new node of type `HBoxContainer`, which is a container that will lay its children from left-to-right. Drag the corners of the node so it starts in the upper left corner and is tall enough and wide enough to cover the player and ball text. In the *Scene* panel drag the `player` and `ball` nodes onto the `HBoxContainer` to make them children of that container, and make sure the `player` node is first (above) and the `ball` node is second (below).

![image](../images/hbox_container.png)

And that's it! You can start the project and run MPF to see it in action: the ball number will appear initially in the upper left corner, and when another player is added the player number will appear there and the ball number will shift to the right.
Loading

0 comments on commit 5c39f64

Please sign in to comment.