Ninja Character - Dynamic gravity for characters & objects

@mrphilip246 @PRESSURE2000
Hey, i made super simple logic for camera smoothing with basic interpolation, maybe that will work for you:

Basically just disabling ControlRotation for camera once axis been changed and enabling it back if its close to new value:


(increasing interp speed each tick to prevent continues lag if you look around)

Ninja setup:


Spring arm setup (with rotation lag):

Camera setup (make sure to put rotation in WS):

1 Like

Hey @Xaklse, there is an issue in 5.1 with this new Enhanced Input system. Whenever you’re walking on a planet surface, the direction vector, where the engine pushes the character, stays as if the landscape was flat. As a result, the more you travel, the harder for the character is to move forward, because the vector started pointing more and more to the space, up until it can’t move there at all.

The movement direction is calculated in this default function, added by the engine when I created C++ project.

void ASpaceoutCharacter::Move(const FInputActionValue& Value)
{
	// input is a Vector2D
	FVector2D MovementVector = Value.Get<FVector2D>();

	if (Controller != nullptr)
	{
		// find out which way is forward
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);
		
		// get forward vector
		const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);

		// get right vector 
		const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

		// add movement 
		AddMovementInput(ForwardDirection, MovementVector.Y);
		AddMovementInput(RightDirection, MovementVector.X);
	}
}

As you can see, there’s only Yaw taken into consideration. I’ve tried adding there Pitch, but according the debug lines that I drew, the movement direction stays the same (wrong) way. Any idea how to fix it?

Btw, your character input doesn’t work at all in the example map.

Character input works in the example map if you add the required input bindings in the project, you ignored the written indications.

The example Character blueprint graph manages player input, but if you want to do that in C++, you have to translate the nodes to code. I’ll add the translated code as unused functions in NinjaCharacter class.

I just created a new ThirdPerson C++ project for testing, this code might be useful for you:

/* .H: ******************************************************/
#pragma once

#include "CoreMinimal.h"
#include "NinjaCharacter.h"
#include "InputActionValue.h"
#include "MyProjectCharacter.generated.h"

UCLASS(config=Game)
class AMyProjectCharacter : public ANinjaCharacter
{
	GENERATED_BODY()

	/** Camera boom positioning the camera behind the character */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class USpringArmComponent* CameraBoom;

	/** Follow camera */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* FollowCamera;

	/** MappingContext */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	class UInputMappingContext* DefaultMappingContext;

	/** Jump Input Action */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	class UInputAction* JumpAction;

	/** Move Input Action */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	class UInputAction* MoveAction;

	/** Look Input Action */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	class UInputAction* LookAction;

public:
	AMyProjectCharacter(const FObjectInitializer& ObjectInitializer);

protected:
	/** Called for movement input */
	void Move(const FInputActionValue& Value);

	/** Called for looking input */
	void Look(const FInputActionValue& Value);

protected:
	// APawn interface
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	// To add mapping context
	virtual void BeginPlay();

public:
	/** Returns CameraBoom subobject **/
	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
	/** Returns FollowCamera subobject **/
	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};

/* .CPP: ******************************************************/
#include "MyProjectCharacter.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "NinjaMath.h"

//////////////////////////////////////////////////////////////////////////
// AMyProjectCharacter

AMyProjectCharacter::AMyProjectCharacter(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.f);

	// Do not rotate when the controller rotates. Let that just affect the camera.
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	// Configure character movement
	GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input

	// Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint
	// instead of recompiling to adjust them
	GetCharacterMovement()->JumpZVelocity = 700.f;
	GetCharacterMovement()->AirControl = 0.35f;
	GetCharacterMovement()->MaxWalkSpeed = 500.f;
	GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
	GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;

	// Create a camera boom (pulls in towards the player if there is a collision)
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(RootComponent);
	CameraBoom->TargetArmLength = 400.0f; // The camera follows at this distance behind the character
	CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

	// Create a follow camera
	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
	FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm

	// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
	// are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
}

