How to associate C++ code with meshes and spawn them?

Hello fellow developers,

I have a bit of a problem in UE4. I am attempting to build a tank, I have spawned the chassis using blue prints.


ATestMode::ATestMode(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	// set default pawn class to our Blueprinted character
	static ConstructorHelpers::FObjectFinder<UBlueprint> PlayerPawnObject(TEXT("Blueprint'/Game/Blueprints/TestTankChasis.TestTankChasis'"));
	if (PlayerPawnObject.Object != NULL)
	{
		DefaultPawnClass = (UClass*)PlayerPawnObject.Object->GeneratedClass;
	}
}

I was able to set up some code to get the chassis moving around and rotating.

Next I went to create a tank turret, I added the mesh to the chassis using blueprints, and after running it appeared ontop of the chassis moving and rotating with it.
Next I need to attach a boom and camera to the turret and have it rotating individually of the chassis. This is where I ran into problems.

Currently, the boom and camera are attached to the chassis, the objective is to have them be attached to the turret so the chassis can rotate individually of the turret and vice versa. ABaseTank is my chassis.
Header:




#pragma once

#include "GameFramework/Character.h"
#include "BaseTank.generated.h"

/**
 * 
 */
UCLASS()
class ABaseTank : public ACharacter
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
	TSubobjectPtr<class USpringArmComponent> CameraBoom;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
		TSubobjectPtr<class UCameraComponent> FollowCamera;

	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) OVERRIDE;

	UPROPERTY(EditDefaultsOnly, Category = "Tank Variables")
	float ChassisTurnRate;

	UFUNCTION()
		void MoveForward(float Val);
	
	UFUNCTION()
		void RotateChassis(float Val);
};

Source:


ABaseTank::ABaseTank(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	// Create a camera boom (pulls in towards the player if there is a collision)
	CameraBoom = PCIP.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("CameraBoom"));
	CameraBoom->AttachTo(RootComponent);
	CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character	
	CameraBoom->bUseControllerViewRotation = true; // Rotate the arm based on the controller

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

With movement and rotation split up into different functions.
Input:



void ABaseTank::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	check(InputComponent);
	InputComponent->BindAxis("MoveForward", this, &ABaseTank::MoveForward);
	InputComponent->BindAxis("Rotate", this, &ABaseTank::RotateChassis);
}

Movement:



void ABaseTank::MoveForward(float Value)
{
	if ((Controller != NULL) && (Value != 0.0f))
	{
		// find out which way is forward
		FRotator Rotation = Controller->GetControlRotation();
		// Limit pitch when walking or falling
		if (CharacterMovement->IsMovingOnGround() || CharacterMovement->IsFalling())
		{
			Rotation.Pitch = 0.0f;
		}
		// add movement in that direction
		const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X);
		AddMovementInput(Direction, Value);
	}
}

Rotation:


void ABaseTank::RotateChassis(float Value)
{
	if ((Controller != NULL) && (Value != 0.0f))
	{
		AddControllerYawInput(Value * ChassisTurnRate * GetWorld()->GetDeltaSeconds());
	}
}

I added the code necessary to control the boom and camera with the mouse.



void ABaseTank::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	check(InputComponent);
        //Keyboard chassis rotation
	InputComponent->BindAxis("MoveForward", this, &ABaseTank::MoveForward);
	InputComponent->BindAxis("Rotate", this, &ABaseTank::RotateChassis);

        //Mouse chassis rotation
	InputComponent->BindAxis("Turn", this, &ATankTurret::AddControllerYawInput);
	InputComponent->BindAxis("LookUp", this, &ATankTurret::AddControllerPitchInput);
}

Next I created my TankTurret class.
I moved the code for creating the boom and camera and mouse rotation to the tank turret.
Header




#pragma once

#include "GameFramework/Pawn.h"
#include "TankTurret.generated.h"

/**
 * 
 */
UCLASS()
class ATankTurret : public APawn
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
		TSubobjectPtr<class USpringArmComponent> CameraBoom;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
		TSubobjectPtr<class UCameraComponent> FollowCamera;
protected:
	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) OVERRIDE;
};


Source:




#include "../TankGame.h"
#include "TankTurret.h"

ATankTurret::ATankTurret(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	// Create a camera boom (pulls in towards the player if there is a collision)
	CameraBoom = PCIP.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("CameraBoom"));
	CameraBoom->AttachTo(RootComponent);
	CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character	
	CameraBoom->bUseControllerViewRotation = true; // Rotate the arm based on the controller

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

void ATankTurret::SetupPlayerInputComponent(UInputComponent* InputComponent)
{
	InputComponent->BindAxis("Turn", this, &ATankTurret::AddControllerYawInput);
	InputComponent->BindAxis("LookUp", this, &ATankTurret::AddControllerPitchInput);
}

Now, the turret should be able to rotate separately from the chassis, however I do not understand how I need to spawn the turret in order for this code to be executed. Currently, the turret mesh is positioned ontop of the chassis as I attached it there using blueprints, but I can not get it to do anything individually from the chassis. How does UE4 associate classes and meshes together? The way I see it, the chassis and the turret need to be two different classes that effect two different meshes and have separate code from one another, how can I achieve this?

Any help would be greatly appreciated.

Bump. UE4 noob here, please help. I feel like spawning meshes and having different code effecting them is something really basic, but I am still not doing something right here.

I’ll try to help, I know how annoying it is to have your posts ignored. :slight_smile:

Mine is a robot which has the hull (base of your tank) and torso (turret). The torso has weapon mounting points on it, and tracks the mouse.


