Final Zombie Shooter

This is my final report for the Zombie Shooter Game. From the game mechanics point of view, the objective of the game is to kill as many zombies as possible without dying. There are two weapons with different damage and unlimited bullets.

For the creation of the level, I used the simple apocalypse asset pack that I have from the Humble Bundle, to create this beautiful city. The player mechanics is the Easy FPS pack from the asset store. I heavily modified it to suits my needs. I changed the weapons system, HUD and the bullet logic, so everything is working correctly for the purpose of this game. Both Zombie characters and animations are from Adobe Mixamo.

The basic Zombie has 5 states: Wandering, Chasing, Dead, Go to sound, go to shoot. All of them differs from the behavior of the Zombies and the speed they are moving. The Zombie can be a Leader and has a maximum of 5 helpers. The helpers Zombies are following their leader as long as they don’t see a player or they don’t get shoot. Then they are not looking for their leader, just chasing the player.

If their leader is dead, they are looking for the closest one to them, when they can’t find him they are becoming the Leaders for the nearest Zombies without the leader.

The default state of the Zombies is the wandering where they are going from one waypoint to another. When they are near the waypoint, the are looking (shooting ray) for the next waypoint. If they can’t find any they are just going to the random one from the list.

The other states have self-explanatory names. When the player shoots, the Zombies in the given distance are going to the sound source. The same when they are being shot, they go where there was shoot.

Zombies triggered by sound
Zombies triggered by getting shoot
Horde behavior
Zombie behavior

The Big Zombie does not have a leader. He is much more powerful than the normal zombie but he is deaf. He can’t hear when the player is shooting, but his vision is much better with the greater distance and wider angle. Also after their death, they are not becoming ragdoll, but they are playing the death animation.

Big Zombie behavior

The regular Zombie has better AI mechanics then the Big one. I wanted to create the scenario where there are tons of small enemies that are easy to kill, but they are working together and the Big hard to kill an enemy that is behaving not that smart. All the Zombies have the 3d sound effect that helps indicate if the enemy is close or far away from the player. For the animations of the attacking Zombies, I put the exit condition on them. Because without it, when the Zombies could attack they started, but as soon as the player moves they stopped. This created very weird looking characters.

As for the spawner. I put the limit on the number of allowed Zombies, 50 for the small ones and 5 for big ones. While testing I decided that this is more or less the optimal number of enemies on this map. The spawner favors the Big Zombies over the regular, I made it that way to always keep the hardest enemies on the map.

Spawner logic

If I was about to start this project from the beginning I would make a couple of changes to the overall design of the AI system. As I started working on the horde mechanic around week 4, nearly all the AI logic was already in the game, so changing it would require starting from scratch. The 3d sound is also sometimes buggy, and paying too loud or too silent.

I would keep the logic for the Big Zombies, but the smaller Zombies would be controlled by one AI manager that will set the waypoints and state of each of the zombies. This would allow it to be the true horde with more natural movement. It would also have a big impact on the performance of the game, because now every AI character needs to calculate the staff for itself, in the AI manager scenario there would be only one calculation done by it.

My final implementation has its limitations that are visible during the gameplay. Sometimes you can see the lines of Zombies just going one after another until the player shoots to them. Also for the downsides of my game, the death animation of the Big Zombie is sometimes buggy and he floats in the air while playing it.

For the good aspects of my game, I really like the overall gameplay. Shooting to the Zombies is giving satisfaction, and the music from doom is making the game very enjoyable. The behavior of the zombies is good for the game. The player can feel when they are surrounding him and he needs to run and shoot to them. It reminds me of a little a Call of Duty Zombie modes, but much simpler. Overall I’m satisfied with the quality of my game.

References

https://assetstore.unity.com/packages/3d/characters/humanoids/easy-fps-73776

https://assetstore.unity.com/packages/3d/environments/simple-apocalypse-cartoon-assets-44678

https://www.mixamo.com/#/

Week 4 – The next Game idea

For my second project I decided to create the strategy game in the board game style. It’ll be focused on the province control, army and resource management with a turn base system.

The game screen will be combined of 2 elements: the user interface with the provinces, armies, resources on the left side, and the map on the right side.