void AMyProjectCharacter::BeginPlay()
{
	// Call the base class
	Super::BeginPlay();

	//Add Input Mapping Context
	if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
		{
			Subsystem->AddMappingContext(DefaultMappingContext, 0);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
// Input

void AMyProjectCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	// Set up action bindings
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		//Jumping
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);

		//Moving
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyProjectCharacter::Move);

		//Looking
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyProjectCharacter::Look);
	}
}

void AMyProjectCharacter::Move(const FInputActionValue& Value)
{
	if (Controller == nullptr)
	{
		return;
	}

	const FVector RightDirection = FNinjaMath::GetAxisY(Controller->GetControlRotation());
	const FVector ForwardDirection = (RightDirection ^ GetActorAxisZ()).GetSafeNormal();

	// input is a Vector2D
	FVector2D MovementVector = Value.Get<FVector2D>();

	// Add movement input along a world direction vector
	AddMovementInput(ForwardDirection, MovementVector.Y);
	AddMovementInput(RightDirection, MovementVector.X);
}

void AMyProjectCharacter::Look(const FInputActionValue& Value)
{
	if (Controller == nullptr)
	{
		return;
	}

	// input is a Vector2D
	FVector2D LookAxisVector = Value.Get<FVector2D>();

	// Pass yaw and pitch input to the Controller
	AddControllerYawInput(LookAxisVector.X);
	AddControllerPitchInput(LookAxisVector.Y);
}

After replacing the PlayerController to use the NinjaPlayerCameraManager and changing other properties in blueprints, it works nicely.

Since Xaklse said it was okay with him to post about it here, I will be selling the smooth camera code for Ninja Character.

The announcement/setup video has links to the demo and instructions on how to get and use.

Hope this helps with your game development careers.

Thanks, I ended up calculating movement vectors myself (based on the camera rotation, cause controller rolls for some reason when its pitch increases), but I’ll also try your approach with the character rotation.

I was trying to do a smooth character rotation too, but what do you mean by “unrotating” the forward vector, and skipping the delta?

I have a little problem with gravity when using a dedicated server. Every client uses the same character. Every client is supposed to use Point Gravity, but only the first connected client uses it, all others use Fixed Gravity for some reason.

If the fist connected client with Point Gravity leaves the server, the second client that was using Fixed Gravity, switches to Point Gravity.

Any help would be appreciated.

UPD: Okay, I’m stupid. I completely forgot that I was temporarily setting the gravity direction only on the first valid character.

void AGravitySphere::SetPlayerGravity()
{
	ASpaceoutCharacter* spaceoutCharacter = nullptr;

	TArray<AActor*> overlappingActors;
	sphere->GetOverlappingActors(overlappingActors);

	for (int i = 0; i < overlappingActors.Num(); i++)
	{

		spaceoutCharacter = Cast<ASpaceoutCharacter>(overlappingActors[i]);

		if (spaceoutCharacter)
		{
			break; // temp
		}
		else
		{
			return;
		}
	}

		if (UKismetSystemLibrary::IsDedicatedServer(GetWorld())) return;
		spaceoutCharacter->SetCenterOfGravity(GetSphereLocation());
}

Hey all, thought I’d share my Camera Smoothing function for free:

Slope:

Sphere World:

Code:

