Completed course 2 adn 3, working on 4

This commit is contained in:
2026-03-20 17:34:22 +00:00
parent 623ad6f509
commit db966bab55
507 changed files with 67348 additions and 4667 deletions

View File

@@ -1,17 +1,21 @@
using System;
using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
using UnityEngine.UIElements;
public class CameraController : MonoBehaviour
{
public static CameraController instance;
public float moveSpeed;
public float zoomSpeed;
public float rotateSpeed;
public float minZoomDist;
public float maxZoomDist;
private Camera cam;
void Awake()
{
cam = Camera.main;
instance = this;
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
@@ -24,8 +28,16 @@ public class CameraController : MonoBehaviour
{
Move();
Zoom();
Rotate();
}
private void Rotate()
{
float leftInput = Input.GetKey(KeyCode.E) ? 1.0f : 0.0f;
float rightInput = Input.GetKey(KeyCode.Q) ? 1.0f : 0.0f;
float rotAmount = (rightInput - leftInput) * rotateSpeed * Time.deltaTime;
transform.Rotate(Vector3.up, rotAmount);
}
private void Move()
{
float xInput = Input.GetAxisRaw("Horizontal");
@@ -35,12 +47,12 @@ public class CameraController : MonoBehaviour
}
private void Zoom()
{
float scrollInput = Input.GetAxisRaw("Mouse ScrollWheel");
float scrollInput = Input.GetAxis("Mouse ScrollWheel");
float dist = Vector3.Distance(transform.position, cam.transform.position);
if(dist < minZoomDist && scrollInput > 0.0f)
return;
if(dist > maxZoomDist && scrollInput < 0.0f)
return;
return;
else if(dist > maxZoomDist && scrollInput < 0.0f)
return;
cam.transform.position += cam.transform.forward * scrollInput * zoomSpeed;
}
public void FocusOnPosition(Vector3 pos)

55
Assets/Scripts/GameUI.cs Normal file
View File

@@ -0,0 +1,55 @@
using UnityEngine;
using TMPro;
public class GameUI : MonoBehaviour
{
public TextMeshProUGUI unitCountText;
public TextMeshProUGUI foodText;
public TextMeshProUGUI woodText;
public TextMeshProUGUI stoneText;
public static GameUI instance;
void Awake()
{
instance = this;
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void UpdateUnitCountText(int value)
{
unitCountText.text = value.ToString();
}
/*public void UpdateFoodText(int value)
{
foodText.text = value.ToString();
}*/
public void UpdateResourceText(int value, ResourceType type)
{
switch(type)
{
case ResourceType.Wood:
{
woodText.text = value.ToString();
break;
}
case ResourceType.Stone:
{
stoneText.text = value.ToString();
break;
}
case ResourceType.Food:
{
foodText.text = value.ToString();
break;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 95f46a16dc572194b93968c29c29dcc0

View File

@@ -1,25 +1,100 @@
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Events;
public class Player : MonoBehaviour
{
public static Player me;
[Header("Components")]
public GameObject unitPrefab;
public Transform unitSpawnPos;
public readonly int unitCost = 50;
[Header("Resources")]
public int wood;
public int stone;
public int food;
[Header("Units")]
public List<Unit> units = new List<Unit>();
//events
[System.Serializable]
public class UnitCreatedEvent : UnityEvent<Unit> { }
public UnitCreatedEvent onUnitCreated;
public bool isMe;
void Awake()
{
if(isMe)
me = this;
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
if(isMe)
{
GameUI.instance.UpdateUnitCountText(units.Count);
//GameUI.instance.UpdateFoodText(food);
GameUI.instance.UpdateResourceText(wood, ResourceType.Wood);
GameUI.instance.UpdateResourceText(stone, ResourceType.Stone);
GameUI.instance.UpdateResourceText(food, ResourceType.Food);
CameraController.instance.FocusOnPosition(unitSpawnPos.position);
}
food += unitCost;
CreateNewUnit();
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyCode.E))
CreateNewUnit();
}
//is this my unit?
public bool IsMyUnit(Unit unit)
{
return units.Contains(unit);
}
//called when a unit gathers a certain resource
public void GainResouces(ResourceType type, int amount)
{
switch(type)
{
case ResourceType.Wood:
{
wood += amount;
break;
}
case ResourceType.Stone:
{
stone += amount;
break;
}
case ResourceType.Food:
{
food += amount;
break;
}
}
if(isMe)
{
GameUI.instance.UpdateResourceText(wood, ResourceType.Wood);
GameUI.instance.UpdateResourceText(stone, ResourceType.Stone);
GameUI.instance.UpdateResourceText(food, ResourceType.Food);
}
}
public void CreateNewUnit()
{
if(food - unitCost < 0)
return;
GameObject unitObj = Instantiate(unitPrefab, unitSpawnPos.position, Quaternion.identity, transform);
Unit unit = unitObj.GetComponent<Unit>();
units.Add(unit);
food -= unitCost;
if(onUnitCreated != null)
onUnitCreated.Invoke(unit);
if(isMe)
{
GameUI.instance.UpdateUnitCountText(units.Count);
GameUI.instance.UpdateResourceText(food, ResourceType.Food);
}
}
}

View File

@@ -35,5 +35,6 @@ public class ResourceSource : MonoBehaviour
Destroy(gameObject);
if(onQuantityChange != null)
onQuantityChange.Invoke();
gatheringPlayer.GainResouces(type, amountToGive);
}
}

View File

@@ -9,7 +9,7 @@ public class ResourceSourceUI : MonoBehaviour
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
resourceQuantityText.text = resource.quantity.ToString();
}
// Update is called once per frame

View File

@@ -1,5 +1,4 @@
using System.Collections;
using Unity.VisualScripting;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
@@ -9,7 +8,9 @@ public enum UnitState
Idle,
Move,
MoveToResource,
Gather
Gather,
MoveToEnemy,
Attack
}
public class Unit : MonoBehaviour
@@ -23,16 +24,30 @@ public class Unit : MonoBehaviour
public int gatherAmount;
public float gatherRate;
private float lastGatherTime;
private ResourceSource resource;
public int curHp;
public int maxHp;
public int minAttackDamage;
public int maxAttackDamage;
public float attackRate;
private float lastAttackTime;
public float pathUpdateRate = 1.0f;
private float lastPathUpdateTime;
private Unit curEnemyTarget;
public float attackDistance;
public UnitHealthBar healthBar;
public ResourceSource curResourceSource;
//events
public class StateChangeEvent : UnitEvent<UnitState> { }
[System.Serializable]
public class StateChangeEvent : UnityEvent<UnitState> { }
public StateChangeEvent onStateChange;
public Player player;
[System.Obsolete]
void Awake()
{
//get the components
navAgent = GetComponent<NavMeshAgent>();
SetState(UnitState.Idle);
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
@@ -50,26 +65,37 @@ public class Unit : MonoBehaviour
}
switch(state)
{
case UnitState.Move
case UnitState.Move:
{
MoveUpdate();
break;
}
case UnitState.MoveToResource
case UnitState.MoveToResource:
{
MoveToResourceUpdate();
break;
}
case UnitState.Gather
case UnitState.Gather:
{
GatherUpdate();
break;
}
case UnitState.MoveToEnemy:
{
MoveToEnemyUpdate();
break;
}
case UnitState.Attack:
{
AttackUpdate();
break;
}
}
}
public void ToggleSelectionVisual(bool selected)
{
selectionVisual.SetActive(selected);
if(selectionVisual != null)
selectionVisual.SetActive(selected);
}
public void MoveToPosition(Vector3 pos, SelectionMarker marker = null)
{
@@ -81,11 +107,15 @@ public class Unit : MonoBehaviour
navAgent.isStopped = false;
navAgent.SetDestination(pos);
selectionMarker = marker;
SetState(UnitState.Move);
}
//move to a resource and begin to gather it
public void GatherResource(ResourceSource resource, Vector3 pos)
{
curResourceSource = resource;
SetState(UnitState.MoveToResource);
navAgent.isStopped = false;
navAgent.SetDestination(pos);
}
void SetState(UnitState toState)
{
@@ -101,14 +131,100 @@ public class Unit : MonoBehaviour
}
void MoveUpdate()
{
if(!navAgent.pathPending && navAgent.remainingDistance <= navAgent.stoppingDistance)
SetState(UnitState.Idle);
}
void MoveToResourceUpdate()
{
if(curResourceSource == null)
{
SetState(UnitState.Idle);
return;
}
if(!navAgent.pathPending && navAgent.remainingDistance <= navAgent.stoppingDistance)
SetState(UnitState.Gather);
}
void GatherUpdate()
{
if(curResourceSource == null)
{
SetState(UnitState.Idle);
return;
}
LookAt(curResourceSource.transform.position);
if(Time.time - lastGatherTime > gatherRate)
{
lastGatherTime = Time.time;
curResourceSource.GatherResource(gatherAmount, player);
}
}
//called every frame the "MoveToEnemy" state is active
void MoveToEnemyUpdate()
{
if(curEnemyTarget == null)
{
SetState(UnitState.Idle);
return;
}
if(Time.time - lastPathUpdateTime > pathUpdateRate)
{
lastPathUpdateTime = Time.time;
navAgent.isStopped = false;
navAgent.SetDestination(curEnemyTarget.transform.position);
}
if(Vector3.Distance(transform.position, curEnemyTarget.transform.position) <= attackDistance)
SetState(UnitState.Attack);
}
//called every frame the "Attack" state is active
void AttackUpdate()
{
//if our target is dead or null, go idle
if(curEnemyTarget == null)
{
SetState(UnitState.Idle);
return;
}
//if we are still moving
if(!navAgent.isStopped)
navAgent.isStopped = true;
//attack every "attackRate" seconds
if(Time.time - lastAttackTime > attackRate)
{
lastAttackTime = Time.time;
curEnemyTarget.TakeDamage(UnityEngine.Random.Range(minAttackDamage, maxAttackDamage + 1));
}
//Look at the enemy
LookAt(curEnemyTarget.transform.position);
//if we are too far away, move towards the enemy
if(Vector3.Distance(transform.position, curEnemyTarget.transform.position) > attackDistance)
SetState(UnitState.MoveToEnemy);
}
//rotate to face the given position
void LookAt(Vector3 pos)
{
Vector3 dir = (pos - transform.position).normalized;
float angle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0, angle, 0);
}
//move to an enemy unit and attack them
public void AttackUnit(Unit target)
{
curEnemyTarget = target;
SetState(UnitState.MoveToEnemy);
}
//called when we take damage from another unit
public void TakeDamage(int damage)
{
curHp -= damage;
if(curHp <= 0)
Die();
//update the health bar
healthBar.UpdateHealthBar(curHp, maxHp);
}
//called when our health reaches 0
void Die()
{
player.units.Remove(this);
Destroy(gameObject);
}
}

