Intelligent patrol

Intelligent patrol

  • Submission requirements:
  • Game design requirements:
    • Create a map and several patrols (using animation);
    • Each patrol takes a convex polygon with 3 ~ 5 sides, and the position data is the relative address. That is, determine the next target position each time and calculate with your current position as the origin;
    • If the patrol collides with an obstacle, it will automatically select the next point as the target;
    • The patrolman will automatically chase the player if he senses the player within the set range;
    • After losing the player's target, continue patrolling;
    • Rule: players will win if they eat all gold coins, and fail if they collide with patrol soldiers.
  • Program design requirements:
    • You must use subscription and publish mode to deliver messages
      • subject: OnLostGoal
      • Publisher: GameEventManager
      • Subscriber: SceneController
    • Factory mode production patrol

Game implementation

Creation of game map

Build a 3 * 2 maze map

    public void LoadResources() {
        Instantiate(Resources.Load("Prefabs/Light"));
        Instantiate(Resources.Load("Prefabs/Entry"));
        blood = Instantiate(Resources.Load("Prefabs/Blood"), new Vector3(40, 40, 40), Quaternion.identity) as GameObject;
        role = Instantiate(Resources.Load("Prefabs/role")) as GameObject;
        float pos_z = 0, pos_x;
        int dir, sum = 0, size = 5;
        GameObject maze, temp;
        guardFactory gf = guardFactory.getInstance();
        for (int i = 0; i < size; i++) {
            dir = Random.Range(0, 2);
            pos_x = i * 2.8f;
            for (int j = 0; j <= i; j++, sum++) {
                if (sum%3 == 0)
                    maze = Instantiate(Resources.Load("Prefabs/g_maze"), new Vector3(pos_x, 0, pos_z), Quaternion.identity) as GameObject;
                else if (sum%3 == 1)
                    maze = Instantiate(Resources.Load("Prefabs/y_maze"), new Vector3(pos_x, 0, pos_z), Quaternion.identity) as GameObject;
                else maze = Instantiate(Resources.Load("Prefabs/b_maze"), new Vector3(pos_x, 0, pos_z), Quaternion.identity) as GameObject;
                temp = gf.getNewGuard(pos_x - 0.9f, 0, pos_z - 0.9f);
                temp.transform.parent = maze.transform;
                if (j != i)
                    if (dir == 0) pos_z += 2.8f;
                    else pos_z -= 2.8f;
            }
        }
    }
Player action realization

The player's actions include forward, backward and left and right turns through the keyboard ⬆⬇➡⬅ To achieve. Monitor keyboard input and call relevant functions to complete the execution of relevant actions according to the input.

void Update()
{
    float transitionX = Input.GetAxis("Horizontal");
    float transitionZ = Input.GetAxis("Vertical");
    //Mobile player
    action.MovePlayer(transitionX, transitionZ);
    timeCounter = action.GetTime();
}


// SceneController.cs
public void MovePlayer(float x, float z)
{
    if (!gameOver)
    {
        //Move and rotate
        player.transform.Translate(0, 0, z * playerSpeed * Time.deltaTime);
        player.transform.Rotate(0, x * 135f * Time.deltaTime, 0);
        //Prevent movement caused by collision
        if (player.transform.localEulerAngles.x != 0 || player.transform.localEulerAngles.z != 0)
        {
            player.transform.localEulerAngles = new Vector3(0, player.transform.localEulerAngles.y, 0);
        }
        if (player.transform.position.y != 0.5f)
        {
            player.transform.position = new Vector3(player.transform.position.x, 0.5f, player.transform.position.z);
        }
    }
}
patrol

The patrolman has two movement modes: Patrol mode and tracking mode. When the player's area is not under the jurisdiction of the patrolman, continue patrolling, otherwise switch to tracking mode. When patrolling, each patrolman keeps repeating the movement according to a simple line, and changes the direction of the patrolman every time he reaches the specified vertex. The factory mode is used for the creation of patrol soldiers. The factory is responsible for generating a certain number of patrol soldiers at predetermined positions, and assigning a certain jurisdiction to each patrol soldier.

public class PatrolFactory : MonoBehaviour
{
    private GameObject patrolPrefab = null;
    private Vector3[] position = new Vector3[6]; //Record the position of the patrol
    private List<GameObject> patrols = new List<GameObject>();

