Oreon Health System
Week 5 involved working on the Oreon Health System which encompasses logic (currently just) related to the reduction in health of the Oreon based on the situation. The initial implementation considers hunger
as a parameter for this reduction i.e.
an Oreon that has been hungry for a while and cannot find a Diner in the village will lose health over time, also notifying the player about its “plight” every once in a while. Every time the needs_food
node is hit in the Oreon’s behavior tree
and the Oreon cannot find a DIner in the village the OreonHealthSystem
is triggered. If there are no tasks available in the village, then the huger check node is hit every time the BT is run, i.e this would lead to a deduction in the Oreon health parameter
every tick, which would mean a hungry Oreon would have a rather short lifespan. I thought of two probable solutions for this:
- Reduce the health every tick but by a very small quantity, so it seems that Oreon can fight hunger for a long time.
- Use a variable to keep track of the last check and decide whether to run the HealthSystem logic based on the time elapsed.
I decided to go with the second approach, as the first approach would be rather resource intensive considering that the HealthSystem logic would be run every time the game updates, which in turn leads to fetching the OreonAttributeComponent
, changing a field
, replicating this change over the network every tick. So, I added a lastHungerCheck
field to the component and use this decide whether to go ahead with the reduction or not.
What happens when Oreon health hits zero?
An obvious question which was just lurking around the corner while I was working on the health deduction logic. So, I had to destroy the Oreon entity, but this would lead to Oreons just outright vanish from the world, not an elegant solution. Luckily, there are already implemented
animations for the die sequence in the Oreons module. Now it came down to how to run this animation once and destroy the Oreon entity after. In order to accomplish the above the effect I added a
OreonDeathSystem
.
Oreon Death System
A rather grim topic :D
This system handles the destruction of the Oreon entity after it has reached zero health. The challenge was to let the death animation play and trigger the destroy()
method for the Oreon entity after it has ended. The logic behind this is first of all the Behavior
component is removed
so that the Oreon stops looking for any tasks, then all the animations from its skeletal mesh are removed and the ones defined in the DyingComponent
in the Oreon prefabs are attached. Now, to calculate the duration of the animation I used the getFrameCount()
method(code snippet
below) and multiplied with the time required for each frame, for every animation defined in the pool in the DyingComponent
.
float lifespan = 0;
for (MeshAnimation meshAnimation : skeletalMeshComponent.animationPool) {
lifespan += meshAnimation.getTimePerFrame() * (meshAnimation.getFrameCount() - 1);
}
This lifespan is then added to the current game time to get the time of death for the Oreon which is stored in a field defined in a DeathTimeComponent
which
is then added to this Oreons. The update()
method of a UpdateSubscriberSystem
is called every the game updates, so this method in the Death System checks if the game time is past the death time for the all the entities which have a DieAnimationRunningComponent
and destroys the corresponding entity.
@Override
public void update(float delta) {
float currentTime = time.getGameTime();
for (EntityRef oreon : entityManager.getEntitiesWith(DeathTimeComponent.class)) {
DeathTimeComponent deathTimeComponent = oreon.getComponent(DeathTimeComponent.class);
//if the death time has already passed
if (deathTimeComponent.deathTime < currentTime) {
oreon.destroy();
}
}
}
Abandoning an assigned task
This week I also managed to pull a card from Backlog on my board, yay! If an Oreon was stuck “tricky” situtaion(like if it were made to fall in a ditch made using several TNT blasts :D) from which it cannot find a path to its target block in the assigned task area,
it kept on jumping around and trying to get there but in vain. So to handle to handle this situtaion the Oreons must have the ability to ‘let go’ of the task and critter
randomly for a while, which might lead them to an open area. In order to decide whether the Oreon is stuck
a time based approach i.e based on the time elapsed since the Oreon was assigned the task, but this could lead to erroneous situtation if the Oreon was at a far off location from the assigned area or also would be a waste of time if the Oreon got stuck in the start of such an
interval. A better was to check if the Oreon had a collision with the same block twice in a row. The move_to
node in the Behaviors module uses the HorizontalCollisionEvent
to decide when an NPC should jump so as to get around
a block in front of them. I used this event in the TaskManagementSystem
to store the last collision location and check against this value when the next collision occurs, if they are same then the Oreon is freed and this task is added back to the Holding for other Oreons to take
up.
Some other minor bug fixes and enhancements
Multiple Holdings attached to a player
I also came across a minor bug while testing for another GSoC project under the org the Record and Replay project which enables you to record all the events in the game that the player experiences and then run them later. I ran a saved game with MOO enabled to test the
recording though the project is still in an initial state and does not support all events required in the module, I did come across a bug. The HoldingComponent was attached to the player using the onPlayerSpawn()
method, so
every time the player is spawned a Holding component would be attached to their entity, though this was fine when starting new games, playing an already saved game would lead to a new Holding for the player every time a saved game is started, so the Oreons already
spawned would be looking
for tasks in a different holding than the one the player is adding tasks to. The fix for this was pretty easy in comparison to the impact it had on the gameplay logic, it involved just adding a check for the component if it is already attached to the player entity and only add if not found.
All Oreons now have a name
I used the NameGenerator to get a name for each of the Oreons and then added a NameTagComponent
with their first names, to obtain a floating name over each Oreon. This also lead to another handy enhancement of adding these
names to the notifications sent to the player so as to give them a more personalized feel :D.
.