Now, we can move around with the player on the map, but the camera still points to the starting position. Let’s follow the player with the camera. In this chapter, we attach the camera to the player so that they move together. We also adjust how the shadow camera casts shadows.
Following the Player with the Camera
Attach the Camera to the Player
We defined the camera in the Scene component. By default, it has a static position. Instead of that, we want to move it with the player. We could adjust its position at every animation frame just like the player, but it’s easier to attach the camera to the Player component so that they move together.
We can access the camera using the useThree hook from @react-three/fiber. This returns a Three.js camera object that we can add to the player group.
We already have a reference to the group representing the player. We can attach the camera to the player by adding it as a child of the player group. Because the player reference is undefined on the first render, we need to use the useEffect hook to attach the camera only once the player reference is set.
Once we attached the camera to the player, some strange things happen. The camera is moving together with the player now, but it’s also turning whenever the player turns, and moving up and down whenever the player jumps. This is not what we want. We want the camera to follow the player, but we want it to stay level and not turn or jump.
Fix the Player Animation
To ensure the camera follows the player but doesn’t turn or jump with it, we wrap the player objects into another group. The original outer group will be a container that moves horizontally on the map but doesn’t jump or turn. We attach the camera to this group. Then, the new inner group containing the player objects won’t move horizontally but turns and jumps the player.
Let’s adjust the player animation to use this new structure.
In the utility functions of the usePlayerAnimation hook, we need to adjust how we set the player’s position and rotation to work with the new structure. When moving the player along the x and y axes, we need to move the outer group, but when the player turns or jumps, we need to adjust the inner group.
These functions receive a reference to the outer group. We set the x and y positions as before. When we set the position or rotation along the z-axis, we set it on the inner group with player.children[0].
As the player moves further away, another strange behavior occurs. At one point, the objects no longer cast shadows.
If we zoom out even more and add a helper box to show the shadow camera’s frustum, we can see that some parts of the game are now outside it. Here, the gray box shows the shadow camera’s frustum and the red rectangle represents its projection to the ground. Drag the scene to see how it works.
Shadows are only calculated within the frustum of the shadow camera, which is statically attached to the directional light. We need to attach the directional light to the player as well.
Add the Directional Light to the Player
We need to attach the directional light to the player to make the shadows follow the player.
Then, add it to the player in the Player component. We add the light within the outer group so that it follows the player but doesn’t jump or turn with the player.
Make sure to add it as the second element because the usePlayerAnimation hook modifies the first element on jumps and turns.
Directional lights shine from their position toward a target. By default, the target is the origin [0, 0, 0]. This was fine when the light was attached to the scene, but now that it’s attached to the player, we also need to adjust the target. Otherwise, the light and shadows will keep changing direction.
To modify the light’s target property, we need a reference to the underlying THREE.DirectionalLight object in the Player component. We can achieve this by forwarding a ref to the DirectionalLight component.
We change the target to follow the player automatically. In the useEffect hook once our references are ready, we set the target of the light to the player group element.