首页 >Unity3D频道 >【Unity3D研究院之游戏开发】 > Unity3D研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四)
2013
01-05

Unity3D研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四)

          当摄像机处于跟随主角状态时,那么主角移动后很有可能摄像机被别的模型挡住。这样用户体验就非常不好,一般3D游戏在处理视角的时候有两种方法,第一种是让被挡住的模型变成透明,第二种是拉近摄像机。前者时候固定视角游戏使用,后者适合变化视角游戏使用。两个我们都学一学蛤蛤。

          如下图所示,MOMO写了一个简单的例子,通过鼠标或触摸来牵引主角向不同的角度移动,旁边有一面墙当主角被这面墙挡住,此时摄像机将拉近。  

    Unity3D研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四) - 雨松MOMO程序研究院 - 1

 

首先,为了方面鼠标与移动平台上的触摸同时相应我写了一个通用的方法。

包括 开始触摸  触摸中 结束触摸 ,电脑手机都可以用这个方法相应到。

JFConst.cs

using UnityEngine;
using System.Collections;

public class JFConst
{

	public static  bool TouchBegin()
	{
		if(Input.GetMouseButtonDown(0))
		{
			return true;
		}
		if(Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
		{
			return true;
		}
		return false;
	}

	public static bool TouchEnd()
	{
		if(Input.GetMouseButtonUp(0))
		{
			return true;
		}
		if(Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Ended)
		{
			return true;
		}
		return false;
	}

	public static bool TouchIng()
	{
		if(Input.GetMouseButton(0))
		{
			return true;
		}else if(Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Moved)
		{
			return true;
		}
		return false;
	}

}

 

以前写过一篇文章关于移动主角的,如果对移动主角还有不懂的请看这篇 Unity3D研究院之鼠标控制角色移动与奔跑示例(二十四)  。 这里我就不解释移动主角这部分的代码了。 下面是角色控制器ThirdPersonController.cs 绑定在主角身上。
下面代码中需要详细看的就是161行到174行 ,这部分就是根据触摸屏幕的2D坐标计算主角在3D世界中移动的坐标。
using UnityEngine;
using System.Collections;

/**
 *  @Author : www.xuanyusong.com
 */

[RequireComponent(typeof(CharacterController))]

public class ThirdPersonController : MonoBehaviour {

public AnimationClip idleAnimation ;
public AnimationClip walkAnimation ;
public AnimationClip runAnimation ;
public AnimationClip jumpPoseAnimation;

public float walkMaxAnimationSpeed  = 0.75f;
public float trotMaxAnimationSpeed  = 1.0f;
public float runMaxAnimationSpeed  = 1.0f;
public float jumpAnimationSpeed  = 1.15f;
public float landAnimationSpeed  = 1.0f;

private Animation _animation;

enum CharacterState
{
	Idle = 0,
	Walking = 1,
	Trotting = 2,
	Running = 3,
	Jumping = 4,
}

private CharacterState _characterState;

// The speed when walking
public float walkSpeed = 2.0f;
// after trotAfterSeconds of walking we trot with trotSpeed
public float trotSpeed = 4.0f;
// when pressing "Fire3" button (cmd) we start running
public float runSpeed = 6.0f;

public float inAirControlAcceleration = 3.0f;

// How high do we jump when pressing jump and letting go immediately
public float jumpHeight = 0.5f;

// The gravity for the character
public float gravity = 20.0f;
// The gravity in controlled descent mode
public float speedSmoothing = 10.0f;
public float rotateSpeed = 500.0f;
public float trotAfterSeconds = 3.0f;

public bool canJump = true;

private float jumpRepeatTime = 0.05f;
private float jumpTimeout = 0.15f;
private float groundedTimeout = 0.25f;

// The camera doesnt start following the target immediately but waits for a split second to avoid too much waving around.
private float lockCameraTimer = 0.0f;

// The current move direction in x-z
private Vector3 moveDirection = Vector3.zero;
// The current vertical speed
private float verticalSpeed = 0.0f;
// The current x-z move speed
private float moveSpeed = 0.0f;

// The last collision flags returned from controller.Move
private CollisionFlags collisionFlags; 

// Are we jumping? (Initiated with jump button and not grounded yet)
private bool jumping = false;
private bool jumpingReachedApex = false;

// Are we moving backwards (This locks the camera to not do a 180 degree spin)
private bool movingBack = false;
// Is the user pressing any keys?
private bool isMoving = false;
// When did the user start walking (Used for going into trot after a while)
private float walkTimeStart = 0.0f;
// Last time the jump button was clicked down
private float lastJumpButtonTime = -10.0f;
// Last time we performed a jump
private float lastJumpTime = -1.0f;

// the height we jumped from (Used to determine for how long to apply extra jump power after jumping.)
private float lastJumpStartHeight = 0.0f;

private Vector3 inAirVelocity = Vector3.zero;

private float lastGroundedTime = 0.0f;

private bool isControllable = true;

void Awake ()
{
	moveDirection = transform.TransformDirection(Vector3.forward);

	_animation = GetComponent<Animation>();
	if(!_animation)
		Debug.Log("The character you would like to control doesn't have animations. Moving her might look weird.");

	/*
public var idleAnimation : AnimationClip;
public var walkAnimation : AnimationClip;
public var runAnimation : AnimationClip;
public var jumpPoseAnimation : AnimationClip;
	*/
	if(!idleAnimation) {
		_animation = null;
		Debug.Log("No idle animation found. Turning off animations.");
	}
	if(!walkAnimation) {
		_animation = null;
		Debug.Log("No walk animation found. Turning off animations.");
	}
	if(!runAnimation) {
		_animation = null;
		Debug.Log("No run animation found. Turning off animations.");
	}
	if(!jumpPoseAnimation && canJump) {
		_animation = null;
		Debug.Log("No jump animation found and the character has canJump enabled. Turning off animations.");
	}

}

void UpdateSmoothedMovementDirection ()
{

	Transform cameraTransform = Camera.main.transform;
	bool grounded = IsGrounded();

	// Forward vector relative to the camera along the x-z plane
	Vector3 forward = cameraTransform.TransformDirection(Vector3.forward);
	forward.y = 0;
	forward = forward.normalized;

	// Right vector relative to the camera
	// Always orthogonal to the forward vector
	Vector3 right = new Vector3(forward.z, 0, -forward.x);

	float v = Input.GetAxisRaw("Vertical");
	float h = Input.GetAxisRaw("Horizontal");

	// Are we moving backwards or looking backwards
	if (v < -0.2f)
		movingBack = true;
	else
		movingBack = false;

	bool wasMoving = isMoving;
	isMoving = Mathf.Abs (h) > 0.1f || Mathf.Abs (v) > 0.1f;

	// Target direction relative to the camera
	Vector3 targetDirection = h * right + v * forward;

	if(JFConst.TouchIng())
	{
			Vector3 vpos3 = Camera.main.WorldToScreenPoint(transform.position);
			Vector2 vpos2 = new Vector2 (vpos3.x,vpos3.y);
			Vector2 input = new Vector2 (Input.mousePosition.x,Input.mousePosition.y);
			if(Vector2.Distance(vpos2,input) > 10.0f)
			{
				Vector2 normalied = 	((vpos2 - input)).normalized;
				targetDirection = new Vector3(normalied.x,0.0f,normalied.y) ;
				float y = Camera.main.transform.rotation.eulerAngles.y;
				targetDirection = Quaternion.Euler(0f,y - 180,0f) * targetDirection;	

			}
	}	

	// Grounded controls
	if (grounded)
	{
		// Lock camera for short period when transitioning moving & standing still
		lockCameraTimer += Time.deltaTime;
		if (isMoving != wasMoving)
			lockCameraTimer = 0.0f;

		// We store speed and direction seperately,
		// so that when the character stands still we still have a valid forward direction
		// moveDirection is always normalized, and we only update it if there is user input.
		if (targetDirection != Vector3.zero)
		{
			// If we are really slow, just snap to the target direction
			if (moveSpeed < walkSpeed * 0.9f && grounded)
			{
				moveDirection = targetDirection.normalized;
			}
			// Otherwise smoothly turn towards it
			else
			{
				moveDirection = Vector3.RotateTowards(moveDirection, targetDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000);

				moveDirection = moveDirection.normalized;
			}
		}

		// Smooth the speed based on the current target direction
		float curSmooth = speedSmoothing * Time.deltaTime;

		// Choose target speed
		//* We want to support analog input but make sure you cant walk faster diagonally than just forward or sideways
		float targetSpeed = Mathf.Min(targetDirection.magnitude, 1.0f);

		_characterState = CharacterState.Idle;

		// Pick speed modifier
		if (Input.GetKey (KeyCode.LeftShift) | Input.GetKey (KeyCode.RightShift))
		{
			targetSpeed *= runSpeed;
			_characterState = CharacterState.Running;
		}
		else if (Time.time - trotAfterSeconds > walkTimeStart)
		{
			targetSpeed *= trotSpeed;
			_characterState = CharacterState.Trotting;
		}
		else
		{
			targetSpeed *= walkSpeed;
			_characterState = CharacterState.Walking;
		}

		moveSpeed = Mathf.Lerp(moveSpeed, targetSpeed, curSmooth);

		// Reset walk time start when we slow down
		if (moveSpeed < walkSpeed * 0.3f)
			walkTimeStart = Time.time;
	}
	// In air controls
	else
	{
		// Lock camera while in air
		if (jumping)
			lockCameraTimer = 0.0f;

		if (isMoving)
			inAirVelocity += targetDirection.normalized * Time.deltaTime * inAirControlAcceleration;
	}

}

void ApplyJumping ()
{
	// Prevent jumping too fast after each other
	if (lastJumpTime + jumpRepeatTime > Time.time)
		return;

	if (IsGrounded()) {
		// Jump
		// - Only when pressing the button down
		// - With a timeout so you can press the button slightly before landing
		if (canJump && Time.time < lastJumpButtonTime + jumpTimeout) {
			verticalSpeed = CalculateJumpVerticalSpeed (jumpHeight);
			SendMessage("DidJump", SendMessageOptions.DontRequireReceiver);
		}
	}
}

void ApplyGravity ()
{
	if (isControllable)	// don't move player at all if not controllable.
	{
		// Apply gravity
		bool jumpButton = Input.GetButton("Jump");

		// When we reach the apex of the jump we send out a message
		if (jumping && !jumpingReachedApex && verticalSpeed <= 0.0f)
		{
			jumpingReachedApex = true;
			SendMessage("DidJumpReachApex", SendMessageOptions.DontRequireReceiver);
		}

		if (IsGrounded ())
			verticalSpeed = 0.0f;
		else
			verticalSpeed -= gravity * Time.deltaTime;
	}
}

float CalculateJumpVerticalSpeed (float targetJumpHeight)
{
	// From the jump height and gravity we deduce the upwards speed
	// for the character to reach at the apex.
	return Mathf.Sqrt(2 * targetJumpHeight * gravity);
}

void  DidJump ()
{
	jumping = true;
	jumpingReachedApex = false;
	lastJumpTime = Time.time;
	lastJumpStartHeight = transform.position.y;
	lastJumpButtonTime = -10;

	_characterState = CharacterState.Jumping;
}

void  Update() {

	if (!isControllable)
	{
		// kill all inputs if not controllable.
		Input.ResetInputAxes();
	}

	if (Input.GetButtonDown ("Jump"))
	{
		lastJumpButtonTime = Time.time;
	}

	UpdateSmoothedMovementDirection();

	// Apply gravity
	// - extra power jump modifies gravity
	// - controlledDescent mode modifies gravity
	ApplyGravity ();

	// Apply jumping logic
	ApplyJumping ();

	// Calculate actual motion
	Vector3 movement = moveDirection * moveSpeed + new Vector3 (0, verticalSpeed, 0) + inAirVelocity;
	movement *= Time.deltaTime;

	// Move the controller
	CharacterController controller = GetComponent<CharacterController>();
	collisionFlags = controller.Move(movement);

	// ANIMATION sector
	if(_animation) {
		if(_characterState == CharacterState.Jumping)
		{
			if(!jumpingReachedApex) {
				_animation[jumpPoseAnimation.name].speed = jumpAnimationSpeed;
				_animation[jumpPoseAnimation.name].wrapMode = WrapMode.ClampForever;
				_animation.CrossFade(jumpPoseAnimation.name);
			} else {
				_animation[jumpPoseAnimation.name].speed = -landAnimationSpeed;
				_animation[jumpPoseAnimation.name].wrapMode = WrapMode.ClampForever;
				_animation.CrossFade(jumpPoseAnimation.name);
			}
		}
		else
		{
			if(controller.velocity.sqrMagnitude < 0.1f) {
				_animation.CrossFade(idleAnimation.name);
			}
			else
			{
				if(_characterState == CharacterState.Running) {
					_animation[runAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, runMaxAnimationSpeed);
					_animation.CrossFade(runAnimation.name);
				}
				else if(_characterState == CharacterState.Trotting) {
					_animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, trotMaxAnimationSpeed);
					_animation.CrossFade(walkAnimation.name);
				}
				else if(_characterState == CharacterState.Walking) {
					_animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, walkMaxAnimationSpeed);
					_animation.CrossFade(walkAnimation.name);
				}

			}
		}
	}
	// ANIMATION sector

