[C#] Attempting to "Dumb Down" AI, using Invoke() function to delay, only works on first call

I’m currently developing a 3D fighter with my friends and we’re having the AI act against the player depending on their moves, such as attacking when he isn’t blocking, blocking when he’s attacking, etc. My first step was to make the AI a little slower, because right now, as soon as the AI gets close he just lets loose on his sword like there’s no tomorrow, and he can never check in time if the player is blocking, making him both an effective AI and a fairly stupid one. So, I read up on the “Invoke()” command and I thought this would work fine with my game, so I put it in, and everything still functions the way it would. I put a two second delay, for testing purposes, and the AI follows these rules when first, but after the first go, he does the exact same thing as before. Here’s my code

using UnityEngine;
using System.Collections;

public class AIPlayerController : MonoBehaviour {
   
    //Created on September 07, 2015 by
    //Purpose: Enables the AI to move, rotate, and make decisions
   
    //Edited on September 21, 2015 by
    //Purpose: Overhauled code to simplify it, cleaned up comments, added comments
   
    //Edited on September 23, 2015 by
    //Purpose: Allowing AI to attack, attempted to delay decision making, setup foundation for further additions
   
    //Edited on September 24, 2015 by
    //Purpose: Allowing AI to block, added comments, attempted to delay decision making, adjusted comments to edited sections
   
    //Bugs
    // - 1. AI only has "Delay" at the beginning of the script
    // -- Suspects it has something to do with the Invoke() command
   
    //NOTE "Player" refers to the actual Player and NOT the AI, AI is referred to as either Bot or AI.
   
   
   
   
    //BLOCK 0: Defines any variables/classes used in the script
   
    //Classes Used
    public CharacterController CC;        //Referencings the CharacterController component for movement
    public CharacterAnimator PlayerCA;    //References the CharacterAnimator script to check what animation's Player is using
    public Transform Player;            //References the Transform component of the Player for rotations
   
    //Reguarding Movements
    public float JumpHeight = 10f;        //(Currently Not In Use) Allows player to jump with a certain strength
    public float Speed = 1f;            //Multiplied with the CC.Move() to make movement quicker
    public float Gravity = 1f;            //Simulates gravity so falling occurs
    public Vector3 MoveDirection;        //Direction used in Move() which is decided by the AI
   
    //Reguarding Rotations
    Vector3 Target;                        //Finds the "Target" to use in the LookRotation function
    Quaternion NewRotation;                //New rotation to rotate to
   
    //Reguarding Decision Making
    public bool PromptAttack;            //Bool used to prompt the AICharacterAnimator to attack
    public bool PromptBlock;            //Bool used to prompt the AICharacterAnimator to block
    int MoveX = 0;                        //(Currently Not In Use) Tells the AI when to move side to side
    int MoveY = 0;                        //(Currently Not In Use) Tells the AI when to move up or down, aka jump
    int MoveZ = 0;                        //Tells the AI when to move forwards or backwards
    float PlayerDistance;                //Tells the AI how far away the Player is
   
   
   
   
    void Update(){
       
        //BLOCK 1: This block of code manages the object rotation

        //Finds position between Player and self
        Target = Player.position - transform.position;
       
        //Finds Rotation to rotate to using LookRotation, modifies NewRotation to only rotate along the Y-axis
        NewRotation = Quaternion.LookRotation (Target);
        NewRotation.eulerAngles = new Vector3(0,NewRotation.eulerAngles.y,0);
       
        //Sets the rotation
        transform.rotation = NewRotation;
       
        //Invoke meant to delay the AI in decision making
        //Reference the DecideStuff() function for explanation
        Invoke ("AIMain",2f * Time.deltaTime);
       
                       
        //Compiles all data gathered into a Vector3
        MoveDirection = new Vector3(MoveX,MoveY,MoveZ);
        //Makes MoveDirection relative to world cordinates rather than local
        MoveDirection = transform.TransformDirection(MoveDirection);
        //Multiplies the X, Y, and Z of MoveDirection by Speed to give the player more speed
        MoveDirection *= Speed;
       
       
        //Applies Gravity to the MoveDirection variable
        MoveDirection.y -= Gravity;

    } //Closes Update
   
   
    void FixedUpdate(){
       
        //BLOCK 3: Puts the compliled data of BLOCK 2 to use and moves player
       
        //Moves Player
        CC.Move(MoveDirection * Time.deltaTime);


    } //Closes FixedUpdate



   
   
   
   
    //Function meant to act as the centeralized function of all the decision making functions
    void AIMain(){
   
        //BLOCK 2: This block of code manages the decision making of the AI
       
        //Resets Prompt variables to "clear"
        PromptAttack = false;
        PromptBlock = false;
       
        //Invokes AIMove to delay decision making
        Invoke ("AIMove",2f * Time.deltaTime);
   
    } //Closes AIMain function
   
   
   
   
    //Function tests whether or not the player is attacking/blocking and acts accordingly
    void AICombat(){
       
        //If Player is blocking, do not attack
        if(PlayerCA.Blocking == true){
           
            //Sets PromptAttack to true to tell AICharacterAnimator not to attack
            PromptAttack = false;
           
        }
       
        //Checks to see if player is attacking, if true, stops AI and blocks
        else if(PlayerCA.Attacking == true){
           
            //Doesn't allow AI to move
            MoveZ = 0;
           
        }
       
        //If Player isn't blocking, attack
        else{
           
            //Sets PromptAttack to true to tell AICharacterAnimator to attack
            PromptAttack = true;
           
        }

    } //Closes AICombat function
   
   
   
   
    //Function adjusts the variables used in deciding what to do based on distance
    void AIMove(){
       
       
        //Calculates distance, stores in a float
        PlayerDistance = Vector3.Distance(transform.position,Player.position);
       
        //Resets MoveZ so AI doesn't continually walk if no conditions are met
        MoveZ = 0;
       
        //If player is too far away to attack, move forward
        if(PlayerDistance > 3){
           
            MoveZ = 1;
           
        }
       
       
        //If player is too close, move away
        else if(PlayerDistance < 2){
           
            MoveZ = -1;
           
        }
       
        //If either conditions aren't met, Invoke the AICombat Function
        else{
           
            //Check the function for comments
            Invoke("AICombat",2f * Time.deltaTime);
           
        }
   
    } //Closes AIMove function
   
   
   
} //Closes Class

Sorry if the excessive comments get in the way. Any help would be greatly appreciated! This game is only going to be played by a single player, so the AI is fairly important. Lol

You are calling Invoke every frame in update. That means AIMain will get called every frame, making things go crazy.

You could call invoke from Start instead. Or you could investigate coroutines.

FSMs are also worth looking into for this type of work.

Unrelated, but those “//Edited on September 21, 2015 by” comments make me cringe. Seriously look into using version control instead, such as git or mercurial. All changes will be automatically tracked, and rolling back changes or branching becomes a possibility.

1 Like

Yup. Having more lines of comments then there are of code is a bad sign. Comments should be reserved for things not said by the code. This script is a very good example of abusing comments.

I’m only using the Edited By in the top to just keep track of whatever is going on, I’m working on this project with my friends for a competition, it’s my first real Unity game, and I’m just starting to use comments. Could you lead me in the right direction, as to how to use them?

Thanks for your insight.

On comments in general:

Assume the reader is a coder too. They can understand what the code does. Comments like ‘adds on to the life’ really are unneeded.

Comments are great for saying why code choices were made. But not what the code does. Keeping comments in sync with code is hard work, you don’t want to have to do work that doesn’t give you any benefit.

Make your code as self documenting as possible. With auto complete there is no real issue with making method and variable names long and descriptive.

On version control:

Use it. You’ll love it and never go back

I agree on the Finite State Machine recommendation.

Also instead of using an invoke I would add some logic to the AI to allow you to alter it’s ability and add some variation.

So firstly something like “Reaction time” - a value to dictate how fast the AI can act and react. This will allow you to easily up the difficulty.

You could also add some fuzzy logic by adding some additional variables. Like Desperation, Aggressiveness and defensiveness for example .Then check these values against other factors to decide what the AI will do. So for instance if they have high defensiveness they are more likely to block, but as their health goes down they may become more desperate and start attacking more. By changing the values for each AI you can add a lot of variety to the game play.

1 Like

Yeah I was going to add some kind of chance for the AI to mess up in the script, like not block when it’s supposed to. But the “Personality” (I guess) variables would be a pretty good idea too. You said that instead of using an Invoke for this, how would I go about implementing this? Could I not just add it to my function’s that are being used in Invoke? Thanks for your input.

Ah, yeah, sorry. Right now, in my group, the other three don’t know much programming, especially for Unity. I decided to screw around with Unity a little over the Summer, so I’m much more advanced than they are, I wouldn’t want them to get confused. I also looked up something that MIT posted, talking about commenting, and that every 1-4 lines would be a good habit to keep, so I just took that to heart and did that

There are as many different ways to comment as there are coders. I personally don’t comment much (read at all). But I work mostly on my own.

Well if you have a Finite State Machine that decides what the AI does you could for instance have a state transition function - this is just off the top of my head. Or a decision function which contain timers that hold the actual state change until reaction time is reached.

you could do these timers in a coroutine or in your update function depending on your preference.