Camera behaviour like in Tekken or Mortal Kombat 4

Hi, Community!!! I make fighting game for two players by using the SideScrollTemplate. I have extended ACharacter class, AGameMode class, APlayerController class and UGameViewportClient class. I have placed camera in the level. When I launch game in editor, camera is in mesh of first player, but I need camera behaviour like in games Tekken 3 and Mortal Kombat 4. How I can do this?

Code of my character class:



#pragma once
#include "GameFramework/Character.h"
#include "FighingGameCharacter.generated.h"

UCLASS(config=Game)
class AFighingGameCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	AFighingGameCharacter();

};

#include "FighingGame.h"
#include "FighingGameCharacter.h"

AFighingGameCharacter::AFighingGameCharacter()
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	// Don't rotate when the controller rotates.
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	// Configure character movement
	GetCharacterMovement()->bOrientRotationToMovement = true; // Face in the direction we are moving..
	GetCharacterMovement()->GravityScale = 2.f;
	GetCharacterMovement()->GroundFriction = 3.f;
	GetCharacterMovement()->MaxWalkSpeed = 100.f;
	
	// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) 
	// are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)
}


Code of my game mode class:



#pragma once
#include "GameFramework/GameMode.h"
#include "FighingGameGameMode.generated.h"

UCLASS(minimalapi)
class AFighingGameGameMode : public AGameMode
{
	GENERATED_BODY()

public:
	AFighingGameGameMode();

	virtual void BeginPlay() override;
};

#include "FighingGame.h"
#include "FighingGameGameMode.h"
#include "FighingGameCharacter.h"
#include "EngineGlobals.h"
#include "Kismet/GameplayStatics.h"
#include "Engine.h"
#include "FightingGameViewportClient.h"
#include "FightingGamePlayerController.h"

AFighingGameGameMode::AFighingGameGameMode()
{
	// set default pawn class to our Blueprinted character
	//static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/SideScrollerCPP/Blueprints/SideScrollerCharacter"));
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/Blueprints/BP_FighingGameCharacter"));

	if (PlayerPawnBPClass.Class != NULL)
	{
		DefaultPawnClass = PlayerPawnBPClass.Class;
	}

	PlayerControllerClass = AFightingGamePlayerController::StaticClass();
}

void AFighingGameGameMode::BeginPlay()
{
	Super::BeginPlay();

	UGameplayStatics::CreatePlayer(GetWorld());
}


Code of my player controller class:



#pragma once

#include "GameFramework/PlayerController.h"
#include "FightingGamePlayerController.generated.h"

/**
 * 
 */
UCLASS(config=Game)
class FIGHINGGAME_API AFightingGamePlayerController : public APlayerController
{
	GENERATED_BODY()
	
	
public:

	AFightingGamePlayerController();

	virtual void InitInputSystem() override;
	

private:
	void HorizontalMovePawn(float AxisValue);
	void VerticalMovePawn(float AxisValue);
	
};

#include "FighingGame.h"
#include "FightingGamePlayerController.h"
#include "FighingGameCharacter.h"

AFightingGamePlayerController::AFightingGamePlayerController()
{
	
}

void AFightingGamePlayerController::InitInputSystem()
{
	Super::InitInputSystem();

	UE_LOG(LogTemp, Warning, TEXT("AFightingGamePlayerController::InitInputSystem()"));

	if (GetLocalPlayer() != NULL)
	{
		switch (GetLocalPlayer()->GetControllerId())
		{
		case 0:
			InputComponent->BindAxis("HorizontalMoveForFirstPlayer", this, &AFightingGamePlayerController::HorizontalMovePawn);
			InputComponent->BindAxis("VerticalMoveForFirstPlayer", this, &AFightingGamePlayerController::VerticalMovePawn);
			break;

		case 1:
			InputComponent->BindAxis("HorizontalMoveForSecondPlayer", this, &AFightingGamePlayerController::HorizontalMovePawn);
			InputComponent->BindAxis("VerticalMoveForSecondPlayer", this, &AFightingGamePlayerController::VerticalMovePawn);
			break;
		default:
			break;
		}	
	}
}

void AFightingGamePlayerController::HorizontalMovePawn(float Value)
{
	GetPawn()->AddMovementInput(FVector(0.f, -1.f, 0.f), Value);
}

void AFightingGamePlayerController::VerticalMovePawn(float Value)
{
	GetPawn()->AddMovementInput(FVector(-1.f, 0.f, 0.f), Value);
}


Code of my game viewport client class:



#pragma once

#include "Engine/GameViewportClient.h"
#include "FightingGameViewportClient.generated.h"

/**
 * 
 */
UCLASS(Within = Engine, transient, config = Engine)
class FIGHINGGAME_API UFightingGameViewportClient : public UGameViewportClient
{
	GENERATED_UCLASS_BODY()
	
public:
#if WITH_HOT_RELOAD_CTORS
	/** DO NOT USE. This constructor is for internal usage only for hot-reload purposes. */
	UFightingGameViewportClient(FVTableHelper& Helper);
#endif // WITH_HOT_RELOAD_CTORS

