欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-13 08:26:26
...

前言

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

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

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

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

 

思路

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

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

 

 

 

 

 

 

 

最初想法

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

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

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

改进

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

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

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

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

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

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;
    }


}