- Home /
AI pause and attack a player in c# (script included)
Hi all,
Just need a little help understanding how I should be doing this.
Currently, I have a prototype where I have a set of waypoints in my script for an AI object to move along. At some point when a player object enters a detection radius trigger, it will stop and for the moment - jump up in the air (using the attached rigidbody.)
The enemy object(monster) has its own collider so it can collide with objects in the scene, but it also has a parent game object
=>parent (script attached to here, sphere collider trigger here detects player, so any jumping is applied to this rigidbody)
===> monster (child, pretty much the model)
using UnityEngine;
using System.Collections;
public class monster : MonoBehaviour {
public Vector3[] waypoints;
public float time;
public float wait_time;
private float time_till_attack;
public bool smooth = false;
private float node_radius = 0.5f;
private float _t = 0;
private Vector3 start, finish;
private int index = 0;
private float wait_timer = 0;
private bool player_detected = false;
private float attack_timer = 0;
// Use this for initialization
void Start ()
{
SetTargetPosition();
}
void Update()
{
// if no player detected, visit the waypoints
if (!player_detected)
{
if (waypoints.Length > 1)
{
if (_t >= 1.0f)
{
// this is just a pause before moving to next waypoint
if (wait_timer < wait_time)
wait_timer += Time.deltaTime;
else
{
SetTargetPosition();
wait_timer = 0;
}
}
float time_ratio = smooth ? Mathf.SmoothStep(0.0f, 1.0f, _t) : _t;
transform.position = Vector3.Lerp(start,finish, time_ratio);
_t += Time.deltaTime/time;
}
}
else
{
// can this be done as a coroutine?
// I had all sorts of trouble trying to have a simple pause before jump
if (attack_timer < time_till_attack)
attack_timer += Time.deltaTime;
else
{
// after pausing, jump at the player
// repeat this until the player is out of detection zone
// to continue back on waypoint path
}
}
}
void SetTargetPosition()
{
if (index > waypoints.Length - 1) index = 0;
start = waypoints[index];
index++;
if (index > waypoints.Length - 1) index = 0;
finish = waypoints[index];
_t = 0;
}
void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
player_detected = true;
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
player_detected = false;
}
public void OnDrawGizmos()
{
if(waypoints == null)
return;
for(int i=0;i< waypoints.Length;i++) {
Vector3 pos = waypoints[i];
Gizmos.color = Color.yellow;
Gizmos.DrawSphere(pos, node_radius);
if(i>0) {
Vector3 prev = waypoints[i-1];
Gizmos.DrawLine(prev,pos);
}
}
}
}
Again - this is a prototype script! I'll fine tune it once I have the basics down: 1 ai walks along waypoints (done) 2 ai detects player (done) 3 ai stops waypoint movement when player detected (done) 4 ai pauses for a set time (see the comment in code, I could not get the coroutine to work) 5 ai jumps in the air to attempt to knock player (a flying kind of object) 6 repeat from 4 until player leaves detection radius.
ideally the monster will 'jump' in the direction of the player. The jump will always be the same amount on the current level. * with a jump animation, I could just detect when the animation finishes at step 5, but assume I dont have that luxury
Does anyone have any good ideas on how I can do this. It will help me going forward in producing other types on enemy scripts. Also any comments on improving the code (ahem coroutines) would be welcomed.
Cheers
See if this article helps you out http://unitygems.com/basic-ai-character/#Range
great link, i'll go through it and report back where it helps
Cheers!
@cdrandin Thanks for the tip, I always forget unity gems, its a great site. In this case the NPC solution was not for my needs, though I did get some possible ideas about implementing more of a state model in my code - it would certainly clean it up (IS_ROTATING, PLAYER_DETECTED etc)
Thank you.
Answer by hellaeon · Jul 25, 2013 at 12:47 PM
Ok, turns out what I wrote in my original post kind of hinted towards an answer. By creating a jump animation, attaching it to the monster (child object), then calling it when I detected a player, all I had to do was check if it was still playing before either repeating a jump or continuing on my waypoint path. This works exactly to my needs. The only thing missing is making the monster jump towards the player, but its ok for now.
EDIT: this is the updated final draft of the source for now. In the end i went with the current rotation solution and brought in a little bit of a state machine.
using UnityEngine;
using System.Collections;
public class AIMonster : MonoBehaviour {
public Vector3[] waypoints;
public float time;
public float wait_time;
public float time_till_attack;
public bool smooth = false;
private float lerp_ratio = 0;
private Vector3 start, finish;
private int index = 0;
private float wait_timer = 0;
private float attack_timer = 0;
private Animation anim;
private enum STATES { WALKING, TURNING, ATTACK };
private STATES state = STATES.WALKING;
private float node_radius = 0.5f; // gizmo
// Use this for initialization
void Awake ()
{
anim = transform.GetChild(0).animation;
SetTargetPosition();
}
void Update()
{
if (waypoints.Length == 0)
return;
switch (state)
{
case STATES.WALKING:
if(!anim.isPlaying)
{
if (lerp_ratio >= 1.0f)
{
// this is just a pause before moving to next waypoint
if (wait_timer < wait_time)
wait_timer += Time.deltaTime;
else
{
state = STATES.TURNING;
SetTargetPosition();
wait_timer = 0;
}
}
float time_ratio = smooth ? Mathf.SmoothStep(0.0f, 1.0f, lerp_ratio) : lerp_ratio;
transform.position = Vector3.Lerp(start,finish, time_ratio);
lerp_ratio += Time.deltaTime/time;
}
break;
case STATES.TURNING:
Quaternion finish_rot = Quaternion.LookRotation(finish);
transform.rotation = Quaternion.Lerp(transform.rotation, finish_rot, lerp_ratio);
lerp_ratio += Time.deltaTime/time;
if (transform.rotation.eulerAngles.y == finish_rot.eulerAngles.y)
{
lerp_ratio = 0;
state = STATES.WALKING;
}
break;
case STATES.ATTACK:
if (!anim.isPlaying)
{
// pause before jumping
if (attack_timer < time_till_attack)
attack_timer += Time.deltaTime;
else
{
anim.Play("jump");
attack_timer = 0;
}
}
break;
}
}
void SetTargetPosition()
{
if (index > waypoints.Length - 1) index = 0;
start = waypoints[index];
index++;
if (index > waypoints.Length - 1) index = 0;
finish = waypoints[index];
lerp_ratio = 0;
}
void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
state = STATES.ATTACK;
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
state = STATES.WALKING;
}
public void OnDrawGizmos()
{
if(waypoints == null)
return;
for(int i=0;i< waypoints.Length;i++) {
Vector3 pos = waypoints[i];
Gizmos.color = Color.yellow;
Gizmos.DrawSphere(pos, node_radius);
if(i>0) {
Vector3 prev = waypoints[i-1];
Gizmos.DrawLine(prev,pos);
}
}
}
}
Answer by FreeTimeDev · Jul 25, 2013 at 04:57 AM
spin_attacking = true;
rigidbody.AddForce(Vector3.forward);
Which way is "Forward"? Is your Parent object facing/oriented the same as your model? Hopefully it's this simple.
Hi FreeTimeDev, that code is just for testing and should not have been left in there to confuse people. Apologies for that, I have edited the original post with a few comments and removed any bogus code to keep the issue more clear...
Thanks