Unity3D 从入门到放弃(四)----打飞碟

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值