Unity游戏设计之飞碟游戏

编制一个射飞碟游戏。

具体要求如下:

1 假设有一支枪在摄像机位置(0,1,-10),在(0,0,0-10-20)放置三个小球作为距离标记,调整视角直到小球在下中部

2 将鼠标所在平面坐标,转换为子弹(球体)射出的角度方向。子弹使用物理引擎,初速度恒定。(U3d 坐标变换: http://www.cnblogs.com/tekkaman/p/3809409.html

Vector3 mp = Input.mousePosition; //get Screen Position

print (mp.ToString());

Vector3 mp1 = cam.camera.ScreenToViewportPoint (mp);

mp1.z = 10; //距摄像头 10 位置立面

mp1 = cam.camera.ViewportToWorldPoint (mp1);

print (mp1.ToString());

3 游戏要分多个 round , 飞碟数量每个 round 都是 n 个,但色彩,大小;发射位置,速度,角度,每次发射数量按预定规则变化。

4 用户按空格后,321倒数3秒,飞碟飞出(物理引擎控制),点击鼠标,子弹飞出。飞碟落地,或被击中,则准备下一次射击。

5 以下是一些技术要求:

? 子弹仅需要一个,不用时处于 deactive 状态

? 飞碟用一个带缓存的工厂生产,template 中放置预制的飞碟对象

? 程序类图设计大致如下:

具体实现:

脚本实现子弹射击

脚本挂在在摄像机上

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

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

完成飞碟工厂

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

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Com.Mygame;
namespace Com.Mygame {
	public class DiskFactory: System.Object {
		private static DiskFactory _instance;
		private static List<GameObject> diskList;
		public GameObject diskPrefab;
		public static DiskFactory getInstance() {
			if (_instance == null) {
				_instance = new DiskFactory();
				diskList = new List<GameObject>();
			}
			return _instance;
		}
		// 获取可用飞碟id
		public int getDisk() {
			for (int i = 0; i < diskList.Count; ++i) {
				if (!diskList[i].activeInHierarchy) {
					return i;   // 飞碟空闲
				}
			}
			// 无空闲飞碟,则实例新的飞碟预设
			diskList.Add(GameObject.Instantiate(diskPrefab) as GameObject);
			return diskList.Count-1;
		}
		// 获取飞碟对象
		public GameObject getDiskObject(int id) {
			if (id >= 0 && id < diskList.Count) {
				return diskList[id];
			}
			return null;
		}
		// 回收飞碟
		public void free(int id) {
			if (id >= 0 && id < diskList.Count) {
				// 重置飞碟速度
				diskList[id].GetComponent<Rigidbody>().velocity = Vector3.zero;
				// 重置飞碟大小
				diskList[id].transform.localScale = diskPrefab.transform.localScale;
				diskList[id].SetActive(false);
			}
		}
	}
}
public class DiskFactoryBaseCode : MonoBehaviour {
	public GameObject disk;
	void Awake () {
		// 初始化预设对象
		DiskFactory.getInstance().diskPrefab = disk;
	}
}

完成游戏场景

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

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Com.Mygame;
public class GameModel : MonoBehaviour {
	public float countDown = 3f;
	public float timeToEmit;
	private bool counting;
	private bool shooting;
	public bool isCounting() {
		return counting;
	}
	public bool isShooting() {
		return shooting;
	}
	private List<GameObject> disks = new List<GameObject> ();
	private List<int> diskIds = new List<int> ();
	private int diskScale;
	private Color diskColor;
	private Vector3 emitPosition;
	private Vector3 emitDirection;
	private float emitSpeed;
	private int emitNumber;
	private bool emitEnable;
	private SceneController scene;
	void Awake() {
		scene = SceneController.getInstance ();
		scene.setGameModel (this);
	}
	public void setting(int scale, Color color, Vector3 emitPos, Vector3 emitDir, float speed, int num) {
		diskScale = scale;
		diskColor = color;
		emitPosition = emitPos;
		emitDirection = emitDir;
		emitSpeed = speed;
		emitNumber = num;
	}
	public void prepareToEmitDisk() {
		if (!counting && !shooting) {
			timeToEmit = countDown;
			emitEnable = true;
		}
	}
	void emitDisks() {
		for (int i = 0; i < emitNumber; i++) {
			diskIds.Add (DiskFactory.getInstance ().getDisk ());
			disks.Add (DiskFactory.getInstance ().getDiskObject (diskIds [i]));
			disks [i].transform.localScale *= diskScale;
			disks [i].GetComponent<Renderer> ().material.color = diskColor;
			disks [i].transform.position = new Vector3 (emitPosition.x, emitPosition.y + i, emitPosition.z);
			disks [i].SetActive (true);
			disks [i].GetComponent<Rigidbody> ().AddForce (emitDirection * Random.Range (emitSpeed * 5, emitSpeed * 10) / 10, ForceMode.Impulse);
		}
	}
	void freeDisk(int i) {
		DiskFactory.getInstance ().free (diskIds [i]);
		disks.RemoveAt (i);
		diskIds.RemoveAt (i);
	}
	void FixedUpdate() {
		if (timeToEmit > 0) {
			counting = true;
			timeToEmit -= Time.deltaTime;
		} else {
			counting = false;
			if (emitEnable) {
				emitDisks ();
				emitEnable = false;
				shooting = true;
			}
		}
	}

	// Use this for initialization
	void Start () {

	}

	// Update is called once per frame
	void Update () {
		for (int i = 0; i < disks.Count; i++) {
			if (!disks [i].activeInHierarchy) {
				scene.getJudge ().scoreADisk ();
				freeDisk (i);
			} else if (disks [i].transform.position.y < 0) {
				scene.getJudge ().failADisk ();
				freeDisk (i);
			}
		}
		if (disks.Count == 0) {
			shooting = false;
		}
	}
}

场景控制器

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

using UnityEngine;
using System.Collections;
using Com.Mygame;
namespace Com.Mygame {
	// Com.Mygame内添加

	public interface IUserInterface {
		void emitDisk();
	}
	public interface IQueryStatus {
		bool isCounting();
		bool isShooting();
		int getRound();
		int getPoint();
		int getEmitTime();
	}
	public class SceneController : System.Object, IQueryStatus, IUserInterface {
		private static SceneController _instance;
		private GameModel _gameModel;
		private SceneControllerBaseCode _baseCode;
		private int _round;
		private int _point;
		public static SceneController getInstance() {
			if (_instance == null) {
				_instance = new SceneController ();
			}
			return _instance;
		}
		public void setSceneControllerBaseCode (SceneControllerBaseCode obj) {
			_baseCode = obj;
		}
		internal SceneControllerBaseCode getSceneControllerBC() {
			return _baseCode;
		}
		public void setGameModel(GameModel obj) {
			_gameModel = obj;
		}
		// 当前程序或派生类可用
		internal GameModel getGameModel() {
			return _gameModel;
		}
		public void emitDisk() {
			_gameModel.prepareToEmitDisk ();
		}
		public bool isCounting() {
			return _gameModel.isCounting ();
		}
		public bool isShooting() {
			return _gameModel.isShooting ();
		}
		public int getRound() {
			return _round;
		}
		public int getPoint() {
			return _point;
		}
		public int getEmitTime() {
			return (int)_gameModel.timeToEmit + 1;
		}
		public void setPoint(int point) {
			_point = point;
		}
		public void nextRound() {
			_point = 0;
		}
	}
}
public class SceneControllerBaseCode : MonoBehaviour {
	private Color color;
	private Vector3 emitPos;
	private Vector3 emitDir;
	private float speed;
	void Awake() {
		SceneController.getInstance().setSceneControllerBaseCode(this);
	}
	void Start() {
		color = Color.green;
		emitPos = new Vector3(-2.5f, 0.2f, -5f);
		emitDir = new Vector3(24.5f, 40.0f, 67f);
		speed = 4;
		SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 1);
	}
}

