Obstacles Enemies Code - UQdeco2800/2021-ext-studio-2 GitHub Wiki

Table of Contents

Obstacles

Obstacle creation
Randomly generated obstacles on ground
Meteorite generation
Throns effect: characters slow down

Enemies

Flying monkey & Face worm
  Create flying monkey
  Obstacle Attack Task
  Create face worm
  Trigger the Face Worm on the game area

Shared function

Event Handler
  Collision event
  Animation and disappearance

Obstacle creation

In ObstacleFactory.java, There are three feature for the creation of obstacles (static, aggressive, can trigger animations and disappear). createBaseObstacle() is a common attribute for creating such obstacles. The difference between createPlantsObstacle() and createThornsObstacle() is the component or task parameter.

createMeteorite() is separated from the basic obstacle. This obstacle needs to be generated many at a time, and the layer needs to be set to a separate PhysicsLayer.METEORITE to specify them specifically.Stones are divided into three categories according to their size, and each category corresponds to a different basic attack.

    public enum MeteoriteType {
        SmallMeteorite, MiddleMeteorite, BigMeteorite;
    }

Basic attacks for all obstacles can be set in obstacles.json.

Randomly generated obstacles on ground

Randomly generate obstacles function is in ForestGameArea.java. There is a mechanism to prevent obstacles from being randomly generated at the same coordinate. Each time obstacles within a range are randomly generated, the coordinates of these obstacles are gathered in randomPoints. Each time a new random coordinate is generated, it will be checked whether the coordinate already exists, and if it is, it will be regenerated.

Since the interval between the first loading of the game and subsequent map updates is different, the function uses firstGenerate to distinguish these two situations.

    /* The number of each type of obstacle. Note: total obstacles cannot be greater than 20 (range of loading map)*/
    private static final int NUM_OBSTACLES = 2;

    private boolean firstGenerate = true;

    /**
     * Obstacles are randomly generated according to the position of the character. The first time it
     * is called, NUM_OBSTACLES obstacles are generated in the range of (0-30) units after the
     * player's position. For each subsequent call, the position of generating obstacles is twenty
     * units behind the player, and the generating range is 20 units.
     * <p>
     * For example, the first call to the player x position is 0, and the x range for generating
     * obstacles is 0-30.  The second call to the player's x position is 10, and the x range for
     * generating obstacles is 31-50.
     */
    public void spawnObstacles() {
        GridPoint2 minPos, maxPos;
        // Record the coordinates of all obstacles, Record the coordinates of all obstacles to prevent obstacles
        // from being generated at the same location
        ArrayList<GridPoint2> randomPoints = new ArrayList<GridPoint2>();

        int playerX = (int) player.getPosition().x;

        if (firstGenerate) {
            minPos = new GridPoint2(playerX, 0);
            maxPos = new GridPoint2(playerX + 30, 0);
            firstGenerate = false;
        } else {
            minPos = new GridPoint2(playerX + 21, 0);
            maxPos = new GridPoint2(playerX + 40, 0);
        }

        for (int i = 0; i < NUM_OBSTACLES; i++) {
            do {
                randomPos = RandomUtils.randomX(3, minPos, maxPos);
            } while (randomPoints.contains(randomPos));
            randomPoints.add(randomPos);

            //...
            //create an entity and spawn it
            //...
        }
    }

Meteorite generation

This function spawnMeteorites() in ForestGameArea can control the number of meteorites spawned. Here, meteorites are divided into three categories according to their size. The size of each type of meteorite fluctuates randomly within a certain range. You can control the generation of different numbers of meteorites and the number of random generations of various types of meteorites by changing the parameters of the function when calling the function.

    /**
     * Generate a certain number of meteorites, called by render() in MainGameScreen.java. The final number of meteorites
     * is the sum of all parameters.
     * <p>
     * Big size meteorites: 1.5 - 2 times of the multiples of meteorites texture,
     * total number is bigNum(+bigRandomRange), the values in parentheses are random.
     * Midden size meteorites: 1 - 1.5 times of the multiples of meteorites texture,
     * total number is middleNum(+midRandomRange)
     * Small size meteorites: 0.5 + randomSize: 0.5 - 1 times of the multiples of meteorites texture,
     * total number is smallNum(+smallRandomRange)
     * <p>
     * e.g. 1(+2) means that the number of generations is at least 1, and the final possible range is 1-3.
     *
     * @param bigNum           At least the number of large meteorites generated.
     * @param middleNum        At least the number of middle meteorites generated.
     * @param smallNum         At least the number of small meteorites generated.
     * @param bigRandomRange   The number of large meteorites that may be randomly generated.
     * @param midRandomRange   The number of middle meteorites that may be randomly generated.
     * @param smallRandomRange The number of small meteorites that may be randomly generated.
     */
    public void spawnMeteorites(int bigNum, int middleNum, int smallNum, int bigRandomRange, int midRandomRange,
                                int smallRandomRange)

