Correction: In the video, we made a reference to the PlayerController
component in the PlayerInteraction
class. However, we later found that we didn’t make use of it at least in this part, so you can choose to skip that bit in the video for now, as it is redundant. They are highlighted in red in the finalised codes below.
Ever wanted to create a game like Harvest Moon in Unity? This is Part 3 of our guide, where we go through how to set up farmland elements that our player character will interact with. You can also find Part 2 of our guide here, where we went through how to set up our player camera.
1. Finding the textures
Our plots of interactable land will have 3 states:
- Soil: The default state of the soil.
- Farmland: Land that is plowed from a hoe.
- Watered Farmland: Farmland that has been watered with a watering can.
We need a texture for each of the 3 states. To find suitable free textures, we can search Polyhaven for them.
When you have found the textures you want, extract and import them into your project. In this tutorial, we place them in the Imported Asset > Farmland folder. We also create a separate folder for each set of textures (i.e. the Watered Farmland textures are under Imported Asset > Farmland > Watered Land).
In your Scene, create a cube and drag a texture into it. This will generate a Material asset that is automatically assigned to the cube. On the generated material, assign the texture set’s normal texture to its normal map, and the texture with the _disp prefix (e.g. aerial_ground_rock_disp_1k.png
) to the height map, as shown in the image below:
Do the same thing to the other 2 sets of textures. You should end up with 3 materials. Set the cube to use the Soil Material, and make it into a Prefab called Land.
In the materials we made above, we are making use of Albedo, Normal and Height maps to add different kinds of details. To find out more about what kinds of details these maps add, click on the links we have provided.
2. Handling the material changes
Create a new script, Land.cs
, and attach it to the prefab. We will have to represent each of the materials we’ve just set up with a corresponding variable, so add the highlighted line below into your script:
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour { public Material soilMat, farmlandMat, wateredMat; // Start is called before the first frame update void Start() { } // Update is called once per frame public void Update() { } }
Article continues after the advertisement:
a. Defining constants
In our Land.cs
script, we will need to represent the 3 states that a piece of land can have. This can be done by creating a new enumeration type (i.e. enum type), which is used to define a set of constants. In this case, our 3 possible enumerations for our land type — Soil, Farmland, and Watered — will be members of an enumerated type called LandStatus
.
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour { public enum LandStatus { Soil, Farmland, Watered } public Material soilMat, farmlandMat, wateredMat; // Start is called before the first frame update void Start() { } // Update is called once per frame public void Update() { } }
On the Land prefab, assign our materials to their corresponding material variables on the script.
Drag the prefab into the scene so we can test it later.
We don’t need the Update()
function, so it can be removed. Also, represent the current state of the Land prefab with a variable called landStatus
:
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour { public enum LandStatus { Soil, Farmland, Watered } public LandStatus landStatus; public Material soilMat, farmlandMat, wateredMat; // Start is called before the first frame update void Start() { }// Update is called once per frame public void Update() { }}
b. Processing state changes
When the Land prefab changes its state, the following needs to happen:
- The variable
landStatus
, needs to be updated to reflect the changes - The material of the prefab needs to be updated to reflect the current state.
We can create a function to handle these. In addition to that, we need to access the Renderer
component of our GameObject to handle the material changes.
Hence, make the following changes:
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour { public enum LandStatus { Soil, Farmland, Watered } public LandStatus landStatus; public Material soilMat, farmlandMat, wateredMat; new Renderer renderer; // Start is called before the first frame update void Start() { //Get the renderer component renderer = GetComponent<Renderer>(); } public void SwitchLandStatus(LandStatus statusToSwitch) { //Set land status accordingly landStatus = statusToSwitch; Material materialToSwitch = soilMat; //Decide what material to switch to switch (statusToSwitch) { case LandStatus.Soil: //Switch to the soil material materialToSwitch = soilMat; break; case LandStatus.Farmland: //Switch to farmland material materialToSwitch = farmlandMat; break; case LandStatus.Watered: //Switch to watered material materialToSwitch = wateredMat; break; } //Get the renderer to apply the changes renderer.material = materialToSwitch; } }
When the player first encounters the land, its default state should be Soil. So let’s set that in Start()
:
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour { public enum LandStatus { Soil, Farmland, Watered } public LandStatus landStatus; public Material soilMat, farmlandMat, wateredMat; new Renderer renderer; // Start is called before the first frame update void Start() { //Get the renderer component renderer = GetComponent<Renderer>(); //Set the land to soil by default SwitchLandStatus(LandStatus.Soil); } public void SwitchLandStatus(LandStatus statusToSwitch) { //Set land status accordingly landStatus = statusToSwitch; Material materialToSwitch = soilMat; //Decide what material to switch to switch (statusToSwitch) { case LandStatus.Soil: //Switch to the soil material materialToSwitch = soilMat; break; case LandStatus.Farmland: //Switch to farmland material materialToSwitch = farmlandMat; break; case LandStatus.Watered: //Switch to watered material materialToSwitch = wateredMat; break; } //Get the renderer to apply the changes renderer.material = materialToSwitch; } }
3. Setting up the interaction point
Now that we’ve set up the Land script for state changes, we can move on to create a way for the player to interact with it. In the Player prefab, create an empty GameObject and place it slightly in front of the player model. Call it Interactor.
Create a new script called PlayerInteraction
, and attach it to the Interactor GameObject. By the end of this article, this class should work with other classes to handle player interaction, as summarised in this table:
Class | Role in Interaction |
PlayerController.cs | Processes keyboard/mouse inputs from the player and directs it to PlayerInteraction |
PlayerInteraction.cs | Figures out which Land instance to interact with (selection). |
Land.cs | Handles state changes based on input from PlayerInteraction.cs |
Article continues after the advertisement:
4. Selection
The selection system essentially works by sending a Raycast down the y-axis of the Interactor GameObject.
a. Checking with Raycast
As we will need to check if the player is standing on Land at every frame, we will put this in Update()
. Add the following to PlayerInteraction.cs
:
PlayerInteraction.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerInteraction : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { RaycastHit hit; if(Physics.Raycast(transform.position, Vector3.down,out hit, 1)) { OnInteractableHit(hit); } } //Handles what happens when the interaction raycast hits something interactable void OnInteractableHit(RaycastHit hit) { Debug.Log(hit); } }
For now, it just debugs the RaycastHit
object when the player walks over anything.
b. Distinguishing interactables from non-interactables
We need the function to discriminate Land from other objects. Before that, let’s add a Quad to the scene for testing purposes.
We can set the interactable Land apart from the Quad with the use of Tags. On the Land prefab, go to the Tag dropdown and select Add Tag…
Add a new Tag called Land:
Go back to the Land prefab and set the tag to the newly created ‘Land’.
To check the tag of the RaycastHit object, we have to:
- Retrieve its Collider component
- From there, get its
tag
property
To this end, we’ll make the following changes:
PlayerInteraction.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerInteraction : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { RaycastHit hit; if(Physics.Raycast(transform.position, Vector3.down,out hit, 1)) { OnInteractableHit(hit); } } //Handles what happens when the interaction raycast hits something interactable void OnInteractableHit(RaycastHit hit) {Debug.Log(hit);Collider other = hit.collider; //Check if the player is going to interact with land if(other.tag == "Land") { Debug.Log("I am standing by farmable land"); } } }
Now if you were to test it, the console should output “I am standing by farmable land” only when your player is facing the Land prefab.
Article continues after the advertisement:
c. Adding feedback
This debug message is not going to cut it, since the game’s console will not be visible to the player. Hence, we need to add some UI elements. On the Land prefab, add a Cube GameObject and set it up like the picture below:
Rename it to ‘Select’ and set it to inactive.
How it works is simple: It pops up when the player is selecting this Instance of Land. Otherwise, it will be out of sight.
Just to make debugging easier, we’re going to duplicate the Land prefabs like this:
Set up a function that handles the selection and deselection of the plot of land:
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour { public enum LandStatus { Soil, Farmland, Watered } public LandStatus landStatus; public Material soilMat, farmlandMat, wateredMat; new Renderer renderer; //The selection gameobject to enable when the player is selecting the land public GameObject select; // Start is called before the first frame update void Start() { //Get the renderer component renderer = GetComponent<Renderer>(); //Set the land to soil by default SwitchLandStatus(LandStatus.Soil); } public void SwitchLandStatus(LandStatus statusToSwitch) { //Set land status accordingly landStatus = statusToSwitch; Material materialToSwitch = soilMat; //Decide what material to switch to switch (statusToSwitch) { case LandStatus.Soil: //Switch to the soil material materialToSwitch = soilMat; break; case LandStatus.Farmland: //Switch to farmland material materialToSwitch = farmlandMat; break; case LandStatus.Watered: //Switch to watered material materialToSwitch = wateredMat; break; } //Get the renderer to apply the changes renderer.material = materialToSwitch; } public void Select(bool toggle) { select.SetActive(toggle); } }
Assign the Select GameObject to the Select variable in the Inspector.
Replace the Debug message in PlayerInteraction
with a call to use the newly-declared Select
function from the Land
component:
PlayerInteraction.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerInteraction : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { RaycastHit hit; if(Physics.Raycast(transform.position, Vector3.down,out hit, 1)) { OnInteractableHit(hit); } } //Handles what happens when the interaction raycast hits something interactable void OnInteractableHit(RaycastHit hit) { Collider other = hit.collider; //Check if the player is going to interact with land if(other.tag == "Land") {Debug.Log("I am standing by farmable land");//Get the land component Land land = other.GetComponent<Land>(); land.Select(true); } } }
Article continues after the advertisement:
d. Selecting and Deselecting
The player is now able to select instances of Land, but as of now, there is no way to deselect it.
We will need to make the following changes to the selection logic:
- The player should only be selecting a maximum of one instance of Land at any point of time.
- This should be tracked by a variable.
- When the player is selecting a new instance of Land, the previously selected land must be deselected.
- The tracking variable should then be set to the newly selected instance of Land.
Since it has gotten more complex, we should move the selection process to a new function, SelectLand()
:
PlayerInteraction.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerInteraction : MonoBehaviour { //The land the player is currently selecting Land selectedLand = null; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { RaycastHit hit; if(Physics.Raycast(transform.position, Vector3.down,out hit, 1)) { OnInteractableHit(hit); } } //Handles what happens when the interaction raycast hits something interactable void OnInteractableHit(RaycastHit hit) { Collider other = hit.collider; //Check if the player is going to interact with land if(other.tag == "Land") { Debug.Log("I am standing by farmable land"); //Get the land component Land land = other.GetComponent<Land>();land.Select(true);SelectLand(land); } } //Handles the selection process of the land void SelectLand(Land land) { //Set the previously selected land to false (If any) if (selectedLand != null) { selectedLand.Select(false); } //Set the new selected land to the land we're selecting now. selectedLand = land; land.Select(true); } }
Now the basic selection mechanism works for the most part. However, a glitch remains:
We just need to deselect the previously selected Land instance when the Raycast is not hitting any Land instances:
PlayerInteraction.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerInteraction : MonoBehaviour { //The land the player is currently selecting Land selectedLand = null; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { RaycastHit hit; if(Physics.Raycast(transform.position, Vector3.down,out hit, 1)) { OnInteractableHit(hit); } } //Handles what happens when the interaction raycast hits something interactable void OnInteractableHit(RaycastHit hit) { Collider other = hit.collider; //Check if the player is going to interact with land if(other.tag == "Land") { //Get the land component Land land = other.GetComponent<Land>(); SelectLand(land); return; } //Deselect the land if the player is not standing on any land at the moment if(selectedLand != null) { selectedLand.Select(false); selectedLand = null; } } //Handles the selection process of the land void SelectLand(Land land) { //Set the previously selected land to false (If any) if (selectedLand != null) { selectedLand.Select(false); } //Set the new selected land to the land we're selecting now. selectedLand = land; land.Select(true); } }
Note: The return
statement is necessary to terminate execution of the function, as we don’t want this newly-implemented deselection logic to override the one from SelectLand
!
Don’t forget to remove the BoxCollider component from the Select GameObject or it will conflict with the selection logic.
e. Polishing up the UI
Having a white block as our selection UI is fine, but it can look better. Open up your image editing software, and create a square texture with a white border and transparent filling. Alternatively, you can download and use the PNG below.
You can use Photoshop or any image editing software that supports transparency. In our video, we use Paint.NET, a free and lightweight photo editing software for quick and easy edits.
Import it into the Project, and save it under a new folder Imported Asset/UI.
When you first drag the texture over to the Select Cube, it is white with a black fill:
Just set the material’s Rendering Mode to Transparent, and change the colour of the Albedo to a colour of your choice.
Article continues after the advertisement:
To save yourself the hassle of having to re-enable and disable the Select GameObject to preview your changes, you can just leave the GameObject and rely on code to do the deselection for you:
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour { public enum LandStatus { Soil, Farmland, Watered } public LandStatus landStatus; public Material soilMat, farmlandMat, wateredMat; new Renderer renderer; //The selection gameobject to enable when the player is selecting the land public GameObject select; // Start is called before the first frame update void Start() { //Get the renderer component renderer = GetComponent<Renderer>(); //Set the land to soil by default SwitchLandStatus(LandStatus.Soil); //Deselect the land by default Select(false); } public void SwitchLandStatus(LandStatus statusToSwitch) { //Set land status accordingly landStatus = statusToSwitch; Material materialToSwitch = soilMat; //Decide what material to switch to switch (statusToSwitch) { case LandStatus.Soil: //Switch to the soil material materialToSwitch = soilMat; break; case LandStatus.Farmland: //Switch to farmland material materialToSwitch = farmlandMat; break; case LandStatus.Watered: //Switch to watered material materialToSwitch = wateredMat; break; } //Get the renderer to apply the changes renderer.material = materialToSwitch; } public void Select(bool toggle) { select.SetActive(toggle); } }
5. Finishing up
Now that we have all the classes we need set up with a selection system, here’s a refresher on how the interaction system is going to work:
Class | Role in Interaction |
PlayerController.cs | Processes keyboard/mouse inputs from the player and directs it to PlayerInteraction |
PlayerInteraction.cs | Figures out which Land instance to interact with (selection). |
Land.cs | Handles state changes based on input from PlayerInteraction.cs |
To keep things tidy, we will place all the interaction-related actions in an Interact()
method. Here’s a summary of how it will work when we’re done:
a. PlayerController
First, we need to pass a reference to PlayerInteraction.cs
:
PlayerInteraction.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { //Movement Components private CharacterController controller; private Animator animator; private float moveSpeed = 4f; [Header("Movement System")] public float walkSpeed = 4f; public float runSpeed = 8f; //Interaction components PlayerInteraction playerInteraction; // Start is called before the first frame update void Start() { //Get movement components controller = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); //Get interaction component playerInteraction = GetComponentInChildren<PlayerInteraction>(); } // Update is called once per frame void Update() { //Runs the function that handles all movement Move(); } public void Move() { //Get the horizontal and vertical inputs as a number float horizontal = Input.GetAxisRaw("Horizontal"); float vertical = Input.GetAxisRaw("Vertical"); //Direction in a normalised vector Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized; Vector3 velocity = moveSpeed * Time.deltaTime * dir; //Is the sprint key pressed down? if (Input.GetButton("Sprint")) { //Set the animation to run and increase our movespeed moveSpeed = runSpeed; animator.SetBool("Running", true); } else { //Set the animation to walk and decrease our movespeed moveSpeed = walkSpeed; animator.SetBool("Running", false); } //Check if there is movement if (dir.magnitude >= 0.1f) { //Look towards that direction transform.rotation = Quaternion.LookRotation(dir); //Move controller.Move(velocity); } //Animation speed parameter animator.SetFloat("Speed", velocity.magnitude); } }
Like what we did for movement, we’ll add a function to handle all interaction inputs in Update()
. This will also handle item interactions as well; though for now, we will just have it direct the interaction logic to PlayerInteraction.cs
on a Left-click:
PlayerInteraction.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { //Movement Components private CharacterController controller; private Animator animator; private float moveSpeed = 4f; [Header("Movement System")] public float walkSpeed = 4f; public float runSpeed = 8f; //Interaction components PlayerInteraction playerInteraction; // Start is called before the first frame update void Start() { //Get movement components controller = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); //Get interaction component playerInteraction = GetComponentInChildren<PlayerInteraction>(); } // Update is called once per frame void Update() { //Runs the function that handles all movement Move(); //Runs the function that handles all interaction Interact(); } public void Interact() { //Tool interaction if (Input.GetButtonDown("Fire1")) { //Interact playerInteraction.Interact(); } //TODO: Set up item interaction } public void Move() { //Get the horizontal and vertical inputs as a number float horizontal = Input.GetAxisRaw("Horizontal"); float vertical = Input.GetAxisRaw("Vertical"); //Direction in a normalised vector Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized; Vector3 velocity = moveSpeed * Time.deltaTime * dir; //Is the sprint key pressed down? if (Input.GetButton("Sprint")) { //Set the animation to run and increase our movespeed moveSpeed = runSpeed; animator.SetBool("Running", true); } else { //Set the animation to walk and decrease our movespeed moveSpeed = walkSpeed; animator.SetBool("Running", false); } //Check if there is movement if (dir.magnitude >= 0.1f) { //Look towards that direction transform.rotation = Quaternion.LookRotation(dir); //Move controller.Move(velocity); } //Animation speed parameter animator.SetFloat("Speed", velocity.magnitude); } }
Article continues after the advertisement:
b. PlayerInteraction
There are cases where the player will not be selecting any Land, so we have to check if there is any selected land before directing interaction to it:
PlayerInteraction.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerInteraction : MonoBehaviour { //The land the player is currently selecting Land selectedLand = null; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { RaycastHit hit; if(Physics.Raycast(transform.position, Vector3.down,out hit, 1)) { OnInteractableHit(hit); } } //Handles what happens when the interaction raycast hits something interactable void OnInteractableHit(RaycastHit hit) { Collider other = hit.collider; //Check if the player is going to interact with land if(other.tag == "Land") { //Get the land component Land land = other.GetComponent<Land>(); SelectLand(land); return; } //Deselect the land if the player is not standing on any land at the moment if(selectedLand != null) { selectedLand.Select(false); selectedLand = null; } } //Handles the selection process of the land void SelectLand(Land land) { //Set the previously selected land to false (If any) if (selectedLand != null) { selectedLand.Select(false); } //Set the new selected land to the land we're selecting now. selectedLand = land; land.Select(true); } //Triggered when the player presses the tool button public void Interact() { //Check if the player is selecting any land if(selectedLand != null) { selectedLand.Interact(); return; } Debug.Log("Not on any land!"); } }
c. Land
In the final product, the Player should be able to till, water, and harvest from the Land. However, we have not set up our item and inventory system yet, so for this part, we’ll just make it switch states to Farmland:
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour { public enum LandStatus { Soil, Farmland, Watered } public LandStatus landStatus; public Material soilMat, farmlandMat, wateredMat; new Renderer renderer; //The selection gameobject to enable when the player is selecting the land public GameObject select; // Start is called before the first frame update void Start() { //Get the renderer component renderer = GetComponent<Renderer>(); //Set the land to soil by default SwitchLandStatus(LandStatus.Soil); //Deselect the land by default Select(false); } public void SwitchLandStatus(LandStatus statusToSwitch) { //Set land status accordingly landStatus = statusToSwitch; Material materialToSwitch = soilMat; //Decide what material to switch to switch (statusToSwitch) { case LandStatus.Soil: //Switch to the soil material materialToSwitch = soilMat; break; case LandStatus.Farmland: //Switch to farmland material materialToSwitch = farmlandMat; break; case LandStatus.Watered: //Switch to watered material materialToSwitch = wateredMat; break; } //Get the renderer to apply the changes renderer.material = materialToSwitch; } public void Select(bool toggle) { select.SetActive(toggle); } //When the player presses the interact button while selecting this land public void Interact() { //Interaction SwitchLandStatus(LandStatus.Farmland); } }
Article continues after the advertisement:
Conclusion
With that, we have a basic interaction system. Here is the final code for all the scripts we have worked with today:
Note: The parts highlighted in red are redundant parts from the video that are omitted in this article.
PlayerInteraction.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerInteraction : MonoBehaviour { PlayerController playerController; //The land the player is currently selecting Land selectedLand = null; // Start is called before the first frame update void Start() { //Get access to our PlayerController component playerController = transform.parent.GetComponent<PlayerController>(); } // Update is called once per frame void Update() { RaycastHit hit; if(Physics.Raycast(transform.position, Vector3.down,out hit, 1)) { OnInteractableHit(hit); } } //Handles what happens when the interaction raycast hits something interactable void OnInteractableHit(RaycastHit hit) { Collider other = hit.collider; //Check if the player is going to interact with land if(other.tag == "Land") { //Get the land component Land land = other.GetComponent<Land>(); SelectLand(land); return; } //Deselect the land if the player is not standing on any land at the moment if(selectedLand != null) { selectedLand.Select(false); selectedLand = null; } } //Handles the selection process of the land void SelectLand(Land land) { //Set the previously selected land to false (If any) if (selectedLand != null) { selectedLand.Select(false); } //Set the new selected land to the land we're selecting now. selectedLand = land; land.Select(true); } //Triggered when the player presses the tool button public void Interact() { //Check if the player is selecting any land if(selectedLand != null) { selectedLand.Interact(); return; } Debug.Log("Not on any land!"); } }
Land.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Land : MonoBehaviour { public enum LandStatus { Soil, Farmland, Watered } public LandStatus landStatus; public Material soilMat, farmlandMat, wateredMat; new Renderer renderer; //The selection gameobject to enable when the player is selecting the land public GameObject select; // Start is called before the first frame update void Start() { //Get the renderer component renderer = GetComponent<Renderer>(); //Set the land to soil by default SwitchLandStatus(LandStatus.Soil); //Deselect the land by default Select(false); } public void SwitchLandStatus(LandStatus statusToSwitch) { //Set land status accordingly landStatus = statusToSwitch; Material materialToSwitch = soilMat; //Decide what material to switch to switch (statusToSwitch) { case LandStatus.Soil: //Switch to the soil material materialToSwitch = soilMat; break; case LandStatus.Farmland: //Switch to farmland material materialToSwitch = farmlandMat; break; case LandStatus.Watered: //Switch to watered material materialToSwitch = wateredMat; break; } //Get the renderer to apply the changes renderer.material = materialToSwitch; } public void Select(bool toggle) { select.SetActive(toggle); } //When the player presses the interact button while selecting this land public void Interact() { //Interaction SwitchLandStatus(LandStatus.Farmland); } }
PlayerController.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { //Movement Components private CharacterController controller; private Animator animator; private float moveSpeed = 4f; [Header("Movement System")] public float walkSpeed = 4f; public float runSpeed = 8f; //Interaction components PlayerInteraction playerInteraction; // Start is called before the first frame update void Start() { //Get movement components controller = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); //Get interaction component playerInteraction = GetComponentInChildren<PlayerInteraction>(); } // Update is called once per frame void Update() { //Runs the function that handles all movement Move(); //Runs the function that handles all interaction Interact(); } public void Interact() { //Tool interaction if (Input.GetButtonDown("Fire1")) { //Interact playerInteraction.Interact(); } //TODO: Set up item interaction } public void Move() { //Get the horizontal and vertical inputs as a number float horizontal = Input.GetAxisRaw("Horizontal"); float vertical = Input.GetAxisRaw("Vertical"); //Direction in a normalised vector Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized; Vector3 velocity = moveSpeed * Time.deltaTime * dir; //Is the sprint key pressed down? if (Input.GetButton("Sprint")) { //Set the animation to run and increase our movespeed moveSpeed = runSpeed; animator.SetBool("Running", true); } else { //Set the animation to walk and decrease our movespeed moveSpeed = walkSpeed; animator.SetBool("Running", false); } //Check if there is movement if (dir.magnitude >= 0.1f) { //Look towards that direction transform.rotation = Quaternion.LookRotation(dir); //Move controller.Move(velocity); } //Animation speed parameter animator.SetFloat("Speed", velocity.magnitude); } }
Article continues after the advertisement: