【游戏算法】2D游戏中聚光灯效果

前言

前几日在游戏技术群里吹水时,有个人问了下面图片中的效果怎么做

当时我就觉得这个效果应该不难,研究了一段时间后,便在Unity里完成了2D聚光灯的效果,闲言少叙,先上图!

思路

首先可以确定的是,这个效果肯定是要结合碰撞体去做的,从光源点发射射线去检测碰撞,再将碰撞点按照顺序整合成一个网格面片,就可以做出聚光灯效果。

问题就在于怎么去检测碰撞..................................

最初想法

我最初的想法是:光源点向着碰撞体的每个顶点P发射一条射线,当检测到碰撞,并且检测到的点不为P时,便将该点存储起来用于生成网格面片,如下图

但是后来发现这个方法有问题,做出来的效果就像后面那张图一样。很显然,这个方法行不通,射线检测后,虽然检测到了碰撞点,但是这些点并不能完整的组成一个网格面片。例如:点1和点2之间、点2和点3之间其实应该再连接一个蓝色的点,但是该蓝色点并没有算进去;点4并不应该算进去等.......

改进

原先的方法虽然行不通,但不是彻底没啥用。后面为了检测这些特殊的点,在原来的基础上,我采用了将原射线一分为二的方法,原射线分别向左和向右偏移,再去检测碰撞:

左右两边各检测一遍,这样就可以正确检测点了。

最后,上代码!注释全都有!拷走直接用!!!

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

[RequireComponent(typeof(EdgeCollider2D))]
public class Light2D : MonoBehaviour
{
    [Range(0.1f,179.9f)]
    public float range;
    public float length;
    public LayerMask castLayers;
    public Collider2D[] colliders;//用于检测的碰撞盒
    MeshFilter meshFilter;
    EdgeCollider2D myCollider;
    Vector2 start, end;//光的左边和右边
    /// <summary>
    /// 射线向左和向右偏移的距离
    /// </summary>
    float rayOffsetDis = 0.001f;
    /// <summary>
    /// 用于检测点的射线
    /// </summary>
    Vector2[] rays;
    /// <summary>
    /// 用于生成光面片的顶点
    /// </summary>
    List<Vector2> vertics;

    private void Awake()
    {
        myCollider = GetComponent<EdgeCollider2D>();
        meshFilter = GetComponent<MeshFilter>();
    }

    // Start is called before the first frame update
    void Start()
    {
        start = Quaternion.AngleAxis(-range / 2, transform.forward) * -transform.up;
        end = Quaternion.AngleAxis(range / 2, transform.forward) * -transform.up;
        GenerateCollider();
    }

    // Update is called once per frame
    void Update()
    {
        start = Quaternion.AngleAxis(-range / 2, transform.forward) * -transform.up;
        end = Quaternion.AngleAxis(range / 2, transform.forward) * -transform.up;
        Calculate();
        Debug.DrawRay(transform.position, start,Color.red);
        Debug.DrawRay(transform.position, end,Color.green);
    }

