2014-03-05 22:08:10 bean244 阅读数 19031
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4712 人正在学习 去看看 张刚
 在TORQUE引擎中,我们只要设置几个点,然后物体会按照这几个点顺序移动,Unity3d也可以的,现在介绍一个很简单的按照路径移动的方法。


目标是让蓝色方块沿着紫色方块组成的路径移动,设计思想就是让蓝色方块移动第一个,然后继续移动到第二个,然后.....,我们使用碰撞检测判定蓝色方块是否已经到位置来改变方向。

首先编写2个简单的脚本:

/----------------------------- move.cs 给蓝色方块使用------------------------------/
using UnityEngine;
using System.Collections;

public class move : MonoBehaviour
{
    //用来保存目标的数组
    public Transform [] obj;
    //用来改变数组的值
    private static int i = 0;
 
    void Start()
    {
    }
  
    void Update()
    {
        // 让我们的物体朝目标移动
        transform.LookAt(obj[i % obj.Length]);
        transform.Translate(Vector3.forward*Time.deltaTime*5);
    }

    //改变目标物体
    public static void Add()
    {
        i++;
    }
}
/----------------------------- move.cs end --------------------------/

/----------------------------- point.cs 给紫色方块使用 ----------------/
using UnityEngine;
using System.Collections;

public class point : MonoBehaviour {

// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}

    //当用户接触到目标物体改变参数到下一个目标物体
    void OnTriggerEnter(Collider cos)
    {
        move.Add();
    }
}
/----------------------------- point.cs end --------------------------/

蓝色方块的参数设置,注意要将box collider 换成Character Controller,这样才能跟紫色方块产生碰撞触发。

紫色方块的参数设置,注意要将Is Trigger 参数打钩,否则也不会产生碰撞检测。


运行工程,你就会发现蓝色方块会沿着紫色方块路径移动,不停的循环......

2012-11-28 13:22:54 xggllc 阅读数 5212
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4712 人正在学习 去看看 张刚

    这几天在写一个数字漫游项目,有一个要求是在高空鸟瞰,沿着固定的路径移动.........

    部分代码如下:C#代码,CameraMove.cs

using UnityEngine;
using System.Collections;
public class CameraMove : MonoBehaviour 
{
     int SpeedUp = 10;
     bool Automatic = true;
     Vector3 Goal = new Vector3(200,10,100);
     void Update ()
    {
       if(Automatic)
		{
			Speed = true;
			transform.position=Vector3.MoveTowards(transform.position,Goal,Time.deltaTime * SpeedUp);
		}
    }
}

主要是 这个 Vector3.MoveTowards当前的地点移向目标。

API 上没有具体介绍,

说是 “这个函数基本上和Vector3.Lerp相同,而是该函数将确保我们的速度不会超过maxDistanceDelta。maxDistanceDelta的负值从目标推开向量,就是说maxDistanceDelta是正值,当前地点移向目标,如果是负值当前地点将远离目标。”

第一个参数是 开始位置,第二个参数是 目标位置; 第三个参数 我理解为控制 移动速度与方向的;

上面代码中 SpeedUp 是为了控制 移动的速度的.......

上面代码拖动到摄像机上 ,就可以实现摄像机移动了......

貌似通过这个还可以设置按固定曲线轨迹移动,个人觉得可以通过一些逻辑判断来实现(PS:目前没要求曲线我就没有做测试)。

嘿嘿........小弟新手 ,希望大家多多指教~

                                                                                                                                                                                                                                                         破破

                                                                                                                                                                                                                                           2012年11月28日 13:02:44
2018-07-04 17:48:31 IT_yanghui 阅读数 2000
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4712 人正在学习 去看看 张刚

近日,项目需求:前提:三维空间

1.动态添加(删除)路径点,通过两个以上的路径点来绘制曲线,删除点之后不影响其他点绘制曲线;

2.每个路径点都可以被拖拽发生位移,可以通过锁定某个轴,使该轴不发生位移;

3.每个路径点处有两个可调节点(首尾只有一个调节点),可通过调节点来调节曲线切线(速度方向线)斜率,以达到平滑曲线;

4.使该曲线形成路径,隐藏曲线,Player沿曲线完成自动寻路。

