Hi all,
I ported Lerpz 3D Plateform sample to c# to allow me to reuse some of it in my own project. All of it was succesfully ported and behave as expected.
The only problem i’m facing now is the EnemyPoliceGuy.js script not behaving as it shoulds. EnemyPoliceGuy.js is the main controller for enemies and it manages a state-machine and acts upon it to make them alive.
I’m sure the problem lies in the usage i’m making of the yield statement. Sadly, i’ve never used the yield statement in my previous c# projects.
So here’s my translation to c# of the famous EnemyPoliceGuy.js script. ![]()
using UnityEngine;
using System.Collections;
/*
animations played are:
idle, threaten, turnjump, attackrun
*/
[RequireComponent(typeof(AudioSource))]
[RequireComponent(typeof(CharacterController))]
public class EnemyPoliceGuy : MonoBehaviour
{
public float attackTurnTime = 0.7f;
public float rotateSpeed = 120.0f;
public float attackDistance = 17.0f;
public float extraRunTime = 2.0f;
public float damage = 1.0f;
public float attackSpeed = 5.0f;
public float attackRotateSpeed = 20.0f;
public float idleTime = 1.6f;
public Vector3 punchPosition = new Vector3(0.4f, 0, 0.7f);
public float punchRadius = 1.1f;
// sounds
public AudioClip idleSound; // played during "idle" state.
public AudioClip attackSound; // played during the seek and attack modes.
private float attackAngle = 10.0f;
private bool isAttacking = false;
private float lastPunchTime = 0.0f;
public Transform target;
// Cache a reference to the controller
private CharacterController characterController;
// Cache a link to LevelStatus state machine script:
public LevelStatus levelStateMachine;
public void Awake()
{
characterController = GetComponent<CharacterController>();
levelStateMachine = GameObject.Find("/Level").GetComponent<LevelStatus>();
if (!levelStateMachine)
{
Debug.Log("EnemyPoliceGuy: ERROR! NO LEVEL STATUS SCRIPT FOUND.");
}
}
public IEnumerator Start()
{
if (!target)
target = GameObject.FindWithTag("Player").transform;
if (animation)
{
animation.wrapMode = WrapMode.Loop;
// Setup animations
animation.Play("idle");
animation["threaten"].wrapMode = WrapMode.Once;
animation["turnjump"].wrapMode = WrapMode.Once;
animation["gothit"].wrapMode = WrapMode.Once;
animation["gothit"].layer = 1;
}
// initialize audio clip. Make sure it's set to the "idle" sound.
audio.clip = idleSound;
yield return new WaitForSeconds(Random.value);
// Just attack for now
while (true)
{
// Don't do anything when idle. And wait for player to be in range!
// This is the perfect time for the player to attack us
yield return Idle();
// Prepare, turn to player and attack him
yield return Attack();
}
}
public IEnumerator Idle()
{
// if idling sound isn't already set up, set it and start it playing.
if (idleSound)
{
if (audio.clip != idleSound)
{
audio.Stop();
audio.clip = idleSound;
audio.loop = true;
audio.Play(); // play the idle sound.
}
}
// Don't do anything when idle
// The perfect time for the player to attack us
yield return new WaitForSeconds(idleTime);
// And if the player is really far away.
// We just idle around until he comes back
// unless we're dying, in which case we just keep idling.
while (true)
{
characterController.SimpleMove(Vector3.zero);
yield return new WaitForSeconds(0.2f);
var offset = transform.position - target.position;
// if player is in range again, stop lazyness
// Good Hunting!
if (offset.magnitude < attackDistance)
yield return null;
}
}
public float RotateTowardsPosition(Vector3 targetPos, float rotateSpeed)
{
// Compute relative point and get the angle towards it
var relative = transform.InverseTransformPoint(targetPos);
var angle = Mathf.Atan2(relative.x, relative.z) * Mathf.Rad2Deg;
// Clamp it with the max rotation speed
var maxRotation = rotateSpeed * Time.deltaTime;
var clampedAngle = Mathf.Clamp(angle, -maxRotation, maxRotation);
// Rotate
transform.Rotate(0, clampedAngle, 0);
// Return the current angle
return angle;
}
public IEnumerator Attack()
{
isAttacking = true;
if (attackSound)
{
if (audio.clip != attackSound)
{
audio.Stop(); // stop the idling audio so we can switch out the audio clip.
audio.clip = attackSound;
audio.loop = true; // change the clip, then play
audio.Play();
}
}
// Already queue up the attack run animation but set it's blend wieght to 0
// it gets blended in later
// it is looping so it will keep playing until we stop it.
animation.Play("attackrun");
// First we wait for a bit so the player can prepare while we turn around
// As we near an angle of 0, we will begin to move
float angle;
angle = 180.0f;
float time;
time = 0.0f;
Vector3 direction;
while (angle > 5 || time < attackTurnTime)
{
time += Time.deltaTime;
angle = Mathf.Abs(RotateTowardsPosition(target.position, rotateSpeed));
var move = Mathf.Clamp01((90 - angle) / 90);
// depending on the angle, start moving
animation["attackrun"].weight = animation["attackrun"].speed = move;
direction = transform.TransformDirection(Vector3.forward * attackSpeed * move);
characterController.Move(direction);
yield return null;
}
// Run towards player
var timer = 0.0f;
var lostSight = false;
while (timer < extraRunTime)
{
angle = RotateTowardsPosition(target.position, attackRotateSpeed);
// The angle of our forward direction and the player position is larger than 50 degrees
// That means he is out of sight
if (Mathf.Abs(angle) > 40)
lostSight = true;
// If we lost sight then we keep running for some more time (extraRunTime).
// then stop attacking
if (lostSight)
timer += Time.deltaTime;
// Just move forward at constant speed
direction = transform.TransformDirection(Vector3.forward * attackSpeed);
characterController.SimpleMove(direction);
// Keep looking if we are hitting our target
// If we are, knock them out of the way dealing damage
var pos = transform.TransformPoint(punchPosition);
if (Time.time > lastPunchTime + 0.3 (pos - target.position).magnitude < punchRadius)
{
// deal damage
target.SendMessage("ApplyDamage", damage);
// knock the player back and to the side
var slamDirection = transform.InverseTransformDirection(target.position - transform.position);
slamDirection.y = 0;
slamDirection.z = 1;
if (slamDirection.x >= 0)
slamDirection.x = 1;
else
slamDirection.x = -1;
target.SendMessage("Slam", transform.TransformDirection(slamDirection));
lastPunchTime = Time.time;
}
// We are not actually moving forward.
// This probably means we ran into a wall or something. Stop attacking the player.
if (characterController.velocity.magnitude < attackSpeed * 0.3)
break;
// yield for one frame
yield return null;
}
isAttacking = false;
// Now we can go back to playing the idle animation
animation.CrossFade("idle");
}
public void ApplyDamage()
{
animation.CrossFade("gothit");
}
public void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.TransformPoint(punchPosition), punchRadius);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackDistance);
}
}
To my knowledge, im pretty sure the mistake is in the Start method, where the yield statement is used to maintain and allow state changes.
Any inputs would be appreciated,
Thanks!