Really Basic rts mechanics.
Really need a way to attack and enemies to be a true MVP. But for iteration 0 this is fine. Need Kenney Art. Need a way to Attack. Need Rally Points. Need Fog of War. Need a HUD for better unit selection. Need Mini Map. I can go on, this is by far the most complex Prototype I've ever done.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
// TODO: remove me!
|
||||
public class AddToTeam : MonoBehaviour
|
||||
{
|
||||
public bool addToBuilding;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
var building = GetComponent<Building>();
|
||||
if (addToBuilding)
|
||||
{
|
||||
Team.AllBuildings.Add(building);
|
||||
}
|
||||
|
||||
var unit = GetComponent<Unit>();
|
||||
if (!addToBuilding)
|
||||
{
|
||||
Team.AllUnits.Add(unit);
|
||||
}
|
||||
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51bf038dd0e34e240b57160e0b947f93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
// TODO: The actual building logic and the construction logic should be separated!
|
||||
[RequireComponent(typeof(BoxCollider2D), typeof(SpriteRenderer))]
|
||||
public class Building : MonoBehaviour
|
||||
{
|
||||
private struct WorkOrder
|
||||
{
|
||||
public float startTime;
|
||||
public float duration;
|
||||
public Action completed;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct UnitCommand
|
||||
{
|
||||
public Unit unit;
|
||||
public Key keyControl;
|
||||
public int cost;
|
||||
public float buildDuration;
|
||||
}
|
||||
|
||||
[NonSerialized] public BoxCollider2D buildingCollider;
|
||||
|
||||
public int maxQueueCount;
|
||||
public float constructionTime;
|
||||
public bool startBuilt;
|
||||
public int buildingCost;
|
||||
public List<UnitCommand> buildableUnits;
|
||||
|
||||
[NonSerialized] public bool isUnderConstruction = true;
|
||||
|
||||
private readonly Queue<WorkOrder> _workQueue = new Queue<WorkOrder>();
|
||||
private WorkOrder _currentWorkOrder;
|
||||
|
||||
private bool _hasCurrentWorkOrder;
|
||||
|
||||
// In seconds.
|
||||
private float _currentConstructionProgress;
|
||||
private readonly Color _constructionColor = Color.black;
|
||||
private SpriteRenderer _sprite;
|
||||
|
||||
private Transform _transform;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
buildingCollider = GetComponent<BoxCollider2D>();
|
||||
_sprite = GetComponent<SpriteRenderer>();
|
||||
isUnderConstruction = !startBuilt;
|
||||
_transform = transform;
|
||||
}
|
||||
|
||||
public void UpdateBuildProgress(float amount)
|
||||
{
|
||||
if (isUnderConstruction)
|
||||
{
|
||||
_currentConstructionProgress += amount;
|
||||
if (_currentConstructionProgress >= constructionTime)
|
||||
{
|
||||
isUnderConstruction = false;
|
||||
}
|
||||
|
||||
_sprite.color = Color.Lerp(_constructionColor, Color.white,
|
||||
_currentConstructionProgress / constructionTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_hasCurrentWorkOrder)
|
||||
{
|
||||
if (Time.time > _currentWorkOrder.startTime + _currentWorkOrder.duration)
|
||||
{
|
||||
_currentWorkOrder.completed();
|
||||
_hasCurrentWorkOrder = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Could probably do all this once we complete the order.
|
||||
// Would also need to do it when we receive an order.
|
||||
if (_workQueue.Count > 0)
|
||||
{
|
||||
_currentWorkOrder = _workQueue.Dequeue();
|
||||
_currentWorkOrder.startTime = Time.time;
|
||||
_hasCurrentWorkOrder = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessKeyInput(Key key)
|
||||
{
|
||||
if (isUnderConstruction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var command in buildableUnits)
|
||||
{
|
||||
if (command.keyControl == key)
|
||||
{
|
||||
if (command.cost > Team.Minerals)
|
||||
{
|
||||
// TODO: announcer.
|
||||
Debug.LogWarning("You've not enough minerals");
|
||||
break;
|
||||
}
|
||||
|
||||
if (maxQueueCount > _workQueue.Count)
|
||||
{
|
||||
Team.Minerals -= command.cost;
|
||||
_workQueue.Enqueue(new WorkOrder
|
||||
{
|
||||
startTime = Time.time,
|
||||
duration = command.buildDuration,
|
||||
completed = () =>
|
||||
{
|
||||
var unitGo = Instantiate(command.unit,
|
||||
_transform.position, _transform.rotation);
|
||||
Team.AllUnits.Add(unitGo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7dcd2279d5fa112459fa847e09209f5e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,275 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
[RequireComponent(typeof(Camera))]
|
||||
public class CameraController : MonoBehaviour
|
||||
{
|
||||
public float scrollSpeed;
|
||||
public RectTransform unitSelector;
|
||||
// TODO: this is not ideal.
|
||||
public List<Minerals> minerals;
|
||||
|
||||
private Camera _camera;
|
||||
private Keyboard _keyboard;
|
||||
private Mouse _mouse;
|
||||
private Transform _transform;
|
||||
private Vector3 _positionChange;
|
||||
private bool _isSelecting;
|
||||
private Rect _selectorRect;
|
||||
private readonly List<Unit> _selectedUnits = new List<Unit>();
|
||||
private Building _currentBuilding;
|
||||
private bool _wantsBuildBarracks;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_mouse = Mouse.current;
|
||||
_keyboard = Keyboard.current;
|
||||
_transform = transform;
|
||||
_positionChange = Vector3.zero;
|
||||
_camera = GetComponent<Camera>();
|
||||
|
||||
Cursor.lockState = CursorLockMode.Confined;
|
||||
var pivot = unitSelector.pivot;
|
||||
pivot.x = 0;
|
||||
pivot.y = 0;
|
||||
unitSelector.pivot = pivot;
|
||||
|
||||
Team.Minerals = 10;
|
||||
|
||||
_keyboard.onTextInput += OnTextInput;
|
||||
}
|
||||
|
||||
private void OnTextInput(char ch)
|
||||
{
|
||||
var keyControl = _keyboard.FindKeyOnCurrentKeyboardLayout(ch.ToString());
|
||||
|
||||
if (_currentBuilding != null && keyControl != null)
|
||||
{
|
||||
_currentBuilding.ProcessKeyInput(keyControl.keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (_keyboard.escapeKey.wasPressedThisFrame)
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!Application.isFocused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mousePos = _mouse.position.ReadValue();
|
||||
|
||||
_positionChange.x = 0;
|
||||
_positionChange.y = 0;
|
||||
if (mousePos.x <= 1)
|
||||
{
|
||||
_positionChange.x -= scrollSpeed * Time.deltaTime;
|
||||
}
|
||||
|
||||
if (mousePos.x >= Screen.width - 1)
|
||||
{
|
||||
_positionChange.x += scrollSpeed * Time.deltaTime;
|
||||
}
|
||||
|
||||
if (mousePos.y <= 1)
|
||||
{
|
||||
_positionChange.y -= scrollSpeed * Time.deltaTime;
|
||||
}
|
||||
|
||||
if (mousePos.y >= Screen.height - 1)
|
||||
{
|
||||
_positionChange.y += scrollSpeed * Time.deltaTime;
|
||||
}
|
||||
|
||||
_transform.position += _positionChange;
|
||||
|
||||
if (_mouse.rightButton.wasPressedThisFrame)
|
||||
{
|
||||
// TODO: pick logic, if the mouse was not dragged then it should select whatever is in the tile.
|
||||
var worldPos = _camera.ScreenToWorldPoint(mousePos);
|
||||
worldPos.x = Mathf.RoundToInt(worldPos.x);
|
||||
worldPos.y = Mathf.RoundToInt(worldPos.y);
|
||||
worldPos.z = 0;
|
||||
|
||||
var hasIssuedCommand = false;
|
||||
foreach (var building in Team.AllBuildings)
|
||||
{
|
||||
if (building.buildingCollider.OverlapPoint(worldPos) && building.isUnderConstruction)
|
||||
{
|
||||
//var worker = Team.AllUnits.FirstOrDefault() as Worker;
|
||||
var worker = _selectedUnits.FirstOrDefault(u => u is Worker) as Worker;
|
||||
if (worker)
|
||||
{
|
||||
hasIssuedCommand = true;
|
||||
worker.ConstructBuilding(building, worldPos);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var mineral in minerals)
|
||||
{
|
||||
if (mineral.boxCollider.OverlapPoint(worldPos))
|
||||
{
|
||||
var cmd = Team.AllBuildings.FirstOrDefault(b => b.name == "CommandCenter");
|
||||
// Not the best, but it will do.
|
||||
// TODO: basically will need to search all buildings for command centers (should be a type?) then find the closest to the patch.
|
||||
if (cmd)
|
||||
{
|
||||
var workers = _selectedUnits.FindAll(u => u is Worker);
|
||||
hasIssuedCommand = workers.Count > 0;
|
||||
Debug.Log($"has issued cmd: {hasIssuedCommand}");
|
||||
workers.ForEach(w => ((Worker)w).StartMining(mineral,
|
||||
mineral.transform.position,
|
||||
cmd.transform.position));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasIssuedCommand)
|
||||
{
|
||||
MoveAllSelectedUnits(worldPos);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: for the selection box, we should select whatever thing there is most of in the box.
|
||||
// IE: if there is one worker but 3 combat units, then select the 3 combat units.
|
||||
// This is not ideal but since the prototype for iteration 0 is limited to 2 buildings, a worker, and a combat
|
||||
// unit for the types of selectables, I think it will be acceptable until I add more stuff.
|
||||
if (_mouse.leftButton.wasPressedThisFrame && !_wantsBuildBarracks)
|
||||
{
|
||||
_isSelecting = true;
|
||||
_selectorRect = unitSelector.rect;
|
||||
_selectorRect.min = _mouse.position.ReadValue();
|
||||
}
|
||||
|
||||
if (_isSelecting)
|
||||
{
|
||||
_selectorRect.max = _mouse.position.ReadValue();
|
||||
if (_mouse.leftButton.wasReleasedThisFrame)
|
||||
{
|
||||
// Needs to be called before we clear the selector rect.
|
||||
FillSelection();
|
||||
_isSelecting = false;
|
||||
_selectorRect.min = Vector2.zero;
|
||||
_selectorRect.max = Vector2.zero;
|
||||
unitSelector.anchoredPosition = _selectorRect.min;
|
||||
unitSelector.sizeDelta = _selectorRect.max;
|
||||
}
|
||||
|
||||
Vector2 min;
|
||||
min.x = _selectorRect.min.x > _selectorRect.max.x ? _selectorRect.max.x : _selectorRect.min.x;
|
||||
min.y = _selectorRect.min.y > _selectorRect.max.y ? _selectorRect.max.y : _selectorRect.min.y;
|
||||
|
||||
Vector2 max;
|
||||
max.x = _selectorRect.min.x > _selectorRect.max.x ? _selectorRect.min.x : _selectorRect.max.x;
|
||||
max.y = _selectorRect.min.y > _selectorRect.max.y ? _selectorRect.min.y : _selectorRect.max.y;
|
||||
|
||||
unitSelector.anchoredPosition = min;
|
||||
unitSelector.sizeDelta = max - min;
|
||||
}
|
||||
|
||||
// if (_keyboard.sKey.wasPressedThisFrame && _currentBuilding)
|
||||
// {
|
||||
// _currentBuilding.BuildWorker();
|
||||
// }
|
||||
|
||||
// TODO: this needs to cost money
|
||||
// B->B build barracks
|
||||
if (_selectedUnits.Count > 0)
|
||||
{
|
||||
if (_keyboard.bKey.wasPressedThisFrame)
|
||||
{
|
||||
_wantsBuildBarracks = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_wantsBuildBarracks && _selectedUnits.Count > 0)
|
||||
{
|
||||
if (_mouse.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
var worldPos = _camera.ScreenToWorldPoint(mousePos);
|
||||
worldPos.z = 0;
|
||||
worldPos.x = Mathf.RoundToInt(worldPos.x);
|
||||
worldPos.y = Mathf.RoundToInt(worldPos.y);
|
||||
|
||||
var worker = _selectedUnits.FirstOrDefault() as Worker;
|
||||
if (worker)
|
||||
{
|
||||
worker.BuildBarracks(worldPos);
|
||||
_wantsBuildBarracks = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_wantsBuildBarracks = false;
|
||||
}
|
||||
// Barracks -> A marine
|
||||
// B->C command center.
|
||||
}
|
||||
|
||||
private void FillSelection()
|
||||
{
|
||||
_selectedUnits.Clear();
|
||||
|
||||
var worldMin = _camera.ScreenToWorldPoint(_selectorRect.min);
|
||||
var worldMax = _camera.ScreenToWorldPoint(_selectorRect.max);
|
||||
|
||||
var min = Vector3.zero;
|
||||
min.x = worldMin.x > worldMax.x ? worldMax.x : worldMin.x;
|
||||
min.y = worldMin.y > worldMax.y ? worldMax.y : worldMin.y;
|
||||
|
||||
var max = Vector3.zero;
|
||||
max.x = worldMin.x > worldMax.x ? worldMin.x : worldMax.x;
|
||||
max.y = worldMin.y > worldMax.y ? worldMin.y : worldMax.y;
|
||||
|
||||
var hasUnits = false;
|
||||
foreach (var unit in Team.AllUnits)
|
||||
{
|
||||
var pos = unit.transform.position;
|
||||
if (pos.x > min.x && pos.y > min.y &&
|
||||
pos.x < max.x && pos.y < max.y)
|
||||
{
|
||||
_selectedUnits.Add(unit);
|
||||
hasUnits = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is really not good, Ideally would just have a selectable type that provides abilities to the user.
|
||||
// IE a unit selectable type provides the ability to move. A building selectable type provides the ability to manufacture a unit. etc.
|
||||
if (!hasUnits)
|
||||
{
|
||||
// Try to select a building
|
||||
foreach (var building in Team.AllBuildings)
|
||||
{
|
||||
if (building.buildingCollider.OverlapPoint(min)
|
||||
|| building.buildingCollider.OverlapPoint(max))
|
||||
{
|
||||
Debug.Log("Building selected");
|
||||
_currentBuilding = building;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveAllSelectedUnits(Vector3 worldPos)
|
||||
{
|
||||
// TODO: not sure if pathfinding should happen here or on each unit.
|
||||
// Probably start with each unit and when swarming logic is needed re-think the solution.
|
||||
foreach (var unit in _selectedUnits)
|
||||
{
|
||||
unit.MoveTo(worldPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5822fae2646a8c943b31d77e917f8d21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
public class Marine : Unit
|
||||
{
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
// TODO: obviously this will be removed...
|
||||
MoveTo(_transform.position);
|
||||
}
|
||||
|
||||
protected override void CancelCurrentTask()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
private void Update()
|
||||
{
|
||||
MovementRoutine();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c3931f60c368434f8e1375690a83af5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[RequireComponent(typeof(Text))]
|
||||
public class MineralDisplay : MonoBehaviour
|
||||
{
|
||||
private Text _text;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_text = GetComponent<Text>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// TODO: probably want more of an event driven UI update.
|
||||
_text.text = $"Minerals: {Team.Minerals}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb5c2f049d9f7bb49b6570fed958ea9d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(BoxCollider2D))]
|
||||
public class Minerals : MonoBehaviour
|
||||
{
|
||||
[NonSerialized]
|
||||
public BoxCollider2D boxCollider;
|
||||
public int amount;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
boxCollider = GetComponent<BoxCollider2D>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78d69b3c81ba1e54cbf6735fb66af605
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class Pathfinder : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e98790d9791e90c498ee2b0187d66f76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
// TODO: this is probably the least final piece of code in this project.
|
||||
public static class Team
|
||||
{
|
||||
public static List<Unit> AllUnits = new List<Unit>();
|
||||
public static List<Building> AllBuildings = new List<Building>();
|
||||
public static int Minerals;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3744dac5e4f019449ee54cb770831b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public abstract class Unit : MonoBehaviour
|
||||
{
|
||||
// Meters is 1 unity unit, also a tile width/height.
|
||||
[Tooltip("How many meters per second")]
|
||||
public float speed;
|
||||
|
||||
protected Transform _transform;
|
||||
private Vector3 _startPosition;
|
||||
private Vector3 _moveToPosition;
|
||||
private float _startTime;
|
||||
private float _moveDuration;
|
||||
protected Action _doThingAfterMove;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
_transform = transform;
|
||||
}
|
||||
|
||||
protected void MovementRoutine()
|
||||
{
|
||||
_startTime += Time.deltaTime;
|
||||
_transform.position = Vector3.Lerp(_startPosition, _moveToPosition, _startTime / _moveDuration);
|
||||
|
||||
if (_startTime / _moveDuration > 1.0f)
|
||||
{
|
||||
_doThingAfterMove?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void CancelCurrentTask();
|
||||
|
||||
// TODO: replace this with pathfinding.
|
||||
public virtual void MoveTo(Vector3 position)
|
||||
{
|
||||
CancelCurrentTask();
|
||||
_startPosition = _transform.position;
|
||||
_moveToPosition = position;
|
||||
_moveDuration = Vector3.Distance(_startPosition, _moveToPosition) / speed;
|
||||
_startTime = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2719e163c057add49bd4bee7081826fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
// TODO: maybe instead of inheritance I could use behaviours
|
||||
// So the Unit will still be a thing, and that's what the CameraController interfaces with
|
||||
// But then commands will be sent to any attached behaviour (worker, marine, etc).
|
||||
// and that behaviour can do whatever it needs.
|
||||
// Now that I think about it more, maybe more granular behaviours (move, mine, build, attack, etc).
|
||||
public class Worker : Unit
|
||||
{
|
||||
private enum WorkerState
|
||||
{
|
||||
None,
|
||||
Building,
|
||||
Mining,
|
||||
Moving
|
||||
}
|
||||
|
||||
public Building barracksGo;
|
||||
public float mineDuration;
|
||||
public int mineralHarvestAmount;
|
||||
|
||||
private WorkerState _currentState;
|
||||
|
||||
private Building _constructionBuilding;
|
||||
|
||||
private Minerals _minerals;
|
||||
private Vector2 _mineralPatch;
|
||||
private Vector2 _commandCenter;
|
||||
private int _currentMineralsHeld;
|
||||
private float _mineStartTime;
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
_currentState = WorkerState.None;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// TODO: maybe a function pointer instead of this switch?
|
||||
switch (_currentState)
|
||||
{
|
||||
case WorkerState.None:
|
||||
// Literally do nothing.
|
||||
break;
|
||||
case WorkerState.Building:
|
||||
BuildBarracks();
|
||||
break;
|
||||
case WorkerState.Mining:
|
||||
MineMinerals();
|
||||
break;
|
||||
case WorkerState.Moving:
|
||||
MovementRoutine();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private void MineMinerals()
|
||||
{
|
||||
_currentState = WorkerState.Mining;
|
||||
if (_currentMineralsHeld <= 0)
|
||||
{
|
||||
if ((Vector2) _transform.position != _mineralPatch)
|
||||
{
|
||||
MoveTo(_mineralPatch);
|
||||
_doThingAfterMove = () =>
|
||||
{
|
||||
_mineStartTime = Time.time;
|
||||
MineMinerals();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: wait to mine minerals;
|
||||
if (Time.time >= _mineStartTime + mineDuration)
|
||||
{
|
||||
var amount = mineralHarvestAmount;
|
||||
if (_minerals.amount < amount)
|
||||
{
|
||||
amount = _minerals.amount;
|
||||
}
|
||||
|
||||
_minerals.amount -= amount;
|
||||
_currentMineralsHeld = amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((Vector2) _transform.position != _commandCenter)
|
||||
{
|
||||
MoveTo(_commandCenter);
|
||||
_doThingAfterMove = MineMinerals;
|
||||
}
|
||||
else
|
||||
{
|
||||
Team.Minerals += _currentMineralsHeld;
|
||||
_currentMineralsHeld = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildBarracks()
|
||||
{
|
||||
// Since we are in the Building state, just assume we have a construction building.
|
||||
if (_constructionBuilding.isUnderConstruction)
|
||||
{
|
||||
_constructionBuilding.UpdateBuildProgress(Time.deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// change state
|
||||
_currentState = WorkerState.None;
|
||||
}
|
||||
}
|
||||
|
||||
private void InstantiateBarracks()
|
||||
{
|
||||
if (barracksGo.buildingCost > Team.Minerals)
|
||||
{
|
||||
Debug.LogWarning("You've not enough minerals!");
|
||||
return;
|
||||
}
|
||||
|
||||
Team.Minerals -= barracksGo.buildingCost;
|
||||
_constructionBuilding = Instantiate(barracksGo,
|
||||
_transform.position + new Vector3(0.5f, 0, 0),
|
||||
_transform.rotation);
|
||||
Team.AllBuildings.Add(_constructionBuilding);
|
||||
_currentState = WorkerState.Building;
|
||||
_doThingAfterMove = null;
|
||||
}
|
||||
|
||||
protected override void CancelCurrentTask()
|
||||
{
|
||||
_currentState = WorkerState.None;
|
||||
_doThingAfterMove = null;
|
||||
// Might be able to just set the building to null all the time?
|
||||
if (_currentState == WorkerState.Building)
|
||||
{
|
||||
_constructionBuilding = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void MoveTo(Vector3 position)
|
||||
{
|
||||
base.MoveTo(position);
|
||||
_currentState = WorkerState.Moving;
|
||||
}
|
||||
|
||||
// Called when the user selects the build barracks command.
|
||||
public void BuildBarracks(Vector3 position)
|
||||
{
|
||||
MoveTo(position);
|
||||
_doThingAfterMove = InstantiateBarracks;
|
||||
}
|
||||
|
||||
// Called when the user right clicks on an unfinished building.
|
||||
public void ConstructBuilding(Building building, Vector3 position)
|
||||
{
|
||||
MoveTo(position);
|
||||
_constructionBuilding = building;
|
||||
_doThingAfterMove = () => _currentState = WorkerState.Building;
|
||||
}
|
||||
|
||||
public void StartMining(Minerals patch, Vector2 patchLocation, Vector2 commandCenterLocation)
|
||||
{
|
||||
_mineralPatch = patchLocation;
|
||||
_commandCenter = commandCenterLocation;
|
||||
_minerals = patch;
|
||||
_currentState = WorkerState.Mining;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32f92ee571b07704d883772d5a8bb0d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user