Unity3d learning diary C# script writing optimization and full-automatic enemy script implementation

Posted by jamesl on Tue, 30 Nov 2021 07:14:21 +0100

requirement analysis

Scripts need to be written to control the enemy's behavior, including patrol, shooting, pursuit and escape

Solution ideas

Considering that the behavior of the robot is affected by a decision tree, a finite state automata is written to form a decision tree, and the behavior of the robot is limited and controlled by conditional branching statements.

Solution

The solution of this paper draws lessons from the following blog posts, infringement deletion
Introduction of finite state automata and framework writing method

Understanding finite state automata

Finite state machine (FSM), also known as finite state automata (FSM), is a mathematical model that represents finite states, transitions and actions between these states. It reflects the input change from the beginning of the system to the present time, indicates the state change of the transition, and describes it with the conditions that must be met to ensure the transition; An action is a description of an activity to be performed at a given time.

Development of patrol function

The patrol function uses the navigation system of Unity3D and the navigation system agent to plan the robot patrol. See the article for the specific process
Unity navigation system
Then, in my setting, I set four empty objects in the map, and use the circular array to realize the robot patrolling in these four places until the event is triggered
The code is as follows:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class partorl : MonoBehaviour
{
    public Transform target;
    private Vector3 targetPoint;
    private NavMeshAgent agent;
    private List<Vector3> dstPoints;
    private int nextIdx=0;
    private float calcdist = 5f;

    // Start is called before the first frame update
    void Start()
    {
        //Generate coordinates of each navigation point
        dstPoints = new List<Vector3>();
        dstPoints.Add(GameObject.Find("LactionMarkingFlag_F4_Buff").transform.position);
        dstPoints.Add(GameObject.Find("LactionMarkingFlag_bule_spawn").transform.position);
        dstPoints.Add(GameObject.Find("LactionMarkingFlag_red_spawn").transform.position);
        dstPoints.Add(GameObject.Find("LactionMarkingFlag_F5_Buff").transform.position);
        agent = this.transform.GetComponent<NavMeshAgent>();

    }

    // Update is called once per frame
    void Update()
    {
        if(Vector3.Distance(this.transform.position,dstPoints[nextIdx])< calcdist)//Pass the error value and identify whether to reach the destination
        {
            if (nextIdx == dstPoints.Count - 1)
            {
                nextIdx = 0;
            }
            else
            {
                nextIdx++;
            }
        }
        agent.SetDestination(dstPoints[nextIdx]);
    }

}

The patrol function is relatively simple. After completing the patrol, let's consider rewriting the script code of automatic robot firing and blood bar
However, one thing to consider is how to cooperate with the patrol function and the following functions?
We thought that patrolling is used as an entry. The following functions are possible only when the AI robot meets my machine, so
The real function of patrol is as a trigger to detect and judge whether my robot meets an AI robot, and the trigger condition function is needed

Development of automatic shooting function

Realization of automatic shooting function: it is preliminarily expected that when you meet the enemy and enter the range, you will enter this state. The realization of this state is divided into two steps:
Step 1: the robot PTZ's muzzle switch position is aligned with the enemy's Center (the error can be adjusted by about 2) as the firing range, and fire
Step 2: do collision detection, adjust the ammunition amount of AI robot, and judge the blood deduction of player robot

Development of the function of chasing the enemy

First, the state of chasing the enemy needs to be judged. If your own blood volume is greater than 500 and your ammunition volume is greater than 30, it can be judged that you can chase, otherwise you will enter the state of escape when you encounter the enemy
In fact, this function should have a sequential relationship with the automatic shooting function. Only after chasing the enemy can you shoot. Therefore, the realization of the pursuit state can be divided into two steps
Step 1: find the enemy in the field of vision (consider implementation) and stop ordinary patrol
Step 2: enter the alert state and approach the enemy according to the new wayfinding rules

Development of escape function

This function is opposite to pursuit. Of course, the trigger conditions are also opposite. When their own blood volume is less than 50 and their ammunition volume is less than 30, they escape. The escape route planning is different from pursuit. Escape requires to be as far away from the enemy as possible, which requires dynamic planning of escape route

code implementation

1. Realization of enemy vision

Here, a collision body with isTrigger is used as the detection of the enemy's field of view (that is, an area following the lens) to detect as a trigger to judge whether the player has entered the field of view. Because the trigger knowledge has been learned, the code can be written soon

    bool isFirst = false;
    public float fileldOfView = 100f;//Field width
    public bool playerInsight = false;//Whether the player is in the field of view
    public Vector3 playerLastSight;//Where the player last appeared in the field of view
    //public Vector3 resetPos = GameObject.Find("LactionMarkingFlag_red_spawn").transform.position;// Reset the location to Hongfang's hometown

    private BoxCollider col;//Get the colliding body of visual field detection
    //Colarr [0]: collision body of airobot
    //colArr[1]:AIRobot's visual field detection trigger
    //Colarr [2]: range detection trigger of airobot
    private GameObject player;
    void Start()
    {
        Debug.Log("The script for detecting visual field is running normally");
        BoxCollider[] colArr = GetComponents<BoxCollider>();
        col = colArr[1];//The second collider is used as the collider for visual field detection
        player = GameObject.FindWithTag("PlayerRobot");
        //playerLastSight = resetPos;
    }

    private void OnTriggerStay(Collider other)
    {
        if (other.gameObject == player)
        {
            playerInsight = false;
            Vector3 dir = other.transform.position - transform.position;//Form a ray from AI robot to player robot
            float angle = Vector3.Angle(dir,transform.forward);//Calculate the angle between this line and the AI robot's orientation
            if(angle < this.fileldOfView*0.5)
            {
                Debug.Log("Into the vision of the robot");
                //In other words, if there are no obstacles, it can be regarded as found
                //The ray is used to determine whether there is an obstacle
                RaycastHit raycastHit;
                if (Physics.Raycast(transform.position + transform.up, dir.normalized, out raycastHit, col.size.z))
                {
                    if(raycastHit.collider.gameObject == player)
                    {
                        playerInsight = true;
                        playerLastSight = player.transform.position;
                        Debug.Log("No obstructions");
                    }
                }



            }
        }
    }
    private void OnTriggerExit(Collider other)
    {
        if (isFirst == false)
        {
            isFirst = true;
            return;
        }
        if (other.gameObject == player)
        {
            playerInsight = false;
            Debug.Log("The player is out of sight");
        }
    }

    void Update()
    {
        
    }

