弹道实现

 

转自:https://www.gameres.com/677540.html

上篇

今天谈谈一个有趣的内容——u3d实现子弹弹道。当然这个完整的说,也非常复杂,还是由浅入深,先说说最核心的原理。

一、定义

我将子弹分为至少两种:

1、实体型。即发射后,生成一个带刚体的gameobject,可以通过u3d的物理引擎实现碰撞检测。比如机关枪子弹、弹道等等。

2、射线型。即子弹的运动和碰撞不由刚体实现,弹道控制由代码实现,通过Physics2D下函数或自己计算物体检测。比如定向激光、AOE伤害。

本文分别讲两种的实现。

二、理解U3D的物理引擎

知其所以然,才能更好、更快的进行代码实现。这个章节从运用和现象入手,说明一些有关u3d物理引擎的“坑”

Collider和Rigidbody

如果完全不了解U3D的Collider(碰撞体)、Rigidbody(刚体)可以先找下相关的文章看下,网上很多很好的文章。

这里用自己的理解简单说下(针对2D):

Collider描绘物体的碰撞形状,有Box、Cirlce等,是碰撞检测的基础,比如鼠标点击的碰撞判断,都是依赖于定义的Collider来知晓物体的“碰撞形状”

Rigidbody是给物体赋予一个接受物理规律的属性,比如给一个小球加上刚体,默认的小球就会收到重力加速度往下掉。

刚体必须有collider,不然就没有“形状”。

在u3d的2D物理效果的设计理念是:

不会动的物体,只设Collider,例如大地、树木;

会动的,需要有物理运动效果的,设置Collider和Rigidbody,Collider说明“外形”,Rigidbody说明物理属性,如质量、碰撞属性等。

注意这个理念,因为我首次使用2D物理到游戏中的时候,有很多地方很费解,不明白为什么要这么设计,如果理解这个理念了,很多东西就想明白了。

碰撞检测的相关参数

U3D的2D物理引擎,可以不用做任何编码实现刚体的运动碰撞效果。

但如果需要代码获取碰撞信息,相关的属性有:

Collider的Is Trigger,Rigidbody的Is Kinematic
 

至于其他刚体参数的作用,可以摆个场景逐个调整参数看看效果,就基本明白了。

因为我主要是想通过u3d的2D物理引擎获得的碰撞信息,而不关注碰撞后的物理效果,所以其他参数都可以不管或设置为0。

特别的,由于游戏是一个top-down视角的游戏,而u3d的2D物理引擎默认y轴是2D世界的上下方向,所以Gravity Scale要设置为0。
 

对应的相关的函数有:

OnTriggerEnter2D、OnTriggerExit2D、OnTriggerStay2D(Collider发生碰撞时被调用)

OnCollisionEnter2D、 OnCollisionExit2D、OnCollisionStay2D(Rigidbody发生碰撞时被调用)

下面将解释这些属性和函数的作用。

碰撞检测的推荐实现方式——Rigidbody的Collision

需要用u3d的2d物理引擎实现碰撞检测时,需要这么设置(假定检测A和B之间的碰撞):

1、A和B都添加collider,不勾选Is Trigger

2、A或B至少一个添加rigidbody,不勾选IsKenematic

3、对A或B添加脚本,添加OnCollisionEnter2D、 OnCollisionExit2D或OnCollisionStay2D函数获取碰撞信息。

以本文的实体型子弹为例:

1、  对游戏单位和子弹都添加collider

2、  对子弹添加rigidbody

3、  对子弹添加OnCollisionEnter2D方法,编写造成伤害的逻辑代码,并销毁子弹对象。

关于Rigidbody的Is Kinematic的属性:勾选后,2D物理引擎对这个刚体不起作用,只能代码去实现物体的运动。同时,OnCollisionEnter2D也不会被触发。

另外一种碰撞检测的实现——Collider的Trigger

通过Collider的Is Trigger的,也能实现“碰撞检测”。

Collider的Is Trigger:顾名思义,这个属性说明是否触发,勾选后,则会有“碰撞时”OnTriggerEnter2D、OnTriggerExit2D, OnTriggerStay2D函数。

