3D游戏编程作业5

这是一个3D游戏编程作业,涉及创建一个名为'Hit UFO'的游戏,玩家通过鼠标点击打飞碟得分。游戏采用带缓存的工厂模式管理飞碟,使用MVC架构实现人机交互与游戏模型分离。随着游戏进程,飞碟数量增加,速度变快,难度提升。游戏包括飞碟工厂、场景控制器、场景与界面、分数记录器等设计,代码可在提供的链接中获取。
摘要由CSDN通过智能技术生成

作业目标

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

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

游戏规则

单击运行开始游戏:

  • 每次从第一回合开始,玩家按空格键“space”发射飞碟;
  • 玩家通过鼠标点击打飞碟,击中飞碟加十分;
  • 若飞碟掉落到地面还未被玩家击中,则扣除10分;
  • 当玩家分数达到回合数的20倍时,进入下一回合,分数累计到下一回合;
  • 随着回合数增加,飞碟数量增加,飞碟飞行速度变快,体积减小;
  • 当玩家分数低于0分时,游戏结束。

游戏设计

1、飞碟工厂

根据要求,我们要设计一个飞碟工厂来管理飞碟的产生和回收,这里要注意的是,我们回收的飞碟是可以再利用的,也就是说,当飞碟回收后,我们不应该把它销毁掉,而是应该改变它的状态,让它处于待使用的状态,所以我们建立了两个链:usingDisks和uselessDisks来分别存储这两种飞碟。

带缓存工厂模式的伪代码:

prepareDisks(diskCount)
BEGIN
	IF (free list has disk) THEN
	a_disk = remove one from list
	ELSE
	a_disk = clone from Prefabs
	ENDIF
	Set DiskData of a_disk with the diskCount
	Add a_disk to used list
	Return a_disk
END
destroyDisk(disk)
BEGIN
	Find disk in used list
	IF (not found) THEN THROW exception
	Move disk from used to free list
END

具体实现代码(位于DiskFactoryMono.cs中):

    public class DiskFactory : System.Object {
        public GameObject diskPrefab;
        private static DiskFactory diskFactory;
        List<GameObject> usingDisks;
        List<GameObject> uselessDisks;

        public static DiskFactory getFactory () {
            if (diskFactory == null) {
                diskFactory = new DiskFactory ();
                diskFactory.uselessDisks = new List<GameObject> ();
                diskFactory.usingDisks = new List<GameObject> ();
            }

            return diskFactory;
        }

        public List<GameObject> prepareDisks (int diskCount) {
            for (int i = 0; i < diskCount; i++) {
                if (uselessDisks.Count == 0) {
                    GameObject disk = GameObject.Instantiate<GameObject> (diskPrefab);
                    usingDisks.Add (disk);
                } else {
                    GameObject disk = uselessDisks[0];
                    uselessDisks.RemoveAt (0);
                    usingDisks.Add (disk);
                }
            }

            return this.usingDisks;
        }

        public void recycleDisk (GameObject disk) {
            int index = usingDisks.FindIndex (x => x == disk);
            uselessDisks.Add (disk);
            usingDisks.RemoveAt (index);
        }
    }

2、场景控制器( C )

根据自顶向下的思路考虑,要想设计一个MVC架构的游戏,首先我们需要考虑顶层的控制类场景控制器SceneController,这里我们为其设计调用了四种接口并在控制器中完成了对接口的实现,分别是IUserInterface(用户操作接口)、IQueryStatus(游戏状态查询接口)、setStatus(状态设置接口)和IScore(分数控制接口)。

public class SceneController : IUserInterface, IQueryStatus, setStatus, IScore {
        public int sendDiskCount { get; private set; }

        private static SceneController sceneController;
        private GameStatus gameStatus;
        private SceneStatus sceneStatus;
        private DiskFactory diskFactory = DiskFactory.getFactory ();
        public Scene scene;

        public static SceneController getGSController () {
            if (sceneController == null) {
                sceneController = new SceneController ();
            }

            return sceneController;
        }

        public void sendDisk () {
            int diskCount = scene.diskCount;
            var diskList = diskFactory.prepareDisks (diskCount);
            scene.sendDisk (diskList);
        }