Throns effect: characters slow down

The deceleration effect is triggered by the collision event in ObstacleDisappear.java.

MainGameScreen.setSlowPlayer(5f);

The key to slowing down is in MainGameScreen.java. The deceleration state is global, and every time a deceleration is set, the slowPlayer will be judged. If slowPlayer is false, set the time (slowPlayerTime) required for slowPlayer and the player to decelerate, otherwise extend the deceleration time. slowPlayer() is called by render(), and every time the game is rendered, DeltaTime is subtracted and the remaining slowPlayerTime is judged. If slowPlayerTime is not less than zero, the character will move backward (the same as the idea of the character automatically forward). Otherwise, the deceleration state is false.

    private static boolean slowPlayer = false; // Character deceleration state
    private static float slowPlayerTime; // Remaining deceleration time
    /**
     * Set the player deceleration time, the value set in the function is used by slowPlayer(), and the function is
     * implemented in render(). This function called by thornsDisappear() in ObstacleDisappear.java
     *
     * @param slowPlayerTime How many seconds the player slows down.
     */
    public static void setSlowPlayer(float slowPlayerTime) {
        if (!slowPlayer) { // if the player already slow, don't set double times
            slowPlayer = true;
            MainGameScreen.slowPlayerTime = slowPlayerTime;
        } else { // double set
            MainGameScreen.slowPlayerTime += slowPlayerTime;
        }
    }


    /**
     * Slow down the player, called by render().
     */
    private void slowPlayer() {
        if (slowPlayer) {
            slowPlayerTime -= ServiceLocator.getTimeSource().getDeltaTime();
            if (slowPlayerTime > 0) {
                player.setPosition((float) (player.getPosition().x - 0.06), player.getPosition().y);
            } else {
                slowPlayer = false;
            }
        }
    }

Create Flying Monkey and Face Worm

Create flying monkey

Define the attributes of fly monkey in NPCFactory.javaAdd physics, animation, AI and other components, and generate fly monkey in ForestGameArea.javaby using spawnFlyingMonkey().

      public static Entity createFlyingMonkey(Entity target) {

       Entity Monkey = new Entity();

          AITaskComponent aiComponent =
                    new AITaskComponent()
                    .addTask(new ObstacleAttackTask(target,10,6f));

         AnimationRenderComponent animator =
                    new AnimationRenderComponent(
                    ServiceLocator.getResourceService()
                            .getAsset("images/monkey.atlas", TextureAtlas.class));

        animator.addAnimation("1m", 0.2f, Animation.PlayMode.LOOP);

        Monkey
            .addComponent(animator)
            .addComponent(aiComponent);

       animator.startAnimation("1m");
       Monkey.setScale(2.3f, 2.3f);
       logger.debug("Create a Flying Monkey");
       return Monkey;
  }

Obstacle Attack Task

ObstacleAttackTask.java is sets the trigger function according to the distance between the character and the obstacle.When the distance is close enough, getPriority() will return the set priority parameter. enemyCreatePosition() will execute and return the position of the fly monkey, Later, it will be used to spawn a face worm at this location.

    @Override
    public void start() {
        super.start();
        MainGameScreen.setSpownFacehugger(enemyCreatePosition());
    }

    /**
     * @return priority
     */
    @Override
    public int getPriority() {
        float dst = getDistanceToTarget();
        if (dst < viewDistance) {
            return priority;
        }

        return -1;
    }
     // return the position of the entity
    public Vector2 enemyCreatePosition(){
        
        return owner.getEntity().getPosition();
    }

Create face worm

Define the properties of the face bug in NPCFactory.java, and add various components to it, such as AI (Call the chase task), animation, physics, texture.

    public static Entity createFaceWorm(Entity target) {
    Entity FaceWorm = createBaseNPC(target);
    BaseEntityConfig config = configs.faceWorm;

    AnimationRenderComponent animator =
        new AnimationRenderComponent(
            ServiceLocator.getResourceService().getAsset("images/Facehugger.atlas", TextureAtlas.class));
    animator.addAnimation("baolian1", 0.1f, Animation.PlayMode.LOOP);

    FaceWorm
        .addComponent(new CombatStatsComponent(config.health, config.baseAttack))
        .addComponent(animator)
        .addComponent(new EnemyAnimationController())
        .addComponent(new ObstacleDisappear(ObstacleDisappear.ObstacleType.FaceWorm));

    FaceWorm.setScale(2.4f,2.4f);
    logger.debug("Create a Face Worm");
    return FaceWorm;
  }
  }

Trigger the Face Worm on the game area

