Chapter 3. Exploration and Combat

We have a design for our game and an Unreal project set up for our game. It's now time to dive into the actual game code.

In this chapter, we'll be making a game character that moves around the world, defining our game data, and prototyping a basic combat system for the game. We will cover the following topics in this chapter:

  • Creating the player pawn
  • Defining characters, classes, and enemies
  • Keeping track of active party members
  • Creating a basic turn-based combat engine
  • Triggering a game over screen

This particular chapter is the most C++ heavy portion of the book, and provides the basic framework that the rest of the book is going to use. Because this chapter provides much of the backend of our game, the code in this chapter must work to completion before moving on to the rest of the content in the book. If you bought this book because you are a programmer looking for more background in creating a framework for an RPG, this chapter is for you! If you bought this book because you are a designer, and care more about building upon the framework rather than programming it from scratch, you will probably be more into the upcoming chapters because those chapters contain less C++ and more UMG and Blueprints. No matter who you are, it is a good idea to download the source code provided through the directions located in the preface of the book in case you get stuck or would like to skip chapters based on your interests.

Creating the player pawn

The very first thing we are going to do is create a new Pawn class. In Unreal, the Pawn is the representation of a character. It handles the movement, physics, and rendering of a character.

Here's how our character pawn is going to work. The player is divided into two parts: there's the Pawn that, as mentioned earlier, is responsible for handling the movement, physics, and rendering. Then there's the Player Controller, responsible for translating the player's input into making the Pawn perform what the player wants.

The Pawn

Now, let's create the actual pawn.

Create a new C++ class and select Character as the parent class for it. We will be deriving this class from the Character class because Character has a lot of built-in move functions that we can use for our field player. Name the class RPGCharacter. Open RPGCharacter.h and change the class definition using the following code:

UCLASS(config = Game)
class ARPGCharacter : public ACharacter
{
  GENERATED_BODY()

  /** Camera boom positioning the camera behind the character */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) class USpringArmComponent* CameraBoom;

  /** Follow camera */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) class UCameraComponent* FollowCamera;
public:
  ARPGCharacter();

  /**Base turn rate, in deg/sec. Other scaling may affect final turn rate.*/
  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
    float BaseTurnRate;

protected:

  /** Called for forwards/backward input */
  void MoveForward(float Value);

  /** Called for side to side input */
  void MoveRight(float Value);

  /**
  * Called via input to turn at a given rate.
  * @param Rate  This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
  */
  void TurnAtRate(float Rate);

protected:
  // APawn interface
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
  // End of APawn interface

public:
  /** Returns CameraBoom subobject **/
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
  /** Returns FollowCamera subobject **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};

Next, open RPGCharacter.cpp and replace it with the following code:

#include "RPG.h"
#include "RPGCharacter.h"

ARPGCharacter::ARPGCharacter()
{
  // Set size for collision capsule
  GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

  // set our turn rates for input
  BaseTurnRate = 45.f;

// Don't rotate when the controller rotates. //Let that just affect the camera.
  bUseControllerRotationPitch = false;
  bUseControllerRotationYaw = false;
  bUseControllerRotationRoll = false;

  // Configure character movement
// Character moves in the direction of input...
GetCharacterMovement()->bOrientRotationToMovement = true; // ...at this rotation rate  
GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); 

  // Create a camera boom
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
  CameraBoom->SetupAttachment(RootComponent);
// The camera follows at this distance behind the character  CameraBoom->TargetArmLength = 300.0f; 
CameraBoom->RelativeLocation = FVector(0.f, 0.f, 500.f);// Rotate the arm based on the controller
CameraBoom->bUsePawnControlRotation = true; 

// Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Camera does not rotate relative to arm
FollowCamera->bUsePawnControlRotation = false;  FollowCamera->RelativeRotation = FRotator(-45.f, 0.f, 0.f);

/* Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)*/
}

//////////////////////////////////////////////////////////////////
// Input

void ARPGCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
  // Set up gameplay key bindings
  check(InputComponent);

InputComponent->BindAxis("MoveForward", this, &ARPGCharacter::MoveForward);
InputComponent->BindAxis("MoveRight", this, &ARPGCharacter::MoveRight);