APCRobotPawn::APCRobotPawn(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	SetReplicates(true);

	HullComponent = PCIP.CreateOptionalDefaultSubobject<USkeletalMeshComponent>(this, FName(TEXT("HullComponent")), false);
	RootComponent = HullComponent;

	TorsoComponent = PCIP.CreateOptionalDefaultSubobject<USkeletalMeshComponent>(this, FName(TEXT("TorsoComponent")), false);
	TorsoComponent->AttachParent = RootComponent;
	TorsoComponent->AttachSocketName = "TorsoBone";

	CameraSpringArm = PCIP.CreateOptionalDefaultSubobject<USpringArmComponent>(this, FName(TEXT("SpringArm")), false);
	CameraSpringArm->AttachParent = HullComponent;
	CameraSpringArm->RelativeRotation.Pitch = -70.f;
	CameraSpringArm->SetHiddenInGame(false);

	Camera = PCIP.CreateOptionalDefaultSubobject<UCameraComponent>(this, FName(TEXT("Camera")), false);
	Camera->bUseControllerViewRotation = false;
	Camera->AttachParent = CameraSpringArm;
	Camera->AttachSocketName = "SpringEndpoint";
	Camera->RelativeLocation = FVector::ZeroVector;

	for (int i = 0; i < 4; i++)
	{
		TSubobjectPtr<USkeletalMeshComponent> weapon = PCIP.CreateOptionalDefaultSubobject<USkeletalMeshComponent>(this, FName(TEXT("Weapon_%d"), i));
		UnusedWeapons.Add(weapon);
	}
}

I am using skeletal meshes to make sure the things attach at the right spots, I suggest you do the same.

In this constructor, the camera’s spring arm is attached to the hull, but that’s a very simple change. Just attach it to the torso (turret) instead and you get the behaviour you want.

When you subclass this class with a blueprint, you get a blueprint with all the components in the right order. If you didn’t want a camera defined here, you couldf simply omit the spring arm and camera and define it in the blueprint, attaching it to the torso/turret. Keep in mind that the way you should be going is C++ class->Blueprint (and stop there). Your C++ class can expose useful things to level editors/yourself.

If you’ve got any specific questions, I’m happy to answer them.

Not sure why you would need this to be the case? My robot pawn is built from a bunch of different pieces dynamically in a data driven way, but the pawn itself is still one class. The various definitions of the way it looks and its stats ARE separate classes. What did you want to achieve?

Also, I wouldn’t recommend using ACharacter as a base class for this, but that’s just me. It’s really useful for some stuff, but you don’t need it for a tank.

I was starting to lose hope, sorry for the late response.

This does help shed some light on things, and I’ll try to use this to get my tank working right. An issue/question though:

The meshes I am using for the tank are static meshes and have no bones, is it possible to add my own bones to them where ever I want? I just need one to attach the turret to(After a bit of research I found some tutorials talking about using bones, but none about adding them.). If that is not possible, how can I render the turret relative to the tank and manipulate it? My current way of doing it was to open the tank chassis in the blue print editor and I manually dropped in and positioned the turret mesh on top of the tank. The treads that were already part of the chassis blueprint look like they were also added in the same way, they don’t animate though, just statically render like the turret. As a last resort I’m just going to find some meshes with bones and try your method just to see if I can get it working.

The problem I ran into was that in the ATestMode constructor you can only specify one DefaultPawnClass for unreal to spawn, which makes sense you can only play as one thing, but then I was faced with the problem of how do I spawn the turret? After a bit of digging it looked to me like I need to set it up with the blueprints to be part of the TestTankChasis, but then how do I get my ATankTurret class to effect it so I can get it rotating.

From looking at your code you spawn the different parts of the robot using CreateOptionalDefaultSubobject and then attach them to each other using AttachParent and AttachSocketName.

I am trying to achieve the proper setup in UE4, maybe this was an error in thinking and not how things are done in UE4, and if so by all means correct me so I can organize things in a way that makes sense for UE4. They way I thought about it was that the chassis has some functionality, and the turret has some functionality, each specific to a different static mesh so I tried to keep things clean by not writing my code all in one file and split it up into two different ones. The chassis handles keyboard rotation and movement, the turret handles the camera and mouse rotation and the camera boom. The chassis then has a turret that it initializes in its constructor to spawn in a specific place relative to the chassis. This should allow me to swap different turrets very easily later on. I’d actually move the camera/rotation functionality to a base class and then have other turrets inherit from it so all I have to give them is a mesh to render.

Yes, that’s easy enough. It depends on the file/3d modeller though. So if you’ve got an FBX, open it in a modelling program and then figure out how to add a bone. It took me about an hour ish of watching youtube to figure out how to do it in Maya. If you don’t don’t have one, Blender is free and will do it, although there’s a learning curve.

You can probably do it without bones, but it does complicate things. Essentially what you’d be doing then is setting the relative location of the sub object, rather than just letting the bones do the work. I really recommend you do it the bones way. Without a bone to pivot around, rotating the turret becomes annoying as well.

Ya. that’s the wrong road. One pawn/actor/character for each player, and stuff like the turret and hull should be components.

That’s actually pretty easy - essentially all you do to swap turrets is tell your skeletal mesh component to use a different skeletal mesh. If the bone names are consistent it just pops into place in the right position and everything is good. Any other stuff (rotation rate, stats, etc) is just part of the data and can be separated out in any data classes you like.

Give the approach I’ve got a try, it seems you may be over complicating it a bit. Good luck.