It’s unfinished AI but it’s getting close to being done. I’m making it for an after-school club that is pretty much Unity3D oriented. We have a robotics team in the school and the club thought it would be cool if we would make the robotics tournament this year, into a Unity3D game. The tournament is Rebound Rumble http://www.youtube.com/watch?v=VkoEvWa62w4 Now we aren’t going to put in the balancing on the bridges part because our game really is just going to be collecting balls and shooting them in the hoops; I decided to make AI for the sake of making it more interesting… and for a good learning experience.
The AI graphics aren’t made yet as it’s just capsules with cubes as faces, but the code part works. I’ve just got to touch up the code some more to where it’d fit in the game scenario. The court isn’t on here, as this is just an AI testing area.
Play As Another Robot
In this webplayer, you play as one of the robots. The AI picks up balls on their side, and throws them at you. You can pick up balls yourself by running over them; you can only hold 3 balls at a time. You can shoot the balls by pressing the LEFT SHIFT.
http://dl.dropbox.com/u/53915463/Player%20AI%20test/WebPlayer.html
Play As a Ball:
In this webplayer, you play from the perspective of one of the balls that are being collected and shot by the AI. http://dl.dropbox.com/u/53915463/AI%20Testing%20Webplayer/WebPlayer.html
AI CODE:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MyBestAI : MonoBehaviour {
public Vector3 shootSpot;
public Vector3 hoop;
public Vector3 shootBallFrom;
public float maxDistance;
public int rotationSpeed;
public float moveSpeed;
private GameObject closestBall;
//The different States being used
public enum State {
TargetClosestBall,
CollectClosestBall,
RotateToOtherCourtSideAndShootBall,
NoFreeBalls
}
public State state;
//assign variables in awake and make current state be TargetClosestBall
public void Awake() {
shootSpot = GameObject.Find("shootSpot").transform.position;
hoop = GameObject.Find("hoop").transform.position;
state = State.TargetClosestBall;
}
/*make it so collisions don't rotate the AI and
and also start the Finite State Machine*/
void Start() {
rigidbody.freezeRotation = true;
StartCoroutine(FSM());
}
/*can somebody please tell me what exactly this is called
and how it works? All I know is that it finds objects with
the tag "FreeBall" and assigns the closest one as closestBall*/
GameObject FindClosestEnemy() {
GameObject[] gos;
gos = GameObject.FindGameObjectsWithTag("FreeBall");
float distance = Mathf.Infinity;
Vector3 position = transform.position;
foreach (GameObject go in gos) {
Vector3 diff = go.transform.position - position;
float curDistance = diff.sqrMagnitude;
if (curDistance < distance) {
closestBall = go;
distance = curDistance;
}
}
return null;
}
IEnumerator FSM() {
// Execute the current coroutine (state)
while (true)
yield return StartCoroutine(state.ToString());
}
IEnumerator TargetClosestBall() {
/* This part works as the Enter function
of the previous post (it's optional) */
FindClosestEnemy();
yield return null;
/* Now we enter in the Execute part of a state
which will be usually inside a while - yield block */
//check if closestBall is assigned to an object
if(closestBall != null) {
bool moveToBall = true;
bool ballIsGrabbable = false;
bool closeToBall = false;
Vector3 targettedBall = closestBall.transform.position;
targettedBall.y = transform.position.y;
while (moveToBall) {
while(!closeToBall) {
/*check if the tag is still "FreeBall" in the case
of another AI getting the ball before we do*/
if (closestBall.tag == "FreeBall") {
targettedBall = closestBall.transform.position;
targettedBall.y = transform.position.y;
//start rotating towards the ball
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targettedBall - transform.position), rotationSpeed * Time.deltaTime);
/*if the angle difference between our current look rotation and the look rotation of looking right at the object,
is within 10 degrees; start moving forward. But continue rotating to accommodate for a rolling ball*/
if(Quaternion.Angle(Quaternion.LookRotation(targettedBall - transform.position), transform.rotation) <= 10) {
transform.position += transform.forward * (moveSpeed * Time.deltaTime);
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targettedBall - transform.position), rotationSpeed * Time.deltaTime);
/*check if the distance between us and the ball, is below or equal to maxDistance,
and if the ball is bellow our current y transform (if the ball is touching the ground)*/
if(Vector3.Distance(targettedBall, transform.position) <= maxDistance closestBall.transform.position.y <= gameObject.transform.position.y) {
ballIsGrabbable = true;
closeToBall = true;
//do one last check if the tag and name is still "FreeBall" to see if another AI has grabbed the ball already
if(closestBall.tag == "FreeBall" closestBall.name == "FreeBall") {
closestBall.transform.parent = gameObject.transform;
closestBall.name = "CapturedBall " + gameObject.name;
closestBall.tag = "CapturedBall";
closestBall.layer = 10;
}
//if the tag and/or name is other than "FreeBall" (AKA, ball is already grabbed by other AI)
else {
moveToBall = false;
closestBall = null;
closeToBall = true;
state = State.TargetClosestBall;
}
}
}
}
//if the ball does not have the tag of "FreeBall" (from the first check)
else {
moveToBall = false;
closestBall = null;
closeToBall = true;
state = State.TargetClosestBall;
}
yield return new WaitForFixedUpdate();
}
//after (!closeToBall) while loop
if (ballIsGrabbable closeToBall) {
moveToBall = false;
state = State.CollectClosestBall;
}
yield return new WaitForFixedUpdate();
}
}
//there are no FreeBalls on the court, make state NoFreeBalls
else {
state = State.NoFreeBalls;
}
/* And finally do something before leaving
the state (optional too) and starting a new one */
}
IEnumerator CollectClosestBall() {
//Enter
bool grabbingBall = false;
yield return null;
//Execute
int grabbingTime = 2;
closestBall.rigidbody.isKinematic = true;
closestBall.rigidbody.useGravity = false;
//this check seems unnecessary but I like to have it just incase
if(closestBall.name == "CapturedBall " + gameObject.name) {
grabbingBall = true;
while(grabbingBall){
grabbingTime --;
/*this check seems necessary, in the case of
two AI grabbing a ball at the same time*/
if (closestBall.name == "CapturedBall " + gameObject.name) {
if(grabbingTime <= 0){
grabbingBall = false;
closestBall.transform.localPosition = shootBallFrom;
state = State.RotateToOtherCourtSideAndShootBall;
}
}
//if another AI has grabbed the ball while we were grabbing it
else {
grabbingBall = false;
state = State.TargetClosestBall;
}
//if still grabbing the ball, wait for 1 second
if(grabbingBall) {
yield return new WaitForSeconds(1f);
}
//else just continue on to the next set of code
else {
yield return null;
}
}
}
//if the ball's name is not "FreeBall"
else {
state = State.TargetClosestBall;
}
}
/*I would like to split this into two IEnumerators "Move to shooting spot" "Shoot Ball"
to easily make some more advanced shooting options without having while loops inside of while loops.
I just think it's more clean if you keep functions seperate don't combine IEnumerators*/
IEnumerator RotateToOtherCourtSideAndShootBall() {
//Enter
yield return null;
//Execute
bool canShoot = false;
bool aiming = false;
bool rotating = true;
//while not in the shooting spot (shootSpot)
while(!canShoot){
shootSpot.y = transform.position.y;
//first rotate towards shootSpot, then move towards it
if(rotating) {
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(shootSpot - transform.position), rotationSpeed * Time.deltaTime);
if(Quaternion.Angle(Quaternion.LookRotation(shootSpot - transform.position), transform.rotation) < 1) {
transform.rotation = Quaternion.LookRotation(shootSpot - transform.position);
rotating = false;
}
}
//moving towards shootSpot after rotating towards it
else {
//while distance from shootSpot is over maxDistance
if(Vector3.Distance(shootSpot, transform.position) > maxDistance) {
transform.position += transform.forward * (moveSpeed * Time.deltaTime);
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(shootSpot - transform.position), rotationSpeed * Time.deltaTime);
}
//if distance from shootSpot is under/equal to maxDistance
else {
canShoot = true;
}
}
yield return new WaitForFixedUpdate();
}
//if in the shootSpot
if (canShoot) {
aiming = true;
//rotate towards the player
while(aiming){
hoop = GameObject.Find("Player").transform.position;
hoop.y = transform.position.y;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(hoop - transform.position), rotationSpeed * Time.deltaTime);
if (Quaternion.Angle(Quaternion.LookRotation(hoop - transform.position), transform.rotation) <= 2){
aiming = false;
}
yield return new WaitForFixedUpdate();
}
//shoot the ball at the player
closestBall.transform.parent = null;
closestBall.rigidbody.useGravity = true;
closestBall.rigidbody.isKinematic = false;
closestBall.transform.renderer.material.color = Color.red;
closestBall.transform.rotation = gameObject.transform.rotation;
closestBall.rigidbody.AddRelativeForce(0,10,5, ForceMode.Impulse);
closestBall = null;
canShoot = false;
}
//returning back to square one
state = State.TargetClosestBall;
}
//this just checks if there is a FreeBall, every second
IEnumerator NoFreeBalls() {
//Enter
yield return null;
while(closestBall == null) {
FindClosestEnemy();
yield return new WaitForSeconds(1);
}
//Exit
state = State.TargetClosestBall;
}
}