Unity3D 从入门到放弃(四)----打飞碟
2017-03-23 14:15
239 查看
Unity3D 从入门到放弃(四)
—–打飞碟
填坑计划的万恶之源
写在开头:本来感觉应该是不会写博客的,而且也不是很擅长写 博客。但在后来,突然醒悟到,博客这个东西,实际上并不是给别人看的(感觉也不会有人看),更重要的,是给自己一个提示,一个记录。只有把原始森林中所有的危险地点全部都标记下来,下次来的时候才不会重蹈覆辙。
该说的就这么多,要赶紧填坑了。
打飞碟
程序规则:写个用鼠标打飞碟的游戏。游戏要分多个 round , 飞碟数量每个 round 都是 n 个,但色彩,大小;发射位置,速度,角度,每次发射数量可以变化。
游戏过程中,仅能创建 n 个飞碟, 且不容许初始化阶段生成任何飞碟。 飞碟线路计算请使用 mathf 类。 向下加速度 a 是常数。 飞碟被用户击中,则回收。并按你定义的规则计算分数。飞碟落地(y < c),则自动回收。
预设部分
程序中用到的对象如下:飞碟:创建一个Cylinder,将该物体的scale.y改成0.1,加入预设。
地板:新建一个Plane进行拉伸,加上贴图后即可。(PS:如果是x,y拉伸不一样的,会导致墙面变形,建议用多个Plane组合)
文本框组:在 Component的UI里新建text,并设置三个文本,分别表示当前回合数,当前分数和倒计时。
效果如下:
逻辑部分
先放上UML图:(不知道看不看得懂)场景切换思想:
运用enum Status,进行不同类在执行完动作后的回调函数中切换状态,并且利用Singleton类保持类的唯一性。
类似下面代码:
/* void Update() { if (nowState == 符合当前类执行的状态) { // 实现当前类操作 if (任务完成条件达成) { // 重置或者设置某些状态 nowState = 符合下一个类执行的状态; } } } */
实际代码
辅助类文件
BaseAction类(模板):// ======================================================== // 描 述:动作基类文件 // 作 者:hza // 创建时间:2017/04/01 14:14:33 // 版 本:v 1.0 // ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Tem.Action { public enum SSActionEventType : int { STARTED, COMPLETED } public interface ISSActionCallback { void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED, int intParam = 0, string strParam = null, Object objParam = null); } public class SSAction : ScriptableObject // 动作的基类 { public bool enable = true; public bool destory = false; public GameObject gameObject { get; set; } public Transform transform { get; set; } public ISSActionCallback callback { get; set; } public virtual void Start() { throw new System.NotImplementedException("Action Start Error!"); } public virtual void Update() { throw new System.NotImplementedException("Action Update Error!"); } } public class CCMoveToAction : SSAction { public Vector3 target; public float speed; public static CCMoveToAction GetSSAction(Vector3 _target, float _speed) { CCMoveToAction currentAction = ScriptableObject.CreateInstance<CCMoveToAction>(); currentAction.target = _target; currentAction.speed = _speed; return currentAction; } public override void Start() { } public override void Update() { this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime); if (this.transform.position == target) { this.destory = true; this.callback.SSEventAction(this); } } } public class CCBezierMoveAction : SSAction // 仅用于三次贝塞尔曲线 { public List<Vector3> vectors; public Vector3 target; public float speed; private Vector3 begin; private float t; private bool firstTime; /*private Vector3 vector2begin; private Vector3 vector2mid; private Vector3 vector2end; private Vector3 vector3begin; private Vector3 vector3end; private float timeBegin; private float timeDiff;*/ public static CCBezierMoveAction GetCCBezierMoveAction(List<Vector3> _vectors, float _speed) // vector里面最后一个值是目标位置 { CCBezierMoveAction action = ScriptableObject.CreateInstance<CCBezierMoveAction>(); action.vectors = _vectors; action.target = _vectors[_vectors.Count - 1]; action.vectors.RemoveAt(action.vectors.Count - 1); action.speed = _speed; return action; } public override void Start() // 公式写法 { //timeDiff = 0; firstTime = true; t = 0; } public override void Update() { if (firstTime) { speed = speed / Vector3.Distance(this.transform.position, target) / 1.5f; // 速度除以相对距离并除以2作为速度比 begin = this.transform.position; firstTime = false; } t += Time.deltaTime * speed; if (t > 1) t = 1; float _t = 1 - t; this.transform.position = begin * Mathf.Pow(_t, 3) + 3 * vectors[0] * Mathf.Pow(_t, 2) * t + 3 * vectors[1] * _t * Mathf.Pow(t, 2) + target * Mathf.Pow(t, 3); if (this.transform.position == target) { this.destory = true; this.callback.SSEventAction(this); } } /*public override void Update() // 正常写法 { timeDiff += Time.deltaTime; vector2begin = Vector3.Lerp(this.transform.position, vectors[0], speed * timeDiff); vector2mid = Vector3.Lerp(vectors[0], vectors[1], speed * timeDiff); vector2end = Vector3.Lerp(vectors[1], target, speed * timeDiff); // 第一次计算差值 vector3begin = Vector3.Lerp(vector2begin, vector2mid, speed * timeDiff); vector3end = Vector3.Lerp(vector2mid, vector2end, speed * timeDiff); // 第二次计算差值 this.transform.position = Vector3.Lerp(vector3begin, vector3end, speed * timeDiff); // 最后一次计算差值 if (this.transform.position == target) { this.destory = true; this.callback.SSEventAction(this); } }*/ } public class CCSequenceAction : SSAction, ISSActionCallback { public List<SSAction> sequence; public int repeat = -1; public int start = 0; public static CCSequenceAction GetSSAction(List<SSAction> _sequence, int _start = 0, int _repead = 1) { CCSequenceAction actions = ScriptableObject.CreateInstance<CCSequenceAction>(); actions.sequence = _sequence; actions.start = _start; actions.repeat = _repead; return actions; } public override void Start() { foreach (SSAction ac in sequence) { ac.gameObject = this.gameObject; ac.transform = this.transform; ac.callback = this; ac.Start(); } } public override void Update() { if (sequence.Count == 0) return; if (start < sequence.Count) sequence[start].Update(); } public void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED, int intParam = 0, string strParam = null, Object objParam = null) //通过对callback函数的调用执行下个动作 { source.destory = false; // 当前动作不能销毁(有可能执行下一次) this.start++; if (this.start >= this.sequence.Count) { this.start = 0; if (this.repeat > 0) repeat--; if (this.repeat == 0) { this.destory = true; this.callback.SSEventAction(this); } } } private void OnDestroy() { this.destory = true; } } public class SSActionManager : MonoBehaviour { private Dictionary<int, SSAction> dictionary = new Dictionary<int, SSAction>(); private List<SSAction> watingAddAction = new List<SSAction>(); private List<int> watingDelete = new List<int>(); protected void Start() { } protected void Update() { foreach (SSAction ac in watingAddAction) dictionary[ac.GetInstanceID()] = ac; watingAddAction.Clear(); // 将待加入动作加入dictionary执行 foreach (KeyValuePair<int, SSAction> dic in dictionary) { SSAction ac = dic.Value; if (ac.destory) watingDelete.Add(ac.GetInstanceID()); else if (ac.enable) ac.Update(); } // 如果要删除,加入要删除的list,否则更新 foreach (int id in watingDelete) { SSAction ac = dictionary[id]; dictionary.Remove(id); DestroyObject(ac); } watingDelete.Clear(); // 将deletelist中的动作删除 } public void runAction(GameObject gameObject, SSAction action, ISSActionCallback callback) { action.gameObject = gameObject; action.transform = gameObject.transform; action.callback = callback; watingAddAction.Add(action); action.Start(); } } }
Don’tclickme(用于帮其他文件在创建的时候添加注释):
using UnityEngine; using System.Collections; using System.IO; public class ChangeScriptTemplates : UnityEditor.AssetModificationProcessor { // 添加脚本注释模板 private static string str = "// ========================================================\r\n" +"// 描 述:\r\n" +"// 作 者:hza \r\n" +"// 创建时间:#CreateTime#\r\n" +"// 版 本:v 1.0\r\n" +"// ========================================================\r\n\n"; // 创建资源调用 public static void OnWillCreateAsset(string path) { // 只修改C#脚本 path = path.Replace(".meta", ""); if (path.EndsWith(".cs")) { string allText = str; allText += File.ReadAllText(path); // 替换字符串为系统时间 allText = allText.Replace("#CreateTime#",System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")); File.WriteAllText(path, allText); } } }
Singleton:
// ======================================================== // 描 述:这是一个单实例模板,用来引用别的继承monobehaviour的单实例类 // 作 者:hza // 创建时间:2017/03/27 00:02:13 // 版 本:v 1.0 // ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; public class Singleton<T> : MonoBehaviour where T : MonoBehaviour { // 私有static变量 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 it not!"); } } return instance; } } }
实际类文件
SceneController:// ======================================================== // 描 述:场景控制,主要控制开始结束按钮和场景启动 // 作 者:hza // 创建时间:2017/03/27 00:21:36 // 版 本:v 1.0 // ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; using My.Disk; using UnityEngine.UI; public enum Status { BEGIN, COUNTING, WATING, GAMING, OVER } public class SceneController : MonoBehaviour { public ParticleSystem boom; public Text centerText; public Text scoreText; public Text roundText; public ScoreRecorder recorder; RoundController round; public Status nowState; void Start() { nowState = Status.BEGIN; centerText.text = ""; roundText.text = ""; recorder = new ScoreRecorder(scoreText); round = Singleton<RoundController>.Instance; round.resetRound(); recorder.setDisActive(); // 初始设置 } private void OnGUI() { if (nowState == Status.BEGIN) { if (GUI.RepeatButton(new Rect(10, 10, 100, 30), "Game Rule")) GUI.TextArea(new Rect(120, 10, Screen.width / 2, 30), "点击空格开始关卡,鼠标点击射击,击中所有目标即可进行下一关"); if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 25, 100, 50), "Play Game")) { nowState = Status.WATING; recorder.resetScore(); } } else if (nowState == Status.OVER) { if (GUI.RepeatButton(new Rect(Screen.width / 2 - 50, Screen.height * 3 / 4 - 25, 100, 50), "Reset Game")) restart(); } } public void restart() { round.resetRound(); centerText.text = ""; recorder.setDisActive(); roundText.text = ""; nowState = Status.BEGIN; } }
ScoreRecorder:
// ======================================================== // 描 述:用来计算得分,加减分数后将得分面板刷新 // 作 者:hza // 创建时间:2017/03/29 21:53:48 // 版 本:v 1.0 // ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ScoreRecorder { public Text scoreText; // 计分板 private int score; // 纪录分数 public ScoreRecorder(Text _scoreText) { scoreText = _scoreText; } public void resetScore() { score = 0; } // 飞碟点击中加分 public void addScore(int addscore) { score += addscore; scoreText.text = "Score:" + score; } public void setDisActive() { scoreText.text = ""; } public void setActive() { scoreText.text = "Score:" + score; } }
RoundController:
// ======================================================== // 描 述:用于回合数控制,回合循环 // 作 者:hza // 创建时间:2017/04/02 20:22:30 // 版 本:v 1.0 // ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class RoundController : MonoBehaviour { public Text roundText; public Text centerText; // 外部引用 SceneController scene; Count count; ScoreRecorder recorder; DiskActionManager actionManager; // 类间引用 int round; // Use this for initialization void Start () { scene = Singleton<SceneController>.Instance; count = Singleton<Count>.Instance; actionManager = Singleton<DiskActionManager>.Instance; recorder = scene.recorder; } // Update is called once per frame void Update () { if (scene.nowState == Status.WATING) // 等待阶段 { centerText.text = "Round:" + round; if (Input.GetKeyDown("space")) { count.beginCount(); ResumeRecord(); scene.nowState = Status.COUNTING; // 开始计数 } } } public void runRound() { actionManager.runActionByRound(round); } public void NextRound() { round++; } public void resetRound() { round = 1; } // 用于暂停显示回合数和当前分数 public void PauseRecord() { roundText.text = ""; recorder.setDisActive(); } // 用于恢复显示回合数和当前分数 public void ResumeRecord() { roundText.text = "Round:" + round; recorder.setActive(); } }
Count:
// ======================================================== // 描 述:用于回合间的计数 // 作 者:hza // 创建时间:2017/03/27 22:46:23 // 版 本:v 1.0 // ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Count : MonoBehaviour { public Text centerText; bool active = false; float beginTime; // Update is called once per frame void Update () { if (!active) return; // 不活跃 beginTime -= Time.deltaTime; if (beginTime > 0) { centerText.text = " " + countingNumber(); } else { centerText.text = ""; SceneController scene = Singleton<SceneController>.Instance; scene.nowState = Status.GAMING; RoundController round = Singleton<RoundController>.Instance; round.runRound(); active = false; // 设置为不活跃 } } public void beginCount() { beginTime = 3; active = true; } private int countingNumber() { return (int)beginTime + 1; } }
DiskActionManager:
// ======================================================== // 描 述:用于管理飞碟飞行,其中包含FlyAction类 // 作 者:hza // 创建时间:2017/04/01 14:25:14 // 版 本:v 1.0 // ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; using Tem.Action; using My.Disk; using UnityEngine.UI; public class FlyAction : SSAction { public Vector3 firstDirect; // 飞行方向 private float time; // 已经飞行时间 private static float g = 5f; public static FlyAction GetSSAction(Vector3 _firstDirect) { FlyAction currentAction = ScriptableObject.CreateInstance<FlyAction>(); currentAction.firstDirect = _firstDirect; currentAction.time = 0; return currentAction; } public override void Start() { } public override void Update() { if (!this.gameObject.activeSelf) // 如果飞碟已经回收 { this.destory = true; this.callback.SSEventAction(this, SSActionEventType.STARTED); return; } time += Time.deltaTime; transform.position += Time.deltaTime * firstDirect; // 各个方向的匀速运动 transform.position += Vector3.down * g * time * Time.deltaTime; // 竖直方向的匀加速运动 if (this.transform.position.y < 0) { this.destory = true; DiskFactory fac = Singleton<DiskFactory>.Instance; fac.freeDisk(gameObject); // 回收飞碟 this.callback.SSEventAction(this); } } } public class DiskActionManager : SSActionManager, ISSActionCallback { public GameObject cam; public Text centerText; DiskFactory dic; Vector3 leftEmitPos = new Vector3(-15, 10, -5); Vector3 rightRmitPos = new Vector3(15, 10, -5); int sumNum; bool isLose; new void Start () { dic = Singleton<DiskFactory>.Instance; } // Update is called once per frame new void Update () { base.Update(); if (Input.GetMouseButtonDown(0)) { Ray ray = cam.GetComponent<Camera>().ScreenPointToRay(Input.mousePosition); // 获取射线 RaycastHit hit; if (Physics.Raycast(ray, out hit) && hit.collider.tag == "Disk") // 如果点击的物体是Disk { SceneController scene = Singleton<SceneController>.Instance; scene.recorder.addScore(10); // 射中+10分 /*boom.transform.position = hit.collider.gameObject.transform.position; boom.GetComponent<Renderer>().material.color = hit.collider.GetComponent<Renderer>().material.color; boom.Play(); // 粒子爆炸效果*/ dic.freeDisk(hit.collider.gameObject); // 点中,毁掉自身脚本,返回工厂 } } } public void runActionByRound(int round) { sumNum = round; isLose = false; GameObject disk; for (int i = 0; i < round; i += 2) { disk = dic.setDiskOnPos(leftEmitPos); FlyAction fly = FlyAction.GetSSAction(new Vector3(Random.Range(5f, 20), Random.Range(2.5f, 5), Random.Range(0, 0.75f))); this.runAction(disk, fly, this); } for (int i = 1; i < round; i += 2) { disk = dic.setDiskOnPos(rightRmitPos); FlyAction fly = FlyAction.GetSSAction(new Vector3(Random.Range(-20f, -5f), Random.Range(2.5f, 5), Random.Range(0, 0.75f))); this.runAction(disk, fly, this); } // 设置飞碟发射,发射round个飞碟 } // 回调函数 public void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED, int intParam = 0, string strParam = null, Object objParam = null) { if (events == SSActionEventType.COMPLETED) // 落到y轴以下 { isLose = true; sumNum--; } else { sumNum--; } if (sumNum == 0) // 如果本回合结束 { SceneController scene = Singleton<SceneController>.Instance; if (isLose) { centerText.text = " LOSE"; scene.nowState = Status.OVER; } else { RoundController round = Singleton<RoundController>.Instance; round.NextRound(); round.PauseRecord(); scene.nowState = Status.WATING; } } } }
DiskFactory:
// ======================================================== // 描 述:飞碟工厂,用来生产飞碟和保留不用的飞碟 // 作 者:hza // 创建时间:2017/03/27 00:02:54 // 版 本:v 1.0 // ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; namespace My.Disk { public class DiskFactory : MonoBehaviour { private static List<GameObject> used = new List<GameObject>(); // 正在使用的对象链表 private static List<GameObject> free = new List<GameObject>(); // 正在空闲的对象链表 DiskInformation inf; private void Start() { inf = new DiskInformation(); } // 此函数表示将Target物体放到一个位置 public GameObject setDiskOnPos(Vector3 targetposition) { if (free.Count == 0) { GameObject aGameObject = Instantiate(Resources.Load("prefabs/Cylinder") , targetposition, Quaternion.identity) as GameObject; // 新建实例,将位置设置成为targetposition used.Add(aGameObject); } else { used.Add(free[0]); free.RemoveAt(0); used[used.Count - 1].SetActive(true); used[used.Count - 1].transform.position = targetposition; inf.processDisk(used[used.Count - 1]); // 加工disk } return used[used.Count - 1]; } public void freeDisk(GameObject oj) { oj.SetActive(false); used.Remove(oj); free.Add(oj); } } }
DiskInformation:
// ======================================================== // 描 述:此类保存Disk的各种information,和构建disk的属性 // 作 者:hza // 创建时间:2017/03/28 20:33:20 // 版 本:v 1.0 // ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; public class DiskInformation { private Color color; private float scale; public void processDisk(GameObject _disk) { color = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)); _disk.GetComponent<Renderer>().material.color = color; // 初始化color scale = Random.Range(2f, 4f); _disk.transform.localScale *= scale; // 初始化大小 } public void processDisk(GameObject _disk, Color _color, float _scale) { _disk.GetComponent<Renderer>().material.color = _color; _disk.transform.localScale *= _scale; // 自己设置color和大小 } }
本次作业就结束了,其实搞清楚类的关系后发现蛮好写的。。。
(由于感冒生病现在才补交,不知道还有没有机会了TAT)
相关文章推荐
- Unity3D 从入门到放弃(六)-----巡逻兵
- Unity3D 从入门到放弃(五)----射箭游戏
- Unity3D从入门到放弃(七) ----DoTween的实现
- Unity3D 从入门到放弃 ——巡逻兵 观察者模式
- unity3D-游戏/AR/VR在线就业班 蓝鸥C#入门字典学习笔记
- 《Java从入门到放弃》框架入门篇:Struts2的常用验证方式(二)
- KSFramework:Unity3D开发框架快速入门
- 《Java从入门到放弃》入门篇:Struts2的常用验证方式
- Docker 从入门到放弃(三)镜像使用
- Unity3d-C#入门基础
- React Native从入门到放弃之坑总结
- Unity3D学习(三)打飞碟游戏
- Unity3D入门 第壹章 :简单的碰撞检测
- 【微信小程序】从入门到放弃
- [C++ 从入门到放弃-03]C++STL之set
- Git从入门到放弃——Git服务器搭建-Linux篇
- JSP--(二)从入门到放弃
- Spring AOP从入门到放弃之自定义注解收集系统日志
- 【译】Unity3D Shader 新手教程(4/6) —— 卡通shader(入门版)
- Spring AOP从入门到放弃之多数据源读写动态切换