例如,检测A和B之间的碰撞:

1、  A和B都添加collider,A勾选Is Trigger,B不勾选

2、  A添加rigidbody

3、  对A脚本添加OnTriggerEnter2D

A和B的collider发生接触时,则A的OnTriggerEnter2D被调用。如果B的脚本也有OnTriggerEnter2D,也会被调用,尽管B没有勾选Is Trigger。

这种“碰撞检测”,依靠Collider的trigger机制,在Collider层面就可以完成,其原理应该和鼠标点击事件的触发类似。但有以下问题:

1、  这个触发机制的碰撞检测频率和Update一样,而上文中推荐方式(利用OnCollisionEnter2D)是和FixedUpdate一样,后者是专门是做刚体物理运算,其计算频率更好,碰撞检测更准确。如果使用OnTriggerEnter2D的方式,检测到碰撞发生时可能两个碰撞的物体已经相互嵌入很久了,如果其中一个物体运动速度过快,可能已经“穿”过去了

2、  OnCollisionEnter2D的参数提供的碰撞信息更丰富,而OnTriggerEnter2D只有一个碰撞对方collider的信息,得不到更精确的点。

3、  虽然碰撞是在Collider层面完成,感觉跟Rigidbody没有什么关系(1、2两点的想象也侧面印证了我这个想法),但A和B之间必须有一个是Rigidbody,不然碰撞事件触发不了。Physics2D中IsTouching等函数也有这样。

4、  设置Trigger后,所有的碰撞事件被Trigger拦截,OnCollisionEnter2D不会再被调用。

基于以上因素,这种碰撞检测,不能称之为有效的“碰撞检测”,在实际运用中要根据实际情况判断是否合适。

作为游戏物体和物体的碰撞检测,不推荐使用Collider的Trigger方式。

关于碰撞检测的总结

1、如果想使用物理引擎实现碰撞,包括Collider的Trigger,rigidbody的Collision,Physics 2D的IsTouching等方法,除了碰撞双方都有Collider,必须有1个有rigidbody。(此点让我无力吐槽)。

2、使用Collider的Trigger(勾选Is Trigger),可以使用OnTriggerEnter2D、OnTriggerExit2D, OnTriggerStay2D监听碰撞,但没有碰撞物理效果,rigidbody的collision无法使用。Collider的Trigger不是在物理引擎层面上工作的,不管是碰撞检测的更新频率、是碰撞结果都不好,且它直接“阻止”了物理引擎的对物体的作用。

3、使用rigidbody的collision(不勾选Is Kinematic),使用OnCollisionEnter2D、 OnCollisionExit2D、OnCollisionStay2D监听碰撞,物理引擎会影响刚体的运动,会有碰撞反弹的物理效果。最好在OnCollisionEnter2D只获取状态而不更新物体运动,因为物理引擎这是也在控制它的运动。

综上,u3d物理引擎的使用限制还是很多的,实现很多逻辑功能都有障碍。由于对于实体子弹的实现,子弹打击单位后,子弹自我销毁,和以上第3点正好满足,可以使用u3d的collision,而非实体子弹显然不能使用。

三、实体型子弹

如果认真阅读上面的分析且理解了原理,应该对u3d的2D碰撞(3D类似)的套路应该很清楚,实现实体子弹打击效果,仅仅是点点、配配的事。

现实方式为:

1、  对游戏单位和子弹都添加collider

2、  对子弹添加rigidbody

3、  对子弹添加OnCollisionEnter2D方法,编写造成伤害的逻辑代码,并销毁子弹对象。

关键代码:
  1. void Update ()
  2.     {
  3.         if (Common.pause)
  4.             return;
  5.  
  6.         m_Anim.OnUpdate (GetComponent<SpriteRenderer> ());
  7.  
  8.         float l = Time.deltaTime * m_Info.speed;
  9.         transform.position += m_Direction * l;
  10.         m_LeftDistance -= l;
  11.         if (m_LeftDistance < 0 || m_DestroySelf)
  12.         {
  13.             FlyerManager.Free(gameObject);
  14.         }
  15.  
  16.     }
  17.  
  18.  
  19.     public virtual void OnCollisionEnter2D (Collision2D coll)
  20.     {
  21.         if (m_DestroySelf)
  22.             return;
  23.  
  24.         TargetPick pick = TargetPick.From (coll);
  25.         m_Info.AttackOn (pick,m_Direction, m_myUnit,hitEffectType);
  26.         m_DestroySelf = true;
  27.  
  28.     }
复制代码


其中有很多类和函数已经封装,例如:

FlyerManager.Free(),内部实现了子弹的回收,便于再利用。

再如TargetPick和AttackOn,实现了拾取最合适的游戏单位和计算打击伤害的功能。

 

 

 

 

 

中篇

 

文说明在弹道实现中,关于u3d的物理引擎的一些相关要点,并跟给了实体性子弹的关键实现代码。

本篇中,将继续说明射线型弹道的实现的。

四、射线型子弹

本章先讲子弹的碰撞逻辑实现,由于射线型子弹用u3d的sprite是绘制不出来的,所有需要特殊的技巧,绘制方法在下一章中说明。

使用Physic2D库,进行非自动碰撞检测

区别于实体子弹,在游戏中,我需要实现类似于激光射线、范围伤害(AOE)的攻击类型。

这种情况下,就不能依靠rigidbody来实现碰撞的检测,前文中又说了,u3d的物理引擎不支持两个collider的碰撞检测。

所以,没有办法“自动”做碰撞检测(这里所谓自动,就是去实现一个函数,然后等着u3d在碰撞发生时自动调用)

我依然使用u3d的2D物理库,在每一帧(每个Update)中,对目标collider进行检测。

例如,AOE伤害:

RaycastHit2D[] hits = Physics2D.CircleCastAll(transform.position, m_Info.aoeRadius, Vector2.zero,
     LayerManager.GetLayer(m_Faction).oppUnitMask );

使用Physics2D的CircleCastAll、RaycastAll、BoxCastAll,对指定layer中的所有collider,做圆形、射线、长方形的碰撞检测。

即,这里不再用collider和collider,而是判断指定的圆形、射线、长方形,和哪些collider碰撞(Cast),包括相交和包含。

这些函数有一个All和非All的版本,返回所有检测到collider或者最近的collider。

返回值RaycastHit包含collider、point(碰撞点)、normal(碰撞法线)等,具体请参考u3d api,这里不再累述。

和上文说的Collider的碰撞检测一样,碰撞的代码实现放在Update里,可能出现“嵌入过多”或“穿过”的情况。

但由于,需求本身是针对非实体子弹的,其面积、范围比子弹大很多,所有没有太严重的影响。

(如果放在FixedUpdate会精确很多,但消耗太大)。

几种弹道类型的攻击逻辑实现

穿透激光

穿透激光,可以对射线上的单位造成伤害。

其攻击的实现代码:

复制代码

IEnumerator _TakeAttack ()
{
        yield return new WaitForSeconds (RAY_TAKE_ATTACK); 

        Vector3 v = Utils.Up (transform);
        Vector3 worldCenter = transform.position + v * m_Info.attackDistance / 2;
        Vector2 size = new Vector2 (width, m_Info.attackDistance);

        RaycastHit2D[] hits = 
            Physics2D.BoxCastAll (worldCenter, size, 
                Utils.DirToAngle(v), new Vector2 (0, 0),
                Mathf.Infinity,LayerManager.GetLayer(m_Faction).oppUnitMask);  

      m_Filter.Clear();
      for (int s = 0; s < hits.Length; ++s)
      {
        TargetPick pick = TargetPick.From (ref hits [s]);
        pick.ToShield();
        if( pick )
          if( m_Filter.Test(ref pick) )
        m_Info.AttackOn (pick, v, m_myUnit,hitEffectType,null);
      }

}

复制代码

注意,攻击使用StartCoroutine做一个延时,这是因为,为了动画效果更真实,在做激光的绘制时,有一个很快的激光射线变长的过程,所以攻击的实际效果要和增长时间配合。

代码里面有很多游戏逻辑相关的东西,不用太关注,关键是要获取到一个box的形状描述,需要知道Up,Center,Size。