先看效果,再贴代码。(使用贝塞尔曲线公式与LineRenderer绘制3D可调节曲线


代码如下:两个脚本。

1. DMDrawCurve.cs 挂载到任意对象即可

该脚本实现绘制曲线,动态添加(删除)点,发生位移时更新绘制曲线,以及隐藏该曲线。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DM.Editor.View
{
    [RequireComponent(typeof(LineRenderer))]
    public class DMDrawCurve : MonoBehaviour
    {
        public List<Transform> m_allPoints;
        private GameObject m_anchorPoint;
        private GameObject m_controlPoint;
        private GameObject m_pointParent;
        private LineRenderer m_lineRenderer;
        
        private int m_curveCount = 0;
        private int SEGMENT_COUNT = 60;//曲线取点个数(取点越多这个长度越趋向于精确)

        private static DMDrawCurve m_instance;
        public static DMDrawCurve Instance
        {
            get {
                if (null == m_instance)
                    m_instance = new DMDrawCurve();
                return m_instance;
            }
        }
        void Awake()
        {
            if (null == m_instance)
                m_instance = this;
            SetLine();
            if (null == m_anchorPoint)
                m_anchorPoint = Resources.Load("Prefabs/AnchorPoint") as GameObject;
            if (null == m_controlPoint)
                m_controlPoint = Resources.Load("Prefabs/ControlPoint") as GameObject;
        }
        void SetLine()
        {
            if (null == m_lineRenderer)
                m_lineRenderer = GetComponent<LineRenderer>();
            m_lineRenderer.material = Resources.Load("Materials/Line") as Material;
            m_lineRenderer.startColor = Color.red;
            m_lineRenderer.endColor = Color.green;
            m_lineRenderer.widthMultiplier = 0.2f;
        }

        public void Init(GameObject player)
        {//初始化一个基准点(Player)
            if (player == null) return;
            GameObject anchorPoint = LoadPoint(m_anchorPoint, player.transform.position);
            m_allPoints.Add(anchorPoint.transform);
        }      
        public void AddPoint(Vector3 anchorPointPos)
        {
            //初始化时m_allPoints添加了一个player
            if (m_allPoints.Count == 0) return;
            Transform lastPoint = m_allPoints[m_allPoints.Count - 1];
            GameObject controlPoint2 = LoadPoint(m_controlPoint, lastPoint.position+new Vector3(0,0,-1));   
            GameObject controlPoint = LoadPoint(m_controlPoint, anchorPointPos + new Vector3(0, 0, 1));
            GameObject anchorPoint = LoadPoint(m_anchorPoint, anchorPointPos);

            anchorPoint.GetComponent<CurvePointControl>().m_controlObject = controlPoint;
            lastPoint.GetComponent<CurvePointControl>().m_controlObject2 = controlPoint2;

            m_allPoints.Add(controlPoint2.transform);
            m_allPoints.Add(controlPoint.transform);
            m_allPoints.Add(anchorPoint.transform);

            DrawCurve();
        }
        public void DeletePoint(GameObject anchorPoint)
        {
            if (anchorPoint == null) return;
            CurvePointControl curvePoint = anchorPoint.GetComponent<CurvePointControl>();
            if (curvePoint && anchorPoint.tag.Equals("AnchorPoint"))
            {
                if (curvePoint.m_controlObject)
                {
                    m_allPoints.Remove(curvePoint.m_controlObject.transform);
                    Destroy(curvePoint.m_controlObject);
                } 
                if (curvePoint.m_controlObject2)
                {
                    m_allPoints.Remove(curvePoint.m_controlObject2.transform);
                    Destroy(curvePoint.m_controlObject2);
                }
                if (m_allPoints.IndexOf(curvePoint.transform) == (m_allPoints.Count - 1))
                {//先判断删除的是最后一个元素再移除
                    m_allPoints.Remove(curvePoint.transform);
                    Transform lastPoint = m_allPoints[m_allPoints.Count - 2];
                    GameObject lastPointCtrObject = lastPoint.GetComponent<CurvePointControl>().m_controlObject2;
                    if (lastPointCtrObject)
                    {
                        m_allPoints.Remove(lastPointCtrObject.transform);
                        Destroy(lastPointCtrObject);
                        lastPoint.GetComponent<CurvePointControl>().m_controlObject2 = null;
                    }
                }
                else
                {
                    m_allPoints.Remove(curvePoint.transform);
                }
                Destroy(anchorPoint);
                if(m_allPoints.Count == 1)
                {
                    m_lineRenderer.positionCount = 0;
                }
            }

            DrawCurve();
        }
        public void UpdateLine(GameObject anchorPoint, Vector3 offsetPos1, Vector3 offsetPos2)
        {
            if (anchorPoint == null) return;
            if (anchorPoint.tag.Equals("AnchorPoint"))
            {
                CurvePointControl curvePoint = anchorPoint.GetComponent<CurvePointControl>();
                if (curvePoint)
                {
                    if (curvePoint.m_controlObject)
                        curvePoint.m_controlObject.transform.position = anchorPoint.transform.position + offsetPos1;
                    if (curvePoint.m_controlObject2)
                        curvePoint.m_controlObject2.transform.position = anchorPoint.transform.position + offsetPos2;
                }
            }
            DrawCurve();
        }
        public List<Vector3> HiddenLine(bool isHidden=false)
        {
            m_pointParent.SetActive(isHidden);
            m_lineRenderer.enabled = isHidden;
            List<Vector3> pathPoints = new List<Vector3>();
            if(!isHidden)
            {
                for(int i = 0; i < m_lineRenderer.positionCount; i++)
                {
                    pathPoints.Add(m_lineRenderer.GetPosition(i));
                }
            }
            return pathPoints;
        }

        private void DrawCurve()//画曲线
        {
            if (m_allPoints.Count < 4) return;
            m_curveCount = (int)m_allPoints.Count / 3;
            for (int j = 0; j < m_curveCount; j++)
            {
                for (int i = 1; i <= SEGMENT_COUNT; i++)
                {
                    float t = (float)i / (float)SEGMENT_COUNT;
                    int nodeIndex = j * 3;
                    Vector3 pixel = CalculateCubicBezierPoint(t, m_allPoints[nodeIndex].position, m_allPoints[nodeIndex + 1].position, m_allPoints[nodeIndex + 2].position, m_allPoints[nodeIndex + 3].position);
                    m_lineRenderer.positionCount = j * SEGMENT_COUNT + i;
                    m_lineRenderer.SetPosition((j * SEGMENT_COUNT) + (i - 1), pixel);
                }
            }
        }
        private GameObject LoadPoint(GameObject pointPrefab,Vector3 pos)
        {
            if (pointPrefab == null)
            {
                Debug.LogError("The Prefab is Null!");
                return null;
            }
            if (null == m_pointParent)
                m_pointParent = new GameObject("AllPoints");
            GameObject pointClone = Instantiate(pointPrefab);
            pointClone.name = pointClone.name.Replace("(Clone)", "");
            pointClone.transform.SetParent(m_pointParent.transform);
            pointClone.transform.position = pos;

            return pointClone;
        }

        //贝塞尔曲线公式:B(t)=P0*(1-t)^3 + 3*P1*t(1-t)^2 + 3*P2*t^2*(1-t) + P3*t^3 ,t属于[0,1].
        Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
        {
            float u = 1 - t;
            float tt = t * t;
            float uu = u * u;
            float uuu = uu * u;
            float ttt = tt * t;

            Vector3 p = uuu * p0;
            p += 3 * uu * t * p1;
            p += 3 * u * tt * p2;
            p += ttt * p3;

            return p;
        }
    }
}

2. CurvePointControl.cs (挂载到路径点与调节点上(两个预制体))

该脚本实现每个路径点对应的两个调节点,以及绘制调节点与路径点之间的切线。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace DM.Editor.View
{
    public class CurvePointControl : MonoBehaviour
    {
        [Header("锁定X轴")]
        public bool m_isLockX = false;
        [Header("锁定Y轴")]
        public bool m_isLockY = true;
        [Header("锁定Z轴")]
        public bool m_isLockZ = false;
       
        [HideInInspector]
        public GameObject m_controlObject;
        [HideInInspector]
        public GameObject m_controlObject2;


        private Vector3 offsetPos1 = Vector3.zero;
        private Vector3 offsetPos2 = Vector3.zero;
        private LineRenderer lineRenderer;
        void Start()
        {
            if (gameObject.tag.Equals("AnchorPoint") && !lineRenderer)
                lineRenderer = gameObject.AddComponent<LineRenderer>();
            if (lineRenderer)
            {
                lineRenderer.sortingOrder = 1;
                lineRenderer.material = new Material(Shader.Find("Particles/Alpha Blended"));
                lineRenderer.startColor = lineRenderer.endColor = Color.yellow;
                lineRenderer.widthMultiplier = 0.03f;
                lineRenderer.positionCount = 0;
            }
        }
        void OnMouseDown()
        {
            if (!gameObject.tag.Equals("AnchorPoint")) return;
            OffsetPos();
        }
        public List<Vector3> OffsetPos()
        {
            List<Vector3> offsetPosList = new List<Vector3>();
            if (m_controlObject)
                offsetPos1 = m_controlObject.transform.position - transform.position;
            if (m_controlObject2)
                offsetPos2 = m_controlObject2.transform.position - transform.position;
            offsetPosList.Add(offsetPos1);
            offsetPosList.Add(offsetPos2);


            return offsetPosList;
        }
        void OnMouseDrag()
        {
            //if (gameObject.tag.Equals("AnchorPoint")) return;
            Vector3 pos0 = Camera.main.WorldToScreenPoint(transform.position);
            Vector3 mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, pos0.z);
            Vector3 mousePosInWorld= Camera.main.ScreenToWorldPoint(mousePos);
            Vector3 thisPos = mousePosInWorld;
            if (m_isLockX)
                thisPos.x = transform.position.x;
            if (m_isLockY)
                thisPos.y = transform.position.y;
            if (m_isLockZ)
                thisPos.z = transform.position.z;
            transform.position = thisPos;
            DMDrawCurve.Instance.UpdateLine(gameObject, offsetPos1, offsetPos2);   
        }      
        private void DrawControlLine()
        {
            if (!gameObject.tag.Equals("AnchorPoint") || (!m_controlObject && !m_controlObject2)) return;
            if (lineRenderer)
            {
                lineRenderer.positionCount = (m_controlObject && m_controlObject2) ? 3 : 2;
                if (m_controlObject && !m_controlObject2)
                {
                    lineRenderer.SetPosition(0, m_controlObject.transform.position);
                    lineRenderer.SetPosition(1, transform.position);
                }
                if (m_controlObject2 && !m_controlObject)
                {
                    lineRenderer.SetPosition(0, transform.position);
                    lineRenderer.SetPosition(1, m_controlObject2.transform.position);
                }
                if (m_controlObject && m_controlObject2)
                {
                    lineRenderer.SetPosition(0, m_controlObject.transform.position);
                    lineRenderer.SetPosition(1, transform.position);
                    lineRenderer.SetPosition(2, m_controlObject2.transform.position);
                }
            }
        }
        void Update()
        {
            DrawControlLine();
        }
    }
}