	// Set rotation to the move direction
	if (IsGrounded())
	{

		transform.rotation = Quaternion.LookRotation(moveDirection);

	}
	else
	{
		Vector3 xzMove = movement;
		xzMove.y = 0;
		if (xzMove.sqrMagnitude > 0.001f)
		{
			transform.rotation = Quaternion.LookRotation(xzMove);
		}
	}	

	// We are in jump mode but just became grounded
	if (IsGrounded())
	{
		lastGroundedTime = Time.time;
		inAirVelocity = Vector3.zero;
		if (jumping)
		{
			jumping = false;
			SendMessage("DidLand", SendMessageOptions.DontRequireReceiver);
		}
	}
}

void  OnControllerColliderHit (ControllerColliderHit hit )
{
//	Debug.DrawRay(hit.point, hit.normal);
	if (hit.moveDirection.y > 0.01f)
		return;
}

float GetSpeed () {
	return moveSpeed;
}

public bool IsJumping () {
	return jumping;
}

bool IsGrounded () {
	return (collisionFlags & CollisionFlags.CollidedBelow) != 0;
}

Vector3 GetDirection () {
	return moveDirection;
}

public bool IsMovingBackwards () {
	return movingBack;
}

public float GetLockCameraTimer ()
{
	return lockCameraTimer;
}

