•  在上一篇文章中,我们从Unity3D为我们提供的相机原型实现了非编码式的小地图,如果结合GUI在这个小地图下面绘制一些背景贴图,相信整体的效果会更好一些。博主希望这个问题大家能够自己去做更深入的研究,因为贴图...

            大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei

            在上一篇文章中,我们从Unity3D为我们提供的相机原型实现了非编码式的小地图,如果结合GUI在这个小地图下面绘制一些背景贴图,相信整体的效果会更好一些。博主希望这个问题大家能够自己去做更深入的研究,因为贴图的绘制在前面的文章中,我们已经已经提到了,所以这里就不打算再多说。今天呢,我们继续为这个小项目加入一些有趣的元素。首先请大家看一下下面的图片:


             相信熟悉国产单机游戏的朋友看到这幅图片一定会有种熟悉的感觉,博主在本系列的第一篇文章中,就已经提到了博主是一个国产单机游戏迷,博主喜欢这样有内涵、有深度的游戏。或许从操作性上来说,仙剑系列的回合制在很大程度上落后于目前的即时制,但是我认为回合制和即时制从本质上来说没有什么区别,即时制是不限制攻击次数的回合制,所以从玩法上来讲,回合制玩家需要均衡地培养每一个角色,在战斗中寻找最优策略,以发挥各个角色的优势,因此博主认为如果把即时制成为武斗,那么回合制在某种程度上就可以称之为文斗,正是因为如此,仙剑系列注重剧情、注重故事性,为玩家带来了无数感动。鉴于国内网游玩家的素质,博主一贯反感网游,所以比较钟情于武侠/仙侠单机游戏,虽然仙剑同样推出了网络版,但是在游戏里开着喇叭、挂着语音、相互谩骂的网游环境,实在让我找不回仙剑的感觉。好了,闲话先说到这里,今天我们来说一说现价奇侠传四里面的角色控制。玩过仙剑奇侠传的人都知道,仙剑奇侠传真正进入3D界面的跨时代作品当属上海软星开发的仙剑奇侠传四,该公司之前曾开发了仙剑奇侠传三、仙剑奇侠传三外传等作品,后来由于某些原因,该公司被迫解散。而这家公司就是后来在国产单机游戏中的新锐——上海烛龙科技的《古剑奇谭》。有很多故事,我们不愿意相信结局或者看到了结局而不愿意承认,青鸾峰上蓝衣白衫、白发苍苍的慕容紫英,随着魔剑幽蓝的剑影御剑而去的身影,我们都曾记得,或许他真的去了天墉城,只为一句:承君此诺,必守一生。好了,我们正式开始技术分享(博主内心有很多话想说)!

             在仙剑奇侠传四中,玩家可以通过鼠标右键来旋转场景(水平方向),按下前进键时角色将向着朝前(Forward)的方向运动,按下后退键时角色将向着朝后(Backword)的方向运动、当按下向左、向右键时角色将向左、向右旋转90度。从严格意义上来说,仙剑四不算是一部完全的3D游戏,因为游戏视角是锁死的,所以玩家在平时跑地图的时候基本上是看不到角色的正面的。我们今天要做的就是基于Unity3D来做这样一个角色控制器。虽然Unity3D为我们提供了第一人称角色控制器和第三人称角色控制器,但是博主感觉官方提供的第三人称角色控制器用起来感觉怪怪的,尤其是按下左右键时那个旋转,感觉控制起来很不容易,所以博主决定自己来写一个角色控制器。首先我们打开项目,我们还是用昨天的那个例子:

            

               很多朋友可能觉得控制角色的脚本很好写嘛,这是一个我们通常见到的版本:

               //向左
    	   if(Input.GetKey(KeyCode.A))
    	   {
    	      SetAnimation(LeftAnim);
    	      this.mState=PersonState.Walk;
    	      mHero.transform.Translate(Vector3.right*Time.deltaTime*mSpeed);
    	   }
    	   //向右
    	   if(Input.GetKey(KeyCode.D))
    	   {
    	      SetAnimation(RightAnim);
    	      this.mState=PersonState.Walk;
    	      mHero.transform.Translate(Vector3.right*Time.deltaTime*(-mSpeed));
    	   }
    	   //向上
    	   if(Input.GetKey(KeyCode.W))
    	   {
    	      SetAnimation(UpAnim);
    	      this.mState=PersonState.Walk;
    	      mHero.transform.Translate(Vector3.forward*Time.deltaTime*(-mSpeed));
    	   }
    	   //向下
    	   if(Input.GetKey(KeyCode.S))
    	   {
    	      SetAnimation(DownAnim);
    	      this.mState=PersonState.Walk;
    	      mHero.transform.Translate(Vector3.forward*Time.deltaTime*(mSpeed));
    	      Vector3 mHeroPos=mHero.transform.position;
    	   }
    
               那么,我们姑且认为这样写没什么问题,那么现在我们导入官方提供的Script脚本资源包,找到MouseLook脚本,在Update()方法中添加对右键是否按下的判断,这样我们就可以实现按下鼠标右键时视角的旋转。修改后的脚本如下:

    using UnityEngine;
    using System.Collections;
    
    /// MouseLook rotates the transform based on the mouse delta.
    /// Minimum and Maximum values can be used to constrain the possible rotation
    
    /// To make an FPS style character:
    /// - Create a capsule.
    /// - Add the MouseLook script to the capsule.
    ///   -> Set the mouse look to use LookX. (You want to only turn character but not tilt it)
    /// - Add FPSInputController script to the capsule
    ///   -> A CharacterMotor and a CharacterController component will be automatically added.
    
    /// - Create a camera. Make the camera a child of the capsule. Reset it's transform.
    /// - Add a MouseLook script to the camera.
    ///   -> Set the mouse look to use LookY. (You want the camera to tilt up and down like a head. The character already turns.)
    [AddComponentMenu("Camera-Control/Mouse Look")]
    public class MouseLook : MonoBehaviour {
    
    	public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 }
    	public RotationAxes axes = RotationAxes.MouseXAndY;
    	public float sensitivityX = 15F;
    	public float sensitivityY = 15F;
    
    	public float minimumX = -360F;
    	public float maximumX = 360F;
    
    	public float minimumY = -60F;
    	public float maximumY = 60F;
    
    	float rotationY = 0F;
    
    	void Update ()
    	{
    		if(Input.GetMouseButton(1))
    		{
    		  if (axes == RotationAxes.MouseXAndY)
    		  {
    			float rotationX = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * sensitivityX;
    			
    			rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
    			rotationY = Mathf.Clamp (rotationY, minimumY, maximumY);
    			
    			transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0);
    		  }
    		  else if (axes == RotationAxes.MouseX)
    		  {
    			transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivityX, 0);
    			
    		  }
    		  else
    		  {
    			rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
    			rotationY = Mathf.Clamp (rotationY, minimumY, maximumY);
    			
    			transform.localEulerAngles = new Vector3(-rotationY, transform.localEulerAngles.y, 0);
    		  }
    		}
    	}
    	
    	void Start ()
    	{
    		// Make the rigid body not change rotation
    		if (rigidbody)
    			rigidbody.freezeRotation = true;
    	}
    }
           接下来,我们将这个脚本拖放到我们的角色上,运行游戏,我们发现了一个问题:当旋转视角后,角色并没有如我们期望地向朝前的方向移动,相反,角色依然沿着世界坐标系里的Vector3.forward向前运动。按照我们的想法,当旋转视角以后,角色应该可以朝着前方运动。怎么办呢?这里我们在上面的代码中加上这样的代码:

    //计算旋转角
    	   if(Input.GetMouseButton(1))
    	   {
    		  //计算水平旋转角
    		  mAngles+=Input.GetAxis("Mouse X") * 15;
    		  //旋转角色
    		  transform.rotation=Quaternion.Euler(new Vector3(0,mAngles,0));
    	   }

          这里代码的作用是当用户按下鼠标右键旋转视角时,我们首先计算在水平上的旋转角,然后让角色的坐标系跟着视角一起旋转,这样就相当于把Vector3.forward和旋转后的目标角度平行。这样的话,我们控制人物向前运动的时候,它就会按照这个新的方向去运动。这样我们的第一个问题就解决了。我们继续往下看,由于这个模型中只提供了一个行走/奔跑的方向动画,所以就出现了角色动画和角色行为不符的问题,怎么办呢?这时候,我们可以这样想,我们可以先把角色旋转到指定的方向,然后让角色朝着向前的方向运动,这样角色动画和角色行为就可以相互对应起来了。为此我们做下面的工作:

    //角色行动方向枚举
    	public enum PersonDirection
    	{
    		//正常向前
    		Forward=90,
    		//正常向后
    		Backward=270,
    		//正常向左
    		Left=180,
    		//正常向右
    		Right=0,
    	}
             我们这里定义了四个方向上的角度,当我们角色旋转到Forward方向时,我们根据用户按下的键,来判断角色要向那个方向旋转:

    private void SetPersonDirection(PersonDirection mDir)
    	{
    		//根据目标方向与当前方向让角色旋转
    		if(mDirection!=mDir)
    		{
    		    transform.Rotate(Vector3.up*(mDirection-mDir));
    		    mDirection=mDir;
    		}
    	}
            在该方法中,如果目标方向大于当前方向,那么角色将逆时针旋转,否则将顺时针旋转,角度差值为0,则不旋转。

            好了,现在角色已经旋转到相应的方向了,我们让它朝前运动:

    transform.Translate(Vector3.forward * WalkSpeed * Time.deltaTime);
           接下来我们为角色定义状态枚举值:

    	//角色状态枚举
    	public enum PersonState
    	{
    		idle,
    		run,
    		walk,
    		jump,
    		attack
    	}
           我们在上面的代码上面做修改,最终形成的代码为:

    using UnityEngine;
    using System.Collections;
    
    public class RPGControl : MonoBehaviour {
    	
    	//定义角色动画
    	private Animation mAnimation;
    	//定义角色状态
    	public PersonState mState=PersonState.idle;
    	//定义方向状态
    	public PersonDirection mDirection=PersonDirection.Forward;
    	//定义角色弹跳量
    	public float mJumpValue=2F;
             //定义旋转角
    	private float mAngles;
    	//定义相机
    	public GameObject mCamera;
    	//定义角色行动方式
    	public PersonState RunOrWalk=PersonState.walk;
    	
    	public float WalkSpeed=1.5F;
    	public float RunSpeed=3.0F;
    	//角色状态枚举
    	public enum PersonState
    	{
    		idle,
    		run,
    		walk,
    		jump,
    		attack
    	}
    	//角色行动方向枚举
    	public enum PersonDirection
    	{
    		//正常向前
    		Forward=90,
    		//正常向后
    		Backward=270,
    		//正常向左
    		Left=180,
    		//正常向右
    		Right=0,
    	}
    	
    	void Start () 
    	{
    	   //获取动画
    	   mAnimation=gameObject.GetComponent<Animation>();
    	}
    
    	void Update () 
    	{
    	   //前进
    	   if(Input.GetKey(KeyCode.W))
    	   {
    	     SetPersonDirection(PersonDirection.Forward);
    		 SetPersonAnimation();
    	   }
    	   //后退
    	   if(Input.GetKey(KeyCode.S))
    	   {
    		 SetPersonDirection(PersonDirection.Backward);
    		 SetPersonAnimation();
    	   }
    	   //向左
    	   if(Input.GetKey(KeyCode.A))
    	   {
    		 SetPersonDirection(PersonDirection.Left);
    		 SetPersonAnimation();
    	   }
    	   //向右
    	   if(Input.GetKey(KeyCode.D))
    	   {
    		 SetPersonDirection(PersonDirection.Right);
    		 SetPersonAnimation();
    	   }
    	   //巡逻或等待
    	   if(Input.GetKeyUp(KeyCode.A)||Input.GetKeyUp(KeyCode.D)||Input.GetKeyUp(KeyCode.S)||Input.GetKeyUp(KeyCode.W)||Input.GetKeyUp(KeyCode.Space))	
    	   {
    		 mAnimation.Play("idle");
    	     mState=PersonState.idle;
    	   }
    	   //跳跃
    	   if(Input.GetKey(KeyCode.Space))
    	   {
    		 transform.GetComponent<Rigidbody>().AddForce(Vector3.up * mJumpValue,ForceMode.Force);
    		 mAnimation.Play("Jump");
    		 mState=PersonState.jump;
    	   }
    	   //攻击
    	   if(Input.GetMouseButton(0))
    	   {
    		 mAnimation.Play("Attack");
    		 mState=PersonState.attack;
    		 StartCoroutine("ReSetState");
    	   }
    	   //计算旋转角
    	   if(Input.GetMouseButton(1))
    	   {
    		  //计算水平旋转角
    		  mAngles+=Input.GetAxis("Mouse X") * 15;
    		  //旋转角色
    		  transform.rotation=Quaternion.Euler(new Vector3(0,mAngles,0));
    	   }
    	}
    	
    	private void SetPersonDirection(PersonDirection mDir)
    	{
    		//根据目标方向与当前方向让角色旋转
    		if(mDirection!=mDir)
    		{
    		    transform.Rotate(Vector3.up*(mDirection-mDir));
    		    mDirection=mDir;
    		}
    	}
    	
    	private void SetPersonAnimation()
    	{
    		if(RunOrWalk==PersonState.walk)
    		{
    		   mAnimation.Play("Walk");
    		   mState=PersonState.walk;
    		   transform.Translate(Vector3.forward * WalkSpeed * Time.deltaTime);
    		}
    		else if(RunOrWalk==PersonState.run)
    		{
    		   mAnimation.Play("Run");
    		   mState=PersonState.run;
    		   transform.Translate(Vector3.forward * RunSpeed * Time.deltaTime);
    		}
    	}
    	
    	IEnumerator ReSetState()
    	{
    		//当攻击动画播放完毕时,自动切换到巡逻状态
    		yield return new WaitForSeconds(mAnimation.clip.length);
    		mAnimation.Play("idle");
    	    mState=PersonState.idle;
    	}
    	
    		
    }
    
            其中,SetPersonAnimation()方法将根据RunOrWalk值来决定角色是采用行走还是奔跑的方式移动。最后,我们加上一个摄像机跟随的脚本SmoothFollow,这个脚本在官方提供的Script资源包里,我们把该脚本绑定到主摄像机上,并设定我们的角色为其跟随目标。

           最后看看效果动画吧:从这里下载动画(2M图片大小的限制啊,还有这难用的编辑器啊)

           需要项目文件的朋友可以给我留言哦!

           喜欢我请记住我的名字:秦元培,我的博客地址是:blog.csdn.net/qinyuanpei!

           转载请注明出处,本文作者:秦元培,本文出处:http://blog.csdn.net/qinyuanpei/article/details/23709427       

           

           2014年9月12日更新:

        大家好,这里是博主最近更新的内容,由于当初写这篇文章的时候,博主刚刚开始学习Unity3D技术,所以对于很多问题都没有透彻的理解。当时的这篇文章中虽然最终实现了博主想要的效果,可是作为一名有节操的程序员,对于自己过去写得不好或者错误的程序,总是应该及时完善或者改正的。所以,下面大家看到的内容和这篇文章的主旨是一致的,本文的代码基本上参考了[Unity3D]Unity3D游戏开发之自由视角下的角色控制的写法,因为这里的原理是相通的。好了,下面直接给出最终的成果吧!


         一、角色控制

    using UnityEngine;
    using System.Collections;
    
    public class PlayerController : MonoBehaviour {
    	
    	//移动速度
    	public float MoveSpeed=1.5F;
    	//奔跑速度
    	public float RunSpeed=4.5F;
    	//旋转速度
    	public float RotateSpeed=30;
    	//重力
    	public float Gravity=20;
    	//动画组件
    	private Animator mAnim;
    	//声音组件
    	private AudioSource mAudio;
    	//速度
    	private float mSpeed;
    	//移动方式,默认为Walk
    	public TransportType MoveType=TransportType.Walk;
    	//游戏管理器
    	private GameManager mManager;
    	//角色控制器
    	private CharacterController mController;
    	
    
    	void Start () 
    	{
    	   //获取动画组件
    	   mAnim=GetComponentInChildren<Animator>();
    	   //获取声音组件
    	   mAudio=GetComponent<AudioSource>();
    	   //获取游戏管理器
    	   mManager=GameObject.Find("GameManager").GetComponent<GameManager>();
    	   //获取角色控制器
    	   mController=GetComponent<CharacterController>();
    	}
    
    	void Update () 
    	{
    		//只有处于正常状态时玩家可以行动
    		if(mManager.Manager_State==GameState.Normal)
    		{
    	        MoveManager();
    	    }
    	}
        
    	//移动管理
    	void MoveManager()
    	{
    		//移动方向
    		Vector3 mDir=Vector3.zero;
    		if(mController.isGrounded)
    		{
    	       if(Input.GetAxis("Vertical")==1)
    	       {
    		      SetTransportType(MoveType);
    			  mDir=Vector3.forward * RunSpeed * Time.deltaTime;
    	       }
    	       if(Input.GetAxis("Vertical")==-1)
    	       {
    		      SetTransportType(MoveType);
    			  mDir=Vector3.forward * -RunSpeed * Time.deltaTime;
    	       }
    	       if(Input.GetAxis("Horizontal")==-1)
    	       {
    		      SetTransportType(MoveType);
    		      Vector3 mTarget=new Vector3(0,-RotateSpeed* Time.deltaTime,0);
    		      transform.Rotate(mTarget);
    	       }
    	       if(Input.GetAxis("Horizontal")==1)
    	       {
    		      SetTransportType(MoveType);
    		      Vector3 mTarget=new Vector3(0,RotateSpeed* Time.deltaTime,0);
    		      transform.Rotate(mTarget);
    	       }
    	       if(Input.GetAxis("Vertical")==0 && Input.GetAxis("Horizontal")==0)
    	       {
    		      mAnim.SetBool("Walk",false);
    		      mAnim.SetBool("Run",false);
    	       }
    	   }
    		//考虑重力因素
    		mDir=transform.TransformDirection(mDir);
    		float y=mDir.y-Gravity *Time.deltaTime;
    		mDir=new Vector3(mDir.x,y,mDir.z);
    		mController.Move(mDir);
    
    	   //使用Tab键切换移动方式
    	   if(Input.GetKey(KeyCode.Tab))
    	   {
    		  if(MoveType==TransportType.Walk){
    			MoveType=TransportType.Run;
    		  }else if(MoveType==TransportType.Run){
    			MoveType=TransportType.Walk;
    		  }
    	   }
    	}
    
    
        
    	//设置角色移动的方式
    	public void SetTransportType(TransportType MoveType)
    	{
    	   switch(MoveType)
    	   {
    			case TransportType.Walk:
    				MoveType=TransportType.Walk;
    				mAnim.SetBool("Walk",true);
    				mSpeed=MoveSpeed;
    				break;
    			case TransportType.Run:
    				MoveType=TransportType.Run;
    				mAnim.SetBool("Run",true);
    				mSpeed=RunSpeed;
    				break;
    	   }
    	}
    
    }
    

             二、相机控制

    using UnityEngine;
    using System.Collections;
    
    public class TargetFollow : MonoBehaviour {
    
    	//相机追随的目标
    	public Transform Target;
    	//相机追随的距离
    	public float Distance = 2.5F;
    	//相机高度
    	public float Height = 5.0F;
    	
    	//插值处理
    	public bool isNeedDamping=true;
    	//高度插值参数
    	private float HeightDamping = 2.0F;
    	//角度插值参数
    	private float RotationDamping = 3.0F;
    	
    	//鼠标缩放距离最值
    	private float MaxDistance=5;
    	private float MinDistance=0.5F;
    	//鼠标缩放速率
    	private float ZoomSpeed=2F;
    	
    
    	void Update() 
    	{
    		//目标合法性检查
    		if(!Target) 
    			return;
    		
    		//计算目标角度和高度
    		float mTargetAnagle=Target.eulerAngles.y;
    		float mTargetHeight=Target.position.y+Height;
    
    		//当前角度和高度
    		float mCurrentAngle  = transform.eulerAngles.y;
    		float mCurrentHeight = transform.position.y;
    		
    		//插值处理
    		if(isNeedDamping){
    			mCurrentAngle=Mathf.Lerp(mCurrentAngle,mTargetAnagle,RotationDamping * Time.deltaTime);
    			mCurrentHeight=Mathf.Lerp(mCurrentHeight,mTargetHeight,HeightDamping * Time.deltaTime);                 
    		}else{
    			mCurrentAngle=mTargetAnagle;
    			mCurrentHeight=mTargetHeight;
    		}
    		
    		//计算相机的角度
    		Quaternion mRotation= Quaternion.Euler(new Vector3(0,mCurrentAngle,0));
    		//鼠标滚轮事件处理
    		Distance-=Input.GetAxis("Mouse ScrollWheel") * ZoomSpeed;
    		Distance=Mathf.Clamp(Distance,MinDistance,MaxDistance);
    		//计算相机的位置
    		transform.position = mRotation * new Vector3(0,0,-Distance) * Distance + Target.position;
    		//设置相机高度
    		Vector3 mPos=transform.position;
    		transform.position=new Vector3(mPos.x,mCurrentHeight,mPos.z);
    		
    		//始终观察目标
    		transform.LookAt(Target);
    	}
    }
    

          这里博主并没有编写鼠标旋转视角的相关代码,这部分代码可以参考博主的文章。这次更新的内容就是这样啦,希望大家喜欢。


        



           

    展开全文
  • Unity3d 《新仙剑奇侠传》游戏源码 C#脚本 仅供学习,请不要商用!
  •  在前面的文章中,我们分别实现了一个自定义的角色控制器《[Unity3D]Unity3D游戏开发之仿仙剑奇侠传角色控制效果》和角色死亡的效果《[Unity3D]Unity3D游戏开发之仿仙剑奇侠传角色死亡效果实现》。今天我们继续来做...

              大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei

             在前面的文章中,我们分别实现了一个自定义的角色控制器《[Unity3D]Unity3D游戏开发之仿仙剑奇侠传角色控制效果》和角色死亡的效果《[Unity3D]Unity3D游戏开发之仿仙剑奇侠传角色死亡效果实现》。今天我们继续来做点和仙剑相关的东西,首先我们来看一副图:


             这幅图呢,是仙剑奇侠传的第一个迷宫场景——仙灵岛的莲花池。男主角李逍遥为替婶婶求药独闯仙灵岛,在经历了前面的小草妖战斗后,李逍遥就来到了 莲花池,这个迷宫的解法很简单,乘坐莲花池中的芦苇垫到不同的地方,然后用破天锤打碎五座阿修罗像,通往仙灵岛的通路就会被打开,就是在仙灵岛李逍遥邂逅了正在沐浴的赵灵儿,仙剑奇侠传的故事从此展开。好了,故事先说到这里,我们来看这里很重要的一个信息:李逍遥踩着芦苇垫到达不同的地方,然后用破天锤打碎石像开启机关。我们把最终要的东西提取出来的话就是我们可以踩着芦苇垫子到不同的地方去。好,我们今天就来实现一个简化的"仙灵岛"吧!那么怎么做呢?不知道大家还记不记得我在《[Unity3D]Unity3D游戏开发之基于ITween实现寻路功能》这篇文章里提到过的寻路方法,在这篇文章中,我们最终实现的效果是这样的:


            现在呢,让我们把莲花池里的构件做一下抽象:每一个 石柱可以看做是一个寻路节点,芦苇垫子可以看做是绑定了寻路组件的一个GameObject。唯一的区别就在于这里的芦苇垫子是需要我们的角色站在上面才能让它运动的,而我们在这篇文章里的游戏体是自己运动的。好,现在我们开始来实现这个功能吧。我们先创建如下图所示的场景:


           我们在水中布置了8个石柱,两个芦苇垫子,当游戏开始时,玩家控制角色走上芦苇垫子时,芦苇垫子将按照事先设定的路线,将角色送达最后一个石柱位置,此时,玩家可以控制角色到达对岸。当玩家再次踏上芦苇垫子时,芦苇垫子会将角色送回起点位置。当芦苇垫子处于移动状态中时,角色可以向四个方向旋转,活动范围仅限于芦苇垫子上。好,交待清楚了场景,我们下面来讲解原理:

           1、角色控制

                 采用在《[Unity3D]Unity3D游戏开发之仿仙剑奇侠传角色控制效果》中实现的角色控制器来控制角色的行为。

           2、寻路的实现

                 采用在《[Unity3D]Unity3D游戏开发之基于ITween实现寻路功能》中实现的寻路组件作为基础脚本,本节在此基础上做扩充和完善,保证角色可以自由的移动

           基于以上两个原理,我们下面来编写今天的脚本:

    using UnityEngine;
    using System.Collections;
    
    public class TransportScript : MonoBehaviour {
    	
    	//寻路组件节点
    	public Transform[] mPaths;
    	//芦苇垫传送器
    	private Transform mTransportor;
    	//被传送的角色
    	public Transform mTransportPlayer;
    	
    	//起点
    	Vector3 StartPoint=new Vector3(0F,0.5F,-13F);
    	//终点
    	Vector3 EndPoint=new Vector3(0F,0.5F,10F);
    	
    	//定义移动的方向类型:从起点到终点为0,从终点到起点为1
    	private int MoveType=0;
    	//定义移动状态的标志变量
    	private bool isMoving=false;
    	
    	//存储寻路组件设置的哈希表
    	private Hashtable args=new Hashtable();
    	
    	void Start () 
    	{
    		//获取传送器
    		mTransportor=this.transform;
    
    		//初始化寻路插件
    	    Hashtable args = new Hashtable();
    	    //设置路径的点
    		args.Add("path",mPaths);
    		//设置类型为线性,线性效果会好一些。
    		args.Add("easeType", iTween.EaseType.linear);
    		//设置寻路的速度
    		args.Add("speed",2.5f);
    	    //移动的整体时间。如果与speed共存那么优先speed
    		args.Add("time",10f);
    		//是否先从原始位置走到路径中第一个点的位置
    		args.Add("movetopath",true);
    		//延迟执行时间
    		args.Add("delay", 0.1f);
    		//移动的过程中面朝一个点
    		args.Add("looktarget",Vector3.up);
    		//三个循环类型 none loop pingPong (一般 循环 来回)	
    		args.Add("loopType", "pingPong");
    		//是否让模型始终面朝当面目标的方向
    		//如果你发现你的模型在寻路的时候时钟都是一个方向那么一定要打开这个
    		args.Add("orienttopath",true);
    	
    		iTween.MoveTo(gameObject,args);
    		iTween.Pause();
    	}
    	
    	void  Update()
    	{
    	   //如果当前处于停止状态则暂停动画
    	   if(isMoving==false)
    	   {
    		  iTween.Pause();
    	   }
    
    	   //从起点到终点的控制
    	   if(MoveType==0 && mTransportor.position!=EndPoint && isMoving==true)
    	   {
    		  //未到达终点则继续运动
    	      mTransportPlayer.position=mTransportor.position;
    	   }else if(MoveType==0 && mTransportor.position==EndPoint)
    	   {
    		  //到达终点则暂停运动
    		  iTween.Pause();
    		  isMoving=false;
    		  MoveType=1;
    	   }
    		
    	   //从终点到起点的控制
    	   if(MoveType==1 && mTransportor.position!=StartPoint && isMoving==true)
    	   {
    		  //未到达终点则继续运动
    	      mTransportPlayer.position=mTransportor.position;
    	   }else if(MoveType==1 && mTransportor.position==StartPoint)
    	   {
    		  //到达终点则暂停运动
    		  iTween.Pause();
    	      isMoving=false;
    		  MoveType=0;
    	   }
    	   
    	}
    	
    	//当角色走上芦苇垫的时候开始移动动画
    	void OnTriggerEnter(Collider mCollider)
    	{
    		if(mCollider.gameObject.name=="Samuzai")
    		{
    		   
    		   if(isMoving==false)
    		   {
    			  isMoving=true;
    	          iTween.Resume();
    		   }
    		}
        }
    }
    

            在上面的代码中,我们需要把握以下几点:

            1、在Start()方法中,我们主要完成的是寻路组件的初始化,这里我们将寻路动画设置为来回,然后通过Pause()、Resume()方法两个方法来控制动画的播放,因为iTween没有提供在Update()中实现动画播放的方法,iTween只支持部分方法在Update()方法中使用,具体的大家可以自己去了解iTween的API文档。

            2、我们主要通过isMoving标志来标记当前的状态,使用OnTriggerEnter()方法来判断角色是否走上了芦苇垫子,当角色走上芦苇垫子上时,就开始播放寻路动画。当到达起点/终点时,寻路动画即停止。当角色再次走上芦苇垫子时,再次开始寻路动画。

           3、我们再Update()方法中通过改变角色的位置实现了角色和芦苇垫子的同步移动,当到达起点/终点时,寻路动画即停止,直到下一次被触发。

           4、触发与碰撞的区别在于触发不会产生力的作用,可以通过OnTriggerEnter/OnTriggerStay/OnTriggerExit方法分别监听触发开始、触发中、触发结束。要使用Trigger需要勾选碰撞器的isTrigger选项。具体的区别我会在后面的文章中为大家做讲解。

          

           这篇文章中用到的东西就是这么多,主要的难点是寻路动画的控制,由于官方没有提供有关的方法,所以我们只能采用Pause()、Resume()方法来控制角色的动画。最后我们来一起看看实现的效果吧,是不是有仙灵到的感觉呢?呵呵

          



          最后奉上几个民间的同人游戏演示:

          1、UDK制作的仙剑一3D版仙灵岛场景

          2、U3D制作的仙剑五前传同人演示1

                U3D制作的仙剑五前传同人演示2

          每次看到这里,内心都会充满一种希望,希望在有生之年玩到更多的仙剑,踏歌长行、梦想永续!继续努力吧!  

          喜欢我的博客请记住我的名字:秦元培,我的博客地址是:blog.csdn.net/qinyuanpei

          转载请注明出处,本文作者:秦元培,本文出处:http://blog.csdn.net/qinyuanpei/article/details/24038749


    展开全文
  •  感谢对我的支持,在上一篇文章《 [Unity3D]Unity3D游戏开发之仿仙剑奇侠传角色控制效果》中,我们通过自定义脚本实现了在RPG游戏中的角色控制器,当然这个角色器目前还不完善,为什么这么说呢,我在后面的一篇...

             大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei

             感谢对我的支持,在上一篇文章《 [Unity3D]Unity3D游戏开发之仿仙剑奇侠传角色控制效果》中,我们通过自定义脚本实现了在RPG游戏中的角色控制器,当然这个角色器目前还不完善,在碰撞以及控制等方面还存在某些问题和不足,对于这些问题,我会在后面的一篇文章中会给大家做详细的说明,力图设计出一个为RPG游戏服务的角色控制器,方便大家开发RPG类型的游戏。那么,今天我想和大家分享的是在Unity3D中一个比较重要的组件——布娃娃(Ragdoll)。我们首先来看张图片吧!

     

              相信熟悉仙剑的朋友一定知道这样一句话:胜败乃兵家常事,大侠请重新来过。从仙剑一到仙剑五前传,仙剑带给我们的感动里不仅仅是一个个跌宕起伏、感人至深的故事,还有每一次战斗失败之后从头再来的勇敢。人生有时候就像游戏一样,我们总会遇到这样或着那样的挫折和困难。虽然在仙剑这样一个五灵六界的世界观下,人类基本作为最弱小的生命存在,但是每次我们的主角们不是一直在努力去做吗?所以我们的人生一定要积极向上,充满激情地去做一件事情。博主最讨厌的就是玩游戏玩到最后只剩下打嘴仗的这种玩家,游戏一定是有输有赢的,试问各位仙剑玩家哪一个没有被Boss虐过,可是游戏的乐趣不就是在于一遍遍地去超越自己吗?输不可怕,怕的是我们因为输了就丧失了努力的勇气。我觉得仙剑一直再像我们传递一种正能量,那就是我们一定要让自己勇敢,人生中有很多的事情或许我们都无法掌控,就像李逍遥剑术超凡却不能帮助灵儿逃脱宿命、云天河拥有神龙之息、后羿射日神弓,为救山下百姓而箭射琼华,却救不了心爱的女孩儿的性命、夏侯瑾轩以为 凡事尽心尽力就能做好,可是到最后他却只能拿匕首刺向瑕妹子,只是为了不让枯木占据她的身体。或许我们并不知道未来会是什么样子吧,但是至少在此时此刻,我们曾经努力过、我们很用心地做过某些事情,这样就够了。好了,我们不去评论这些玩家了,我们正式开始今天的内容。

             在RPG游戏中,当角色HP降低到0时,角色即进入了死亡状态,此时角色将倒在地上。在过去的游戏设计中,角色倒在地上的动作通常是由动画师绘制一定的动画来完成的,这种方式虽然简单,但是由于角色倒地的动作都是相同的,无法实时反映出角色受攻击的情况,所以在游戏开发领域逐渐形成了一种新的模型即布娃娃(Ragdoll),该模型就是用来模拟角色死亡的状态的。在Unity3D中,系统为我们提供了布娃娃组件,下面请大家和我一起来学习怎样使用布娃娃来模拟角色死亡的状态吧:

             首先我们创建一个新项目,在场景中创建一个Plane和平行光,接下来我们导入我们事先准备好的FBX模型,此时在游戏场景中应该可以看到下面的内容:


           接下来,我们选中模型Samuzai,为其创建一个布娃娃组件:


              我们一起来看打开的对话框:


            如果我们的模型中提供了骨骼动画的话,那么我们单击右侧的选择按钮就能找到相应的文件,然后我们点Create,此时模型下的骨骼组件应该会变成下面这个样子:


          我们将盒子碰撞器移除,将模型的动画设为None,现在我们来运行程序:


          可以看到,我们的角色很真实地倒在了地上,而且如果我们多次运行程序的话,会发现角色每次死亡的时候都是不一样的,这样大大增强了游戏的真实感,如果每次战斗死亡的姿势都是一样的,这样会不会视觉疲劳呢?虽然《轩辕剑6》的市场反响并不怎么样,然而得益于Unity3D中的强大物理引擎,每次怪物死亡的时候都死得比较有范儿,大家可以自己去玩玩试玩版啊,哈哈。这里希望仙剑奇侠传的后续作品能够注意到这个问题,作为大侠,我们要死得轰轰烈烈的嘛,好了,这就是今天的文章了,希望大家喜欢!

         喜欢我的博客请记住我的名字:秦元培,我的博客地址是:blog.csdn.net/qinyuanpei

         转载请注明出处,本文作者:秦元培,本文出处:http://blog.csdn.net/qinyuanpei/article/details/23869275


    展开全文
  • 大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei...那么,作为这个月的最后一篇文章,今天博主想和大家分享的是关于Unity3D中的Mecanim动画系统。博主很早就想写这篇文章了,可是由
            大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。博主总算赶在这个月底写出了这篇文章。这个月因为期末考试一直没时间研究太多关于技术方面的东西,虽然博客一直在更新,可是对于文章的质量博主还是有一个很清楚的认识的。那么,作为这个月的最后一篇文章,今天博主想和大家分享的是关于Unity3D中的Mecanim动画系统。博主很早就想写这篇文章了,可是由于各种原因一直没有机会去深入地研究这个问题,这几天考完试时间变得宽裕起来,所以博主就花时间研究了下这个问题。Mecanim动画系统是Unity3D推出的全新的动画系统,具有重定向、可融合等诸多新特性,通过和美工人员的紧密合作,可以帮助程序设计人员快速地设计出角色动画。

            今天文章的标题叫做《Unity3D游戏开发之当仙剑遇上Mecanim动画系统》,为什么取这样一个题目呢?因为博主今天想和大家分享的是Mecanim动画系统的重定向特性,众所周知,《仙剑奇侠传》是一部经典的RPG游戏,这部游戏到今天依然焕发着强大的生命力。博主在网上认识了一个制作《仙剑奇侠传》同人游戏的小团队,他们目前正在着手制作一个称为《仙剑奇侠传五外传:心愿》的同人游戏,该游戏基于Unity3D,由于技术上的一致性,博主向他们索取了一些游戏素材,而这成为了博主决心要研究Mecanim动画系统的一个主要原因。如图是《仙剑奇侠传五前传》中瑕的一个模型:


    拿到这个模型的时候,博主发现这个模型的所有动画都是放在一个AnimationClip中的,所以博主果断将这个动画文件进行了分割,分割的方法是在资源目录中找到这个AnimationClip,然后在右侧的属性窗口中选择Animations选项卡中通过+号来新建一个AnmationClip,在指定了起点帧数和终点帧数后就实现对动画的分割。如图示博主经过分割后的动画文件列表:


      可以看到这里博主将一个动画片段分割成了4个动画片段,这样我们就可以通过Animation组件来实现对动画的播放。而这正是在Mecanim动画系统推出之前Unity3D采用的动画控制形式。由于这个模型在建模的时候存在问题,所以导致瑕在游戏场景中的角度出现错误,博主只好自己用3D建模软件进行调整,结果意外得发现瑕的模型中是有骨骼的,博主忽然间想到了一个问题,是否可以利用Mecanim动画系统来为这个模型添加动画呢?答案是肯定的。由于这个模型在建模的时候将动画和模型一起创建了,因此我们需要首先将这个动画从模型中去除,因为Mecanim动画系统的一个主要思路就是让一套动画可以通过重定向应用到不同的模型上,既然有动画可用,那么模型自带的动画可以暂时去除。而让动画从模型中去除的方法很简单,就是在导出FBX模型的时候将嵌入的媒体选项不要勾选,这样我们的模型就可以和动画分离开了,经过这一步后,我们正式开始Mecanim动画系统的讲解。要理解Mecanim动画系统,首先需要了解这样几个概念:

            一、Avater,即阿凡达,这是Mecanim动画系统中实现动画绑定的一个接口,该接口可以实现骨骼和肌肉系统的匹配,从而保证角色在执行动画的时候,角色能够按照预先设定的动画来运动。我们首先在项目资源窗口中找到了模型文件,如图:


    我们注意到在右侧的属性窗口中有三个选项卡:Model、Rig、Animations。这里我们选择Rig选项卡,将Animation Type设为Humanoid,即双足类型的动画。这种类型可以支持大多数的类人形的模型。此外,Lagacy类型用来兼容Unity3D低版本中的模型动画,Generic类型是一种介于人形和非人形的模型之间的通用类型。我们选择Humanoid类型后,在下方选择生成Avater的类型诶Create Form This Model,即通过当前的模型来生成一个Avater。此时,Configure按钮由不可用状态变成可用状态,我们点击该按钮,如果我们没有保存游戏场景的话,此时Unity3D会提示我们保存场景,我们直接选择保存,接下来回打开一个新的场景,如图:


    此时场景中会显示模型的骨骼结构,对应地,在右侧窗口中显示了该模型主要的身体结构,可以看到此时右侧窗口中所有的点都是以绿色显示的,这表示当前模型的骨骼关节和模型匹配正确。此时,我们可以切换到Muscles选项卡中,这是我们可以通过拖动滑块来检查模型匹配是否正确。在使用Avater的时候,有两点需要注意:

            1、如果模型建模没有严重的错误的话,可以通过窗口底部的Mipping->AutoMap来实现对模型的自动匹配,这一点可以提高工作效率,但是作为一名有节操的开发人员,我们绝不能过度地依赖于这一功能。
      2、当在手动调整模型匹配时,如果提示T-Pose错误,可以通过Pose->Enforce T-Pose来实现强制转化,博主最近才开始学习游戏建模,所以这里可能暂时无法解释清楚,大家就作为经验之谈来看吧。
      确认模型匹配没有什么问题后,点击Done完成编辑。

            二、AnimatorController:如果说Avater是将模型的身体和骨骼实现匹配的接口,那么AnimatorController就是讲动画和模型实现绑定的接口。我们这里创建一个XiaAnimaterController。双击该文件,会打开Animator窗口,如图:


    博主这里不想详细介绍关于Unity3D动画系统中的状态机和混合树等概念,因为这些概念现在解释起来实在困难,博主打算放在下一篇文章中来详细的讲解Mecanim动画的状态机和混合树的概念。今天我们只是对Mecanim动画系统做一个初步的了解,因为要想彻底理解这个动画系统,绝非一朝一夕之功啊。博主在这里创建了6种动画状态,并通过带箭头的线条将它们连了起来。博主这里想实现什么功能呢?博主希望在一定条件下可以触发相应的条件,当出发结束后立即回到默认的状态,这里默认的状态是Idle。估计大家到这里会有点混乱,博主前面不是说这个模型没有动画的嘛,那么这个的动画是哪里来的呢?对此,博主这里做一下解释啊,博主在创建这个项目之前呢,提前准备了一个叫做WomanKnightAnimationPack的动画包,它提供了游戏中常用的动画,我们这里就是要通过Mecanim的重定向功能将这个动画运用到我们的模型上去,这个动画包可以从官方的资源商店中下载。好了,现在我们来讲解怎么讲动画和模型绑定,我们单击默认的Idle动画,在窗口右侧将会显示如下内容:


    通过Motion文本框我们可以为当前的状态绑定一种动画,选中Foot IK和Mirror这两个选项,似乎可以解决角色在行走或者奔跑过程中出现的问题,可是博主在查API文档的时候并没有找到相关的解释,如果有朋友知道的话,希望可以告诉博主啊。类似地,我们可以为不同的状态绑定一个动画片段,可是问题马上又来了,我们如何按照游戏策划在适当地时候播放相应的动画呢?这就是动画的触发问题了,博主请大家想一想有限状态机的概念,如果不知道这个概念的,请自行了解,这是游戏设计中非常重要、非常有用的一个概念。我们知道在有限状态机中,无论我们使用switch-case结构还是if-else结构,都需要一个状态值来作为判断的依据,那么在Mecanim中这个原则依然适用。因为,Mecanim动画系统就是基于状态机的。好了,下面我们来创建一个状态值。如图,

    在Mecanim动画系统中提供了四种类型的状态值,分别是Float、Int、Bool、Trigger,其中Trigger和OnTrigger()方法可以联用,实现触发式播放动画。好了,现在我们创建一个整型的状态值,默认值为0。我们首先选中Idle->Walk这条路线,在右侧的窗口中将参数设为ActionID,将值设为1,将方法设为Equals,其含义是当ActionID的取值为1时将执行Walk动画。同样地,我们将Walk->Idle这条路线选中,设将参数设为ActionID,将值设为0,将方法设为Equals,其含义是当ActionID的取值为1时将执行Idle动画。其余的动画状态参数和Walk设置方法相同,从Idle出发的线路依次为1,2,3,4,5,指向Idle的路线全部为0。现在我们将这个AnimatorController指定给场景中的模型,如图:


    此时我们运行程序,由于我们已经为瑕指定了默认的动画,于是我们将看到:


      虽然这个动画和我们的瑕妹子搭配得有点违和,但是我们已经初步掌握了使用Mecanim动画系统的方法,好了,下面我们来编写一段脚本来控制人物切换动画,因为只有默认动画是没有什么实际用途的:

    using UnityEngine;
    using System.Collections;
    
    public class MecanimController : MonoBehaviour {
    
    	//Mecanim动画组件
    	private Animator mAnim;
    
    	void Start () 
    	{
    		mAnim=GetComponent<Animator>();
    	}
    
    	void Update () 
    	{
    		//如果按下W则步行前进
    		if(Input.GetKey(KeyCode.W))
    		{
    			//播放行走动画
    			mAnim.SetInteger("ActionID",1);
    			transform.Translate(Vector3.forward * 2.5f * Time.deltaTime);
    		}
    		//如果按下Shift+W则跑步前进
    		if(Input.GetKey(KeyCode.W) && Input.GetKey(KeyCode.LeftShift))
    		{
    			//播放奔跑动画
    			mAnim.SetInteger("ActionID",2);
    			transform.Translate(Vector3.forward * 2.5f * Time.deltaTime);
    		}
    		//如果按下I,则播放攻击动画1
    		if(Input.GetKey(KeyCode.I))
    		{
    			//播放攻击动画1
    			mAnim.SetInteger("ActionID",3);
    		}
    		//如果按下J,则播放攻击动画2
    		if(Input.GetKey(KeyCode.J))
    		{
    			//播放攻击动画2
    			mAnim.SetInteger("ActionID",4);
    		}
    		//如果按下K,则播放攻击动画3
    		if(Input.GetKey(KeyCode.K))
    		{
    			//播放攻击动画3
    			mAnim.SetInteger("ActionID",5);
    		}
    
    		//如果松开各键位,则还原为Idle状态
    		if(Input.GetKeyUp(KeyCode.W) || Input.GetKeyUp(KeyCode.I) || Input.GetKeyUp(KeyCode.J) 
    		   || Input.GetKeyUp(KeyCode.J) || Input.GetKeyUp(KeyCode.K) || Input.GetKeyUp(KeyCode.LeftShift))
    		{
    		   //播放待机动画
    		   mAnim.SetInteger("ActionID",0);
    		}
    	}
    }
    


      最后我们一起来看实际的效果吧!


      场景中的地形由Unity3D自带的地形工具创建,其实博主是想搭建一个《仙剑奇侠传五前传》中的云来石的场景,因为博主坚信,凭借瑾轩的智慧怎么会想不到在和瑕双双坠崖后召唤云来石呢,所以博主坚信他们俩都没有死,因为暮姐最后回眸的一瞬间表情是喜悦的,而背后传来了一急一慢两种脚步声,所以结局大家可以自己去想象啦。在同人游戏《仙剑奇侠传五后传:心愿》中他们策划的剧情和博主的基本一致,或许这就是《仙剑奇侠传》系列游戏之所以能让博主一直愿意乐观下去、努力下去的原因吧,我们都是普通人,可是我们并不是因为普通而普通,而是我们选择了不作为。以前博主听说过一款由仙剑玩家发起的游戏《仙剑奇侠传四:回到起点》,当时看到游戏的宣传动画时激动了好一阵子,可是到了后来这部游戏因为制作团队的解散而宣布停止,这似乎总让人想起上海软星的过去。细心的朋友一定发现了博主使用的素材是仙剑四中的模型,可以发现这个模型是较为简陋的,树木基本上是纸片拼成的,影子是在模型里创建的,当年弓长君带领上软制作仙剑三、仙剑三外传、仙剑四的艰难程度可窥一斑。不过目前由上软核心骨干组成的上海烛龙已经从当年的困境中走了出来,成为和大宇双剑抗衡的第三把剑。为何博主总是钟情于剑呢?因为剑的传说,直到永恒。好了,谢谢大家关注我的博客,今天的内容就是这样了,希望大家喜欢啊,呵呵。


               学习Mecanim动画系统推荐视频地址:http://www.tudou.com/programs/view/Ul78LC6rukI/


        每日箴言:事情并不是因为难而让我们不敢做,而是因为我们不敢做事情才变得难.




       喜欢我的博客请记住我的名字:秦元培,我博客地址是blog.csdn.net/qinyuanpei。
       转载请注明出处,本文作者:秦元培,本文出处:http://blog.csdn.net/qinyuanpei/article/details/35330525


    展开全文
  • 仙剑奇侠传源代码

    2020-07-03 10:16:43
    仙剑奇侠传项目源码,大家有兴趣的可以下载学习一下
  • 在上一篇文章中,我们从Unity3D为我们提供的相机原型实现了非编码式的小地图更多精彩请关注【狗刨学习网】,如果结合GUI在这个小地图下面绘制一些背景贴图,相信整体的效果会更好一些。博主希望这个问题大家能够自己...
    在上一篇文章中,我们从Unity3D为我们提供的相机原型实现了非编码式的小地图更多精彩请关注【狗刨学习网】,如果结合GUI在这个小地图下面绘制一些背景贴图,相信整体的效果会更好一些。博主希望这个问题大家能够自己去做更深入的研究,因为贴图的绘制在前面的文章中,我们已经已经提到了,所以这里就不打算再多说。今天呢,我们继续为这个小项目加入一些有趣的元素。首先请大家看一下下面的图片:

             

          相信熟悉国产单机游戏的朋友看到这幅图片一定会有种熟悉的感觉,博主在本系列的第一篇文章中,就已经提到了博主是一个国产单机游戏迷,博主喜欢这样有内涵、有深度的游戏。或许从操作性上来说,仙剑系列的回合制在很大程度上落后于目前的即时制,但是我认为回合制和即时制从本质上来说没有什么区别,即时制是不限制攻击次数的回合制,所以从玩法上来讲,回合制玩家需要均衡地培养每一个角色,在战斗中寻找最优策略,以发挥各个角色的优势,因此博主认为如果把即时制成为武斗,那么回合制在某种程度上就可以称之为文斗,正是因为如此,仙剑系列注重剧情、注重故事性,为玩家带来了无数感动。鉴于国内网游玩家的素质,博主一贯反感网游,所以比较钟情于武侠/仙侠单机游戏,虽然仙剑同样推出了网络版,但是在游戏里开着喇叭、挂着语音、相互谩骂的网游环境,实在让我找不回仙剑的感觉。好了,闲话先说到这里,今天我们来说一说现价奇侠传四里面的角色控制。玩过仙剑奇侠传的人都知道,仙剑奇侠传真正进入3D界面的跨时代作品当属上海软星开发的仙剑奇侠传四,该公司之前曾开发了仙剑奇侠传三、仙剑奇侠传三外传等作品,后来由于某些原因,该公司被迫解散。而这家公司就是后来在国产单机游戏中的新锐——上海烛龙科技的《古剑奇谭》。有很多故事,我们不愿意相信结局或者看到了结局而不愿意承认,青鸾峰上蓝衣白衫、白发苍苍的慕容紫英,随着魔剑幽蓝的剑影御剑而去的身影,我们都曾记得,或许他真的去了天墉城,只为一句:承君此诺,必守一生。好了,我们正式开始技术分享(博主内心有很多话想说)!

             在仙剑奇侠传四中,玩家可以通过鼠标右键来旋转场景(水平方向),按下前进键时角色将向着朝前(Forward)的方向运动,按下后退键时角色将向着朝后(Backword)的方向运动、当按下向左、向右键时角色将向左、向右旋转90度。从严格意义上来说,仙剑四不算是一部完全的3D游戏,因为游戏视角是锁死的,所以玩家在平时跑地图的时候基本上是看不到角色的正面的。我们今天要做的就是基于Unity3D来做这样一个角色控制器。虽然Unity3D为我们提供了第一人称角色控制器和第三人称角色控制器,但是博主感觉官方提供的第三人称角色控制器用起来感觉怪怪的,尤其是按下左右键时那个旋转,感觉控制起来很不容易,所以博主决定自己来写一个角色控制器。首先我们打开项目,我们还是用昨天的那个例子:

              
            
               很多朋友可能觉得控制角色的脚本很好写嘛,这是一个我们通常见到的版本:
    1. <font face="宋体" size="2">//向左  
    2. if(Input.GetKey(KeyCode.A))  
    3. {  
    4.    SetAnimation(LeftAnim);  
    5.    this.mState=PersonState.Walk;  
    6.    mHero.transform.Translate(Vector3.right*Time.deltaTime*mSpeed);  
    7. }  
    8. //向右  
    9. if(Input.GetKey(KeyCode.D))  
    10. {  
    11.    SetAnimation(RightAnim);  
    12.    this.mState=PersonState.Walk;  
    13.    mHero.transform.Translate(Vector3.right*Time.deltaTime*(-mSpeed));  
    14. }  
    15. //向上  
    16. if(Input.GetKey(KeyCode.W))  
    17. {  
    18.    SetAnimation(UpAnim);  
    19.    this.mState=PersonState.Walk;  
    20.    mHero.transform.Translate(Vector3.forward*Time.deltaTime*(-mSpeed));  
    21. }  
    22. //向下  
    23. if(Input.GetKey(KeyCode.S))  
    24. {  
    25.    SetAnimation(DownAnim);  
    26.    this.mState=PersonState.Walk;  
    27.    mHero.transform.Translate(Vector3.forward*Time.deltaTime*(mSpeed));  
    28.    Vector3 mHeroPos=mHero.transform.position;  
    29. }  </font>
    复制代码

           那么,我们姑且认为这样写没什么问题,那么现在我们导入官方提供的Script脚本资源包,找到MouseLook脚本,在Update()方法中添加对右键是否按下的判断,这样我们就可以实现按下鼠标右键时视角的旋转。修改后的脚本如下:
    1. <font face="宋体" size="2">
    2. using UnityEngine;  
    3. using System.Collections;  
    4.   
    5. /// MouseLook rotates the transform based on the mouse delta.  
    6. /// Minimum and Maximum values can be used to constrain the possible rotation  
    7.   
    8. /// To make an FPS style character:  
    9. /// - Create a capsule.  
    10. /// - Add the MouseLook script to the capsule.  
    11. ///   -> Set the mouse look to use LookX. (You want to only turn character but not tilt it)  
    12. /// - Add FPSInputController script to the capsule  
    13. ///   -> A CharacterMotor and a CharacterController component will be automatically added.  
    14.   
    15. /// - Create a camera. Make the camera a child of the capsule. Reset it's transform.  
    16. /// - Add a MouseLook script to the camera.  
    17. ///   -> Set the mouse look to use LookY. (You want the camera to tilt up and down like a head. The character already turns.)  
    18. [AddComponentMenu("Camera-Control/Mouse Look")]  
    19. public class MouseLook : MonoBehaviour {  
    20.   
    21.     public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 }  
    22.     public RotationAxes axes = RotationAxes.MouseXAndY;  
    23.     public float sensitivityX = 15F;  
    24.     public float sensitivityY = 15F;  
    25.   
    26.     public float minimumX = -360F;  
    27.     public float maximumX = 360F;  
    28.   
    29.     public float minimumY = -60F;  
    30.     public float maximumY = 60F;  
    31.   
    32.     float rotationY = 0F;  
    33.   
    34.     void Update ()  
    35.     {  
    36.         if(Input.GetMouseButton(1))  
    37.         {  
    38.           if (axes == RotationAxes.MouseXAndY)  
    39.           {  
    40.             float rotationX = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * sensitivityX;  
    41.               
    42.             rotationY += Input.GetAxis("Mouse Y") * sensitivityY;  
    43.             rotationY = Mathf.Clamp (rotationY, minimumY, maximumY);  
    44.               
    45.             transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0);  
    46.           }  
    47.           else if (axes == RotationAxes.MouseX)  
    48.           {  
    49.             transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivityX, 0);  
    50.               
    51.           }  
    52.           else  
    53.           {  
    54.             rotationY += Input.GetAxis("Mouse Y") * sensitivityY;  
    55.             rotationY = Mathf.Clamp (rotationY, minimumY, maximumY);  
    56.               
    57.             transform.localEulerAngles = new Vector3(-rotationY, transform.localEulerAngles.y, 0);  
    58.           }  
    59.         }  
    60.     }  
    61.       
    62.     void Start ()  
    63.     {  
    64.         // Make the rigid body not change rotation  
    65.         if (rigidbody)  
    66.             rigidbody.freezeRotation = true;  
    67.     }  
    68. }  
    69. </font>
    复制代码

           接下来,我们将这个脚本拖放到我们的角色上,运行游戏,我们发现了一个问题:当旋转视角后,角色并没有如我们期望地向朝前的方向移动,相反,角色依然沿着世界坐标系里的Vector3.forward向前运动。按照我们的想法,当旋转视角以后,角色应该可以朝着前方运动。怎么办呢?这里我们在上面的代码中加上这样的代码:

    1. <font face="宋体" size="2">
    2. //计算旋转角  
    3.        if(Input.GetMouseButton(1))  
    4.        {  
    5.           //计算水平旋转角  
    6.           mAngles+=Input.GetAxis("Mouse X") * 15;  
    7.           //旋转角色  
    8.           transform.rotation=Quaternion.Euler(new Vector3(0,mAngles,0));  
    9.        }  
    10. </font>
    复制代码

          这里代码的作用是当用户按下鼠标右键旋转视角时,我们首先计算在水平上的旋转角,然后让角色的坐标系跟着视角一起旋转,这样就相当于把Vector3.forward和旋转后的目标角度平行。这样的话,我们控制人物向前运动的时候,它就会按照这个新的方向去运动。这样我们的第一个问题就解决了。我们继续往下看,由于这个模型中只提供了一个行走/奔跑的方向动画,所以就出现了角色动画和角色行为不符的问题,怎么办呢?这时候,我们可以这样想,我们可以先把角色旋转到指定的方向,然后让角色朝着向前的方向运动,这样角色动画和角色行为就可以相互对应起来了。为此我们做下面的工作:
    1. <font face="宋体" size="2">//角色行动方向枚举  
    2.     public enum PersonDirection  
    3.     {  
    4.         //正常向前  
    5.         Forward=90,  
    6.         //正常向后  
    7.         Backward=270,  
    8.         //正常向左  
    9.         Left=180,  
    10.         //正常向右  
    11.         Right=0,  
    12.     }  </font>
    复制代码

             我们这里定义了四个方向上的角度,当我们角色旋转到Forward方向时,我们根据用户按下的键,来判断角色要向那个方向旋转:
    1. <font face="宋体" size="2">private void SetPersonDirection(PersonDirection mDir)  
    2.     {  
    3.         //根据目标方向与当前方向让角色旋转  
    4.         if(mDirection!=mDir)  
    5.         {  
    6.             transform.Rotate(Vector3.up*(mDirection-mDir));  
    7.             mDirection=mDir;  
    8.         }  
    9.     }  </font>
    复制代码

            在该方法中,如果目标方向大于当前方向,那么角色将逆时针旋转,否则将顺时针旋转,角度差值为0,则不旋转。

            好了,现在角色已经旋转到相应的方向了,我们让它朝前运动:
    1. <font face="宋体" size="2">transform.Translate(Vector3.forward * WalkSpeed * Time.deltaTime);  </font>
    复制代码

           接下来我们为角色定义状态枚举值:
    1. <font face="宋体" size="2">//角色状态枚举  
    2. public enum PersonState  
    3. {  
    4.     idle,  
    5.     run,  
    6.     walk,  
    7.     jump,  
    8.     attack  
    9. }  </font>
    复制代码

           我们在上面的代码上面做修改,最终形成的代码为:
    1. <font face="宋体" size="2">
    2. using UnityEngine;  
    3. using System.Collections;  
    4.   
    5. public class RPGControl : MonoBehaviour {  
    6.       
    7.     //定义角色动画  
    8.     private Animation mAnimation;  
    9.     //定义角色状态  
    10.     public PersonState mState=PersonState.idle;  
    11.     //定义方向状态  
    12.     public PersonDirection mDirection=PersonDirection.Forward;  
    13.     //定义角色弹跳量  
    14.     public float mJumpValue=2F;  
    15.          //定义旋转角  
    16.     private float mAngles;  
    17.     //定义相机  
    18.     public GameObject mCamera;  
    19.     //定义角色行动方式  
    20.     public PersonState RunOrWalk=PersonState.walk;  
    21.       
    22.     public float WalkSpeed=1.5F;  
    23.     public float RunSpeed=3.0F;  
    24.     //角色状态枚举  
    25.     public enum PersonState  
    26.     {  
    27.         idle,  
    28.         run,  
    29.         walk,  
    30.         jump,  
    31.         attack  
    32.     }  
    33.     //角色行动方向枚举  
    34.     public enum PersonDirection  
    35.     {  
    36.         //正常向前  
    37.         Forward=90,  
    38.         //正常向后  
    39.         Backward=270,  
    40.         //正常向左  
    41.         Left=180,  
    42.         //正常向右  
    43.         Right=0,  
    44.     }  
    45.       
    46.     void Start ()   
    47.     {  
    48.        //获取动画  
    49.        mAnimation=gameObject.GetComponent();  
    50.     }  
    51.   
    52.     void Update ()   
    53.     {  
    54.        //前进  
    55.        if(Input.GetKey(KeyCode.W))  
    56.        {  
    57.          SetPersonDirection(PersonDirection.Forward);  
    58.          SetPersonAnimation();  
    59.        }  
    60.        //后退  
    61.        if(Input.GetKey(KeyCode.S))  
    62.        {  
    63.          SetPersonDirection(PersonDirection.Backward);  
    64.          SetPersonAnimation();  
    65.        }  
    66.        //向左  
    67.        if(Input.GetKey(KeyCode.A))  
    68.        {  
    69.          SetPersonDirection(PersonDirection.Left);  
    70.          SetPersonAnimation();  
    71.        }  
    72.        //向右  
    73.        if(Input.GetKey(KeyCode.D))  
    74.        {  
    75.          SetPersonDirection(PersonDirection.Right);  
    76.          SetPersonAnimation();  
    77.        }  
    78.        //巡逻或等待  
    79.        if(Input.GetKeyUp(KeyCode.A)||Input.GetKeyUp(KeyCode.D)||Input.GetKeyUp(KeyCode.S)||Input.GetKeyUp(KeyCode.W)||Input.GetKeyUp(KeyCode.Space))      
    80.        {  
    81.          mAnimation.Play("idle");  
    82.          mState=PersonState.idle;  
    83.        }  
    84.        //跳跃  
    85.        if(Input.GetKey(KeyCode.Space))  
    86.        {  
    87.          transform.GetComponent().AddForce(Vector3.up * mJumpValue,ForceMode.Force);  
    88.          mAnimation.Play("Jump");  
    89.          mState=PersonState.jump;  
    90.        }  
    91.        //攻击  
    92.        if(Input.GetMouseButton(0))  
    93.        {  
    94.          mAnimation.Play("Attack");  
    95.          mState=PersonState.attack;  
    96.          StartCoroutine("ReSetState");  
    97.        }  
    98.        //计算旋转角  
    99.        if(Input.GetMouseButton(1))  
    100.        {  
    101.           //计算水平旋转角  
    102.           mAngles+=Input.GetAxis("Mouse X") * 15;  
    103.           //旋转角色  
    104.           transform.rotation=Quaternion.Euler(new Vector3(0,mAngles,0));  
    105.        }  
    106.     }  
    107.       
    108.     private void SetPersonDirection(PersonDirection mDir)  
    109.     {  
    110.         //根据目标方向与当前方向让角色旋转  
    111.         if(mDirection!=mDir)  
    112.         {  
    113.             transform.Rotate(Vector3.up*(mDirection-mDir));  
    114.             mDirection=mDir;  
    115.         }  
    116.     }  
    117.       
    118.     private void SetPersonAnimation()  
    119.     {  
    120.         if(RunOrWalk==PersonState.walk)  
    121.         {  
    122.            mAnimation.Play("Walk");  
    123.            mState=PersonState.walk;  
    124.            transform.Translate(Vector3.forward * WalkSpeed * Time.deltaTime);  
    125.         }  
    126.         else if(RunOrWalk==PersonState.run)  
    127.         {  
    128.            mAnimation.Play("Run");  
    129.            mState=PersonState.run;  
    130.            transform.Translate(Vector3.forward * RunSpeed * Time.deltaTime);  
    131.         }  
    132.     }  
    133.       
    134.     IEnumerator ReSetState()  
    135.     {  
    136.         //当攻击动画播放完毕时,自动切换到巡逻状态  
    137.         yield return new WaitForSeconds(mAnimation.clip.length);  
    138.         mAnimation.Play("idle");  
    139.         mState=PersonState.idle;  
    140.     }  
    141.       
    142.          
    143. }  </font>
    复制代码

            其中,SetPersonAnimation()方法将根据RunOrWalk值来决定角色是采用行走还是奔跑的方式移动。最后,我们加上一个摄像机跟随的脚本SmoothFollow,这个脚本在官方提供的Script资源包里,我们把该脚本绑定到主摄像机上,并设定我们的角色为其跟随目标。
    展开全文
  • 今天要和大家分享的是基于Unity3D开发2D游戏,博主一直钟爱于国产武侠RPG,这个我在开始写Unity3D游戏开发系列文章的时候就已经说过了,所以我们今天要做的就是利用Unity3D来实现在2D游戏中人物的走动控制。...
  • 仙剑奇侠传是中国最为经典的武侠PRG游戏了,跌宕起伏的剧情,深入人心的人设,精心制作的画面。可以说是传承了一代人的青春记忆。 下面是小编整理好的一套C/C++资料,加小编C/C++学习群825414254,获取系统性学习C/...
  • 在RPG游戏中,当角色HP降低到0时,角色即进入了死亡状态,此时角色将倒在地上。在过去的游戏设计中,角色倒...在Unity3D中,系统为我们提供了布娃娃组件,下面请大家和我一起来学习怎样使用布娃娃来模拟角色死亡的状态
  • 目前游戏主线剧情进行到50%左右,在游戏尚未通关前,我对于这一部游戏的感觉始终是一种说不清道不明的情感,作为仙剑系列中唯一一部,从项目立项到宣传曝光再到游戏上市整个过程中持续关注的游戏,它可以说是承载了...
  •  众所周知,在国产RPG游戏里面,《仙剑奇侠传》是永恒的经典。发布近二十年以来,依旧话题不断。但是鲜有人对其进行逆向分析,只是听说多年之前曾有人为了探究其是否有隐藏剧情,从而采取了某种逆向分析的手段。在...
  • Unity3D游戏开发之仿仙剑奇侠传仙灵岛机关的实现
  •  在上一篇文章中,我们从Unity3D为我们提供的相机原型实现了非编码式的小地图,如果结合GUI在这个小地图下面绘制一些背景贴图,相信整体的效果会更好一些。博主希望这个问题大家能够自己去做更深入的研究,因为贴图...
  • 今天要和大家分享的是基于Unity3D开发2D游戏,博主一直钟爱于国产武侠RPG,这个我在开始写Unity3D游戏开发系列文章的时候就已经说过了,所以我们今天要做的就是利用Unity3D来实现在2D游戏中人物的走动控制。...
  • 在《仙剑奇侠传》、《古剑奇谭》等游戏中,经常需要玩家在一个3D场景中选取场景中的物体。比如为我方角色添加状态、为我们角色增加血量、选择要攻击的敌人等,通常我们使用鼠标来选择一个目标物体,当鼠标移动到...
  • 各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/...比如博主在写《[Unity3D]Unity3D游戏开发之自由视角下的角色控制》和《[Unity3D]Unity3D游戏开发之角色控制漫谈》这两篇
  • 在RPG游戏中,某些游戏场景常常需要玩家沿着墙壁或者梯子攀爬到高处,例如《仙剑奇侠传三》的九龙坡场景中,玩家需要沿着梯子爬到高处才能收集场景中的物品,而在《古剑奇谭一》中的翻云寨,玩家则需要爬到顶层才能...
  • 各位朋友,大家好,欢迎大家关注我的博客,我是秦元培,我的博客地址是blog....血条是生命值的一种体现,就像《仙剑奇侠传三》电视剧中,当景天说他想让那些被邪剑仙害死的人活过来的时候,天帝说需要等量的生命值来换
1 2 3 4 5 ... 13
收藏数 242
精华内容 96