View File

@@ -28,30 +28,44 @@ public class UnitCommander : MonoBehaviour
{
if(Input.GetMouseButtonDown(1) && unitSelection.HasUnitSelected())
{
unitSelection.RemoveNullUnitsFromSelection();
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
Unit[] selectedUnits = unitSelection.GetSelectedUnits();
if(Physics.Raycast(ray, out hit, 100, layerMask))
{
//are we clicking on the ground?
if(hit.collider.CompareTag("Ground"))
{
SelectionMarker marker = CreateSelectionMarker(hit.point, false);
UnitsMoveToPosition(hit.point, selectedUnits, marker);
}
//are we clicking on a resource?
else if(hit.collider.CompareTag("Resource"))
{
UnitsGatherResource(hit.collider.GetComponent<ResourceSource>(), selectedUnits);
CreateSelectionMarker(hit.point, true);
CreateSelectionMarker(hit.collider.transform.position, true);
}
//did we click on the enemy?
else if(hit.collider.CompareTag("Unit"))
{
Unit enemy = hit.collider.GetComponent<Unit>();
if(!Player.me.IsMyUnit(enemy))
{
UnitsAttackEnemy(enemy, selectedUnits);
CreateSelectionMarker(enemy.transform.position, false);
}
}
}
}
}
void UnitsMoveToPosition(Vector3 movePos, Unit[] units, SelectionMarker marker)
{
Vector3[] destinations = UnitMover.GetUnitGroupDestination(movePos, units.Length, 2);
for(int x = 0; x < units.Length; x++)
{
// Only the first unit owns the marker so it isn't destroyed multiple times
units[x].MoveToPosition(movePos, x == 0 ? marker : null);
units[x].MoveToPosition(destinations[x], x == 0 ? marker : null);
}
}
//creates a new selection marker at the position we right click
@@ -79,7 +93,7 @@ public class UnitCommander : MonoBehaviour
units[0].GatherResource(resource, UnitMover.GetUnitDestinationAroundResource(resource.transform.position));
}
else
{
{
Vector3[] destinations = UnitMover.GetUnitGroupDestinationsAroundResource(resource.transform.position, units.Length);
for(int x = 0; x <units.Length; x++)
{
@@ -87,4 +101,10 @@ public class UnitCommander : MonoBehaviour
}
}
}
//called when we command units to attack an enemy
void UnitsAttackEnemy(Unit enemy, Unit[] units)
{
for(int x = 0; x < units.Length; x++)
units[x].AttackUnit(enemy);
}
}

