Level 1: Basic Enemy Gym Bro - UQdeco2800/2022-studio-2 GitHub Wiki
Sprint 1
In Sprint 1, created different stats for enemies (health, attack, movement speed). Moved the AI Component from the base entity creation method, to each individual enemy creation methods allowing for more customised movement patterns.
Level 1 Basic Enemy (Gym Bro):
In Sprint 1, we created the basic enemy for level 1 (the Gym Bro).
Code
1. Basic stats for the Gym Bro:
In this sprint, the statistic for the enemy has been customized to fit the game. They also have the movement speed variable now which allows them to move faster to fit the game when it's progressed in the future. However, there was a bug along with the movement speed. When the speed is too high (around 120f, the animation is glitched which decreased the usability of the game features).
PhysicsMovementComponent.java
manages the movement of the entity (player and enemy). setToVelocity()
is the function inside PhysicsMovementComponent.java
that allows changing the speed of the entity based on the scalable input of speed. Chase Task will use this class to implement the task with the speed indicated by each enemy.
public class PhysicsMovementComponent extends Component implements MovementController {
private static final Logger logger = LoggerFactory.getLogger(PhysicsMovementComponent.class);
private static final Vector2 maxSpeed = Vector2Utils.ONE;
private float speed = 1;
private PhysicsComponent physicsComponent;
private Vector2 targetPosition;
private boolean movementEnabled = true;
private void setToVelocity(Body body, Vector2 desiredVelocity) {
// impulse force = (desired velocity - current velocity) * mass
Vector2 velocity = body.getLinearVelocity();
Vector2 impulse = desiredVelocity.cpy().sub(velocity).scl(body.getMass()).scl(speed);
//body.applyLinearImpulse(impulse, body.getWorldCenter(),true);
body.applyForce(impulse, body.getWorldCenter(), true);
}
Also, to make movement customisable for each enemy, the code was adjusted so that the different tasks an enemy can do are specified in that enemies method, rather than inheriting a set list of tasks from the base NPC creation method. For sprint 1, as we only have 1 enemy this customisation isn't as important, however, in later sprints as more enemies are developed, having this ability to customise movement for each enemy will be very important to keep the enemies varied and engaging.
This can be seen in the code (with omissions represented by ...)
private static Entity createBaseNPC(Entity target) {
AITaskComponent aiComponent = new AITaskComponent();
}
public static Entity createAtlantisCitizen(Entity target) {
...
atlantisCitizen.getComponent(AITaskComponent.class)
.addTask(new WanderTask(new Vector2(2f, 2f), 2f))
.addTask(new ChaseTask(target, 10, 5f, 6f, 120f));
...
}
Test Plan: The speed of the enemy can be changed via NPCs.json
in the speed
variable of the atlantisCitizen
.
1/ Change the speed to 10f
2/ Run the game to visualize the speed within the game
3/ Repeat step 1 with 60f, 100f, 120f
2. Enemy drops:
In this sprint, we have tried to implement the enemy drop which allows players to get a better buff or weapon after the enemies are defeated. However, due to the lack of gameplay development, we couldn't test out the buff to locate as well as how to damage the enemy since the player cannot attack at the moment. Hence, there is a kill switch with the key pressed "K", it will kill gym bro which allows spawning the buff (if available) at the location. Also, the enemy has been given a weapon stat which will eventually become apart of this enemy drop system. This is shown in code below:
public class AtlantisCitizenConfig extends BaseEntityConfig {
public float movementSpeed = 0.05f;
public Weapon weapon = new Melee(1,1, 1, 1);
}
public void killEnemy(){
for (Entity enemy : ServiceLocator.getEntityService().getEntityList()) {
if (enemy.checkEntityType(EntityTypes.ENEMY)) {
enemy.flagDead();
}
}
}
Test Plan: Kill the GYM BRO enemies with key pressed K
1/ Run the game
2/ Press K to kill all GYM BRO Enemy (Ghost enemy is not treated as enemy within the game.
3. Attack pattern:
When there is collision between the player and enemies, the melee attack will be triggered. player's HP will decrease by the attack value and attack animation will be implemented.
if (targetStats != null) {
if (target.getCenterPosition().sub(entity.getCenterPosition()).x >= 0) {
// Player occurs at right
targetStats.hit(combatStats);
Me.getEvents().trigger("meleeRightAttack");
} else {
// Player occurs at left
targetStats.hit(combatStats);
Me.getEvents().trigger("meleeLeftAttack");
}
}
After rendering the attack animation, to turn back to chasing animation, the onCollisonEnd
method will implement it by default:
private void onCollisionEnd(Fixture me, Fixture other) {
Entity attacker = ((BodyUserData) me.getBody().getUserData()).entity;
attacker.getEvents().trigger("chaseStart");
}
Test plan: Run the game and move the player to be attacked, then watch the animation and HP status.
4. Javadoc:
Javadoc was added to various methods to explain clearly what the different methods do and how they are used. This was done in AtlantisCitizenConfig.java, ChaseTask.java, MovementTask.java, AITaskComponent.java, WaitTask.java, WanderTask.java, Entity.java and NPCFactory.java.
Design
Description
The Atlantis Citizen Gym Bro is a melee enemy that attacks the player.
1. Final design
1.1 Walking animation
1.2 Attacking animation
2. Inspiration
3. Design Process
The idea given by the story team is that in sprint 1 the enemy is a snobby gym bro. After researching and browsing through the gym people's cartoons, considering the need to implement this gym bro in pixel style, it was feasible to show off their arm and leg muscles, so it was initially decided that he would wear short sleeves and shorts. After doing some general drawing, it was found that holding the arms up was not logical, so I tried to lower them and bend them slightly to show the muscles. The pose was then decided. Moreover, as it was an enemy character, the facial expression was drawn as an angry face, more in keeping with the enemy.
4. Colours Palette
5. Draw pixel art
An iterative design was developed based on previous ideas. The first version was 128 x 128, later changed to 64x64 to make it a consistent size with other groups. The colour of the muscle line has been added to make the enemy looks stronger.
We then discussed that the character could walk backwards and forwards so that all four directions of the enemy were designed.
Walking animations of four directions of the enemy were designed.
For the attacks, we took reference from boxing movements, where the strikes diagonally above are attacking and visible. We make our enemies wear wristbands, more suited to gym people. Also, we started with the idea of an armed attack. However, taking into account that one arm would be hidden after turning left or right, we chose to use the left arm to attack when turning to the left.
6. User Testing
We conducted user testing using google forms to see what people expect to see in a level 1 enemy. We asked potential users about which key characteristics (body structure, clothing, colours) would be most appropriate for the design of the level 1 enemy. The corresponding feedback from the forms provided overarching insight into user preferences. The insights below are a summarization of the user insights collected with the preferences of the enemy development team.
- Strong, tall enemy with large sticking-out muscles ( Level 1 crook that hits the gym).
- White, purple colour scheme (colour scheme starts light and then evolves into darker colours the higher the enemy).
- Singlet and shorts (classic gym/scrappy enemy attire).
Sprint 2:
Gym Bro (From Sprint 1)
Added in animations for the gym bro by adding a Controller and also, by modifying the Chase and Wander tasks. This shows the GymBroAnimationController class:
public class GymBroAnimationController extends Component {
AnimationRenderComponent animator;
/**
* Creates the gym bro animation controller
*/
@Override
public void create() {
super.create();
animator = this.entity.getComponent(AnimationRenderComponent.class);
entity.getEvents().addListener("attackFront", this::animateAttackFront);
entity.getEvents().addListener("attackBack", this::animateAttackBack);
entity.getEvents().addListener("attackLeft", this::animateAttackLeft);
entity.getEvents().addListener("attackRight", this::animateAttackRight);
entity.getEvents().addListener("walkFront", this::animateWalkFront);
entity.getEvents().addListener("walkBack", this::animateWalkBack);
entity.getEvents().addListener("walkLeft", this::animateWalkLeft);
entity.getEvents().addListener("walkRight", this::animateWalkRight);
entity.getEvents().trigger("walkFront");
}
/**
* Animates the gym bro walking when facing right
*/
private void animateWalkRight() {
if (animator.getCurrentAnimation() != "walk_right") {
animator.startAnimation("walk_right");
}
}
...
This shows the attackAnimate() method added to the ChaseTask class (a similar animate method was also added to the WanderTask class):
private void attackAnimate() {
Vector2 enemy = owner.getEntity().getCenterPosition();
Vector2 player = target.getCenterPosition();
float y = enemy.y - player.y;
float x = enemy.x - player.x;
if (Math.abs(y) > Math.abs(x)) {
if (y >= 0) {
this.owner.getEntity().getEvents().trigger("attackFront");
} else {
this.owner.getEntity().getEvents().trigger("attackBack");
}
} else {
if (x >= 0) {
this.owner.getEntity().getEvents().trigger("attackLeft");
} else {
this.owner.getEntity().getEvents().trigger("attackRight");
}
}
}
Inside the Update() method of the ChaseTask, if the player appears in melee attacking range, both the value effect and attacking animation will be triggered.
public void update() {
float currentTime = gameTime.getTime();
movementTask.setTarget(target.getPosition());
movementTask.update();
// Attack target if it appears in range
if (getDistanceToTarget() <= attackRange) {
if (currentTime - lastAttackTime > 1500L) {
PlayerActions playerActions = target.getComponent(PlayerActions.class);
if (playerActions != null && !(playerActions.getSkillComponent().skillDamageTrigger())) {
target.getComponent(CombatStatsComponent.class)
.hit(owner.getEntity().getComponent(CombatStatsComponent.class));
}
lastAttackTime = gameTime.getTime();
}
attackAnimate();
} else {
walkAnimate();
}
...
}
Basic enemy attacking with dumbbell
After working with the weapon team, they sent us a picture of the completed dumbbell, which we added to the basic enemy by having him attack with dumbbells in his hands.
However, as the size of the dumbbell provided by the weapon team was relatively large (700 x 700 pixels), we re-drew a smaller dumbbell (still using the colours they supplied).