• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

3D游戏编程学习笔记五和游戏世界交互

武飞扬头像
m0_61694542
帮助1

一、前言

本次3D游戏编程我们将设计一个简单打飞碟(Hit UFO)有游戏。

二、游戏基本内容及规定

  • 游戏基本内容
  1. 游戏有 n 个 round,每个 round 都包括10 次 trial;
  2. 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
  3. 每个 trial 的飞碟有随机性,总体难度随 round 上升;
  4. 鼠标点中得分,得分规则按色彩、大小、速度不同计算。
  • 游戏设计方式
  1. 使用带缓存的工厂模式管理不同飞碟的生产与回收,工厂是场景单实例的。
  2. 使用MVC 结构实现人机交互与游戏模型分离
  3. 综上,游戏使用单例模式和工厂模式。单例模式的目的是为了实现只能有一个实例的类,好处是可以节省资源和防止错误。工厂模式是用一个类作为工厂,生产创建不同的产品,也就是其他对象,好处是方便代码的扩展以及减少代码编写。‘

UML设计图如下:
学新通

三、游戏实现

  1. 动作管理的相关实现
    本次游戏要操作的游戏对象为飞碟,因此动作管理除了上一个游戏的代码外,还需要使用两个新的类来实现飞碟的运动,下面来分别介绍这两个类。
  • FlyActionManager类:
    FlyActionManager类实现了初始化操作以及管理飞行的动作,该类需要关联到DiskFlyAction类,以定义相关的动作。该类主要被SceneController类所调用。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FlyActionManager : SSActionManager {
    public DiskFlyAction fly;  
    public SceneController scene_controller;           

    protected void Start() {
        scene_controller = (SceneController)SSDirector.GetInstance().CurrentScenceController;
        scene_controller.action_manager = this;     
    }

    //飞碟飞行控制
    public void DiskFly(GameObject disk, float angle, float power) {
        int lor = 1;
        if (disk.transform.position.x > 0) lor = -1;
        fly = DiskFlyAction.GetSSAction(lor, angle, power);
        this.RunAction(disk, fly, this);
    }
}

学新通
  • DiskFlyAction类:
    如上所述,FlyActionManager类需要DiskFlyAction类来提供相应动作的定义,因此DiskFlyAction类主要负责实现飞碟的动作,需要指定一系列和移动相关的参数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskFlyAction : SSAction {
    public float gravity = -5;                                 //向下加速度
    private Vector3 start_vector;                              //初速度
    private Vector3 gravity_vector = Vector3.zero;             //加速度
    private Vector3 current_angle = Vector3.zero;              //欧拉角
    private float time;                                        //时间

    private DiskFlyAction() { }
    public static DiskFlyAction GetSSAction(int lor, float angle, float power) {
        //初始化物体
        DiskFlyAction action = CreateInstance<DiskFlyAction>();
        if (lor == -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() {
        //计算物体的向下的速度
        time  = Time.fixedDeltaTime;
        gravity_vector.y = gravity * time/5f;

        //位移模拟
        transform.position  = (start_vector   gravity_vector) * Time.fixedDeltaTime/5f;
        current_angle.z = Mathf.Atan((start_vector.y   gravity_vector.y) / start_vector.x) * Mathf.Rad2Deg;
        transform.eulerAngles = current_angle;

        //如果物体y坐标小于-10,动作就做完了
        if (this.transform.position.y < -10) {
            this.destroy = true;
            this.callback.SSActionEvent(this);      
        }
    }

    public override void Start() { }
}

学新通
  1. UI界面设计
    具体来说,UI界面主要需要对使用标签和按钮进行构造,比如:未开始游戏时需要有游戏开始的按钮,而开始游戏后需要显示游戏Round和Trial以及分数等等。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UserGUI : MonoBehaviour {
    private IUserAction action;
    
    //每个GUI的style
    GUIStyle bold_style = new GUIStyle();
    GUIStyle text_style = new GUIStyle();
    GUIStyle over_style = new GUIStyle();
    private bool game_start = false;

    void Start () {
        action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
    }
	
	void OnGUI () {
        bold_style.normal.textColor = new Color(1, 0, 0);
        bold_style.fontSize = 16;
        text_style.normal.textColor = new Color(0, 0, 0, 1);
        text_style.fontSize = 16;
        over_style.normal.textColor = new Color(0, 0, 1);
        over_style.fontSize = 25;

        //游戏启动则开始绘图
        if (game_start) {

            GUI.Label(new Rect(Screen.width - 150, 5, 200, 50), "当前分数:"  action.GetScore().ToString(), text_style);
            GUI.Label(new Rect(100, 5, 50, 50), "Round:"   action.GetRound().ToString(), text_style);
            GUI.Label(new Rect(180, 5, 50, 50), "                                                    Trial:"   action.GetTrial().ToString(), text_style);

            if (action.GetRound() == 3 && action.GetTrial() == 10) {
                GUI.Label(new Rect(Screen.width / 2 - 20, Screen.height / 2 - 250, 100, 100), "游戏结束", over_style);
                GUI.Label(new Rect(Screen.width / 2 - 40, Screen.height / 2 - 100, 50, 50), "你的分数:"   action.GetScore().ToString(), text_style);
                if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 70, 100, 50), "重新开始")) {
                    action.ReStart();
                    return;
                }
                action.GameOver();
            }
        }
        else {
            GUI.Label(new Rect(Screen.width / 2 - 80, 100, 100, 100), "简单打飞碟游戏", over_style);
            GUI.Label(new Rect(Screen.width / 2 - 90, 150, 100, 100), "游戏玩法:用鼠标点击飞碟", text_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 50, 200, 100, 50), "游戏开始")) {
                game_start = true;
                action.ReStart();
            }
        }
    }
   
}

