Melee Enemies - UQdeco2800/2022-studio-1 GitHub Wiki
Page Navigation
Jump to a specific section
- Summary
- Design Inspiration & Ideation
- Testing & Validation
- Technical User Testing
- Technical
- Sprint 2
- Implementation of Spawning
- Implementation of Boss
Summary
The melee enemies of Atlantis Sinks are integral to the player experience as they present an immediate threat to the city.
Sprint 1
Within sprint 1, Team 9 created a Pirate Crab enemy to provide the player character with a close range combat challenge.
Challenges
During these two weeks, several issues were faced with putting the enemy into the game from rendering to spawning the entity into the game world.
Rendering in Pixels
One of the issues we ran into during development was with rendering in pixels. It didn't occur that rendering was being done in units described to be equivalent to metres. When pixmaps for the healthbar were rendered onto the screen, they appeared to be really big and so Google was used to investigate the issue. An article was stumbled upon that described the solution. The solution was to temporarily swap the projection to render in pixels and then swap it back to its original.
This obviously looked like a feature that was going to get used several times so a class called RenderUtil
was created and placed in the util
package. This created an easy way to make draw
calls.
Unit Testing
While unit testing, mocking and spying methods were quite difficult, but a couple of rounds of Googling and searching stackoverflow for answers helped. The difficulty was that some methods couldn't be easily mocked or stub as they lived in a static class and Mockito does not interact well with static classes. So the solution was to convert RenderUtil
to a singleton so that only one of it will always be available in the entire game. Once this had been implemented, mocking and stubbing was then possible. The existing tests also really helped in figuring out how to write tests for the engine.
Spawning within the terrain bound
The provided engine had the enemies spawning randomly across a map which spanned the entire screen, but in Atlantis Sinks the map layout was changed to a confined area which can be expanded as the player progresses through the game. Due to this, the initial approach of the engine failed in spawning the enemy within the map confines. Hence to overcome the issue, it was decided to spawn the enemy with the code in respect to the environmental objects for now, with the intention of converting this to a function in a later sprint for a better implementation.
More information regarding the objectives and tasks associated with the development of the melee enemy: Enemies Task Ticket and Melee Character Design
Design Inspiration & Ideation
The design for the Pirate Crab melee enemy follows the design principles set out in the Base Enemy Entities page to keep enemies consistent across the design of the game.
More information on the design of melee enemies for Atlantis Sinks can be found here!
Testing & Validation
To test the effectiveness of the design of melee enemies, some form of testing has to be conducted to ensure it is recognisable and fits the theme of the game.
More information on the user testing for the Pirate Crab design can be found here.
Technical User Testing
Writing tests for the Pirate Crab successfully spawning in and chasing/attacking the player proved to be very difficult in JUnit; however, it is something that was found to be very easy to verify visually. The video below shows the enemy to be working as expected, but highlights the need for better obstacle pathing as the entity got stuck quite easily.
Technical
Config
Like all enemies, the Pirate Crab is configured within the NPCs.json
file with stats based off of the EnemyConfig
class. As a standard enemy, the Pirate Crab has relatively low health and therefore drops a low amount of gold when defeated (as can be seen below), but these values can easily be balanced from within the json file.
"pirateCrab": {
"health": 50,
"baseAttack": 10,
"gold": 10
}
Spawning
The Pirate Crab is spawned using the NPCFactory
class with the createPirateCrab()
method. This method takes a single argument for the target that the Pirate Crab should chase when in range of it. The method then returns a single Entity configured with the combat stats and textures for the Pirate Crab.
public static Entity createPirateCrabEnemy(Entity target) {
Entity pirateCrabEnemy = createBaseNPC(target);
EnemyConfig config = configs.pirateCrab;
TextureRenderComponent textureRenderComponent = new TextureRenderComponent("images/pirate_crab_SW.png");
pirateCrabEnemy
.addComponent(new CombatStatsComponent(config.health, config.baseAttack))
.addComponent(new HealthBarComponent(100, 10))
.addComponent(textureRenderComponent);
pirateCrabEnemy.getComponent(TextureRenderComponent.class).scaleEntity();
return pirateCrabEnemy;
}
The current implementation passes through the Player
entity as the enemies' target (with the intention of this being replaced with the Crystal
entity in future sprints) inside the spawnPirateCrabEnemy()
method from the ForestGameArea
class. The method contains a while
loop that attempts to find a position within the game world that it is able to spawn the Pirate Crab enemy at - if it is not able to find an available position after 1000 attempts, it will break the loop and not spawn the enemy in.
private void spawnPirateCrabEnemy(){
Entity pirateCrabEnemy = NPCFactory.createPirateCrabEnemy(player);
GridPoint2 minPos = new GridPoint2(0,0);
GridPoint2 maxPos = terrain.getMapBounds(0);
GridPoint2 randomPos = RandomUtils.random(minPos,maxPos);
int counter = 0;
while (this.entityMapping.wouldCollide(pirateCrabEnemy, randomPos.x, randomPos.y)
||entityMapping.isNearWater(randomPos.x, randomPos.y)){
randomPos = RandomUtils.random(minPos,maxPos);
if (counter > 1000){
return;
}
counter++;
}
spawnEntityAt(pirateCrabEnemy,randomPos,true,true);
}
Sprint 2
Sprint 2 was taken over by Team 4. The main purpose of this sprint was to introduce pathfinding for Melee Enemies, introduce melee enemy spawning and implement a boss melee enemy. This was done so that the player has an active threat where in past sprints enemies wondered relatively aimlessly and only spawned on game start up. The boss itself is a bigger more buffed version of an enemy that also applies special affects to surrounding enemies.
Further Refinements to Enemies
To further help and support this team and other teams code, a component was made that is soley used to classify entities called EntityClassification
. This entity classification contains a single value which is an ENUM of NPCClassification
and can be:
- NONE
- ENEMY
- BOSS
- PLAYER
- STRUCTURE This allows for easy checking of the type of entity and saves significant error handling when compared to checking for components.
Implementation of Spawning
Enemies, instead of spawning only at the start of the game and at random positions across the land tiles, now spawn from the water tiles surrounding the land and does so every night. A problem that arose were the wall boundaries at the perimeter of the island preventing the enemies from traveling to the land. To overcome this, a new method was created in ColliderComponent
to set the tangibility of an entity by changing the bit mask to fit the other entity's physics layer. The wall boundaries' tangibility were set only to the player, meaning it is only tangible to the player and any other entity can travel through it.
The spawning positions of the enemies was set with the help of team 3, this sprint's terrain team; they created spawnableTiles
which is an arrayList
of tile coordinates created in TerrainFactory
. The enemies would then spawn at a random coordinate from that arrayList.
To make the enemies spawn at night, team 9's DayNightCycleService
was used to trigger the spawnSetEnemies
method on the event of EVENT_PART_OF_DAY_PASSED
. On the event of part of the day changing, DayNightCycleService
passes the DayNightCycleStatus
which used to tell what part of day it is currently. Using switch case, the enemies are only spawned if the part of the day is NIGHT
.
The range of the numbers of enemies spawned is are set in MIN_NUM_CRABS
, MAX_NUM_CRABS
, MIN_NUM_EELS
, and MAX_NUM_EELS
. The numbers are chosen between the MIN and the MAX (inclusive). In addition, the day number in which the boss spawns is set in BOSS_DAY
that is also triggered in spawnSetEnemies
but only if dayNum
, which is gotten at the event of EVENT_DAY_PASSED
, is the same as BOSS_DAY
which is declared as 3.
Spawning Implementation Diagram
The class diagram of the implementation is below. Empty classes have not been modified. Dashed lines represents usage.
Implementation of Boss
Like all other enemies and in previous sprints, a json file was used to store the health and damage of the boss. This was called MeleeBossConfig
and can be located in the configs folder. The melee boss is created in NPCFactory
by attaching the various standard enemy components. Compared to a normal Melee enemy the boss has higher base stats including health and damage but does however have a significantly reduce speed to balance this.
What separates the boss from the a base enemy besides stats, is that it has the new component EffectNearBY
attached to apply special affects.
EffectNearBY
This is a new component designed specifically to be attached to the boss. It allows the boss to apply speed affects, regen or attack buffs to nearby enemies via the use of entity service. It allows for expansion of additional affects easily and more target types. In future sprints this will be expanded out to include effects on structures.
To instaniate this component it must be told if it will be applying it's affects to player, structure and/or enemy or combination of before. Then the user must enable the affects to be applied by using the relevant getters and setters to toggle them on/off as required. An example of attaching and constructing this to the boss is:
Entity boss = createBaseNPC(target);
boss.addComponent(new EffectNearBy(true, true, true));
boss.getComponent(EffectNearBy.class).enableSpeed();
boss.getComponent(EffectNearBy.class).enableRegen();
boss.getComponent(EffectNearBy.class).enableAttackDamageBuff();
where the true, true, true represents affect Enemy
, affect Player
and affect Structure
in that order.
From here the update functionn is used to trigger the speed, regen and attack buffs with relevant error checking.
Boss Implementation Diagram
The class diagram of the implementation is below. Empty classes have not been modified. Dashed lines represent usage which full lines represent dependency.
Testing
Implementaion of Spawning
During the initial implementations of the DayNightCycleService service to spawn the enemies at night, the game would always crash upon the creation of an entity. Because of that, a new test was created in GameAreaTest
to ensure that would not happen again.
Boss Implementation
Due to the shear number of dependencies when testing the boss implementation such as needing entity services, players, structures etc. Visual testing and peer code review testing was conducted. To support this the following video was taken showing the Boss being correctly spawned (Brown Square as placeholder image) and enemies regenning health: