第三人称相机有非常多种,今天要实现的一个第三人称射击游戏的相机。
如果对相机控制不是很了解,建议看一下上一篇博文 FPS相机。
鼠标控制yaw和pitch,添加一个distance变量来记录角色和相机之间的距离。通过yaw和pitch来得到相机的position。
最后添加一个向右的位移和向上的位移量,将角色放在屏幕偏左边的位置。
transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0); characterModel.transform.forward = new Vector3(transform.forward.x, characterModel.transform.forward.y, transform.forward.z); target.forward = new Vector3(transform.forward.x, 0, transform.forward.z); float yaw = rotationX; float pitch = rotationY; float yawRed = Mathf.Deg2Rad * (yaw - 90f); float pitchRed = Mathf.Deg2Rad * pitch; Vector3 direction = new Vector3(-Mathf.Cos(yawRed) * Mathf.Cos(pitchRed), -Mathf.Sin(pitchRed), Mathf.Sin(yawRed) * Mathf.Cos(pitchRed)); transform.position = target.transform.position + distance * direction; transform.position += transform.right + transform.up;
在这里,相机只控制了model的rotation。
direction是通过yaw和pitch计算出的角色到相机的Ray的方向。
角色往斜方向跑的动画处理
通常在TPS游戏中,角色的背面是始终对着摄像机的。当玩家希望角色往斜方向走的时候,不能直接播放角色往前走的动画,这时候就需要给角色Model一个额外的角度偏移量,这个偏移量由玩家的输入决定。
代码如下
characterModel.transform.forward = new Vector3(transform.forward.x, characterModel.transform.forward.y, transform.forward.z); if (characterModel.transform.parent.GetComponent<Character>().characterFPAnimation.extraRotation == 0) { extraRot = Mathf.Lerp(extraRot, 0f, 10 * Time.deltaTime); }else { extraRot = Mathf.Lerp(extraRot, characterModel.transform.parent.GetComponent<Character>().characterFPAnimation.extraRotation, 10 * Time.deltaTime); } Quaternion targetRotation = characterModel.transform.rotation * Quaternion.AngleAxis(extraRot, Vector3.up); characterModel.transform.rotation = targetRotation;
墙体遮挡
环境遮挡是第三人称摄像机一个经常遇到问题,下面是几个常见的方法。
解法一 射线检测,将相机移动到不被遮挡的位置。
在Unity官网的一个Tutorial里面,处理的方法是将相机慢慢上移,直到看到角色(游戏的场景是没有天花板的)
bool ViewingPosCheck (Vector3 checkPos) { RaycastHit hit; // If a raycast from the check position to the player hits something... if(Physics.Raycast(checkPos, player.position - checkPos, out hit, relCameraPosMag)) // ... if it is not the player... if(hit.transform != player) // This position isn‘t appropriate. return false; // If we haven‘t hit anything or we‘ve hit the player, this is an appropriate position. newPos = checkPos; return true; } void SmoothLookAt () { // Create a vector from the camera towards the player. Vector3 relPlayerPosition = player.position - transform.position; // Create a rotation based on the relative position of the player being the forward vector. Quaternion lookAtRotation = Quaternion.LookRotation(relPlayerPosition, Vector3.up); // Lerp the camera‘s rotation between it‘s current rotation and the rotation that looks at the player. transform.rotation = Quaternion.Lerp(transform.rotation, lookAtRotation, smooth * Time.deltaTime); }
void FixedUpdate () { // The standard position of the camera is the relative position of the camera from the player. Vector3 standardPos = player.position + relCameraPos; // The abovePos is directly above the player at the same distance as the standard position. Vector3 abovePos = player.position + Vector3.up * relCameraPosMag; // An array of 5 points to check if the camera can see the player. Vector3[] checkPoints = new Vector3[5]; // The first is the standard position of the camera. checkPoints[0] = standardPos; // The next three are 25%, 50% and 75% of the distance between the standard position and abovePos. checkPoints[1] = Vector3.Lerp(standardPos, abovePos, 0.25f); checkPoints[2] = Vector3.Lerp(standardPos, abovePos, 0.5f); checkPoints[3] = Vector3.Lerp(standardPos, abovePos, 0.75f); // The last is the abovePos. checkPoints[4] = abovePos; // Run through the check points... for(int i = 0; i < checkPoints.Length; i++) { // ... if the camera can see the player... if(ViewingPosCheck(checkPoints[i])) // ... break from the loop. break; } // Lerp the camera‘s position between it‘s current position and it‘s new position. transform.position = Vector3.Lerp(transform.position, newPos, smooth * Time.deltaTime); // Make sure the camera is looking at the player. SmoothLookAt(); }
类似的还可以将相机拉到被遮挡的墙前面。
检测的代码如下
void ShelterTest() { RaycastResult result = new RaycastResult(); float characterHeight = GameManager.GetInstance().character.height * 0.4f; Vector3 targetHeadPos = new Vector3(target.position.x, target.position.y + characterHeight, target.position.z); Ray[] testRays = new Ray[5]; testRays[0] = new Ray(targetHeadPos, transform.position + 0.8f * transform.right + 0.5f * transform.up - targetHeadPos); testRays[1] = new Ray(targetHeadPos, transform.position + 0.8f * transform.right - 0.5f * transform.up - targetHeadPos); testRays[2] = new Ray(targetHeadPos, transform.position - 0.8f * transform.right + 0.5f * transform.up - targetHeadPos); testRays[3] = new Ray(targetHeadPos, transform.position - 0.8f * transform.right - 0.5f * transform.up - targetHeadPos); testRays[4] = new Ray(transform.position, transform.position - targetHeadPos); float castDist = (transform.position - targetHeadPos).magnitude; float[] dists = new float[5]; for (int i = 0; i < 5; i++) { if (RaycastHelper.RaycastAll(testRays[i], castDist, true, GameManager.GetInstance().character.floorMask, out result)) { Debug.DrawLine(targetHeadPos, result.point, Color.red); dists[i] = Vector3.Distance(result.point, targetHeadPos); }else { Debug.DrawLine(targetHeadPos, targetHeadPos + castDist * testRays[i].direction, Color.blue); dists[i] = castDist; } } float minDist0 = Mathf.Min(dists[0], dists[1]); float minDist1 = Mathf.Min(dists[2], dists[3]); float minDist2 = Mathf.Min(minDist0, minDist1); float minDist = Mathf.Min(minDist2, dists[4]); transform.position = targetHeadPos + minDist * testRays[4].direction.normalized; }
解法二 半透明掉中间遮挡的物体
用raycast进行检测,然后动态替换掉材质就可以了。
解法三 利用Stencil对角色进行重绘
对Stencil Buffer 不了解的请参考这一篇 : Stencil buffer
通过Ztest将角色被遮挡部分的Stencial标记出来,然后就可以对这部分的像素进行处理。要么用一种单色绘制出来,要么绘制成透明,要么绘制一个发光的描边,都可以。
简单的效果如下:
这里分三个pass处理,第一遍绘制利用ZTest写Stencil
Shader "Custom/Player" { Properties { _MaskValue("Mask Value", int) = 2 _MainTex ("Base (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 Stencil { Ref [_MaskValue] Comp always Pass replace ZFail keep } CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
Shader "Custom/ClearZbuffer" { Properties { _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {} } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent+100"} LOD 80 ColorMask 0 ZTest Greater ZWrite On CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = half4(1,0,0,1); o.Alpha = 0.3; } ENDCG } FallBack "Diffuse" }
Shader "Custom/StencilTransparent" { Properties { _MaskValue("Mask Value", int) = 2 _MainTex ("Base (RGB)", 2D) = "white" {} _TransVal ("Transparency Value", Range(0,1)) = 1.0 _ColorAdd ("Color (Add, RGB)", Color) = (0.5,0,0) } SubShader { Tags { "RenderType"="Opaque" "Queue"="Transparent+100"} LOD 80 Stencil { Ref [_MaskValue] Comp notequal Pass keep } ZTest LEqual ZWrite On Blend SrcAlpha OneMinusSrcAlpha BlendOp Add CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; fixed _TransVal; half4 _ColorAdd; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); //o.Albedo = c.rgb * half4(1,0,0,1); //o.Alpha = 1; o.Albedo = c.rgb * _ColorAdd; o.Alpha = _TransVal; } ENDCG } FallBack "Diffuse" }
Real-Time Cameras:A Guide for Game Designers and Developers
Unity tutorial stealth - http://unity3d.com/learn/tutorials/projects/stealth-tutorial-4x-only
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/silangquan/article/details/49700863