对应Inspector,如图


3. Test.cs (任意挂载)

该脚本实现Player自动寻路。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DM.Editor.View
{
    public class Test : MonoBehaviour
    {
        public GameObject m_player;
        public List<Vector3> m_pathPoints;
        void Start()
        {
            DMDrawCurve.Instance.Init(m_player);
        }

        IEnumerator Move()
        {
            if (m_pathPoints.Count == 0) yield break;
            int item = 1;
            while (true)
            {
                m_player.transform.LookAt(m_pathPoints[item]);
                m_player.transform.position = Vector3.Lerp(m_pathPoints[item - 1], m_pathPoints[item], 1f);
                item++;
                if (item >= m_pathPoints.Count)
                {
                    item = 1;
                    yield break;
                }
                yield return new WaitForEndOfFrame();
            }
        }
        void Update()
        {
            if (Input.GetKey(KeyCode.LeftControl) && (Input.GetMouseButtonUp(0) || Input.GetMouseButtonUp(1)))
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit))
                {
                    if (Input.GetMouseButtonUp(0) && hit.collider.tag.Equals("Terrain"))
                    {
                        Vector3 pointPos = new Vector3(hit.point.x, m_player.transform.position.y, hit.point.z);
                        DMDrawCurve.Instance.AddPoint(pointPos);
                    }
                    else if (Input.GetMouseButtonUp(1) && hit.collider.tag.Equals("AnchorPoint"))
                    {
                        DMDrawCurve.Instance.DeletePoint(hit.collider.gameObject);
                    }
                }
            }
            if (Input.GetKeyUp(KeyCode.A))
                m_pathPoints = DMDrawCurve.Instance.HiddenLine(false);
            else if (Input.GetKeyUp(KeyCode.Escape))
            {
                DMDrawCurve.Instance.HiddenLine(true);
                m_pathPoints.Clear();
            }
            if (Input.GetKeyUp(KeyCode.B))
            {
                StartCoroutine(Move());
            }
        }
    }
}

