3D游戏编程与设计——打飞碟游戏

本次作业参考了学长的博客:博客地址

编写一个简单的鼠标打飞碟(Hit UFO)游戏

  • 游戏内容要求:
    1. 游戏有 n 个 round,每个 round 都包括10 次 trial;
    2. 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
    3. 每个 trial 的飞碟有随机性,总体难度随 round 上升;
    4. 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
  • 游戏的要求:
    • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
    • 近可能使用前面 MVC 结构实现人机交互与游戏模型分离

游戏的UML图:

项目代码结构:

       

飞碟预制体的创建:

        比较简单,一个带厚度的圆盘套上一个球体或胶囊体就可以了。

        由于需要不同的颜色,所以还要再加上颜色的material

比较关键的部分代码:

UFOFactory:

        包含 GetDisk 函数产生飞碟和飞碟回收方法 Free

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

public class DiskFactory : MonoBehaviour
{
    public GameObject diskPrefab;
    public List<DiskData> used = new List<DiskData>();
    public List<DiskData> free = new List<DiskData>();

    private Director director = Director.getInstance();

    private void Awake()
    {
        diskPrefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Disk"), Vector3.zero, Quaternion.identity);
        diskPrefab.SetActive(false);
    }

    public GameObject getDisk(int round,bool isRigidbody)
    {
        GameObject disk = null;
        if (free.Count > 0)
        {
            disk = free[0].gameObject;
            free.Remove(free[0]);
        }
        else
        {
            disk = GameObject.Instantiate<GameObject>(diskPrefab, Vector3.zero, Quaternion.identity);
            disk.AddComponent<DiskData>();
        }

        if (isRigidbody && disk.GetComponent<Rigidbody>() == null)        /* 添加刚体性质 */
        {
            disk.AddComponent<Rigidbody>();
        }

        int start;
        switch (round)
        {
            case 0: start = 0; break;
            case 1: start = 100; break;
            default: start = 200; break;
        }
        int selectColor = Random.Range(start, round * 499);
        round = selectColor / 250;
        DiskData diskData = disk.GetComponent<DiskData>();
        Renderer renderer = disk.GetComponent<Renderer>();
        Renderer childRenderer = disk.transform.GetChild(0).GetComponent<Renderer>();
        float ranX = Random.Range(-1, 1) < 0 ? -1.2f : 1.2f;
        Vector3 direction = new Vector3(ranX, 1, 0);
        switch (round)
        {
            case 0:
                diskData.setDiskData(new Vector3(1.35f, 1.35f, 1.35f), Color.white, 4.0f, direction);
                renderer.material.color = Color.white;
                childRenderer.material.color = Color.white;
                break;
            case 1:
                diskData.setDiskData(new Vector3(1f, 1f, 1f), Color.gray, 6.0f, direction);
                renderer.material.color = Color.yellow;
                childRenderer.material.color = Color.yellow;
                break;
            case 2:
                diskData.setDiskData(new Vector3(0.7f, 0.7f, 0.7f), Color.black, 8.0f, direction);
                renderer.material.color = Color.blue;
                childRenderer.material.color = Color.blue;
                break;
        }
        used.Add(diskData);
        diskData.name = diskData.GetInstanceID().ToString();
        disk.transform.localScale = diskData.getSize();

        return disk;
    }

    public void freeDisk(GameObject disk)
    {
        DiskData temp = null;
        foreach (DiskData i in used)
        {
            if (disk.GetInstanceID() == i.gameObject.GetInstanceID())
            {
                temp = i;
            }
        }
        if (temp != null)
        {
            temp.gameObject.SetActive(false);
            free.Add(temp);
            used.Remove(temp);
        }
    }
}

DiskData(飞碟数据):

包括初始化颜色、位置、方向、速度等

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

public class DiskData : MonoBehaviour
{
    private Vector3 size;
    private Color color;
    private float speed;
    private Vector3 direction;

    public DiskData() { }

    public Vector3 getSize()
    {
        return size;
    }

    public float getSpeed()
    {
        return speed;
    }

    public Vector3 getDirection()
    {
        return direction;
    }

