Having drafted a project plan and an Inheritance Diagram (See Dev Log #1: A 3D Parkour Danmaku), I can now begin writing code. Looking at the Inheritance Diagram, it becomes clear that the first class I need to write is the Moving Tile Class. This class will form the base for a majority of the classes that will form the backbone of the game.
So what are the requirements for this class? First, it needs to be able to translate and rotate a block in every direction imaginable. It should also be able to call the block back to its starting position if desired as well. It also needs to be easily editable while building out the Levels so that I can focus on level building. This seems simple enough.
To begin with, I will make a class to handle the static functions of the Moving Tile Class. I’ll call it the Static Tile Class. It will handle the loading of the Static Mesh and a Box Collider. The Static Mesh is what the Player will see and often interact with (for example walking or jumping on). This can be anything from a simple cube to a train car. The Box Collider will allow me to add further functionality to the Tile. For example, the Box Collider could be used to detect when the Player jumps on and off said Tile and enable some property, such as gravity. Later on, I might add functions to this class for spawning and despawning the Tile.
Now with the Static Tile Class, I can build the Moving Tile Class that inherits from it. The first thing to do, is write in all the variables that I want to have control of from the Level Editor.
Writing the code for the translation of the tile went fairly well and there were not any issues in writing the code. I used code I had written from the Udemy Course I took on Unreal Engine 5 to guide me on this part. The way the code works is it uses a Vector for the distance the Tile is suppose to travel and a Float for the velocity. A Boolean can be checked true if the Tile is suppose to return to its starting location. Below is the code that is called when the Tile is translating.
// Call to translate the tile
void AMoverTile::TranslateTile(float DeltaTime)
{
// If tile meets or passes a bound, reset tile to bound, flip bounds, and flip movement direction
if (bCanReturnToStartLocation && bTileReturnToStartLocation())
{
SetActorLocation(EndLocation);
CurrentLocation = EndLocation;
EndLocation = StartLocation;
StartLocation = CurrentLocation;
MovementDirection = -MovementDirection;
}
// Else, move tile to the next location
else
{
CurrentLocation = GetActorLocation();
CurrentLocation = CurrentLocation + (MovementDirection * TranslationVelocity * DeltaTime);
SetActorLocation(CurrentLocation);
}
}
// Get the distance moved
float AMoverTile::GetDistanceMoved() const
{
return FVector::Dist(GetActorLocation(), StartLocation);
}
// Determine if platform should turn around
bool AMoverTile::bTileReturnToStartLocation() const
{
return GetDistanceMoved() > MaxDistance;
}
Code language: C++ (cpp)
With Translation out of the way, I can now work on Rotating the Tile. This is where I began to face difficulty. The desired output was for all three axis of rotation to be able to rotate independently of each other. Making the Roll Axis and Yaw Axis rotate went fairly smoothly, however the Pitch Axis did not. When the Pitch Axis rotated, it would rotate all the way to either 90º or -90º, and then lock up and start glitching out. It took quite a bit of fiddling to figure out what was going on. Eventually, the answer I found was that when the Pitch reached either 90º or -90º, to flip the sign of the rotation velocity on the Pitch Axis. Below is the code that is called to rotate the tile. Eventually, I plan to add functionality for checking the number of rotations and the desired end rotation of the tile. For now, simply rotating infinitely will do.
// Call to rotate the tile
void AMoverTile::RotateTile(float DeltaTime)
{
// If tile meets or exceeds bounds on an axis AND can return to starting rotation
// Set Tile to end rotation on axis
// Flip Rotation Velocity Rotator on axis
// Flip start and end rotations
// else
// Rotate tile on each axis
RotateTileOnAxis(RotationVelocity.Roll, "Roll", DeltaTime);
RotateTileOnAxis(RotationVelocity.Pitch, "Pitch", DeltaTime);
RotateTileOnAxis(RotationVelocity.Yaw, "Yaw", DeltaTime);
}
// Call to rotate tile on an axis
void AMoverTile::RotateTileOnAxis(float RotationOnAxis, FString AxisName, float DeltaTime)
{
FRotator CurrentRotation = GetActorRotation();
// Check for the Axis to be updated, then update accordingly
if (AxisName == "Roll")
{
CurrentRotation.Roll = CurrentRotation.Roll + (RotationOnAxis * DeltaTime);
}
if (AxisName == "Pitch")
{
CurrentRotation.Pitch = CurrentRotation.Pitch + (RotationOnAxis * DeltaTime);
// When pitch hits 90 degrees up or down, flip the pitch rotation velocity
if (CurrentRotation.Pitch >= 90 || CurrentRotation.Pitch <= -90)
{
RotationVelocity.Pitch = RotationVelocity.Pitch * -1;
}
}
if (AxisName == "Yaw")
{
CurrentRotation.Yaw = CurrentRotation.Yaw + (RotationOnAxis * DeltaTime);
}
SetActorRotation(CurrentRotation);
}
Code language: C++ (cpp)
With the Moving Tile Class out of the way, the next big piece to tackle is making the game playable. For this I began the construction of my own Base Character Class. This class will handle all the inputs that the various controllers will provide from basic inputs such as movement, to more custom inputs such as firing a bullet. I built a quick Blueprint from my Base Character Class I created and attached a mesh, a spring arm, and a camera. Now the game is playable. I can begin testing future functionality from here.
With these two classes built, the base of most of the game is now built out. My next steps now will be to make a class for Destructible Tiles. This will involve building out a Health Class, a class to handle using items, and a class for the bullets.
Pingback: Dev Log #3: Adding Health and Death – Knot Gamer
Pingback: Dev Log #4: Fixing A Nasty Bug – Knot Gamer