For TL;DR - Read just the boldfaced text.
I have a simple setup of using A, S, and D keys with the combination of left and right mouse buttons. No scrolling with the mouse wheel, and no other keys needed (other than exiting the game).
I’ve been tackling the issue of handling the input combinations in regards to the 3 keys and 2 buttons. A, S, and D all have their own respective commands the player can use, while the left mouse button is for selection /canceling selection only, and right mouse button is for issuing orders and targets.
The code below is me handling only the inputs, and setting the variables around to accommodate the issuing of orders and the states the controlled unit is doing. States are split into pending and current action states.
private void HandleOrders() {
bool waitOrders = this.commandState.Equals(UnitCommand.ATTACK_ORDER) || this.commandState.Equals(UnitCommand.SPLIT_ORDER) || this.commandState.Equals(UnitCommand.MERGE_ORDER);
if (Input.GetKeyDown(KeyCode.A) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.ATTACK_ORDER;
}
else if (Input.GetKeyDown(KeyCode.S) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.SPLIT_ORDER;
}
else if (Input.GetKeyDown(KeyCode.D) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.MERGE_ORDER;
}
else if (Input.GetMouseButtonDown(0) && this.pendingActionState.Equals(UnitState.WAITING) && waitOrders) {
Debug.Log("Hit");
this.commandState = UnitCommand.NO_ORDERS;
this.pendingActionState = this.actionState;
}
else if (Input.GetMouseButtonUp(1)) {
if (this.pendingActionState.Equals(UnitState.WAITING) && waitOrders) {
switch (this.commandState) {
case UnitCommand.NO_ORDERS:
this.pendingActionState = UnitState.MOVING;
this.actionState = this.pendingActionState;
Move();
break;
case UnitCommand.MOVE_ORDER:
this.pendingActionState = UnitState.MOVING;
this.actionState = this.pendingActionState;
Move();
break;
case UnitCommand.ATTACK_ORDER:
this.pendingActionState = UnitState.ATTACKING;
this.actionState = this.pendingActionState;
AttackAction();
break;
default:
break;
}
}
else if (!this.pendingActionState.Equals(UnitState.WAITING) && waitOrders){
if (Input.GetKeyDown(KeyCode.A) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.ATTACK_ORDER;
}
else if (Input.GetKeyDown(KeyCode.S) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.SPLIT_ORDER;
}
else if (Input.GetKeyDown(KeyCode.D) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.MERGE_ORDER;
}
else if (Input.GetMouseButtonDown(0) && this.pendingActionState.Equals(UnitState.WAITING) && waitOrders) {
Debug.Log("Hit");
this.commandState = UnitCommand.NO_ORDERS;
this.pendingActionState = this.actionState;
}
}
else {
this.pendingActionState = UnitState.MOVING;
this.actionState = this.pendingActionState;
Move();
}
}
}
Full code can be seen at the very end of this post.
Not considering refactoring and other ways of better usages of if…else, or switch statements, I am defeated by the complexity of the input combinations, and trying to get it so the players can issue their orders/commands correctly. I am trying to handle corner cases and cases that players with high APM could easily do that my HandleOrders() can break. For instance, units are quickly issued in succession, attack, move, attack, attack, move, select, deselect, and attack. Just the first three commands will break the HandleOrders().
All I am asking for is either a better way of handling input combinations, or some way to handle the aggressive inputs coming into their way. Does anyone know how?
Full code:
using UnityEngine;
using System.Collections;
public enum UnitState {
IDLING,
ATTACKING,
SCOUTING,
MOVING,
DIVIDING,
MERGING,
WAITING,
DYING
}
public enum UnitCommand {
ATTACK_ORDER,
SPLIT_ORDER,
MERGE_ORDER,
MOVE_ORDER,
WAITING_FOR_ORDERS,
NO_ORDERS
}
public class UnitStateManager : MonoBehaviour {
public UnitState actionState;
public UnitState pendingActionState;
public bool selectFlag;
public UnitCommand commandState;
private GameObject attackee;
private NavMeshAgent agent;
private float countdown;
private void Start() {
this.actionState = UnitState.IDLING;
this.agent = this.GetComponent<NavMeshAgent>();
if (this.agent == null) {
Debug.LogError(new System.NullReferenceException("No nav mesh agent detected."));
}
this.agent.updateRotation = true;
this.agent.stoppingDistance = 0.85f;
this.selectFlag = false;
this.commandState = UnitCommand.NO_ORDERS;
}
private void Update() {
if (this.selectFlag) {
DetectInput();
}
}
private void DetectInput() {
if (Input.GetMouseButtonDown(0)) {
}
else if (Input.GetMouseButtonDown(1)) {
}
}
private void WaitingOnOrders() {
if (this.commandState.Equals(UnitCommand.WAITING_FOR_ORDERS)) {
if (Input.GetMouseButtonUp(0)) {
this.commandState = UnitCommand.NO_ORDERS;
this.actionState = UnitState.IDLING;
Debug.Log("CANCEL ORDER");
}
}
}
private void HandleOrders() {
bool waitOrders = this.commandState.Equals(UnitCommand.ATTACK_ORDER) || this.commandState.Equals(UnitCommand.SPLIT_ORDER) || this.commandState.Equals(UnitCommand.MERGE_ORDER);
if (Input.GetKeyDown(KeyCode.A) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.ATTACK_ORDER;
}
else if (Input.GetKeyDown(KeyCode.S) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.SPLIT_ORDER;
}
else if (Input.GetKeyDown(KeyCode.D) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.MERGE_ORDER;
}
else if (Input.GetMouseButtonDown(0) && this.pendingActionState.Equals(UnitState.WAITING) && waitOrders) {
Debug.Log("Hit");
this.commandState = UnitCommand.NO_ORDERS;
this.pendingActionState = this.actionState;
}
else if (Input.GetMouseButtonUp(1)) {
if (this.pendingActionState.Equals(UnitState.WAITING) && waitOrders) {
switch (this.commandState) {
case UnitCommand.NO_ORDERS:
this.pendingActionState = UnitState.MOVING;
this.actionState = this.pendingActionState;
Move();
break;
case UnitCommand.MOVE_ORDER:
this.pendingActionState = UnitState.MOVING;
this.actionState = this.pendingActionState;
Move();
break;
case UnitCommand.ATTACK_ORDER:
this.pendingActionState = UnitState.ATTACKING;
this.actionState = this.pendingActionState;
AttackAction();
break;
default:
break;
}
}
else if (!this.pendingActionState.Equals(UnitState.WAITING) && waitOrders){
if (Input.GetKeyDown(KeyCode.A) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.ATTACK_ORDER;
}
else if (Input.GetKeyDown(KeyCode.S) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.SPLIT_ORDER;
}
else if (Input.GetKeyDown(KeyCode.D) && !waitOrders) {
this.pendingActionState = UnitState.WAITING;
this.commandState = UnitCommand.MERGE_ORDER;
}
else if (Input.GetMouseButtonDown(0) && this.pendingActionState.Equals(UnitState.WAITING) && waitOrders) {
Debug.Log("Hit");
this.commandState = UnitCommand.NO_ORDERS;
this.pendingActionState = this.actionState;
}
}
else {
this.pendingActionState = UnitState.MOVING;
this.actionState = this.pendingActionState;
Move();
}
}
}
private void HandleAction() {
ReachingDestination();
}
private void AttackAction() {
if (this.attackee != null) {
if (!this.agent.pathPending) {
if (this.agent.remainingDistance <= this.agent.stoppingDistance) {
if (this.agent.hasPath || this.agent.velocity.sqrMagnitude == 0f) {
//Stopped.
float distance = Vector3.Distance(this.transform.position, this.attackee.transform.position);
if (distance >= 1f) {
this.agent.stoppingDistance = 0.9f;
this.agent.SetDestination(this.attackee.transform.position);
}
}
}
}
//Do attack here.
UnitHealth health = this.attackee.GetComponent<UnitHealth>();
if (health != null && health.healthPoints > 0) {
this.attackee.SendMessage("DoDamage");
}
return;
}
else {
CheckSurroundings();
if (this.attackee == null) {
AttackMove();
}
ReachingDestination();
}
}
private void AttackObject(GameObject victim) {
Debug.Log("Attacking the " + victim.name.ToString());
this.actionState = UnitState.ATTACKING;
Vector3 spacing = victim.transform.position - this.transform.position;
spacing *= 0.8f;
spacing += this.transform.position;
this.agent.SetDestination(spacing);
}
private void AttackMove() {
this.actionState = UnitState.ATTACKING;
this.countdown = 2f;
if (Input.GetMouseButtonUp(1)) {
Debug.Log("Giving attack order and setting new destination.");
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo)) {
Debug.Log("New move target position: " + hitInfo.point);
this.agent.SetDestination(hitInfo.point);
}
}
}
private void DivideAction() {
}
private void DeathAction() {
}
private void IdleAction() {
CheckSurroundings();
}
private void MergeAction() {
}
private void ScoutAction() {
}
private void MoveAction() {
ReachingDestination();
}
private void CheckSurroundings() {
//Check if enemies are nearby.
bool currentState = this.actionState.Equals(UnitState.IDLING) || this.actionState.Equals(UnitState.ATTACKING);
if (currentState) {
Collider[] colliders = Physics.OverlapSphere(this.transform.position, 3f);
bool enemyNearby = false;
for (int i = 0; i < colliders.Length; i++) {
if (!colliders[i].name.Equals("Floor") && !colliders[i].gameObject.Equals(this.gameObject)) {
float distance = Vector3.Distance(colliders[i].transform.position, this.transform.position);
if (distance < 4f) {
this.attackee = colliders[i].gameObject;
this.SendMessage("AttackObject", this.attackee);
enemyNearby = true;
break;
}
}
}
if (this.attackee != null && !enemyNearby) {
this.attackee = null;
}
}
}
private void Move() {
Debug.Log("Giving attack order and setting new destination.");
this.actionState = UnitState.MOVING;
this.commandState = UnitCommand.MOVE_ORDER;
this.countdown = 2f;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo)) {
Debug.Log("New move target position: " + hitInfo.point);
this.agent.SetDestination(hitInfo.point);
}
}
private void ReachingDestination() {
if (!this.agent.pathPending) {
if (this.agent.remainingDistance <= this.agent.stoppingDistance) {
if (this.agent.hasPath || this.agent.velocity.sqrMagnitude == 0f) {
if (!this.pendingActionState.Equals(UnitState.WAITING)) {
this.commandState = UnitCommand.NO_ORDERS;
this.pendingActionState = UnitState.IDLING;
if (this.countdown > 0f) {
this.countdown -= Time.deltaTime;
}
else if (!this.actionState.Equals(UnitState.WAITING)) {
this.actionState = this.pendingActionState;
}
}
}
}
}
}
}