转载自http://blog.csdn.net/lzhq1982/article/details/12783493
在做这个demo的过程中,制作小地图着实刁难了我一把,百度了很多文章,花了好长的时间,需要的知识点实在太多了,尤其是shader语言,好在最后成功把它啃下来了,先声明一下,本篇文章将会是这个系列中最难的,不过如果成功做出来成就感也是大大的,其实按照我的步骤一步一步来也没那么复杂啦,接下来我把这个过程分享给大家,下面上一张截图:
看右上角,那个就是小地图,也许有点不太好看,没办法,谁让我不是美术啊,需要的图素都是自己用Photoshop做的,记得有人说过,不会美术的程序不是好策划,所以自己来吧。
先说一下原理,我们做小地图用的技术就是遮罩,玩过flash的对这个词应该不新鲜,何为遮罩呢,通俗一下讲,你有两张纸,一张纸上画了漂亮的山川河流,另一张纸就是张白纸,中间挖个圆洞,然后你把白纸盖在那张画满山川河流的纸上,只有中间圆洞的地方你能看到,其他地方都被盖住了,保持白纸不动,移动后面的画纸,你会看到连续的不同的地貌,我们的小地图原理就是这样的,时刻保持角色在中间,动的只是后面的背景罢了。只是这里需要多处理一下,就是除了那个圆洞,其他的部分我们要透明掉。
相信这么解释应该没有不懂的了吧。如果还不清楚那要么是我的表达能力有问题,要么你的智商。。。咳咳,言归正传,按照上面的理论,我们都该准备些什么呢。最起码的得有一张完整的地图吧,得有个圆形的遮罩吧,再漂亮点圆形遮罩需要个圆框,地图上得有个小标志显示玩家的位置和方向。
地图好搞,在unity中调到场景的顶视图,然后截个图就好了,缩放一下比例,我的地图是512*512的,取名map,如下图:
丑是丑了点,谁让我的地形简单呢,再一次声明,我是个程序。
遮罩就需要你自己画个了,要保证它和背景图一样大,我这里也是512*512的,中间是个白色的圆,其他地方alpha是透明的。如图:
我这里并没有截全部512的图,不要以为只这么小啊,除了白圆,其他地方都是透明的,其实什么颜色的无所谓,只要是圆的就行。
然后做个圆框,也是512的,圆框和白圆大小一致,中间和其他地方都是透明的。
最后是角色的那个小标记
资源就这些,准备好就可以开干了。
关于NGUI我这里就不解释了,看过我前面文章的童鞋应该知道怎么用了,我们要把小地图放在右上角的锚点上,不是简单的放几个sprite的事情,那我们需要做些什么呢。来,我们倒着说,先看结果,我希望我的右上角先有个Panel,然后Panel下有个Textrue,而我的小地图就是绘制在这个Texture上;那小地图是以什么方式绘制到这个Texture上的呢,当然是靠material(材质),而且是一个能实现遮罩,透明,动态渲染的材质。那只能靠材质中的shader了;先不说遮罩和透明,先说动态渲染,我们以前用到的材质都是静态的,而现在我们要用到的是地图可以移动的动态材质,所以要用动态渲染,要说动态渲染,那最先想到的就是Render Texture,要说什么是Render Texture,看这里好了:渲染纹理。由果推因,我们就知道都该做些什么了,一个一个攻克吧,现在我们由因及果。
1、绘制渲染纹理(Render Texture)
a、先在MainCamera下创建一个UI,选择NGUI->Open the UI Wizard,保持设置,点Create Your UI,然后Anchor下的Panel改名为MiniMapRenderPanel。
b、创建一个Atlas,不知道什么是Atlas的可以看我前面的文章,选NGUI->Open the Atlas Maker,新弹出的界面上修改你的atlas名称,点击那个完整的地图,就是我上面的那个map,然后点Create。
d、在资源里新建个Render Texture,Assets->Create->Render Texture,起名MiniMapRenderTexture。
e、设置camera,那个NGUI的camera设置如下:
Clear Flags设为Solid Color,Background设为黑色,这样当你走到地图的边缘时,没有地图的地方会绘制成黑色。Projection设为平行投影Orthographic,不了解平行投影和透视投影的话需要补一下3d基础了。Target Texture那里把之前做的那个Render Texture拖上去。这样摄像机投影的地方就会绘制在这个Render Texture上,也就是地图会绘制在它上面了,然后我们就可以做material了。
f、有一点差点忘了,Anchor那里的Side一定要选择TopLeft,地图的原点是从左下角开始的。
2、制作材质(material)
这是本篇的难点,要用到shader语言,如果没有3d基础的可能理解起来有点费劲,不懂也没关系,按着操作也能做出来。
a、创建一个Shader,Assets->Create->Shader。
b、打开shader,把下面的代码覆盖过去。
Shader "Transparent/Mask"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_Mask("Culling Mask",2D)="white"{}
_Cutoff("Alpha cutoff",Range(0,1))=0.1
}
SubShader
{
Tags { "Queue"="Transparent" }
Lighting Off
ZWrite Off
Blend Off
AlphaTest GEqual[_Cutoff]
Pass
{
SetTexture[_Mask]{combine texture}
SetTexture[_MainTex]{combine texture,previous}
}
}
}
你百度遮罩shader的话会有很多,但基本没有解释的,我这里解释一下,第一行是设置你的shader的路径和名称,比如上面的shader就建在了Transparent下,起名Mask,大括号里面有两个部分,第一个部分是属性Properties,第二部分是SubShader。
Properties里是设置渲染的属性,比如这里设置了两张图片和一个滑动条,第一张图片用来加载背景图片,就是我们之前做的那个Render Texture,用来显示地图,第二张图片就是遮罩图片,用我们之前的那个白圆,滑动条范围从0到1,初始值0.1,再看一下语法。
_MainTex ("Base (RGB)", 2D) = "white" {}
_MainTex是属性名,会在SubShader中用到,Base (RGB)是在界面上显示的名称,你会在属性面板上看到,2D是图片维数,white是默认值。
SubShader是处理渲染的主体,Tags是标签,Queue标签决定被渲染的次序,而Transparent是四个预定义的渲染队列之一,任何有关alpha混合的对象都应该在这里处理,看这里可以了解的更多: SubShader Tags ,关闭光照,关闭z缓冲器的写操作,关闭混合,Alpha测试是当Alpha大于等于你之前设定的_Cutoff时有效,也就是说这里当alpha大于等于0.1的图素会被渲染出来,其他的就透明了。Pass通道里处理混合,先设置第一张图片:SetTexture [_Mask] {combine texture},这里是遮罩图片,然后第二张图片和前一张混合:SetTexture [_MainTex] {combine texture, previous},而我们之前关闭了混合,所以第二张图片只是纯粹的显示,但大括号里第二个参数表示alpha,这里previous表示我们用之前那张图片的alpha,而第一张图片的alpha除了白圆部分,其余部分都是0,所以这张图片除了与白圆 重合的地方,其他地方alpha也是0,这样就透明了,只剩下了圆的地方,想了解Pass看这里: 着色器语法:Pass ,想了解SetTexture看这里: 着色器语法:Texturing 。
写了这么多,纯粹手打,觉得好的麻烦支持一下,哈哈。Shader有一定难度,但据说会shader的程序员薪水都在2万以上啊,望眼欲穿啊,所以大家努力学吧。
如果上面的没看懂,你又不想学,那就跳过吧,反正把代码粘过去就可以了。
c、创建一个material,Assets->Create->Material,然后Shader那里找到你建的那个shader,在Transparent下面。
d、这时界面上会出现Base(RGB)和Culling Mask,Base(RGB)里把你之前那个Render Texture拖上去,Culling Mask把那个白圆的图片拖上去,如下图:
这样你的材质就做完了。坚持到这步的为自己鼓个掌吧,你离成功不远了。
3、在界面的右上角用NGUI建地图
终于到了最后一步了,剩下的工作就简单多了,回到你的界面布局那里,有关NGUI界面布局不了解的看我前面的文章,在右上角的Anchor下建个Panel,起名MiniMapPanel,然后下面加两个sprite和一个Texture,一个sprite是圆框,一个sprite是小箭头,表示地图上的玩家,Texture用来接收之前的材质显示地图,如下图:
Texture那里大小调成512*512的,Material那里把之前做的材质拖上去就ok了,如果显示顺序有问题别忘了调Depth或Z值。
现在你应该可以看到东西了,但小箭头位置不对,当然,还没上代码呢。
创建一个MiniMap的脚本,我把全部代码贴上来。
public class MiniMap : MonoBehaviour {
// Use this for initialization
public GameObject point;
public GameObject map;
private GameObject hero;
private float miniMapScaleRatio;
void Start () {
map.transform.localScale = new Vector3(Screen.height, Screen.height, 1);
hero = GameObject.Find("/Blade_Girl_Prefab");
GameObject terrain = GameObject.Find("Terrain");
Terrain script = terrain.GetComponent<Terrain>();
miniMapScaleRatio = (float)map.transform.localScale.x / script.terrainData.size.x;
}
// Update is called once per frame
void Update () {
if (hero && point && map) {
point.transform.rotation = Quaternion.Euler(0, 0, -hero.transform.rotation.eulerAngles.y);
map.transform.localPosition = new Vector3()
{
x = -hero.transform.position.x * miniMapScaleRatio,
y = -hero.transform.position.z * miniMapScaleRatio,
z = 0,
};
}
}
}
代码不多吧,point是玩家标志小箭头,map是地图面板,就是1里面那个MiniMapRenderPanel,public属性的,自己拖上去吧。hero是我们的主角,miniMapScaleRatio是地图和真实地形尺寸比例,start里的第一句是在干吗呢,虽然我们的地图是512*512的,但经过实测,发现这个尺寸会随着屏幕的高度而有误差,需要设置成屏幕的高度这种尺寸,好吧,我也需要有人帮我解释下,Update里时刻获得主角的转向,主角在世界中是绕y轴旋转的,小标志是绕z轴旋转的,并且方向相反,这里要注意一下。然后按照角色在地形上的位置乘以地图与地形的比例获得小标志在地图上的位置,噢啦,大功告成,打完收工。