完善UserInterface

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using Com.Mygame;
public class UserInterface : MonoBehaviour {
	public Text mainText;
	public Text scoreText;
	public Text roundText;
	private int round;
	public GameObject bullet;
	public ParticleSystem explosion;
	public float fireRate = .25f;
	public float speed = 500f;
	private float nextFireTime;
	private IUserInterface userInt;
	private IQueryStatus queryInt;
	// Use this for initialization
	void Start () {
		bullet = GameObject.Instantiate (bullet) as GameObject;
		explosion = GameObject.Instantiate (explosion) as ParticleSystem;
		userInt = SceneController.getInstance () as IUserInterface;
		queryInt = SceneController.getInstance () as IQueryStatus;
	}

	// Update is called once per frame
	void Update () {
		if (queryInt.isCounting ()) {
			mainText.text = ((int)queryInt.getEmitTime ()).ToString ();
		} else {
			if (Input.GetKeyDown (KeyCode.Space)) {
				userInt.emitDisk ();
			}
			if (queryInt.isShooting ()) {
				mainText.text = " ";
			} else {
				mainText.text = "Press space";
			}
			if (queryInt.isShooting() && Input.GetMouseButtonDown (0) && Time.time > nextFireTime) {
				nextFireTime = Time.time + fireRate;
				Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
				bullet.GetComponent<Rigidbody> ().velocity = Vector3.zero;
				bullet.transform.position = transform.position;
				bullet.GetComponent<Rigidbody> ().AddForce (ray.direction * speed, ForceMode.Impulse);
				RaycastHit hit;
				if (Physics.Raycast (ray, out hit) && hit.collider.gameObject.tag == "Disk") {
					explosion.transform.position = hit.collider.gameObject.transform.position;
					explosion.GetComponent<Renderer> ().material.color = hit.collider.gameObject.GetComponent<Renderer> ().material.color;
					explosion.Play ();
					hit.collider.gameObject.SetActive (false);
				}
			}
		}
		roundText.text = " Round: " + queryInt.getRound ().ToString ();
		scoreText.text = " Score: " + queryInt.getPoint ().ToString ();
		if (round != queryInt.getRound ()) {
			round = queryInt.getRound ();
			mainText.text = "Round: " + round.ToString() + "!";
		}
	}
}

