Skip to content

Commit

Permalink
Merge pull request #113 from Meorge/feat/gavel-slam
Browse files Browse the repository at this point in the history
Judge gavel slam animation
  • Loading branch information
LuisMayo authored Dec 30, 2023
2 parents d3b1215 + bf263cb commit 05b547e
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 1 deletion.
14 changes: 13 additions & 1 deletion docs/DialogueActionCommands.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ Immediately moves the camera to the specified `position`.

See the documentation for the `sprite` command for valid `position` values.

In addition to those positions, `gavel` is also a valid position to cut to
when displaying a gavel slam animation.

### `pan <position>`
Performs the "courtroom pan" animation from the camera's current position
to the specified `position`. The animation takes `0.5` seconds,
Expand Down Expand Up @@ -123,4 +126,13 @@ appearing, you will need to use a `wait` command.

### `verdict clear`
Immediately removes the verdict text from the screen, and resets the internal
string to an empty one.
string to an empty one.

## Gavel slam
### `gavel <frame>`
Sets the frame of the gavel slam to `<frame>`. Valid frames are:
- `0` - The gavel is not visible.
- `1` - The bottom of the gavel is visible at the top of the screen.
- `2` - The entire gavel is visible and touching the block.
- `3` - The entire gavel is visible and touching the block, and there are impact
lines around the screen.
87 changes: 87 additions & 0 deletions docs/UsingGavelSlamEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Using the Gavel Slam Effect
When things get heated in the courtroom, you can make the Judge slam his gavel.

![Judge slamming his gavel example](img/gavel-slam.gif)

This document will show you how to implement this in your own scripts, both
via the `compose_gavel_slam()` convenience function as well as by hand using
`DialogueAction` objects in Python.

## Doing it quick with `compose_gavel_slam()`
Most of the time, you'll probably want the gavel slam's timing to look like
the original games. Writing out all of the `DialogueAction` commands to get
it right is a real pain. That's where the `compose_gavel_slam()` function
comes in! Simply call it with the number of slams you want, then concatenate
the returned list with the rest of your `BaseDialogueItem` objects for your
`DialoguePage`.

For example, to play a single gavel slam, you can simply call:
```py
compose_gavel_slam(num_slams=1)
```
This should be a nearly frame-for-frame recreation of a single gavel slam
from the games. The function has optional parameters `delay_between_slams`
and `finish_wait_time` which allow you to customize the amount of time between
consecutive gavel slams and the amount of time the camera lingers on the last
gavel slam, respectively.

See `example_gavel_simple.py` for examples of how to use the function.

After the gavel slam animation is complete, make sure to add a `cut` command
to move the camera somewhere else.

## Doing it yourself
On the off-chance that you want more control over the gavel slam than the
`compose_gavel_slam()` function provides, you can write your own series of
commands.

The gavel slam animation itself is controlled by the `gavel <frame>` command,
where `<frame>` is a number from `0` to `3`:
- `0` - The gavel is not visible.
- `1` - The bottom of the gavel is visible at the top of the screen.
- `2` - The entire gavel is visible and touching the block.
- `3` - The entire gavel is visible and touching the block, and there are impact
lines around the screen.

Essentially, to play an animation with the gavel, you alternate between `gavel`
commands to update the visible frame and `wait` commands to make that frame
stay on-screen for a certain amount of time.

To see this in action, let's take a look at the output of
`compose_gavel_slam(num_slams=1)`:

```py
[
# Move the camera to the gavel slam location
DialogueAction("hidebox", 0),
DialogueAction("cut gavel", 0),

# Gavel not visible for 0.3 seconds (~10 frames)
DialogueAction("gavel 0", 0),
DialogueAction("wait 0.3", 0),

# Gavel barely visible for 0.04 seconds (~1 frame)
DialogueAction("gavel 1", 0),
DialogueAction("wait 0.04", 0),

# Gavel on block for 0.04 seconds (~1 frame)
DialogueAction("gavel 2", 0),
DialogueAction("wait 0.04", 0),

# Gavel on block with impact lines for 0.766 seconds (~23 frames)
# Also play the "gavel slam" sound effect and shake the screen
DialogueAction("gavel 3", 0),
DialogueAction("sound gavel", 0),
DialogueAction("shake 3 0.2", 0),
DialogueAction(f"wait 0.766", 0),
]
```
You can, of course, change the timing on the animation's frames by changing
the values for the `wait` commands.

Just as with the `compose_gavel_slam()` function's output, once you're done with
the animation, make sure to use the `cut` and `showbox` commands to return the
camera to somewhere in the scene and display the dialogue box again.