In ForestGameArea.java Generate Face Worm at current Flying Monkeys location. Called by render() in MainGameScreen.java and position the location of flying monkeys

     public void spawnFaceWorm(Vector2 position) {
        Entity ghost = NPCFactory.createFaceWorm(player);
        spawnEntityAt(ghost, position, false, false);
   }

In MainGameScreen.java Set the location where the monster is spawned, and called by start() in ObstacleAttackTask.java, the variable is used by the spokenFacehugger(). spokenFacehugger() generate face worm based on the position of the flying monkey, which is called by render().

     public static void setSpownFacehugger(Vector2 position) {
        facehuggerPosition = position;
        spownFacehugger = true;
    }
    
    
    private void spownFacehugger() {
        if (spownFacehugger) {
            forestGameArea.spawnFaceWorm(facehuggerPosition);
            spownFacehugger = false;
        }
    }

Event Handler

Collision event

All collision events are handled in ObstacleEventHandler.java.The constructor of ObstacleDisappear requires the ObstacleType variable. When ObstacleDisappear is created, the collision event adds different processing functions according to the type of obstacles (including enemies). One of the most important things in the collision handling function is to determine the layer of itself and other entities when the event is triggered.

    /**
     * The types of obstacles and enemies are used to determine the type of entity that triggers the event.
     */
    public enum ObstacleType {
        PlantsObstacle, ThornsObstacle, Meteorite, FaceWorm;
    }

    public void create() {
        hitboxComponent = this.entity.getComponent(HitboxComponent.class);
        animator = this.entity.getComponent(AnimationRenderComponent.class);

        switch (obstacleType) {
            case PlantsObstacle:
                entity.getEvents().addListener("collisionStart", this::plantsDisappear);
                break;
            case ThornsObstacle:
                entity.getEvents().addListener("collisionStart", this::thornsDisappear);
                break;
            case Meteorite:
                entity.getEvents().addListener("collisionStart", this::meteoriteDisappear);
                break;
            case FaceWorm:
                entity.getEvents().addListener("collisionStart", this::faceWormDisappear);
                break;
            default:
                logger.error("No corresponding event.");
        }
    }

Animation and disappearance

There are three corresponding problem:

  1. The texture needs to disappear when the animation is playing, otherwise the texture and the animation will overlap.
  2. The disappearance of obstacles has to wait for the animation to play for a while before it works.
  3. Due to the animation components involved, simply using the dispose() function cannot achieve the purpose of the individual obstacle disappearing (for entities that use the same animation, one animation is disposed, and all other entities cannot load the corresponding animation).

For problem 1

In Entity.java setRemoveTexture() and update() solved the problem of removing texture. The original code of updata() is modified. Every time the component is updated, it is now determined whether the texture needs to be removed.

    /**
     * Perform an update on all components. This is called by the entity service and should not be
     * called manually.
     */
    public void update() {
        if (!enabled) {
            return;
        }

        if (dispose) {
            this.dispose();
            return;
        }

        for (Component component : createdComponents) {
            // When texture and animation are given an entity at the same time, the texture needs to disappear when the
            // animation is played to avoid the conflict between the texture and the animation.
            if (removeTexture) {
                if (component.getClass().equals(TextureRenderComponent.class)) {
                    component.dispose();
                }
            }
            component.triggerUpdate();
        }
        if (disappear) {
            this.removeAfterAnimation();
            return;
        }
    }

For problems 2 and 3

setDisappear(), update() and removeAfterAnimation() used to solve these two problems. setDisappear() can set how long the animation will disappear after playing, and set the disappear variable of the current entity to true. Every time the component is updated, determine whether disappear is set to true. If so, call removeAfterAnimation(). In removeAfterAnimation(), get the duration of the animation that has been played. When the time length is greater than animationTime, pause the animation and dispose of all components except the animation component.

    /**
     * Set disappear to true. These variables play a role in removeAfterAnimation() and update().
     * @param animationTime Set how long the animation will disappear after playing
     */
    public void setDisappearAfterAnimation(float animationTime) {
        this.disappear = true;
        this.animationTime = animationTime;
    }

    /**
     * Let the obstacles disappear after playing the animation for animationTime second. Is called by update().
     *
     * The purpose of setting this method: When dispose() is used for animation components, all entities that use the
     * same animation become black boxes. Therefore, this method is currently used to make obstacles disappear.
     */
    public void removeAfterAnimation() {
        if (this.getComponent(AnimationRenderComponent.class).getAnimationPlayTime() > animationTime) {
            for (Component component : createdComponents) {
                if (component.getClass().equals(AnimationRenderComponent.class)) {
                    ((AnimationRenderComponent) component).stopAnimation();
                } else {
                    component.dispose();
                }
            }
            ServiceLocator.getEntityService().unregister(this);
        }
    }
⚠️ **GitHub.com Fallback** ⚠️