补充游戏规则–Judge计分系统

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

using UnityEngine;
using System.Collections;
using Com.Mygame;  

public class Judge : MonoBehaviour {
	public int oneDiskScore = 10;
	public int oneDiskFail = 10;
	public int disksToWin = 4;  

	private SceneController scene;  

	void Awake() {
		scene = SceneController.getInstance();
		scene.setJudge(this);
	}  

	void Start() {
		scene.nextRound();  // 默认开始第一关
	}  

	// 击中飞碟得分
	public void scoreADisk() {
		scene.setPoint(scene.getPoint() + oneDiskScore);
		if (scene.getPoint() == disksToWin*oneDiskScore) {
			scene.nextRound();
		}
	}  

	// 掉落飞碟失分
	public void failADisk() {
		scene.setPoint(scene.getPoint() - oneDiskFail);
	}
}

在场景控制器中添加相应裁判的代码

// Com.Mygame内添加
	public interface IjudgeEvent {
		void nextRound();
		void setPoint(int point);
	}
// 类内部添加并且类继承IjudgeEvent
		private Judge _judge;
		public void setJudge(Judge obj) { _judge = obj; }
		internal Judge getJudge() { return _judge; }

在GameModel中调用裁判计分功能

void Update () {
		for (int i = 0; i < disks.Count; i++) {
			if (!disks [i].activeInHierarchy) {
				scene.getJudge ().scoreADisk ();
				freeDisk (i);
			} else if (disks [i].transform.position.y < 0) {
				scene.getJudge ().failADisk ();
				freeDisk (i);
			}
		}
		if (disks.Count == 0) {
			shooting = false;
		}
	}