注意:需要添加两个Tag值(Terrain,AnchorPoint),也可动态添加,一个给场景地面,一个给路径点(AnchorPoint),调节点不需要Tag。

开发测试Demo以及.unitypackage的网盘链接如下,需要的自行下载:

链接:https://pan.baidu.com/s/1dwIOxcMB-Lhq_Tlxenb4fQ 密码:7g8y

以上就是近日开发结果,如有不足,请批评指正。如有疑问,请留言,看到自然回复。

如转载,请注明出处:IT_yanghui的博客

2015-03-01 19:20:17 qinyuanpei 阅读数 13344
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4712 人正在学习 去看看 张刚

各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是http://blog.csdn.net/qinyuanpei
在上一篇文章中,我们从塔防游戏的三个组成要素地图、敌人、防守单位对塔防游戏进行了较为全面的阐述,并通过实例完成了塔防游戏的第一部分:地图篇。那么我们今天来继续完成这个塔防游戏的开发,首先我们来看第一部分:敌人篇。

敌人篇

敌人在游戏中有一个基本的行为,即沿着寻路路径向我方阵地移动并发起攻击。在地图篇中,我们详细地介绍了敌人寻路路径的生成原理。既然有了敌人寻路的路线,那么怎么让敌人沿着路线移动呢?其实只要指定敌人寻路的起点,因为在寻路路径的设计中,我们使用的是一个类似于链表的结构,这样我们就能根据每个结点获取它的目标结点,从而实现敌人沿着寻路路径移动的效果了。因为敌人寻路的路线是定义在PathNode类中的,因此我们可以写出下面这样的代码:

void Move()
{
    Vector3 mPos1=this.transform.position;
    Vector3 mPos2=this.StartNode.transform.position;
    //计算敌人与路径节点间的距离
    float mDis=Vector2.Distance(new Vector2(mPos1.x,mPos1.y),new Vector2(mPos2.x,mPos2.y));
    if(mDis<0.1F){
        if(StartNode.ThatNode==null){
            //对防守阵地进行摧毁
            GameManager.Instance.PlayerHP-=20;
            //从敌人列表中移除自身
            GameManager.Instance.Enemys.Remove(this);
            //销毁自身
            Destroy(this.gameObject);
            //销毁血条
            Destroy(mHPBar.gameObject);
            }else{
            StartNode=StartNode.ThatNode;
        }
    }
    //计算敌人的移动方向
    Vector3 mDir=new Vector3(mPos2.x-mPos1.x,mPos2.y-mPos1.y,0).normalized;
    transform.Translate(mDir * MoveSpeed * Time.deltaTime);
} 

好了,现在我们来一起分析这段代码。首先,我们计算了敌人与路径结点间的距离,这里我们用0.1来近似地表示敌人已经到了路径结点上,此时如果该结点的目标结点为null则表示此时敌人已经到了最后一个结点处,所以敌人会对我方的阵地造成20点的伤害并销毁敌人。在GameManager我们使用了一个列表来管理和维护当前场景中的所有敌人,因此当当前敌人销毁时需要从列表中移除,GameManager类是一个静态类,负责对游戏的全局维护,这个类我们放到稍后来讲啊。那么如果敌人没有走到最后一个结点怎么办呢?我们只需要将StartNode指向StartNode的目标节点,这样我们就可以对整个路径结点实现遍历。这里是不是有种数据结构的感觉呢?哈哈,数据结构和算法是编程中最基础、最重要的内容,这些内容到了游戏开发领域同样是适用的。那么,好了,既然知道敌人是怎么移动的,现在我们就来对敌人进行移动吧,这里是采用计算移动方向的方式来实现,这个很简单啦。

