Skip to content

Subclassing Player

petercondoleon edited this page Oct 1, 2017 · 11 revisions

Introduction

If you wish to create your own custom player, this is the right place for you! In the following tutorial, we will:

  • Create a subclass of player
  • Specify specific player attributes
  • Create custom player animations
  • Design special abilities

Be sure to check out the Player documentation before getting started!

Creating a subclass of player

In your IDE of choice, naviagte to the com.deco2800.potatoes.entities.player package in the project explorer. In this package we will create a new class that inherits from Player.Java.

public class Caveman extends Player {

}

Specifying Player Attributes

Now that we have a subclass, the action begins. Create a constructor for our player that passes the spawn coordinates to the super() constructor. Here we can choose to initialise the caveman player to have a specific move speed, direction and state, however it is best practice to set the current state of the player to PlayerState.IDLE.

public Caveman(float posX, float posY, float posZ) {
    super(posX, posY, posZ);
    this.movementSpeed = 0.08f;
    this.currentDirection = Direction.SE;
    this.currentState = PlayerState.IDLE;
}

Above we set the move speed to 0.08f, the initial direction to Direction.SE and initial state to PlayerState.IDLE. In future we may also include functionality for attributes such as health, damage scaling and size so keep an eye out for updates!

Creating Player Animations

In this section we will set up animations for the caveman player being idle, walking and taking damage.

Setting up Textures

The first thing we need to do is set up the textures for these animations in the TextureManager class. Textures will need to be provided for each state, direction and frame and to keep track of this, we will use my file naming convention. Our textures will be labeled according to type_state_direction_frame.png. So for example, the texture for the caveman walking in the north direction at frame 2 will be caveman_walk_N_2.png. If you are rendering your sprites using the script from this tutorial, you can change the output file name string to match the convention and save countless hours of file renaming! Who knew? With the caveman textures in the resources/player/caveman/ folder we can use a for loop to loop through all directions and states and add our sprites into the game. The following is added to the loadTextures() method in TextureManager.Java:

for (Direction direction : Direction.values()) {
    String textureNameIdle = "caveman_idle_" + direction.name() + "_1";
    saveTexture(textureNameIdle, "resources/player/caveman/idle/" + textureNameIdle + ".png");
        		
    for (int i=1; i<=5; i++) {
        String textureNameAttack = "caveman_attack_" + direction.name() + "_" + i;
        saveTexture(textureNameAttack, "resources/player/caveman/attack/" + textureNameAttack + ".png");
    }
        		
    for (int i=1; i<=8; i++) {
        String textureNameWalk = "caveman_walk_" + direction.name() + "_" + i;
        saveTexture(textureNameWalk, "resources/player/caveman/walk/" + textureNameWalk + ".png");
    }
}

The inner for loop counting variables (i) are set to match the number of frames a particular animation has. Because the idle animation for the player consists of only one frame, an inner for loop is not needed. And the caveman attack and walk animations consist of 5 and 8 frames respectively, we set the loop to repeat enough times to save all frames.

Initialising Animations

Now that the textures are ready to go, we can use the static method makePlayerAnimation from the player class to create a map of directions to timed animations. This map will later allow us to update the animation depending on which direction the player faces - Cool!. Back in our subclass we add the following private variables:

private Map<Direction, TimeAnimation> cavemanWalkAnimations = makePlayerAnimation("caveman", 
            PlayerState.WALK, 8, 800, null);
private Map<Direction, TimeAnimation> cavemanIdleAnimations = makePlayerAnimation("caveman", 
            PlayerState.IDLE, 1, 1, null);

Here we created the animation maps for the walking state and the idle state. Note that these states do not require us to handle completion (this is handled within the Player class internally), so we can set the completionHandler parameter null. The attack animation will, however, require us to manually handle completion. We can accomplish this with the following:

