3D游戏建模与设计6:基于Unity平台编写游戏:打飞碟(clay pigeon shooting)

编程入门 行业动态 更新时间:2024-10-05 03:27:53

3D<a href=https://www.elefans.com/category/jswz/34/1770081.html style=游戏建模与设计6:基于Unity平台编写游戏:打飞碟(clay pigeon shooting)"/>

3D游戏建模与设计6:基于Unity平台编写游戏:打飞碟(clay pigeon shooting)

1.游戏简介

  在美国,有一种游戏叫飞碟射击(clay pigeon shooting),又称为打飞碟。它是一种用枪械射击抛在空中作为飞靶的圆形黏土碟的射击运动。英文中对飞碟的称呼是“黏土鸽子”(clay pigeon)或“鸟”(bird),原因是过去英国的射击比赛曾使用活鸽子作为靶子,虽然在1921年被立法禁止而改用陶碟模拟飞鸟,但叫法还是保留了下来,把命中目标叫“打死”(kill)、没能击中叫“鸟逃了”(bird away)、发射飞碟的机械叫“鸟阱”(trap)。  

  我们用游戏模拟现实世界,本次游戏就模拟现实世界中的飞碟射击运动,并将这一运动进行简化,我们不再利用枪支打飞碟,而是利用鼠标我们就可以进行飞碟射击运动,鼠标点击飞碟可使飞碟消失,模拟真实世界中子弹击中飞碟的效果,现在点击开始游戏按钮并进行游玩吧。

2.游戏规则

  1)玩家打到红色飞碟得1分,打到绿色飞碟得2分,打到蓝色飞碟得3分 。

  2)一共发射五轮飞碟,对于第n轮,会发射2的n-1次方个飞碟,例如第一轮发射1个飞碟,第二轮发射2个飞碟,第三轮发射4个飞碟,以此类推,当所有飞碟发射完毕时游戏结束。

3.游戏玩法

  使用鼠标点击飞镖,点击不同颜色的飞碟就有相应的得分,为了得到更高的分数,玩家需要在有限的轮数内尽力打到最多的飞碟,此外,轮次数越大,发射的飞碟数更多,玩家一次性无法打中所有飞碟,故玩家要选择得分高的颜色的飞碟打,这无疑增加了游戏的可玩性。

4.游戏UML图

MainControllor为主控制器,用于加载资源、开始游戏、重启游戏、判断游戏是否结束等。

IUserAction:用户动作基类接口,内含有用户能通过GUI进行的操作。

UserGUI:用户控制相关的GUI等。

CCFlyAction:主要用于回调,当飞碟飞出屏幕时进行回调。

ISSActionCallbck:实现回调函数。

SceneControllor:用于控制场景。

 SSAction:动作基类接口。

SSActionManger:动作管理者基类并不直接作为动作管理者使用,它的存在是为调用者提供较简单和通用的接口。

SSDirector:场记。

DiskFactory:以工厂模式控制飞碟。

DiskData:飞碟基础数据。

CCActionManager:具体动作作为类中的一个对象从而管理动作。

5.游戏编写

  本游戏基于Unity引擎编写,使用的系统为Windows10,使用的脚本文件语言为C#,首先需要确保保证电脑中安装Unity引擎,我的版本是2023.3.8f1c1,还需下载VSCode或VStudio用于编写代码,还需要Unity的Plastic SCM用于管理代码,具体安装方法这里不过多介绍。

接着我们打开Unity,找到项目,选择新项目。

 选择3D,项目命名为clay pigeon shooting,保存地址我选择的是D盘。(启动版本管理可选可不选)。

现在我们进入了Unity引擎界面,如图所示,什么内容也没有。

本项目中运用到的代码、文件非常多,为了保证界面的简洁性,建议在Assests项目栏中如下图建

立几个文件夹方便管理。

建立方法如下:在Assets栏中空白区用鼠标右键点击,选择Create中的Folder生成我们需要的游戏文件。