The objective of the game is to conquer the capital of the enemy. The player will need to occupy the provinces with the resources and the manpower in order to have control over the map.

I decided to create the game in the Unreal Engine using the C++ instead of the blueprints. The AI will have the behavior tree determining the possible next move. I will implement the dijkstra algorithm for the path finding of the AI. In my case it may be superior to the A*, allowing to implement the higher values to the provinces that have usable resources. Thus making the AI choose the path that will better suits their needs.

The finite-state machine will determine the final movement that the AI opponent will make. It will make its decisions based on the behavior, it can be cautious, balanced, aggressive and defense. It will affect the the decisions and make each play slight different. For the testing of the game I’ll stick with one behavior and then later during the development process I will test more AI behaviors.

At this point I stopped doing the things from the AI tutorial and I implemented my spawner from scratch. Here I will only talk about the things that relates directly to the Tutorials, and I will leave the other AI improvement for the Final Zombie Post, to don’t repeat myself.

In my test scene, the spawner have 3 spawn points, after each spawn, the points is going to the next one. I introduced two enemies: Zombie and Big Zombie. Where the Zombie can act in groups, the Big Zombie is working alone. So Whenever I want to spawn Zombie, I’m looking how many Big Zombies there are on the map, if the amount is smaller than the max allowed number I spawn them. If there is max of Big Zombies I spawn the normal Zombie. If there is no Leader (more on that in next week post) I spawn the Zombies as a Leader, also every 5th zombie becomes leader.

Instead of texturing the simple cubes on the map I used the 3D assets to create the full map with probes, buildings and particle effects. For the little time put in the distribution of the models on the map, it significantly improved the overall quality of the game. Especially that this asset pack’s theme is Apocalypse, making it the perfect fit for that game.

Week 3 – Dynamic Difficulty Adjustment

The first thing I did this week was finishing the state machine of the Zombie character. My adding the new state Chasing and a new function CanSeePlayer.

By finding the Player object, and checking if it is in the given angle, the Zombie is shooting the ray and looking if it hits the player. In this case it’s very important to check for the layers, because the Zombie can shoot itself, making the player detection impossible.

public bool CanSeePlayer()
    {
        Vector3 playerPos = GameObject.FindGameObjectWithTag("Player").transform.position;
        if(playerPos == null)
        {
            return false;
        }
        //Debug.Log(Vector3.Angle(transform.forward, playerPos - transform.position));
        if(Vector3.Angle(transform.forward, playerPos - transform.position) <= 90f)
        {
            
            LayerMask layerMask = LayerMask.NameToLayer("Zombie");

            RaycastHit hit;

            if(Physics.Raycast(transform.position + new Vector3(0f, 0.5f, 0f), playerPos - transform.position, out hit, Mathf.Infinity, layerMask) && Vector3.Distance(transform.position, playerPos) < 45f)
            {
                
                return (hit.collider.tag.Equals("Player"));
            }
        }
        return false;
    }

I experimented a little with the distance and the angle of visibility for the player, and I ended up making it much bigger than the ones in the tutorial.

Next I added also the attack option for the Zombie. I downlaoded the attack animation from Mixamo, and made it when the Zombie is close to the player it attacks it. I needed to adjust the animation and leave the exit time for returning, so that the animation is not looking buggy when the player moves away from the zombie.

The last thing regarding the tutorial this week was adding the dead animation with the ragdoll. After adding it i had so much troubles with it not acting correctly. My ragdoll was flying into space, going under the map etc. But after the third try I finally made it working as expected for the game.

AI Problems

The biggest problem with the Game AI for me is adjusting the difficulty level. In most cases as the player starts the new game he’s choosing the normal difficulty level. But during the game he realizes that this level is not suited for him, it’s either too easy or too hard. Some games are allowing the player to change the difficulty during the playthorught, but then they are blocking the achievements for finishing game on certain level.

The players have the psychological barier for changing their difficulty level. They won’t go too low, because they don’t want to play on easy, but hard may seems too difficult for them. They are just stacked on the normal difficulkty.

The concept of the dynamic difficulty adjustment of the AI is the solution to that problem in most cases, but it is rarely used in the video games in regards of the AI. Racing games genre is the only one that fully adapted this. It’s very visible when you’re the last, the drivers in front are going slower, and if your’re first they are going faster.

