You are on page 1of 27

SHOOTER TOP-DOWN

Creamos un proxecto novo, baleiro, non precisamos contido inicial.


Podemos borrar os elementos do Viewport, deixando unicamente o chan e unha luz.
Como queremos un xogo top-down, vamos a colocar unha cámara que nos permita esa
vista. Arrastramos o obxecto Camera ao Viewport e colocámolo na posición 0-0-1000.
Poñemos a -90 a rotación no eixo Y para que a cámara mire ao chan.
Coa cámara seleccionada abrimos o BP do nivel e desde o evento de inicio do xogo
asociamos o nodo Set View Target with Blend ao que enlazamos o Player Controller no
target e unha referencia á camara no novo target. Deste xeito asegurámonos de que
non se modifique a posición da cámara.

Para controlar o comportamento da nosa nave espacial crearemos unha clase de C++
de tipo Pawn á que chamaremos SpaceShipController. Ao final do proceso abriranse
dous arquivos: .h (header) é a interfaz e .cpp a implementación dos métodos que se
chaman desde a interfaz.
Arrastramos a clase desde o Content Browser ao Viewport e asociámoslle unha
compoñente cono para poder velo. Axustamos a posición, rotación (Y=-90) e cor do
elemento.

No input das preferencias do proxecto definimos dúas Axis Mapping: MoveY (coas
frechas esquerda e dereita) e MoveX (coas teclas arriba e abaixo). Os valores de
esquerda e abaixo deben ser -1.0.

No arquivo de interfaz do controlador engadimos:

UPROPERTY (EditAnywhere)
UShapeComponent* CollisionBox;

UPROPERTY (EditAnywhere)
float Speed;

void Move_XAxis(float AxisValue);


void Move_YAxis(float AxisValue);

FVector CurrentVelocity;

Velocity vai ter máis información que a velocidade, sentido, orientación etc.
No arquivo de implementación, engadimos:

1
#include "Components/BoxComponent.h"
#include "Components/InputComponent.h"
#include "Engine/World.h"

E despois do construtor ASpaceShipController engadimos:

CollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));

AutoPossessPlayer = EAutoReceiveInput::Player0;

Speed = 10.0f;

Definimos a caixa de colisión da nosa nave como un subobxecto da caixa de colisión


básica e facemos que todos os inputs do xogo sexan xestionados polo noso
controlador da nave.
E na función Tick vamos calcular a nova posición da nave cando se move:

if (!CurrentVelocity.IsZero()){
FVector NewLocation = GetActorLocation() + Speed *
CurrentVelocity * DeltaTime;

SetActorLocation(NewLocation);
}

Por último, no SetupPlayerInputComponent engadimos:


PlayerInputComponent->BindAxis("MoveX", this, &
ASpaceShipController::Move_XAxis);

PlayerInputComponent->BindAxis("MoveY", this, &


ASpaceShipController::Move_YAxis);

Agora temos que programar eses dous métodos que acabamos de enlazar coas
funcións de entrada de teclado:

void ASpaceShipController::Move_XAxis(float AxisValue){


CurrentVelocity.X = AxisValue * 100.0f;
}

void ASpaceShipController::Move_YAxis(float AxisValue){


CurrentVelocity.Y = AxisValue * 100.0f;
}

Agora vamos a definir as balas e para iso crearemos unha clase de C++ de tipo Actor á
que chamaremos BulletController. Arrastramos a clase desde o Content Browser ao
Viewport e asociámoslle unha compoñente esfera para poder vela. Axustamos a
posición, tamaño e cor do elemento.

Vamos facer que a bala se mova cara arriba un pouco en cada Tick, para iso, no arquivo
de interfaz do controlador (.h) engadimos:

2
UPROPERTY(EditAnywhere)
UShapeComponent* RootBox;

UPROPERTY(EditAnywhere)
float Speed;
E despois no construtor (.cpp) engadimos dentro do ABulletController:

Speed = 300.0f;
PrimaryActorTick.bCanEverTick = true;
RootBox = CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));

Así asignamos á variable RootBox a caixa por defecto que englobará a nosa bala, e
que nos servirá despois para saber se choca con algo.

Para a creación do movemento collemos a posición actual da bala en función do


DeltaTime e variamos a posición, e cando esta posición saia da área visible,
destruímola (así evitamos sobrecargar a escena de elementos).
Así, dentro da función Tick engadimos:

void ABulletController::Tick(float DeltaTime)


{
Super::Tick(DeltaTime);

FVector NewLocation = GetActorLocation();


NewLocation.X = NewLocation.X + Speed*DeltaTime;
SetActorLocation(NewLocation);

if (NewLocation.X > 700.0f) {


this->Destroy();
}
}

