Declaring and implementing new functions results in unresolved external symbol

I’m trying to extend the unreal learn shooter project, and I’m trying to add a teleport function to Character.h/.cpp and CharacterMovementComponent.h/.cpp .

I’ve replicated the functions used for jumping and adapted them to my purpose, but even declaring and implementing everything it still results in multiple unresolved symbol errors.

Here what I copied and modified in Character.h and .cpp. The jump related stuff is the original, and the teleport related stuff is my work.

character.h:

class ENGINE_API ACharacter : public APawn
{
...
	UPROPERTY(BlueprintReadOnly, Category=Character)
	uint32 bPressedJump:1;
	
	UPROPERTY(BlueprintReadOnly, Category=Character)
	uint32 bPressedTeleport:1;
...
	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Transient, Category=Character)
	uint32 bWasJumping : 1;

	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Transient, Category=Character)
	uint32 bWasTeleporting : 1;

	UPROPERTY(Transient, BlueprintReadOnly, VisibleInstanceOnly, Category=Character)
	float JumpKeyHoldTime;

	UPROPERTY(Transient, BlueprintReadOnly, VisibleInstanceOnly, Category=Character)
	float JumpForceTimeRemaining;

	UPROPERTY(Transient, BlueprintReadOnly, VisibleInstanceOnly, Category=Character)
	float TeleportSinceActivationTime;
...
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category=Character, Meta=(ClampMin=0.0, UIMin=0.0))
	float JumpMaxHoldTime;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category=Character, Meta=(ClampMin=0.0, UIMin=0.0))
	float TeleportCooldown;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category=Character)
    int32 JumpMaxCount;

    UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category=Character)
    int32 JumpCurrentCount;


    UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Character)
	int32 JumpCurrentCountPreJump;
...
	UFUNCTION(BlueprintCallable, Category=Character)
	virtual void Jump();

	UFUNCTION(BlueprintCallable, Category=Character)
	virtual void Teleport();
...
	UFUNCTION(BlueprintCallable, Category=Character)
	virtual void StopJumping();

	UFUNCTION(BlueprintCallable, Category=Character)
	virtual void StopTeleporting();
...
	UFUNCTION(BlueprintCallable, Category=Character)
	bool CanJump() const;
	
	UFUNCTION(BlueprintCallable, Category=Character)
	bool CanTeleport() const;
...
	UFUNCTION(BlueprintNativeEvent, Category=Character, meta=(DisplayName="CanJump"))
	bool CanJumpInternal() const;
	UFUNCTION(BlueprintNativeEvent, Category=Character, meta=(DisplayName="CanTeleport"))
	bool CanTeleportInternal() const;
	virtual bool CanJumpInternal_Implementation() const;
	virtual bool  CanTeleportInternal_Implementation() const;
...
	virtual void ResetJumpState();
	virtual void ResetTeleportState();
...
	UFUNCTION(BlueprintNativeEvent, Category=Character)
	void OnJumped();
	virtual void OnJumped_Implementation();

and character.cpp

bool ACharacter::CanJump() const
{
	return CanJumpInternal();
}

bool ACharacter::CanTeleport() const
{
	return CanTeleportInternal();
}

bool ACharacter::CanJumpInternal_Implementation() const
{
	// Ensure the character isn't currently crouched.
	bool bCanJump = !bIsCrouched;

	// Ensure that the CharacterMovement state is valid
	bCanJump &= CharacterMovement->CanAttemptJump();

	if (bCanJump)
	{
		// Ensure JumpHoldTime and JumpCount are valid.
		if (!bWasJumping || GetJumpMaxHoldTime() <= 0.0f)
		{
			if (JumpCurrentCount == 0 && CharacterMovement->IsFalling())
			{
				bCanJump = JumpCurrentCount + 1 < JumpMaxCount;
			}
			else
			{
				bCanJump = JumpCurrentCount < JumpMaxCount;
			}
		}
		else
		{
			// Only consider JumpKeyHoldTime as long as:
			// A) The jump limit hasn't been met OR
			// B) The jump limit has been met AND we were already jumping
			const bool bJumpKeyHeld = (bPressedJump && JumpKeyHoldTime < GetJumpMaxHoldTime());
			bCanJump = bJumpKeyHeld &&
						((JumpCurrentCount < JumpMaxCount) || (bWasJumping && JumpCurrentCount == JumpMaxCount));
		}
	}

	return bCanJump;
}