But the games that really need system like that are the RPG games. How common scenerio is that at the begging you struggle with most of the enemies, and later after completing all of the side quests, the main story line is not a problem anymore. The AI may let the player bet the basic characters at the begging of the game, to improve their abilities later. So for example at level 1 the wolves are attacking you one by one, but later at level 15 the wolves are attacking as the horde, making them much more deadly.

Week 2 – Unity State Machine

For the second week of the Zombie Game Tutorial I was working on the movement of the agent on the nav mesh with the predefined waypoints.

At first I set up a dozen of the waypoints on the scene with the same “waypoint” tag. Then by changing the script for the AI character, from going to the certain point chosen by the player, it moves from one waypoint to another.

Then by changing the script responsible for controlling the AI character I added the enum variable, responsible for the different state changes. While being in the wandering state, the AI character will go to one point, then when he reaches this points, he will go to another one, and so on. This solution is not ideal, because it limits the movement of the AI only to the predefined points on the map. It doesn’t allow the free movement.

Putting too little waypoints on the map may lead to the repetitive movement, and putting too much points may create the effect of chaos, where the AI is constantly changing its direction and target from one point to another.

The last thing I did this week is putting the models on my Zombie and Player. I used the Adobe Mixamo and their pre-build characters for that.

The Unity pathfinder uses the variation of the A* algorithm. The A* is one of the oldest and well knows pathfinding algorithms with the very good performance factor. But due to its simple design it has some limitations.

In some cases it find the simplest, not the shortest path making it little harder for the AI controller to move. But it also allows different states and very custom option in regards of the height and step length. There are options inside the editor allowing the controller to make the jumps.

There is also the problem with the larg amount of agents, where the navmesh becomes too crowded and the characters get their state to not grounded and triggering the falling animation. This may be the problem while creating the map in the future, to don’t allow such behavior.

But overall, working with the unity navmesh is super simple and baking the navmesh, changing the not walk-able objects values is not a problem. Developing such tool from scratch would take a very long time, so is it very useful tool for the developers.

Week 1 – AI in Divinity Original Sin 2 / Zombie Tutorial

Divinity Origin Sin 2 is a CRPG fantasy game with a turn base fighting system. The AI in this game is divided into two main parts. One is during the map exploration and second is during the fighting turns. The game allow you to have up to 4 playable characters at once.

During the map exploration the AI main behavior task is to simple shadow fallow the player and avoid dangerous terrain like poison or fire. During the environmental puzzles of the exploration there are some lying traps, fireballs shooting from the walls or other unpleasant situations that may lead to the death of one of the characters. As the AI is very good at avoiding the things that can harm in on the ground, they lack of common sense when it cams to not standing in the way of incoming projectiles or not blocking the player in certain situations. In this cases the player must unchain its additional characters and do the logic puzzle by himself, rather than have the group of the suicidal NPCs following him. Unfortunately in this case the AI behavior is annoying to the player.

During the fighting phase the behavior trees of the enemies are very easy to predict. In most cases they are attacking the player with the smallest amount of HP. Even thought that that player may have the least damage, and the AI will benefit way more by killing other charaters, they decides to focus on that one most of the time. This leads to very easy predictions of the movement of the enemies and creating the plans that are exploiting the game mechanics. The enemies are also not fighting as a group, taking advantage of the skills combinations, but rather fighting alone all the time. This is crippling the experience of the game significantly, making players able to fight even large groups of enemies on higher level without any problem.

This week for the zombie tutorial I was meant to establish the basics for the project itself. I begun with setting up the scene and downloading the standard asset package from the asset store. Then I placed the basic geometry on the scene, with the plane and couple quads obstacles. With the use of the nagivation options I build the nav mesh on the scene and set up the proper distance from the obstacles.

Next I imported the prefab of the third person controller and give it a camera from the back of it and imported the AI third person controller. With the simple script for the AI character I enabled it to move it to the desired position by clicking the mouse on the plane. There is a raycast shot of the mouse to the specific position, and then the target object appears in that position, and the character moves there.

Final Space Racing Game

Space Racing Game Video

