Another common table game that has made its way into the digital realm is table football. In this recipe, we will create a competitor, imitating the way a human plays the game and using some techniques that emulate human senses and limitations.
In this recipe, we will use the knowledge gained from Chapter 5, Agent Awareness, and the emulation of vision.
First, it is important to have a couple of enum
data structures, as shown in the following code:
public enum TFRAxisCompare { X, Y, Z } public enum TFRState { ATTACK, DEFEND, OPEN }
This is a very extensive recipe. We'll build a couple of classes, one for the table-football bar and the other for the main AI agent that handles the bars, as follows:
using UnityEngine; using System.Collections; public class TFRBar : MonoBehaviour { [HideInInspector] public int barId; public float barSpeed; public float attackDegrees = 30f; public float defendDegrees = 0f; public float openDegrees = 90f; public GameObject ball; private Coroutine crTransition; private bool isLocked; // next steps }
Awake
function:void Awake() { crTransition = null; isLocked = false; }
public void SetState(TFRState state, float speed = 0f) { // next steps }
// optional if (isLocked) return; isLocked = true;
if (speed == 0) speed = barSpeed; float degrees = 0f;
switch(state) { case TFRState.ATTACK: degrees = attackDegrees; break; default: case TFRState.DEFEND: degrees = defendDegrees; break; case TFRState.OPEN: degrees = openDegrees; break; }
if (crTransition != null) StopCoroutine(crTransition); crTransition = StartCoroutine(Rotate(degrees, speed));
public IEnumerator Rotate(float target, float speed) { // next steps }
while (transform.rotation.x != target) { Quaternion rot = transform.rotation; if (Mathf.Approximately(rot.x, target)) { rot.x = target; transform.rotation = rot; } float vel = target - rot.x; rot.x += speed * Time.deltaTime * vel; yield return null; }
isLocked = false; transform.rotation = Quaternion.identity;
public void Slide(float target, float speed) { Vector3 targetPos = transform.position; targetPos.x = target; Vector3 trans = transform.position - targetPos; trans *= speed * Time.deltaTime; transform.Translate(trans, Space.World); }
using UnityEngine; using System.Collections; using System.Collections.Generic; public class TFRival : MonoBehaviour { public string tagPiece = "TFPiece"; public string tagWall = "TFWall"; public int numBarsToHandle = 2; public float handleSpeed; public float attackDistance; public TFRAxisCompare depthAxis = TFRAxisCompare.Z; public TFRAxisCompare widthAxis = TFRAxisCompare.X; public GameObject ball; public GameObject[] bars; List<GameObject>[] pieceList; // next }
Awake
function for initializing the piece list:void Awake() { int numBars = bars.Length; pieceList = new List<GameObject>[numBars]; for (int i = 0; i < numBars; i++) { pieceList[i] = new List<GameObject>(); } }
Update
function:void Update() { int[] currBars = GetNearestBars(); Vector3 ballPos = ball.transform.position; Vector3 barsPos; int i; // next steps }
for (i = 0; i < currBars.Length; i++) { GameObject barObj = bars[currBars[i]]; TFRBar bar = barObj.GetComponent<TFRBar>(); barsPos = barObj.transform.position; float ballVisible = Vector3.Dot(barsPos, ballPos); float dist = Vector3.Distance(barsPos, ballPos); if (ballVisible > 0f && dist <= attackDistance) bar.SetState(TFRState.ATTACK, handleSpeed); else if (ballVisible > 0f) bar.SetState(TFRState.DEFEND); else bar.SetState(TFRState.OPEN); }
OnGUI
function. This will handle the prediction at 30 frames per second:public void OnGUI() { Predict(); }
private void Predict() { Rigidbody rb = ball.GetComponent<Rigidbody>(); Vector3 position = rb.position; Vector3 velocity = rb.velocity.normalized; int[] barsToCheck = GetNearestBars(); List<GameObject> barsChecked; GameObject piece; barsChecked = new List<GameObject>(); int id = -1; // next steps }
do { RaycastHit[] hits = Physics.RaycastAll(position, velocity.normalized); RaycastHit wallHit = null; foreach (RaycastHit h in hits) { // next steps } } while (barsChecked.Count == numBarsToHandle);
GameObject obj = h.collider.gameObject; if (obj.CompareTag(tagWall)) wallHit = h; if (!IsBar(obj)) continue; if (barsChecked.Contains(obj)) continue;
bool isToCheck = false; for (int i = 0; i < barsToCheck.Length; i++) { id = barsToCheck[i]; GameObject barObj = bars[id]; if (obj == barObj) { isToCheck = true; break; } } if (!isToCheck) continue;
Vector3 p = h.point; piece = GetNearestPiece(h.point, id); Vector3 piecePos = piece.transform.position; float diff = Vector3.Distance(h.point, piecePos); obj.GetComponent<TFRBar>().Slide(diff, handleSpeed); barsChecked.Add(obj);
C
void SetPieces() { // next steps }
// Create a dictionary between z-index and bar Dictionary<float, int> zBarDict; zBarDict = new Dictionary<float, int>(); int i;
for (i = 0; i < bars.Length; i++) { Vector3 p = bars[i].transform.position; float index = GetVectorAxis(p, this.depthAxis); zBarDict.Add(index, i); }
// Map the pieces to the bars GameObject[] objs = GameObject.FindGameObjectsWithTag(tagPiece); Dictionary<float, List<GameObject>> dict; dict = new Dictionary<float, List<GameObject>>();
foreach (GameObject p in objs) { float zIndex = p.transform.position.z; if (!dict.ContainsKey(zIndex)) dict.Add(zIndex, new List<GameObject>()); dict[zIndex].Add(p); }
int GetBarIndex(Vector3 position, TFRAxisCompare axis) { // next steps }
int index = 0; if (bars.Length == 0) return index;
float pos = GetVectorAxis(position, axis); float min = Mathf.Infinity; float barPos; Vector3 p;
for (int i = 0; i < bars.Length; i++) { p = bars[i].transform.position; barPos = GetVectorAxis(p, axis); float diff = Mathf.Abs(pos - barPos); if (diff < min) { min = diff; index = i; } }
return index;
float GetVectorAxis(Vector3 v, TFRAxisCompare a) { if (a == TFRAxisCompare.X) return v.x; if (a == TFRAxisCompare.Y) return v.y; return v.z; }
public int[] GetNearestBars() { // next steps }
int numBars = Mathf.Clamp(numBarsToHandle, 0, bars.Length); Dictionary<float, int> distBar; distBar = new Dictionary<float, int>(bars.Length); List<float> distances = new List<float>(bars.Length); int i; Vector3 ballPos = ball.transform.position; Vector3 barPos;
for (i = 0; i < bars.Length; i++) { barPos = bars[i].transform.position; float d = Vector3.Distance(ballPos, barPos); distBar.Add(d, i); distances.Add(d); }
distances.Sort();
int[] barsNear = new int[numBars]; for (i = 0; i < numBars; i++) { float d = distances[i]; int id = distBar[d]; barsNear[i] = id; }
return barsNear;
private bool IsBar(GameObject gobj) { foreach (GameObject b in bars) { if (b == gobj) return true; } return false; }
private GameObject GetNearestPiece(Vector3 position, int barId) { // next steps }
float minDist = Mathf.Infinity; float dist; GameObject piece = null;
foreach (GameObject p in pieceList[barId]) { dist = Vector3.Distance(position, p.transform.position); if (dist < minDist) { minDist = dist; piece = p; } }
return piece;
The table-football competitor draws on the skills developed from the air-hockey rival. This means casting rays to get the trajectory of the ball and moving the nearest bar considering the pieces. It also moves the bar, depending on whether the rival is attacking or defending, so that it can block the ball or let it go further.
3.133.124.21