The previous recipe explored the possibilities of using MotionPaths
to move objects around. One step up from that and a way to organize many events in a sequence is cinematics. It can be used both to create in-game scripted events and advanced cutscenes. The power of a well-scripted in-game event should not be underestimated but neither should the time it takes to get one right.
In this recipe, we'll explore the possibilities of the Cinematics
system by creating a cutscene using content that we have created before.
Some basic knowledge of MotionPaths
and MotionEvents
is required. Checking out the Performing complex movements with MotionPath recipe should provide enough information to get started. A new class that is introduced is the Cinematic
class. This works as a sequencer or manager of many events firing them at set times. The events don't just have to be MotionEvents
but could be AnimationEvents
dealing with skeleton-based animations, SoundEvents
, and even GuiEvents
. It can also manage several cameras and switch between them.
Before starting the actual implementation of a cinematic scene, it's good to have some kind of script describing what is going to happen. This will help organize the cinematic event and will save time in the end.
This recipe will use the TestScene
from Chapter 1, SDK Game Development Hub. We can also use the animated sky box from earlier in this chapter. It will display Jaime walking from his initial position to stand by the edge of the water looking into the horizon. While he's walking, several switches between panning cameras will occur.
Finding out good waypoints for characters and cameras can be difficult enough and it's not any easier if you have to do it all in code. A trick is to use the SceneComposer to create markers much like real movie makers use tape to designate where actors should move. Right-clicking on the scene node and selecting Add Spatial.../New Node will give us an invisible marker. Give this a recognizable name and drag it into place by using the Move
function.
So now that we have a scene prepared with some waypoints we can get to work with implementing the cutscene itself by performing the following steps:
Cinematic
will be played out. The scene reference will be used in several places, so it is a good idea to store it in a field.Spatial
field called Jaime, the main actor and either load or extract him from the scene (depending on the setup).MotionPath
instance called jaimePath
for Jaime. Since we created Nodes
for each waypoint in SceneComposer
, we can get their location from the scene by using:jaimePath.addWayPoint(scene.getChild("WayPoint1").getWorldTranslation());
MotionEvent
called jaimeMotionEvent
using jaimePath
and initialDuration
of 25 seconds:jaimeMotionEvent = new MotionEvent(jaime, jaimePath, 25f);
directionType
to MotionEvent.Direction.Path
.Before we get too far, we want to check out that the path Jaime follows is alright. Therefore, we should go ahead and create a Cinematic
instance at this point. To do this, perform the following steps:
rootNode
together with the total duration of the cinematic:cinematic = new Cinematic(scene, 60f);
MotionEvent
with the following line; 0 being the time it should start at:cinematic.addCinematicEvent(0, jaimeMotionEvent);
stateManager
of the application with stateManager.attach(cinematic)
.cinematic.play()
at this point should display Jaime sliding along the path.Once we're happy with it, we can go on and do the camera work as follows:
Cinematic
instance will create a CameraNode
for us if we call cinematic.bindCamera("cam1", cam)
, so let's do that for our first camera. The string is the reference that Cinematic
will know the camera by.MotionPath
instance and a MotionEvent
instance for it. Again, we can get the waypoints of the camera path from the scene. Since the Node
we added in SceneComposer
by default snaps to the ground, we need to add between 1.5f and 2.0f to the y axis to get it to a suitable height.directionType
of camera's MotionEvent
to LookAt
and then also set the direction it should look at with cam1Event.setLookAt
where the first Vector3f
is the location to look at and the second is Vector3f
, which is up in the world:cam1Event = new MotionEvent(camNode, camPath1, 5f); cam1Event.setDirectionType(MotionEvent.Direction.LookAt); cam1Event.setLookAt(Vector3f.UNIT_X.mult(100), Vector3f.UNIT_Y);
cinematic.activateCamera(0, "cam1");
MotionPath
and MotionEvent
instances and can similarly get its own CameraNode
. It's perfectly fine to use the same physical camera for both of the CameraNodes.Now, we can start doing something about the lack of animation in the scene.
AnimationEvent
instance that uses the Walk
animation:AnimationEventwalkEvent = new AnimationEvent(jaime, "Walk", LoopMode.Loop);
cinematic
at 0 seconds:cinematic.addCinematicEvent(0, walkEvent);
jaimeMotionEvent
ends. So we create another AnimationEvent
with the idle animation and add it at the end of the duration of the jaimeMotionEvent
.At the time of writing this, it seems the cinematic doesn't end the animation when it starts a new one, so we have to do something to stop it ourselves. Using a MotionPathListener, we can check when the final waypoint is reached and manually stop the walking animation:
jaimePath.addListener(new MotionPathListener() { public void onWayPointReach(MotionEventmotionControl, intwayPointIndex) { if(wayPointIndex == 2){ walkEvent.stop(); } } });
The Cinematic
acts as a sequencer for all the different events and apart from firing events at the defined intervals, we can instead use cinematic.enqueueCinematicEvent
. Doing so will start the supplied event just after the previous one is done. This can be useful if we want to trigger a series of animations right after each other. Cinematics can also be set to loop or cycle just like MotionEvents
and you don't need to start them at time 0.
In conclusion, using cinematics is not particularly technical. It's just difficult to get all the positions, angles, and timings right, especially since there's no intelligence or collision involved in the script. Once you get it right, however, the result will be extremely rewarding.
3.147.77.208