这里把激光看成一个很长的box,长度是激光的最大攻击距离。

最后,由于有些单位有多个Collider,所有需要过滤一下(即m_Filter),原理很简单:

1、找hit或collider对应的单位(TargetPick.From)

2、检查单位在过滤器中是否已经存在。存在就不在处理,不存在就继续,并添加到过滤器中(m_Filter.test)

3、进行伤害的计算逻辑(m_Info.Attack)

定向激光(持续)

定向激光对指定的单位进行攻击。

定向激光不需要通过碰撞检测去“探测”激光与哪些collider相交的,因为定向激光是对已指定的单位进行持续攻击。

需要解决的问题,激光与单位的碰撞点到底在哪。根据这个碰撞点,绘制激光的形状。

其攻击的实现代码:

复制代码

    public bool _TakeAttack (out Vector3 point)
    {
        point = Vector3.zero;
        Vector3 v = Utils.Up (transform);
        // The colliders in the array are sorted in order of distance from the origin point
        RaycastHit2D[] hits = Physics2D.RaycastAll (transform.position, v, m_Info.attackDistance,
            LayerManager.GetLayer(m_Faction).oppUnitMask);

        // 是否有target的hit
        TargetPick pick = TargetPick.none;
        for (int s = 0; s < hits.Length; ++s)
        {
            pick = TargetPick.IsTarget (target, ref hits [s]);
            if (pick)
                break;
        }

        if (pick)
        {
            point = pick.point;
            m_Info.AttackOn (pick, v, m_myUnit, Const.NONE_EFFECT, this);
            if (pick.unit && pick.unit.curHp <= 0)
                return false;

            if (pick.part && pick.part.unit.curHp <= 0)
                return false;

            return true;
        }
        return false;
    }

复制代码

攻击函数返回是否攻击到对象单位(target),并返回攻击点。和穿透激光的区别,使用RayCastAll目的只是找到要攻击的对象是否在其中,并确定碰撞点。

定向激光(单次攻击)

类似于图中的闪电效果。原理持续的定向激光基本一致,区别是单次攻击时播放闪电(或其他效果)动画。

同持续定向激光一样,在攻击过程中不停的用RayCast判断闪电是否“打”到了目标上,

如果没有需要立即中断攻击和攻击动画,否则攻击单位在出现突然转身的是否,闪电会随着攻击攻击单位移动,出现bug。

不再给出代码。

范围攻击

典型的是喷火器或者爆炸,在一定范围内所有单位收到伤害。

一时找不到图。后面补。

复制代码

    bool _TakeAttack ()
    {
        Vector3 dir = Utils.Up (transform);

        m_Filter.Clear ();
        RaycastHit2D[] hits = Physics2D.CircleCastAll (transform.position, m_Info.attackDistance, Vector2.zero,
                                  Mathf.Infinity, LayerManager.GetLayer(m_Faction).oppUnitMask);
        
        bool f = false;
        // 是否有target的hit
        for (int s = 0; s < hits.Length; ++s)
        {
            Vector3 v = Utils.V2toV3 (hits [s].point) - transform.position;
            if (Mathf.Abs (Vector3.Angle (dir, v)) < m_Info.attackArc / 2)
            {
                f = true;
                TargetPick pick = TargetPick.From (ref hits [s]);
                // AOE CircleCastAll 可能选不到shield
                pick.ToShield ();
                if (pick)
                if (m_Filter.Test (ref pick))
                    m_Info.AttackOn (pick, Vector3.zero, null, Const.NONE_EFFECT ,this);
            }

        }

        return f;
    }

复制代码

原理很简单,先找到圆形范围内的所有collider,再判断是否在喷火器的扇形角度内。

 

攻击、弹道这块内容游戏逻辑是游戏的一个重点,其实很难写一辆篇文章说清,其实我很想把从最下层的u3d的物理、绘制到最上层代码逻辑架构 全部说清楚,

但发现写一篇文博耗费的时间比我想象长,长到我写代码实现的一个功能的时间还没写一篇文章长。

所以,我考虑了下,不能指望所有的东西全部说清楚,最要还是讲原理,不同于网上大部分的教程讲的是最基础的内容,甚至是解释api,