bool IsMoving ()
{
	return Mathf.Abs(Input.GetAxisRaw("Vertical")) + Mathf.Abs(Input.GetAxisRaw("Horizontal")) > 0.5f;
}

bool HasJumpReachedApex ()
{
	return jumpingReachedApex;
}

bool IsGroundedWithTimeout ()
{
	return lastGroundedTime + groundedTimeout > Time.time;
}

void Reset ()
{
	gameObject.tag = "Player";
}

}

 

如此我们就可以通过鼠标与触摸牵引控制主角移动了,别急这紧紧是开始,下面是本章的要点。

这里我们分析一下摄像机到底什么时候该拉近,什么时候该不拉近。 我的作法是这样的,当角色移动的时候我会从主角身上向摄像机方向发射一条射线,如果射线碰撞到的第一个对象是“摄像机” 那么就标示主角和摄像机之间没有别的模型挡住,此时摄像机就不应该拉近。如果射线碰撞到的第一个对象不是摄像机,那么就标示主角和摄像机之间有别的模型所挡住,此时取得射线与别的模型碰撞的3D坐标接着将“摄像机”的坐标移动到这个坐标上即可。

如下图所示,(红色表示由主角发射到摄像机的射线)主角身后的射线没有被别的模型挡住,射线机不会拉近。

 

