I have an FPS game in a mixture of C++ and Blueprint. The project is using 4.8 on OS X 10.3. The intent is to pick up a weapon (blueprint based on C++ class) and attach it to a socket in the first person character mesh (also a C++ class with extended blueprint). When the weapon is laying on the ground, it has simulation enabled and can’t be picked up, the weapon’s mesh will remain on the ground even though the attachment has been made (it’s made in the character blueprint via the AttachActorToComponent node, see below). If I go in the blueprint of the weapon, turn off simulation, then it can be picked up. In code, to avoid this issue, I execute the following function in my weapon class just prior to the attachment:
void AFPSWeapon::SetPhysicsOff(){
TArray<UStaticMeshComponent*> comps;
this->GetComponents<UStaticMeshComponent>(comps);
if(comps.Num()>0) MeshReference=comps[0];
else {
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Failed to get component"));
}
MeshReference->SetSimulatePhysics(false);
}
Note that MeshReference is defined as a UStaticMeshComponent*.
This turns off the simulation programmatically, and then the mesh can be picked up and attached to the socket on the right hand of the FPS Character mesh. When I do it this way, the weapon mesh stays where it was sitting (completely disassociated with the now attached weapon blueprint, everything else in the weapon blueprint is connected though, e.g. firing fires from the correct position following Character aiming) until I pause the game, click on the weapon blueprint in the World Outliner (now situated heirachically under the FPS blueprint). After clicking on it, resuming the game will cause the mesh to follow the animations and movements of the socket it’s attached to. The weapon mesh starts from the point I click in the outliner, and doesn’t snap to what would have been it’s original position related to the character.
Is this a bug or something I’m doing in the code or inappropriate concept?
The attachment is done as follows:
In the above,
PickupWeaponEvent - event I fire within the C++ character class
CurrentWeapon - the pointer returned to me from a LineTraceSingleByChannel
FirstPersonMesh - the character humanoid mesh that is attached to my First Person Camera Component.
The entire project was based on the wiki First Person Tutorial, heavily modified.
EDIT (to respond to 's question):
Yes, the standalone execution has the same behavior. As is, running standalone did not pick up the mesh. Exiting and manually ticking the Simulation off, let the mesh be picked up with the weapons blueprint.
Here is the .h for the weapon class:
#pragma once
#include "FPSProject.h"
#include "GameFramework/Actor.h"
#include "UsableItem.h"
#include "FPSWeapon.generated.h"
UCLASS()
class FPSPROJECT_API AFPSWeapon : public AUsableItem
{
GENERATED_BODY()
// Sets default values for this actor's properties
AFPSWeapon();
/** Projectile class to spawn */
UPROPERTY(EditDefaultsOnly, Category=Projectile)
TSubclassOf<class AFPSProjectile> ProjectileClass;
//UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=MeshComponent)
UStaticMeshComponent* MeshReference;
public:
/** Gun muzzle's offset from the camera location */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=WeaponParameters)
FVector MuzzleOffset;
/** Damage factor, merged with projectile damage */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=WeaponParameters)
float Damage;
/** Tells whether we have a Auto, single shot or burst */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=WeaponParameters)
WeaponRepeatTypes FireMethod;
/** Time between Trigger Reset or between Bursts or Auto fire */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=WeaponParameters)
int32 FireRate;
/** Burst count */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=WeaponParameters)
int32 BurstCount;
/** Clip Size, number shots between reload */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=WeaponParameters)
int32 ClipSize;
void SetPhysicsOff();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// this fires the weapon
virtual void Fire();
virtual int32 FPSObjectType() override;
virtual FString FPSPickupMessage() override;
virtual FString FPSDescription() override;
virtual void FPSSetDescription(FString mystring) override;
};
Here is the .cpp for the weapon class:
// Fill out your copyright notice in the Description page of Project Settings.
#include "FPSProject.h"
#include "FPSProjectile.h"
#include "FPSWeapon.h"
// Sets default values
AFPSWeapon::AFPSWeapon()
{
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AFPSWeapon::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AFPSWeapon::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
void AFPSWeapon::SetPhysicsOff(){
TArray<UStaticMeshComponent*> comps;
this->GetComponents<UStaticMeshComponent>(comps);
if(comps.Num()>0) MeshReference=comps[0];
else {
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Failed to get component"));
}
MeshReference->SetSimulatePhysics(false);
}
// Called by OnFire in Character when player presses left mouse button
void AFPSWeapon::Fire() {
if (ProjectileClass != NULL) {
// Get the camera transform
FVector CameraLoc;
FRotator CameraRot;
GetActorEyesViewPoint(CameraLoc, CameraRot);
// MuzzleOffset is in camera space, so transform it to world space before offsetting from the camera to find the final muzzle position
FVector const MuzzleLocation = CameraLoc + FTransform(CameraRot).TransformVector(MuzzleOffset);
FRotator MuzzleRotation = CameraRot;
MuzzleRotation.Pitch += 10.0f; // skew the aim upwards a bit
UWorld* const World = GetWorld();
if (World) {
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = Instigator;
// spawn the projectile at the muzzle
AFPSProjectile* const Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams);
if (Projectile) {
// find launch direction
FVector const LaunchDir = MuzzleRotation.Vector();
Projectile->InitVelocity(LaunchDir);
}
}
}
}
ItemPickupMacro(AFPSWeapon,PROJECTILEWEAPON," pick up ","Weapon.");
Here is the .h for the Character class (the class which the weapon is to attach to):
#pragma once
#include "GameFramework/Character.h"
#include "FPSPlayerController.h"
#include "FPSWheeledVehicle.h"
#include "FPSWeapon.h"
#include "FPSLevelScript.h"
#include "FPSCharacter.generated.h"
//
/**
*
*/
UCLASS()
class FPSPROJECT_API AFPSCharacter : public ACharacter
{
GENERATED_BODY()
// Constructor for AFPSCharacter
AFPSCharacter(const FObjectInitializer& ObjectInitializer);
UPROPERTY()
bool MenuOpen; //determines if the menu is displayed or not
UPROPERTY()
bool InThirdPerson; //determines if we are in 3rd or 1st person
UPROPERTY()
bool WeaponReady; //determines whether weapon can be fired or not
/** Pawn mesh: 1st person view (arms; seen only by self) */
UPROPERTY(VisibleDefaultsOnly, Category=Mesh)
USkeletalMeshComponent* FirstPersonMesh;
/** Projectile class to spawn */
UPROPERTY(EditDefaultsOnly, Category=Projectile)
TSubclassOf<class AFPSProjectile> ProjectileClass;
virtual void Tick(float DeltaSeconds) OVERRIDE;
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
AFPSWeapon* CurrentWeapon;
UPROPERTY(EditAnywhere, BluePrintReadWrite, Category="GamePlay")
FString AnnounceItem;
//AFPSLevelScript* TheLS;
virtual void BeginPlay() override;
//UFUNCTION(BlueprintCallable, WithValidation, Server, Reliable, Category = PlayerAbility)
virtual void Use();
protected:
/** Get actor derived from UsableActor currently looked at by the player */
int GetUsableInView();
AFPSWheeledVehicle* ThisVehicle=nullptr;
AUsableItem* ThisItem=nullptr;
AUsableItem* CurrentItem; //Pointer to the currently in use item
/* True only in first frame when focused on new usable actor. */
bool bHasNewFocus;
/* Actor derived from UsableActor currently in center-view. */
AFPSWheeledVehicle* FocusedUsableVehicle;
AUsableItem* FocusedUsableItem;
/* Max distance to use/focus on actors. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
float MaxUseDistance;
//attempt to control speed for animation
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
float CPPSpeed;
//attempt to control Direction for animation
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
float CPPDirection;
/** Gun muzzle's offset from the camera location */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Gameplay)
FVector MuzzleOffset;
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
//handles moving forward/backward
UFUNCTION()
void MoveForward(float Val);
//handles strafing
UFUNCTION()
void MoveRight(float Val);
//sets jump flag when key is pressed
UFUNCTION()
void OnStartJump();
//clears jump flag when key is released
UFUNCTION()
void OnStopJump();
//Opens or Closes Menu
UFUNCTION()
void MenuPressed();
//Toggles between third and first person
UFUNCTION()
void ThirdPerson();
//Reload requested
UFUNCTION()
void Reload();
//handles firing
UFUNCTION()
void OnFire();
//Exit game
UFUNCTION()
void Exit();
/** First person camera */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
UCameraComponent* FirstPersonCameraComponent;
AFPSPlayerController* ThePC;
//Start the elevator
UFUNCTION(BlueprintImplementableEvent, Category = PlayerAbility)
virtual void StartTheElevator();
UFUNCTION(BlueprintImplementableEvent, Category = PlayerAbility)
virtual void PickUpWeapon();
};
Here is the .cpp for the character class:
#include "FPSProject.h"
#include "FPSCharacter.h"
#include "FPSProjectile.h"
//#include "FPSWheeledVehicle.h"
#include "FPSLift.h"
#include "UsableItem.h"
void AFPSCharacter::BeginPlay() {
Super::BeginPlay();
if (GEngine) {
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Using FPSCharacter"));
}
// Position the camera a bit above the eyes
FirstPersonCameraComponent->RelativeLocation = FVector(0, 0, 50.0f + BaseEyeHeight);
// Allow the pawn to control rotation.
FirstPersonCameraComponent->bUsePawnControlRotation = true;
//This gets a pointer to my player controller so I can execute functions and access variables within PC
ThePC= Cast<AFPSPlayerController>(GetWorld()->GetFirstPlayerController());
// MyLS = Cast<AFPSLevelScript>(GetWorld()->GetLevelScriptActor());
}
AFPSCharacter::AFPSCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Create a CameraComponent
FirstPersonCameraComponent = ObjectInitializer.CreateDefaultSubobject<UCameraComponent>(this, TEXT("FirstPersonCamera"));
FirstPersonCameraComponent->AttachParent = GetCapsuleComponent();
// Create a mesh component that will be used when being viewed from a '1st person' view (when controlling this pawn)
FirstPersonMesh = ObjectInitializer.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("FirstPersonMesh"));
FirstPersonMesh->SetOnlyOwnerSee(true); // only the owning player will see this mesh
FirstPersonMesh->AttachParent = FirstPersonCameraComponent;
FirstPersonMesh->bCastDynamicShadow = false;
FirstPersonMesh->CastShadow = false;
// everyone but the owner can see the regular body mesh
GetMesh()->SetOwnerNoSee(true);
MaxUseDistance = 260;
bHasNewFocus = true;
}
void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* InputComponent) {
MenuOpen = false; //Menu is close to start
InThirdPerson = false; //Starting in First Person
WeaponReady = true; //starting out with weapon ready
// set up gameplay key bindings
InputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
InputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);
InputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput);
InputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput);
InputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::OnStartJump);
InputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::OnStopJump);
InputComponent->BindAction("ActOnItem", IE_Released, this, &AFPSCharacter::Use);
InputComponent->BindAction("ThirdPersonToggle", IE_Released, this, &AFPSCharacter::ThirdPerson);
InputComponent->BindAction("Menu", IE_Released, this, &AFPSCharacter::MenuPressed);
InputComponent->BindAction("Reload", IE_Released, this, &AFPSCharacter::Reload);
InputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::OnFire);
InputComponent->BindAction("Exit", IE_Released, this, &AFPSCharacter::Exit);
}
/*
Performs raytrace to find closest looked-at UsableActor.
*/
int AFPSCharacter::GetUsableInView()
{
FVector camLoc;
FRotator camRot;
if (Controller == NULL)
return false;
Controller->GetPlayerViewPoint(camLoc, camRot);
const FVector start_trace = camLoc;
const FVector direction = camRot.Vector();
const FVector end_trace = start_trace + (direction * MaxUseDistance);
FCollisionQueryParams TraceParams(FName(TEXT("")), true, this);
TraceParams.bTraceAsyncScene = true;
TraceParams.bReturnPhysicalMaterial = false;
TraceParams.bTraceComplex = true;
FHitResult Hit(ForceInit);
GetWorld()->LineTraceSingleByChannel(Hit, start_trace, end_trace, COLLISION_PROJECTILE, TraceParams);
if(Hit.GetActor()) {
if(Hit.GetActor()->IsA(AFPSWheeledVehicle::StaticClass())) {
ThisVehicle=Cast<AFPSWheeledVehicle>(Hit.GetActor());
ThisItem=nullptr;
return VEHICLE;
}
if(Hit.GetActor()->IsA(AUsableItem::StaticClass())) {
ThisItem=Cast<AUsableItem>(Hit.GetActor());
ThisVehicle=nullptr;
if((AFPSWeapon*)ThisItem != CurrentWeapon)
return ThisItem->FPSObjectType();
}
}
return NOTINTERACTIVE; //Cast<AActor>(Hit.GetActor());
}
void AFPSCharacter::Tick(float DeltaSeconds){
Super::Tick(DeltaSeconds);
if(ThePC->isVehicle) {
AnnounceItem=""; //if we are in a vehicle, we don't want messages popping up from this controller
} else {
if (Controller && Controller->IsLocalController()){
int GetUsableReturn = GetUsableInView();
if(GetUsableReturn == NOTINTERACTIVE) {
bHasNewFocus=true;
AnnounceItem="";
ThePC->CurrentVehicle=nullptr;
CurrentItem=nullptr;
} else {
// Handling Vehicle
if(GetUsableReturn == VEHICLE) {
AFPSWheeledVehicle* usable = ThisVehicle;
// End Focus
if (FocusedUsableVehicle != usable) {
if (FocusedUsableVehicle) {
AnnounceItem="";
//FocusedUsableVehicle->EndFocusItem();
}
bHasNewFocus = true;
}
// Assign new Focus
FocusedUsableVehicle = usable;
// Start Focus.
if (usable){
if (bHasNewFocus){
ThePC->CurrentVehicle=usable;
//usable->StartFocusItem();
AnnounceItem=usable->FPSPickupMessage();
bHasNewFocus = false;
}
}
} else {
//Handle Usable Item
AUsableItem* usable = ThisItem;
// End Focus
if (FocusedUsableItem != usable) {
if (FocusedUsableItem) {
AnnounceItem="";
//FocusedUsableVehicle->EndFocusItem();
}
bHasNewFocus = true;
}
// Assign new Focus
FocusedUsableItem = usable;
// Start Focus.
if (usable){
if (bHasNewFocus){
CurrentItem=usable;
//usable->StartFocusItem();
AnnounceItem=usable->FPSPickupMessage();
bHasNewFocus = false;
}
}
}
}
}
}
if(AnnounceItem != NULL) ThePC->SetHUDMessage("Press E to"+AnnounceItem,this->GetUniqueID());
else ThePC->ClearHUDMessage(this->GetUniqueID());
}
void AFPSCharacter::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 (GetCharacterMovement()->IsMovingOnGround() || GetCharacterMovement()->IsFalling() ) {
Rotation.Pitch = 0.0f;
}
// add movement in that direction
const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X);
AddMovementInput(Direction, Value);
CPPSpeed += Value;
if(CPPSpeed > 16.0f)CPPSpeed=16.0f;
if(CPPSpeed < -16.0f)CPPSpeed= -16.0f;
} else {
if(CPPSpeed != 0.0f) {
if(CPPSpeed < 0.0f) {
CPPSpeed += 1.0f;
} else {
CPPSpeed -= 1.0f;
}
}
}
}
void AFPSCharacter::MoveRight(float Value) {
if ( (Controller != NULL) && (Value != 0.0f) ) {
// find out which way is right
const FRotator Rotation = Controller->GetControlRotation();
const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
// add movement in that direction
AddMovementInput(Direction, Value);
CPPDirection += Value;
if(CPPDirection > 16.0f)CPPDirection=16.0f;
if(CPPDirection < -16.0f)CPPDirection= -16.0f;
} else {
if(CPPDirection != 0.0f) {
if(CPPDirection < 0.0f) {
CPPDirection += 1.0f;
} else {
CPPDirection -= 1.0f;
}
}
}
}
void AFPSCharacter::OnStartJump() {
bPressedJump = true;
}
void AFPSCharacter::OnStopJump() {
bPressedJump = false;
}
void AFPSCharacter::Use()
{
if(ThePC->CurrentVehicle != nullptr) {
ThePC->ChangePawn();
} else {
if (CurrentItem != nullptr){
switch(CurrentItem->FPSObjectType()) {
case PROJECTILEWEAPON:
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We picked up a projectile Weapon"));
CurrentWeapon=(AFPSWeapon*)CurrentItem;
CurrentWeapon->SetPhysicsOff();
//AFPSLevelScript* MyLS = Cast<AFPSLevelScript>(GetWorld()->GetLevelScriptActor());
//MyLS->FPSWeaponDataSave();
this->PickUpWeapon();
break;
case MISCELLANEOUSITEM:
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We picked up a Miscellaneous Item"));
break;
case MELEEWEAPON:
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We picked up a Melee weapon"));
break;
case TANK:
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We got in a Tank"));
break;
case FLYINGVEHICLE:
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We got in a flying vehicle"));
break;
case ELEVATOR:
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We started an elevator"));
break;
case DOOR:
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We opened a Door"));
break;
}
}
}
}
void AFPSCharacter::ThirdPerson() {
if(InThirdPerson) { //we are going to first person
if (GEngine) {
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Changing to First Person"));
}
InThirdPerson = false;
GetMesh()->SetOwnerNoSee(true);
FirstPersonMesh->SetOwnerNoSee(false);
// Position the camera a bit above the eyes
FirstPersonCameraComponent->RelativeLocation = FVector(0, 0, 50.0f + BaseEyeHeight);
} else { // we are going to third person
if (GEngine) {
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Changing to Third Person"));
}
InThirdPerson = true;
GetMesh()->SetOwnerNoSee(false);
FirstPersonMesh->SetOwnerNoSee(true); // only the owning player will see this mesh
// Position the camera a bit above the eyes
FirstPersonCameraComponent->RelativeLocation = FVector(-100.0f, 40.0f, 30.0f + BaseEyeHeight);
}
}
void AFPSCharacter::MenuPressed() {
if(MenuOpen) {
MenuOpen = false;
if (GEngine) {
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Closing Menu"));
}
} else {
MenuOpen = true;
if (GEngine) {
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Opening Menu"));
}
}
}
void AFPSCharacter::Reload() {
if (GEngine) {
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Reloading"));
}
WeaponReady = false; //can't shoot during reload
}
void AFPSCharacter::OnFire() {
if(CurrentWeapon != NULL) {
CurrentWeapon->Fire();
}
}
void AFPSCharacter::Exit() {
GetWorld()->GetFirstPlayerController()->ConsoleCommand("quit");
}