        public void destroyDisk (GameObject disk) {
            scene.destroyDisk (disk);
            diskFactory.recycleDisk (disk);
        }

        public GameStatus queryGameStatus () {
            return gameStatus;
        }

        public SceneStatus querySceneStatus () {
            return sceneStatus;
        }

        public void setGameStatus (GameStatus gameStatus) {
            this.gameStatus = gameStatus;
        }

        public void setSceneStatus (SceneStatus sceneStatus) {
            this.sceneStatus = sceneStatus;
        }

        public void addScore () {
            ScoreRecorder.getScoreRecorder ().addScore ();
        }

        public void subScore () {
            ScoreRecorder.getScoreRecorder ().subScore ();
        }

        public int getScore () {
            return ScoreRecorder.getScoreRecorder ().score;
        }

        public void update () {
            scene.sceneUpdate ();
        }
    }

3、场景和界面( V )

接着考虑View层,主要是两部分:用户界面和场景设置。

场景设置(Scene)是用来设置飞碟的数量、大小、飞行速度等属性,控制飞碟的发射与回收,以及判断飞碟的状态并传输给控制类。

而用户界面则是提供人机交互的具体界面,包括四个部分:计分板、游戏状态栏、一个平面还有飞碟,其中飞碟只有在我们“召唤”时才会出现,游戏状态栏只有在游戏失败或成功时出现,这四个部分分别在UI.cs和UserInterface.cs文件中实现了。

Scene.cs:

public class Scene : MonoBehaviour
{
    public int round{get; set;}
    public int diskCount{get; private set;}

    private float diskScale;
    private float diskSpeed;
    private Color diskColor;
    private Vector3 startP;
    private Vector3 startD;

    List<GameObject> usingDisks;

    public void Reset(int round){
        this.round = round;
        this.diskCount = round;
        this.diskScale = 1;
        this.diskSpeed = 0.1f;

        if(round%2 == 1){
            this.diskColor = Color.red;
            this.startP = new Vector3(-5f, 3f, -15f);
            this.startD = new Vector3(3f, 8f, 5f);
        }
        else {
            this.diskColor = Color.green;
            this.startP = new Vector3(5f, 3f, -15f);
            this.startD = new Vector3(-3f, 8f, 5f);
        }

        for(int i=1 ; i<round ; i++){
            this.diskScale *= 0.8f;
            this.diskSpeed *= 1.1f;
        }
    }

    public void sendDisk(List<GameObject> usingDisks){
        this.usingDisks = usingDisks;
        this.Reset(round);

        for(int i=0 ; i<usingDisks.Count ; i++){
            var localScale = usingDisks[i].transform.localScale;

            usingDisks[i].transform.localScale = new Vector3(localScale.x*diskScale, localScale.y*diskScale, localScale.z*diskScale);
            usingDisks[i].GetComponent<Renderer>().material.color = diskColor;
            usingDisks[i].transform.position = new Vector3(startP.x, startP.y+i, startP.z);

            Rigidbody rigidbody;
            rigidbody = usingDisks[i].GetComponent<Rigidbody>();
            rigidbody.WakeUp();
            rigidbody.useGravity = true;
            rigidbody.AddForce(startD*Random.Range(diskSpeed*5, diskSpeed*8)/5, ForceMode.Impulse);
            SceneController.getGSController().setSceneStatus(SceneStatus.shooting);
        }
    }

    public void destroyDisk(GameObject disk){
        disk.GetComponent<Rigidbody>().Sleep();
        disk.GetComponent<Rigidbody>().useGravity = false;
        disk.transform.position = new Vector3(0f, -99f, 0f);
    }

    public void sceneUpdate(){
        round++;
        Reset(round);
    }

    private void Start(){
        this.round = 1;
        Reset(round);
    }

    private void Update(){
        if(usingDisks != null){
            for(int i=0 ; i<usingDisks.Count ; i++){
                if (usingDisks[i].transform.position.y <= 1){
                    SceneController.getGSController().destroyDisk(usingDisks[i]);
                    SceneController.getGSController().subScore();
                }
            }

            if(usingDisks.Count == 0){
                SceneController.getGSController().setSceneStatus(SceneStatus.waiting);
            }
        }
    }
}