而是建立读者有一定基础上,讲原理、讲结构,讲自认为的难点,有助于自己梳理游戏代码,也是对关键的技术点做一个备忘。

下篇预告:弹道的图形效果实现、攻击逻辑的结构和要点(比如本文中展示代码中定义的类的意义)

 

 

下篇

 

EffectRTGenerator
EffectRTGenerator是用来做渲染到纹理的辅助类。

通过Inst方法来访问EffectRTGenerator,Inst内部会在首次调用时创建一个实例,不需要在Scene预先放一个RTCamera碍眼,让开发人员完全不用去理会rt生成的细节
CreateOutterline方法会创建一个轮廓的rendertexture。CreateOutterline的逻辑框架是:
1、_Begin(),生成rt,移动target
2、RenderWithShader
3、_End(),还原target
4、根据需求处理纹理
类的定位:EffectRTGenerator放在场景中“非常远”的位置,专门用来做渲染到纹理,其他对象有rt的相关需求时,只需要调用相关的方法即可生成rt。这个类的定位不仅仅是做轮廓rt的生成,而是扩展并支持各种类似rt的生成。例如,可以加入生成“发光”、“烟雾“等贴图,只要按CreateOutterline的模式进行改造就行了。
**注意:**CreateOutterline不能在target的Awake和Start里调用。
代码:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Camera))]
public class EffectRTGenerator : MonoBehaviour
{
    //public int RTGenLayer = 31;

    static EffectRTGenerator m_Instance;

    public static EffectRTGenerator Inst()
    {
        if (m_Instance == null)
        {
            GameObject rtCamera = new GameObject("RTCamera");
            rtCamera.transform.position = new Vector3(9999999, 9999999, -10);
            Camera c = rtCamera.AddComponent<Camera>();
            c.clearFlags = CameraClearFlags.Color;
            c.backgroundColor = Color.black;
            c.orthographic = true;
            c.orthographicSize = 5;
            c.nearClipPlane = 0.3f;
            c.farClipPlane = 1000f;
            c.enabled = false;
            m_Instance = rtCamera.AddComponent<EffectRTGenerator>();    
        }
        return m_Instance;
    }

    Material m_SolidMat = null;
    Material m_BlurMaterial = null;
    Material m_CutoffMaterial = null;
    Material m_CompositeMaterial = null;

    Vector3 m_TargetOldPosition;
    Vector3 m_TargetOldEulerAngles;
    Vector3 m_TargetOldScale;
    int m_TargetOldLayer;

    public void Awake()
    {
        m_Instance = this;

        m_SolidMat = new Material(Shader.Find("Outterline/Solid"));
        m_SolidMat.hideFlags = HideFlags.HideAndDontSave;
        //m_SolidMat.shader.hideFlags = HideFlags.None;

        m_BlurMaterial = new Material(Shader.Find("Outterline/ShapeBlur"));
        m_BlurMaterial.hideFlags = HideFlags.HideAndDontSave;
        //m_BlurMaterial.shader.hideFlags = HideFlags.None;

        m_CutoffMaterial = new Material(Shader.Find("Outterline/CutoffBody"));
        m_CutoffMaterial.hideFlags = HideFlags.HideAndDontSave;
        //m_CutoffMaterial.shader.hideFlags = HideFlags.None;

        m_CompositeMaterial = new Material(Shader.Find("Outterline/Composer"));
        m_CompositeMaterial.hideFlags = HideFlags.HideAndDontSave;
        //m_CompositeMaterial.shader.hideFlags = HideFlags.None;

        // 设置到一个极其远的地方,以便于只渲染目标
        transform.position = new Vector3(99999, 99999, -10);
        //GetComponent<Camera>().cullingMask = 1 >> RTGenLayer;

    }

    void OnDestroy()
    {
        DestroyImmediate(m_SolidMat);
        DestroyImmediate(m_BlurMaterial);
        DestroyImmediate(m_CutoffMaterial);
        DestroyImmediate(m_CompositeMaterial);

    }