然后我们先制作飞碟,先在Resources文件夹中建立文件夹Prefabs。

然后在文件夹Prefabs制作飞碟,在左侧SampleScene中右键鼠标,选择3D Object->Cylinder

将Cylinder命名为Disk_Prefab,我的飞碟各个参数设置如下,也可以自行设置飞碟。

然后在Scripts文件夹中编写代码,共有以下几个脚本:

我们先编写UserGUI脚本,UerGUI主要是设置Label和button,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class UserGUI : MonoBehaviour
{private IUserAction userAction;public string gameMessage;public int points;bool gameStart = false; //判断是否在游戏中void Start(){points = 0;gameMessage = "";userAction = SSDirector.GetInstance().CurrentScenceController as IUserAction;gameStart = false;}void OnGUI(){//小字体初始化GUIStyle style = new GUIStyle();style.normal.textColor = Color.white;style.fontSize = 30;//大字体初始化GUIStyle bigStyle = new GUIStyle();bigStyle.normal.textColor = Color.white;bigStyle.fontSize = 50;if (!gameStart){GUI.Label(new Rect(Screen.width * 0.5f - 195, Screen.height * 0.25f, 100, 100), "Clay Pigeon Shooting", bigStyle);if (GUI.Button(new Rect(Screen.width * 0.5f - 50, Screen.height * 0.5f, 100, 40), "开始游戏")){gameStart = true;userAction.GameStart(gameStart);}}if(gameStart){GUI.Label(new Rect(Screen.width * 0.5f - 50, 20, 100, 30), "得分: " + points, style);GUI.Label(new Rect(Screen.width * 0.5f - 50, Screen.height * 0.5f - 200, 100, 100), gameMessage, style);userAction.SetMode(false);if (GUI.Button(new Rect(20, 50, 100, 40), "Restart")){userAction.Restart();}if (Input.GetButtonDown("Fire1")){userAction.Hit(Input.mousePosition);}}}
}

我们现在编写IUserAction脚本,用户有三个动作,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public interface IUserAction
{void Hit(Vector3 position);void Restart();void GameStart(bool Mystart);
}

我们现在编写DiskData,飞碟主要有speed、points和direction三个属性,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class DiskData : MonoBehaviour
{public float speed;         //水平速度public int points;          //得分public Vector3 direction;   //初始方向
}

我们现在编写DiskFactory脚本,该脚本主要用于生成飞碟与释放飞碟,其中GetDisk用于生产飞碟,我们先从free列表中查找是否有空闲的飞碟,如果没有则另外创建一个新飞碟。为了确保难度是由简单到复杂,及低轮次数只产生分数比较小的飞碟(红色),高轮次数产生分数比较大的飞碟(蓝色与绿色),我们给每个飞碟设置等级,飞碟的等级与轮次数有关,我们在0~2之间的随机数乘以轮次数获得飞碟的等级,并按照等级设置飞碟的分数、颜色、速度、方向等基本信息,这样就实现了我们的目的。而FreeDisk用于释放飞碟,将飞碟从used列表中移除并添加到free列表中以供下一轮的使用。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class DiskFactory : MonoBehaviour
{public GameObject disk_Prefab;              //飞碟预制private List<DiskData> used;                //正被使用的飞碟private List<DiskData> free;                //空闲的飞碟public void Start(){used = new List<DiskData>();free = new List<DiskData>();disk_Prefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Disk_Prefab"), Vector3.zero, Quaternion.identity);disk_Prefab.SetActive(false);}public GameObject GetDisk(int round){GameObject disk;//如果有空闲的飞碟,则直接使用,否则生成一个新的if (free.Count > 0){disk = free[0].gameObject;free.Remove(free[0]);}else{disk = GameObject.Instantiate<GameObject>(disk_Prefab, Vector3.zero, Quaternion.identity);disk.AddComponent<DiskData>();}//按照round来设置飞碟属性//不同等级数对应不同颜色的飞镖,当等级数为://0~4是红色飞碟  //4~7是绿色飞碟  //7~10是蓝色飞碟//飞碟的等级 = 0~2之间的随机数 * 轮次数float level = UnityEngine.Random.Range(0, 2f) * (round + 1);if (level < 4)//红色飞碟{disk.GetComponent<DiskData>().points = 1;disk.GetComponent<DiskData>().speed = 4.0f;disk.GetComponent<DiskData>().direction = new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);disk.GetComponent<Renderer>().material.color = Color.red;}else if (level > 7)//绿色飞碟{disk.GetComponent<DiskData>().points = 3;disk.GetComponent<DiskData>().speed = 8.0f;disk.GetComponent<DiskData>().direction = new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);disk.GetComponent<Renderer>().material.color = Color.blue;}else//蓝色飞碟{disk.GetComponent<DiskData>().points = 2;disk.GetComponent<DiskData>().speed = 6.0f;disk.GetComponent<DiskData>().direction = new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);disk.GetComponent<Renderer>().material.color = Color.green;}used.Add(disk.GetComponent<DiskData>());return disk;}public void FreeDisk(GameObject disk){//找到使用中的飞碟,将其踢出并加入到空闲队列foreach (DiskData diskData in used){if (diskData.gameObject.GetInstanceID() == disk.GetInstanceID()){disk.SetActive(false);free.Add(diskData);used.Remove(diskData);break;}}}
}