Vamos a convertir a bala nun Blueprint, porque o que queremos é que apareza ao
pulsar a tecla espaciadora. Asegurámonos de que está colocada no 0,0,0 e eliminamos
a bala do editor.
Na sección Input das Project Settings incluímos unha nova Action Mapping de nome
Shoot asociada á barra espaciadora.
Agora programaremos a acción no SpaceShipController. No arquivo de cabeceira
incluímos:

UPROPERTY (EditAnywhere, Category="Spawing")


TSubclassOf<class ABulletController>BulletBlueprint;

void OnShootPress();

E no arquivo cpp, dentro da función SetupPlayerInputComponent:

PlayerInputComponent->BindAction("Shoot", IE_Pressed, this,


&ASpaceShipController::OnShootPress);

E despois definimos a función OnShootPress. Creamos unha referencia á escena, e se


esa referencia á válida, xeramos a bala na posición do actor (da nosa nave).

void ASpaceShipController::OnShootPress(){
UWorld * World = GetWorld(); //referencia á escena

3
if (World) {
FVector Location = GetActorLocation();
World->SpawnActor<ABulletController>(BulletBlueprint,
Location, FRotator::ZeroRotator);
}
}
Estamos facendo unha referencia ao controlador das balas, polo tanto teremos que
incluír ao comezo do arquivo:

#include "BulletController.h"

Nos Detalles do SpaceShipController asociaremos o BulletBP na categoría “Spawning”.

Agora vamos a crear o inimigo, e para iso creamos unha nova clase de C++ de tipo
Actor chamada Enemy. O inimigo aparecerá na parte superior da pantalla e irá
baixando, definimos a súa velocidade e crearemos unha caixa de colisión. No arquivo
de cabeceira engadimos:
UPROPERTY(EditAnywhere)
UShapeComponent * RootBox;

UPROPERTY(EditAnywhere)
float Speed;

E no construtor, dentro do Controlador :

Speed = -150.0f;
PrimaryActorTick.bCanEverTick = true;
RootBox = CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));

E dentro do Tick:

FVector NewLocation = GetActorLocation();


NewLocation.X += Speed * DeltaTime;
SetActorLocation(NewLocation);

if (NewLocation.X < -600.0f) {


this->Destroy();
}

Despois de agregar o include de compoñentes, compilamos, arrastramos o controlador


do inimigo na pantalla e engadímoslle unha compoñente para poder velo (por exemplo
un cubo).

Enemy.h:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Enemy.generated.h"

4
UCLASS()
class SHOOTERC_API AEnemy : public AActor
{
GENERATED_BODY()

protected:
virtual void BeginPlay() override;

public:
AEnemy();
virtual void Tick(float DeltaTime) override;

UPROPERTY(EditAnywhere)
UShapeComponent* RootBox;

UPROPERTY(EditAnywhere)
float Speed;
};

Enemy.cpp
#include "Enemy.h"
#include "Components/BoxComponent.h"

AEnemy::AEnemy()
{
PrimaryActorTick.bCanEverTick = true;
Speed = -150.0f;
RootBox =
CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));
}