void ANinjaCharacter::AdjustControlRotationToActor(float DeltaTime)
{
	if(Controller)
	{
		// Get the current control rotation
		FRotator CurrentControlRotator = Controller->GetControlRotation();

		// Convert world-space control rotation to local-space control rotation relative to characters rotation
		FQuat WorldToCharacterRotation = GetActorRotation().Quaternion().Inverse();
		FQuat LocalControlRotation = WorldToCharacterRotation * CurrentControlRotator.Quaternion();

		// Perform the pitch and yaw adjustments in this local space
		// We create a desired local control rotation with the same pitch and yaw but zero roll
		FRotator DesiredLocalControlRotator(LocalControlRotation.Rotator().Pitch, LocalControlRotation.Rotator().Yaw, 0);
		FQuat DesiredLocalControlRotation = DesiredLocalControlRotator.Quaternion();

		// Interpolate from the current to the desired local control rotation
		FQuat NewLocalControlRotation = FQuat::Slerp(LocalControlRotation, DesiredLocalControlRotation, DeltaTime * CameraInterpSpeed);

		// Convert this local-space control rotation back to world-space control rotation
		FQuat NewWorldControlRotation = GetActorRotation().Quaternion() * NewLocalControlRotation;

		// And finally apply the new world-space rotation to the controller
		Controller->SetControlRotation(NewWorldControlRotation.Rotator());
	}	
}

There are a few caveats:

This requires you turn Off “Capsule Rotates Control Rotation”.

This function also runs on tick, but it’s pretty minimal. There may be a cleaner method with less instructions but this feels good, smooth, and responsive imo.

The camera roll is pretty much the only thing adjusted with direction change, the pitch and yaw remain the same. This is ideal for me when developing a shooter, so that my aim is affected as little as possible.

(The project here uses Halo assets as a placeholder)

No credit or payment required to use this

1 Like

Hello everyone!
If you don’t mind, please share a link to information about character replication in this plugin for a dedicated server.

This is exactly what I was looking for, thanks a million dude! This solves an issue I was having where the camera is only updating its rotation on player 1 in a multiplayer setup.

2 Likes

I have previously asked this in the questions tab in the store but I had worded it wrong.
I want wall jumps and i followed this tutorial and i want it to also work with ninja character plugin so i can wall jump on any wall while having any gravity direction. I’m pretty sure that to do this, the player’s gravity vector is probably going to help a lot, but i cannot seem to find it anywhere.
the wall jump works by rotating the player to be facing the wall by rotating their yaw to be the yaw of the wall (I’m pretty sure that’s how it works) and then launching them up and away from the wall when they press the jump key while on the wall

Also is there a way so that only the gravity area with the highest priority effects the player where you get every gravity area you are in and find the highest priority (which will be a variable in the gravity actor), then makes the one with the highest value effect the player? I’m not very good at unreal engine and I cant figure it out.

where do we place this code?

Controls seem to break whenever on a wall, either the camera doesn’t follow the player or movements rotation and no longer correctly apply to the character, i’m also after if i jump from the wall my direction would default back to original gravity (think like a spider its stuck to the wall if it detached from the wall it would fall to the floor) while also being able to define certain walls and surfaces as walking, how would i go about this?

PhysicsVolumes have a priority setting, the example map has solutions for all your troubles, carefully inspect the level blueprints.

@Xaklse We’re maintaining a derivative class for our game, but have some minor bugfixes and new DirectionModes to possibly contribute back to the core plugin.

Do you recommend just continuing to maintain our own code, or are there plans to open source and put the plugin on Github/Gitlab with a group of trusted stewards to handle pull requests / versioning / improvements etc.?

I noticed 5.3 has officially implemented arbitrary gravity direction.

Haven’t tested it yet. No announcement, so it may not be finished.

I downloaded the plugin for 5.2 but its giving errors and wont compile in 5.3 source code… does anyone have a working 5.3 plugin?

is this why the plugin is dead? I dont understand what happened from 5.2 to 5.3 that things can never be the same again

After checking Epic’s pending changes that will arrive with UE 5.4, after carefully reviewing the present worrisome state of the official arbitrary gravity code, and according to additional info I have, I decided to reverse my decision and release an update for UE 5.3.

The 5.3 update is ready and awaiting approval by Epic. It could take hours or days from now.

I chose to completely ignore Epic’s new implementation of gravity to save a lot of time, so changing or obtaining their gravity direction won’t work properly.

1 Like