	UFightingGameViewportClient();

	//UFightingGameViewportClient(class FObjectInitializer const& Initializer);

	virtual ~UFightingGameViewportClient();
	
	virtual bool InputKey(FViewport* Viewport, int32 ControllerId, FKey Key, EInputEvent EventType, float AmountDepressed = 1.f, bool bGamepad = false) override;
};

#include "FighingGame.h"
#include "EngineGlobals.h"
#include "Engine/Engine.h"
#include "Engine.h"
#include "FightingGameViewportClient.h"
#include "Engine/Console.h"
#include "SlateApplication.h"

UFightingGameViewportClient::UFightingGameViewportClient()
{

}

UFightingGameViewportClient::UFightingGameViewportClient(class FObjectInitializer const& Initializer)
	: Super(Initializer)
{

}

#if WITH_HOT_RELOAD_CTORS
/** DO NOT USE. This constructor is for internal usage only for hot-reload purposes. */
UFightingGameViewportClient::UFightingGameViewportClient(FVTableHelper& Helper)
	: Super(Helper)
{

}
#endif // WITH_HOT_RELOAD_CTORS

UFightingGameViewportClient::~UFightingGameViewportClient()
{

}

bool UFightingGameViewportClient::InputKey(FViewport* InViewport, int32 ControllerId, FKey Key, EInputEvent EventType, float AmountDepressed, bool bGamepad)
{
	if (IgnoreInput())
	{
		return ViewportConsole ? ViewportConsole->InputKey(ControllerId, Key, EventType, AmountDepressed, bGamepad) : false;
	}

	if (Key == EKeys::Enter && EventType == EInputEvent::IE_Pressed && FSlateApplication::Get().GetModifierKeys().IsAltDown() && GetDefault<UInputSettings>()->bAltEnterTogglesFullscreen)
	{
		HandleToggleFullscreenCommand();
		return true;
	}

	if (InViewport->IsPlayInEditorViewport() && Key.IsGamepadKey())
	{
		GEngine->RemapGamepadControllerIdForPIE(this, ControllerId);
	}

	// route to subsystems that care
	bool bResult = (ViewportConsole ? ViewportConsole->InputKey(ControllerId, Key, EventType, AmountDepressed, bGamepad) : false);
	if (!bResult)
	{
		const TArray<class ULocalPlayer*>& GamePlayers = GEngine->GetGamePlayers(this);

		for (ULocalPlayer *GamePlayer : GamePlayers)
		{
			if (GamePlayer && GamePlayer->PlayerController)
			{
				bResult = bResult & GamePlayer->PlayerController->InputKey(Key, EventType, AmountDepressed, bGamepad);
			}
		}



		// A gameviewport is always considered to have responded to a mouse buttons to avoid throttling
		if (!bResult && Key.IsMouseButton())
		{
			bResult = true;
		}
	}

	// For PIE, let the next PIE window handle the input if we didn't
	// (this allows people to use multiple controllers to control each window)
	if (!bResult && ControllerId > 0 && InViewport->IsPlayInEditorViewport())
	{
		UGameViewportClient *NextViewport = GEngine->GetNextPIEViewport(this);
		if (NextViewport)
		{
			bResult = NextViewport->InputKey(InViewport, ControllerId - 1, Key, EventType, AmountDepressed, bGamepad);
		}
	}

	return bResult;
}



Best regards from Russia.

Hey Russia!
I think a spring arm should do the trick, check this link: Spring Arms

What you want to do is find the mid point between your characters and aim your camera at it. You would then change the camera position based on the distance between the characters while aiming it at a point between them. A spring arm is not necessary, but you can use one to add lag and acceleration to the camera’s movement if you like.

Thanks a lot! I have did this by using USpringArmComponent and another empty actor. This is my code



FVector FirstCtrlLoc = FirstController->GetPawn()->GetActorLocation();
FVector SecondCtrlLoc = SecondController->GetPawn()->GetActorLocation();

CameraBoom->TargetArmLength = FirstController->GetPawn()->GetDistanceTo(SecondController->GetPawn());
	
//GEngine->AddOnScreenDebugMessage(0, 0.1, FColor::Blue, FString::Printf(TEXT("CameraBoom->TargetArmLength: %f"), CameraBoom->TargetArmLength));

SetActorLocation((FirstCtrlLoc + SecondCtrlLoc) * 0.5f);

FVector v = SecondCtrlLoc - FirstCtrlLoc;
float angle = FMath::RadiansToDegrees(FMath::Atan2(v.Y, v.X)) + 270.f;

SetActorRotation(UKismetMathLibrary::RInterpTo(
	GetActorRotation(),
	FRotator::MakeFromEuler(
		FVector(
			CameraBoom->GetComponentRotation().Roll,
			CameraBoom->GetComponentRotation().Pitch,
			angle)), DeltaTime, InterpolationSpeedOfRotation));

Camera->GetCameraView(DeltaTime, Out);