View File

@@ -0,0 +1,31 @@
using UnityEngine;
using UnityEngine.UI;
public class UnitHealthBar : MonoBehaviour
{
public GameObject healthContainer;
public RectTransform healthFill;
private float maxSize;
void Awake()
{
maxSize = healthFill.sizeDelta.x;
healthContainer.SetActive(false);
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void UpdateHealthBar(int curHp, int maxHp)
{
healthContainer.SetActive(true);
float healthPercentage = (float)curHp / (float)maxHp;
healthFill.sizeDelta = new Vector2(maxSize * healthPercentage, healthFill.sizeDelta.y);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 51d78e6b4041df94ead9a85e17d54bdd

View File

@@ -22,7 +22,7 @@ public class UnitMover : MonoBehaviour
Vector3[] destinations = new Vector3[numUnits];
//calculate the rows and columns
int rows = Mathf.RoundToInt(Mathf.Sqrt(numUnits));
int cols = Mathf.CeilToInt((float)numUnits / rows);
int cols = Mathf.CeilToInt((float)numUnits / (float)rows);
//we need to know the current row and column we're calculating
int curRow = 0;
int curCol = 0;
@@ -30,7 +30,7 @@ public class UnitMover : MonoBehaviour
float length = ((float)cols - 1) * unitGap;
for(int x = 0; x < numUnits; x++)
{
destinations[x] = moveToPos = (new Vector3(curRow, 0, curCol) * unitGap) - new Vector3(length / 2, 0, width / 2);
destinations[x] = moveToPos + (new Vector3(curRow, 0, curCol) * unitGap) - new Vector3(length / 2, 0, width / 2);
curCol++;
if(curCol == rows)
{
@@ -43,7 +43,7 @@ public class UnitMover : MonoBehaviour
public static Vector3[] GetUnitGroupDestinationsAroundResource(Vector3 resourcePos, int unitsNum)
{
Vector3[] destinations = new Vector3[unitsNum];
float unitDistanceGap = 160.0f / (float)unitsNum;
float unitDistanceGap = 360.0f / (float)unitsNum;
for(int x = 0; x < unitsNum; x++)
{
float angle = unitDistanceGap * x;
@@ -56,6 +56,6 @@ public class UnitMover : MonoBehaviour
{
float angle = Random.Range(0, 360);
Vector3 dir = new Vector3(Mathf.Sin(angle * Mathf.Deg2Rad), 0, Mathf.Cos(angle * Mathf.Deg2Rad));
return resourcePos;
return resourcePos + dir;
}
}

View File

@@ -28,6 +28,15 @@ public class UnitSelection : MonoBehaviour
// Update is called once per frame
void Update()
{
//mouse down
if(Input.GetMouseButtonDown(0))
{
startPos = Input.mousePosition;
ToggleSelectionVisual(false);
selectedUnits = new List<Unit>();
TrySelect(Input.mousePosition);
startPos = Input.mousePosition;
}
//mouse up
if(Input.GetMouseButtonUp(0))
{
@@ -38,14 +47,6 @@ public class UnitSelection : MonoBehaviour
{
UpdateSelectionBox(Input.mousePosition);
}
//mouse down
if(Input.GetMouseButtonDown(0))
{
startPos = Input.mousePosition;
ToggleSelectionVisual(false);
selectedUnits = new List<Unit>();
TrySelect(Input.mousePosition);
}
}
//called when we release the selection box
@@ -110,4 +111,13 @@ public class UnitSelection : MonoBehaviour
unit.ToggleSelectionVisual(selected);
}
}
//removes all destroyed or missing units from the selected list
public void RemoveNullUnitsFromSelection()
{
for(int x = 0; x < selectedUnits.Count; x++)
{
if(selectedUnits[x] == null)
selectedUnits.RemoveAt(x);
}
}
}

View File

@@ -0,0 +1,49 @@
using UnityEngine;
using UnityEngine.UI;
public class UnitStateBubble : MonoBehaviour
{
public Image stateBubble;
public Sprite idleSprite;
public Sprite gatherSprite;
public Sprite attackSprite;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void OnStateChange(UnitState state)
{
stateBubble.enabled = true;
switch(state)
{
case UnitState.Idle:
{
stateBubble.sprite = idleSprite;
break;
}
case UnitState.Gather:
{
stateBubble.sprite = gatherSprite;
break;
}
case UnitState.Attack:
{
stateBubble.sprite = attackSprite;
break;
}
default:
{
stateBubble.enabled = false;
break;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4143539b2680fcc428a1b684a6a5ea02