    private void OnDrawGizmos()
    {
        if(vertics == null)
        { return; }
        for (int i = 0; i < vertics.Count; i++)
        {
            Gizmos.color = new Color((float)i / vertics.Count, 0, 0, 1);
            Gizmos.DrawSphere(vertics[i], 0.003f);
            Gizmos.DrawLine(transform.position, vertics[i]);
        }
    }
    /// <summary>
    /// 生成一个扇形碰撞体
    /// </summary>
    void GenerateCollider()
    {
        List<Vector2> ps = new List<Vector2>();
        float angAdd = 5f;
        float ang = 0;
        do
        {
            Vector3 dir = Quaternion.AngleAxis(ang, transform.forward) * -transform.right;
            ps.Add(dir * length);
            ang += angAdd;
        } while (ang <= 180);
        this.myCollider.points = ps.ToArray();
    }
    /// <summary>
    /// 计算光
    /// </summary>
    void Calculate()
    {
        rays = GetRays();
        vertics = new List<Vector2>();
        vertics.Add(transform.position);//网格顶点第一个为本身坐标
        foreach (var item in rays)
        {
            var hit = Physics2D.Raycast(transform.position, item, length, castLayers);//射线检测,检测到碰撞点就加入顶点列表
            if(hit.collider != null)
                vertics.Add(hit.point);
        }
        GenerateMesh();
    }
    /// <summary>
    /// 生成面片
    /// </summary>
    void GenerateMesh()
    {
        Mesh mesh = new Mesh();
        int[] tris = new int[vertics.Count * 3 - 6];
        for (int i = 0; i < tris.Length/3; i ++)
        {
            tris[i * 3] = 0;
            tris[i * 3 + 1] = i + 1;
            tris[i * 3 + 2] = i + 2;
        }
        var vts = new Vector3[vertics.Count];
        for (int i = 0; i < vts.Length; i++)
        {
            vts[i] = transform.InverseTransformPoint(vertics[i]);
        }
        mesh.SetVertices(vts);
        mesh.triangles = tris;
        mesh.RecalculateNormals();
        mesh.RecalculateTangents();
        meshFilter.mesh = mesh;
    }

    /// <summary>
    /// 获取所有用于检测的射线
    /// </summary>
    /// <returns></returns>
    Vector2[] GetRays()
    {
        List<Vector2> rays = new List<Vector2>();
        rays.Add(start);
        rays.Add(end);
        foreach (var col in colliders)
        {
            var colPoints = GetColliderPoints(col);
            foreach (var point in colPoints)
            {
                Vector3 p = point;
                Vector3 dir = p - transform.position;//光源点到碰撞盒点的向量
                if (Vector3.Cross(start, dir).z <= 0 || Vector3.Cross(end, dir).z >= 0)//如果超出光的边界,则不计算该点
                    continue;

                Vector3 dir2 = Vector3.Cross(dir, Vector3.forward).normalized;//用于计算射线偏移的向量

                //分别得到向右和向左偏移的向量
                var p0 = p + dir2 * rayOffsetDis;
                var p1 = p - dir2 * rayOffsetDis;
                rays.Add(p0 - transform.position);
                rays.Add(p1 - transform.position);
            }
        }

        //下面根据从左到右的顺序重排射线,该步骤主要用于后面获取正确的网格面片顶点顺序
        for (int i = 0; i <= rays.Count - 1; i++)
        {
            for (int j = 0; j < rays.Count - i - 1; j++)
            {
                if (Vector2.Dot(-transform.right, rays[j].normalized) > Vector2.Dot(-transform.right, rays[j + 1].normalized))
                {
                    Vector2 t = rays[j];
                    rays[j] = rays[j + 1];
                    rays[j + 1] = t;
                }
            }
        }
        return rays.ToArray();
    }
    /// <summary>
    /// 获取组成碰撞盒的点的世界坐标
    /// </summary>
    /// <param name="collider2D"></param>
    /// <returns></returns>
    Vector2[] GetColliderPoints(Collider2D collider2D)
    {
        Vector2[] ps = new Vector2[0];
        if(collider2D is PolygonCollider2D)
        {
            ps = ((PolygonCollider2D)collider2D).points;
        }
        else if (collider2D is EdgeCollider2D)
        {
            ps = ((EdgeCollider2D)collider2D).points;
        }
        else if (collider2D is BoxCollider2D)
        {
            var bc = ((BoxCollider2D)collider2D);
            ps = new Vector2[4];
            ps[0] = bc.offset - bc.size * 0.5f;
            ps[1] = bc.offset + new Vector2(bc.size.x * -0.5f, bc.size.y * 0.5f);
            ps[2] = bc.offset + bc.size * 0.5f;
            ps[3] = bc.offset + new Vector2(bc.size.x * 0.5f, bc.size.y * -0.5f);
        }
        for (int i = 0; i < ps.Length; i++)
        {
            ps[i] = collider2D.transform.TransformPoint(ps[i]);
        }
        return ps;
    }


}

猜你喜欢

转载自blog.csdn.net/b8566679/article/details/108262163