private Map<Direction, TimeAnimation> cavemanDamagedAnimations = makePlayerAnimation("caveman", 
            PlayerState.damaged, 1, 200, this::completionHandler);

private Void completionHandler() {
    // Handle finishing attack
    clearState();
    updateSprites();
    return null;
}

Doing so allows us to reset the player state back to idle after attacking so that the player doesn't get stuck in the attacking animation.

Updating The Sprite From The Animation

The final step to adding in the animation is to write the code to set them. This is accomplished by overriding the updateSprites() method. We can then simply enable our animations as follows:

@Override
public void updateSprites() {
    super.updateSprites();
    switch (this.getState()) {
    case idle:
        this.setAnimation(cavemanIdleAnimations.get(this.getDirection()));
        break;
    case walk:
        this.setAnimation(cavemanWalkAnimations.get(this.getDirection()));
        break;
    case attack:
        this.setAnimation(cavemanAttackAnimations.get(this.getDirection()));
        break;
    case damaged:
        break;
    case death:
        break;
    case interact:
        break;
    default:
        this.setAnimation(cavemanIdleAnimations.get(this.getDirection()));
        break;
    }
}

The player class now takes care of the rest. Note that you can add animations for the remaining states as well.

Designing Special Abilities

There are currently two abilities that can be added to a player; attacking and interacting. The attacking ability is designed for allowing the player to perform sort of attack on enemies and might, for example, do something like fire a rocket projectile. It is important that however you decide to implement an attack, you do so like so:

@Override
public void attack() {
    super.attack();
    if (this.setState(PlayerState.ATTACK)) {
        // Attack code here
    }
}

By doing so, the attack will only trigger if the player class allows the player to enter the attack state. A reason for why we do this is because, for instance, we might not want the player to be able to attack if they're dead! A similar technique is used for the interact ability:

@Override
public void interact() {
    super.interact();
    if (this.setState(PlayerState.INTERACT)) {
	 // Interact code here
    }
}

For this case we also include super.interact() to inherit other interaction abilities such as tossing items and harvesting resources. Neither of these method need to be overridden, but if you want special abilities go for it.

Finishing up

And that's about it, good job you've made your first player and are ready to put it in the game! To do so, open up PlayerManager.Java and scroll down to public enum PlayerType. First add the name of your player to this enumeration like follows:

// Types of players in the game
public enum PlayerType { CAVEMAN, WIZARD, ARCHER;  // Add new types here
    @Override
    public String toString() {
	return super.toString().toLowerCase();
    }
		
    public static Array<String> names() {
	Array<String> names = new Array<>();
	for (PlayerType type : PlayerType.values()) {
	    names.add(type.toString());
        }
	return names;
    }
};

The final step is to now handle the case where your player is selected, which can be done in the setPlayer() method. Simply add the case for your player type and the manager will take care of the rest:

/**
 * Sets the player based on the player type.
 * 
 * @param player
 */
public void setPlayer(float posX, float posY) {
    switch (this.playerType) {
    case CAVEMAN:
	this.player = new Caveman(posX, posY);
	break;
    case ARCHER:
	this.player = new Archer(posX, posY);
	break;
    case WIZARD:
	this.player = new Wizard(posX, posY);
	break;
    default:
	this.player = new Player(posX, posY);
	break;
    }
		
    // Set camera manager to target the player
    GameManager.get().getManager(CameraManager.class).setTarget(this.player);
}

You can now select your player from the main menu. Happy RocketPotato-ing!


Gameplay

Design

Asset Creation

User Testing

Code Guidelines


http://cultofthepartyparrot.com/parrots/partyparrot.gifhttp://cultofthepartyparrot.com/parrots/partyparrot.gifhttp://cultofthepartyparrot.com/parrots/partyparrot.gifhttp://cultofthepartyparrot.com/parrots/partyparrot.gifhttp://cultofthepartyparrot.com/parrots/partyparrot.gif

Clone this wiki locally