2. Drive the enemy's action after the enemy has captured the field of view

Add member variable

    /*Part I: variables needed for patrol*/
    public Transform target;
    private Vector3 targetPoint;
    private NavMeshAgent agent;
    private List<Vector3> dstPoints;
    private int nextIdx = 0;
    private float calcdist = 5f;
    /****************************/

    /*Part II: add member variables of tracking status and shooting status*/
    //Member variables that perform tracking
    public float chaseSpeed = 15f; //Tracking speed
    public float chaseWait = 5f;   //Tracking upper limit time
    private float chaseTimer = 0f; //Tracking timer
    public float sqrPlayerDist = 4f;   //Distance from player
    private bool isChasing = false;  //Is it currently in tracking status
    //Member variable to execute the attack
    public float shootRotSpeed =4f;//When there is an enemy in the field of vision, the angle of view is shifted
    public float shootFreeTime = 2f;//Attack interval
    private float shootTimer = 0f;//Attack interval calculator
    private AIRobotSight aiRobotSight;//Enemy's field of view object
    private Transform player;//Player information
    /***************************************/

    /*Part III: member variables of robot firing bullets*/
    public GameObject bullet;//Define a public member to refer to bullets
    public Transform muzzle;//Similarly, define an object that refers to the muzzle
    public Text ammorText;//Bullet text
    private float speed;//Defines a private member variable that refers to the speed of the bullet
    public int ammorCnt;
    private string robotType;
    private string UIType;
    public Slider slider;
    /********************************/

Logic of attack function

    /*Encapsulation of attack function*/
    void shooting()
    {
        Vector3 lookPos = player.position;//For the player's position, first save the position information to be oriented
        lookPos.y = transform.position.y;//Set the height to avoid misjudgment due to height difference

        Vector3 targetDir = lookPos - transform.position;//Set the true distance vector
        //Set the orientation of AI robot according to the distance vector
        transform.rotation = Quaternion.Slerp(transform.rotation,Quaternion.LookRotation(targetDir),Mathf.Min(1f,Time.deltaTime*shootRotSpeed));
        agent.isStopped = true;//Stop pathfinding and attack

        //Make a judgment on whether to face the enemy, at least not from the angle of the horse gun
        if (Vector3.Angle(transform.forward,targetDir) <2)//If the deviation is less than two degrees, it is considered that the probability will not be high
        {
            //Write the function of firing bullets here
            //1. First, avoid firing bullets all the time. It is necessary to judge whether it is in the cooling time
            if(shootTimer > shootFreeTime)
            {
                shootTimer = 0f;//Timer zero
                GameObject bulletClone;//Create a new clone of bullets, because new bullets will be fired every time

                bulletClone = Instantiate(bullet, muzzle.position,Quaternion.LookRotation(player.position-muzzle.position));

                //Keep the firing position consistent with the PTZ position

                //Vector3 fwd = transform.TransformDirection(Vector3.forward * speed);
                bulletClone.AddComponent<destory>();
                bulletClone.GetComponent<Rigidbody>().velocity = transform.TransformDirection(Vector3.forward * speed);
                ammorCnt--;
                ammorText.text = "Bullet allowance:" + ammorCnt + "/300";
                slider.value = ammorCnt;
            }
            shootTimer += Time.deltaTime;
        }
    }

Logic of pursuit function

    /*Encapsulation of pursuit function*/
    void chasing()
    {
        agent.isStopped = false;//Enable the navigation agent to pursue the enemy
        Vector3 sightDelPos = aiRobotSight.playerLastSight-transform.position;//Calculate the player's last position and my current position
        if(sightDelPos.sqrMagnitude > sqrPlayerDist)
        {
            agent.destination = aiRobotSight.playerLastSight;
        }
        agent.speed = chaseSpeed;
        if(agent.remainingDistance < agent.stoppingDistance)
        {
            chaseTimer += Time.deltaTime;
            if (chaseTimer > chaseWait)
            {
                //Return to patrol status
                isChasing = false;
                chaseTimer = 0f;
            }
        }
        else
        {
            chaseTimer = 0f;
        }
    }

Logic of escape function

    void runaway()
    {
        //The patrol point on the map is used as the escape planning point, and the challenge algorithm is used to find a patrol point farthest from the enemy to escape
        Vector3 playerPos = player.position - dstPoints[0];
        float minDst = playerPos.sqrMagnitude;
        int minIdx=0;
        for(int i = 1; i < dstPoints.Count; i++)
        {
            Vector3 temp = player.position - dstPoints[i];
            if (minDst < temp.sqrMagnitude)
            {
                minIdx = i;
                minDst = temp.sqrMagnitude;
            }
        }
        //Finally decided to go to this patrol point
        agent.speed = 20f;
        agent.SetDestination(dstPoints[minIdx]);
    }

4. Optimize the project and modify the bug of data disorder in the blood bar UI and ammunition UI

5. Modify the player's movement mode

6. Connection BUFF effect

Topics: C# Game Development