当前位置:   article > 正文

Unity3D学习笔记(6)—— 飞碟射击游戏_射击飞碟训练笔记怎么写

射击飞碟训练笔记怎么写

游戏规则:

         游戏分多个回合,每个回合有N个飞碟,玩家按空格后,321倒数3秒,飞碟飞出,点击鼠标,子弹飞出。飞碟落地或被击中,则准备下一次射击。每回合飞碟的大小、颜色、发射位置、发射角度、每次发射的数量可以变化。

游戏效果:


        录了好几次,每次飞碟都打不中啊!_(:з」∠)_

游戏类图:


代码说明:

一、飞碟回收工厂类(DiskFactory)

        创建新的命名空间Com.Mygame,单例类DiskFactory和SceneController都定义其中。飞碟工厂类的目的是管理飞碟实例,同时对外屏蔽飞碟实例的的提取和回收细节,对于需要使用飞碟的其他对象,只能使用工厂类提供的3个函数,分别是getDisk()、getDiskObject()、free()。

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using Com.Mygame;
  5. namespace Com.Mygame {
  6. public class DiskFactory : System.Object {
  7. private static DiskFactory _instance;
  8. private static List<GameObject> diskList; // 飞碟队列
  9. public GameObject diskTemplate; // 预设对象
  10. public static DiskFactory getInstance() {
  11. if (_instance == null) {
  12. _instance = new DiskFactory();
  13. diskList = new List<GameObject>();
  14. }
  15. return _instance;
  16. }
  17. // 获取可用飞碟id
  18. public int getDisk() {
  19. for (int i = 0; i < diskList.Count; ++i) {
  20. if (!diskList[i].activeInHierarchy) {
  21. return i; // 飞碟空闲
  22. }
  23. }
  24. // 无空闲飞碟,则实例新的飞碟预设
  25. diskList.Add(GameObject.Instantiate(diskTemplate) as GameObject);
  26. return diskList.Count-1;
  27. }
  28. // 获取飞碟对象
  29. public GameObject getDiskObject(int id) {
  30. if (id > -1 && id < diskList.Count) {
  31. return diskList[id];
  32. }
  33. return null;
  34. }
  35. // 回收飞碟
  36. public void free(int id) {
  37. if (id > -1 && id < diskList.Count) {
  38. // 重置飞碟速度
  39. diskList[id].rigidbody.velocity = Vector3.zero;
  40. // 重置飞碟大小
  41. diskList[id].transform.localScale = diskTemplate.transform.localScale;
  42. diskList[id].SetActive(false);
  43. }
  44. }
  45. }
  46. }
  47. public class DiskFactoryBC : MonoBehaviour {
  48. public GameObject disk;
  49. void Awake () {
  50. // 初始化预设对象
  51. DiskFactory.getInstance().diskTemplate = disk;
  52. }
  53. }

有几点需要注意:

1)  当且仅当请求队列里的所有对象都在被使用(飞碟在场景中活跃)时,才会发生实例化,此时队列会变长。

2)  getDisk返回的是可用飞碟在队列里的index,这是为了方便free。

3)  free通过index找到飞碟在队列中的位置,并将飞碟设置为不活跃的。注意,由于飞碟使用了刚体组件,回收时需要把速度重置,并且大小可能会被改变,也应该重置。


