No moving with the arrow keys. Only shooting.

Hi all!

I am working on a small prototype for a school project. Its a top-down shooter and my goal is to have the player use WASD to move and the arrow keys to shoot. I have managed to get the projectile spawn working, but I have run up against two issues. First, the arrow keys move the player character. I want this to not happen. I have tried several things, including implementing my own movement logic, to no avail. Is there an option in the editor I missed? Or, what is the best way to achieve this?

Second, when the character shoots, they are ‘teleported’ a short distance. Here is a short video of what I’m talking about. I would like this to also not happen. I suspect the two issues are related because, when I disable the shoot function, the teleport stops happening, but I can’t see anything in my code that would cause it. See below:

If someone could point me in the right direction on this, I would appreciate it!
Thanks

Hey!

Unfortunately the amount of information you have provided is insufficient to be able to accurately help you.

  • Which engine version are you using?

  • Are you using the new input system? (Enhanced Input, default since Unreal Engine 5.x, unsure if it was 5.0 or 5.1)

  • Could you show us the code snippet where you are binding your input? Check your Character and/or Player Controller class.

1 Like

Hey, thanks for the reply!

To answer your questions, I am using v 5.1. I had been using enhanced input for movement, but not for shooting. I have corrected this. Here are the bindings I’m currently using
binding

The enhanced input wound up being the key to making the arrow keys function the way I want. I hadn’t removed the mapping in the IMC. I feel a bit silly, but there it is.

But I am still having the problem with the teleporting. It got a little funny, though. I suspect that spawning the projectile is forcing the character to move. I’m going to try and offset the spawn location and see if that helps. And also add a timer to limit the fire rate.

So thank you for pointing me towards the enhanced inputs, that was a big help.

Hey @Metalclayman

here is an example in which you can run around with WASD and shoot with the arrow keys. The controls are inside the TopdownController.

UE51_CMovement.zip (67.0 KB)

TopdownGamemode.h:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/StaticMeshComponent.h"
#include "TopdownProjectile.generated.h"

class USphereComponent;
class UProjectileMovementComponent;

UCLASS(config = Game)
class CMOVEMENT_API ATopdownProjectile : public AActor
{
	GENERATED_BODY()

	/** Sphere collision component */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Projectile, meta = (AllowPrivateAccess = "true"))
	USphereComponent* CollisionComp;

	/** Projectile movement component */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
	UProjectileMovementComponent* ProjectileMovement;
	
public:	
	// Sets default values for this actor's properties
	ATopdownProjectile();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
	UStaticMeshComponent* MeshComponent;

	UFUNCTION()
	void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);

	/** Returns CollisionComp subobject **/
	USphereComponent* GetCollisionComp() const { return CollisionComp; }
	/** Returns ProjectileMovement subobject **/
	UProjectileMovementComponent* GetProjectileMovement() const { return ProjectileMovement; }


};

TopdownGamemode.cpp:

#include "TopdownGamemode.h"
#include "TopdownController.h"
#include "TopdownCharacter.h"
#include "UObject/ConstructorHelpers.h"

ATopdownGamemode::ATopdownGamemode()
{
	// use our custom PlayerController class
	PlayerControllerClass = ATopdownController::StaticClass();

	// set default pawn class to our Blueprinted character
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/CMovement/Blueprints/BP_TopdownCharacter"));
	if (PlayerPawnBPClass.Class != nullptr)
	{
		DefaultPawnClass = PlayerPawnBPClass.Class;
	}

	// set default controller to our Blueprinted controller
	static ConstructorHelpers::FClassFinder<APlayerController> PlayerControllerBPClass(TEXT("/Game/CMovement/Blueprints/BP_TopdownController"));
	if (PlayerControllerBPClass.Class != NULL)
	{
		PlayerControllerClass = PlayerControllerBPClass.Class;
	}
}

TopdownController.h

#pragma once

#include "CoreMinimal.h"
#include "Templates/SubclassOf.h"
#include "GameFramework/PlayerController.h"
#include "InputActionValue.h"
#include "TopdownProjectile.h"
#include "TopdownController.generated.h"


/**
 * 
 */
