unity3d_ shoot out

Write a simple mouse flying saucer (Hit UFO) game

Game content requirements:

  • The game has n round s, each containing 10 trial s;
  • The color, size, launch position, speed, angle and number of simultaneous occurrences of each trial may vary. They are controlled by the round's ruler;
  • The flying saucers of each trial are random, and the overall difficulty increases with the round.
  • Score in the mouse point, the scoring rules are calculated according to different colors, sizes and speeds, and the rules can be set freely.

Game Requirements:

  • Use cached factory mode to manage the production and recycling of different flying saucers, which must be a single instance of the scene! See the reference resource Singleton template class for implementation
  • Separate human-computer interaction from game models using the previous MVC structure whenever possible

Rules of the game

  • The game is divided into 3 rounds, each player has 5 lives, one life is deducted for each missing flying disc, otherwise, the corresponding score can be obtained by one flying disc per mouse point.
  • The difficulty is increased by turns, and the flying speed of the flying saucer is faster with the increase of turns.
  • Color, size of each trail's flying saucer; The launch position, speed, angle, and number of flying saucers per launch vary;

_Because an object instance is created every time a flying saucer appears, the destruction of the flying saucer requires the destruction of the instance, which greatly increases the cost of running the game, so use factory mode. The flying saucer factory manages the creation and recycling of prefabricated flying saucers separately. The flying saucer factory first creates a stack of flying saucers of different colors, then randomly removes a usable flying saucer from the factory when it is needed. When the flying saucer is not needed, it is placed in the factory, eliminating the cost of creating and destroying instances of flying saucers.

The UML diagram of the game project is as follows

Game Realization

DiskFactory

The flying saucer factory class contains the generation and recycling of flying saucers, as well as settings that adjust the speed of the flying saucer according to the turn. A list is used to generate and destroy flying saucers. First, an instance of the flying saucer is created and placed in the free queue. Then, when the flying saucer is needed, it is fetched from the free queue and the used flying saucer is placed in the user queue.

public class DiskFactory : MonoBehaviour
{
    public GameObject disk_prefab = null;                 //Prefabricated flying saucer
    private List<DiskData> used = new List<DiskData>();   //List of flying saucers in use
    private List<DiskData> free = new List<DiskData>();   //Free list of flying saucers

    public GameObject GetDisk(int round)
    {
        int choice = 0;
        int scope1 = 1, scope2 = 4, scope3 = 7;           //Random Range
        float start_y = -10f;                             //Vertical position of the flying saucer just instantiated
        string tag;
        disk_prefab = null;

        //Randomly select flying saucers to fly out based on rounds
        if (round == 1)
        {
            choice = Random.Range(0, scope1);
        }
        else if(round == 2)
        {
            choice = Random.Range(0, scope2);
        }
        else
        {
            choice = Random.Range(0, scope3);
        }
        //tag of the flying saucer to be selected
        if(choice <= scope1)
        {
            tag = "disk1";
        }
        else if(choice <= scope2 && choice > scope1)
        {
            tag = "disk2";
        }
        else
        {
            tag = "disk3";
        }
        //Find a free flying saucer with the same tag
        for(int i=0;i<free.Count;i++)
        {
            if(free[i].tag == tag)
            {
                disk_prefab = free[i].gameObject;
                free.Remove(free[i]);
                break;
            }
        }
        //Reinstantiate the flying saucer if it is not in the free list
        if(disk_prefab == null)
        {
            if (tag == "disk1")
            {
                disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk1"), new Vector3(0, start_y, 0), Quaternion.identity);
            }
            else if (tag == "disk2")
            {
                disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk2"), new Vector3(0, start_y, 0), Quaternion.identity);
            }
            else
            {
                disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk3"), new Vector3(0, start_y, 0), Quaternion.identity);
            }
            //Give the newly instantiated flying saucer additional attributes
            float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
            disk_prefab.GetComponent<Renderer>().material.color = disk_prefab.GetComponent<DiskData>().color;
            disk_prefab.GetComponent<DiskData>().direction = new Vector3(ran_x, start_y, 0);
            disk_prefab.transform.localScale = disk_prefab.GetComponent<DiskData>().scale;
        }
        //Add to Usage List
        used.Add(disk_prefab.GetComponent<DiskData>());
        return disk_prefab;
    }

    //Recycle flying saucer
    public void FreeDisk(GameObject disk)
    {
        for(int i = 0;i < used.Count; i++)
        {
            if (disk.GetInstanceID() == used[i].gameObject.GetInstanceID())
            {
                used[i].gameObject.SetActive(false);
                free.Add(used[i]);
                used.Remove(used[i]);
                break;
            }
        }
    }
}
DiskData

Diskdata, a data class used to provide attributes to a flying saucer, the score you get when you click on it, the color of the flying saucer, and the speed and direction at which the flying saucer flies.

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

