Skip to content

Boss enemies

ShiyanSu edited this page Oct 19, 2021 · 15 revisions

Purpose

In order to make threats in the game more interactive with the player, we decide to add five functional boss enemies that can shoot life-threatening bullets or lasers to the game character. We create boss enemies based on alien characteristics to suit the space theme and game background story. The player should be cautious when they appear to the boss as boss enemies are more powerful than general enemies (i.e. robot and ufo) and they can cause more damage.

Boss enemies

Boss enemies belong to one alien family that rules the entire solar system. In the game, five alien boss enemies live on different planets (levels) and they have different attacking patterns. Each enemy character is inspired based on real-world animals.

  • Blue octopus (called alien soldier): It appears at level one. It is the dumbest alien because its sensory system can't detect the game character's position. Instead, it only has a self-protection mechanism that shoots multiple bullets in arbitrary directions. Each time the game character gets hit by its bullet, the player will lose 20 health rates.
  • Yellow box (called alien monster): It appears at level two. It spawns one baby alien to the player each time. This enemy will detect the game characters' position and shoot the baby alien bullet that traces the player. The player will lose 10 health rates if he gets hit by the baby alien.
  • Spider queen (called alien boss): It appears at level three and it is the evilest alien. Its multiple bullets all trace the player character. Thus, the player needs to do some agile jumps or sprints to escape from this boss. The player will lose 20 health rates if he gets hit by any bullet.
  • Green bee (called alien barbette or alien wasp): It appears at a random level. It only shoots one single bullet each time to its left-hand side. However, the shooting speed of this enemy and the moving speed of bullets are relatively quick. The player should either jump to avoid getting hurt or change to the platform. The player will lose 20 health rates if he gets hit by any bullet.
  • Sleepy squid (called alien laser hole or alien squid): When the squid feels sleepy, it starts its self-protection mechanism, which is the transparent rainbow lasers. The player should not pass by the squid when lasers are on, else the player will lose 20 health rates.

Implementation

Key Components

  • AttackTask: Defines how enemies shoot bullets.
  • AttackListener: Creates alienMonster's weapon.
  • AlienBossAttackListener: Create AlienBoss's weapon.
  • AlienSoldierAttackListener: Create AlienSoldier's weapon.
  • AlienBarbetteAttackListener: Create AlienBarbette's weapon.
  • AlienLaserAttackListener: Create AlienLaserHole's weapon.
  • EnemyFactory: Factory to create enemy and bullet entities, including createAlienMonster(), createAlienMonsterWeapon(), createAlienSoldier(), createAlienSoldierWeapon(), createAlienBoss(), createAlienBossWeapon(), createALienBarbette(), createAlienBarbetteWeapon(), createAlienLaserHole(), createAlienLaserHoleWeapon().
  • ForestGameArea, LevelTwoArea, LevelThreeArea, LevelFourArea: Area for initializing all the enemy entities.
  • BulletHitPlayer: Bullets disappear when they touch the player and deal 20 damage.

Usage

Creating a new enemy can be divided into the following parts. First create a task for the enemy, it can set the way of enemy shoots bullets. The AttackTask is written for all the enemy entities and could always be reused.

Code of creating AttackTask

public AttackTask(Entity target, float waitTime, int priority, float distance) {
        this.target = target;
        this.waitTime = waitTime;
        this.priority = priority;
        this.distance = distance;
        this.timeSource = ServiceLocator.getTimeSource();
    }

    @Override
    public void start() {
        super.start();
        endtime = timeSource.getTime() + (int)(waitTime * 1000);
    }

    @Override
    public void update() {
        if (timeSource.getTime() >= endtime) {
            this.owner.getEntity().getEvents().trigger("attack");
            endtime = timeSource.getTime() + (int)(waitTime * 1000);
        }

    }

    @Override
    public void stop() {
        super.stop();
    }

    @Override
    public int getPriority() {
        if (status == Status.ACTIVE) {
            return Active();
        }

        return inActive();
    }

    private float DistanceToTarget() {
        return owner.getEntity().getPosition().dst(target.getPosition());
    }

    private int Active() {
        float dst = DistanceToTarget();
        if (dst > distance) {
            return -1;
        }
        return priority;
    }

    private int inActive() {
        float dst = DistanceToTarget();
        if (dst <= distance) {
            return priority;
        }
        return -1;
    }
}