    public RenderTexture _Begin(GameObject target, float x, float y)
    {
        // 根据输入的x和y,创建一个rt
        RenderTexture rt = new RenderTexture((int)x, (int)y,0);

        rt.useMipMap = true;
        rt.filterMode = FilterMode.Bilinear;
        rt.depth = 0;
        rt.generateMips = true;
        rt.Create();
        GetComponent<Camera>().targetTexture = rt;

        // 调整cemara的范围以适应rt
        float cameraSize = y / 2;
        GetComponent<Camera>().orthographicSize = cameraSize;
        //float r = (float)Screen.width / Screen.height;
        //float r_x =  x / ( y * 1);
        //GetComponent<Camera>().orthographicSize = cameraSize;
        //GetComponent<Camera>().rect = new Rect(0, 0, r_x, 1);

        // 保持旧的位置
        m_TargetOldPosition = target.transform.position;
        m_TargetOldEulerAngles = target.transform.eulerAngles;
        m_TargetOldScale = target.transform.localScale;
        //m_TargetOldLayer = target.layer;

        // 移动目标到摄像机处,保证只有目标在摄像机的范围内
        // 由于此用一个极远的位置,此处只有目标,所有可以不用设置layer
        Vector3 pos = transform.position;
        pos.z = 0;
        target.transform.localScale = Vector3.one;
        target.transform.position = pos;
        target.transform.eulerAngles = Vector3.zero;
        //target.layer = RTGenLayer;

        return rt;
    }

    void _End(GameObject target)
    {
        // 还原
        target.transform.position = m_TargetOldPosition;
        target.transform.eulerAngles = m_TargetOldEulerAngles;
        target.transform.localScale = m_TargetOldScale;
        //target.layer = m_TargetOldLayer;
    }

    public RenderTexture CreateOutterline(GameObject target, float x, float y)
    {
        int iterations = 5;
        float spread = 0.5f;

        RenderTexture rt = _Begin(target, x, y);

        GetComponent<Camera>().RenderWithShader(m_SolidMat.shader, "");

        _End(target);

        iterations = Mathf.Clamp(iterations, 0, 15);
        spread = Mathf.Clamp(spread, 0.5f, 6.0f);

        RenderTexture buffer = RenderTexture.GetTemporary(rt.width, rt.height, 0);
        RenderTexture buffer2 = RenderTexture.GetTemporary(rt.width, rt.height, 0);
        //buffer2.filterMode = FilterMode.Bilinear;
        //buffer.filterMode = FilterMode.Bilinear;
        //rt.filterMode = FilterMode.Bilinear;
        Graphics.Blit(rt, buffer);

        bool oddEven = true;
        for (int i = 0; i < iterations; i++)
        {
            if (oddEven)
                _FourTapCone(buffer, buffer2, i, spread);
            else
                _FourTapCone(buffer2, buffer, i, spread);
            oddEven = !oddEven;
        }
        if (oddEven)
        {
            Graphics.Blit(rt, buffer, m_CutoffMaterial);
            Graphics.Blit(buffer, rt, m_CompositeMaterial);
        }
        else
        {
            Graphics.Blit(rt, buffer2, m_CutoffMaterial);
            Graphics.Blit(buffer2, rt, m_CompositeMaterial);
        }       

        RenderTexture.ReleaseTemporary(buffer);
        RenderTexture.ReleaseTemporary(buffer2);
        return rt;
    }

