Rocket Rider

Rocket riding, Combat, Platforming

Overview



Responsibilities



This is a showcase of what I made during the project

This nine-week project, developed with a team of 17 members (5 programmers, 4 designers, and 8 artists), was my first experience with Unreal Engine. 

I appreciated the larger team size, which allowed me to focus on areas I enjoy, such as player controllers and overall feedback.

Targeting the PlayStation 5 platform, I had the opportunity to write code for key console features, particularly the DualSense controller. 

I developed modular DualSense code, enabling designers to implement features through blueprints without needing to access any C++ classes.

In addition to coding, I dedicated significant time to optimizing the game, ensuring well a optimized game and great performance despite the demands of raytracing and lumen.

I hope you enjoy exploring this project, which was a significant part of my journey with PlaygroundSquad!

Rocket Movement

A specialized constant forward velocity character controller was required for the game

HandleRocketMovement(const FVector& MovementDirection)

Smoothly adjusts and normalizes the movement direction towards the desired direction based on time and speed.

 LastMovementDirection = FMath::VInterpTo(LastMovementDirection,

    MovementDirection.GetSafeNormal(),

    GetWorld()->GetDeltaSeconds(),

    RocketDirectionalChangeSpeed);


    LastMovementDirection = LastMovementDirection.GetSafeNormal();


Adjusts the movement direction away from the opposite direction if the angle between them exceeds the threshold, 

rotating slightly and interpolating towards the new direction. 


    if (FVector::DotProduct(MovementDirection.GetSafeNormal(), LastMovementDirection) < -AngleThreshold)

    {

        const FVector AdjustmentVector = LastMovementDirection.RotateAngleAxis(1.0f, FVector::UpVector);


        LastMovementDirection = FMath::VInterpTo(LastMovementDirection,

        AdjustmentVector,

        GetWorld()->GetDeltaSeconds(),

        RocketDirectionalChangeSpeed);

    }

Move(const FInputActionValue& InputValue)

Calculates movement direction based on normalized input and controller's yaw rotation. 

    MoveAxisValue = InputValue.Get<FVector2D>().GetSafeNormal();


    const FRotator YawRotation(0, Controller->GetControlRotation().Yaw, 0);

    const FVector ForwardDir = FRotationMatrix(YawRotation).GetScaledAxis(EAxis::X);

    const FVector RightDir = FRotationMatrix(YawRotation).GetScaledAxis(EAxis::Y);

    const FVector MovementDirection = ForwardDir * MoveAxisValue.Y + RightDir * MoveAxisValue.X;

Updates rocket movement and decays the last movement direction, clamping its size to a maximum of 1.0. 

    HandleRocketMovement(MovementDirection);

    LastMovementDirection -= LastMovementDirection * RocketDirectionalChangeSpeed * GetWorld()->GetDeltaSeconds();

    LastMovementDirection = LastMovementDirection.GetClampedToMaxSize(1.0f);

Rail Grinding

Predetermined splines in the world allows you to grind, which the magnet helps to make it more forgiving while jumping

RailGrindingMagnet(float DeltaTime)

if (!bIsRailSplineCollided && !GetCharacterMovement()->IsMovingOnGround())

    {


        if (GetWorld()->SweepMultiByChannel(HitResults, 

           TraceStart, 

           TraceEnd, 

           FQuat::Identity, 

           ECC_Pawn, 

           FCollisionShape::MakeSphere(100.0f), Params))

        {

            float MinDistanceSquared = MAX_FLT;

            FVector ClosestPointOnSpline = FVector::ZeroVector;


            for (const FHitResult& HitResult : HitResults)

            {

               

                if (HitResult.GetActor() && HitResult.GetComponent())

                {

                    AActor* HitActor = HitResult.GetActor();

                    TArray<FName> ActorTags = HitActor->Tags;

                   Detects nearby spline actors with the tag "Rail", finds the closest point on their spline, 

and smoothly moves the player toward that point if closer than previous points. 

                    if (HitActor->ActorHasTag("Rail"))

                    {

                        USplineComponent* SplineComponent = 

                        Cast<USplineComponent>(HitActor->FindComponentByClass<USplineComponent>());

                        if (SplineComponent)

                        {

                            FVector ClosestPoint =  SplineComponent->FindLocationClosestToWorldLocation(HitResult.Location

                                                  ESplineCoordinateSpace::World);


                            float DistanceSquared = FVector::DistSquared(TraceStart, ClosestPoint);


                            if (DistanceSquared < MinDistanceSquared)

                            {

                                MinDistanceSquared = DistanceSquared;

                                ClosestPointOnSpline = ClosestPoint;

                            }

                            FVector NewLocation = FMath::VInterpTo(GetActorLocation(), ClosestPointOnSpline, DeltaTime, 18.5f);

                            SetActorLocation(NewLocation);

                        }

                    }

                }

            }

        }

    }