Then set up the listener, it can help create the entity of the bullet. For example, within the AttackListener class, add the event listener to the attack() function to create the weapon for the enemy.

Code of AttackListener

    @Override
    public void create() {
        super.create();
        entity.getEvents().addListener("attack", this::attack);
    }

    void attack() {
        gameArea.spawnEntity(EnemyFactory.createAlienMonsterWeapon(this.entity, target, gameArea));

    }

Creating component of BulletHitPlayer, it can make the bullet disappear and reduce the player's HP when the bullet hits the player. You can set the bullet damage here.

Code of BulletHitPlayer

public BulletHitPlayer(Entity target, GameArea GameArea) {
        this.target = target;
        this.GameArea = GameArea;

    }

    @Override
    public void create() {
        super.create();
        entity.getEvents().addListener("collisionStart", this::Hit);
    }

    private void Hit(Fixture attack, Fixture player) {
        Entity bullet = ((BodyUserData) attack.getBody().getUserData()).entity;
        Entity target = ((BodyUserData) player.getBody().getUserData()).entity;
        if(target == this.target) {
            bullet.getComponent(PhysicsComponent.class).getPhysics().addToDestroy(bullet);
            target.getComponent(CombatStatsComponent.class).decreaseHealth(20);
        }
    }

Finally, add AttackTask and AttackListener while creating the enemy.

Code of createAlienMonster(similar as createAlienSoldier and createAlienBoss)

public static Entity createAlienMonster(Entity target, GameArea gameArea) {
        AlienMonsterConfig config = configs.alienMonster;
        AITaskComponent aiComponent =
                new AITaskComponent()                  
                        .addTask(new WanderTask(new Vector2(3f, 2f), 0f))
                        .addTask(new AttackTask(target, 2, 10, 6f));

        Entity alienMonster = new Entity()
                .addComponent(new TextureRenderComponent("images/alien_monster.png"))
                .addComponent(new PhysicsComponent())
                .addComponent(new PhysicsMovementComponent())
                .addComponent(new ColliderComponent())
                .addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC))
                .addComponent(new ColliderComponent().setLayer(PhysicsLayer.OBSTACLE))
                .addComponent(new TouchAttackComponent(PhysicsLayer.PLAYER, 0f))
                .addComponent(new CombatStatsComponent(config.health, config.baseAttack))
                .addComponent(aiComponent)
                .addComponent(new AttackListener(target, gameArea));

        PhysicsUtils.setScaledCollider(alienMonster, 1f,1f);
        alienMonster.scaleHeight(2f);
        return alienMonster;

Add BulletHitPlayer for the bullet and set the motion trajectory for the bullet.

Code of createAlienMonsterWeapon(similar as createAlienSoldierWeapon and createAlienBossWeapon)

public static Entity createAlienMonsterWeapon(Entity from, Entity target, GameArea gameArea) {

        float x1 = from.getPosition().x;
        float y1 = from.getPosition().y;
        float x2 = target.getPosition().x;
        float y2 = target.getPosition().y;

        Vector2 newTarget = new Vector2(x2 - x1, y2 - y1);

        newTarget = newTarget.scl(1000).add(from.getPosition());

        Entity alienMonsterWeapon =
                new Entity()
                        .addComponent(new TextureRenderComponent("images/alien_monster_weapon_02.png"))
                        .addComponent(new PhysicsComponent())
                        .addComponent(new PhysicsMovementComponent())
                        .addComponent(new ColliderComponent())
                        .addComponent(new BulletHitPlayer(target, gameArea));

        alienMonsterWeapon.getComponent(TextureRenderComponent.class).scaleEntity();
        alienMonsterWeapon.scaleHeight(0.5f);
        PhysicsUtils.setScaledCollider(alienMonsterWeapon, 0.5f, 0.3f);

        alienMonsterWeapon.setPosition(x1 - alienMonsterWeapon.getScale().x / 2 + from.getScale().x / 2,
                y1 - alienMonsterWeapon.getScale().y / 2 + from.getScale().y / 2);

        alienMonsterWeapon.getComponent(PhysicsMovementComponent.class).setTarget(newTarget);
        alienMonsterWeapon.getComponent(PhysicsMovementComponent.class).setMoving(true);
        alienMonsterWeapon.getComponent(ColliderComponent.class).setSensor(true);
        return alienMonsterWeapon;
    }