    void _FourTapCone(RenderTexture source, RenderTexture dest, int iteration, float spread)
    {
        float off = 0.5f + iteration * spread;
        Graphics.BlitMultiTap(source, dest, m_BlurMaterial,
            new Vector2(off, off),
            new Vector2(-off, off),
            new Vector2(off, -off),
            new Vector2(-off, -off)
        );
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
OutterlineRenderer
OutterlineRenderer是目标GameObject的子GameObject,用来绘制轮廓。

提供CreateOutlineRenderer静态方法,target调用该静态方法来创建一个OutterlineRenderer子节点。
FindOrCreateOutlineRenderer静态方法是对CreateOutlineRenderer的封装,对于已存在OutterlineRenderer则不再创建。
通过color属性,设置轮廓颜色
代码:

using UnityEngine;
using System.Collections;

public class OutterlineRenderer : MonoBehaviour
{
    Material m_Material;
    public RenderTexture m_RT;

    public static OutterlineRenderer FindOrCreateOutlineRenderer(Unit target)
    {
        Transform t =  target.transform.Find("OutterlineRenderer");
        if (t == null)
            return CreateOutlineRenderer(target);
        else
            return t.GetComponent<OutterlineRenderer>();
    }

    public static OutterlineRenderer CreateOutlineRenderer(Unit target)
    {
        float x = target.bound.size.x * 2;
        float y = target.bound.size.y * 2;
        RenderTexture rt = EffectRTGenerator.Inst().CreateOutterline(target.gameObject, x, y);

        string path = "Misc/OutterlineRenderer";
        GameObject type = Resources.Load<GameObject>(path);
        if (type == null)
        {
            rt.Release();
            return null;
        }
        GameObject go = GameObject.Instantiate(type);
        go.name = type.name;

        go.GetComponent<OutterlineRenderer>().Init(rt, x, y, Color.white);

        go.transform.parent = target.transform;
        go.transform.localPosition = new Vector3(0, 0, 1f);
        go.transform.localScale = Vector3.one;
        go.transform.localEulerAngles = Vector3.zero;

        return go.GetComponent<OutterlineRenderer>();
    }

    public Color color
    {
        get{
            return gameObject.GetComponent<MeshRenderer>().material.color;
        }
        set{
            gameObject.GetComponent<MeshRenderer>().material.color = value;  
        }

    }

    void Init(RenderTexture rt, float x, float y ,Color color)
    {
        Mesh mesh; 

        MeshRenderer renderer = gameObject.GetComponent<MeshRenderer>(); 
        mesh = GetComponent<MeshFilter>().mesh;

        /* 6 7
         * 4 5
         * 2 3
         * 0 1
         */

        int sectionCount = 1;
        int[] triangles = new int[ sectionCount * 6];  
        Vector3[] vertices = new Vector3[ sectionCount * 4];
        Color[] colors = new Color[ sectionCount * 4];
        Vector2[] uv = new Vector2[ sectionCount * 4];
        for (int i = 0; i < sectionCount; i++)
        {  
            vertices[4 * i] = new Vector2(-x / 2, -y / 2);
            vertices[4 * i + 1] = new Vector2(x / 2, -y / 2);
            vertices[4 * i + 2] = new Vector2(-x / 2, y / 2);
            vertices[4 * i + 3] = new Vector2(x / 2, y / 2);
            colors[4 * i] = Color.white;
            colors[4 * i + 1] = Color.white;
            colors[4 * i + 2] = Color.white;
            colors[4 * i + 3] = Color.white;
            uv[4 * i] = new Vector2(0, 0);
            uv[4 * i + 1] = new Vector2(1, 0);
            uv[4 * i + 2] = new Vector2(0, 1);
            uv[4 * i + 3] = new Vector2(1, 1);
        }  

        for (int i = 0; i < sectionCount; i++)
        {  
            triangles[6 * i] = (i * 4) + 0;  
            triangles[6 * i + 1] = (i * 4) + 3;  
            triangles[6 * i + 2] = (i * 4) + 1; 
            triangles[6 * i + 3] = (i * 4) + 0;  
            triangles[6 * i + 4] = (i * 4) + 2;  
            triangles[6 * i + 5] = (i * 4) + 3; 
        }

        mesh.vertices = vertices;  
        mesh.triangles = triangles;
        mesh.colors = colors;
        mesh.uv = uv;

        m_RT = rt;
        m_Material = new Material(Shader.Find("Outterline/Render"));
        m_Material.hideFlags = HideFlags.HideAndDontSave;
        renderer.material = m_Material;
        renderer.material.color = color;
        renderer.material.mainTexture = rt;

    }

    void OnDestroy()
    {
        DestroyImmediate(m_Material);
        DestroyImmediate(m_Material);
    }

    public void Hide()
    {
        gameObject.SetActive(false);
    }

    public void Show()
    {
        gameObject.SetActive(true);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值