MoveAlongRail(float DeltaTime)

 if ( RailSpline && bIsRailSplineCollided )

    {

Updates position along the spline, adjusts actor's location, 

and smoothly interpolates the mesh's vertical position based on a cosine wave. 

        TimeAlongSpline += DeltaTime;


        SplineLocation = RailSpline->GetLocationAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::World);

        RailTangent = RailSpline->GetTangentAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::World);

       

        if(!SetSplineDirection)

            SetRailVariables();

       

        SetActorLocation(SplineLocation + FVector(0.f, 0.f, GetCapsuleComponent()->GetScaledCapsuleHalfHeight()));


        FVector NewMeshLocation = FVector(0, 0, FMath::Lerp(-79.5f, -80.5f, FMath::Cos(CurveFrequency * GetWorld()->TimeSeconds)));

        FVector InterpolatedLocation = FMath::VInterpTo(GetMesh()->GetRelativeLocation(), NewMeshLocation, DeltaTime, 5.0f);


        GetMesh()->SetRelativeLocation(InterpolatedLocation);


Calculates movement direction along the spline, 

smoothly rotates the actor towards the spline's direction, 

and updates the last movement direction. 

        SplineMoveDirection = ( SplineLocation - LastPlayerPos );


        const FVector RotationDirection = ( SplineLocation - PreviousRailLocation ).GetSafeNormal();

        FRotator TargetRotation = RotationDirection.Rotation();


        float InterpSpeed = 5.0f;


        FRotator NewRotation = FMath::RInterpTo(GetActorRotation(), TargetRotation, DeltaTime, InterpSpeed);

        SetActorRotation(NewRotation);

       

        LastMovementDirection = SplineMoveDirection;

Moves along the spline based on direction and speed, handles boundary conditions to trigger exit, and updates position. 

        bool bMovingForward = SplineDot > 0.0f;

        if ( !bIsSprinting )

            SplineMoveDistance = RailGrindSpeed * DeltaTime;

        else

            SplineMoveDistance = RailGrindSpeed * ( RocketSpeedMultiplier / 1.5f ) * DeltaTime;


        if ( !bMovingForward )

            SplineMoveDistance = -SplineMoveDistance;


        if ( ( DistanceAlongSpline <= 0.01f || DistanceAlongSpline >= SplineLength ) && TimeAlongSpline > 0.2f )

        {

            SplineExit();

        }

        DistanceAlongSpline = FMath::Clamp( DistanceAlongSpline + SplineMoveDistance, 0.f, SplineLength );


        PreviousRailLocation = SplineLocation;

    }

Challenges 

Movement

You lost your rocket after attacking or being attacked, leading to gameplay where you only had to back off because you didn't have any other form of offense/defense abilities, which wasn't intuitive gameplay.

Rail Grinding

Initially rail grinding was frustrating due to the size of the players collider making it difficult to jump on the rails

Solutions & Outcome

Movement

After playtesting, we removed the option to exit the rocket to streamline gameplay.

This adjustment focused the player’s movement on the rocket mechanics, enhancing overall game coherence and simplicity.

Rail Grinding

Added a magnet feature that activates when the player is jumping. This helps to grab the rails without making the game easy. 

The magnet improved the gameplay experience by reducing frustration while maintaining the game's challenge.