Obstacles Enemies Code - UQdeco2800/2021-ext-studio-2 GitHub Wiki
Obstacle creation
Randomly generated obstacles on ground
Meteorite generation
Throns effect: characters slow down
Flying monkey & Face worm
Create flying monkey
Obstacle Attack Task
Create face worm
Trigger the Face Worm on the game area
Event Handler
Collision event
Animation and disappearance
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 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
//...
}
}
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)
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;
}
}
}
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;
}
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();
}
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;
}
}
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;
}
}
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.");
}
}
There are three corresponding problem:
- The texture needs to disappear when the animation is playing, otherwise the texture and the animation will overlap.
- The disappearance of obstacles has to wait for the animation to play for a while before it works.
- 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);
}
}