UCLASS()
class CMOVEMENT_API ATopdownController : public APlayerController
{
	GENERATED_BODY()


public:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
		class UInputMappingContext* DefaultMappingContext;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
		class UInputAction* MoveUpAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
		class UInputAction* MoveRightAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
		class UInputAction* ShootUpAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
		class UInputAction* ShootRightAction;

	UPROPERTY(EditDefaultsOnly, Category = Projectile)
		TSubclassOf<class ATopdownProjectile> ProjectileClass;


protected:
	virtual void SetupInputComponent() override;
	virtual void BeginPlay();
	void MoveUp(const FInputActionValue& ActionValue);
	void MoveRight(const FInputActionValue& ActionValue);
	void ShootUp(const FInputActionValue& ActionValue);
	void ShootRight(const FInputActionValue& ActionValue);
};

TopdownController.cpp

#include "TopdownController.h"
#include "GameFramework/Pawn.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "Engine/World.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"



void ATopdownController::BeginPlay()
{
	// Call the base class  
	Super::BeginPlay();

	//Add Input Mapping Context

	if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
	{
		Subsystem->AddMappingContext(DefaultMappingContext, 0);
	}

}

void ATopdownController::SetupInputComponent()
{
	// set up gameplay key bindings
	Super::SetupInputComponent();

	// Set up action bindings
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent))
	{
		// Setup keyboard input events
		EnhancedInputComponent->BindAction(MoveUpAction, ETriggerEvent::Triggered, this, &ATopdownController::MoveUp);
		EnhancedInputComponent->BindAction(MoveRightAction, ETriggerEvent::Triggered, this, &ATopdownController::MoveRight);
		EnhancedInputComponent->BindAction(ShootUpAction, ETriggerEvent::Started, this, &ATopdownController::ShootUp);
		EnhancedInputComponent->BindAction(ShootRightAction, ETriggerEvent::Started, this, &ATopdownController::ShootRight);


	}
}

void ATopdownController::MoveUp(const FInputActionValue& ActionValue)
{
	float Scale = ActionValue.Get<float>();
	FVector WorldX = FVector(1,0,0);

	APawn* ControlledPawn = GetPawn();
	if (ControlledPawn != nullptr)
	{
		ControlledPawn->AddMovementInput(WorldX, Scale, false);
	}
}

void ATopdownController::MoveRight(const FInputActionValue& ActionValue)
{
	float Scale = ActionValue.Get<float>();
	FVector WorldY = FVector(0, 1, 0);

	APawn* ControlledPawn = GetPawn();
	if (ControlledPawn != nullptr)
	{
		ControlledPawn->AddMovementInput(WorldY, Scale, false);
	}
}

void ATopdownController::ShootUp(const FInputActionValue& ActionValue)
{
	float Scale = ActionValue.Get<float>();
	FVector WorldX = FVector(1*Scale,0, 0);
	APawn* ControlledPawn = GetPawn();
	if (ControlledPawn != nullptr)
	{
		if (ProjectileClass != nullptr)
		{
			UWorld* const World = GetWorld();
			if (World != nullptr)
			{				
				FVector SLocation = ControlledPawn->GetActorLocation();
				FRotator SRotation = WorldX.Rotation();

				//Set Spawn Collision Handling Override
				FActorSpawnParameters ActorSpawnParams;
				ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;
				ActorSpawnParams.Owner = ControlledPawn;

				// Spawn the projectile at the muzzle
				World->SpawnActor<ATopdownProjectile>(ProjectileClass, SLocation, SRotation, ActorSpawnParams);
			}
		}
	}
}

void ATopdownController::ShootRight(const FInputActionValue& ActionValue)
{
	float Scale = ActionValue.Get<float>();
	FVector WorldY = FVector(0,1 * Scale, 0);
	APawn* ControlledPawn = GetPawn();
	if (ControlledPawn != nullptr)
	{
		if (ProjectileClass != nullptr)
		{
			UWorld* const World = GetWorld();
			if (World != nullptr)
			{
				FVector SLocation = ControlledPawn->GetActorLocation();
				FRotator SRotation = WorldY.Rotation();

				//Set Spawn Collision Handling Override
				FActorSpawnParameters ActorSpawnParams;
				ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;
				ActorSpawnParams.Owner = ControlledPawn;

				// Spawn the projectile at the muzzle
				World->SpawnActor<ATopdownProjectile>(ProjectileClass, SLocation, SRotation, ActorSpawnParams);
			}
		}
	}
}