Unity3D研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四) - 雨松MOMO程序研究院 - 2

 

如下图所示,MOMO改变了一下主角的位置,此时主角身后的射向已经被墙挡住,这时把摄像机的坐标移动到被墙挡住点的坐标,这样摄像机就拉近了。

Unity3D研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四) - 雨松MOMO程序研究院 - 3

 

原理大概就是这样,接着我们学习一下这段代码该如何来写。

MyCamera.cs 挂在射线机上

using UnityEngine;
using System.Collections;

public class MyCamera : MonoBehaviour
{

    //摄像机朝向的目标模型
	public Transform target;
	//摄像机与模型保持的距离
	public float distance = 10.0f;
	//射线机与模型保持的高度
	public float height = 5.0f;
	//高度阻尼
	public float heightDamping = 2.0f;
	//旋转阻尼
	public float rotationDamping = 3.0f;
	//主角对象
	private GameObject controller; 

	void Start()
	{
		//得到主角对象
		controller = GameObject.FindGameObjectWithTag("Player");
	}

	void Update()
	{

	}

	void LateUpdate ()
	{
		// Early out if we don't have a target
		if (!target)
			return;

		//当鼠标或者手指在触摸中时
		if(JFConst.TouchIng())
		{

			bool follow = true;
	        //计算相机与主角Y轴旋转角度的差。
			float abs = Mathf.Abs(transform.rotation.eulerAngles.y - controller.transform.rotation.eulerAngles.y);
			//abs等于180的时候标示摄像机完全面对这主角, 》130 《 230 表示让面对的角度左右偏移50度
			//这样做是不希望摄像机跟随主角,具体效果大家把代码下载下来看看,这样的摄像机效果很好。
			if(abs > 130 && abs < 230)
			{
				follow = false;
			}else
			{
				follow = true;
			}

			float wantedRotationAngle = target.eulerAngles.y;
			float wantedHeight = target.position.y + height;

			float currentRotationAngle = transform.eulerAngles.y;
			float currentHeight = transform.position.y;

			//主角面朝射线机 和背对射线机 计算正确的位置
			if(follow)
			{
				currentRotationAngle = Mathf.LerpAngle (currentRotationAngle, wantedRotationAngle, rotationDamping * Time.deltaTime);
				currentHeight = Mathf.Lerp (currentHeight, wantedHeight, heightDamping * Time.deltaTime);
				Quaternion currentRotation = Quaternion.Euler (0, currentRotationAngle, 0);
				Vector3 positon = target.position;
				positon -= currentRotation * Vector3.forward * distance;
				positon = new Vector3(positon.x,currentHeight,positon.z);
				transform.position = Vector3.Lerp(transform.position,positon,Time.time);

			}else
			{
				Vector3 positon = target.position;
				Quaternion cr = Quaternion.Euler (0, currentRotationAngle, 0);	

				positon += cr * Vector3.back * distance;
				positon = new Vector3(positon.x,target.position.y + height,positon.z);
				transform.position = Vector3.Lerp(transform.position,positon,Time.time);
			}
		}

		    //这里是计算射线的方向,从主角发射方向是射线机方向
			Vector3 aim = target.position;
			//得到方向
			Vector3 ve = (target.position - transform.position).normalized;
			float an = transform.eulerAngles.y;
			aim -= an * ve ;
		    //在场景视图中可以看到这条射线
			Debug.DrawLine(target.position,aim,Color.red);
		    //主角朝着这个方向发射射线
			RaycastHit hit;
			if(Physics.Linecast(target.position,aim,out hit))
			{
				string name =  hit.collider.gameObject.tag;
				if(name != "MainCamera" && name !="terrain")
				{
					//当碰撞的不是摄像机也不是地形 那么直接移动摄像机的坐标
					transform.position = hit.point;

				}
		}

		// 让射线机永远看着主角
		transform.LookAt (target);	

	}

}

 