设置关卡

在SceneControllerBaseCode中添加关卡信息,通过添加loadRoundData来完成每个关卡对游戏对象属性的设置。

public void loadRoundData(int round) {
		switch(round) {
		case 1:     // 第一关
			color = Color.green;
			emitPos = new Vector3(-2.5f, 0.2f, -5f);
			emitDir = new Vector3(24.5f, 40.0f, 67f);
			speed = 4;
			SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 1);
			break;
		case 2:     // 第二关
			color = Color.red;
			emitPos = new Vector3(2.5f, 0.2f, -5f);
			emitDir = new Vector3(-24.5f, 35.0f, 67f);
			speed = 4;
			SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 2);
			break;
		case 3:     // 第二关
			color = Color.yellow;
			emitPos = new Vector3(2.5f, 0.2f, -5f);
			emitDir = new Vector3(-24.5f, 35.0f, 67f);
			speed = 4;
			SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 3);
			break;
		}
	}

游戏效果:

参考链接:

http://blog.csdn.net/simba_scorpio/article/details/51051241

时间: 2024-10-14 11:44:50

Unity游戏设计之飞碟游戏的相关文章

独立开发游戏越来越容易:Unity 发布旗下的最新游戏引擎 Unity 5,依然有免费版(转)

独立开发者开发游戏正变得越来越容易,因为在游戏设计中很多吃力不讨好的工作可以直接采用像 Epic Games 或 Unity Technologies 这样的游戏引擎来解决.而这几天,游戏引擎商们先后宣布,旗下产品将以更廉价甚至是免费的方式提供给游戏开发者们,开发游戏的门槛又进一步降低了. 先是 Epic Games 在周一通过官方博客宣布旗下 Unreal Engine 4 (虚幻引擎 4)即日起免费提供给 所有开发者使用.只有当开发者利用 Unreal Engine 4 所制作的游戏每个季度

Unity3D 学习笔记 - Garen Pick the Balls 捡球小游戏设计

注:本游戏开发环境为Unity 3D v4.6 老师说这星期作业比较简单,所以我决定写得规整一些. 开发时间:8小时 游戏要求: 小游戏争分夺秒:随机位置生成七个球,控制主角在地图拾取七个球,十秒钟内必须完成,否则失败具体要求: 1 随机位置在地图上生成七个球(球可以用系统自带的球体) 2 用键盘控制本课程中的角色移动,鼠标左键攻击到达打击帧时,拾取碰到的球. 3 通过Time类显示每次拾取球花费的时间 经过试验,十秒根本捡不完= =,15秒还可以... 首先来看看帅气万分的主角Garen哥 .

C语言小游戏设计报告

课程设计名称:贪吃蛇小游戏 专业班级:计科15-2 学号:150809229 姓名:XXX 一.设计目标 通过设计,培养学生对电脑的动手能力,使学生巩固<C语言程序设计>课程学习的内容,掌握编写程序的基本方法,强化对其的动手能力,可以独自完成程序的编写. 二.设计内容和要求 设计内容 编写贪吃蛇的小游戏,使其可以成功运行并且操作玩耍. 设计要求 1)源程序要有适当的注释,使程序便于阅读. 2)要有程序运行结果作为依据 三.程序流程 1.编写地图 运用函数,数组编写地图 2.相同的方法把蛇添加进

花了一天读了《游戏设计快乐之道》

关于大脑及学习 一个弹了二十年吉他的人,可以很快学会其他乐器.这其中的关键不是肌肉记忆,因为肌肉没有记忆,而是神经.而是大脑.游戏的作用,也就在此显明了 你应当去玩你不喜欢的游戏,以及那些不吸引你的游戏.如此才能弥补自己的缺点 成功游戏的关键 一个拥有这些元素的游戏,堪称好游戏. 准备-如,战前疗伤 空间感-比如,象棋的棋盘.桥牌的座次 坚固的核心机制-比如,象棋,一次移动一枚棋子 一系列挑战-但挑战不可改变规则 解决问题的能力(给玩家多重选择) 运用能力所需的技能-诸如:资源管理.时间控制.敏