    public List<GameObject> GetPatrols()
    {
        int[] pos_x = { -10, 3, 18, -11, 3, 15 };
        int[] pos_z = { -18, -15, -15, 12, 10, 13 };

        for(int i = 0; i < 6; i++)
        {
            position[i] = new Vector3(pos_x[i], 0, pos_z[i]);
            patrolPrefab = Object.Instantiate(Resources.Load<GameObject>("Prefabs/patrol2"), position[i], Quaternion.identity);
            patrolPrefab.name = "patrol" + i;
            patrolPrefab.AddComponent<PatrolData>();

            patrolPrefab.GetComponent<PatrolData>().manageFloor = i + 1;
            patrolPrefab.GetComponent<PatrolData>().initPosition = position[i];
            patrols.Add(patrolPrefab);
        }
        return patrols;
    }

    public void Reset()
    {
        for(int i = 0; i < patrols.Count; i++)
        {
            patrols[i].transform.position = position[i];
            patrols[i].GetComponent<Animator>().SetBool("shoot", false);
        }
    }

}

Patrol action:

public class PatrolMoveAction : SSAction
{
    private float posX, posZ;
    private float rectLength;                           // Side length
    private enum Dirction { EAST, NORTH, WEST, SOUTH }; // Four directions of patrol
    private float speed = 2f;                           // Patrol speed
    private bool reach = true;                          // Whether to reach the destination
    private Dirction dirction = Dirction.EAST;          // Direction of movement
    private PatrolData data;                            // Patrol data

    public static PatrolMoveAction GetSSAction(Vector3 location)
    {
        PatrolMoveAction action = CreateInstance<PatrolMoveAction>();
        action.posX = location.x;
        action.posZ = location.z;

        action.rectLength = Random.Range(5, 8);
        return action;
    }

    public override void Start()
    {
        data = this.gameObject.GetComponent<PatrolData>();
    }

    public override void Update()
    {
        //Prevent rotation after collision
        if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0)
        {
            transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);
        }
        if (transform.position.y != 0.5f)
        {
            transform.position = new Vector3(transform.position.x, 0.5f, transform.position.z);
        }
        // move
        Move();
         
        // If the room is the same, destroy the current action and call back
        if (data.manageFloor == data.plyerFloor)
        {
            this.destory = true;
            this.callback.SSActionEvent(this, SSActionEventType.Compeleted, 0 ,"follow player", this.gameObject);
        }
    }

    public void Move()
    {
        if (reach)
        {
            // If you have arrived, change direction
            switch (dirction)
            {
                case Dirction.EAST:
                    posX -= rectLength;
                    break;
                case Dirction.NORTH:
                    posZ += rectLength;
                    break;
                case Dirction.WEST:
                    posX += rectLength;
                    break;
                case Dirction.SOUTH:
                    posZ -= rectLength;
                    break;
            }
            reach = false;
        }

        //Face the destination
        this.transform.LookAt(new Vector3(posX, 0.5f, posZ));

        //Calculate distance
        float distance = Vector3.Distance(transform.position, new Vector3(posX, 0.5f, posZ));
        if (distance > 1)
        {
            transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(posX, 0.5f, posZ), speed * Time.deltaTime);
        }
        else
        {
            dirction = dirction + 1;
            if (dirction > Dirction.SOUTH)
            {
                dirction = Dirction.EAST;
            }
            reach = true;
        }
    }
}
public class PatrolFollowAction : SSAction
{     
    private GameObject player;        // When you create an action, you pass in a player object to track the player
    private float speed = 3f;         // Track player speed
    private PatrolData data;          // Patrol data

    public static PatrolFollowAction GetSSAction(GameObject player)
    {
        PatrolFollowAction action = CreateInstance<PatrolFollowAction>();
        action.player = player;

        return action;
    }

    public override void Start()
    {
        data = this.gameObject.GetComponent<PatrolData>();
    }

    public override void Update()
    {
        if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0)
        {
            transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);
        }
        if (transform.position.y != 0.5f)
        {
            transform.position = new Vector3(transform.position.x, 0.5f, transform.position.z);
        }

        transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
        //Face the player when tracking
        this.transform.LookAt(player.transform.position);

        //Lost target, stop tracking
        //If the Scout does not follow the target, or the player who needs to follow is not in the Scout's area
        if (data.manageFloor != data.plyerFloor)
        {
            this.destory = true;
            this.callback.SSActionEvent(this, SSActionEventType.Compeleted, 1, "stop follow", this.gameObject);
        }
    }
}
Patrol action manager

