This is the game born during the fail-safe 2025 gamejam, since then it changed drastically and now it's a project inspired by Sword of the Sea. I mainly did programming, my work consisted in everything related to the 3Cs, all of it in C++, but I also worked on some technical art parts like the sand shader and character control rig and animations procedurally generated.
// Parameters -----------
// ----------- Follow
UPROPERTY(EditAnywhere, Category = "Follow")
float FollowRotationSpeedNoInput = 1.0f;
UPROPERTY(EditAnywhere, Category = "Follow")
float TimeToResetCamera = 0.1f;
UPROPERTY(EditAnywhere, Category = "Follow")
float ResetCameraSpeed = 1.0f;
UPROPERTY(EditAnywhere, Category = "Follow - Pitch")
float CameraPitchSpeed = 0.1f;
UPROPERTY(EditAnywhere, Category = "Follow - Pitch")
float CameraBoomMinPitch = -60.0f;
UPROPERTY(EditAnywhere, Category = "Follow - Pitch")
float CameraBoomMaxPitch = 60.0f;
float CameraDownTraceValue = 400.0f;
float InitialCameraPitch = 0.0f;
// ----------- Zoom
UPROPERTY(EditAnywhere, Category = "Zoom")
float MinArmLength = 400.0f;
UPROPERTY(EditAnywhere, Category = "Zoom")
float MaxArmLength = 450.0f;
UPROPERTY(EditAnywhere, Category = "Zoom")
float ZoomInterpSpeed = 1.5f;
UPROPERTY(EditAnywhere, Category = "Zoom")
float MinSpeedFOV = 90.0f;
UPROPERTY(EditAnywhere, Category = "Zoom")
float MaxSpeedFOV = 130.0f;
// ----------- Sphere
UPROPERTY(EditAnywhere, Category = "Sphere")
float Sphere_MinRadius = 75.0f;
UPROPERTY(EditAnywhere, Category = "Sphere - Follow")
float CameraFollowSpeed = 1.5f;
In this code block you can see all the default parameters used for the camera. I provided a documentation (next gif) to the designers for every parameter, so they could better understand how the systems work and tweak the values to their likings.
Here you can see all the states used for animating the ray. All the animations are shown on the right and I created them with the procedural control rig described in the next section.
// Parameters -----------
// Air Control
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Air")
float AirControlMultiplier = 2.0f;
// ----------- Jump
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump")
UCurveFloat* WalkingJumpingCurve;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump")
UCurveFloat* SprintingJumpingCurve;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump")
UCurveFloat* DivingGravityScale;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump")
UCurveFloat* ImmergingRumbleScale;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "0.0", ClampMax = "4000.0", UIMin = "0.0", UIMax = "4000.0"))
float MaxBaseJumpVelocity = 3000.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "-2000.0", ClampMax = "0.0", UIMin = "-2000.0", UIMax = "0.0"))
float MinBaseJumpVelocity = -1000.0f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Jump", meta = (ClampMin = "0.0", ClampMax = "10.0", UIMin = "0.0", UIMax = "10.0"))
float MaxHoldJumpActionTime = 2.0f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Jump", meta = (ClampMin = "0.0", ClampMax = "10.0", UIMin = "1.0", UIMax = "10.0"))
float FallingGravityScale = 3.5f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Jump", meta = (ClampMin = "0.0", ClampMax = "10.0", UIMin = "1.0", UIMax = "10.0"))
float NormalGravityScale = 2.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump")
float StartJumpZ = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump")
float HigherHeightAnimation = 1700.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump")
float LowerHeightAnimation = 200.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "200.0", ClampMax = "2000.0", UIMin = "200.0", UIMax = "2000.0"))
float DivingDownardCheck = 600.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "100.0", ClampMax = "500.0", UIMin = "100.0", UIMax = "500.0"))
float DivingTailProceduralStrength = 200.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "0.0", ClampMax = "5.0", UIMin = "0.0", UIMax = "5.0"))
float EmersionRumble = 0.25f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "0.0", ClampMax = "5.0", UIMin = "0.0", UIMax = "5.0"))
float DivingRumble = 0.1f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "0.0", ClampMax = "5.0", UIMin = "0.0", UIMax = "5.0"))
float EmersionRumbleDuration = 0.3f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "0.0", ClampMax = "5.0", UIMin = "0.0", UIMax = "5.0"))
float DivingRumbleDuration = 0.2f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump")
bool bActivateImmergingRumble = false;
// ----------- Orbital Movement
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Orbital Movement")
float DefaultOrbitAngularSpeed = 65.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Orbital Movement")
float KeyboardDiagonalInputSpeed = 25.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Orbital Movement")
UCurveFloat* DiagonalMovementCurve;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Orbital Movement")
UCurveFloat* TurningCurve;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Orbital Movement")
float TurnOnSpotTolerance = 0.05f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Orbital Movement")
float FastTurnVelocityTreshold = 500.0f;
float OrbitAngularSpeed = 0.0f;
float CurrentOrbitAngle = 0.0f;
// ----------- Tilting
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tilting")
float SmoothTiltRollSpeed = 5.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tilting")
float SmoothTiltPitchSpeed = 5.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Tilting")
FVector LineDownwardOffset = FVector(0.0f, 0.0f, -400.0f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tilting")
FVector FrontArrowPos;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tilting")
FVector BackArrowPos;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tilting")
FVector RightArrowPos;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tilting")
FVector LeftArrowPos;
// ----------- Camera
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "1.0", ClampMax = "10.0", UIMin = "1.0", UIMax = "10.0"))
float HorizontalSensitivity = 10.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "1.0", ClampMax = "10.0", UIMin = "1.0", UIMax = "10.0"))
float VerticalSensitivity = 10.0f;
UPROPERTY(EditAnywhere, Category = "Camera", meta = (ClampMin = "0.1", ClampMax = "5.0", UIMin = "0.1", UIMax = "5.0"))
float MinSensitivityMultiplier = 0.1;
UPROPERTY(EditAnywhere, Category = "Camera", meta = (ClampMin = "0.1", ClampMax = "5.0", UIMin = "0.1", UIMax = "5.0"))
float MaxSensitivityMultiplier = 5.0f;
UPROPERTY(EditAnywhere, Category = "Camera", meta = (ClampMin = "0.0", ClampMax = "10.0", UIMin = "0.0", UIMax = "10.0"))
float TimeToResetCamera = 2.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.0", ClampMax = "10.0", UIMin = "0.0", UIMax = "10.0"))
float LookInactivityDelay = 2.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
float MaxPitchValue = 25.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
float MinPitchValue = -45.0f;
// ----------- Camera Shakes
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera-Shakes")
TSubclassOf DivingImmersionShakeClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera-Shakes")
TSubclassOf EmersionShakeClass;
// ----------- Procedural Animations
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Anim")
UCurveFloat* BodyStrengthCurve;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Anim")
UCurveFloat* TailStrengthCurve;
// ---------- Animations
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
UAnimMontage* HigherLandingAnim;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
UAnimMontage* SoftLandingAnim;
In this code block you can see almost all the parameters used for the RayCharacter. Almost all those variables are part of the documentation shown at the end of the camera section.
The Control Rig is divided into 3 parts, the construction event (discussed in the next paragraph), the Forward Solve and Backward Solve. The Forward Solve is used to bind controls' transforms to the skeleton, and the Backward Solve do the opposite. In this control rig we can chose between two modes, one that has all the controls for creating animations, and one used during gameplay. To be able to create this control rig I studied Unreal documentation and I also followed this incredible video.
The procedural animation logic is done, as shown in the image, with the set translation and rotation nodes that receive the values from spring interpolation. The second "for each" is there to control differently the spring interpolation of the tail, because it is a slim part of the mesh, it needs an higher spring strength to avoid some artifacts. It's important to exclude from these layers of animation the bones (in the "item array") that we don't want to be affected (for example in our case the bones near the eyes or at the start of the mustaches).
The sand shader was created because we wanted to give a little bit of life to the landscape the ray runs on and add some feedbacks on high jumps (the ground ripple). It was one of the hardest task to complete because we needed to work around various limitations from both the engine version (for example unreal 5 removed the tessellation option from materials) and because my limited knowledge on shaders and landscapes.
Initially our goal was to let the ray to be inlfuenced by the waves movement, unfortunately I couldn't implement it in the limited time I had because changing the landscape collisions at runtime was too difficult (now I'm studying how to achieve it). So instead I proposed a vertex shader (in the image) that could simulate the waves movement (it behaves similarly to flags) and it changes the intensity of the effect based on the camera position. This allows us to have a stable ground near the ray and waves in the distance (as shown in the previous gif).