Space Racing Game Git

Space Racing Game Google Drive

Links on the top presents the video I was working on, that shows the physics aspect in my game and the source code of it. It records the gameplay with all-important features in the game, like acceleration, deceleration, turning, collision and colliders. 

[after downloading the game, you need to build it, because it doesn’t want to run from the .exe file]

Movement

The most important feature in the space racing game like this is movement. I was trying to mimic the real world, by using the physics formulas in my movement, but there are too many forces in the world to make those calculations possible. That’s why I focused mostly on the acceleration and velocity. 

In space where I place my game, all objects once moved remains in the same speed and direction unless there is other force that will prevent this movement. In space, there is no air resistance or gravity that will affect our hovercraft. I nearly recreate this movement, because I don’t want my ship to move forever in space after the force applied to it. That’s why I used the Newtons Law’s of Motion to calculate the movement of the Hovercraft. Using it’s second law we can calculate the acceleration and the velocity.

First thing is the acceleration. It’s present in the equation:

F = m * a

That can be transformed into:

a = F / m

In my game, I set the maximum force to 1.5, and the mass is 5.  From that, we can create a graph showing linear function for acceleration, that describes the rate of change of velocity over time. 

Screenshot 2019-02-22 at 11.14.06.png

From here we can go to calculate the velocity itself. It can be calculated from the function, where t stands from time, and dt for deltaTime that will take value between 0 and 1000 depends on the time change. From that we can calculate our current position

v(t) = v(t – dt) + a(t) * dt

Where current velocity equals the last velocity plus current acceleration multiplied by delta time

p(t) = p(t – dt) + v(t) * dt

Where current position equals the last position plus current velocity multiplied by delta time.

I introduced the mass and the force as a float, where the force is changing depends on the user input. The rest values are stores as a vectors 3. This calculation may give a very good approximation of the results in my game.

velocity over time

Due to having the force as the variable, the velocity has a logarithmic growth. At first it’s growing slowly, and then it’s grow is faster over time and force.

Acceleration, deceleration and collision

Friction

Game is happening in the space, but to add some addition feature I thought about the friction. My Hovercraft is colliding with the racetrack all the time, so it rubs with it. I did some research about the friction, and decided to calculate the friction in the game, in the same way as the Earth friction, because it may be more reliable. At first I needed to calculate the formula for the coefficient of friction in the moving object.

I skipped the calculation of the static friction, and only calculate the dynamic friction, so this one for the objects already in motion. Because we already started moving the hovercraft, so calculating the static friction will only complicate the code

I took the absolute value Fromm the force, divide it by mass multiplied by the 10, so the value for Earth, and got the coefficient of friction. That later multiplied by the mass can give a dynamic friction.

Later I multiplied the current acceleration by the value of 1 – dynamic friction, so it’s harder for the object to move.

Collision

Next very important thing besides the movement is collision system. I introduced 2 colliders in my game. Cube and sphere collider. Cube collider is used in 2 typed of obstacles, the brown ones, that act like bumpers and blue ones that are speed boosts. Also Hovercraft has a cube collider right now. The sphere collider is used with the projectiles that I put in the game, that can be shoot and destroy the obstacles.

Cube collider

Cube collider is just invisible cube (red edges are visible only in debug mode), that detects the intersection of the points. The points most on the -x is said to be the minX and the most on the x axis is maxX, the same corresponds to the other axis. Then the function can check for the crossing of those functions, and return the if necessary. The collision of 2 cubes in 3D is presented in the diagram below.

I also introduce the int name in the Collider header, that can act like the tag for the various gameObjects. By that I can said what object needs to do if collides with specific other game object. That helped me to introduce the bouncing, speed boost and friction in the game.

For example hovercraft have different things assigned, depends on what he collides with. If he collides with blue obstacle (name 2), player is getting the speed boost.

Bouncing

For the collision with the brown obstacle I wanted to make it bounce. I research how this is working in real world. It’s bouncing in the opposite direction that it touched the object. In my case, because I’m calculating the heading vector in the acceleration, all I need to do is to change the sign next to the force, so from positive to negative or vice versa. This will change the direction of the hovercraft to the opposite one, maintaining all the attributes of the velocity.

