Hello, I have a problem with my vehicle movement (or maybe vehicle control?)
I have a project in C++ where the player can choose a car body in the menu. To implement this, I created two Game Modes: one for the menu (MainMenuGameMode) and one for gameplay (GameplayGameMode). Additionally, I built a GameConfig class to store the variables set by the player in the menu.
I created a C++ class named VehiclePawn, inheriting from AWheeledVehicle, and created several car Blueprints based on this class. When the player hits the “Play” button, the new level is loaded with the chosen car, and I switch the Game Mode from MainMenuGameMode to GameplayGameMode. This is set in the level details in the editor.
The cars are properly loaded in the C++ code from the Blueprint instances of my VehiclePawn class, and the view is set up correctly. However, I’m facing an issue: I have no control over the vehicle pawn – there’s no camera movement, and the vehicle itself doesn’t move either.
I’m not sure what I’m missing or why the control isn’t working. Can anyone more experienced point me in the right direction or help identify what I might have overlooked?
Below is the cpp code:
AVehiclePawn.h:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "WheeledVehicle.h"
#include "MyEngines.h"
#include "GameConfig.h"
#include "Weapon.h"
#include "VehiclePawn.generated.h"
/**
*
*/
UCLASS()
class MyProject AVehiclePawn : public AWheeledVehicle
{
GENERATED_BODY()
public:
AVehiclePawn();
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
/*throttle/steering*/
void ApplyThrottle(float val);
void ApplySteering(float val);
/*Look around*/
void LookUp(float val);
void Turn(float val);
/*Handbrake*/
void OnHandbrakePressed();
void OnHandbrakeRelased();
/*Update to air physics*/
void UpdateInAirControl(float DeltaTime);
protected:
/*Spring arm that will offset the camera*/
UPROPERTY(Category = Camera, EditDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAcces = "true"))
class USpringArmComponent* SpringArm;
/*Camera component that will be our viewpoint*/
UPROPERTY(Category = Camera, EditDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
class UCameraComponent* Camera;
public:
void SpawnVehicleFromBlueprint(FVector SpawnLocation, FRotator SpawnRotation, UWorld* World, TSubclassOf<AWheeledVehicle> VehiclePawnBP, APlayerController* MyPlayerCOntroller);
};
AVehiclePawn.cpp:
// Fill out your copyright notice in the Description page of Project Settings.
#include "VehiclePawn.h"
//#include "Engine\Plugins\PhysXVehicles\Public"
#include "Components/SkeletalMeshComponent.h"
#include "SkeletalMeshSocket.generated.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/InputComponent.h"
#include "Components/BoxComponent.h"
#include "GameConfig.h"
#include "Kismet/KismetMathLibrary.h"
#include "WheeledVehicleMovementComponent4W.h"
static const FName NAME_SteerInput("Steering");
static const FName NAME_ThrottleInput("Throttle");
AVehiclePawn::AVehiclePawn() {
//PrimaryActorTick.bCanEverTick = true;
//bReplicates = true;
UWheeledVehicleMovementComponent4W* Vehicle4W = CastChecked<UWheeledVehicleMovementComponent4W>(GetVehicleMovement());
//Adjust the tire loading
Vehicle4W->MinNormalizedTireLoad = 0.0f;
Vehicle4W->MinNormalizedTireLoadFiltered = 0.2f;
Vehicle4W->MaxNormalizedTireLoad = 2.0f;
Vehicle4W->MaxNormalizedTireLoadFiltered = 2.0f;
//torque setup
Vehicle4W->MaxEngineRPM = 5730;
Vehicle4W->EngineSetup.TorqueCurve.GetRichCurve()->Reset();
Vehicle4W->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(0, 1000);
Vehicle4W->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(2800, 500);
Vehicle4W->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(5730, 400);
//adjust the steering
Vehicle4W->SteeringCurve.GetRichCurve()->Reset();
Vehicle4W->SteeringCurve.GetRichCurve()->AddKey(0.0f, 1.0f);
Vehicle4W->SteeringCurve.GetRichCurve()->AddKey(40.0f, 0.7f);
Vehicle4W->SteeringCurve.GetRichCurve()->AddKey(120.0f, 0.6f);
Vehicle4W->DifferentialSetup.DifferentialType = EVehicleDifferential4W::Open_RearDrive;
Vehicle4W->DifferentialSetup.FrontRearSplit = 0.65;
//automatic gearbox
Vehicle4W->TransmissionSetup.bUseGearAutoBox = true;
Vehicle4W->TransmissionSetup.GearSwitchTime = 0.15f;
Vehicle4W->TransmissionSetup.GearAutoBoxLatency = 1.0f;
//create a spring arm component
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->TargetArmLength = 270.0f;
SpringArm->bUsePawnControlRotation = true;
//create the chase camera component
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ChaseCamera"));
Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
Camera->FieldOfView = 85.0f;
}
void AVehiclePawn::SpawnVehicleFromBlueprint(FVector SpawnLocation, FRotator SpawnRotation, UWorld* World, TSubclassOf<AWheeledVehicle> VehiclePawnBP, APlayerController* MyPlayerController) {
FActorSpawnParameters SpawnInfo;
AVehiclePawn* MyVehiclePawn = World->SpawnActor<AVehiclePawn>(VehiclePawnBP, FVector(.0f,.0f,.0f), SpawnRotation, SpawnInfo);
if (MyVehiclePawn) {
APlayerController* FirstPlayerController = MyPlayerController;
if (FirstPlayerController) {
/*FirstPlayerController->UnPossess();
FirstPlayerController->Possess(MyVehiclePawn);*/
FInputModeGameOnly GameplayInputMode;
FirstPlayerController->SetInputMode(GameplayInputMode);
FirstPlayerController->bShowMouseCursor = false;
}
else {
UE_LOG(LogTemp, Warning, TEXT("Player controller is null"));
}
UE_LOG(LogTemp, Warning, TEXT("PlayerControler posses %s"), *FirstPlayerController->GetPawn()->GetName());
}
else {
UE_LOG(LogTemp, Warning, TEXT("VehiclePawn is null"));
}
}
void AVehiclePawn::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
UpdateInAirControl(DeltaTime);
void AVehiclePawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) {
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("Throttle", this, &AVehiclePawn::ApplyThrottle);
PlayerInputComponent->BindAxis("Steering", this, &AVehiclePawn::ApplySteering);
PlayerInputComponent->BindAxis("LookUp", this, &AVehiclePawn::LookUp);
PlayerInputComponent->BindAxis("Turn", this, &AVehiclePawn::Turn);
PlayerInputComponent->BindAction("Handbrake", IE_Pressed, this, &AVehiclePawn::OnHandbrakePressed);
PlayerInputComponent->BindAction("Handbrake", IE_Released, this, &AVehiclePawn::OnHandbrakeRelased);
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AVehiclePawn::OnFire);
}
void AVehiclePawn::ApplyThrottle(float val)
{
GetVehicleMovementComponent()->SetThrottleInput(val);
}
void AVehiclePawn::ApplySteering(float val)
{
GetVehicleMovementComponent()->SetSteeringInput(val);
}
void AVehiclePawn::LookUp(float val)
{
if (val != 0.0f) {
AddControllerPitchInput(val);
}
}
void AVehiclePawn::Turn(float val)
{
if (val != 0.0f) {
AddControllerYawInput(val);
}
}
void AVehiclePawn::OnHandbrakePressed()
{
GetVehicleMovementComponent()->SetHandbrakeInput(true);
}
void AVehiclePawn::OnHandbrakeRelased()
{
GetVehicleMovementComponent()->SetHandbrakeInput(false);
}
void AVehiclePawn::UpdateInAirControl(float DeltaTime)
{
if (UWheeledVehicleMovementComponent4W* Vehicle4W = CastChecked<UWheeledVehicleMovementComponent4W>(GetVehicleMovement())) {
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
const FVector TraceStart = GetActorLocation() + FVector(0.0f, 0.0f, 50.0f);
const FVector TraceEnd = GetActorLocation() + FVector(0.0f, 0.0f, 200.0f);
FHitResult Hit;
//check if car is flipped on its side and check if the car is in air
const bool bInAir = (GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility, QueryParams));
const bool bNotGrounded = FVector::DotProduct(GetActorUpVector(), FVector::UpVector) < 0.1f;
//only allow in air-movement if we are not on the ground, or are in the air
if (bInAir || bNotGrounded) {
const float ForwardInput = InputComponent->GetAxisValue("Throttle");
const float RightInput = InputComponent->GetAxisValue(NAME_SteerInput);
//if car is grounded allow player to roll the car over
const float AirMovementForcePitch = 3.0f;
const float AirMovementForceRoll = !bInAir && bNotGrounded ? 20.0f : 3.0f;
if (UPrimitiveComponent* MyVehicleMesh = Vehicle4W->UpdatedPrimitive) {
const FVector MovementVector = FVector(RightInput * -AirMovementForceRoll, ForwardInput * AirMovementForcePitch, 0.0f) * DeltaTime * 90.0f;//tutaj zmiana z 200 na 100
const FVector NewAngularMovement = GetActorRotation().RotateVector(MovementVector);
MyVehicleMesh->SetPhysicsAngularVelocity(NewAngularMovement, true);
}
}
}
}
UGameConfig.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "GameConfig.h"
#include "Engine/World.h"
#include "GameFramework/Actor.h"
#include "GameplayGameMode.h"
#include "Kismet/GameplayStatics.h"
UGameConfig::UGameConfig() {
static ConstructorHelpers::FObjectFinder<UClass> PawnObject(TEXT("Blueprint'/Game/Vehicles/Pickup/BP_PickupTruck.BP_PickupTruck_C'"));
PawnClass = PawnObject.Object;
}
void UGameConfig::InitializeVehicle(UGameConfig* SettedGameConfig, UWorld* World, APlayerController* GC_PlayerController) {
if (!SettedGameConfig) {
UE_LOG(LogTemp, Warning, TEXT("SettedGameConfig is null"));
return;
}
if (!World) {
UE_LOG(LogTemp, Warning, TEXT("InitializeVehicle() - World is null"));
return;
}
if (SettedGameConfig->CarSkeletalMesh == nullptr) {
UE_LOG(LogTemp, Warning, TEXT("Car is null in GameConfig"));
return;
}
if (SettedGameConfig->CarSkeletalMesh) {
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
FVector SpawnLocation(0.0f,0.0f,0.0f);
FRotator SpawnRotation(0.0f, 0.0f, 0.0f);
AVehiclePawn* NewCar = NewObject<AVehiclePawn>();
NewCar->SpawnVehicleFromBlueprint(SpawnLocation, SpawnRotation, World, PawnClass, GC_PlayerController);
}
else {
UE_LOG(LogTemp, Warning, TEXT("Cannot initialize Car in GameConfig."));
}
}
GameplayGameMode.cpp:
// Copyright Epic Games, Inc. All Rights Reserved.
#include "GameplayGameMode.h"
#include "Kismet/GameplayStatics.h"
void AGameplayGameMode::StartPlay() {
UMyGameInstance* MyGameInstance = Cast<UMyGameInstance>(GetWorld()->GetGameInstance());
APlayerController* PlayerContr = GetGameInstance()->GetPrimaryPlayerController();
if (MyGameInstance) {
VehicleGameConfig = MyGameInstance->GetGameConfig();
if (VehicleGameConfig) {
if (GetWorld()) {
VehicleGameConfig->InitializeVehicle(VehicleGameConfig, GetWorld(), PlayerContr);
}
else {
UE_LOG(LogTemp, Warning, TEXT("World == null (GameplayGameMode.cpp)"));
}
}
else {
UE_LOG(LogTemp, Warning, TEXT("NewVehicleGameConfig in GameplayGameMode is null"));
}
}
else {
UE_LOG(LogTemp, Warning, TEXT("MyGameInstance == null || VehicleGameConfig == null"));
}
}
void AGameplayGameMode::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
}
void AGameplayGameMode::BeginPlay() {
Super::BeginPlay();
}
Everything works fine when I implement all gameplay logic in the level blueprint, but I want to create more levels in the future and I don’t want to replicate the gameplay logic in every level. That’s why I want to implement it once in C++ so it works for all future levels.
Thanks for every idea