好了,现在我们来说说敌人的血条吧,我们知道当怪物沿着寻路路径向我方阵地发起攻击的时候,我方防守单位会自动地对敌人进行防御性攻击,那么此时血条就可以显示敌人的实时血量,从而方便玩家根据战场的情况来调整兵力部署情况。我们知道从Unity4.6以后Unity开始使用全新的GUI系统UGUI,因为博主在之前的项目中一直使用NGUI,加上博主不是很喜欢做UI,所以每次用NGUI的时候整个人的心情都是不好的,有段时间被NGUI虐得体无完肤,感觉整个人都不好了。好了,既然现在我们有了新的选择UGUI,那么就让我们先睹为快吧!如图,全新的NGUI位于GameObect->UI菜单下,基本覆盖了常用的如Button、Image、Slider、ScrollBar等控件,因为UGUI刚推出不久,所以博主希望大家还是能客观、公正的对待UGUI和NGUI,博主认为在短期内这两个GUI系统将处于共存的状态,不存在相互替代的可能性。
这里写图片描述
好了,UGUI所有的控件都是放到一个叫做Canvas的父控件下的,这一点和NGUI的UIRoot有些类似吧!Canvas提供了三种模式的UI系统,即Screen Space-Overlay、Screen Space-Camera、World Space。第一种Screen Space-Overlay它是完全以当前屏幕的像素大小创建的一个矩形范围,即控件是以屏幕坐标来绘制的;第二种Screen Space-Camera它是根据相机的视线范围来确定的一个矩形范围,其控件是根据Camera的ViewPortPoint坐标来绘制的;第三种从名称我们就可以知道,它是完全3D化的UI,使用的是常用的世界坐标。博主是刚开始研究UGUI,如果有不对的地方还希望大家能够原谅啊。好了,下面我们正式来做血条吧,在这里我们使用的是默认的Slider控件,用Slider控件来制作血条需要将Slider控件自带的滑块删除,然后我们通过改变value就可以实现一个简单的血条了。在UGUI中所有的图片素材都是以Sprite的形式来出现的,所以UGUI可以自己生成图集,不需要像NGUI在做UI前首先要生成图集。这是博主做的一个简单的血条。现在血条做好了,可是问题来了:这UGUI的所有控件都必须要放到Canvas下面啊,所以我们没法像NGUI一样直接把做好的血条放到怪物下面。怎么办呢?既然不能放到怪物下面,那我们就放到Canvas下面吧,不过我们需要自己计算血条的位置。好了,下面来看代码:

public class Enemy : MonoBehaviour 
{

    //敌人的生命值
    public float MaxHP;
    public float HP;

    //敌人的初始路径节点
    public PathNode StartNode;
    //敌人的移动速度
    public float MoveSpeed=0.15F;
    //敌人的旋转速度
    public float RotateSpeed=0.3F;

    //敌人血条预制件
    public GameObject HPBar;
    //敌人血条组件
    private Slider mHPBar;

    //public EnemySpawn mSpawn;

    void Awake()
    {
        //在敌人列表中增加一个敌人
        GameManager.Instance.Enemys.Add(this.GetComponent<Enemy>());
        //查找UI
        Transform mUiRoot=GameObject.Find("UIManager").transform;
        //计算血条位置
        Vector3 mPos=this.transform.FindChild("EnemyHP").transform.position;
        //mPos=Camera.main.WorldToViewportPoint(mPos);
        mPos.z=-5;
        //生成血条
        GameObject go=(GameObject)Instantiate(HPBar,mPos,Quaternion.identity);
        //使血条成为Canvas的子物体
        go.transform.parent=mUiRoot;
        //对血条进行放缩
        go.GetComponent<RectTransform>().localScale=new Vector3(0.5F,0.30F,1);
        //获取Slider
        mHPBar=go.transform.GetComponent<Slider>();
    }

    void Move()
    {
        Vector3 mPos1=this.transform.position;
        Vector3 mPos2=this.StartNode.transform.position;
        //计算敌人与路径节点间的距离
        float mDis=Vector2.Distance(new Vector2(mPos1.x,mPos1.y),new Vector2(mPos2.x,mPos2.y));
        if(mDis<0.1F){
            if(StartNode.ThatNode==null){
                //对防守阵地进行摧毁
                GameManager.Instance.PlayerHP-=20;
                //从敌人列表中移除自身
                GameManager.Instance.Enemys.Remove(this);
                //销毁自身
                Destroy(this.gameObject);
                //销毁血条
                Destroy(mHPBar.gameObject);
            }else{
                StartNode=StartNode.ThatNode;
            }
        }
        //计算敌人的移动方向
        Vector3 mDir=new Vector3(mPos2.x-mPos1.x,mPos2.y-mPos1.y,0).normalized;
        transform.Translate(mDir * MoveSpeed * Time.deltaTime);
    } 

    void Rotate()
    {
        //初始角度
        float mStartAngle=this.transform.eulerAngles.z;
        transform.LookAt(StartNode.transform);
        //目标角度
        float mTargetAngle=this.transform.eulerAngles.z;
        //计算旋转量
        float mAngle=Mathf.MoveTowardsAngle(mStartAngle,mTargetAngle,RotateSpeed *Time.deltaTime);
        this.transform.eulerAngles = new Vector3(0,0,mAngle);
    }

    void Update () 
    {
        Move();
        UpdateHPBar();
    }

    private void UpdateHPBar()
    {
        //更新血条位置
        Vector3 mPos=this.transform.FindChild("EnemyHP").transform.position;
        //使血条位于顶层
        mPos.z=-5;
        mHPBar.transform.position=mPos;
        //更新血量
        mHPBar.value=(float)HP/MaxHP;
    }

    public void SetDamage(int mValue)
    {
        HP-=mValue;
        if(HP<=0){
          Destroy(this.gameObject);
          Destroy(mHPBar.gameObject);
          GameManager.Instance.Enemys.Remove(this.GetCopmonent<Enemy>());
        }
    }
}