学新通
  1. 游戏物体生成
    在本次游戏设计中,我们采用工厂模式来生成飞碟,因此实现了Disk类用来表示飞碟,以及DiskFactory类来批量生产飞碟。
  • Disk类
    Disk类只包含三个变量,飞碟类型type,分数score,颜色color,用来创建飞碟。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Disk : MonoBehaviour {
    public int type = 1;//飞碟类型
    public int score = 1;//飞碟分数                          
    public Color color = Color.white;//颜色                    
}
  • DiskFactory类
    DiskFactory类实现了生成飞碟和释放飞碟的函数,其中以链表作为容器来存储飞碟一个是正在使用的飞碟,一个是空闲飞碟。
    获取飞碟时,先在空闲列表中寻找可用的空闲飞碟,如果找不到就根据预制重新实例化一个飞碟。
    回收飞碟的逻辑为遍历使用列表,当有飞碟已经完成了所有动作则回收。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskFactory : MonoBehaviour {
    private List<Disk> used = new List<Disk>();
    private List<Disk> free = new List<Disk>();

    public GameObject GetDisk(int type) {
        GameObject disk_prefab = null;
        //寻找空闲飞碟,如有空闲则不需要多实例化一个
        if (free.Count>0) {
            for(int i = 0; i < free.Count; i  ) {
                if (free[i].type == type) {
                    disk_prefab = free[i].gameObject;
                    free.Remove(free[i]);
                    break;
                }
            }     
        }
        //从预制件中添加游戏物体
        if(disk_prefab == null) {
            if(type == 1) {
                disk_prefab = Instantiate(
                Resources.Load<GameObject>("Prefabs/disk1"),
                new Vector3(0, -10f, 0), Quaternion.identity);
            }
            else if (type == 2) {
                disk_prefab = Instantiate(
                Resources.Load<GameObject>("Prefabs/disk2"),
                new Vector3(0, -10f, 0), Quaternion.identity);
            }
            else {
                disk_prefab = Instantiate(
                Resources.Load<GameObject>("Prefabs/disk3"),
                new Vector3(0, -10f, 0), Quaternion.identity);
            }

            disk_prefab.GetComponent<Renderer>().material.color = disk_prefab.GetComponent<Disk>().color;
        }

        used.Add(disk_prefab.GetComponent<Disk>());
        disk_prefab.SetActive(true);
        return disk_prefab;
    }

    public void FreeDisk() {
        for(int i=0; i<used.Count; i  ) {
            if (used[i].gameObject.transform.position.y <= -10f) {
                free.Add(used[i]);
                used.Remove(used[i]);
            }
        }          
    }

    public void Reset() {
        FreeDisk();
    }
}

学新通
  1. 单例模式设计
    这里使用模板类Singleton,来生成单一实例。当实例第一次被需要时,在场景内搜索该实例,下一次使用时直接返回。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour {
    protected static T instance;
    public static T Instance {
        get {
            if (instance == null) {
                instance = (T)FindObjectOfType(typeof(T));

                if (instance == null) {
                    Debug.LogError("An instance of "   typeof(T)
                          " is needed in the scene, but there is none.");
                }
            }
            return instance;
        }
    }
}
学新通
  1. 计分器
    本次主要使用ScoreRecorder类来实时统计游戏分数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/*记录分数*/
public class ScoreRecorder : MonoBehaviour {
    private float score;
    void Start () {
        score = 0;//初始分数
    }