二、用户界面类(UserInterface)

        用户界面有两大功能,一是处理用户键入,二是显示得分和倒计时等。用户键入有两种:鼠标左键和空格。左键发射子弹,空格发射飞碟。显示有三种:得分、回合和倒计时。

        子弹射击的思路:当用户点击鼠标时,从摄像机到鼠标创建一条射线,射线的方向即是子弹发射的方向,子弹采用刚体组件,因此发射子弹只需要给子弹施加一个力。子弹对象只有一个,下一次发射子弹时,必须改变子弹的位置(虽然有了刚体组件不建议修改transform,但也没有其它方法改变子弹位置了吧)。为了不让子弹继承上一次发射的速度,必须将子弹的速度归零重置。

        子弹的击中判断:采用射线而不是物理引擎,因为物理引擎在高速物体碰撞时经常不能百分百检测得到。

        发射飞碟的思路:调用用户接口。

        显示的思路:得分和回合直接通过查询接口获得。倒计时显示前通过查询接口判断是否正在倒计时,如果是,那么再通过查询接口获得倒计时时间。如果回合发生改变,则显示新的回合,直到用户按下空格。

  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using System.Collections;
  4. using Com.Mygame;
  5. public class UserInterface : MonoBehaviour {
  6. public Text mainText; // 显示主提示:倒计时、新回合
  7. public Text scoreText; // 显示得分
  8. public Text roundText; // 显示回合
  9. private int round; // 当前回合
  10. public GameObject bullet; // 子弹
  11. public ParticleSystem explosion; // 爆炸粒子
  12. public float fireRate = .25f; // 开枪间隔
  13. public float speed = 500f; // 子弹速度
  14. private float nextFireTime; // 下一次开枪时间
  15. private IUserInterface userInt; // 用户接口
  16. private IQueryStatus queryInt; // 查询接口
  17. void Start() {
  18. bullet = GameObject.Instantiate(bullet) as GameObject;
  19. explosion = GameObject.Instantiate(explosion) as ParticleSystem;
  20. userInt = SceneController.getInstance() as IUserInterface;
  21. queryInt = SceneController.getInstance() as IQueryStatus;
  22. }
  23. void Update () {
  24. if (queryInt.isCounting()) {
  25. // 显示倒计时
  26. mainText.text = ((int)queryInt.getEmitTime()).ToString();
  27. }
  28. else {
  29. if (Input.GetKeyDown("space")) {
  30. userInt.emitDisk(); // 发射飞碟
  31. }
  32. if (queryInt.isShooting()) {
  33. mainText.text = ""; // 射击开始,隐藏主提示
  34. }
  35. // 发射子弹
  36. if (queryInt.isShooting() && Input.GetMouseButtonDown(0) && Time.time > nextFireTime ) {
  37. nextFireTime = Time.time + fireRate;
  38. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // 摄像机到鼠标射线
  39. bullet.rigidbody.velocity = Vector3.zero; // 子弹刚体速度重置
  40. bullet.transform.position= transform.position; // 子弹从摄像机位置射出
  41. bullet.rigidbody.AddForce(ray.direction*speed, ForceMode.Impulse);
  42. RaycastHit hit;
  43. if (Physics.Raycast(ray, out hit) && hit.collider.gameObject.tag == "Disk") {
  44. // 播放爆炸粒子特效
  45. explosion.transform.position = hit.collider.gameObject.transform.position;
  46. explosion.renderer.material.color = hit.collider.gameObject.renderer.material.color;
  47. explosion.Play();
  48. // 击中飞碟设置为不活跃,自动回收
  49. hit.collider.gameObject.SetActive(false);
  50. }
  51. }
  52. }
  53. roundText.text = "Round: " + queryInt.getRound().ToString();
  54. scoreText.text = "Score: " + queryInt.getPoint().ToString();
  55. // 如果回合更新,主提示显示新回合
  56. if (round != queryInt.getRound()) {
  57. round = queryInt.getRound();
  58. mainText.text = "Round " + round.ToString() + " !";
  59. }
  60. }
  61. }

三、场景控制器类(SceneController)

        场景控制类主要实现接口定义和保存注入对象。另外它有两个私有变量round和point,分别记录游戏正在进行的回合,以及玩家目前的得分。

  1. using UnityEngine;
  2. using System.Collections;
  3. using Com.Mygame;
  4. namespace Com.Mygame {
  5. public interface IUserInterface {
  6. void emitDisk();
  7. }
  8. public interface IQueryStatus {
  9. bool isCounting();
  10. bool isShooting();
  11. int getRound();
  12. int getPoint();
  13. int getEmitTime();
  14. }
  15. public interface IJudgeEvent {
  16. void nextRound();
  17. void setPoint(int point);
  18. }
  19. public class SceneController : System.Object, IQueryStatus, IUserInterface, IJudgeEvent {
  20. private static SceneController _instance;
  21. private SceneControllerBC _baseCode;
  22. private GameModel _gameModel;
  23. private Judge _judge;
  24. private int _round;
  25. private int _point;
  26. public static SceneController getInstance() {
  27. if (_instance == null) {
  28. _instance = new SceneController();
  29. }
  30. return _instance;
  31. }
  32. public void setGameModel(GameModel obj) { _gameModel = obj; }
  33. internal GameModel getGameModel() { return _gameModel; }
  34. public void setJudge(Judge obj) { _judge = obj; }
  35. internal Judge getJudge() { return _judge; }
  36. public void setSceneControllerBC(SceneControllerBC obj) { _baseCode = obj; }
  37. internal SceneControllerBC getSceneControllerBC() { return _baseCode; }
  38. // 操作接口
  39. public void emitDisk() { _gameModel.prepareToEmitDisk(); }
  40. // 查询接口
  41. public bool isCounting() { return _gameModel.isCounting(); }
  42. public bool isShooting() { return _gameModel.isShooting(); }
  43. public int getRound() { return _round; }
  44. public int getPoint() { return _point; }
  45. public int getEmitTime() { return (int)_gameModel.timeToEmit + 1; }
  46. // 得分接口
  47. public void setPoint(int point) { _point = point; }
  48. public void nextRound() { _point = 0; _baseCode.loadRoundData(++_round); }
  49. }
  50. }