I was trying to figure, how to program the bouncing, and what variables include in it. After couple of drawings I decided not to use heading itself, but the force. It’s giving a much better effect.

Sphere collider

To introduce new game objects with a good colliders I introduced the sphere collider. It’s attached specifically to the projectile, and moving with it. In the gif below there’s shown bigger projectile with a little bigger collider, to present how it looks.

Calculation of the sphere collider can be a little more complicated than the cube collider. There are 2 options here, one is the collision of sphere with sphere, and other is sphere with cube.


In the first one we just need to check if the distance between the both centres is smaller than the radius of 2 spheres combined. If yes, we can return true, and record the collision.

Then there is a collision with a cube. To make it happen we need to calculate the closest point to the centre of the sphere, and look if distance between it, and the centre of the sphere is smaller than the radius to return the true. Due to the limitation of the Collider class unfortunately I didn’t put this in the code, because it was not possible to calculate the closest point in this case. To did that I would need to rewrite the collider function, or use 3rd party library.

Conclusion

Strengths

The main strength of my game is its relation to the real world physics. The calculations of movement are based on the real equations, the hovercraft has it mass, force, speed. In addition, it reacts to friction.

Build of collisions is the next strength in my game. I introduced 2 different colliders, which has different functions. One is just basic cube collider, while other is a sphere collider that inheritance from 2 classes, so it can move with the projectile as a game object and still behave as a collider,

In addition to that, there are couple additional feature. Shooting mechanism is one of them. It fulfilling the laws of physics, and is a test for the cube collider. Bouncing is also working good. It takes the force and reverse it, making the direction completely opposite.

Weaknesses

First weakness is present in the movement of the hovercraft. It is calculated by the laws of physic, but if we place it in the space there is quite hard to calculate all the physics laws. There isn’t in a world the simulator of space ship racing, and this is also no that. That’s why I needed to introduce more arcade deceleration in my game, to make it pleasant to play.

Colliders can be also seen as a weakness. Calculating the collision between so many object is taking a lot of computing power. I didn’t introduce any new technique to do that, so it’s taking n^2 where n is a number of game objects. I needed to decrease the initial number of my game objects due to that calculations. It can be changed for example by calculating only those object that are close together or by reducing the checking, so check only for one object.

The last important weakness is that I didn’t introduce the full sphere collision. there’s only collision between the 2 spheres, but the sphere-cube one unfortunately will require a lot more time to introduce, without using the 3rd party libraries.

Reflection

By and large, this project gave me a lot of new knowledge about C++ and introduced me to the openGL. The beginnings were tough. But, I was really happy at the end of the project, when I understood was was happening, and what I’m doing. I’m glad of what I’ve done here, I know that in the future it can be a lot more, but for about 5 weeks of learning openGL the results are awesome for me.

References:

http://www.softschools.com/formulas/physics/static_friction_formula/30/

http://www.softschools.com/formulas/physics/friction_formula/32/

http://www.pstcc.edu/departments/natural_behavioral_sciences/Web%20Physics/Experiment%2006%20PHYS%201310.htm

https://www.real-world-physics-problems.com/bouncing-ball-physics.html

https://github.coventry.ac.uk/217CR-1819JANMAY/TEACHING-MATERIALS

Week 5 – Game Engine Tutorial

During the last week before our assigment I finally added the collision detection to my game engine. I began from the basic box collision detection, but I also tried to introduce the sphere collider to the game.

At first I needed to create the header file alone, that will store the virtual functions that can be overwrites in the future by functions that will inherited from this class, so the collider.h.


It has a vector for the centre of the collider, so we can determine its centre. Bool for the collision function, that allows us to check for collision with other objects in the scene and a bunch of floats that will determine the corners of the collider.

Now I can create a CubeCollider class that will inherit from the collider header. I need to overwrite all the functions defined in the collider, in other case my CubeCollider will be abstract class, so I can not spawn new gameObjects with it. We can do it by calculating every corner depends on the collide centre and the size of the collider. This is an example of the overwrite function.

Now we actually need to check when the blocks collide. We can do that by checking if the point are overlapping each other. This is presented by the diagram below. We can write the simple checker that will look at the overlapping and only return true if whole points overlap each other.