The action manager is mainly responsible for letting the patrolmen start patrolling at the beginning of the game, and handling different situations through the callback function: switching between patrol action and tracking action according to different callback parameters.

public class PatrolActionManager : SSActionManager, ISSActionCallback
{
    //Patrol action
    private PatrolMoveAction move;
    private SceneController sceneController;

    protected new void Start()
    {
        sceneController = SSDirector.GetInstance().CurrentSceneController as SceneController;
    }

    public void PatrolMove(GameObject patrol)
    {
        move = PatrolMoveAction.GetSSAction(patrol.transform.position);
        this.RunAction(patrol, move, this);
    }

    #region ISSActionCallback implementation
    public void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Compeleted,
        int intParam = 0,
        string strParam = null,
        GameObject objectParam = null)
    {
        //Callback function, which is called after action execution.
        if (intParam == 0)
        {
            //Start following players
            PatrolFollowAction follow = PatrolFollowAction.GetSSAction(sceneController.player);
            this.RunAction(objectParam, follow, this);
        }
        else
        {
            //Lost target, continue patrolling
            PatrolMoveAction move = PatrolMoveAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().initPosition);
            this.RunAction(objectParam, move, this);
            //Player escape
            Singleton<GameEventManager>.Instance.PlayerEscape();
        }
    }
    #endregion
}
Collision and update

In order to detect the player's position so that the patrol can start the tracking mode, the player's position should be marked through the collision between the player and the ground of different maze rooms, so as to continuously update the player's position,

public class AreaCollide : MonoBehaviour
{
    public int sign = 0;
    SceneController sceneController;
    private void Start()
    {
        sceneController = SSDirector.GetInstance().CurrentSceneController as SceneController;
    }
    void OnTriggerEnter(Collider collider)
    {
        //Mark players to enter their own area
        if (collider.gameObject.name == "player")
        {
            Debug.Log("player enter floor " + sign);
            sceneController.floorNumber = sign;
        }
    }
}
Players eat gold coins

After the player eats the gold coin, set the gold coin invisible and release the message of gold coin reduction.

public class CoinCollide : MonoBehaviour
{
    void OnTriggerEnter(Collider collider)
    {
        //Triggered when the player eats gold coins
        if (collider.gameObject.name == "player")
        {
            this.gameObject.SetActive(false);
            Singleton<GameEventManager>.Instance.RecudeCoinNum();
        }
    }
}
Player arrested

When a player collides with a patrol, it is marked as that the player is arrested and the message is released.

public class PatrolCollide : MonoBehaviour
{
    void OnCollisionStay(Collision other)
    {
        //When scouts collide with players
        if (other.gameObject.name == "player")
        {
            other.gameObject.GetComponent<Animator>().SetBool("death", true);
            this.GetComponent<Animator>().SetBool("shoot", true);
            Singleton<GameEventManager>.Instance.PlayerArrested();
        }
    }
}

Subscription and publication mode

Publisher:
Publishers will post three types of messages: coins are eaten by players, the game is over, and scores

public class GameEventManager : MonoBehaviour
{
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;

    public delegate void GameOverEvent();
    public static event GameOverEvent GameOver;

    public delegate void CoinEvent();
    public static event CoinEvent CoinNumberChange;

    //Player escape
    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    //Player arrested
    public void PlayerArrested()
    {
        if (GameOver != null)
        {
            GameOver();
        }
    }

    public void RecudeCoinNum()
    {
        if(CoinNumberChange != null)
        {
            CoinNumberChange();
        }
    }

    public void TimeOut()
    {
        if (GameOver != null)
        {
            GameOver();
        }
    }
}

Subscriber: the subscriber is the scene controller

void OnEnable()
{
    //Registration event
    GameEventManager.ScoreChange += AddScore;
    GameEventManager.GameOver += GameOver;
    GameEventManager.CoinNumberChange += ReduceCoinNumber;
}
void OnDisable()
{
    //Unregister event
    GameEventManager.ScoreChange -= AddScore;
    GameEventManager.GameOver -= GameOver;
    GameEventManager.CoinNumberChange -= ReduceCoinNumber;
}

void AddScore()
{
    scoreRecorder.AddScore();
}

void GameOver()
{
    gameOver = true;
    actionManager.DestroyAll();
}

void ReduceCoinNumber()
{
    coinNumberGet += 1;
}

Tags: Unity3d

Posted on Sun, 05 Dec 2021 03:48:45 -0500 by QbertsBrother