public class DiskData : MonoBehaviour
{
    //Shoot this flying saucer score
    public int score = 1;
    //colour
    public Color color = Color.red;
    //speed
    public float speed = 20;
    //direction
    public Vector3 direction;
}
RoundController

RoundController, a scene controller, and a scene controller are responsible for the overall management of each component of a game project. The game is divided into three states: the beginning of the game, the ongoing game, and the end of the game. Depending on the game, invoke the interface with the UFO Communist so that the difficulty of the UFO game matches the game rounds. The scene controller surges into the timer and sends a flying saucer after a certain interval, updating its life value whenever a player misses a flying saucer.

public class RoundController : MonoBehaviour, ISceneController, IUserAction
{
    public DiskFactory diskFactory;
    public CCActionManager actionManager;
    public ScoreRecorder scoreRecorder;
    public UserGUI userGui;

    private Queue<GameObject> diskQueue = new Queue<GameObject>();
    private List<GameObject> diskMissed = new List<GameObject>();

    private int totalRound = 3;
    private int trialNumPerRound = 10;
    private int currentRound = -1;
    private int currentTrial = -1;
    private float throwSpeed = 2f;
    private int gameState = 0; //-1: Failure 0: Initial state 1: In progress 2: Win
    private float throwInterval = 0;
    private int userBlood = 10;

    void Awake()
    {
        SSDirector director = SSDirector.GetInstance();
        director.CurrentSceneController = this;
        
        diskFactory = Singleton<DiskFactory>.Instance;
        userGui = gameObject.AddComponent<UserGUI>() as UserGUI;
        actionManager = gameObject.AddComponent<CCActionManager>() as CCActionManager;

        scoreRecorder = new ScoreRecorder();
    }

    public void LoadResource()
    {
        diskQueue.Enqueue(diskFactory.GetDisk(currentRound));
    }

    public void ThrowDisk(int count)
    {
        while(diskQueue.Count <= count)
        {
            LoadResource();
        }
        for(int i = 0; i < count; i++)
        {
                    float position_x = 16;
        GameObject disk = diskQueue.Dequeue();
        diskMissed.Add(disk);
        disk.SetActive(true);
        //Set the position of the flying saucer
        float ran_y = Random.Range(-3f, 3f);
        float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
        disk.GetComponent<DiskData>().direction = new Vector3(ran_x, ran_y, 0);
        Vector3 position = new Vector3(-disk.GetComponent<DiskData>().direction.x * position_x, ran_y, 0);
        disk.transform.position = position;
        //Set the initial force and angle of the flying saucer
        float power = Random.Range(10f, 15f);
        float angle = Random.Range(15f, 28f);
        actionManager.diskFly(disk, angle, power);
        }
    }

    void levelUp()
    {
        currentRound += 1;
        throwSpeed -= 0.5f;
        currentTrial = 1;
    }

    void Update()
    {
        if(gameState == 1)
        {
            if(userBlood <= 0 || (currentRound == totalRound && currentTrial == trialNumPerRound))
            {
                GameOver();
                return;
            }
            else
            {
                if (currentTrial > trialNumPerRound)
                {
                    levelUp();
                }
                if (throwInterval > throwSpeed)
                {
                    int throwCount = generateCount(currentRound);
                    ThrowDisk(throwCount);
                    throwInterval = 0;
                    currentTrial += 1;
                }
                else
                {
                    throwInterval += Time.deltaTime;
                }
            }
        }
        for (int i = 0; i < diskMissed.Count; i++)
        {
            GameObject temp = diskMissed[i];
            //The flying saucer flew out of the camera's view and was not hit
            if (temp.transform.position.y < -8 && temp.gameObject.activeSelf == true)
            {
                diskFactory.FreeDisk(diskMissed[i]);
                diskMissed.Remove(diskMissed[i]);
                userBlood -= 1;
            }
        }
    }

    public int generateCount(int currentRound)
    {
        if(currentRound == 1)
        {
            return 1;
        }
        else if(currentRound == 2)
        {
            return Random.Range(1, 2);
        }
        else
        {
            return Random.Range(1, 3);
        }
    }


    public void StartGame()
    {
        gameState = 1;
        currentRound = 1;
        currentTrial = 1;
        userBlood = 10;
        throwSpeed = 2f;
        throwInterval = 0;
}

    public void GameOver()
    {
        if(userBlood <= 0)
        {
            gameState = -1;//fail
        }
        else
        {
            gameState = 2;//victory
        }
    }

    public void Restart()
    {
        scoreRecorder.Reset();
        StartGame();
    }

