Exploring the unity game engine II
Exploring the Unity Game Engine II
In the previous blog, I introduced the basics of the Unity Game Engine as well as created basic player movement for a simple game. Now that we have a good grasp of the basics of unity, I want to slowly add more and more features to the game while also keeping the scope of the game relatively small. Some things I want to focus on implementing for this blog are:
- Basic Camera Setup
- More player movement functions
- Player Status (Health & Stamina)
- UI
- Basic Enemy AI
Basic Camera Setup:
Currently, I have the game camera position stationary over our environment. In the context of this project, this is very impractical. I want the camera to display the point of view of the player, as well as follow it around once the player is moving. To do this, I created an empty game object that is a child of our player called "CameraHolder" and positioned it to be relative to the head of the player. I place the game camera inside the "CameraHolder" gameobject and create a mouselook script. This script will control where the camera is looking, as well as the game sensitivity.
Inside the script I want to declare a "mouseSensitivity" variable, an xRotation variable, and a "playerBody" transform component.
These will return a 1 or -1 depending on the mouse input. I multiply these with the mouse sensitivity as well as Time.deltaTime so that its not reliant on frame rate.
With this, we should be able to look around freely once we run the game and now you're free to change your sensitivity to your liking. Upon running the game, you will notice that you are able to rotate your camera vertically in a 360 degree angle. I want to limit this so that it doesn't seem like the player is breaking their necks when they look up or down. This can be done using the "Mathf.Clamp()" function; this function takes an axis and limits its rotation to a positive and a negative value.
To finish things off, I also want the cursor to remain locked and disappear once the game is started. This can easily be done in the Start() method by entering
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
When we start our game, we should be in first person perspective and we are able to accurately navigate around the game scene.
Additional Player Movement:
The movement script that we created in the previous blog was very barebones and its something that I chose to revise completely. With the new movement script that I want to create, I want to add a jump, double jump, and sprint function.
To start, I attach a CharacterController component to our FirstPersonPlayer and create a PlayerMovement script. Inside this script, I want to call a CharacterController gameObject as well as a few variables that we will be using for our movement.
In the Update() method, I need to call Input.GetAxis() on both the vertical and horizontal inputs. We can then create a moveDirection variable which will take in both inputs and convert them to a vector which we can then use to physically move the player with the "controller.Move()" command. Additionally we can add a sprint functionality by creating an if statement. We can check to see if the Left Shift Button is being pressed and switch the walkSpeed for the sprintSpeed.
Now we have a more scalable version of our movement script. I also want our character to be able to jump and double jump. We can easily create a jump function by using an IF statement. If the jump key is pressed we can take velocity.y and add force according to our jumpHeight and gravity variables. We can also set the isJumping boolean to true.
With this, our player should be able to successfully perform a jump. However there is a slight problem where the player can rapidly press the jump button and the character will just keep flying in the air. Naturally don't want this feature in the game, and we can use the isGrounded boolean to combat this.
To use the isGrounded boolean, we first want to perform a groundcheck. A groundcheck is performed by using the checkSphere command. CheckSphere essensially will return true if there are any colliders overlapping the sphere defined by position in the world. We can start by creating a transform component, a groundDistance variable and a layermask component inside the script.
Inside the Update() method, we want to call the isGrounded boolean with CheckSphere using the variables we created. Now everytime that our character is touching the ground, isGrounded will return true.
We can then put the isGrounded boolean inside our jump IF statement so that the player is only allowed to jump when isGrounded is true. From here we can easily create a double jump function by adding an else statement that will allow the player to jump only if our isJumping boolean true.
valocity.y += gravity * Time.deltaTime;
We have successfully completed our movement script which should look like this:
Now once we run the game we should be able to move around, jump, sprint and double jump. There are a couple more movement mechanics that i'm thinking of adding to the game, but for the current stage of the game, it does its job.
Player Statuses:
The main things that I want to create for player statuses are health and stamina. These are pretty self explanatory I would essentially have a health variable that would decrease depending on what I do in the game (e.g. fall damage, enemy attacks) as well as a stamina variable that will continuously drain when doing certain movement mechanics (e.g. jumping, sprinting).
I start by creating a few variables for health and stamina; maxHealth, playerHealth, damage, maxStamina, playerStamina, recovery, sprintDrain, jumpDrain as well as a timer.
The first thing that I want to work on is the fall damage. For fall damage to work, the game needs to be able to detect whether the player is airborne or not. To do this, I used the same groundCheck component that I used in the playerMovement script. We can then create a fallDamage() method inside our script that will contain a simple IF statement. If isGrounded returns false our timer starts, and if the player is airborne for more than a certain amount of time, damage is set to the falltime. Once the player comes into contact with the ground, playerHealth is decreased based on the damage variable multiplied by the damageMultiplyer.
We can create another method called takeFallDamage() which will contain our groundCheck component as well as making sure that we only take fall damage if our player is alive.
Following this, we want to work on our stamina functionality. We can create a drainStamina() function inside our script which will also contain an IF statement. Essentially, if our sprint or jump button is pressed, we want the stamina to gradually drain.
We can then call the takeFallDamage() and drainStamina() methods inside our Update() method to ensure that the players statuses are actually updated. But before we can start the game, we have to initialize some of the variables that we created. This will be done inside the Start() method.
Once we start the game, we should see the health and stamina status inside the inspector. The stamina will drain if you're sprinting or jumping. And the health will decrease if you take fall damage.
Basic UI:
I want to be able to display the players health and stamina on the screen while the game is running. To do this I have to create a canvas. Inside the canvas I can add two images called fill and border. You can make the colors of each image to your liking, but since I'm making a health bar I choose to go with red and green.
We have to add a slider component to the canvas inside the inspector and set the "Fill Rect" to our "Fill" image, from there we create a HealthBar script which will allow us to control the slider to display how much health we have. All we have to do is to create a Slider gameObject in the script and set its values to maxHealth and playerHealth.
For the stamina bar, we basically follow the same process. We have our two scripts, I position the both the HealthBar and the StaminaBar to be located on the top left of the screen. However, if we start the game, the status bars wont actually be functional. Thats because we haven't fully connected them to the playerStatuses.
Inside the PlayerStatuses script, we have to call StaminaBarScript and HealthBarScript to allow us to use the methods from each script.
and setStamina() to change the values of our sliders to correspond to our actual values.
Basic Enemy AI:
Creating basic enemies will be a little bit trickier than everything else we have done so far. I want the enemy to remain in a certain spot and approach the player only when it enters a given radius. I also want the enemy to attack our player as well as have its own individual healthBar displayed above the model.
To start I create an empty gameObject called "Enemy" and create a cube to act as a model for the enemy. Since we wont be controlling the enemy manually, we have to attach a Nav Mesh Agent to our game object. This will essentially allow us to create a script so that the enemy can navigate the scene without player controls. We can then create an enemyController script and create all the variables that we will be needing.
Inside the Start() method, we have to initialize the "target" Transfrom gameObject that we created and assign it to the player. Additionally we can create a reference to the NavMeshAgent we added to our enemy model.
In the Update() method, I created a distance variable and set it to the distance between the player and the enemy. From there, we can create an IF statement that will allow our enemy to approach the player if it enters a given radius.
We can create a FaceTarget() and an AttackPlayer() method. The FaceTarget() methods only functionality is to rotates the enemy to face the player at all times. Inside the AttackPlayer() method, I have timer to limit the attack frequency of the enemy. It then calls a function inside the PlayerStatuses script that decreases the playerHealth based on the damage of the enemy.
One really useful thing that I discovered while working on this script was that we can draw gizmos around an object. This makes it easier to visualize the attack radius of the enemy.
As seen in the image above, if the player enters the red dome the enemy will approach the player and attack it every 2 seconds.
The enemy health was made pretty similar to player health. but instead of placing it in a static spot on the canvas, I created a billboard script so that the healthbar is always facing the player. This was relatively simple, I created a new script and called a Transform gameObject attached to the camera and then call a transform command that will allow for the canvas to be facing the player at all times.
To test the healthBar for the enemy, I settle with a simple IF statement inside the Update() method of our enemyController. Every time we press spacebar the enemy health is decreased. When the enemy health reaches 0 its destroyed.
With this, we have successfully created basic enemy behavior. The enemy will chase the player if its within range, attack and damage the player, as well as have a working health system.
Conclusion:
In this blogpost, we successfully configured our game camera, revised our player movement, added player health and stamina, created a UI for health and stamina, as well as created a simple enemy AI. There are still a lot of things that need to be added for this project to be truly called a game and I will definitely continue working on this project after the course is over. I hope that you learned something by reading my past two blogposts; although this blogpost is the final post that we have to do for the course, I found that creating blogposts encourages me to constantly learn new things as well as force me to fully understand the things that I am learning. With that being said, I will be continuing to publish blogposts throughout my game development journey and would welcome any feedback.
Comments
Post a Comment