The example file `example_gavel.py` demonstrates how to write `DialogueAction`
commands to do a gavel slam animation manually.
Binary file added docs/img/gavel-slam.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
109 changes: 109 additions & 0 deletions examples/example_gavel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from time import time
from objection_engine.ace_attorney_scene import AceAttorneyDirector
from objection_engine.parse_tags import (
DialoguePage,
DialogueAction,
DialogueTextChunk,
)

pages = [
DialoguePage([DialogueAction("music start pwr/cross-moderato", 0)]),
DialoguePage(
[
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-idle.gif", 0
),
DialogueAction("cut judge", 0),
DialogueAction("nametag 'Judge'", 0),
DialogueAction("showbox", 0),
DialogueAction("startblip male", 0),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-talk.gif", 0
),
DialogueTextChunk("I'm going to slam my gavel.", []),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-idle.gif", 0
),
DialogueAction("stopblip", 0),
DialogueAction("showarrow", 0),
DialogueAction("wait 2", 0),
DialogueAction("hidearrow", 0),
DialogueAction("sound pichoop", 0),
DialogueAction("wait 0.3", 0),
]
),
DialoguePage(
[
DialogueAction("hidebox", 0),
DialogueAction("cut gavel", 0),
DialogueAction("gavel 0", 0),
DialogueAction("wait 0.3", 0),
DialogueAction("gavel 1", 0),
DialogueAction("wait 0.04", 0),
DialogueAction("gavel 2", 0),
DialogueAction("wait 0.04", 0),
DialogueAction("gavel 3", 0),
DialogueAction("sound gavel", 0),
DialogueAction("shake 3 0.2", 0),
DialogueAction("wait 0.766", 0),
]
),
DialoguePage(
[
DialogueAction("hidebox", 0),
DialogueAction("cut judge", 0),
DialogueAction("nametag 'Judge'", 0),
DialogueAction("showbox", 0),
DialogueAction("startblip male", 0),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-talk.gif", 0
),
DialogueTextChunk("Now I'll do it three times.", []),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-idle.gif", 0
),
DialogueAction("stopblip", 0),
DialogueAction("showarrow", 0),
DialogueAction("wait 2", 0),
DialogueAction("hidearrow", 0),
DialogueAction("sound pichoop", 0),
DialogueAction("wait 0.3", 0),
]
),
DialoguePage(
[
DialogueAction("hidebox", 0),
DialogueAction("cut gavel", 0),
DialogueAction("gavel 0", 0),
DialogueAction("wait 0.300", 0),
DialogueAction("gavel 1", 0),
DialogueAction("wait 0.04", 0),
DialogueAction("gavel 3", 0),
DialogueAction("sound gavel", 0),
DialogueAction("shake 3 0.2", 0),
DialogueAction("wait 0.17", 0),
DialogueAction("gavel 2", 0),
DialogueAction("wait 0.04", 0),
DialogueAction("gavel 1", 0),
DialogueAction("wait 0.17", 0),
DialogueAction("gavel 3", 0),
DialogueAction("sound gavel", 0),
DialogueAction("shake 3 0.2", 0),
DialogueAction("wait 0.17", 0),
DialogueAction("gavel 2", 0),
DialogueAction("wait 0.04", 0),
DialogueAction("gavel 1", 0),
DialogueAction("wait 0.17", 0),
DialogueAction("gavel 2", 0),
DialogueAction("wait 0.04", 0),
DialogueAction("gavel 3", 0),
DialogueAction("sound gavel", 0),
DialogueAction("shake 3 0.2", 0),
DialogueAction("wait 0.766", 0),
]
),
]

director = AceAttorneyDirector()
director.set_current_pages(pages)
director.render_movie(output_filename=f"example-gavel-{int(time())}.mp4")
110 changes: 110 additions & 0 deletions examples/example_gavel_simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from time import time
from objection_engine.ace_attorney_scene import AceAttorneyDirector
from objection_engine.parse_tags import (
DialoguePage,
DialogueAction,
DialogueTextChunk,
)
from objection_engine.composers.compose_gavel_slam import compose_gavel_slam