    public void Hit(Vector3 pos)
    {
        Ray ray = Camera.main.ScreenPointToRay(pos);
        RaycastHit[] hits;
        hits = Physics.RaycastAll(ray);
        bool notHit = false;
        foreach (RaycastHit hit in hits)
            //Radiation hit object
            if (hit.collider.gameObject.GetComponent<DiskData>() != null)
            {
                //Objects to be shot in the list of flying saucers not hit
                for (int j = 0; j < diskMissed.Count; j++)
                {
                    if (hit.collider.gameObject.GetInstanceID() == diskMissed[j].gameObject.GetInstanceID())
                    {
                        notHit = true;
                    }
                }
                if (!notHit)
                {
                    return;
                }
                diskMissed.Remove(hit.collider.gameObject);
                //Record score
                scoreRecorder.Record(hit.collider.gameObject);
                diskFactory.FreeDisk(hit.collider.gameObject);
                break;
            }
    }

    public int GetScore()
    {
        return scoreRecorder.GetScore();
    }

    public int GetCurrentRound()
    {
        return currentRound;
    }
    public int GetBlood()
    {
        return userBlood;
    }
    public int GetGameState()
    {
        return gameState;
    }
}
Game interface

The game interface is used to update the game and the player's data, including score, life, current round, and so on.

public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    private string score, round;
    int blood, gameState, HighestScore;

    // Start is called before the first frame update
    void Start()
    {
        action = SSDirector.GetInstance().CurrentSceneController as IUserAction;
    }

    // Update is called once per frame
    void Update()
    {
        gameState = action.GetGameState();
    }

    void OnGUI()
    {
        GUIStyle text_style;
        GUIStyle button_style;
        text_style = new GUIStyle()
        {
            fontSize = 20
        };
        button_style = new GUIStyle("button")
        {
            fontSize = 15
        };

        if (gameState == 0)
        {
            //Initial interface
            if (GUI.Button(new Rect(Screen.width / 2 - 50, 80, 100, 60), "Start Game", button_style))
            {
                action.StartGame();
            }
        }
        else if(gameState == 1)
        {
            //The game is in progress
            //User Shooting
            if (Input.GetButtonDown("Fire1"))
            {
                Vector3 mousePos = Input.mousePosition;
                action.Hit(mousePos);
            }

            score = "Score: " + action.GetScore().ToString();
            GUI.Label(new Rect(200, 5, 100, 100), score, text_style);
            round = "Round: " + action.GetCurrentRound().ToString();
            GUI.Label(new Rect(400, 5, 100, 100), round, text_style);

            blood = action.GetBlood();
            string bloodStr = "Blood: " + blood.ToString();
            GUI.Label(new Rect(600, 5, 50, 50), bloodStr, text_style);
        }
        else
        {
            //There are two situations when the game ends
            if (gameState == 2)
            {

                if (action.GetScore() > HighestScore) {
                    HighestScore = action.GetScore();
                }       
                GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 250, 100, 60), "Game Over", text_style);
                string record = "Highest Score: " + HighestScore.ToString();  
                GUI.Label(new Rect(Screen.width / 2 - 70, Screen.height / 2 - 150, 150, 60), record, text_style);
            }
            else
            {
                GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 150, 100, 70), "You Lost!", text_style);
            }

            if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 30, 100, 60), "Restart", button_style))
            {
                action.Restart();
            }
        }
    }     
}
FlyAction

Flying saucer action class, mainly responsible for achieving the flying effect of the saucer, including giving the saucer a direction and force, making the saucer do a movement with similar gravity acceleration, which will stop when the saucer flies out of the game interface.

public class FlyAction : SSAction
{
    public float gravity = -5;                                 //Downward acceleration
    private Vector3 start_vector;                              //Initial Velocity Vector
    private Vector3 gravity_vector = Vector3.zero;             //Vector of acceleration, initially 0
    private float time;                                        //Time Past
    private Vector3 current_angle = Vector3.zero;               //Euler's angle of current time

    private UFOFlyAction() { }
    public static UFOFlyAction GetSSAction(Vector3 direction, float angle, float power)
    {
        //Initialize the initial velocity vector that the object will move
        UFOFlyAction action = CreateInstance<UFOFlyAction>();
        if (direction.x == -1)
        {
            action.start_vector = Quaternion.Euler(new Vector3(0, 0, -angle)) * Vector3.left * power;
        }
        else
        {
            action.start_vector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
        }
        return action;
    }

    public override void Update()
    {
        //Calculates the downward speed of an object, v=at
        time += Time.fixedDeltaTime;
        gravity_vector.y = gravity * time;

        //Displacement simulation
        transform.position += (start_vector + gravity_vector) * Time.fixedDeltaTime;
        current_angle.z = Mathf.Atan((start_vector.y + gravity_vector.y) / start_vector.x) * Mathf.Rad2Deg;
        transform.eulerAngles = current_angle;

        //If the y-coordinate of the object is less than -10, the action is complete
        if (this.transform.position.y < -10)
        {
            this.destroy = true;
            this.callback.SSActionEvent(this);      
        }
    }

    public override void Start() { }
}

Effect Display

Start Interface

Game in progress interface
Game End Interface

Tags: Unity Unity3d 3d

Posted on Wed, 20 Oct 2021 12:57:20 -0400 by rline101