牛头人被墙挡住了,摄像机直接拉近了,小牛头人是不是很帅气?哈哈哈!!

 

Unity3D研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四) - 雨松MOMO程序研究院 - 4

 

最后雨松MOMO把源码放出来,希望大家学习愉快,哇咔咔。。。

下载地址:http://vdisk.weibo.com/s/mAhiI

 

补充 :摄像机遮挡物体透明。

遮挡透明最简单的办法就是修改材质的透明度,前提需要把材质设置成支持透明通道的 shader Transparent 。 如下图所示,摄像机挡住的那面墙已经成透明状态了。

 

Unity3D研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四) - 雨松MOMO程序研究院 - 5

 

看看代码是如何写的,把上述代码MyCamera.cs 简单的改一下就可以。 主要是92行到101行。

代码核心就是通过射线得到碰撞模型的材质,然后修改材质的透明度。color.a就是透明度 1是完全不透明,0是完全透明。注意一定是支持透明通道的模型才可以。lastobj是记录上次透明的对象,用于模型透明还原使用。

			if(Physics.Linecast(target.position,aim,out hit))
			{
				GameObject obj = hit.collider.gameObject;

				string name =  obj.tag;
				if(name != "MainCamera" && name !="terrain")
				{

					Color color = obj.renderer.material.color;
					color.a = 0.5f;
					obj.renderer.material.SetColor("_Color", color);

					lastobj = obj;

				}else
				{
					if(lastobj != null)
					{
						Color color = lastobj.renderer.material.color;
						color.a = 1f;
						lastobj.renderer.material.SetColor("_Color", color);
					}
				}
			}

 

