文章目录
一、前言
小地图功能在很多游戏中都有,特别是RPG
游戏,在Unity
中如何实现小地图功能呢,我来教你。
二、思路
专门创建一个顶视角的摄像机,然后摄像机有个Target Texture
属性,可以将渲染的图形输出到Render Texture
中,我们使用UGUI
的Raw Image
来显示这张Render Texture
即可。
三、GitHub工程
本文的Demo
工程我已上传到GitHub
,感兴趣的同学可以自行下载进行学习。注:我用的Unity
版本是:Unity 2020.1.2f1c1 (64-bit)
。
GitHub工程地址:https://github.com/linxinfa/Unity-Minimap
四、具体步骤
1、创建一个顶视角摄像机
2、创建Render Texture
创建Render Texture
。
Render Texture
赋值给顶视角摄像机的Target Texture
。
3、使用Raw Image显示
创建一个RawImage
将RenderTexture
赋值给RawImage
的Texture
属性
四、运行效果
运行Unity
效果如下
五、优化
1、Mask蒙版边缘锯齿问题
上面小地图通过了Mask
组件实现了圆形蒙版的效果。有一个小小的缺点,就是圆形边缘有锯齿。放大看一看就可以看到
2、解决方案
首先我们的蒙版图片如下,是一个白色的圆,周围是透明的。
我们知道颜色值的表示是:(r, g, b, a)
,白色就是(1, 1, 1, 1)
,透明部分的颜色为(0, 0, 0, 0)
。
摄像机渲染出来的小地图RenderTexture
如下。
如果我们取某个像素点的颜色看,它可能是这样子的(0.31, 0.15, 0.7, 1)
。
想要让小地图以蒙版图的形状显示,我们只需取蒙版图的alpha
通道作为小地图的alpha
通道即可。
对应成shader
代码就是
fixed4 frag(v2f IN) : SV_Target
{
//原图
half4 color = tex2D(_MainTex, IN.texcoord) * IN.color;
//蒙版图
half4 color2 = tex2D(_Mask, IN.texcoord);
//以蒙版图的alpha通道作为原图的alpha通道输出
return fixed4(color.r, color.g, color.b, color2.a);
}
现在我们只自定义一个shader
(完整shader
代码见下文),赋值给一个材质球,然后把材质球赋值给小地图RawImage
的Material
即可
我们放大效果对比看看,左边是UGUI
的Mask
效果,右边是我们通过shader
实现的Mask
效果,可以看到锯齿基本没有了,平滑了很多。
六、加个主角和摄像机跟随逻辑
主角,我就用一个正方体Cube
替代了,加上WASD
控制移动逻辑,然后主摄像机和顶视角摄像机跟随逻辑,再对比展示两种方式实现的小地图效果,最终效果如下,详细代码见下文或直接下载我的Demo
工程学习
七、代码部分
1、shader脚本:Default-Mask.shader
Shader " UIEx/Default-Mask"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Mask ("Mask Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
half2 texcoord : TEXCOORD0;
};
fixed4 _Color;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
#ifdef UNITY_HALF_TEXEL_OFFSET
OUT.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
#endif
OUT.color = IN.color * _Color;
return OUT;
}
sampler2D _MainTex;
sampler2D _Mask;
fixed4 frag(v2f IN) : SV_Target
{
//原图
half4 color = tex2D(_MainTex, IN.texcoord) * IN.color;
//蒙版图
half4 color2 = tex2D(_Mask, IN.texcoord);
//以蒙版图的alpha通道作为原图的alpha通道输出
return fixed4(color.r, color.g, color.b, color2.a);
}
ENDCG
}
}
}
2、主角移动控制脚本:PlayerCtrler.cs
//PlayerCtrler.cs
using UnityEngine;
/// <summary>
/// 角色移动控制
/// Author: linxinfa
/// </summary>
public class PlayerCtrler : MonoBehaviour
{
/// <summary>
/// 移动速度
/// </summary>
public float speed;
private Transform m_transform;
private Vector3 m_curPos;
void Start()
{
m_transform = transform;
}
void Update()
{
var delta = Time.deltaTime* speed;
m_curPos = m_transform.position;
if (Input.GetKey(KeyCode.W))
{
m_curPos += new Vector3(0, 0, 1) * delta;
}
else if(Input.GetKey(KeyCode.S))
{
m_curPos -= new Vector3(0, 0, 1) * delta;
}
if (Input.GetKey(KeyCode.A))
{
m_curPos -= new Vector3(1, 0, 0) * delta;
}
else if (Input.GetKey(KeyCode.D))
{
m_curPos += new Vector3(1, 0, 0) * delta;
}
m_transform.position = m_curPos;
}
}
3、主摄像机跟随脚本:PlayerCameraFollow.cs
//PlayerCameraFollow.cs
using UnityEngine;
/// <summary>
/// 第三人称视角摄像机跟随
/// Author: linxinfa
/// </summary>
public class PlayerCameraFollow : MonoBehaviour
{
public Transform targetPlayer;
/// <summary>
/// 距离
/// </summary>
public float distanceAway = 1.7f;
/// <summary>
/// 高度
/// </summary>
public float distanceUp = 1.3f;
/// <summary>
/// 平滑度
/// </summary>
public float smooth = 2f;
private Vector3 m_targetPos;
private Transform m_cameraTransform;
void Awake()
{
m_cameraTransform = transform;
}
void LateUpdate()
{
if (null == targetPlayer) return;
// 计算摄像机的目标坐标
m_targetPos = targetPlayer.position + Vector3.up * distanceUp - targetPlayer.forward * distanceAway;
// 插值设置坐标,有个平滑跟随效果
m_cameraTransform.position = Vector3.Lerp(m_cameraTransform.position, m_targetPos, Time.deltaTime * smooth);
// 设置摄像机角度,对准跟随目标
m_cameraTransform.LookAt(targetPlayer);
}
}
4、顶视角摄像机跟随脚本:MiniMapCameraFollow.cs
//MiniMapCameraFollow.cs
using UnityEngine;
/// <summary>
/// 小地图摄像机跟随
/// Auhor: linxinfa
/// </summary>
public class MiniMapCameraFollow : MonoBehaviour
{
public Transform targetPlayer;
/// <summary>
/// 高度
/// </summary>
public float distanceUp = 1.3f;
private Transform m_cameraTransform;
void Awake()
{
m_cameraTransform = transform;
}
void LateUpdate()
{
if (null == targetPlayer) return;
m_cameraTransform.position = targetPlayer.position + Vector3.up * distanceUp;
}
}