    public Color getColor()
    {
        return color;
    }

    public void setDiskData(Vector3 size, Color color, float speed, Vector3 direction)
    {
        this.size = size;
        this.color = color;
        this.speed = speed;
        this.direction = direction;
    }
}

ScoreRecorder(分数记录):
记录游戏分数

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

namespace MyGame
{
    public class ScoreRecorder : MonoBehaviour
    {
        public int score;
        private Dictionary<Color, int> scoreTable = new Dictionary<Color, int>();

        void Start()                                        /* 初始化各种颜色的分数值 */
        {
            score = 0;
            scoreTable.Add(Color.white, 1);
            scoreTable.Add(Color.gray, 2);
            scoreTable.Add(Color.black, 4);
        }

        public void reset()
        {
            score = 0;
        }

        public void record(GameObject disk)                 /* 击中时触发该函数,加分 */
        {
            score += scoreTable[disk.GetComponent<DiskData>().getColor()];
        }
    }
}

控制飞碟运动的代码(CC FlyAction):

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

// 继承自SSAction类
public class CCFlyAction : SSAction
{
    float acceleration; // 加速度
    float horizontalSpeed; // 水平速度
    Vector3 direction; // 飞行方向
    float time; // 经过的时间
    bool flag = false; // 记录游戏是否经过暂停状态
    Vector3 temp; // 记录游戏暂停时刚体游戏对象的速度矢量
    Rigidbody rigidbody; // 物理运动,添加刚体

    // 获取CCFlyAction实例的静态方法
    public static CCFlyAction getCCFlyAction()
    {
        CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();
        return action;
    }

    // 在动作开始时调用的方法
    public override void Start()
    {
        enable = true; // 启用动作
        acceleration = 9.8f; // 设置加速度
        time = 0; // 初始化时间
        horizontalSpeed = gameObject.GetComponent<DiskData>().getSpeed(); // 获取飞碟的水平速度
        direction = gameObject.GetComponent<DiskData>().getDirection(); // 获取飞碟的飞行方向
        rigidbody = gameObject.GetComponent<Rigidbody>(); // 获取飞碟的刚体组件

        // 如果飞碟有刚体组件,则设置刚体的速度属性
        if (rigidbody)
        {
            rigidbody.velocity = horizontalSpeed * direction;
            temp = rigidbody.velocity; /* temp的初始化 */
        }
    }

    // 在每一帧更新时调用的方法
    public override void Update()
    {
        if (gameObject.activeSelf)
        {
            time += Time.deltaTime;
            transform.Translate(Vector3.down * acceleration * time * Time.deltaTime); // 下落运动
            transform.Translate(direction * horizontalSpeed * Time.deltaTime); // 水平运动

            // 如果飞碟位置低于一定高度,则销毁飞碟并触发动作事件
            if (this.transform.position.y < -4)
            {
                this.destroy = true;
                this.enable = false;
                this.callback.SSActionEvent(this);
            }
        }
    }

    // 在固定时间间隔更新时调用的方法
    public override void FixedUpdate()
    {
        if (gameObject.activeSelf)
        {
            // 如果飞碟位置低于一定高度,则销毁飞碟并触发动作事件
            if (this.transform.position.y < -4)
            {
                this.destroy = true;
                this.enable = false;
                this.callback.SSActionEvent(this);
            }
        }
    }

    // 停止刚体动作的方法
    public override void rigidbodyStopAction()
    {
        if (rigidbody)
        {
            if (!flag)
            {
                temp = rigidbody.velocity;
                flag = true;
            }
            rigidbody.velocity = Vector3.zero;
            rigidbody.useGravity = false;
        }
    }

    // 启动刚体动作的方法
    public override void rigidbodyStartAction()
    {
        if (rigidbody)
        {
            rigidbody.useGravity = true;
            if (flag)
            {
                rigidbody.velocity = temp;
                flag = false;
            }
        }
    }
}

FirstSceneController:包含游戏运行逻辑的关键代码

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