Further implementation

In order to make the enemies adapt to different difficulty and level settings, it is better to alter the attack attributes of the enemies corresponding to different gaming situations. First of all, if we want to change the movement properties of the enemy instead of holding it in a fixed position, there are two ways which we could choose to implement:

  1. Add the WanderTask for the enemy entity to make it move in a given x-y area at a given speed. For example, we implement this functionality for the alien monster enemy.
        AITaskComponent aiComponent =
                new AITaskComponent()                  
                        .addTask(new WanderTask(new Vector2(3f, 2f), 0f));
        Entity alienMonster = new Entity().addComponent(aiComponent);
  1. Add the ChaseTask for the enemy entity to allow it to chase the player character in a given area. This will significantly increase the threat of the enemy. In the current level design, we did not enable the chase task for the enemies as it will significantly increase the difficulty of the game. However, one of the obstacles called UFO has been enabled this task. Secondly, we could also modify several settings of the AttackTask for the enemy to change its attack attributes including the attack trigger distance, and the attack frequency. For example, for the alien barbette enemy, we gave it a relatively faster attack frequency by setting the waiting time to 1 and increased its attack trigger distance to 12f. Below is the example of the implementation code:
 AITaskComponent aiComponent =
                new AITaskComponent()
                        .addTask(new WanderTask(new Vector2(0f, 0f), 0f))
                        .addTask(new AttackTask(target, 1, 10, 12f));

In addition to changing the settings of the enemy entities themselves, we could also modify the settings of the enemy's weapon/bullet. There are several settings that can be modified to make significant changes. Firstly, the shooting direction could be set in two various ways:

  1. The direction of attack will depend on the position of the player character and the bullet will be fired directly to the positon of the player character before last movement. For example, this way has been used by the bullets of the alien monster. Here is the example of implementation code: The new target is the fire target for the weapon which is the position of the player character.
        float x1 = from.getPosition().x;
        float y1 = from.getPosition().y;
        float x2 = target.getPosition().x;
        float y2 = target.getPosition().y;

        Vector2 newTarget = new Vector2(x2 - x1, y2 - y1);

        newTarget = newTarget.scl(1000).add(from.getPosition());

Then, set the attack target and add the moving property to the bullets.

        alienMonsterWeapon.getComponent(PhysicsMovementComponent.class).setTarget(newTarget);
        alienMonsterWeapon.getComponent(PhysicsMovementComponent.class).setMoving(true);
        alienMonsterWeapon.getComponent(ColliderComponent.class).setSensor(true);
  1. You can also give the weapon a fixed position as the fire target. The AlienSoldierWeapon is one of the example that uses this way.
        float x1 = from.getPosition().x;
        float y1 = from.getPosition().y;

        Vector2 target1 = new Vector2(x1 + 1, 0);
        Vector2 target2 = new Vector2(x1 - 10, 0);
        Vector2 target3 = new Vector2(x1 + 10, 0);
        Vector2 target4 = new Vector2(x1 - 30, 0);
        Vector2 target5 = new Vector2(x1 + 30, 0);

All those five targets are the fixed given position, they will not be affected by the player characters' position. Also, after we add a new constructor for the PhysicsMovementComponent class, the moving speed of the bullet could be changed by initializing the PhysicsMovementComponent class with a defined speed value. Below are the implementation of the new constructor of the PhysicsMovementComponent class and how to use it in EnemyFactory class when creating the enemy's weapon.

  public PhysicsMovementComponent(Vector2 speed) {
    maxSpeed = speed;
  }
Entity alienSoldierWeapon1 =
                new Entity()
                        .addComponent(new PhysicsComponent())
                        .addComponent(new PhysicsMovementComponent(new Vector2(5f, 5f)));                      

UML Diagram

Class Diagram1

Table of Contents

Home

Introduction

Main Menu

Main Game Screen

Gameplay

Player Movement

Character Animations

Enemy Monster Design and Animations

Game basic functionalities

User Testing

GitHub Wiki Tutorial

Game Engine

Getting Started

Documentation

Entities and Components

Service Locator

Loading Resources

Logging

Unit Testing

Debug Terminal

Input Handling

UI

Animations

Audio

AI

Physics

Game Screens and Areas

Terrain

Concurrency & Threading

Settings

Troubleshooting

MacOS Setup Guide

Clone this wiki locally