void AEnemy::BeginPlay()
{
Super::BeginPlay();

void AEnemy::Tick(float DeltaTime)


{
Super::Tick(DeltaTime);

FVector NewLocation = GetActorLocation();


NewLocation.X += Speed * DeltaTime;
SetActorLocation(NewLocation);

if (NewLocation.X < -600.0f)


{
this->Destroy();
}
}

5
Agora vamos a crear un blueprint a partir do inimigo para xeralos de forma aleatoria.
Despois, creamos unha nova clase de tipo GameMode para controlar as variables do
xogo, chamarémoslle SpaceShooterGameMode e un Blueprint de esta clase que
acabamos de crear.

Definiremos algunhas variables (públicas e privadas) dentro do arquivo de cabeceira


do Game Mode:

public:
UPROPERTY (EditAnywhere, Category="Spawing")
TSubclassOf<class AEnemy> Enemy_Blueprint;

float EnemyTimer; //Cada canto tempo xero un inimigo


float GameTimer; //O tempo de xogo

protected:
int Score = 0; //Puntuación. Variable que non é pública

De novo estamos facendo referencia ao controlador do inimigo, polo que teremos que
incluílo na sección de includes.

No construtor, o primeiro que temos que facer é instanciar os métodos que vamos
empregar cunha referencia ás superclases ás que pertencen:

void ASpaceShooterGameMode::BeginPlay()
{
Super::BeginPlay();
}

void ASpaceShooterGameMode::Tick(float DeltaTime)


{
Super::Tick(DeltaTime);
}

Teremos que referenciar estes métodos no arquivo de cabeceira, antes das variables
colocamos o seguinte cógido, indicando que non son nosos, que vamos a sobreescribir
os métodos do mesmo nome da clase nai:

virtual void BeginPlay() override;


virtual void Tick(float DeltaTime) override;

Volvemos ao construtor e no método Tick engadimos:

GameTimer += DeltaTime; //Vamos gardando o tempo total do xogo


EnemyTimer -= DeltaTime; //Contador decrecente

if (EnemyTimer<=0.0f) {
//Debo xerar un novo inimigo
//Debo decidir canto tempo debe pasar ata o próximo inimigo
}

Definiremos as constantes do tempo mínimo e máximo que debe transcorrer para


xerar un novo inimigo, na cabeceira colocamos antes das variables:

6
float MIN_TIME_SPAWN = 0.5f;
float MAX_TIME_SPAWN = 2.5f;
float TIME_TO_MAX_DIFF = 60.0f;

E agora facemos os cálculos de tempo e xeramos os inimigos:

if (EnemyTimer<=0.0f) {
float DiffPercentage = FMath::Min(GameTimer/TIME_TO_MAX_DIFF,
1.0f);
EnemyTimer = MAX_TIME_SPAWN - (MAX_TIME_SPAWN - MIN_TIME_SPAWN)*
DiffPercentage;
UWorld* World = GetWorld();
if (World){
FVector NewLocation = FVector(600.0f, FMath::RandRange(-
700.0f, 700.0f), 70.0f);
World->SpawnActor<AEnemy>(Enemy_Blueprint, NewLocation,
FRotator::ZeroRotator);
}
}

O inimigo aparecerá na parte superior, nun punto aleatorio na coordenada Y entre os


valores do noso escenario.
Só falta que na sección Spawning dos detalles do BP do Game Mode seleccionemos
BP_Enemy e que nos detalles do mundo (World Settings) escollamos o BP do Game
Mode no apartado Game Mode Override.

GameModeBase.h:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "shooterCGameModeBase.generated.h"

UCLASS()
class SHOOTERC_API AshooterCGameModeBase : public AGameModeBase
{
GENERATED_BODY()

public:
AshooterCGameModeBase();
UPROPERTY(EditAnywhere, Category = "Spawing")
TSubclassOf<class Aenemy> Enemy_Blueprint;

float EnemyTimer; //Cada canto tempo xero un inimigo


float GameTimer; //O tempo de xogo

virtual void Tick(float DeltaTime) override;

UPROPERTY(EditAnywhere, Category="Spawn")
float MIN_TIME_SPAWN;
UPROPERTY(EditAnywhere, Category = "Spawn")
float MAX_TIME_SPAWN;
UPROPERTY(EditAnywhere, Category = "Spawn")
float TIME_TO_MAX_DIFF;

7
protected:
virtual void BeginPlay() override;
int Score = 0; //Puntuación. Variable que non é pública
};

GameModeBase.cpp:

#include "shooterCGameModeBase.h"
#include "Enemy.h"

AshooterCGameModeBase::AshooterCGameModeBase()
{
PrimaryActorTick.bCanEverTick = true;
MIN_TIME_SPAWN = 0.5f;
MAX_TIME_SPAWN = 2.5f;
TIME_TO_MAX_DIFF = 60.0f;

void AshooterCGameModeBase::BeginPlay()
{
Super::BeginPlay();
}

void AshooterCGameModeBase::Tick(float DeltaTime)


{
Super::Tick(DeltaTime);

GameTimer += DeltaTime; //Vamos gardando o tempo total do xogo


EnemyTimer -= DeltaTime; //Contador decrecente

if (EnemyTimer <= 0.0f) {


float DiffPercentage = FMath::Min(GameTimer /
TIME_TO_MAX_DIFF, 1.0f);
EnemyTimer = MAX_TIME_SPAWN - (MAX_TIME_SPAWN -
MIN_TIME_SPAWN) * DiffPercentage;
UWorld* World = GetWorld();
if (World) {
FVector NewLocation = FVector(600.0f, FMath::RandRange(-
700.0f, 700.0f), 70.0f);
World->SpawnActor<AEnemy>(Enemy_Blueprint, NewLocation,
FRotator::ZeroRotator);
}
}
}

8
Vamos a programar agora a colisión entre o inimigo e a nave, que asociaremos á nave,
para non repetir o código (podemos ter varios inimigos). O primeiro que facemos é
definir no arquivo de cabaceira unha variable booleana que nos diga se estamos
mortos ou vivos e unha función de colisións que desenvolveremos no arquivo cpp.

bool Died;

UFUNCTION()
void OnOverlap(UPrimitiveComponent* OverlapedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComponent,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult);

void ASpaceShipController::OnOverlap(UPrimitiveComponent*
OverlapedComponent,AActor* OtherActor,UPrimitiveComponent*
OtherComponent, int32 OtherBodyIndex, bool bFromSweep, const
FHitResult& SweepResult){

if (OtherActor->IsA(AEnemy::StaticClass())){
Died = true;
this->SetActorHiddenInGame(true);
UGameplayStatics::SetGamePaused(GetWorld(), true);
}

Estamos facendo referencia ao controlador do inimigo, polo que teremos que incluílo
na sección de includes:

#include "Enemy.h"

Para que todo isto funcione temos que facer que a caixa de colisión estea atenta a
posibles choques, para iso dentro da función do controlador incluímos:

CollisionBox->SetGenerateOverlapEvents(true);
CollisionBox->OnComponentBeginOverlap.AddDynamic(this,
&ASpaceShipController::OnOverlap);

SpaceShipController.h:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "SpaceShipController.generated.h"

UCLASS()
class SHOOTERC_API ASpaceShipController : public APawn
{
GENERATED_BODY()

protected:
virtual void BeginPlay() override;

9
public:
ASpaceShipController();
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent*
PlayerInputComponent) override;

UPROPERTY(EditAnywhere)
UShapeComponent* CollisionBox;

UPROPERTY(EditAnywhere)
float Speed;

FVector CurrentVelocity;

void MoveXAxis(float AxisValue);


void MoveYAxis(float AxisValue);

UPROPERTY(EditAnywhere, Category = "Spawning")


TSubclassOf<class ABulletController>Bullet;

void OnShootPress();

bool Died;

UFUNCTION()
void OnOverlap(UPrimitiveComponent* OverlapedComponent,
AActor* OtherActor, UPrimitiveComponent* OtherComponent, int32
OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
};

SpaceShipController.cpp:
#include "SpaceShipController.h"
#include "Components/BoxComponent.h"
#include "Components/InputComponent.h"
#include "Engine/World.h"
#include "BulletController.h"
#include "Enemy.h"
#include "shooterCGameModeBase.h"
#include "Kismet/GameplayStatics.h"

ASpaceShipController::ASpaceShipController()
{
PrimaryActorTick.bCanEverTick = true;
Speed = 10.0f;

CollisionBox =
CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));
AutoPossessPlayer = EAutoReceiveInput::Player0;

CollisionBox->SetGenerateOverlapEvents(true);
CollisionBox->OnComponentBeginOverlap.AddDynamic(this,
&ASpaceShipController::OnOverlap);
}

10
void ASpaceShipController::BeginPlay()
{
Super::BeginPlay();

void ASpaceShipController::Tick(float DeltaTime)


{
Super::Tick(DeltaTime);

if (!CurrentVelocity.IsZero())
{
FVector NewLocation = GetActorLocation() + Speed *
CurrentVelocity * DeltaTime;
SetActorLocation(NewLocation);
}
}

void ASpaceShipController::SetupPlayerInputComponent(UInputComponent*
PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);

PlayerInputComponent->BindAxis("MoveX", this,
&ASpaceShipController::MoveXAxis);
PlayerInputComponent->BindAxis("MoveY", this,
&ASpaceShipController::MoveYAxis);

PlayerInputComponent->BindAction("Shoot", IE_Pressed, this,


&ASpaceShipController::OnShootPress);

void ASpaceShipController::MoveXAxis(float AxisValue)


{
CurrentVelocity.X = AxisValue * 100.0f;
}

void ASpaceShipController::MoveYAxis(float AxisValue)


{
CurrentVelocity.Y = AxisValue * 100.0f;
}

void ASpaceShipController::OnShootPress()
{
UWorld* World = GetWorld();
if (World)
{
FVector Location = GetActorLocation();
World->SpawnActor<ABulletController>(Bullet, Location,
FRotator::ZeroRotator);
}
}

11
void ASpaceShipController::OnOverlap(UPrimitiveComponent*
OverlapedComponent, AActor* OtherActor, UPrimitiveComponent*
OtherComponent, int32 OtherBodyIndex, bool bFromSweep, const
FHitResult& SweepResult)
{
if (OtherActor->IsA(AEnemy::StaticClass()))
{
Died = true;
this->SetActorHiddenInGame(true);
UGameplayStatics::SetGamePaused(GetWorld(), true);
}

E a continuación programaremos as colisións entre o inimigo e as balas. Podemos


copiar a definición da función OnOverlap do arquivo SpaceShipController.h e
pegámolo no BulletController.h
No construtor o primeiro que facemos é incluír a referecia ao controlador do inimigo
porque vamos a chequear colisións entre a bala e o inimigo.
#include "Enemy.h"

Dentro do construtor do ABulletController escribimos:

RootBox->SetGenerateOverlapEvents(true);
RootBox->OnComponentBeginOverlap.AddDynamic(this,
&ABulletController::OnOverlap);

E ao final do arquivo definimos a función OnOverlap:


void ABulletController::OnOverlap(UPrimitiveComponent*
OverlapedComponent, AActor* OtherActor, UPrimitiveComponent*
OtherComponent, int32 OtherBodyIndex, bool bFromSweep, const
FHitResult& SweepResult){
if (OtherActor->IsA(AEnemy::StaticClass())){
this->Destroy();
OtherActor->Destroy();
}
}

12
Para resetear a partida o primeiro que facemos é definir unha nova Action Mapping á
que chamaremos “Reset” e que estará asociada coa tecla R e despois definiremos a
función no controlador da nave.
No arquivo H do SpaceShipController engadimos:

void OnResetPress();

E no CPP engadiremos a chamada a esta función dentro do


SetupPlayerInputComponent:

PlayerInputComponent->BindAction("Reset", IE_Pressed, this,


&ASpaceShipController::OnResetPress).bExecuteWhenPaused=true;

bExecuteWhenPaused=true indica que a función pode ser chamada aínda que o xogo
estea en pausa.

Por último engadimos a función:

void ASpaceShipController::OnResetPress()
{
if (Died)
{
UGameplayStatics::OpenLevel(this, FName(*GetWorld()-
>GetName()), false);
//Abrimos de novo o nivel (collemos o nome do mundo) e
reseteamos todas as variables do nivel co parámetro "false"
}
}

Vamos incluír unha interface que nos mostre a puntuación. O primeiro que temos que
facer é modificar o arquivo con extensión .Build.cs engadindo o módulo UMG e
quitando o comentario da liña inferior.

PublicDependencyModuleNames.AddRange(new string[] { "Core",


"CoreUObject", "Engine", "InputCore", "UMG" });

// Uncomment if you are using Slate UI


PrivateDependencyModuleNames.AddRange(new string[] { "Slate",
"SlateCore" });

Crearemos unha nova clase C++ de tipo UserWidget á que chamamos GameWidget, e
no seu arquivo de cabeceira engadimos os seguintes includes:

#include "Runtime/UMG/Public/UMG.h"
#include "Runtime/UMG/Public/UMGStyle.h"
#include "Runtime/UMG/Public/IUMGModule.h"
#include "Runtime/UMG/Public/Slate/SObjectWidget.h"
#include "Runtime/UMG/Public/Blueprint/UserWidget.h"

13
Definimos 3 funcións: unha para cargar a interface, outra para a puntuación e a última
para a finalización do xogo e unha propiedade de tipo TextBlock onde mostraremos
esta información.

void Load();
void SetScore(int score);
void OnGameOver(int score);

UPROPERTY()
UTextBlock* ScoreTextBlock;

CPP:
#include "GameWidget.h"

void UGameWidget::Load()
{
const FName TextBlockName =
FName(TEXT("GameTextBlock"));

if (ScoreTextBlock == nullptr)
{
ScoreTextBlock = (UTextBlock*)(WidgetTree-
>FindWidget (TextBlockName));
}
}

void UGameWidget::SetScore(int score)


{
if (ScoreTextBlock != nullptr)
{
ScoreTextBlock-
>SetText(FText::FromString(FString(TEXT("Score: ")) +
FString::FromInt (score)));
}
}

void UGameWidget::OnGameOver(int score)


{
if (ScoreTextBlock != nullptr)
{
ScoreTextBlock-
>SetText(FText::FromString(FString(TEXT("Score: ")) +
FString::FromInt(score)
+ TEXT("\n Pulsa R para reiniciar")));
}
}

Creamos un WidgetBP que asociamos á clase GameWidget (Reparent Blueprint) onde


colocaremos un bloque de texto ao que chamaremos GameTextBlock.

14
Para que podamos velo temos que colocalo no Viewport, e isto farémolo desde o
GameMode. Na parte pública do arquivo de cabeceiras engadimos:

UFUNCTION(BlueprintCallable)
void ChangeMenuWidget(TSubclassOf<UUserWidget>
NewWidgetClass);

void IncrementScore();
void GameOver();

E na parte protexida, de forma que non se poda modificar desde outras clases,
engadimos unha subclase do Widget do usuario, que será o widget inicial, e un
punteiro ao widget actual, o que está activo na pantalla:

UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSubclassOf<UUserWidget> StartingWidgetClass;

UPROPERTY()
UUserWidget* CurrentWidget;

No CPP temos que engadir o include do GameWidget: #include "GameWidget.h"

Dentro da función BeginPlay chamamos á función que inicializa o widget e despois á


que o carga en pantalla:
ChangeMenuWidget(StartingWidgetClass);
((UGameWidget*)CurrentWidget)->Load();

E detallamos as accións da función:

void
ASpaceShooterGameMode::ChangeMenuWidget(TSubclassOf<UUserWidge
t> NewWidgetClass)
{
if (CurrentWidget != nullptr)
{
CurrentWidget->RemoveFromViewport();
CurrentWidget = nullptr;
}

if (NewWidgetClass != nullptr)
{
CurrentWidget = CreateWidget<UUserWidget> (GetWorld(),
NewWidgetClass);

if(CurrentWidget != nullptr)
{
CurrentWidget->AddToViewport();
}
}
}

15
Podemos compilar e, despois de asociar o BP do noso Widget no campo “Starting
Widget Class” do BP do Game Mode, veremos que aparece o texto na pantalla.

O último paso será actualizar os datos da interface cos puntos do usuario, para iso
engadimos 2 novos métodos no arquivo de cabeceiras da clase do noso GameMode
(onde xa temos definida a variable Score para gardar a puntuación):

void IncrementScore();
void GameOver();

Implementamos estas funcións no arquivo CPP, onde non podemos esquecernos de


engadir o include ao GameWidget:

#include "GameWidget.h"

void ASpaceShooterGameMode::IncrementScore()
{
Score += 10;
((UGameWidget*)CurrentWidget)->SetScore(Score);
}

void ASpaceShooterGameMode::GameOver()
{
((UGameWidget*)CurrentWidget)->OnGameOver(Score);
}

A función de incremento da puntuación chamarémola desde o BulletController, que


precisará o include ao GameMode:

#include "SpaceShooterGameMode.h"

E ao final da función OnOverlap incluímos:

((ASpaceShooterGameMode*)GetWorld()->GetAuthGameMode())-
>IncrementScore();

A función de GameOver será chamada desde o SpaceShipController, tamén na función


OnOverlap, e xusto antes de poñer o xogo en pausa:

((ASpaceShooterGameMode*)GetWorld()->GetAuthGameMode())-
>GameOver();

Ao igual que no caso anterior, non podemos esquecernos do include ao GameMode.

16
// SpaceShipController.h

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "SpaceShipController.generated.h"

UCLASS()
class TOPDOWN_API ASpaceShipController : public APawn
{
GENERATED_BODY()

public:
ASpaceShipController();

virtual void Tick(float DeltaTime) override;

virtual void SetupPlayerInputComponent(class


UInputComponent* PlayerInputComponent) override;

UPROPERTY (EditAnywhere)
UShapeComponent* CollisionBox;

UPROPERTY (EditAnywhere)
float Speed;

UPROPERTY (EditAnywhere, Category="Spawing")


TSubclassOf<class ABulletController>BulletBlueprint;

void Move_XAxis(float AxisValue);


void Move_YAxis(float AxisValue);
void OnShootPress();
void OnResetPress();

FVector CurrentVelocity;

bool Died;

UFUNCTION()
void OnOverlap(UPrimitiveComponent*
OverlapedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComponent,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult);

};

17
// SpaceShipController.cpp
#include "SpaceShipController.h"
#include "Components/BoxComponent.h"
#include "Components/InputComponent.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
#include "BulletController.h"
#include "Enemy.h"
#include "SpaceShooterGameMode.h"

ASpaceShipController::ASpaceShipController()
{
PrimaryActorTick.bCanEverTick = true;
Speed = 10.0f;
Died= false;

CollisionBox =
CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));

AutoPossessPlayer = EAutoReceiveInput::Player0;

CollisionBox->SetGenerateOverlapEvents(true);
CollisionBox->OnComponentBeginOverlap.AddDynamic(this,
&ASpaceShipController::OnOverlap);

void ASpaceShipController::Tick(float DeltaTime)


{
Super::Tick(DeltaTime);

if (!CurrentVelocity.IsZero()){
FVector NewLocation = GetActorLocation() + Speed *
CurrentVelocity * DeltaTime;
SetActorLocation(NewLocation);
}

void
ASpaceShipController::SetupPlayerInputComponent(UInputComp
onent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent)
;

PlayerInputComponent->BindAxis("MoveX", this, &


ASpaceShipController::Move_XAxis);
PlayerInputComponent->BindAxis("MoveY", this, &
ASpaceShipController::Move_YAxis);

18
PlayerInputComponent->BindAction("Shoot", IE_Pressed,
this, &ASpaceShipController::OnShootPress);
PlayerInputComponent->BindAction("Reset", IE_Pressed,
this,
&ASpaceShipController::OnResetPress).bExecuteWhenPaused=tr
ue;

void ASpaceShipController::Move_XAxis(float AxisValue){


CurrentVelocity.X = AxisValue * 100.0f;
}

void ASpaceShipController::Move_YAxis(float AxisValue){


CurrentVelocity.Y = AxisValue * 100.0f;
}

void ASpaceShipController::OnShootPress(){
UWorld * World = GetWorld(); //referencia á escena
if (World) {
FVector Location = GetActorLocation();
World-
>SpawnActor<ABulletController>(BulletBlueprint, Location,
FRotator::ZeroRotator);
}
}

void ASpaceShipController::OnOverlap(UPrimitiveComponent*
OverlapedComponent,AActor* OtherActor,UPrimitiveComponent*
OtherComponent, int32 OtherBodyIndex, bool bFromSweep,
const FHitResult& SweepResult){

if (OtherActor->IsA(AEnemy::StaticClass()))
{
Died = true;
this->SetActorHiddenInGame(true);

((ASpaceShooterGameMode*)GetWorld()-
>GetAuthGameMode())->GameOver();

UGameplayStatics::SetGamePaused(GetWorld(), true);

19
void ASpaceShipController::OnResetPress()
{
if (Died)
{
UGameplayStatics::OpenLevel(this,
FName(*GetWorld()->GetName()), false);
//Abrimos de novo o nivel (collemos o nome do
mundo) e reseteamos todas as variables do nivel co
parámetro "false"
}
}

// Enemy.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Enemy.generated.h"

UCLASS()
class TOPDOWN_API AEnemy : public AActor
{
GENERATED_BODY()

protected:
virtual void BeginPlay() override;

public:
AEnemy();
virtual void Tick(float DeltaTime) override;

UPROPERTY(EditAnywhere)
UShapeComponent * RootBox;

UPROPERTY(EditAnywhere)
float Speed = -150.0f;

};

20
// Enemy.cpp
#include "Enemy.h"
#include "Components/BoxComponent.h"

AEnemy::AEnemy()
{
PrimaryActorTick.bCanEverTick = true;

RootBox =
CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));
}

void AEnemy::BeginPlay()
{
Super::BeginPlay();
}

void AEnemy::Tick(float DeltaTime)


{
Super::Tick(DeltaTime);

FVector NewLocation = GetActorLocation();


NewLocation.X += Speed * DeltaTime;
SetActorLocation(NewLocation);

if (NewLocation.X < -600.0f) {


this->Destroy();
}
}

// BulletControler.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "BulletController.generated.h"

UCLASS()
class TOPDOWN_API ABulletController : public AActor
{
GENERATED_BODY()

public:
ABulletController();
virtual void Tick(float DeltaTime) override;

UPROPERTY(EditAnywhere)
UShapeComponent * RootBox;

UPROPERTY(EditAnywhere)

21
float Speed;

UFUNCTION()
void OnOverlap(UPrimitiveComponent*
OverlapedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComponent,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult);
};

// BulletControler.cpp
#include "BulletController.h"
#include "Components/BoxComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
#include "Enemy.h"
#include "SpaceShooterGameMode.h"

ABulletController::ABulletController()
{
PrimaryActorTick.bCanEverTick = true;
Speed = 400.0f;

RootBox =
CreateDefaultSubobject<UBoxComponent>(TEXT("Root"));

RootBox->SetGenerateOverlapEvents(true);
RootBox->OnComponentBeginOverlap.AddDynamic(this,
&ABulletController::OnOverlap);
}

void ABulletController::Tick(float DeltaTime)


{
Super::Tick(DeltaTime);

FVector NewLocation = GetActorLocation();


NewLocation.X = NewLocation.X + Speed*DeltaTime;
SetActorLocation(NewLocation);

if (NewLocation.X > 700.0f) {


this->Destroy();
}
}

22
void ABulletController::OnOverlap(UPrimitiveComponent*
OverlapedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComponent, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult){
if (OtherActor->IsA(AEnemy::StaticClass())){
this->Destroy();
OtherActor->Destroy();

((ASpaceShooterGameMode*)GetWorld()-
>GetAuthGameMode())->IncrementScore();
}
}

// SpaceShooterGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "Enemy.h"
#include "SpaceShooterGameMode.generated.h"

UCLASS()
class TOPDOWN_API ASpaceShooterGameMode : public
AGameModeBase
{
GENERATED_BODY()

protected:
virtual void BeginPlay() override;
int Score; //Puntuación. Variable que non é pública

UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSubclassOf<UUserWidget> StartingWidgetClass;

UPROPERTY()
UUserWidget* CurrentWidget;

public:

UPROPERTY (EditAnywhere, Category="Spawn")


TSubclassOf<class AEnemy> Enemy_Blueprint;

UPROPERTY (EditAnywhere, Category="Spawn")


float MIN_TIME_SPAWN;
UPROPERTY (EditAnywhere, Category="Spawn")
float MAX_TIME_SPAWN;
UPROPERTY (EditAnywhere, Category="Spawn")
float TIME_TO_MAX_DIFF;
float EnemyTimer; //Cada canto tempo xero un inimigo
float GameTimer; //O tempo de xogo

23
ASpaceShooterGameMode();
virtual void Tick(float DeltaTime) override;

UFUNCTION(BlueprintCallable)
void ChangeMenuWidget(TSubclassOf<UUserWidget>
NewWidgetClass);

void IncrementScore();
void GameOver();
};

// SpaceShooterGameMode.cpp
#include "SpaceShooterGameMode.h"
#include "GameWidget.h"

ASpaceShooterGameMode::ASpaceShooterGameMode()
{
PrimaryActorTick.bCanEverTick = true;
Score = 0;
MIN_TIME_SPAWN = 0.5f;
MAX_TIME_SPAWN = 2.5f;
TIME_TO_MAX_DIFF = 60.0f;
}

void ASpaceShooterGameMode::BeginPlay()
{
Super::BeginPlay();

ChangeMenuWidget(StartingWidgetClass);
((UGameWidget*)CurrentWidget)->Load();
}

void ASpaceShooterGameMode::Tick(float DeltaTime)


{
Super::Tick(DeltaTime);

GameTimer += DeltaTime; //Vamos gardando o tempo total


do xogo
EnemyTimer -= DeltaTime; //Contador decrecente

if (EnemyTimer<=0.0f) {
float DiffPercentage =
FMath::Min(GameTimer/TIME_TO_MAX_DIFF, 1.0f);
EnemyTimer = MAX_TIME_SPAWN - (MAX_TIME_SPAWN -
MIN_TIME_SPAWN)* DiffPercentage;
UWorld* World = GetWorld();

24
if (World){
FVector NewLocation = FVector(600.0f,
FMath::RandRange(-700.0f, 700.0f), 70.0f);
World->SpawnActor<AEnemy>(Enemy_Blueprint,
NewLocation, FRotator::ZeroRotator);
}
}
}

void
ASpaceShooterGameMode::ChangeMenuWidget(TSubclassOf<UUserW
idget> NewWidgetClass)
{
if (CurrentWidget != nullptr)
{
CurrentWidget->RemoveFromViewport();
CurrentWidget = nullptr;
}

if (NewWidgetClass != nullptr)
{
CurrentWidget = CreateWidget<UUserWidget>
(GetWorld(), NewWidgetClass);

if(CurrentWidget != nullptr)
{
CurrentWidget->AddToViewport();
}
}
}

void ASpaceShooterGameMode::IncrementScore()
{
Score += 10;
((UGameWidget*)CurrentWidget)->SetScore(Score);
}

void ASpaceShooterGameMode::GameOver()
{
((UGameWidget*)CurrentWidget)->OnGameOver(Score);
}

25
// GameWidget.h
#pragma once
#include "Runtime/UMG/Public/UMG.h"
#include "Runtime/UMG/Public/UMGStyle.h"
#include "Runtime/UMG/Public/IUMGModule.h"
#include "Runtime/UMG/Public/Slate/SObjectWidget.h"
#include "Runtime/UMG/Public/Blueprint/UserWidget.h"

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "GameWidget.generated.h"

UCLASS()
class TOPDOWN_API UGameWidget : public UUserWidget
{
GENERATED_BODY()

public:
void Load();
void SetScore(int score);
void OnGameOver(int score);

UPROPERTY()
UTextBlock* ScoreTextBlock;
};

// GameWidget.cpp
#include "GameWidget.h"

void UGameWidget::Load()
{
const FName TextBlockName =
FName(TEXT("GameTextBlock"));
if (ScoreTextBlock == nullptr)
{
ScoreTextBlock = (UTextBlock*)(WidgetTree-
>FindWidget(TextBlockName));
}
}

void UGameWidget::SetScore(int score)


{
if (ScoreTextBlock != nullptr)
{
ScoreTextBlock-
>SetText(FText::FromString(FString(TEXT("Score: ")) +
FString::FromInt(score)));
}
}

26
void UGameWidget::OnGameOver(int score)
{
if (ScoreTextBlock != nullptr)
{
ScoreTextBlock-
>SetText(FText::FromString(FString(TEXT("Score: "))
+
FString::FromInt(score)
+
TEXT("\n Pulsa R para reiniciar")));
}
}

27

You might also like