-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
81 changed files
with
3,118 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Oops, something went wrong.