TopdownCharacter.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "TopdownCharacter.generated.h"

UCLASS()
class CMOVEMENT_API ATopdownCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	ATopdownCharacter();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

};

TopdownCharacter.cpp

#include "TopdownCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"

// Sets default values
ATopdownCharacter::ATopdownCharacter()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;


	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(0.f, 640.f, 0.f);
	GetCharacterMovement()->bUseControllerDesiredRotation = false;
}

// Called when the game starts or when spawned
void ATopdownCharacter::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void ATopdownCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
void ATopdownCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}

TopdownProjectile.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/StaticMeshComponent.h"
#include "TopdownProjectile.generated.h"

class USphereComponent;
class UProjectileMovementComponent;

UCLASS(config = Game)
class CMOVEMENT_API ATopdownProjectile : public AActor
{
	GENERATED_BODY()

	/** Sphere collision component */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Projectile, meta = (AllowPrivateAccess = "true"))
	USphereComponent* CollisionComp;

	/** Projectile movement component */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
	UProjectileMovementComponent* ProjectileMovement;
	
public:	
	// Sets default values for this actor's properties
	ATopdownProjectile();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
	UStaticMeshComponent* MeshComponent;

	UFUNCTION()
	void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);

	/** Returns CollisionComp subobject **/
	USphereComponent* GetCollisionComp() const { return CollisionComp; }
	/** Returns ProjectileMovement subobject **/
	UProjectileMovementComponent* GetProjectileMovement() const { return ProjectileMovement; }


};

TopdownProjectile.cpp

#include "TopdownProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"



// Sets default values
ATopdownProjectile::ATopdownProjectile()
{
 	// Use a sphere as a simple collision representation
	CollisionComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp"));
	CollisionComp->InitSphereRadius(5.0f);
	CollisionComp->BodyInstance.SetCollisionProfileName("Projectile");
	CollisionComp->OnComponentHit.AddDynamic(this, &ATopdownProjectile::OnHit);		// set up a notification for when this component hits something blocking

	// Players can't walk on it
	CollisionComp->SetWalkableSlopeOverride(FWalkableSlopeOverride(WalkableSlope_Unwalkable, 0.f));
	CollisionComp->CanCharacterStepUpOn = ECB_No;

	// Set as root component
	RootComponent = CollisionComp;

	// Use a ProjectileMovementComponent to govern this projectile's movement
	ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileComp"));
	ProjectileMovement->UpdatedComponent = CollisionComp;
	ProjectileMovement->InitialSpeed = 3000.f;
	ProjectileMovement->MaxSpeed = 3000.f;
	ProjectileMovement->bRotationFollowsVelocity = true;
	ProjectileMovement->bShouldBounce = true;


	MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMesh"));
	MeshComponent->SetupAttachment(RootComponent);
	static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereMeshAsset(TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"));
	if (SphereMeshAsset.Succeeded()) {
		MeshComponent->SetStaticMesh(SphereMeshAsset.Object);
		MeshComponent->SetCollisionProfileName(TEXT("OverlapAll"));
		MeshComponent->SetWorldScale3D(FVector(0.25, 0.25, 0.25));
	}
	


	// Die after 3 seconds by default
	InitialLifeSpan = 3.0f;

}


void ATopdownProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	// Only add impulse and destroy projectile if we hit a physics
	if ((OtherActor != nullptr) && (OtherActor != this) && (OtherActor != GetOwner()) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics())
	{
		OtherComp->AddImpulseAtLocation(GetVelocity() * 100.0f, GetActorLocation());

		Destroy();
	}
}
1 Like

@Metalclayman, I am glad it helped! :slightly_smiling_face:

About the projectile forcing the character to move problem, you are definitely thinking in the right direction. Off the top of my head, I think that upon spawning, the projectile collides with the character and applies an impulse to it, thus pushing it.

Also recommend having a look at this: How to prevent a projectile being blocked by the instigator - Development / World Creation - Epic Developer Community Forums (unrealengine.com)

1 Like

@L1z4rD89 Thank you, this is great!

@8Zerocool8 Thanks for the link. That seems like a much better solution.

2 Likes