在这里我们做了三件事情:
* 第一,在Awake方法中我们首先计算出血条的位置然后在这个位置生成血条,并取得相关的变量备用。
* 第二,在Update方法中增加一个UpdateHPBar方法以实现对血条血量的更新。
* 第三,增加了一个SetDamage方法,当敌人血量为0时销毁自身、销毁血条、从敌人列表中移除敌人
好了,到现在为止,对于敌人的逻辑我们就全部实现了。可是我们知道在塔防游戏中敌人通常是一波一波出现的,所以我们需要一个敌人生成器EnemySpawn。那么,怎么来生成敌人呢,这里我们使用Xml文件来配置要生成的敌人列表,首先我们来构建一个Xml文件:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Enemies>
    <Enemy Wave="1" EnemyName="Enemy" Level="1" Wait="0.5"/>
    <Enemy Wave="2" EnemyName="Enemy" Level="2" Wait="0.45"/>
    <Enemy Wave="2" EnemyName="Enemy" Level="2" Wait="0.45"/>
    <Enemy Wave="3" EnemyName="Enemy" Level="3" Wait="0.4"/>
    <Enemy Wave="3" EnemyName="Enemy" Level="3" Wait="0.4"/>
    <Enemy Wave="3" EnemyName="Enemy" Level="3" Wait="0.4"/>
    <Enemy Wave="4" EnemyName="Enemy" Level="4" Wait="0.35"/>
    <Enemy Wave="4" EnemyName="Enemy" Level="4" Wait="0.35"/>
    <Enemy Wave="4" EnemyName="Enemy" Level="4" Wait="0.35"/>
    <Enemy Wave="4" EnemyName="Enemy" Level="4" Wait="0.35"/>
    <Enemy Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/>
    <Enemy Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/>
    <Enemy Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/>
    <Enemy Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/>
    <Enemy Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/>
    <Enemy Wave="6" EnemyName="Enemy" Level="99" Wait="0.15"/>
</Enemies>

从这个Xml文件中我们可以看到这样一个结构:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Xml;


public class SpawnData 
{
    //敌人进攻波数
    public int Wave;
    ///敌人名称,我们将根据这个名称来生成不同的敌人
    public string EnemyName;
    //敌人等级,我们将根据这个值来调整敌人的生命值和移动速度
    public int Level;

    public float Wait;
}

在SpawnData这个结构中,我们可以得到敌人攻击的波数、敌人的名称、敌人等级、敌人生成需要等待的时间,因为博主在游戏中只有一种敌人,所以敌人的名称都是一样的。好了,现在我们可以开始解析Xml了:

//解析Xml文件
void ReadXml()
{
    //创建一个字典以存储敌人列表
    mEnemyDatas=new List<SpawnData>();
    //加载Xml文档
    XmlDocument mDocument=new XmlDocument();
    mDocument.LoadXml(ConfigFile.text);
    XmlElement mRoot=mDocument.DocumentElement;
    //解析Xml文档
    XmlNodeList mNodes=mRoot.SelectNodes("/Enemies/Enemy");
    foreach(XmlNode mNode in mNodes)
    {
        //为每一个SpawnData赋值
        SpawnData mData=new SpawnData();
        mData.Wave=int.Parse(mNode.Attributes[0].Value);
        mData.EnemyName=mNode.Attributes[1].Value;
        mData.Level=int.Parse(mNode.Attributes[2].Value);
        mData.Wait=float.Parse(mNode.Attributes[3].Value);

        mEnemyDatas.Add(mData);
    }
}

那么好了,在解析完Xml后我们得到了所有的敌人数据,接下来我们只需要按照顺序生成敌人就可以了。具体怎么做呢,我们知道在塔防游戏中生成敌人有两种情况:
* 一个是要生成的敌人和当前敌人是同一波的,这种情况只要继续生成就好了。
* 一个是要生成的敌人的波数大于当前波数,这种情况需要等待这一波敌人被消灭完。
好了,现在来写代码:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Xml;

public class EnemySpawn : MonoBehaviour {

    //敌人寻路起点
    public PathNode SpawnPath;
    //敌人预制件
    public GameObject Enemy;
    //Xml文件
    public TextAsset ConfigFile;

    //存放敌人的数组
    private List<SpawnData> mEnemyDatas;

    //当前敌人进攻波数
    private int mWave=0;
    //当前敌人索引
    private int mIndex=0;
    //当前等待的时间
    private float mWait;

    void Start()
    {
        //读取Xml数据
        ReadXml();
        Debug.Log(mEnemyDatas.Count);
        //初始化攻击波数
        SpawnData mData=mEnemyDatas[mIndex];
        //设置攻击波数和等待时间
        mWave=mData.Wave;
        mWait=mData.Wait;
        GameManager.Instance.AttackWave=mWave;
        //生成第一个敌人
        CreateEnemy(mData);
        mIndex+=1;
    }

    void CreateEnemy(SpawnData mData)
    {
        GameObject go=(GameObject)Instantiate(Enemy,SpawnPath.transform.position,Quaternion.identity);
        Enemy _Enemy=go.GetComponent<Enemy>();
        //根据Level计算敌人的生命值和移动速度
        _Enemy.MaxHP= (float)mData.Level*0.25F * 100;
        _Enemy.HP= (float)mData.Level*0.25F * 100;
        go.GetComponent<Enemy>().MoveSpeed=(float)mData.Level * 0.15F;
        go.GetComponent<Enemy>().StartNode=SpawnPath;
    }

    void Update () 
    {
       if(mIndex<=mEnemyDatas.Count-1){
         SpawnEnemy();
       }else
        {
            //当索引数目大于敌人列表中的数目时,表示所有敌人以及生成完毕,此时
            //如果所有的敌人都被消灭,则表示玩家获胜。
            if(GameManager.Instance.Enemys.Count==0){
                GameManager.Instance.IsWin=true;
                Debug.Log("玩家胜");
            }
        }
    }