pages = [
DialoguePage([DialogueAction("music start pwr/cross-moderato", 0)]),
DialoguePage(
[
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-idle.gif", 0
),
DialogueAction("cut judge", 0),
DialogueAction("nametag 'Judge'", 0),
DialogueAction("showbox", 0),
DialogueAction("startblip male", 0),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-talk.gif", 0
),
DialogueTextChunk("I'm going to slam my gavel.", []),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-idle.gif", 0
),
DialogueAction("stopblip", 0),
DialogueAction("showarrow", 0),
DialogueAction("wait 2", 0),
DialogueAction("hidearrow", 0),
DialogueAction("sound pichoop", 0),
DialogueAction("wait 0.3", 0),
]
),
DialoguePage(compose_gavel_slam(1)),
DialoguePage(
[
DialogueAction("hidebox", 0),
DialogueAction("cut judge", 0),
DialogueAction("nametag 'Judge'", 0),
DialogueAction("showbox", 0),
DialogueAction("startblip male", 0),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-talk.gif", 0
),
DialogueTextChunk("Now I'll do it three times.", []),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-idle.gif", 0
),
DialogueAction("stopblip", 0),
DialogueAction("showarrow", 0),
DialogueAction("wait 2", 0),
DialogueAction("hidearrow", 0),
DialogueAction("sound pichoop", 0),
DialogueAction("wait 0.3", 0),
]
),
DialoguePage(compose_gavel_slam(num_slams=3)),
DialoguePage(
[
DialogueAction("hidebox", 0),
DialogueAction("cut judge", 0),
DialogueAction("nametag 'Judge'", 0),
DialogueAction("showbox", 0),
DialogueAction("startblip male", 0),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-talk.gif", 0
),
DialogueTextChunk("How about 5 times?", []),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-idle.gif", 0
),
DialogueAction("stopblip", 0),
DialogueAction("showarrow", 0),
DialogueAction("wait 2", 0),
DialogueAction("hidearrow", 0),
DialogueAction("sound pichoop", 0),
DialogueAction("wait 0.3", 0),
]
),
DialoguePage(compose_gavel_slam(num_slams=5)),
DialoguePage(
[
DialogueAction("hidebox", 0),
DialogueAction("cut judge", 0),
DialogueAction("nametag 'Judge'", 0),
DialogueAction("showbox", 0),
DialogueAction("startblip male", 0),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-talk.gif", 0
),
DialogueTextChunk("Now 10 times, really fast.", []),
DialogueAction(
"sprite judge assets/characters/judge/judge-normal-idle.gif", 0
),
DialogueAction("stopblip", 0),
DialogueAction("showarrow", 0),
DialogueAction("wait 2", 0),
DialogueAction("hidearrow", 0),
DialogueAction("sound pichoop", 0),
DialogueAction("wait 0.3", 0),
]
),
DialoguePage(compose_gavel_slam(num_slams=10, delay_between_slams=0.04))
]

director = AceAttorneyDirector()
director.set_current_pages(pages)
director.render_movie(output_filename=f"example-gavel-simple-{int(time())}.mp4")
18 changes: 18 additions & 0 deletions objection_engine/ace_attorney_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from os import environ, getenv
from turtle import pos

from objection_engine.gavel_slam import GavelSlamObject
from objection_engine.testimony_indicator import TestimonyIndicatorTextObject

environ["TOKENIZERS_PARALLELISM"] = "false" # to make HF Transformers happy
Expand Down Expand Up @@ -606,6 +607,12 @@ def __init__(self, callbacks: dict = None, fps: float = 30):
parent=self.root, name="Testimony Indicator"
)

self.gavel_slam = GavelSlamObject(
parent=self.world_shaker,
name="Gavel Slam",
pos=(560, 256, 0)
)

self.scene = Scene(width=256, height=192, root=self.root)

if "on_director_initialized" in self.callbacks:
Expand Down Expand Up @@ -790,6 +797,8 @@ def update(self, delta: float):
self.cut_to_phoenix_action()
elif position == "rightzoom":
self.cut_to_edgeworth_action()
elif position == "gavel":
self.cut_to_gavel()
current_dialogue_obj.completed = True

elif c == "pan":
Expand Down Expand Up @@ -838,6 +847,10 @@ def update(self, delta: float):

current_dialogue_obj.completed = True

elif c == "gavel":
self.gavel_slam.set_gavel_frame(int(action_split[1]))
current_dialogue_obj.completed = True

elif c == "testimony":
command = action_split[1]
if command == "set":
Expand Down Expand Up @@ -865,6 +878,7 @@ def update(self, delta: float):
self.testimony_indicator.make_invisible()

current_dialogue_obj.completed = True

elif c == "nop":
current_dialogue_obj.completed = True

Expand Down Expand Up @@ -999,6 +1013,10 @@ def cut_to_edgeworth_action(self):
self.world_root.set_x(0)
self.world_root.set_y(-768)

def cut_to_gavel(self):
self.world_root.set_x(-560)
self.world_root.set_y(-256)

current_music_track: Optional[dict] = None
current_voice_blips: Optional[dict] = None

Expand Down
Loading

0 comments on commit 05b547e

Please sign in to comment.