Created
April 24, 2017 11:13
-
-
Save 404ryannotfound/492faea8346493296c36471781d56b2f to your computer and use it in GitHub Desktop.
Unity3d Zombie Movement script via http://answers.unity3d.com/questions/1281147/zombie-ai-running-in-place-but-not-moving.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
[RequireComponent (typeof (Animator))] | |
[RequireComponent (typeof (NavMeshAgent))] | |
[RequireComponent (typeof (CapsuleCollider))] | |
public class ZombieInstance : MonoBehaviour { | |
public float hp = 100.0f, damage = 20.25f, bodieRemovalTime = 10.0f, moveSpeed = 2.0f, fieldOfView = 45.0f, viewDistance = 5.0f, playerSearchInterval = 1.0f, minChase = 5.0f, maxChase = 10.0f, minWander = 5.0f, maxWander = 20.0f; | |
/* | |
* Health points of this zombie. | |
* Damage of the attacks of this zombie. | |
* Time to wait for deleting the dead bodie. | |
* GLOBAL SPEED OF THE ZOMBIE I RECOMMEND 2 FOR WALKERS AND 7 FOR RUNNERS, DEPENDS ON YOUR GAME | |
* FIELD OF VIEW OF THE ZOMBIE | |
* VIEW DISTANCE OF THE ZOMBIE | |
* INTERVAL USED TO CHECK IF THE ZOMBIE IS LOOKING AT THE PLAYER, JUST TO PREVENT OVERUSE OF RESOURCES | |
SET 0 IF YOU DON'T CARE OR KEEP IT BETWEEN 1.0F AND 0.5F, RECOMMENDED | |
*Min time to chase the player | |
*Max time to chase the player (will be random between these 2 values) | |
*Min time to wander around | |
*Max time to wander around (works the same way) | |
*/ | |
public string playerTag = "Player", bodiesTag = "DeadPlayer"; | |
/* | |
* SET HERE THE TAG TO IDENTIFY YOUR PLAYER | |
* YOUR PLAYER DEAD BODIES | |
*/ | |
public bool canRun = false, eatBodies = false; | |
/* | |
* CAN YOUR ZOMBIE RUN? | |
* WILL IT EAT THE DEAD PLAYER BODIES? | |
*/ | |
public Transform zombieHead = null;//Pivot point to use as reference, use it as if it were the eyes of the zombie. | |
public LayerMask checkLayers;//Layers to check when searching for the player (after the check interval) | |
/*Zombie sounds AnimationStates | |
* 0 = Sound on wandering, idle or whatever. | |
* 1 = Sound for chasing player. | |
* 2 = Sound for losing player or extras. | |
* 3 = Sound while eating. | |
* Length of 4. | |
*/ | |
public AudioClip[] audioClips = new AudioClip[4]; | |
/* | |
* If you want to set a new clip, you must change the length of the array and edit the inspector script. | |
* You can find the needed line by searching "AnimationStates", on the script ZombieUI.cs | |
*/ | |
private bool playerChase = false, wandering = false, eatingBodie = false; | |
/* | |
* Will be true when the zombie is chasing the player, then the code will randomize when will stop doing it. | |
*/ | |
private float lastCheck = -10.0f, lastChaseInterval = -10.0f, lastWander = -10.0f; | |
/* | |
*Last time we checked the player's position. | |
*Last or next time we chased the player before losing him. | |
*Last time we set a wander position | |
*/ | |
private NavMeshAgent agent;//This zombie's nav mesh agent | |
private Animator Anim;//This zombie's animator | |
private Transform player = null, wanderManager = null;//Player position/transform | |
private Vector3 lastKnownPos = Vector3.zero;//Last known player position | |
private AudioSource SNDSource;//zombieHead should always have 2 audioSources | |
void Start () { | |
//THIS IS USED TO GET THE LOCAL NAV MESH AGENT, wanderManager AND ANIMATOR OF THIS ZOMBIE | |
agent = GetComponent<NavMeshAgent>(); | |
Anim = GetComponent<Animator>(); | |
SNDSource = zombieHead.GetComponent<AudioSource>(); | |
wanderManager = GameObject.FindWithTag("wanderManager").transform; | |
//SET THE MAIN VALUES | |
agent.speed = moveSpeed; | |
agent.acceleration = moveSpeed * 40; | |
agent.angularSpeed = 999; | |
resetZombie(); | |
} | |
void Update(){ | |
/* IN CASE YOU CHANGE YOUR PLAYER GAMEOBJECT OR THE ZOMBIE IS SPAWNINg */ | |
if(Anim.GetCurrentAnimatorStateInfo(0).IsName("Spawn")) | |
return; | |
if(player == null){ | |
if(eatingBodie) | |
resetZombie(); | |
GameObject newPlayer = GameObject.FindWithTag(playerTag); | |
GameObject bodySearch = GameObject.FindWithTag(bodiesTag); | |
if(newPlayer != null && !newPlayer.name.Contains("Clone")){ | |
//There is a bug in unity that i couldn't fix, so i added the clone thing, so yeah... | |
player = newPlayer.transform; | |
}else if(eatBodies && bodySearch != null){ | |
player = bodySearch.transform; | |
eatingBodie = true; | |
followBodie(); | |
}else{ | |
doWanderFunctions(); | |
if(newPlayer != null && newPlayer.name.Contains("Clone")){//Prevent that bug on the next search | |
Destroy(newPlayer); | |
} | |
return;//DON'T DO ANYTHING UNTIL WE HAVE THE PLAYER GAMEOBJECT, GO BACK, ABOOORRTTT!!!! | |
} | |
} | |
/* ACTUAL ZOMBIE CODE */ | |
if(Time.time > lastCheck){//SEARCH FOR THE PLAYER AFTER INTERVAL | |
checkView(); | |
lastCheck = Time.time + playerSearchInterval; | |
} | |
if(!eatingBodie){ | |
/* PLAYER SEARCH ALGORITHMS */ | |
if(playerChase && Anim.GetBool("isChasing")){ | |
if(Time.time > lastChaseInterval){ | |
gotoLastKnown(); | |
}else{ | |
chasePlayer(); | |
} | |
} | |
//SET THE ATTACK AND RESET IT | |
AnimatorStateInfo state = Anim.GetCurrentAnimatorStateInfo(0); | |
if(!state.IsName("Attack") && playerChase && reachedPathEnd()){//READY TO ATTACK! | |
Anim.SetTrigger("doAttack"); | |
Anim.SetBool("isIdle", false); | |
Anim.SetBool("isChasing", false); | |
playerChase = false; | |
} | |
if(state.IsName("Attack") && state.normalizedTime > 0.90f){ | |
chasePlayer(); | |
Attack();//HERE WE CALL THE PLAYER'S DAMAGE | |
} | |
}else{ | |
/* EAT BODIE ALGORITHMS */ | |
if(reachedPathEnd()){//This means we are in the right position to eat the bodie. | |
startEating(); | |
}else{ | |
followBodie();//In case or explosions or stuff like that. | |
} | |
} | |
//MAKE THE ZOMBIE WANDER AROUND THE MAP | |
if(wandering){ | |
if(reachedPathEnd()){ | |
resetZombie(); | |
} | |
if(Time.time > lastWander){//IF WE ARE READY TO CHOOSE A NEW POSITION | |
wanderManager.SendMessage("getNewPos", this.gameObject, SendMessageOptions.RequireReceiver); | |
} | |
} | |
} | |
//Just to make this code prettier, simplify with functions... | |
void checkView(){ | |
RaycastHit hit = new RaycastHit(); | |
Vector3 checkPosition = player.position - zombieHead.position; | |
if(Vector3.Angle(checkPosition, zombieHead.forward) < fieldOfView){ //Check if player is inside the field of view | |
if (Physics.Raycast(zombieHead.position, checkPosition, out hit, viewDistance, checkLayers)) { | |
if(hit.collider.tag == playerTag){//do this.. | |
chasePlayer(); | |
lastChaseInterval = Time.time + Random.Range(minChase, maxChase); | |
} | |
} | |
}else if(meleeDistance()){ | |
chasePlayer(); | |
lastChaseInterval = Time.time + Random.Range(minChase, maxChase); | |
} | |
} | |
void gotoLastKnown(){ | |
Anim.SetBool("isChasing", true); | |
if(canRun) | |
Anim.SetBool("isRunning", true); | |
Anim.SetBool("isIdle", false); | |
playerChase = true; | |
agent.SetDestination(lastKnownPos); | |
agent.Resume(); | |
wandering = true; | |
eatingBodie = false; | |
} | |
void chasePlayer(){ | |
Anim.SetBool("isChasing", true); | |
if(canRun) | |
Anim.SetBool("isRunning", true); | |
Anim.SetBool("isIdle", false); | |
playerChase = true; | |
agent.SetDestination(player.position); | |
lastKnownPos = player.position; | |
agent.Resume(); | |
wandering = false; | |
eatingBodie = false; | |
playSound(audioClips[1], false, false, true, true); | |
} | |
void stopChase(){ | |
Anim.SetBool("isChasing", false); | |
if(canRun) | |
Anim.SetBool("isRunning", false); | |
Anim.SetBool("isIdle", true); | |
playerChase = false; | |
agent.Stop(); | |
wandering = false; | |
eatingBodie = false; | |
} | |
void resetZombie(){ | |
Anim.SetBool("isIdle", true); | |
Anim.SetBool("isChasing", false); | |
Anim.SetBool("isEating", false); | |
if(canRun) | |
Anim.SetBool("isRunning", false); | |
playerChase = false; | |
agent.Stop(); | |
wandering = true; | |
eatingBodie = false; | |
playSound(audioClips[0], false, false, true, true); | |
} | |
void startEating(){ | |
Anim.SetBool("isChasing", false);; | |
if(canRun) | |
Anim.SetBool("isRunning", false); | |
Anim.SetBool("isIdle", false); | |
Anim.SetBool("isEating", true); | |
playerChase = false; | |
agent.SetDestination(player.position);//Just to keep track of it, ignore this. | |
agent.Stop();//Won't actually follow the bodie, let's store that for later. | |
wandering = false; | |
eatingBodie = true; | |
playSound(audioClips[3], true, true, false, true); | |
} | |
void followBodie(){ | |
Anim.SetBool("isChasing", true); | |
if(canRun) | |
Anim.SetBool("isRunning", true); | |
Anim.SetBool("isIdle", false); | |
Anim.SetBool("isEating", false); | |
playerChase = false; | |
agent.SetDestination(player.position);//In this case "player" will be a dead bodie. | |
agent.Resume();//Lets follow it, to prevent mistakes. | |
wandering = false; | |
eatingBodie = true; | |
SNDSource.Stop(); | |
} | |
void setNewWanderPos(Vector3 targetPos){ | |
Anim.SetBool("isIdle", false); | |
Anim.SetBool("isChasing", true); | |
if(canRun) | |
Anim.SetBool("isRunning", true); | |
playerChase = false; | |
agent.SetDestination(targetPos); | |
agent.Resume(); | |
lastWander = Time.time + Random.Range(minWander, maxWander); | |
playSound(audioClips[2], false, false, true, true); | |
} | |
/* PARAMS: | |
* Sound: Sound clip AudioClip | |
* loop: boolean, to make the audio loop | |
* randomStart: boolean, Start the clip at a random time | |
* checkSameClip: boolean, prevent the replay of the audio if it is the same clip | |
* isPlayingCheck: boolean, when preventing replay, should check if it's still playing while the same clip? | |
*/ | |
void playSound(AudioClip sound, bool loop, bool randomStart = false, bool checkSameClip = false, bool isPlayingCheck = false){ | |
if(checkSameClip && SNDSource.clip == sound && !SNDSource.isPlaying) | |
return; | |
if(isPlayingCheck && SNDSource.clip == sound && SNDSource.isPlaying) | |
return; | |
SNDSource.clip = sound; | |
SNDSource.loop = loop; | |
if(randomStart) | |
SNDSource.time = Random.Range(0.0f, sound.length); | |
SNDSource.Play(); | |
} | |
//ONLY WHEN THE PLAYER AND DEAD BODY VARIABLE ARE NULL | |
void doWanderFunctions(){ | |
//MAKE THE ZOMBIE WANDER AROUND THE MAP EXACTLY LIKE THE UPDATE SYSTEM, JUST TO MAKE EVERYTHING EASIER | |
if(wandering){ | |
if(reachedPathEnd()){ | |
resetZombie(); | |
} | |
if(Time.time > lastWander){//IF WE ARE READY TO CHOOSE A NEW POSITION | |
wanderManager.SendMessage("getNewPos", this.gameObject, SendMessageOptions.RequireReceiver); | |
} | |
} | |
} | |
void Attack(){ | |
if(!meleeDistance())//DON'T DO ANYTHING IF WE ARE NOT AT A MELEE DISTANCE TO THE PLAYER | |
return; | |
/* | |
* HERE YOU SET DAMAGE TO PLAYER YOU MUST DO THE CODE BY YOURSELF | |
* REMEMBER THE PLAYER VARIABLE IS ALREADY SET | |
*/ | |
agent.updateRotation = false; | |
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(agent.destination - transform.position, transform.up), 7.0f *Time.deltaTime); | |
agent.updateRotation = true; | |
player.SendMessage("Damage", damage, SendMessageOptions.RequireReceiver); | |
} | |
void Damage(float dmg){ | |
/*HERE YOU CALL DAMAGE FOR ZOMBIE EXAMPLE: | |
*zombieObject.SendMessage("ZombieDamage", float value, SendMessageOptions.RequireReceiver); | |
*/ | |
hp -= dmg; | |
if(player != null) | |
chasePlayer(); | |
if(hp < 0.0f || hp == 0.0f){//This zombie is freakin' dead! | |
this.tag = "Untagged"; | |
BroadcastMessage("ActivateR", SendMessageOptions.RequireReceiver);//Activate ragdoll or spawn the dead bodie. | |
Anim.enabled = false;//Deactivate things that might screw the ragdoll up. | |
agent.enabled = false; | |
GetComponent<Collider>().enabled = false; | |
Destroy(gameObject, bodieRemovalTime); | |
enabled = false; | |
} | |
} | |
//Check if agent reached the player | |
bool reachedPathEnd (){ | |
if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance){ | |
if (!agent.hasPath || agent.velocity.sqrMagnitude == 0.0f){ | |
return true; | |
} | |
return true; | |
} | |
return false; | |
} | |
bool meleeDistance(){ | |
if(Vector3.Distance(transform.position, player.position) < 2.0f){ | |
return true; | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Unity?