首页 >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. 孙佳说:

    您好,我想请问一下,unity3d添加碰撞可以通过component——>physics——>mesh collider来实现,请问如何用程序实现上一过程呢?即在程序运行过程中通过鼠标键盘来控制碰撞的添加。

  2. 欧宇龙说:

    刚刚我跟 那边提了个建议 被鄙视了 各位看看我的想法到底有多幼稚哈我们做一个 MMO类型的,我的建议是,整个分4个国家,A B C D广东这一代的用户登录就是 A 国上海 浙江 江苏 登录就是 B 国北京天津东北 就是 C 国....  就是D 国然后 各地 搞PK    3天一次国战各位 看着想法咋样呢

  3. 欧宇龙说:

    今天点了四次广告….. 这…..

  4. 文章很不错说:

    支持!! 文章很不错,

  5. Java【略懂】说:

    有没有android的3D游戏呀?特想学。

  6. 貌似找到两个错误:1,83行,Vector3 aim = target.position; 应该改为Vector3 aim = transform.position;否则射线的起始点都是一样的;2, 用于还原透明模型的else{}语句,应该放在if(Physics.Linecast(target.position,aim,out hit)) 外,否则不会执行到这里。另外补充一个:设置shader模式:bj.renderer.material.shader = Shader.Find(“Transparent/Diffuse”);设置为这样就可以实现半透明了。momo,我这样的学生勤奋吧,嘿嘿

  7. 雨松老师你好!最近买了你的<unity3d游戏开发>在研究!我现在想实现一个效果!就是我吧自己的模型做成预览效果!靠鼠标点击屏幕后拖动来改变摄像机的旋转角度,以达到可以360度行赏模型的效果!我现在看到粒子系统这一章节了,还是没什么头绪,希望雨松老师能给个思路,或说说改用什么方法来实现!求教了!

  8. 娃娃说:

    很经典 支持!!!

  9. 三无屌丝路过,博客不错呵

  10. 娃娃说:

    好经典的文章!!!

  11. 代码看不懂,来看效果来啦,哈哈…..那么喜欢牛头人,敢不敢当着MO嫂面放个大大的美女到场景里面去???

  12. 好文章说:

    好文章!!

  13. 想找一些关于脚本的管理,或者构架方面的资料,雨松有什么好的资料吗,推荐一下吧~

  14. Haru_Happy说:

    用角色发出一个射线到摄像机 好想法啊 嘿嘿!

  15. 郭青亮说:

    这篇真心不错!受教了 希望看到更多你的文章!

  16. 索拉说:

    又學到寶貴的一課 ! 很實用 ! 謝謝雨松老師

  17. 雨松,请问一下,我现在的主摄像机是一个俯视的视角,我想实现一个上帝视角,应该怎么做?直接Translate相机的话貌似因为相机本身是俯视所以造成相机不是水平移动

  18. 黄杰说:

    又出新博文了啊~感谢雨松老师蛤~默默的顶个~

  19. hjbo说:

    你的博客我每天都来,每天都期待新的文章,加油!

留下一个回复

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