mine 4.18 tanks vs zombies work just fine!
/// Tank.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GameFramework/Pawn.h"
#include "Tank.generated.h"
// This struct covers all possible tank input schemes
// What the inputs do can vary by tank
// but the scheme inputs will always exist.
USTRUCT(BlueprintType)
struct FTankInput
{
GENERATED_USTRUCT_BODY()
/** Sanitized movement input, usable for game logic */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "TankInput")
FVector2D MovementInput;
/** shows whether fire1 button is pressed or not */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "TankInput")
uint32 bFire1 : 1;
/** shows whether fire2 button is pressed or not */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "TankInput")
uint32 bFire2 : 1;
/** makes correct movement input from all received raw movement input
* for example you can have 2 buttons e.g. "W" and "UpArraow" to move tank up
* for user convenience
* and if user press this two buttons at the same time we will have AxisValue = 2.f
* per frame and we will have double speed . . . so anyway we must be able to have
* AxisValue = 1.f maximum per frame.
*/
void Sanitize()
{
// FVector2D::ClampAxes
// Creates a copy of this vector with both axes clamped to the given range.
// New vector with clamped axes.
MovementInput = RawMovementInput.ClampAxes(-1.f, 1.f);
// FVector2D::GetSafeNormal
// Gets a normalized copy of the vector, checking it is safe to do so based on the length.
// Returns zero vector if vector length is too small to safely normalize.
// A normalized copy of the vector if safe, (0, 0) otherwise
MovementInput = MovementInput.GetSafeNormal();
// Set the values of the vector directly
RawMovementInput.Set(0.f, 0.f);
}
/** movement */
void MoveX(float AxisValue) { RawMovementInput.X += AxisValue; }
void MoveY(float AxisValue) { RawMovementInput.Y += AxisValue; }
/** fire */
void Fire1(bool bPressed) { bFire1 = bPressed; }
void Fire2(bool bPressed) { bFire2 = bPressed; }
private:
// Private because it's internal. Game code should never see this
FVector2D RawMovementInput;
};
UCLASS()
class TANKS_API ATank : public APawn
{
GENERATED_BODY()
// Helpful debug tool - which way is the tank facing
// @API A simple arrow rendered using lines. Useful for indicating which way an object is facing.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tank", meta = (AllowPrivateAccess = "true"))
class UArrowComponent* TankDirection;
// Sprite for the tank body
// @API A component that handles rendering and collision for a single instance of a UPaperSprite asset.
// This component is created when you drag a sprite asset from the content browser into a Blueprint,
// or contained inside of the actor created when you drag one into the level.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tank", meta = (AllowPrivateAccess = "true"))
class UPaperSpriteComponent* TankSprite;
// The Actor used as the turret
// @API A component that spawns an Actor when registered, and destroys it when unregistered.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tank", meta = (AllowPrivateAccess = "true"))
class UChildActorComponent* ChildTurret;
// Camera Boom
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tank", meta = (AllowPrivateAccess = "true"))
class USpringArmComponent* SpringArm;
// Our in-game camera
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tank", meta = (AllowPrivateAccess = "true"))
class UCameraComponent* TankCamera;
public:
// Sets default values for this pawn's properties
ATank();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
UFUNCTION(BlueprintCallable, Category = "Tank")
const FTankInput& GetCurrentInput() { return TankInput; }
protected:
/** Our Input Structure */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "TankInput")
FTankInput TankInput;
/** Maximum Turn Rate (degrees/second) of the tank */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tank", meta = (ClampMin = "0.0"))
float YawSpeed = 180.f;
/** Maximum Movement Rate (units/seconds) of the tank */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tank", meta = (ClampMin = "0.0"))
float MoveSpeed = 100.f;
/** movement */
void MoveX(float AxisValue);
void MoveY(float AxisValue);
/** fire */
void Fire1Pressed();
void Fire1Released();
void Fire2Pressed();
void Fire2Released();
};
/// Tank.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Tank.h"
#include "Tanks.h"
#include "PaperSpriteComponent.h"
#include "TankStatics.h"
#include "Components/ArrowComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "ConstructorHelpers.h"
// Sets default values
ATank::ATank()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
if (!RootComponent)
{
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("TankBase"));
}
TankDirection = CreateDefaultSubobject<UArrowComponent>(TEXT("TankDirection"));
TankDirection->SetupAttachment(RootComponent);
TankSprite = CreateDefaultSubobject<UPaperSpriteComponent>(TEXT("TankSprite"));
TankSprite->SetupAttachment(TankDirection);
TankSprite->SetRelativeRotation(FRotator(0.f, 90.0f, -90.0f)); // PYR
TankSprite->SetRelativeLocation(FVector(0.0f, 0.0f, 1.f));
ChildTurret = CreateDefaultSubobject<UChildActorComponent>(TEXT("Turret"));
ChildTurret->SetupAttachment(TankDirection);
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->TargetArmLength = 500.f;
SpringArm->bEnableCameraLag = true;
SpringArm->bEnableCameraRotationLag = false;
SpringArm->bUsePawnControlRotation = false;
SpringArm->CameraLagSpeed = 2.f;
SpringArm->bDoCollisionTest = false;
SpringArm->SetWorldRotation(FRotator(-90.f, 0.0f, 0.0f));
TankCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("TankCamera"));
TankCamera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
TankCamera->bUsePawnControlRotation = false;
// flat view
TankCamera->ProjectionMode = ECameraProjectionMode::Orthographic;
TankCamera->OrthoWidth = 2048.f; // 1024.f by default
TankCamera->AspectRatio = 3.f / 4.f;
TankCamera->SetWorldRotation(FRotator(-90.f, 0.0f, 0.0f));
}
// Called when the game starts or when spawned
void ATank::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ATank::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
TankInput.Sanitize();
// Move the tank this frame
{
FVector DesiredMovementDirection = FVector(TankInput.MovementInput.X, TankInput.MovementInput.Y, 0.f);
if (!DesiredMovementDirection.IsNearlyZero())
{
// Rotate The tank! Note that we rotate the TankDirection component, not the RootComponent !
FRotator MovementAngle = DesiredMovementDirection.Rotation();
float DeltaYaw = UTankStatics::FindDeltaAngleDegrees(TankDirection->GetComponentRotation().Yaw, MovementAngle.Yaw);
bool bReverse = false;
if (DeltaYaw != 0.0f)
{
float AdjustedDeltaYaw = DeltaYaw;
if (AdjustedDeltaYaw < -90.f)
{
AdjustedDeltaYaw += 180.f;
bReverse = true;
}
else if (AdjustedDeltaYaw > 90.f)
{
AdjustedDeltaYaw -= 180.f;
bReverse = true;
}
// Turn toward the desired angle. Stop if we can get there in one frame
float MaxYawThisFrame = YawSpeed * DeltaTime;
if (MaxYawThisFrame >= FMath::Abs(AdjustedDeltaYaw))
{
if (bReverse)
{
// move backward
FRotator FacingAngle = MovementAngle;
FacingAngle.Yaw = MovementAngle.Yaw + 180.f;
TankDirection->SetWorldRotation(FacingAngle);
}
else
{
TankDirection->SetWorldRotation(MovementAngle);
}
}
else
{
// Can't reach our desired angle this frame, rotate part way
TankDirection->AddLocalRotation(FRotator(0.f, FMath::Sign(AdjustedDeltaYaw) * MaxYawThisFrame, 0.f));
}
}
// Move the tank
{
FVector MovementDirection = TankDirection->GetForwardVector() * (bReverse ? -1.f : 1.f);
FVector TankLocation = GetActorLocation();
TankLocation.X += MovementDirection.X * MoveSpeed * DeltaTime;
TankLocation.Y += MovementDirection.Y * MoveSpeed * DeltaTime;
SetActorLocation(TankLocation);
}
}
}
}
// Called to bind functionality to input
void ATank::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
InputComponent->BindAxis("MoveY", this, &ATank::MoveY);
InputComponent->BindAxis("MoveX", this, &ATank::MoveX);
InputComponent->BindAction("Fire1", IE_Pressed, this, &ATank::Fire1Pressed);
InputComponent->BindAction("Fire1", IE_Released, this, &ATank::Fire1Released);
InputComponent->BindAction("Fire2", IE_Pressed, this, &ATank::Fire2Pressed);
InputComponent->BindAction("Fire2", IE_Released, this, &ATank::Fire2Released);
}
void ATank::MoveX(float AxisValue)
{
TankInput.MoveX(AxisValue);
}
void ATank::MoveY(float AxisValue)
{
TankInput.MoveY(AxisValue);
}
void ATank::Fire1Pressed()
{
TankInput.Fire1(true);
}
void ATank::Fire1Released()
{
TankInput.Fire1(false);
}
void ATank::Fire2Pressed()
{
TankInput.Fire2(true);
}
void ATank::Fire2Released()
{
TankInput.Fire2(false);
}