namespace MyGame
{
    // 实现ISceneControl和IUserAction接口
    public class FirstSceneControl : MonoBehaviour, ISceneControl, IUserAction
    {
        public ActionManagerAdapter actionManager { set; get; } // 动作管理适配器
        public ScoreRecorder scoreRecorder { set; get; } // 成绩记录器
        public Queue<GameObject> diskQueue = new Queue<GameObject>(); // 飞碟对象队列
        private int diskNumber = 0;
        private int currentRound = -1;
        private float time = 0;
        private GameState gameState = GameState.START; // 游戏状态
        UserGUI userGUI; // 用户界面
        private bool isPhysical = false; /* 决定是否进行物理运动 */

        void Awake()
        {
            Director director = Director.getInstance();
            director.current = this;
            diskNumber = 10;

            // 添加成绩记录器和飞碟工厂组件
            this.gameObject.AddComponent<ScoreRecorder>();
            this.gameObject.AddComponent<DiskFactory>();
            scoreRecorder = Singleton<ScoreRecorder>.Instance;
            userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;

            // 加载游戏资源
            director.current.loadResources();
        }

        public void loadResources()
        {
            // 游戏资源加载逻辑
        }

        private void Update()
        {
            if (actionManager == null)
            {
                return;
            }

            // 判断游戏状态
            if (actionManager.getDiskNumber() == 0 && gameState == GameState.RUNNING)
            {
                gameState = GameState.ROUND_FINISH;
                if (currentRound == 2) // 如果round = 2,表示已经完成了三局游戏,游戏结束
                {
                    gameState = GameState.FUNISH;
                    return;
                }
            }

            // 如果飞碟数为空,并且是round开始状态,则准备开始下一round游戏
            if (actionManager.getDiskNumber() == 0 && gameState == GameState.ROUND_START)
            {
                currentRound++;
                nextRound();
                actionManager.setDiskNumber(10);
                gameState = GameState.RUNNING;
            }

            // 控制抛飞碟的时间间隔
            if (time > 1 && gameState != GameState.PAUSE)
            {
                throwDisk();
                time = 0;
            }
            else
            {
                time += Time.deltaTime;
            }
        }

        private void nextRound()
        {
            DiskFactory diskFactory = Singleton<DiskFactory>.Instance;
            for (int i = 0; i < diskNumber; i++)
            {
                // 根据当前round数和是否使用物理引擎来取出飞碟,并加入本局抛的飞碟队列
                diskQueue.Enqueue(diskFactory.getDisk(currentRound, isPhysical));
            }
            actionManager.startThrow(diskQueue); // 开始抛这个队列的飞碟
        }

        void throwDisk()
        {
            if (diskQueue.Count != 0)
            {
                GameObject disk = diskQueue.Dequeue(); // 从队列取出一个飞碟
                Vector3 pos = new Vector3(-disk.GetComponent<DiskData>().getDirection().x * 10, Random.Range(0f, 4f), 0);
                disk.transform.position = pos;
                disk.SetActive(true);
            }
        }

        public int getScore()
        {
            return scoreRecorder.score;
        }

        public GameState getGameState()
        {
            return gameState;
        }

        public void setGameState(GameState gameState)
        {
            this.gameState = gameState;
        }

        public bool getActionMode()
        {
            return isPhysical;
        }

        public void setActionMode(bool mode)
        {
            this.isPhysical = mode;
            if (isPhysical)
            {
                this.gameObject.AddComponent<ModifiedActionManager>();
            }
            else
            {
                this.gameObject.AddComponent<CCActionManager>();
            }
        }

        public void hit(Vector3 pos)
        {
            RaycastHit[] hits = Physics.RaycastAll(Camera.main.ScreenPointToRay(pos));
            for (int i = 0; i < hits.Length; i++)
            {
                RaycastHit hit = hits[i];
                if (hit.collider.gameObject.GetComponent<DiskData>() != null)
                {
                    scoreRecorder.record(hit.collider.gameObject);
                    hit.collider.gameObject.transform.position = new Vector3(0, -5, 0);
                }
            }
        }
    }
}

游戏视频:

打飞碟

代码地址:代码地址

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值