这次作业会在后面放完整代码和操作步骤的[破涕为笑]

进入正题之前先解释下几个代码里用到的几个英文单词吧_(:зゝ∠)_:

飞碟:dart   发射:launch   打击:strike   击中:struck

游戏规则rules

按空格键发射飞碟,鼠标点击打飞碟。第n关发射n个飞碟,击中1个飞碟得100分,击不中(飞碟落地)1个扣100分。第n关需要打够n*100分才能进下一关(如:第4关需要打够400分)。

下面po一下效果图:

UML类图:

先大概解释一下UML图吧(下面还会详细解释):

1、MainSceneController:单例控制类,实现IUserAction接口

2、UserInterface:检测用户操作(键盘&鼠标),调用IUserAction接口方法触发发射飞镖和击打飞镖行为

3、DartFactory:单例飞碟工厂,负责提供飞碟、检测飞碟着地、回收飞碟

4、GameModel:获取飞碟并发射;检测是否击中飞碟

5、GameStatus:管理所有游戏状态,如round数、score得分、提示文字等

代码解释:

1、MainSceneController.cs:

为单例控制类。有两个子对象GameModel和GameStatus。实现IUserAction接口(提供给用户UserInterface使用)的两个方法:发射飞碟和根据鼠标位置检测是否击中飞碟,实现方式为调用子对象GameModel的两个同名的方法。另外也实现IGameStatusOp接口(此接口供游戏扩展功能使用,这次没有真正用到,仅由GameModel调用了一下)的三个方法:获取round数、得分、扣分,实现方式也为调用子对象GameStatus的三个同名的方法。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace PlayDarts.Com {public interface IUserAction {void launchDarts();void strikeTheDart(Vector3 mousePos);}public interface IGameStatusOp {int getRoundNum();void addScore();void subScore();}public class MainSceneController : System.Object, IUserAction, IGameStatusOp {private static MainSceneController instance;private GameModel myGameModel;private GameStatus myGameStatus;public static MainSceneController getInstance() {if (instance == null)instance = new MainSceneController();return instance;}internal void setGameModel(GameModel _myGameModel) {if (myGameModel == null) {myGameModel = _myGameModel;}}internal void setGameStatus(GameStatus _myGameStatus) {if (myGameStatus == null) {myGameStatus = _myGameStatus;}}/*** 实现IUserAction接口*/public void launchDarts() {myGameModel.launchDarts();}public void strikeTheDart(Vector3 mousePos) {myGameModel.strikeTheDart(mousePos);}/*** 实现IGameStatusOp接口*/public int getRoundNum() {return myGameStatus.getRoundNum();}public void addScore() {myGameStatus.addScore();}public void subScore() {myGameStatus.subScore();}}
}

2、UserInterface.cs:

在Update()方法里检测用户操作(键盘&鼠标),调用IUserAction接口方法触发发射飞镖和击打飞镖行为

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PlayDarts.Com;public class UserInterface : MonoBehaviour {private IUserAction action;void Start () {action = MainSceneController.getInstance() as IUserAction;}void Update () {detectSpaceKeyAndLaunchDarts();detectMouseDownAndStrikeTheDart();}void detectSpaceKeyAndLaunchDarts() {if (Input.GetKeyDown(KeyCode.Space)) {action.launchDarts();}}void detectMouseDownAndStrikeTheDart() {if (Input.GetMouseButtonDown(0)) {Vector3 mouseWorldPosition = Input.mousePosition;action.strikeTheDart(mouseWorldPosition);}}
}

3、DartFactoryBC.cs:

单例飞碟工厂,负责提供飞碟、检测飞碟着地、回收飞碟。有两个list成员,一个存储正在使用(正在发射)的飞镖,另一个存储没在使用的飞镖(或者落地或者被击中后回收)。这样设置目的在于,当存在没使用的飞镖时,可以重复使用,而不是新创建,从而节省资源!这里的检测落地由GameModel的Update()方法触发。

PS:需要注意的一个点是,飞镖回收的时候注意设置速度为0,不然回收的飞碟重新发射时会发射位置和速度偏差,即下面这句:**.GetComponent<Rigidbody>().velocity= Vector3.zero;

