您的位置:首页 > 移动开发 > Unity3D

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