四、关卡类(SceneControllerBC)

        关卡类保存了各个round飞碟的大小、颜色、发射位置、发射角度、发射数量等信息。它只有一个函数loadRoundData(),用来初始化游戏场景。

  1. public class SceneControllerBC : MonoBehaviour {
  2. private Color color;
  3. private Vector3 emitPos;
  4. private Vector3 emitDir;
  5. private float speed;
  6. void Awake() {
  7. SceneController.getInstance().setSceneControllerBC(this);
  8. }
  9. public void loadRoundData(int round) {
  10. switch(round) {
  11. case 1: // 第一关
  12. color = Color.green;
  13. emitPos = new Vector3(-2.5f, 0.2f, -5f);
  14. emitDir = new Vector3(24.5f, 40.0f, 67f);
  15. speed = 4;
  16. SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 1);
  17. break;
  18. case 2: // 第二关
  19. color = Color.red;
  20. emitPos = new Vector3(2.5f, 0.2f, -5f);
  21. emitDir = new Vector3(-24.5f, 35.0f, 67f);
  22. speed = 4;
  23. SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 2);
  24. break;
  25. }
  26. }
  27. }

五、游戏规则类(Judge)

        游戏规则单独作为一个类,有利于日后修改。这里需要处理的规则无非就两个,得分和失分。另外,得分需要判断是否能晋级下一关。能就调用接口函数nextRound()。

  1. using UnityEngine;
  2. using System.Collections;
  3. using Com.Mygame;
  4. public class Judge : MonoBehaviour {
  5. public int oneDiskScore = 10;
  6. public int oneDiskFail = 10;
  7. public int disksToWin = 4;
  8. private SceneController scene;
  9. void Awake() {
  10. scene = SceneController.getInstance();
  11. scene.setJudge(this);
  12. }
  13. void Start() {
  14. scene.nextRound(); // 默认开始第一关
  15. }
  16. // 击中飞碟得分
  17. public void scoreADisk() {
  18. scene.setPoint(scene.getPoint() + oneDiskScore);
  19. if (scene.getPoint() == disksToWin*oneDiskScore) {
  20. scene.nextRound();
  21. }
  22. }
  23. // 掉落飞碟失分
  24. public void failADisk() {
  25. scene.setPoint(scene.getPoint() - oneDiskFail);
  26. }
  27. }