PPS:还有注意代码里在单例类里面初始化飞碟模板的方法,即需要一个继承MonoBehaviour并挂载到Main Camera的类先初始化:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PlayDarts.Com;namespace PlayDarts.Com {public class DartFactory : System.Object {private static DartFactory instance;private List<GameObject> usingDartList = new List<GameObject>();   //正在使用的飞镖listprivate List<GameObject> unusedDartList = new List<GameObject>();  //没有使用的飞镖listprivate GameObject dartItem;public static DartFactory getInstance() {if (instance == null)instance = new DartFactory();return instance;}//提供飞镖public GameObject getDart() {if (unusedDartList.Count == 0) {    //没有存储飞镖GameObject newDart = Camera.Instantiate(dartItem);usingDartList.Add(newDart);return newDart;}else {                      //有存储飞镖GameObject oldDart = unusedDartList[0];unusedDartList.RemoveAt(0);oldDart.SetActive(true);usingDartList.Add(oldDart);return oldDart;}}//update()检测飞镖落地,回收。此方法由GameModel的update()方法触发public void detectReuseDarts() {for (int i = 0; i < usingDartList.Count; i++) {if (usingDartList[i].transform.position.y <= -8) {usingDartList[i].GetComponent<Rigidbody>().velocity = Vector3.zero;  //很重要usingDartList[i].SetActive(false);unusedDartList.Add(usingDartList[i]);usingDartList.Remove(usingDartList[i]);i--;MainSceneController.getInstance().subScore();  //打不中,扣分}}}//飞镖被击中,回收public void ReuseWhenDartBeingStruck(GameObject StruckDart) {StruckDart.GetComponent<Rigidbody>().velocity = Vector3.zero;  //很重要StruckDart.SetActive(false);unusedDartList.Add(StruckDart);usingDartList.Remove(StruckDart);}//告知是否正在发射飞镖:若是则不能重复发射public bool isLaunching() {return (usingDartList.Count > 0);}//初始化dartItempublic void initItems(GameObject _dartItem) {dartItem = _dartItem;}}
}public class DartFactoryBC : MonoBehaviour {public GameObject dartItem;void Awake() {DartFactory.getInstance().initItems(dartItem);}
}

4、GameModel.cs:

此类是MainSceneController的子对象,首先负责生成游戏场景需要的GameObject,比如地面、飞碟发射器、爆炸粒子。有两个主要方法是:获取飞碟并发射;根据鼠标位置检测是否击中飞碟。

(1) 发射飞碟:由于某Round可能发射多个飞碟,但是发射位置都在同一点,若同时发射,几个飞碟由于自身的刚体rigidbody作用和碰撞体collider作用,互相撞击,导致发射轨迹有偏差。所以我采用了协程Coroutine来实现延时错开发射(Coroutine是个特别好用的东西,有时候做多线程游戏开发时可以做线程用,建议自己网上学习一下)。另外,对于飞碟的轨迹我没有用数学方法去算啊_(:зゝ∠)_,我是先给飞碟加了rigidbody刚体,即加了重力,发射的时候直接加一个带方向的Impulse的冲力就好了(具体见下面代码)

(2) 击打飞碟:根据课上说的射线就好了。击中加分并回收飞碟,同时释放爆炸粒子效果。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PlayDarts.Com;public class GameModel : MonoBehaviour {public GameObject PlaneItem, LauncherItem, ExplosionItem;public Material greenMat, redMat, blueMat;private GameObject plane, launcher, explosion;private MainSceneController scene;private const float LAUNCH_GAP = 0.1f;void Start () {scene = MainSceneController.getInstance();scene.setGameModel(this);plane = Instantiate(PlaneItem);launcher = Instantiate(LauncherItem);explosion = Instantiate(ExplosionItem);}void Update () {DartFactory.getInstance().detectReuseDarts();}//发射飞镖public void launchDarts() {int roundNum = scene.getRoundNum();if (!DartFactory.getInstance().isLaunching())StartCoroutine(launchDartsWithGapTime(roundNum));}//每个飞镖发射之间相差一段时间IEnumerator launchDartsWithGapTime(int roundNum) {for (int i = 0; i < roundNum; i++) {GameObject dart = DartFactory.getInstance().getDart();dart.transform.position = launcher.transform.position;dart.GetComponent<MeshRenderer>().material = getMaterial(roundNum);Vector3 force = getRandomForce();dart.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse);yield return new WaitForSeconds(LAUNCH_GAP);}}Vector3 getRandomForce() {int x = Random.Range(-30, 31);int y = Random.Range(30, 41);int z = Random.Range(20, 31);return new Vector3(x, y, z);}//击打飞镖public void strikeTheDart(Vector3 mousePos) {Ray ray = Camera.main.ScreenPointToRay(mousePos);RaycastHit hit;if (Physics.Raycast(ray, out hit)) {if (hit.collider.gameObject.tag.Equals("Dart")) {createExplosion(hit.collider.gameObject.transform.position);scene.addScore();DartFactory.getInstance().ReuseWhenDartBeingStruck(hit.collider.gameObject);}}}void createExplosion(Vector3 position) {explosion.transform.position = position;explosion.GetComponent<ParticleSystem>().GetComponent<Renderer>().material =getMaterial(scene.getRoundNum());explosion.GetComponent<ParticleSystem>().Play();}Material getMaterial(int roundNum) {switch (roundNum % 3) {case 0:return redMat;case 1:return greenMat;case 2:return blueMat;default:return redMat;}}
}

5、GameStatus.cs:

此类也作为MainSceneController的子对象,同样需要先生成Round数、score数、提示文字。负责管理所有游戏状态,如round数、score得分、提示文字等。加分后都会判断是否满分,如果满分则改变Round数、score数,提示Round x。

PS:这里需要注意的是提示Round x会一段时间后消失,也是使用了上面提到的Coroutine。即延迟一段时间后,使文字消失。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PlayDarts.Com;
using UnityEngine.UI;public class GameStatus : MonoBehaviour {public GameObject canvasItem, roundTextItem, scoreTextItem, TipsTextItem;private int roundNum = 1;private int score = 0;private const float TIPS_TEXT_SHOW_TIME = 0.8f;private GameObject canvas, roundText, scoreText, TipsText;private MainSceneController scene;void Start () {scene = MainSceneController.getInstance();scene.setGameStatus(this);canvas = Instantiate(canvasItem);roundText = Instantiate(roundTextItem, canvas.transform);roundText.transform.Translate(canvas.transform.position);roundText.GetComponent<Text>().text = "Round: " + roundNum;scoreText = Instantiate(scoreTextItem, canvas.transform);scoreText.transform.Translate(canvas.transform.position);scoreText.GetComponent<Text>().text = "Score:  " + score + " / " + (roundNum * 100);TipsText = Instantiate(TipsTextItem, canvas.transform);TipsText.transform.Translate(canvas.transform.position);showTipsText();}void Update () {}public int getRoundNum() {return roundNum;}void addRoundNum() {roundNum++;roundText.GetComponent<Text>().text = "Round: " + roundNum;}public int getScore() {return score;}//得分,+100public void addScore() {score += 100;scoreText.GetComponent<Text>().text = "Score:  " + score + " / " + (roundNum * 100);checkScore();}//扣分,-100public void subScore() {score = score >= 100 ? score - 100 : 0;scoreText.GetComponent<Text>().text = "Score:  " + score + " / " + (roundNum * 100);}//检测分数是否已满void checkScore() {if (score >= roundNum * 100) {  //可以下一关addRoundNum();resetScore();showTipsText();}}void resetScore() {score = 0;scoreText.GetComponent<Text>().text = "Score:  " + score + " / " + (roundNum * 100);}void showTipsText() {TipsText.GetComponent<Text>().text = "Round " + roundNum + " !";TipsText.SetActive(true);StartCoroutine(waitForSomeAndDisappearTipsText());}IEnumerator waitForSomeAndDisappearTipsText() {yield return new WaitForSeconds(TIPS_TEXT_SHOW_TIME);TipsText.SetActive(false);}
}

操作方法:

1、先在Assets文件夹下创建一个Materials文件夹,装材质Material。创建红、绿、蓝、黄4种颜色的材质(用在飞碟和发射器上)

  

2、Assets文件夹下创建Resources文件夹,里面创建Prefabs文件夹,装预设prefab。创建以下预设:

(1) 飞碟Dart:Hierarchy栏里,Create->3D Object->Cylinder。然后修改成以下属性:注意添加并修改tag;另外需要添加Rigidbody刚体(产生重力)。最后拖到Prefabs文件夹。

(2) 地板MyPlane:Create->3D Object->Plane。然后修改成以下属性,最后拖到Prefabs文件夹。

(3) 发射器launcher:Create->3D Object->Cylinder。然后修改成以下属性:注意添加刚在Materials文件夹里创建的黄色材质。最后拖到Prefabs文件夹。

(4) 画板Canvas:Create->UI->Canvas。拖下来

(5) 回合文字Round Text、得分文字Score Text、提示文字Tips Text:Create->UI->Text。修改,然后拖下来。

   

(6) 爆炸粒子效果Explosion:Create->Particle System。修改,拖下来:

预设终于弄完_(:зゝ∠)_

3、Assets下创建Scripts文件夹,装代码文件。然后单击每个cs文件,看右上角,把预设啊、材料啊那些放进去:

     

(注意一定要放完整哦!)

4、最后就是把脚本啊那些拖到合适位置了:创建一个空对象Empty。UserInterface.cs挂载到Main Camera上;DartFactoryBC.cs、GameModel.cs、GameStatus.cs挂载到Empty上

    

5、最后还有很重要的一个步骤哦!因为现在的效果重力下降很缓慢,不真实。所以我们要修改重力加速度,使飞碟发射效果更真实!步骤如下:菜单栏Edit->Project Settings->Physics。Y那里原来是-9.81改为-20 就好啦。

终于搞定!那么开始游戏吧!!!

[Unity3D课堂作业] 打飞碟 PlayDarts相关推荐

  1. [Unity3D课堂作业] Priests and Devils 牧师与恶魔

    #感觉这门课作业不提前写真搞不定啊_(:зゝ∠)_ #文末有全部代码以及操作方法 先把游戏效果po一下吧(白色胶囊体代表牧师.红色胶囊体代表魔鬼.蓝色代表船.两条白色圆柱体代表两岸): 这次作业与TA ...

  2. [Unity3D课堂作业] 改进版:Priests and Devils 牧师与恶魔

    还是先po一下效果咯~~ 这次作业其实就是:将上次作业在Update()函数一帧帧改位置控制运动,改为类似cocos2d的一个Action方法控制物体自己运动,也就是所谓的面向对象的设计.但其实本质上 ...

  3. latex 小于_一份菜鸡的Latex课堂作业works--(ii)

    开学了,每天的课堂作业接踵而来.这是一份包含插入代码块和图片的作业. 先来一波图 documentclass{article} usepackage[UTF8]{ctex} usepackage{fa ...

  4. 软件工程概论课堂作业3

    题目:返回一个整数数组中最大子数组的和 要求: 输入一个一维整形数组,数组里有正数也有负数. 一维数组首尾相接,象个一条首尾相接带子一样. 数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个 ...

  5. 高级软件测试技术17秋第1次课堂作业小结

    第1次课堂作业小结 1.      作业要求 2017年11月13日,周一,在课堂上布置了两个作业,其中,针对每班1-3组以及7班学号前15名同学安排的是编程作业,要求现场限时(15分钟内)根据指定的 ...

  6. 至诚学院MATLAB第四次,MATLAB 第二次实验课课堂作业(4学时)

    MATLAB 第二次实验课课堂作业(4学时) 注:1)此课堂作业作为本课程结业成绩的重要依据,请同学们认真.独立完成,不得抄袭. 2)请在授课教师规定的时间内完成: 3)完成作业后,请以word格式保 ...

  7. 课堂作业-1成绩汇总

    课堂作业-1成绩汇总 学号 姓名 作业标题 作业地址 提交日期 分数 113120180135 周萌 第一次班级作业 https://www.cnblogs.com/AwakenZed/p/10535 ...

  8. 机器学习作业(第十八次课堂作业)

    机器学习作业(第十八次课堂作业) 猜想 对于上述问题, 我首先认为是数据特殊导致. 编程证明 反复更改数据集.减少数据偶然性. 结果 事实是,无论如何更改数据集, sklearn都只显示数据 f1-s ...

  9. 北科大matlab期末考试,MATLAB 第一次实验课课堂作业

    MATLAB 第一次实验课课堂作业(4学时) 姓名 注:1)此课堂作业作为本课程结业成绩的重要依据,请同学们认真.独立完成,不得抄袭. 2)请在授课教师规定的时间内完成: 3)完成作业后,请以word ...