    private void SpawnEnemy()
    {
        //取得下一个生成的敌人的数据
        SpawnData mData=mEnemyDatas[mIndex];

        //开始计时
        mWait-=Time.deltaTime;
        if(mWait<=0 ){
            //如果当前是同一波敌人,则继续生成敌人
            if(mWave==mData.Wave){
               //设置等待时间
               mWait=mEnemyDatas[mIndex].Wait;
               //设置进攻波数
               mWave=mEnemyDatas[mIndex].Wave;
               GameManager.Instance.AttackWave=mWave;
               //生成一个敌人
               if(mData!=null){
               CreateEnemy(mData);
               }
               mIndex+=1;
            }//如果是下一波敌人,则需要等待这一波敌人全部死亡后再生成
            else if(mWave<mData.Wave && GameManager.Instance.Enemys.Count==0){
                //设置等待时间
                mWait=mData.Wait;
                //设置进攻波数
                mWave=mData.Wave;
                GameManager.Instance.AttackWave=mWave;
                //生成一个敌人
                CreateEnemy(mData);
                mIndex+=1;
            }
        }
    }

    //解析Xml文件
    void ReadXml()
    {
        //创建一个字典以存储敌人列表
        mEnemyDatas=new List<SpawnData>();
        //加载Xml文档
        XmlDocument mDocument=new XmlDocument();
        mDocument.LoadXml(ConfigFile.text);
        XmlElement mRoot=mDocument.DocumentElement;
        //解析Xml文档
        XmlNodeList mNodes=mRoot.SelectNodes("/Enemies/Enemy");
        foreach(XmlNode mNode in mNodes)
        {
            //为每一个SpawnData赋值
            SpawnData mData=new SpawnData();
            mData.Wave=int.Parse(mNode.Attributes[0].Value);
            mData.EnemyName=mNode.Attributes[1].Value;
            mData.Level=int.Parse(mNode.Attributes[2].Value);
            mData.Wait=float.Parse(mNode.Attributes[3].Value);

            mEnemyDatas.Add(mData);
        }
    }
}

我们可以注意到,到现在为止敌人相关的内容博主都已经为大家讲解完了,这里博主和大家开了一个小玩笑,不知道大家有没有发现,在敌人的Xml配置文件中博主最后设计了一个等级为99级的敌人,哈哈,这个敌人在游戏中的特点大家要自己从代码中来探索了,大家可以按照博主的思路做出这个塔防游戏然后自己去试试看,相信大家会更加深刻地理解数值平衡的重要性吧!

防守单位篇

防守单位是塔防游戏中玩家可以支配和控制的一种资源,玩家通过合理地分布防守单位的位置来对玩家的防守阵地进行防御,当玩家的防守阵地被摧毁时玩家将无法继续部署防守单位。这就是防守单位在游戏中的主要作用。通常为了增加游戏的可玩性,游戏设计者往往会设计多种防守单位,在博主的这个小游戏中,我们只设计了一种防守单位,更多的防守单位的设计大家可以参考《保卫萝卜》和《植物大战僵尸》这两个游戏。好了,说了这么多,那么防守单位在整个塔防游戏中主要的作用是什么呢?答案就是防守,哈哈,这是一句不折不扣的废话。可是就是这样一句废话,却足以让我们知道防守单位需要对敌人进行自动攻击,这就要涉及到简单的AI算法了。好了,我们来看下面的脚本:

using UnityEngine;
using System.Collections;

public class Defender : MonoBehaviour {

    //目标敌人
    private Enemy mTarget;
    //攻击半径
    public float AttackArea=2.5F;
    //与敌人的距离
    private float mDistance=0;
    //防守单位的旋转速度
    public float RotateSpeed=1.5F;
    //防守单位攻击间隔
    public float AttakTime=2.5F;
    //防守单位攻击间隔
    private float mTime=0.0F;

    //炮弹预设
    public GameObject BulletObject;

    void Start () 
    {
        //初始化防守单位攻击间隔
        mTime=AttakTime;
    }

    //查找攻击范围内的敌人
    void FindEnemy()
    {
        //初始化目标敌人
        mTarget=null;
        //获取敌人列表
        ArrayList mEnemys=GameManager.Instance.Enemys;
        //遍历每个敌人
        foreach(Enemy _enemy in mEnemys)
        {
            //忽略生命值为0的敌人
            if(_enemy.HP==0) continue;
            //计算防守单位与敌人间的距离
            Vector3 mPos1=transform.position;
            Vector3 mPos2=_enemy.transform.position;
            float mDis=Vector2.Distance(new Vector2(mPos1.x,mPos1.y),new Vector2(mPos2.x,mPos2.y));
            if(mDis>AttackArea){
                //Debug.Log("敌人" + _enemy.transform.name + "未进入攻击范围,距离为:" + mDis);
                //return;
            }else{
                //Debug.Log("敌人" + _enemy.transform.name + "已进入攻击范围,距离为:" + mDis);
                //选择最近的敌人
                if(mDistance==0 || mDistance > mDis){
                    mTarget=_enemy;
                    mDistance=mDis;
                }

                /*//选择生命值最低的敌人
                if(mLife==0 || mLife > _enemy.HP){
                    mTarget=_enemy;
                    mLife=_enemy.HP;
                }
                */
            }
        }
        mDistance=0;
    }