bool ACharacter::CanTeleportInternal_Implementation() const
{
	// Ensure the character isn't currently crouched.
	bool bCanTeleport = !bIsCrouched;

	// Ensure that the CharacterMovement state is valid
	bCanTeleport &= CharacterMovement->CanAttemptTeleport();
	
	bCanTeleport &= !bWasTeleporting && TeleportSinceActivationTime >= GetTeleportCooldown();

	return bCanTeleport;
}


void ACharacter::ResetJumpState()
{
	bPressedJump = false;
	bWasJumping = false;
	JumpKeyHoldTime = 0.0f;
	JumpForceTimeRemaining = 0.0f;

	if (CharacterMovement && !CharacterMovement->IsFalling())
	{
		JumpCurrentCount = 0;
		JumpCurrentCountPreJump = 0;
	}
}

void ACharacter::ResetTeleportState()
{
	bPressedTeleport = false;
	bWasTeleporting = false;

}

void ACharacter::OnJumped_Implementation()
{
}
...

void ACharacter::Jump()
{
	bPressedJump = true;
	JumpKeyHoldTime = 0.0f;
}
void ACharacter::Teleport()
{
	bPressedTeleport = true;
	TeleportSinceActivationTime = 0.0f;
}

void ACharacter::StopJumping()
{
	bPressedJump = false;
	ResetJumpState();
}
void ACharacter::StopTeleporting()
{
	bPressedTeleport = false;
	ResetTeleportState();
}

void ACharacter::CheckJumpInput(float DeltaTime)
{
	JumpCurrentCountPreJump = JumpCurrentCount;

	if (CharacterMovement)
	{
		if (bPressedJump)
		{
			// If this is the first jump and we're already falling,
			// then increment the JumpCount to compensate.
			const bool bFirstJump = JumpCurrentCount == 0;
			if (bFirstJump && CharacterMovement->IsFalling())
			{
				JumpCurrentCount++;
			}

			const bool bDidJump = CanJump() && CharacterMovement->DoJump(bClientUpdating);
			if (bDidJump)
			{
				// Transition from not (actively) jumping to jumping.
				if (!bWasJumping)
				{
					JumpCurrentCount++;
					JumpForceTimeRemaining = GetJumpMaxHoldTime();
					OnJumped();
				}
			}

			bWasJumping = bDidJump;
		}
	}
}

void ACharacter::CheckTeleportInput(float DeltaTime)
{
	JumpCurrentCountPreJump = JumpCurrentCount;

	if (CharacterMovement)
	{
		if (bPressedTeleport)
		{

			const bool bDidTeleport = CanTeleport() && CharacterMovement->DoTeleport(bClientUpdating);

			bWasTeleporting = bDidTeleport;
		}
	}
}

void ACharacter::ClearJumpInput(float DeltaTime)
{
	if (bPressedJump)
	{
		JumpKeyHoldTime += DeltaTime;

		// Don't disable bPressedJump right away if it's still held.
		// Don't modify JumpForceTimeRemaining because a frame of update may be remaining.
		if (JumpKeyHoldTime >= GetJumpMaxHoldTime())
		{
			bPressedJump = false;
		}
	}
	else
	{
		JumpForceTimeRemaining = 0.0f;
		bWasJumping = false;
	}
}

void ACharacter::ClearTeleportInput(float DeltaTime)
{
	TeleportSinceActivationTime+=DeltaTime;

	if(TeleportSinceActivationTime>=GetTeleportCooldown() && !bPressedTeleport)
	{
		bWasTeleporting = false;
	}
}

float ACharacter::GetJumpMaxHoldTime() const
{
	return JumpMaxHoldTime;
}

float ACharacter::GetTeleportCooldown() const
{
	return TeleportCooldown;
}

Hi,

Can you provide the exact error messages you are receiving? This will help with the debugging process.

Are you compiling the Engine from source? If not, then you wont be able to alter the base ACharacter class. (I think you’re trying to modify the base class, due to the ENGINE_API macro usage)

I’d recommend you create your own extension of ACharacter, thus inheriting all of that functionality and implementing your teleport method. Something like:

class YOURPROJECT_API AYourCharacter : public ACharacter
{
...
UFUNCTION( BlueprintCallable, Category=Character )
     virtual void Teleport();
...
};

and

void AYourCharacter::Teleport()
 {
     bPressedTeleport = true;
     TeleportSinceActivationTime = 0.0f;
 }
// Do more Teleport stuff

Also, if any of the other functions like Jump() already exist, then you’ll need the override keyword after your declaration.