UI.cs:
其实在设计中游戏状态栏一直存在于界面上,但是它是透明的,只有在有win或fail出现时才能看到。

public class UI : MonoBehaviour
{
    GameObject scoreText;
    GameObject gameStatusText;
    IScore score = SceneController.getGSController() as IScore;
    IQueryStatus status = SceneController.getGSController() as IQueryStatus;

    // Start is called before the first frame update
    void Start()
    {
        scoreText = GameObject.Find("Score");
        gameStatusText = GameObject.Find("GameStatus");
    }

    // Update is called once per frame
    void Update()
    {
        string scores = Convert.ToString(score.getScore());

        if(status.queryGameStatus() == GameStatus.fail){
            gameStatusText.GetComponent<Text>().text = "fail!";
        }
        else if(status.queryGameStatus() == GameStatus.win){
            gameStatusText.GetComponent<Text>().text = "win!";
        }

        scoreText.GetComponent<Text>().text = "Score: " + scores;
    }
}

UserInterface.cs:
这里设计为当我们按空格键(space)时,会在飞碟工厂中唤醒n个飞碟,将其发射出来;而当我们鼠标点击到飞碟时,飞碟会被销毁。

public class UserInterface : MonoBehaviour
{
    public GameObject planePrefab;

    GameStatus gameStatus;
    SceneStatus sceneStatus;

    IUserInterface userInterface;
    IQueryStatus queryStatus;
    IScore score;

    void Start(){
        GameObject plane = GameObject.Instantiate<GameObject>(planePrefab);
        plane.transform.position = new Vector3(0f, 0f, 70f);

        gameStatus = GameStatus.ongoing;
        sceneStatus = SceneStatus.waiting;

        userInterface = SceneController.getGSController() as IUserInterface;
        queryStatus = SceneController.getGSController() as IQueryStatus;
        score = SceneController.getGSController() as IScore;
    }

    void Update(){
        gameStatus = queryStatus.queryGameStatus ();
        sceneStatus = queryStatus.querySceneStatus ();

        if (gameStatus == GameStatus.ongoing) {
            if (sceneStatus == SceneStatus.waiting && Input.GetKeyDown ("space")) {
                userInterface.sendDisk ();
            }
            if (sceneStatus == SceneStatus.shooting && Input.GetMouseButtonDown (0)) {
                Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast (ray, out hit) && hit.collider.gameObject.tag == "Disk") {
                    userInterface.destroyDisk (hit.collider.gameObject);
                    score.addScore ();
                }
            }
        }
    }
}

4、分数记录器( M )

此处的分数分数记录器是属于Model层,它负责游戏逻辑的设置,主要是加减分数,获得分数,判断游戏输赢以及判断是否进入下一回合这四个功能。

public class ScoreRecorder : IScore
{
    public int score{get; set;}
    public int round{get; set;}
    private static ScoreRecorder scoreRecorder;

    public static ScoreRecorder getScoreRecorder(){
        if(scoreRecorder == null){
            scoreRecorder = new ScoreRecorder();
            scoreRecorder.round = 1;
        }

        return scoreRecorder;
    }

    public void addScore(){
        score += 10;
        if(checkUpdate()){
            this.round++;
            SceneController.getGSController().update();
        }
    }

    public void subScore(){
        score -= 10;
        if(score <= 0){
            SceneController.getGSController().setGameStatus(GameStatus.fail);
        }
    }

    public bool checkUpdate(){
        if(score >= round*20)
            return true;
        return false;
    }

    public int getScore(){
        return ScoreRecorder.getScoreRecorder().score;
    }
}

同时游戏模型的预制也是属于Model层的,此次预制包括一个平面(Plane)和刚体飞碟(Disk)。
在这里插入图片描述

游戏运行

将Script中的DiskFactoryMono.cs(内含DiskFactory)、GameMain.cs(内含SceneController)和UserInterface.cs(内含UserInterface)挂载到主摄像机上,并将预制的内容添加到对应位置;

然后创建一个UI游戏对象,其下包含两个Text游戏对象,分别命名为Score和GameStatus,放置在场景中对应位置,将UI.cs挂载到UI上;

点击运行,便可以进入游戏界面了。

在这里插入图片描述

效果展示

在这里插入图片描述

代码地址

传送门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值