最后,假设你的模型做的非常大,贴图材质也非常的多,你像透明其中一小部分那么上述方法就不合适了,除非把他们都拆出来。我觉得也可以自己写shader实现。我们的项目采用了摄像机拉近的方式。欢迎讨论!!!

 

回答楼下问题:上帝视角代码,看到楼下有朋友问我,那么我就贴出来,其实很简单。下面代码挂在摄像机上, target就是角色对象,distance 是摄像机与角色的距离 height是摄像机与角色的高度。在编辑器中手动调节即可。

 

using UnityEngine;
using System.Collections;

public class ICamera : MonoBehaviour {

public Transform target;
// The distance in the x-z plane to the target
public float distance = 10.0f;
// the height we want the camera to be above the target
public float height = 5.0f;

void LateUpdate () {
	// Early out if we don't have a target
	if (!target)
		return;

	// Set the position of the camera on the x-z plane to:
	// distance meters behind the target
	transform.position = target.position;
	transform.position -=  Vector3.forward * distance;
	transform.position = new Vector3(transform.position.x,transform.position.y + height,transform.position.z);

	// Always look at the target
	transform.LookAt (target);
	}
}

 

回答问题 :摄像机抖动如何修改

今天微薄上有个朋友问我这样的问题,我在这里解答一下。

来福12345:我又厚颜无耻的来了,您的《Unity3D的研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四)》对于不规则的物体该怎么弄,我是直接在unity上弄了几个山脉,由于地面凹凸不平,沿着山走的时候发现摄像机会一直透视,而且抖动的很厉害,请问该怎么解决啊

 

1.从设计上避免射线遇到一些复杂的面,如下图所示,红颜色是主角身后的射线,当主角身后的射线碰撞到这样的面,如果此时移动速快快一些摄像机肯定会抖动。 这里最好直接删除Mesh Collider 换成BoxCollider 效率还可以得到提升。产经设计这块我觉得可以参考一下魔兽世界,我在项目中遇到这样问题的地方就是 比较小的门(棱角那种) 或者这样的树。

 

屏幕快照 2013-01-21 下午3.35.00

 

 

2.但是还有一些比较特殊的地方,如果你的项目必需使用MeshColider 的话 我建议你写触发脚本来修改摄像机,假设 当摄像机被挡住后,变成跟随视角,只到摄像机没被挡住的时候在回到原来视角。

3.我检查了一下上面的代码中我们在加一个判断,应该就可以解决大部分问题、在LateUpdate () 中最后加入代码

					if(!closer)
					{
						transform.position = positon;

					}
					else
					{
						float dis = Vector3.Distance(transform.position,controller.transform.position);
						if(dis < 8)
						{
							distance = dis;
						}else
						{
							closer = false;
						}
					}

 

bool closer 是表示是否拉近。 当摄像机被挡住的时候这个数值等于true;

				if(Physics.Linecast(tirggerTaget,aim,out hit))
				{
					string name =  hit.collider.gameObject.tag;
					if(name != "MainCamera")
					{
						closer = true;
						transform.position = hit.point;
						distance = Vector3.Distance(transform.position,controller.transform.position) - 2f;	
					}
				}

 

当closer等于假的时候,此时直接修更新摄像机的位置,因为没被挡住。 当closer等于真的时候,摄像机已经被挡住了,我们不修改摄像机的位置,计算摄像机保证不被挡住时,摄像机此时和主角的距离是多少,然后修改distance全局变量即可。当摄像机不被遮挡的时候在恢复之前的距离。 如有问题请留言,我会即时解答。。 谢谢

 

 

//补充/////////

之前的代码还是有点问题,后来我又仔细的重构了一下摄像机的代码。下面的代码在我的项目中已经完美无暇的模拟摄像机拉近了。。 代码对外有两个公有类型, 一个是模型的 一个是模型位置 与头顶的偏移, 因为射线应该是重头顶向后发射的。