/* We have 2 versions of the rotation bindings to handle different kinds of devices differently "turn" handles devices that provide an absolute delta, such as a mouse. "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick*/
InputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
InputComponent->BindAxis("TurnRate", this, &ARPGCharacter::TurnAtRate);
}


void ARPGCharacter::TurnAtRate(float Rate)
{
  // calculate delta for this frame from the rate information
AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void ARPGCharacter::MoveForward(float Value)
{
  if ((Controller != NULL) && (Value != 0.0f))
  {
    // find out which way is forward
    const FRotator Rotation = Controller->GetControlRotation();
    const FRotator YawRotation(0, Rotation.Yaw, 0);

    // get forward vector
    const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    AddMovementInput(Direction, Value);
  }
}

void ARPGCharacter::MoveRight(float Value)
{
  if ((Controller != NULL) && (Value != 0.0f))
  {
    // find out which way is right
    const FRotator Rotation = Controller->GetControlRotation();
    const FRotator YawRotation(0, Rotation.Yaw, 0);

    // get right vector 
    const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
    // add movement in that direction
    AddMovementInput(Direction, Value);
  }
}

If you have ever created and worked with the C++ ThirdPerson game template, you will notice that we are not reinventing the wheel here. The RPGCharacter class should look familiar because it is a modified version of the ThirdPerson Character class code given to us when we create a C++ ThirdPerson template, provided for us to use by Epic Games.

Since we are not creating a fast-paced action game and are simply using the Pawn as an RPG character to maneuver out in the field, we eliminated mechanics that are often associated with action games, such as jumping. But we kept in the code that is important to us, which is the ability to move forward, backward, left, and right; rotational behaviors of the pawn; a camera that will follow the character around in an isometric view; a collision capsule for the character to be able to collide with collidable objects; configuration for character movement; and a camera boom, which will allow the camera to move closer to the character in case it runs into collisions such as a wall or other meshes, important for not blocking a player's view. If you want to edit the character mechanics, feel free to do so by following the comments in the code to change the values of some specific mechanics such as TargetArmLength to change the distance of the camera from the player, or adding jumping, which can be seen in the ThirdPerson character template that came with the Engine.

Because we derived the RPGCharacter class from the Character class, its default camera is not rotated for an isometric view; instead, the camera rotations and locations are zeroed out by default to the pawn's location. So what we did was add a CameraBoom relative location in RPGCharacter.cpp (CameraBoom->RelativeLocation = FVector(0.f, 0.f, 500.f);); this offsets the camera 500 units up on the z axis. Along with rotating the camera that follows the player -45 units on the pitch (FollowCamera->RelativeRotation = FRotator(-45.f, 0.f, 0.f);), we get a traditional isometric view. If you would like to edit these values to customize your camera even more, it is suggested; for instance, if you still think your camera is too close to the player, you can simply change the relative location of CameraBoom on the z axis to a value higher than 500 units, and/or adjust TargetArmLength to something larger than 300.

Lastly, if you take a look at the MoveForward and MoveRight movement functions, you will notice that no movement is being added to the pawn unless the value that is passed to MoveForward or MoveRight is not equal to 0. Later on in this chapter, we will bind keys W, A, S, and D to these functions and set each one of these inputs to pass a scalar of 1 or -1 to the corresponding movement function as values. This 1 or -1 value is then used as a multiplier to the direction of the pawn, which will then allow the player to move in a specific direction based on its walk speed. For instance, if we set W as a keybind to MoveForward with a scalar of 1, and S as a keybind to MoveFoward with a scalar of -1, when the player presses W, the value in the MoveFoward function will be equal to 1 and cause the pawn to move in the positive forward direction as a result. Alternatively, if the player presses the S key, -1 is then passed into the value used by the MoveForward function, which will cause the pawn to move in the negative forward direction (in other words, backwards). Similar logic can be said about the MoveRight function, which is why we don't have a MoveLeft function—simply because pressing the A key would cause the player to move in the negative right direction, which is in fact left.

The GameMode class

Now, in order to use this pawn as a player character, we need to set up a new game mode class. This game mode will then specify the default pawn and player controller classes to use. We'll also be able to make a Blueprint of the game mode and override these defaults.

Create a new class and choose GameMode as the parent class. Name this new class RPGGameMode (if RPGGameMode already exists in your project, simply navigate to your C++ source directory and proceed to open up RPGGameMode.h, as listed in the next step).

Open RPGGameMode.h and change the class definition using the following code:

UCLASS()
class RPG_API ARPGGameMode : public AGameMode
{
  GENERATED_BODY()

  ARPGGameMode( const class FObjectInitializer& ObjectInitializer );
};

Just as we've done before, we're just defining a constructor for the CPP file to implement.

We're going to implement that constructor now in RPGGameMode.cpp:

#include "RPGCharacter.h"

ARPGGameMode::ARPGGameMode( const class FObjectInitializer& ObjectInitializer )
  : Super( ObjectInitializer )
{

  DefaultPawnClass = ARPGCharacter::StaticClass();
}

Here, we include the RPGCharacter.h file so that we can reference these classes. Then, in the constructor, we set the class as the default class to use for the Pawn.

Now, if you compile this code, you should be able to assign your new game mode class as the default game mode. To do this, go to Edit | Project Settings, find the Default Modes box, expand the Default GameMode drop-down menu, and select RPGGameMode.

However, we don't necessarily want to use this class directly. Instead, if we make a Blueprint, we can expose the properties of the game mode that can be modified in the Blueprint.

So, let's make a new Blueprint Class in Content | Blueprints, pick RPGGameMode as its parent class, and call it DefaultRPGGameMode:

The GameMode class

If you open the Blueprint and navigate to the Defaults tab, you can modify the settings for the game mode for Default Pawn Class, HUD Class, Player Controller Class, and more settings:

The GameMode class

However, we still have one extra step before we can test our new Pawn. If you run the game, you will not see the Pawn at all. In fact, it will appear as if nothing is happening. We need to give our Pawn a skinned mesh and also make the camera follow the pawn.

Adding the skinned mesh

For now, we're just going to import the prototype character that comes with the ThirdPerson sample. To do this, make a new project based on the ThirdPerson sample. Locate the ThirdPersonCharacter Blueprint class in Content | ThirdPersonCPP | Blueprints and migrate it to the Content folder of your RPG project by right-clicking on the ThirdPersonCharacter Blueprint class and navigating to Asset Actions | Migrate…. This action should copy ThirdPersonCharacter with all its assets into your RPG project:

Adding the skinned mesh

Now, let's create a new Blueprint for our Pawn. Create a new Blueprint class and select RPGCharacter as the parent class. Name it FieldPlayer.

Expand Mesh located in the Details tab when selecting the Mesh component from the Components tab and choose SK_Mannequin as the skeletal mesh for the pawn. Next, expand Animation and choose ThirdPerson_AnimBP as the animation Blueprint to use. You will most likely need to move your character's mesh down the z axis so that the bottom of the character's feet meet the bottom of the collision capsule. Also be sure that the character mesh is facing the same direction that the blue arrow in the component is facing. You may need to rotate the character on the z axis as well to ensure that the character is facing the right direction:

Adding the skinned mesh

Finally, open your game mode Blueprint and change the pawn to your new FieldPlayer Blueprint.

Now, our character will be visible, but we may not be able to move it yet because we have not bound keys to any of our movement variables. To do so, go into Project Settings and locate Input. Expand Bindings and then expand Axis Mappings. Add an axis mapping by pressing the + button. Call the first axis mapping MoveRight, which should match the MoveRight movement variable you created earlier in this chapter. Add two key bindings for MoveRight by pressing the + button. Let one of those keys be A with a scale of -1 and other be D with a scale of 1. Add another axis mapping for MoveForward; only this time, have a W key binding with a scale of 1 and an S key binding with a scale of -1:

Adding the skinned mesh

Once you play test, you should see your character moving and animating using the W, A, S, and D keys you bound to the player.

When you run the game, the camera should track the player in an overhead view. Now that we have a character that can explore the game world, let's take a look at defining characters and party members.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.85.135