基于视野(FOV)的战争迷雾,例如LOL的视野:鼠标右键点击地板,目标移动,同时显示角色周围视野,鼠标滚轮可以调节远近。
Unity版本:2019.4.1f1
1.新建工程---右键3D Object---Terrain,随便刷一个地形,尽量高低错落,设置地形大小为100*100
2.导入文件,在Camera上添加Fog Of War Effect脚本,脚本会自动添加<Flare Layer>组件,Unity 2018之前的版本需要添加<GUI Layer>组件,2019以后版本被舍弃,无需添加
3.右键创建一个胶囊,模拟玩家,添加NavMeshAgent组件,添加Fog Of War Explorer脚本
using UnityEngine;
using UnityEngine.AI;
/// <summary>
/// 视野数据-由于计算fov是在子线程操作,通过将视野数据以object类型参数传入,
/// 使用简单数据类型或结构体会产生装箱操作,因此将视野封装成类
/// </summary>
public class FOWFieldData
{
public float radiusSquare;
public Vector3 position;
public float radius;
public FOWFieldData(Vector3 position, float radius)
{
this.position = position;
this.radius = radius;
this.radiusSquare = radius * radius;
}
}
/// <summary>
/// Player
/// </summary>
public class FogOfWarExplorer : MonoBehaviour
{
[Range(10, 25)] public float radius = 15;//视野半径
private Vector3 m_OriginPosition;
private FOWMapPos m_FowMapPos;
private FOWFieldData m_FieldData;
private bool m_IsInitialized;
private float distance = 24;//摄像机高度
private float angle = 45;//摄像机偏移角度
private Vector3 m_CameraPosition;
private NavMeshAgent m_Agent;
void Start()
{
m_FieldData = new FOWFieldData(transform.position, radius);
m_Agent = gameObject.GetComponent<NavMeshAgent>();
Camera.main.transform.rotation = Quaternion.Euler(angle, 0, 0);
Camera.main.transform.position = transform.position - Camera.main.transform.forward * distance;
}
void Update()
{
if (radius <= 0)
return;
if (m_OriginPosition != transform.position)
{
m_OriginPosition = transform.position;
var pos = FogOfWarEffect.WorldPositionToFOW(transform.position);
if (m_FowMapPos.x != pos.x || m_FowMapPos.y != pos.y || !m_IsInitialized)
{
m_FowMapPos = pos;
m_IsInitialized = true;
m_FieldData.position = transform.position;
m_FieldData.radius = radius;
//FogOfWarEffect.SetVisibleAtPosition(m_FieldData);
FogOfWarEffect.UpdateFOWFieldData(m_FieldData);
}
}
m_CameraPosition = transform.position - Camera.main.transform.forward * distance;
Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, m_CameraPosition, Time.deltaTime * 3f);
BasicOperation();
}
void OnDestroy()
{
if (m_FieldData != null)
FogOfWarEffect.ReleaseFOWFieldData(m_FieldData);
m_FieldData = null;
}
void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, radius);
}
void BasicOperation()
{
if (Input.GetMouseButtonDown(1))//鼠标右键点击地板
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
m_Agent.SetDestination(hit.point);
}
}
if (Input.GetAxis("Mouse ScrollWheel") > 0)
{
if (distance >= 7)
distance -= 2;
}
if (Input.GetAxis("Mouse ScrollWheel") < 0)
{
if (distance <= 35)
distance += 2;
}
}
}
4.Scene视角切换成俯视视角,方便调整参数。调整战争迷雾蒙版类型、宽度、高度、战争迷雾中心位置,将特效shader、模糊shader、小地图shader拖入Main Camera上的Fog Of War Effect上
using ASL.FogOfWar;
using System.Collections.Generic;
using UnityEngine;
public struct FOWMapPos
{
public int x;
public int y;
public FOWMapPos(int x, int y)
{
this.x = x;
this.y = y;
}
}
/// <summary>
/// 屏幕空间战争迷雾
/// </summary>
[RequireComponent(typeof(FlareLayer))]
public class FogOfWarEffect : MonoBehaviour
{
//迷雾蒙版类型
public enum FogMaskType
{
/// <summary>
/// 精确计算的FOV
/// </summary>
AccurateFOV,
/// <summary>
/// 基础FOV
/// </summary>
BasicFOV,
/// <summary>
/// 简单圆形
/// </summary>
Circular,
}
//实例化
public static FogOfWarEffect Instance
{
get
{
if (instance == null)
instance = FindObjectOfType<FogOfWarEffect>();
return instance;
}
}
private static FogOfWarEffect instance;
/// <summary>
/// 迷雾蒙版类型
/// </summary>
public FogMaskType fogMaskType { get { return m_FogMaskType; } }
/// <summary>
/// 战争迷雾颜色(RGB迷雾颜色,Alpha已探索区域透明度)
/// </summary>
public Color fogColor { get { return m_FogColor; } }
/// <summary>
/// 迷雾区域宽度
/// </summary>
public float xSize { get { return m_XSize; } }
/// <summary>
/// 迷雾区域高度
/// </summary>
public float zSize { get { return m_ZSize; } }
/// <summary>
/// 迷雾贴图宽度
/// </summary>
public int texWidth { get { return m_TexWidth; } }
/// <summary>
/// 迷雾贴图高度
/// </summary>
public int texHeight { get { return m_TexHeight; } }
/// <summary>
/// 迷雾区域中心坐标
/// </summary>
public Vector3 centerPosition { get { return m_CenterPosition; } }
public float heightRange { get { return m_HeightRange; } }
public Texture2D fowMaskTexture
{
get
{
if (m_Map != null)
return m_Map.GetFOWTexture();
return null;
}
}
public RenderTexture minimapMask
{
get
{
if (!m_GenerateMinimapMask)
return null;
return m_Renderer.GetMimiMapMask();
}
}
[Header("战争迷雾蒙版类型")]
[SerializeField]
private FogMaskType m_FogMaskType;
[Header("战争迷雾颜色")]
[SerializeField]
private Color m_FogColor = Color.black;
[Header("战争迷雾宽度")]
[SerializeField]
private float m_XSize = 77f;
[Header("战争迷雾高度")]
[SerializeField]
private float m_ZSize = 77f;
[Header("战争迷雾贴图宽度")]
[SerializeField]
private int m_TexWidth = 66;
[Header("战争迷雾贴图高度")]
[SerializeField]
private int m_TexHeight = 66;
[Header("战争迷雾中心位置")]
[SerializeField]
private Vector3 m_CenterPosition;
[Header("高度随机值")]
[SerializeField]
private float m_HeightRange = 1.23f;
/// <summary>
/// 模糊偏移量
///
/// </summary>
[Header("模糊偏移量")]
[SerializeField]
private float m_BlurOffset = 0.003f;
/// <summary>
/// 模糊迭代次数
/// </summary>
[Header("模糊迭代次数")]
[SerializeField]
private int m_BlurInteration = 2;
/// <summary>
/// 是否生成小地图蒙版
/// </summary>
private bool m_GenerateMinimapMask;
/// <summary>
/// 迷雾特效shader
/// </summary>
[Header("战争特效Shader")] public Shader effectShader;
/// <summary>
/// 模糊shader
/// </summary>
[Header("模糊Shader")] public Shader blurShader;
/// <summary>
/// 小地图蒙版渲染shader
/// </summary>
[Header("小地图蒙版渲染Shader")] public Shader minimapRenderShader;
[Header("更新周期")][Range(0.0f,1.0f)]public float UpdateTime = 1.0f;
/// <summary>
/// 预生成的地图FOV数据(如果为空则使用实时计算FOV)
/// </summary>
//public FOWPregenerationFOVMapData pregenerationFOVMapData;
/// <summary>
/// 战争迷雾地图对象
/// </summary>
private FOWMap m_Map;
/// <summary>
/// 战争迷雾渲染器
/// </summary>
private FOWRenderer m_Renderer;
private bool m_IsInitialized;
private float m_MixTime = 0.0f;
private float m_RefreshTime = 0.0f;
private float m_DeltaX;
private float m_DeltaZ;
private float m_InvDeltaX;
private float m_InvDeltaZ;
private Camera m_Camera;
private const float kDispearSpeed = 3f;
private const float kRefreshTextureSpeed = 4.0f;
private Vector3 m_BeginPos;
private List<FOWFieldData> m_FieldDatas;
private bool m_IsFieldDatasUpdated;
void Awake()
{
m_IsInitialized = Init();
}
void OnDestroy()
{
if (m_Renderer != null)
m_Renderer.Release();
if (m_Map != null)
m_Map.Release();
if (m_FieldDatas != null)
m_FieldDatas.Clear();
m_FieldDatas = null;
m_Renderer = null;
m_Map = null;
instance = null;
}
void FixedUpdate()
{
/*
更新迷雾纹理
*/
if (m_MixTime >= UpdateTime)
{
if (m_RefreshTime >= UpdateTime)
{
m_RefreshTime = 0.0f;
if (m_Map.RefreshFOWTexture())
{
m_Renderer.SetFogFade(0);
m_MixTime = 0;
m_IsFieldDatasUpdated = false;
//m_Renderer.SetFogTexture(m_Map.GetFOWTexture());
}
}
else
{
m_RefreshTime += Time.deltaTime * kRefreshTextureSpeed;
}
}
else
{
m_MixTime += Time.deltaTime * kDispearSpeed;
m_Renderer.SetFogFade(m_MixTime);
}
}
private bool Init()
{
if (m_XSize <= 0 || m_ZSize <= 0 || m_TexWidth <= 0 || m_TexHeight <= 0)
return false;
if (effectShader == null || !effectShader.isSupported)
return false;
m_Camera = gameObject.GetComponent<Camera>();
if (m_Camera == null)
return false;
m_Camera.depthTextureMode |= DepthTextureMode.Depth;
m_DeltaX = m_XSize / m_TexWidth;
m_DeltaZ = m_ZSize / m_TexHeight;
m_InvDeltaX = 1.0f / m_DeltaX;
m_InvDeltaZ = 1.0f / m_DeltaZ;
m_BeginPos = m_CenterPosition - new Vector3(m_XSize * 0.5f, 0, m_ZSize * 0.5f);
m_Renderer = new FOWRenderer(effectShader, blurShader, minimapRenderShader, m_CenterPosition, m_XSize, m_ZSize, m_FogColor, m_BlurOffset, m_BlurInteration);
m_Map = new FOWMap(m_FogMaskType, m_BeginPos, m_XSize, m_ZSize, m_TexWidth, m_TexHeight, m_HeightRange);
IFOWMapData md = gameObject.GetComponent<IFOWMapData>();
if (md != null)
m_Map.SetMapData(md);
else
{
m_Map.SetMapData(new FOWMapData(m_TexHeight, m_TexHeight));
m_Map.GenerateMapData(m_HeightRange);
}
if (minimapRenderShader != null)
m_GenerateMinimapMask = true;
return true;
}
/// <summary>
/// 世界坐标转战争迷雾坐标
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
public static FOWMapPos WorldPositionToFOW(Vector3 position)
{
if (!Instance)
return default(FOWMapPos);
if (!Instance.m_IsInitialized)
return default(FOWMapPos);
int x = Mathf.FloorToInt((position.x - Instance.m_BeginPos.x) * Instance.m_InvDeltaX);
int z = Mathf.FloorToInt((position.z - Instance.m_BeginPos.z) * Instance.m_InvDeltaZ);
return new FOWMapPos(x, z);
}
public static Vector2 WorldPositionTo2DLocal(Vector3 position)
{
if (!Instance)
return default(Vector2);
if (!Instance.m_IsInitialized)
return default(Vector2);
Vector2 pos = default(Vector2);
pos.x = (position.x - Instance.m_BeginPos.x) / Instance.m_XSize;
pos.y = (position.z - Instance.m_BeginPos.z) / Instance.m_ZSize;
return pos;
}
/ <summary>
/ 将指定位置设置为可见
/ </summary>
/ <param name="fieldData">视野</param>
//public static void SetVisibleAtPosition(FOWFieldData fieldData)
//{
// if (!Instance)
// return;
// if (!Instance.m_IsInitialized)
// return;
// if (fieldData == null)
// return;
// Instance.m_Map.SetVisible(fieldData);
//}
public static void UpdateFOWFieldData(FOWFieldData data)
{
if (!Instance)
return;
if (!Instance.m_IsInitialized)
return;
if (Instance.m_FieldDatas == null)
Instance.m_FieldDatas = new List<FOWFieldData>();
if (!Instance.m_FieldDatas.Contains(data))
{
Instance.m_FieldDatas.Add(data);
}
if (!Instance.m_IsFieldDatasUpdated)
{
//lock (Instance.m_FieldDatas)
{
Instance.m_Map.SetVisible(Instance.m_FieldDatas);
Instance.m_IsFieldDatasUpdated = true;
}
}
}
public static void ReleaseFOWFieldData(FOWFieldData data)
{
if (!instance)
return;
if (!instance.m_IsInitialized)
return;
//lock (instance.m_FieldDatas)
{
if (instance.m_FieldDatas != null && instance.m_FieldDatas.Contains(data))
instance.m_FieldDatas.Remove(data);
}
}
/// <summary>
/// 是否在地图中可见
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
public static bool IsVisibleInMap(Vector3 position)
{
if (!Instance)
return true;
if (!Instance.m_IsInitialized)
return true;
int x = Mathf.FloorToInt((position.x - Instance.m_BeginPos.x) * Instance.m_InvDeltaX);
int z = Mathf.FloorToInt((position.z - Instance.m_BeginPos.z) * Instance.m_InvDeltaZ);
return Instance.m_Map.IsVisibleInMap(x, z);
}
void OnRenderImage(RenderTexture src, RenderTexture dst)
{
if (!m_IsInitialized)
Graphics.Blit(src, dst);
else
{
m_Renderer.RenderFogOfWar(m_Camera, m_Map.GetFOWTexture(), src, dst);
}
}
void OnDrawGizmosSelected()
{
FOWUtils.DrawFogOfWarGizmos(m_CenterPosition, m_XSize, m_ZSize, m_TexWidth, m_TexHeight, m_HeightRange);
}
}
5.单击Terrain 右上角勾选静态Static,Windows----AI---Navigation,Bake---Bake
6.运行即可查看效果,你还可以添加小地图和右键点击动画等效果。
如果你的场景和我的不一样,那也没关系,我改了光照设置,不改也没问题,我的光照设置如下: