【游戏算法】2D游戏中聚光灯效果
程序员文章站
2022-07-13 08:26:26
...
前言
前几日在游戏技术群里吹水时,有个人问了下面图片中的效果怎么做
当时我就觉得这个效果应该不难,研究了一段时间后,便在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;
}
}
上一篇: Unity--Stealth秘密行动开发(三):玩家移动
下一篇: c++代码轻松实现贪吃蛇小游戏