[insert diagram]

Then we just need to add the collides function to the obstacle and hovercraft with the specific values, to determine the collision. And after that write the checker function that will be placed in the GameEngine update function, to determine if the objects are colliding with each other.

This is not the most effective way for doing it. the Big(O) notation for it is n^2, so not the best way of doing it, especially if we have many game objects , it may crash even the best pc.

This gif will show you how the collision is working right now.

After putting together the box collider I started thinking about implementation of the sphere collider, because not all objects are boxes. It was a little more tricky. It depends what objects it collides with. If it’s other cube, we can just calculate if the distance between 2 centres is smaller than the radius of the bigger sphere. But with the box collider, we’ll need to find the closest point to the centre of the sphere, and check if the distance between this point and the centre of the sphere is smaller than the radius of the sphere. Problematic in this case is finding the closest point, because we need to calculate much distances, and pick the smallest one. It can take a lot of computing power. I didn’t implement it fully, because I found some troubles with it and luck of time, but I was trying to implement the general idea to present, and fix it in the future.

Week 4 – Game Engine Tutorial

In week 4 we can finally make our Hovercraft move! That’s the major step forward in the development of the game because it can allow us to finally play it. Other important difference in the project is adding the GameEngine class that will allow us to move everything from the startGame script to the GameEngine itself, leaving only the main function in the StartGame.

In the code provided we had the function to register the keyboard input, but in only allows us to record the pressing the key down.

glutKeyboardFunc(keyInput);

But with the use of the other glut function, that records the release of the key, we can set the book value that will be true if the key is pressed and false when it’s released. 

void glutKeyboardUpFunc(unsigned char key, int x, int y)

For creating the key-value pairs, so we can use any key that is press we are using the map value that will be set in the GameObject header file.

static std::map <int, bool> specialKeys;

static std::map <char, bool> keys;

To make the movement read we need to see know the direction were are we facing. That’s why in the GameObject class we introduce the heading vector, that will tell us that. 

In the Hovercraft cpp we can now refer to the specialKeys function in order to move the Hovercraft. For the movement forward and backward we can add or subtract the heading vector multiplied by the movement speed to the position of the hovercraft. The turning can be affected by the rotation angle that is a part of a heading calculations. 

if (specialKeys[GLUT_KEY_UP]) {
this->position += this->heading * moveStep;

}

if (specialKeys[GLUT_KEY_LEFT]) {

this->rotationAngle += turningSpeed; //in degrees not radians

}
this->heading = glm::rotate(this->startingHeading, glm::radians(rotationAngle), glm::vec3(0.0, 1.0, 0.0));

this->heading = glm::rotate(this->heading, glm::radians(pitchAngle), glm::vec3(0.0, 0.0, 1.0));

Other important thing for the Hovercraft is to use the update functions. It works in the same way as we know for example from unity, but in this case we need to do everything by ourselves.

//Update the game state.

void idle() {

//Calculate delta time (in mili seconds).

oldTimeSinceStart = newTimeSinceStart;

newTimeSinceStart = glutGet(GLUT_ELAPSED_TIME);

int deltaTime = newTimeSinceStart – oldTimeSinceStart;

//If the last frame was rendered less than 1 ms ago, the detalaTime will be 0 ms. This causes problems in calculations, so sleep for 1ms to adjust.

if (deltaTime == 0) {

Sleep(1);

newTimeSinceStart = glutGet(GLUT_ELAPSED_TIME);

deltaTime = newTimeSinceStart – oldTimeSinceStart;

}

//Run update for all game objects.

for (std::vector<GameObject*>::size_type i = 0; i != gameobjects.size(); i++) {

gameobjects[i]->update(deltaTime);

}

//ask glut for a redraw after all the updates

glutPostRedisplay();

}

So the script calculates the delta time based on the glut timing in milliseconds. We also need to check if the delta time is not equals 0, it can happen on some faster machines. In this case we just need to wait and give it 1 instead of 0. 

The last thing is implementing everything to the gameEngine class. To do that I need to mostly copy most of the code from the startGame and place it in the gameEngine. Start game has the main function left, with the declaration and it includes nearly all the header files, so it can spawn all game objects and refer to them from this script.