最新文章

  1. Java学习总结:5
  2. 「AI白身境」搞计算机视觉必备的OpenCV入门基础
  3. 零基础学编程学java还是python-零基础学编程,Java和Python你pick谁?
  4. 腾讯云IoT全栈方案助力智慧交通基建,详解四大重点与两个案例
  5. Google云服务降价,整合持续集成工具,支持Windows和托管虚拟机
  6. fixed 语句(C# 参考)
  7. HDU 4403 A very hard Aoshu problem DFS
  8. redis的多路复用是什么鬼
  9. 已知数组存放一批QQ号码,QQ号码最长为11位,最短为5位String[] strs = {“12345“,“67891“,“12347809933“,“98765432102“,“67891“,“1
  10. jvm gc监控分析常用命令
  11. Android 匿名共享内存C接口分析
  12. 从零开始学 Web 之 jQuery(二)获取和操作元素的属性
  13. 云计算中网络基础知识(升级版)
  14. WPF程序支持多国语言
  15. (JAVA)基于Socket的TCP和UDP编程(第一章)
  16. MacBook系统升级问题
  17. 纯电动汽车架构设计(一) :电动车架构设计核心与前悬架选择
  18. explain的使用及详解
  19. 美团外卖红包,商超生鲜红包,饿了么红包天天领,果蔬抢特价,大额满减券,返利优惠券源代码
  20. 编程思想 定义过滤的方式解耦

热门文章

  1. 设置属性setter方法
  2. C++实现int转char*和char*转int
  3. 拒做小白鼠 今年的你没必要买一部5G手机
  4. matlab ubound,关于VB调用MATLAB函数,出现类型不匹配
  5. 夫妻之间的情与理和哄与捧
  6. 环信PaaS+SaaS齐头并进,打造最具生命力企业服务
  7. catia刨面命令_3.3.7.1-Catia修饰之移除面命令
  8. 案例:Oracle报错ASM磁盘组不存在或没有mount
  9. word在刷全文格式的时候,图片显示不全
  10. Flutter:究竟是大势所趋还是昙花一现?