我们现在编写Singleton脚本,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;}}
}

我们现在编写SSAction脚本,在动作分离版本中,将动作其抽离为一个 SSAction 类,当这个物体需要进行某些动作时,再实时进行动作的创建,并进行Update()。并且为其定义一个 destroy 属性,当动作完成后自动将这个动作实例标记为可以删除的对象,等待游戏引擎自动回收这个对象,减少对系统资源的占用。具体代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SSAction : ScriptableObject
{public bool enable = true;public bool destroy = false;public GameObject gameObject { get; set; }public Transform transform { get; set; }public ISSActionCallback callback { get; set; }protected SSAction(){}// Start is called before the first frame updatepublic virtual void Start(){throw new System.NotImplementedException();}// Update is called once per framepublic virtual void Update(){throw new System.NotImplementedException();}
}

我们现在编写ISSActionCallBack脚本,当一个动作完成之后,我们需要通知其他的部分(如动作管理器或其他的动作)当前的动作已经完成,可以进行下一步的操作。因此添加一个SSActionCallbackInterface 类,使得在动作完成之后再进行一个回调操作,实现通知其他部分的这个功能。具体代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public enum SSActionEventType : int { Started, Competed }
public interface ISSActionCallback
{//回调函数void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competed,int intParam = 0,string strParam = null,Object objectParam = null);
}

我们现在编写CCFlyAction脚本,CCMoveToAction 继承于 SSAction,并给动作管理器提供了GetSSAction的接口。当动作管理器调用这个方法的时候,可以直接生成一个动作的脚本,此时只需要将这个脚本赋予给某个物体,并由动作管理器进行动作的调度之后,即可产生物体做出这个动作的效果。在CCFlyAction脚本中,我们将飞碟的抛物线运动分解为水平方向运动与垂直方向运动,水平速度恒定,垂直方向施加重力加速度。此外,当飞碟到达底部时,动作结束,将进行回调。具体代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CCFlyAction : SSAction
{float gravity;          //重力加速度float speed;            //水平速度Vector3 direction;      //飞行方向float time;             //时间//生产函数(工厂模式)public static CCFlyAction GetSSAction(Vector3 direction, float speed){CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();action.gravity = 9.8f;action.time = 0;action.speed = speed;action.direction = direction;return action;}public override void Start(){}public override void Update(){time += (Time.deltaTime);transform.Translate(Vector3.down * gravity * time * Time.deltaTime);transform.Translate(direction * speed * Time.deltaTime);//如果飞碟到达底部,则动作结束,进行回调if (this.transform.position.y < -6){this.destroy = true;this.enable = false;this.callback.SSActionEvent(this);}}
}