unity3f游戏开发之游戏设计中运营重用体系

游戏设计中,运用重用体系重用资源包括有以下几个设计目的: 1).方便玩家识别 为了方便玩家识别某一类游戏中的要素,而采用的设计目的.如某种某种动物的皮毛的道具图标.为了区分不同,除了基本图素相同外,不同之处只是以变换颜色和更改名称加以区分,这样可以方便玩家快速识别图标. 2).降低客户端的容量 游戏中最占用硬盘空间的,其实是大量的资源,如模型文件.贴图等美术资源文件.为了降低客户端的容量,开发者除了要采用压包技术进行资源压包外,在设计的过程中,设计师还要考虑到客户端容量大小的问题(特别是某些2D

MMORGP大型游戏设计与开发(客户端架构 part1 of vegine)

重写有些核心接口的时候,其实遇到了许多的问题,甚至一度的想过要放弃,但是最终还是坚持了下来.在客户端提供的这些接口中,可以清晰的看到客户端所依赖的各种模块的支持,以及各自之间的一些关联.下面只是介绍了vengine(微引擎)接口的基础模块框架,所谓的接口即对象设计中常见的Interface,为一个框架提供了清晰的规范支持. VEGINE FRAMEWORK 功能实现 该接口已全部实现,具体的实例只需要继承接口封装即可.上图只为简单的模块介绍,其实每个接口都有每个接口其特别的用处,这一点会在接下的

游戏设计中的道德

最近一直在思考一些关于游戏设计方面的问题.入行已经两年了,以前跟所有刚入行的朋友一样,一直想着怎么从自己设计的游戏中赚钱.怎么让玩家为我的劳动成果买单.当然,我并不是说这样不好,因为只要形成一个良性的循环,优秀的游戏才会持续不断的出现,君不见,几年前国内还是有很多优秀的单机游戏的,但是由于破解的存在,以及中国人在几年前还有着"我都花钱买了电脑了,难道软件还要钱吗?更别提游戏了."这些众所周知的原因,现在中国的市场上已经很难看到新的优秀单机游戏的出现了,还好手游平台拓宽了国内的游戏市场,

白--留白与游戏设计

我所谓之白,非颜色之白,乃设计之白,思想之白也. 我对白的理解得益于对日本传统艺术与现代设计的些许了解,前几日偶得日本设计业前辈原研哉先生的一本书<白>.阅后颇有所思,不禁联想至游戏行业.白,也可称间或空.是日本艺术创造中一直沿承的一种创作思维.空不是无,而是留给人们想象的空间,以自我的感受体会填充空,仿佛自己与艺术品融为一体,这也算是艺术至高境界之一了. 举两例也许可使白之意更为真切.其一,日本桃山时代画家长谷川等伯的<松林图>,这幅水墨作品中,稀疏错落的松木三三两两的布局在白色

怎样设计一个好玩的游戏——游戏设计的艺术

前言: 一个好玩的游戏,就是要让玩家在玩游戏的过程中感到愉快的游戏体验.游戏品质一般可以分为三个层次:普通.精品.经典. 仅仅要游戏能赚钱的好游戏可算是精品游戏,而经典的游戏,必定有深厚的游戏内涵,甚至可以从这个游戏产生周边产品:比如从游戏改编电影.玩具等等,有额外附加值.一个游戏的好坏由多方面决定,这里我们仅仅关注趣味性.其实趣味性是一个游戏最重要的部分.游戏画面优美程度,或者玩家可玩时间,或者角色的主角的乳房部位多边形数目,这些都是其次. 1.给予玩家目标(让玩家入局) 游戏開始的时候,我们