六、游戏场景类(GameModel)

        场景类是整个飞碟射击游戏的核心类,主要负责飞碟动作的处理。我是这样设计的:首先需要倒计时功能,可以通过几个整型变量和布尔变量完成。另外需要飞碟发射功能,通过setting函数保存好飞碟的发射信息,每次倒计时完成后,通过emitDisks获取飞碟对象,并通过发射信息初始化飞碟,再给飞碟一个力就可以发射了。而飞碟的回收在Update里完成,一种是飞碟被击中(飞碟不在场景中)了,需要调用Judge获得分数。另一种是飞碟在场景中,但是掉在地上了,需要调用Judge丢失分数。

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using Com.Mygame;
  5. public class GameModel : MonoBehaviour {
  6. public float countDown = 3f; // 飞碟发射倒计时总时间
  7. public float timeToEmit; // 飞碟发射倒计时剩余时间
  8. private bool counting; // 正在倒计时
  9. private bool shooting; // 正在射击
  10. public bool isCounting() { return counting; }
  11. public bool isShooting() { return shooting; }
  12. private List<GameObject> disks = new List<GameObject>(); // 发射的飞碟对象列表
  13. private List<int> diskIds = new List<int>(); // 发射的飞碟id列表
  14. private int diskScale; // 飞碟大小
  15. private Color diskColor; // 飞碟颜色
  16. private Vector3 emitPosition; // 发射位置
  17. private Vector3 emitDirection; // 发射方向
  18. private float emitSpeed; // 发射速度
  19. private int emitNumber; // 发射数量
  20. private bool emitEnable; // 允许新的发射事件
  21. private SceneController scene;
  22. void Awake () {
  23. scene = SceneController.getInstance();
  24. scene.setGameModel(this);
  25. }
  26. // 初始化场景
  27. public void setting(int scale, Color color, Vector3 emitPos, Vector3 emitDir, float speed, int num) {
  28. diskScale = scale;
  29. diskColor = color;
  30. emitPosition = emitPos;
  31. emitDirection = emitDir;
  32. emitSpeed = speed;
  33. emitNumber = num;
  34. }
  35. // 准备下一次发射
  36. public void prepareToEmitDisk() {
  37. if (!counting && !shooting) {
  38. timeToEmit = countDown;
  39. emitEnable = true;
  40. }
  41. }
  42. // 发射飞碟
  43. void emitDisks() {
  44. for (int i = 0; i < emitNumber; ++i) {
  45. diskIds.Add(DiskFactory.getInstance().getDisk());
  46. disks.Add(DiskFactory.getInstance().getDiskObject(diskIds[i]));
  47. disks[i].transform.localScale *= diskScale;
  48. disks[i].renderer.material.color = diskColor;
  49. disks[i].transform.position = new Vector3(emitPosition.x, emitPosition.y+i, emitPosition.z);
  50. disks[i].SetActive(true);
  51. disks[i].rigidbody.AddForce(emitDirection*Random.Range(emitSpeed*5, emitSpeed*10)/10, ForceMode.Impulse);
  52. }
  53. }
  54. // 回收飞碟
  55. void freeADisk(int i) {
  56. DiskFactory.getInstance().free(diskIds[i]);
  57. disks.RemoveAt(i);
  58. diskIds.RemoveAt(i);
  59. }
  60. void FixedUpdate() {
  61. if (timeToEmit > 0) {
  62. counting = true;
  63. timeToEmit -= Time.deltaTime;
  64. } else {
  65. counting = false;
  66. if (emitEnable) {
  67. emitDisks(); // 发射飞碟
  68. emitEnable = false;
  69. shooting = true;
  70. }
  71. }
  72. }
  73. void Update () {
  74. for (int i = 0; i < disks.Count; ++i) {
  75. if (!disks[i].activeInHierarchy) { // 飞碟不在场景中
  76. scene.getJudge().scoreADisk(); // 得分
  77. freeADisk(i);
  78. }
  79. else if (disks[i].transform.position.y < 0) { // 飞碟在场景中但落地
  80. scene.getJudge().failADisk(); // 失分
  81. freeADisk(i);
  82. }
  83. }
  84. if (disks.Count == 0) {
  85. shooting = false;
  86. }
  87. }
  88. }

其他说明:

        场景中预置的所有对象:

一、摄像机

        可将所有脚本挂载在摄像机上。摄像机的参数:

二、预设

        子弹(Bullet):


        飞碟(Disk):


        粒子(Particle System):


        地板材质:

三、文字

        在场景编辑器里右键创建UI元素Text,调整 text 对齐位置和文字大小即可。


四、脚本设置

        最后,将5个脚本挂载在组摄像机上,将预设拖入相应的脚本中:

        OK!游戏完成了!

        我的制作顺序是这样子的,游戏开始的时候先不忙着写脚本,把预设弄好,包括摄像机角度,粒子效果等。写脚本先写子弹射击,然后写飞碟工厂,再写游戏场景,这时候就已经有了一个小Demo可以玩了。然后添加场景控制类,完善用户界面,使得可以显示回合和分数等信息。最后写关卡类和规则类。





声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/104148
推荐阅读
相关标签
  

闽ICP备14008679号