using UnityEngine;
using System.Collections;
using System.Threading;

public class PersonCamera : MonoBehaviour 
{

	public const  float DISTANCE_DEAFULT = 8.0f;
	private float distance = 0.0f;
	public Transform target;
	public float target_offsety = 1.8f;
	private Vector3 payerTaget;	

	void Awake()
	{
		distance = DISTANCE_DEAFULT;
		payerTaget =  new Vector3(target.position.x,target.position.y + target_offsety , target.position.z);
		Quaternion cr = Quaternion.Euler (30f, transform.eulerAngles.y, 0);	
		Vector3 positon = payerTaget;
		positon += cr * Vector3.back * distance;
		transform.position = positon;
		transform.LookAt(payerTaget);
	}
	void LateUpdate()
	{

				payerTaget =  new Vector3(target.position.x,target.position.y + target_offsety , target.position.z);
				Quaternion cr = Quaternion.Euler (30f, transform.eulerAngles.y, 0);	
				Vector3 positon = payerTaget + (cr * Vector3.back * distance);
				RaycastHit []hits = Physics.RaycastAll(new Ray(payerTaget,(positon -payerTaget).normalized));
				distance = DISTANCE_DEAFULT;	
				if(hits.Length > 0)
				{
					RaycastHit stand = hits[0]; 
					foreach(RaycastHit hit in hits)
					{
						if(hit.collider.gameObject.tag == "terrain")
						{
							if(hit.distance < stand.distance)
							{
								stand = hit;
							}
						}
					}
					Debug.Log(stand.point + " " +stand.collider.gameObject.tag);
					string tag = stand.collider.gameObject.tag;
					distance = Vector3.Distance(stand.point,payerTaget);
					if(distance > DISTANCE_DEAFULT)
					{
						distance = DISTANCE_DEAFULT;
					}

				}
				positon = payerTaget + (cr * Vector3.back * distance);
			    transform.position = Vector3.Lerp(transform.position,positon,0.3f);;

				transform.LookAt(payerTaget);

				Debug.DrawRay(payerTaget,positon -payerTaget,Color.red);	
	}
}

 

 

 

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

