- Home /
Reduce AI processor weight
Hey there!
I've just managed to make my first function of a Finite State Machine that will control a bot ship (that can be allied or enemy). However, I've noticed the the Main Thread times when from ~13ms to ~45ms once I implemented it. Once I made that single ship into four diferent ships, the time went up to ~80ms.
However, the game is supposed to be able to have dozens or more ships in visual range. So using the "disable AI script when out of range" trick wont help that much.
So, do you guys have any tip that might help me reduce the AI weight?
Thanks in advance.
JPB18
EDIT: Someone asked me for the code, here it is :P : ShipAI.js:
#pragma strict
//lets start by creating some enumerations
enum Formation {close, standard, loose}
enum ShipType {Frigate, AttackShip, Cruiser, BattleShip}
//now le variables
var type : ShipType;
var formation : Formation = Formation.standard;
var leader : GameObject;
var defence : boolean;
var merchant : boolean;
var minDistance : float;
var followDistance : float;
var interceptRange : float;
var dockStation : float;
var dockShip : float;
var defenseStation : float;
var defenseShip : float;
var arriveTime : float;
var presenceTime : float = 180.0f; //3 minutes
//other scripts
var target : shipTarget;
var triggers : shipTriggers;
var props : shipProperties;
var move : shipMovement;
//other variables
var faceAngle : float = 1.0f;
function Start () {
triggers = gameObject.GetComponent(shipTriggers);
target = gameObject.GetComponent(shipTarget);
props = gameObject.GetComponent(shipProperties);
move = gameObject.GetComponent(shipMovement);
arriveTime = Time.time;
}
function Update () {
if(!props.playerProps.isPlayer)
{
if(hasLeader()) {
if(getFormation() == formation.close) {
follow(leader);
}
}
}
}
//this function checks if there's a leader
function hasLeader() : boolean {
return leader != null;
}
//checks if the ship is in follow distance from game object
//pre target == leader, target != null
//pre target.transform.tag == "Ship"
function isFollowDistance(target : GameObject) : boolean {
var distance : float = Vector3.Distance(target.transform.position, transform.position);
return distance <= followDistance;
}
//checks if the ship is too close to another one
//pre target != null
//pre target.transform.tag == "Ship"
function isTooClose(target : GameObject) : boolean {
var distance : float = Vector3.Distance(target.transform.position, transform.position);
return distance <= minDistance;
}
function getFormation() : Formation {
return formation;
}
//this functions gets the forward speed of a target ship
//pre target.transform.tag == "Ship"
function getSpeed(target : GameObject) : float{
return target.rigidbody.velocity.z;
}
//this function checks if our ship is slower than the target ship
//pre target.transform.tag == "Ship"
function isSlower(target : GameObject) : boolean {
return getSpeed(target) > getSpeed(gameObject);
}
//this function checks if our ship is faster than the target ship
//pre target.transform.tag == "Ship"
function isFaster(target : GameObject) : boolean {
return getSpeed(target) < getSpeed(gameObject);
}
//this function attempts to match the ship speed with its target
//pre target.transform.tag == "Ship"
function matchSpeed(target : GameObject) {//pre target.transform.tag == "Ship"//pre leader.transform.tag == "Ship"return getSpeed(target) > getSpeed(gameObject);
if(isSlower(target)) {
move.increaseSpeed();
}
else if (isFaster(target)) {
move.decreaseSpeed();
}
}
//this function checks if the ship is looking at target
//pre target.transform.tag == "Ship"
function isLookingAt(target : GameObject) {
var targetDir = target.transform.position - transform.position;
var forward = transform.forward;
var angle = Vector3.Angle(targetDir, forward);
return angle < faceAngle;
}
//this function makes the ship look at target
//pre target.transform.tag == "Ship"
function LookAt(target : GameObject) {
AlignX(target);
AlignY(target);
}
//this function check if a value is negatve
function isNeg(dir : float) : boolean {
return dir < 0;
}
//this function checks if a value is positive
function isPos(dir : float) : boolean {
return dir > 0;
}
//this function aligns the ship to the target ship on X
//pre target.transform.tag == "Ship"
function AlignX(target : GameObject) {
var v3 : Vector3 = transform.InverseTransformPoint(target.transform.position);
if(isNeg(v3.x)) {
move.turnLeft();
}
else if(isPos(v3.x)) {
move.turnRight();
}
}
//this function aligns the ship to the target ship on Y
//pre target.transform.tag == "Ship"
function AlignY(target : GameObject) {
var v3 : Vector3 = transform.InverseTransformPoint(target.transform.position);
if(isNeg(v3.y)) {
move.turnDown();
}
else if(isPos(v3.y)) {
move.turnUp();
}
}
//this function aligns the ship to the target ship on Z
//pre target.transform.tag == "Ship"
function AlignZ(target : GameObject) {
if(target.transform.rotation.z > transform.rotation.z) {
move.rotLeft();
}
else if (target.transform.rotation.z > transform.rotation.z) {
move.rotRight();
}
}
//this function makes the ship follow target
//pre target.transform.tag == "Ship"
function follow(target : GameObject) {
if(!isLookingAt(target)) {
LookAt(target);
}
if(isTooClose(target)) {
if(!move.isAtMin()) {
move.decreaseSpeed();
}
}
else if(!isFollowDistance(target)) {
if(!move.isAtMax()) {
move.increaseSpeed();
}
}
else {
move.matchSpeed(target);
}
}
If you guys need to see any other function, let me know...
It may be that your code is just reaaaalllyyy inefficient so maybe post that for others to go through?
I've done AIs before and they are a nightmare :P
At a guess, I'd say it's due to the function on line 56. But I might be wrong - I might be able to give you a more precise answer if you actually showed us the script in question.... ;)
Just edited the post with the script with the code in question. If you want to see any of the methods I don't show in there, just ask...
Answer by Roland1234 · Nov 26, 2013 at 01:11 AM
If performance is starting to become an issue then I'd recommend that you refactor your code in such a way that an entity may move every frame, but only updates its direction and speed according to your AI that does not run every frame. You could add a simple timer to control how often your AI should update, and/or go more complex and manage collections of entities that evenly distribute their AI updates depending on how many entities currently exist.
That'll probably give you the greatest performance boost, but I can see a few details that might optimize your code either way (though maybe not by much, but it also depends on the platform architecture that your running it on):
Distance comparisons between vectors are more efficient comparing square magnitudes - so:
Vector3.Distance(target, current) <= minDistance;
is always the same as (given minDistance is positive)
(target - current).sqrMagnitude <= minDistance * minDistance;
but the latter is more efficient (lines 74 & 83).
Your LookAt (141) method is calling AlignX and AlignY which both transform the same target object's position by the same inverse matrix. Compute the target's local position once and pass in the result instead. And for future reference, if you are making multiple calls to transform.InverseTransformPoint in the same frame again, it'd be better to cache transform.worldToLocalMatrix once per frame and use its *MultiplyPoint3x4* function instead.
It most likely won't make a lick of difference (and may just be nitpicking on my part), but the isNeg and isPos functions would technically be faster if you removed them and in-lined your comparison operations instead - there is a slight overhead incurred in calling any function in general, although for certain functions the compiler may be optimized to inline them anyways. Like I said, won't make much difference but something to keep in mind. Or ignore. Its all good.
Finally, there's gotta be a more efficient way of determining whether an entity is within a threshold of looking at an object or not that doesn't involve computing the actual angle, as that's a relatively expensive operation. If you can reduce the problem to 2D, then using the slope of the target's position in local terms, checking if the target is behind the entity, should be alot faster. But now I'm sleepy.
EDIT: Re: paulygons' answer - You know, I had heard about that but completely forgot about it! I'm gonna have to start a new habit now. More details here.
Thanks for the tips, will try to apply them to the script... =)
Answer by paulygons · Nov 25, 2013 at 11:14 PM
I didn't pick through every line of your code but I will say this, every time you use transform, rigidBody, Distance, collider, etc. If I understand things correctly you are essentially doing something similar to GetComponent look ups. I try very hard to avoid using non-cached component calls in that manner, and would love to know if this is as bad as I think it is. I've also heard people discussing "pointers" but i'm not exactly sure if that is the correct terminology here.
I forgot to mention that using these component calls within an Update every frame is the bad part.
Your answer