    //记录分数
    public void Record(GameObject disk) {
        score  = disk.GetComponent<Disk>().score;
    }

    //获得当前分数
    public float GetScore() {
        return score;
    }
    public void Reset() {
        score = 0;
    }
}

学新通
  1. 场景控制器
    本次游戏设计以SceneController类作为场景控制器。场景控制器主要实现了一些成员变量存储正常游戏的数据信息,游戏对象创建的函数,点击飞盘的函数,初始化函数以及其他相关函数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//场景控制器
public class SceneController : MonoBehaviour, ISceneController, IUserAction {
    public FlyActionManager action_manager;
    public DiskFactory disk_factory;
    public UserGUI user_gui;
    public ScoreRecorder score_recorder;
    private int round = 1;                                                  
    private int trial = 0;
    private float speed = 0.1f;                                             
    private bool running = false;

    void Start () {
        SSDirector director = SSDirector.GetInstance();     
        director.CurrentScenceController = this;
        disk_factory = Singleton<DiskFactory>.Instance;
        score_recorder = Singleton<ScoreRecorder>.Instance;
        action_manager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
        user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
    }

    int count = 0;
	void Update () {
        if(running) {
            count  ;
            if (Input.GetButtonDown("Fire1")) {
                Vector3 pos = Input.mousePosition;
                Hit(pos);
            }
            switch (round) {
                case 1: {
                        if (count >= 150) {
                            count = 0;
                            SendDisk(1);
                            trial  = 1;
                            if (trial == 10) {
                                round  = 1;
                                trial = 0;
                            }
                        }
                        break;
                    }
                case 2: {
                        if (count >= 100) {
                            count = 0;
                            if (trial % 2 == 0) SendDisk(1);
                            else SendDisk(2);
                            trial  = 1;
                            if (trial == 10) {
                                round  = 1;
                                trial = 0;
                            }
                        }
                        break;
                    }
                case 3: {
                        if (count >= 50) {
                            count = 0;
                            if (trial % 3 == 0) SendDisk(1);
                            else if(trial % 3 == 1) SendDisk(2);
                            else SendDisk(3);
                            trial  = 1;
                            if (trial == 10) {
                                running = false;
                            }
                        }
                        break;
                    }
                default:break;
            } 
            disk_factory.FreeDisk();
        }
    }

    public void LoadResources() {
        disk_factory.GetDisk(round);
        disk_factory.FreeDisk();
    }

    private void SendDisk(int type) {

        //从工厂中拿一个飞碟
        GameObject disk = disk_factory.GetDisk(type);

        //飞碟位置
        float ran_y = 0;
        float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
 
        //飞碟初始所受的力和角度
        float power = 0;
        float angle = 0;
        if (type == 1) {
            ran_y = Random.Range(1f, 5f);
            power = Random.Range(5f, 7f);
            angle = Random.Range(25f,30f);
        }
        else if (type == 2) {
            ran_y = Random.Range(2f, 3f);
            power = Random.Range(10f, 12f);
            angle = Random.Range(15f, 17f);
        }
        else {
            ran_y = Random.Range(5f, 6f);
            power = Random.Range(15f, 20f);
            angle = Random.Range(10f, 12f);
        }
        disk.transform.position = new Vector3(ran_x*16f, ran_y, 0);
        action_manager.DiskFly(disk, angle, power);
    }

    public void Hit(Vector3 pos) {
        Ray ray = Camera.main.ScreenPointToRay(pos);
        RaycastHit[] hits;
        hits = Physics.RaycastAll(ray);
        for (int i = 0; i < hits.Length; i  ) {
            RaycastHit hit = hits[i];
            if (hit.collider.gameObject.GetComponent<Disk>() != null) {
                score_recorder.Record(hit.collider.gameObject);
                hit.collider.gameObject.transform.position = new Vector3(0, -10, 0);
            }
        }
    }

    public float GetScore() {
        return score_recorder.GetScore();
    }
    public int GetRound() {
        return round;
    }
    public int GetTrial() {
        return trial;
    }
    //重新开始
    public void ReStart() {
        running = true;
        score_recorder.Reset();
        disk_factory.Reset();
        round = 1;
        trial = 1;
        speed = 2f;
    }
    //游戏结束
    public void GameOver() {
        running = false;
    }
}

学新通

四、游戏效果

如图所示

游戏开始:
学新通

游戏中:
学新通

演示视频:打飞碟(Hit UFO)演示视频

项目地址:Hit UFO

五、参考资料

[1]. unity官方文档
[2]. 师兄的博客

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfjjbke
系列文章
更多 icon
同类精品
更多 icon
继续加载