    void RotateTo()
    {
        //判断目标敌人是否为空
        if(mTarget==null) return;
        //计算要旋转到敌人方向的角度
        Vector3 mPos1=this.transform.position;
        Vector3 mPos2=mTarget.transform.position;
        Vector3 mDir=(mPos2-mPos1).normalized;
        //使得两向量共面
        mDir.z=0;
        //计算两向量角度
        float mAngle=getAngle(Vector3.up,mDir);
        this.transform.eulerAngles=new Vector3(0,0,mAngle) * RotateSpeed;

    }

    //根据向量数学计算角度
    private float getAngle(Vector3 v1,Vector3 v2)
    {
        float mDot=Vector3.Dot(v1,v2);
        float mv1=Mathf.Sqrt(v1.x*v1.x+v1.y*v1.y+v1.z*v1.z);
        float mv2=Mathf.Sqrt(v2.x*v2.x+v2.y*v2.y+v2.z*v2.z);
        if(v2.x>v1.x){
            return -Mathf.Acos(mDot/(mv1*mv2))* Mathf.Rad2Deg ;
        }else{
            return Mathf.Acos(mDot/(mv1*mv2))* Mathf.Rad2Deg ;
        }
    }

    void Attack()
    {
        RotateTo();
        if(mTarget==null) return;
        //以下添加攻击逻辑
        mTime-=Time.deltaTime;
        if(mTime<0){
            Vector3 _angle=transform.Find("Bullet").eulerAngles;
            Vector3 _pos=new Vector3(this.transform.position.x,this.transform.position.y,-2);
            Instantiate(BulletObject,_pos,Quaternion.Euler(_angle));
            mTime=AttakTime;
        }
    }

    void Update () 
    {
        FindEnemy();
        Attack();
    }



防守单位的脚本定义在Defender这个类中,主要的行为有两个,即发现敌人后转向敌人、向敌人发射炮弹,这块的代码较为简单,大家自己去领会就好啦。我们知道在塔防游戏中玩家可以通过点击屏幕来自由地增加或移动防守单位,这部分的内容主要是和GUI相关的,因为目前博主对UGUI掌握地还不是很熟,所以就等以后博主有时间了再来补充吧!好了,这个塔防游戏的讲解教程就是这样了,希望大家能够喜欢,我知道大家等这篇下篇已经好久了,哈哈!
Demo
最后想说的是,博主的独立博客http://qinyuanpei.github.io正式开始使用了,以后发表的文章会在独立博客和CSDN同时更新,希望大家能继续关注博主的博客!谢谢大家

2016-10-25 17:26:46 virus2014 阅读数 4004
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4712 人正在学习 去看看 张刚


协同程序主程序运行时同时开启另外一段逻辑处理,来协同当前的执行;同一时刻只有一个协同程序在运行,并且协同程序会影响到主线程的运行;
开启方法:(方法2可以传递多个参数,并且性能消耗略小)

  1. StartCorourine(string methodName)
  2. StartCorourine(IEnumerator routine)


如下图所示,AI(黑球)沿着5个黄色方块(Cube)所连接的绿色路径(使用Gizmos绘制)行走,当Mage(Hero)接近AI一定范围的时候,AI将会追击Mage,Mage离开范围时,AI继续之前的行走。


布局显示界面


挂载在Cube上的绘制路径的脚本:WayPoint类 

using UnityEngine;
using System.Collections;

public class WayPoint : MonoBehaviour {

	#region
	public WayPoint nextWayPoint;
	#endregion
	
	void OnDrawGizmosSelected() {
		Gizmos.color = Color.green;
		Gizmos.DrawLine(transform.position, nextWayPoint.gameObject.transform.position);
	}
}

挂载在AI上的脚本:

using UnityEngine;
using System.Collections;

public class AI : MonoBehaviour {

	#region variable
	[SerializeField] //保护封装性
	private float speed = 3f;
	[SerializeField]
	private WayPoint targetPoint, startPoint;
	[SerializeField]
	private Hero mage;

	#endregion

	// Use this for initialization
	void Start () {
		if (Vector3.Distance(transform.position, startPoint.transform.position) < 1e-2f) {
			targetPoint = startPoint.nextWayPoint;
		} else {
			targetPoint = startPoint;
		}
		StartCoroutine(AINavMesh());
	}
	
	IEnumerator AINavMesh() {
		while(true) {
			if (Vector3.Distance(transform.position, targetPoint.transform.position) < 1e-2f) {
				targetPoint = targetPoint.nextWayPoint;
				yield return new WaitForSeconds(2f);
			}
			if (mage != null && Vector3.Distance(transform.position, mage.gameObject.transform.position) <= 6f) {
				Debug.Log("侦测到敌人,开始追击!!!");
				yield return StartCoroutine(AIFollowHero());
			}
			Vector3 dir = targetPoint.transform.position - transform.position;
			transform.Translate(dir.normalized * Time.deltaTime * speed);
			yield return new WaitForEndOfFrame();
		}
	}
	IEnumerator AIFollowHero() {
		while (true) {
			if (mage != null && Vector3.Distance(transform.position, mage.gameObject.transform.position) > 6f) {
				Debug.Log("敌人已走远,放弃攻击!!!");
				yield break;
			}
			Vector3 dir = mage.transform.position - transform.position;
			transform.Translate(dir.normalized * Time.deltaTime * speed * 0.8f);
			yield return new WaitForEndOfFrame();
		}
	}
}

挂载在Mage上的脚本:

using UnityEngine;
using System.Collections;
//在本实例中起到标记的作用
public class Hero : MonoBehaviour {
	
}



没有更多推荐了,返回首页