Unity3D研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四)》有 75 条评论

  1. world说:

    大神,透明那个过时了,能更新么

  2. 莫需要说:

    大神还在嘛,好像这个脚本还有点小问题,不知道怎么上传图片,之好符号代替了_____←墙2 当射线第一次碰到墙1的时候 |←墙1 可以拉近相机 |O←相机 | | ←射线 | | O角色_____←墙2 相机离开墙体的时候会穿过去, O |←墙1 碰到墙2,这时墙1又遮挡了 相 | 机 | | ←射线 | | O角色是不是因为默认射线的起点不对啊

  3. 貌似没有考虑到角色离障碍物太近的情况

  4. 大白菜说:

    momo老师你好,请教一下,UpdateSmoothedMovementDirection函数是否只是算出移动的方向?我想知道屏幕的2D坐标在3D世界中的具体坐标,该怎么算呢?

  5. tag说:

    再问一个问题,如果写游戏时对一个对象编写了2种第三人称的控制代码,一个是自动跟踪,另一个是鼠标自由控制(可以360°观察),我想在游戏中通过按钮切换不同的控制效果,是不是用组件的enable来控制就可以了??还有什么别的便捷高效的做法吗?!

  6. 雨松老师,我想请问下,最后你重构的代码中,哪个参数可以修改,摄像机跟随物体时,旋转的速度?我改了好多个,都没改出来!! Unity3D研究院之处理摄像机跟随避免相机穿墙拉近或透明的方法(四十四) - 雨松MOMO程序研究院 - 1

  7. sky说:

    你好。。。我请问一下,有时候会出现这种问题,怎么解决,并且,你这个场景比较简单,假如东西多的话,这种问题会出现很多。

  8. 呵呵说:

    这不利于祖国的统一。。。。

  9. shim说:

    请问您的MyCamera.cs里,target和controller是同一个人物吧?

  10. 换成unity自带的smoothfollow脚本没有画面一卡一卡和抖动(但是就是摄像机会跑到山体里)

  11. 你好,使用了你最后发布的代码,目标跟随一辆赛车,赛车前进的时候,发现画面有一顿、-顿的抖动现象,这是为什么?要调整那些参数?谢谢指点

  12. 写得挺不错的。不过你上面的关于透明度的那段还是有点问题。这种方法只适合一个物体挡住的情况,多个物体挡住的情况只能一个物体变透明,建议用RaycastHit[] hit来判断。

  13. MoMo老师,我想请教你一个问题,如果我想通过其他触控方式控制第三视角移动和旋转,我应该怎么写第三视角的移动h和旋转接口。

  14. 后知后觉说:

    雨松老师,请教一下,像计算机单机<<游击队保罗>>(保罗队长)游戏中,若两人或多人分屏(split screen)模式下摄像机及人物移动到边界时如何控制?描述:当两人或多人向同一方向移动时摄像机跟随移动,当不同方向移动时,若人物移动到边界时人物将不能移动

  15. Vector3 aim = target.transform.position;//得到方向(主角到相机):Vector3 ve = (target.transform.position – transform.position).normalized;float an = transform.eulerAngles.y;aim -= an * ve ; //在场景视图中可以看到这条射线Debug.DrawLine(target.transform.position,aim,Color.red); //主角朝着这个方向发射射线RaycastHit hit;if(Physics.Linecast(target.transform.position ,aim,out hit)){string name = hit.transform.gameObject.tag;if(name != “MainCamera” && name !=”terrain” && name!=”Plane” ){//当碰撞的不是摄像机也不是地形 那么直接移动摄像机的坐标transform.position = hit.point;} }这段代码我试了一下,有时能捕获碰撞,有时却不行。不行的时候大多是开始时角色移动到障碍后面,障碍挡住了相机的视线,但依然无法获得碰撞。能捕获碰撞的时候是角色不动,相机以角色为中心旋转的时候,当障碍挡住相机视线时

  16. Vector3 aim = target.transform.position; //得到方向(主角到相机): Vector3 ve = (target.transform.position – transform.position).normalized; float an = transform.eulerAngles.y; aim -= an * ve ; //在场景视图中可以看到这条射线 Debug.DrawLine(target.transform.position,aim,Color.red); //主角朝着这个方向发射射线 RaycastHit hit; if(Physics.Linecast(target.transform.position ,aim,out hit)) { string name = hit.transform.gameObject.tag; if(name != “MainCamera” && name !=”terrain” && name!=”Plane” ) { //当碰撞的不是摄像机也不是地形 那么直接移动摄像机的坐标 transform.position = hit.point; } }这段代码我试了一下,有时能捕获碰撞,有时却不行。不行的时候大多是开始时角色移动到障碍后面,障碍挡住了相机的视线,但依然无法获得碰撞。能捕获碰撞的时候是角色不动,相机以角色为中心旋转的时候,当障碍挡住相机视线时

  17. 说:

    NullReferenceExceptionUnityEngine.Component.get_transform () (at C:/BuildAgent/work/cac08d8a5e25d4cb/Runtime/ExportGenerated/Editor/UnityEngineComponent.cs:21)ThirdPersonController.UpdateSmoothedMovementDirection () (at Assets/ThirdPersonController.cs:138)ThirdPersonController.Update () (at Assets/ThirdPersonController.cs:329)指向的代码为Transform cameraTransform = Camera.main.transform;

  18. 说:

    NullReferenceExceptionUnityEngine.Component.get_transform () (at C:/BuildAgent/work/cac08d8a5e25d4cb/Runtime/ExportGenerated/Editor/UnityEngineComponent.cs:21)ThirdPersonController.UpdateSmoothedMovementDirection () (at Assets/ThirdPersonController.cs:138)ThirdPersonController.Update () (at Assets/ThirdPersonController.cs:329)指向的代码为 Transform cameraTransform = Camera.main.transform;

  19. 落花无意说:

    求老师解析下:Vector3 ve = (target.position – transform.position ).normalized;float an = transform.eulerAngles.y;aim -= an * ve;这里不是十分理解。。。

  20. 落花无意说:

    求老师解析下:Vector3 ve = (target.position – transform.position ).normalized; float an = transform.eulerAngles.y; aim -= an * ve;这里不是十分理解。。。

留下一个回复

你的email不会被公开。 必填项已用 * 标注。