我们现在编写SSActionManager脚本,SSActionManager就是此次动作管理器实现的核心部分,其实现了对所有动作的管理,接收新创建的动作对象,并进行对已经存在的动作对象进行调度的功能,最后会将被标记为可删除的动作对象进行回收操作。在SSActionManager脚本中,我们用动作集、等待集与删除集管理动作,每次程序运行update()函数时先将waitingAdd中的动作保存,再运行被保存后的动作,运行后销毁该动作。对于下一次要执行的动作,先将动作初始化,然后保存在waitingAdd中,具体代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SSActionManager : MonoBehaviour
{//动作集,以字典形式存在private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();//等待被加入的动作队列(动作即将开始)private List<SSAction> waitingAdd = new List<SSAction>();//等待被删除的动作队列(动作已完成)private List<int> waitingDelete = new List<int>();protected void Update(){//将waitingAdd中的动作保存foreach (SSAction ac in waitingAdd)actions[ac.GetInstanceID()] = ac;waitingAdd.Clear();//运行被保存的事件foreach (KeyValuePair<int, SSAction> kv in actions){SSAction ac = kv.Value;if (ac.destroy){waitingDelete.Add(ac.GetInstanceID());}else if (ac.enable){ac.Update();}}//销毁waitingDelete中的动作foreach (int key in waitingDelete){SSAction ac = actions[key];actions.Remove(key);Destroy(ac);}waitingDelete.Clear();}//准备运行一个动作,将动作初始化,并加入到waitingAddpublic void RunAction(GameObject gameObject, SSAction action, ISSActionCallback manager){action.gameObject = gameObject;action.transform = gameObject.transform;action.callback = manager;waitingAdd.Add(action);action.Start();}// Start is called before the first frame updateprotected void Start(){}}

我们现在编写CCActionManger脚本,由于动作管理器的设计不局限于一个项目的使用,因此在这个项目中继承 SSActionManager 再设计一个具体的CCActionManger类,使得其可以在这个项目中产生具体的动作实例,再进行调度操作。CCActionManger脚本负责生成飞行动作,并接受飞行动作的回调信息,使飞碟被回收具体代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CCActionManager : SSActionManager, ISSActionCallback
{//飞行动作public CCFlyAction flyAction;//控制器public FirstController controller;protected new void Start(){controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;controller.actionManager = this;}public void Fly(GameObject disk, float speed, Vector3 direction){flyAction = CCFlyAction.GetSSAction(direction, speed);RunAction(disk, flyAction, this);}//回调函数public void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competed,int intParam = 0,string strParam = null,Object objectParam = null){//飞碟结束飞行后进行回收controller.diskFactory.FreeDisk(source.gameObject);}
}

我们现在编写场景控制器SceneController脚本,具体代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public interface ISceneController
{void LoadResources();
}

现在我们编写SSDirector脚本,场记,用于加载场景,具体代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SSDirector : System.Object
{private static SSDirector _instance;public ISceneController CurrentScenceController { get; set; }public static SSDirector GetInstance(){if (_instance == null){_instance = new SSDirector();}return _instance;}
}

现在我们编写主控制器MainController脚本,MainController脚本负责游戏主要逻辑,现在具体介绍一下几个函数。

Start()函数:

  Start调用LoadReasources()函数。

LoadReasources()函数:

  LoadReasources用于各个参数初始化。

SendDisk()函数:
  SendDisk用于发射一个飞碟,首先从工厂获得一个飞碟,再为其设置初始位置和飞行动作。
Hit()函数:
  Hit用于处理用户的点击动作,将用户点击到的飞碟移除,并计算分数。

GameStart()函数:

  GameStart用于取得程序是否在游戏中的状态。
Restart()函数:
  Restart用于重置游戏。
Update()函数:
  Update用于发射飞碟与更新状态,飞碟每2s发射一次,每次做多3只,避免太过拥挤,当飞碟发射完毕后判断是否重置或者结束游戏。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FirstController : MonoBehaviour, ISceneController, IUserAction
{public CCActionManager actionManager;                   //动作管理者public DiskFactory diskFactory;                         //飞碟工厂int[] roundDisks;           //对应轮次的飞碟数量bool isInfinite;            //游戏当前模式int points;                 //游戏当前分数int round;                  //游戏当前轮次int sendCnt;                //当前已发送的飞碟数量float sendTime;             //发送时间bool gameStart;void Start(){LoadResources();}public void LoadResources(){SSDirector.GetInstance().CurrentScenceController = this;gameObject.AddComponent<DiskFactory>();gameObject.AddComponent<CCActionManager>();gameObject.AddComponent<UserGUI>();diskFactory = Singleton<DiskFactory>.Instance;sendCnt = 0;round = 0;sendTime = 0;points = 0;isInfinite = false;roundDisks = new int[] { 1, 2, 4 ,8 ,16};}public void SendDisk(){//从工厂生成一个飞碟GameObject disk = diskFactory.GetDisk(round);//设置飞碟的随机位置disk.transform.position = new Vector3(-disk.GetComponent<DiskData>().direction.x * 7, UnityEngine.Random.Range(0f, 8f), 0);disk.SetActive(true);//设置飞碟的飞行动作actionManager.Fly(disk, disk.GetComponent<DiskData>().speed, disk.GetComponent<DiskData>().direction);}public void Hit(Vector3 position){Camera ca = Camera.main;Ray ray = ca.ScreenPointToRay(position);RaycastHit[] hits;hits = Physics.RaycastAll(ray);for (int i = 0; i < hits.Length; i++){RaycastHit hit = hits[i];if (hit.collider.gameObject.GetComponent<DiskData>() != null){//将飞碟移至底端,触发飞行动作的回调hit.collider.gameObject.transform.position = new Vector3(0, -7, 0);//积分points += hit.collider.gameObject.GetComponent<DiskData>().points;//更新GUI数据gameObject.GetComponent<UserGUI>().points = points;}}}public void GameStart(bool Mystart){gameStart = Mystart;}public void Restart(){gameObject.GetComponent<UserGUI>().gameMessage = "";round = 0;sendCnt = 0;points = 0;gameObject.GetComponent<UserGUI>().points = points;}public void SetMode(bool isInfinite){this.isInfinite = isInfinite;}void Update(){if(gameStart){sendTime += Time.deltaTime;//每隔1s发送一次飞碟if (sendTime > 2){sendTime = 0;//每次发送至多3个飞碟for (int i = 0; sendCnt < roundDisks[round]; i++){sendCnt++;SendDisk();}//判断是否需要重置轮次,不需要则输出游戏结束if (sendCnt == roundDisks[round] && round == roundDisks.Length - 1){if (isInfinite){round = 0;sendCnt = 0;gameObject.GetComponent<UserGUI>().gameMessage = "";}else{gameObject.GetComponent<UserGUI>().gameMessage = "Game Over!";}}//更新轮次if (sendCnt == roundDisks[round] && round < roundDisks.Length - 1){sendCnt = 0;round++;}}}}
}

到此,所有脚本编写完毕,可以对游戏进行游玩了。

6.游戏演示

3D游戏编程与设计作业5 基于unity平台编写打飞碟游戏_哔哩哔哩bilibili

更多推荐

3D游戏建模与设计6:基于Unity平台编写游戏:打飞碟(clay pigeon shooting)

本文发布于:2024-02-28 13:47:43,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1769748.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:游戏   建模   平台   pigeon   shooting

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!