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

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

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

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

  1. //向左
  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. }

那么,我们姑且认为这样写没什么问题,那么现在我们导入官方提供的Script脚本资源包,找到MouseLook脚本,在Update()方法中添加对右键是否按下的判断,这样我们就可以实现按下鼠标右键时视角的旋转。修改后的脚本如下:

  1. using UnityEngine;
  2. using System.Collections;
  3. /// MouseLook rotates the transform based on the mouse delta.
  4. /// Minimum and Maximum values can be used to constrain the possible rotation
  5. /// To make an FPS style character:
  6. /// - Create a capsule.
  7. /// - Add the MouseLook script to the capsule.
  8. ///   -> Set the mouse look to use LookX. (You want to only turn character but not tilt it)
  9. /// - Add FPSInputController script to the capsule
  10. ///   -> A CharacterMotor and a CharacterController component will be automatically added.
  11. /// - Create a camera. Make the camera a child of the capsule. Reset it's transform.
  12. /// - Add a MouseLook script to the camera.
  13. ///   -> Set the mouse look to use LookY. (You want the camera to tilt up and down like a head. The character already turns.)
  14. [AddComponentMenu("Camera-Control/Mouse Look")]
  15. public class MouseLook : MonoBehaviour {
  16. public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 }
  17. public RotationAxes axes = RotationAxes.MouseXAndY;
  18. public float sensitivityX = 15F;
  19. public float sensitivityY = 15F;
  20. public float minimumX = -360F;
  21. public float maximumX = 360F;
  22. public float minimumY = -60F;
  23. public float maximumY = 60F;
  24. float rotationY = 0F;
  25. void Update ()
  26. {
  27. if(Input.GetMouseButton(1))
  28. {
  29. if (axes == RotationAxes.MouseXAndY)
  30. {
  31. float rotationX = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * sensitivityX;
  32. rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
  33. rotationY = Mathf.Clamp (rotationY, minimumY, maximumY);
  34. transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0);
  35. }
  36. else if (axes == RotationAxes.MouseX)
  37. {
  38. transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivityX, 0);
  39. }
  40. else
  41. {
  42. rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
  43. rotationY = Mathf.Clamp (rotationY, minimumY, maximumY);
  44. transform.localEulerAngles = new Vector3(-rotationY, transform.localEulerAngles.y, 0);
  45. }
  46. }
  47. }
  48. void Start ()
  49. {
  50. // Make the rigid body not change rotation
  51. if (rigidbody)
  52. rigidbody.freezeRotation = true;
  53. }
  54. }

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

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

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

  1. //角色行动方向枚举
  2. public enum PersonDirection
  3. {
  4. //正常向前
  5. Forward=90,
  6. //正常向后
  7. Backward=270,
  8. //正常向左
  9. Left=180,
  10. //正常向右
  11. Right=0,
  12. }

我们这里定义了四个方向上的角度,当我们角色旋转到Forward方向时,我们根据用户按下的键,来判断角色要向那个方向旋转:

  1. private void SetPersonDirection(PersonDirection mDir)
  2. {
  3. //根据目标方向与当前方向让角色旋转
  4. if(mDirection!=mDir)
  5. {
  6. transform.Rotate(Vector3.up*(mDirection-mDir));
  7. mDirection=mDir;
  8. }
  9. }

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

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

  1. transform.Translate(Vector3.forward * WalkSpeed * Time.deltaTime);

接下来我们为角色定义状态枚举值:

  1. //角色状态枚举
  2. public enum PersonState
  3. {
  4. idle,
  5. run,
  6. walk,
  7. jump,
  8. attack
  9. }

我们在上面的代码上面做修改,最终形成的代码为:

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

其中,SetPersonAnimation()方法将根据RunOrWalk值来决定角色是采用行走还是奔跑的方式移动。最后,我们加上一个摄像机跟随的脚本SmoothFollow,这个脚本在官方提供的Script资源包里,我们把该脚本绑定到主摄像机上,并设定我们的角色为其跟随目标。

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

在《Unity3D游戏开发之当仙剑奇侠传角色控制器效果》上文中,我们实现了一个简单的角色控制器。博主之前曾说过,这个控制器是存在问题的,具体存在什么问题呢?请大家和我一起来看今天的文章。今天博主想和大家说说碰撞器与刚体,为什么要讨论这个呢?我们先来看这样一个需求:我们希望我们的角色在游戏场景中运动时,四面的墙壁对角色一个“撞墙”的感觉,同时不能影响角色的位置,当碰到场景中的某些障碍物(如荆棘等)时,角色能够掉血。基于这样的需求,我们想给角色、墙壁和障碍物加上碰撞器,通过编码的方式来获得碰撞信息以确定碰撞对角色的影响。我们打开之前创建好的项目,如图所示:

我们知道,在Unity3D中存在这几种碰撞器:盒子碰撞器、球体碰撞器、胶囊体碰撞器、网格碰撞器、地形碰撞器、滚轮碰撞器。除了滚轮碰撞器专门为汽车设计以外,其余的碰撞器在平时的游戏设计中我们都能碰到。墙体可以看做是盒子,所以可以使用盒子碰撞器。这里我们用五个大箱子作为我们的障碍物,同样地我们使用盒子碰撞器。那么对于我们的角色呢?我们知道我们的角色模型并不是均匀规则的几何体,如果使用上述的碰撞器,会出现碰撞不精确的情况。怎么办呢?这里我们选择网格碰撞器。我们直接给它创建一个网格碰撞器:

运行程序,可是我们发现我们的角色并没有和场景中的墙体发生碰撞,会从墙体和障碍物中穿过去。这是怎么回事呢?我们发现Mesh Collider有一个Mesh属性,所以这里应该为这个Mesh属性添加一个网格。可是项目中并没有这样一个网格体啊,博主查阅了相关资料发现,Unity3D是可以为模型生成网格体,那么怎么做呢?我们先把这个手动添加的网格碰撞体移除,然后在项目中找到我们模型文件,在右边的属性面板选中Generate Collider,然后点击Apply

现在我们回到游戏场景窗口中,我们会发现是这样的结果:

这模型和网格完全不能匹配啊?这个问题,博主到目前为止没有找到一个合理的解释。如果我们此刻运行游戏,角色倒是可以响应碰撞了,我们这里使用的是Collision检验,如果还有朋友不知道怎么检测碰撞,请看《[Unity3D]Unity3D 游戏开发之碰撞检测》这篇文章。但是新的问题随之而来了,我们的角色由于受到碰撞的影响受到了力的作用,碰撞后不再受玩家控制,直接从画面上消失了。最终博主的解决办法是:给墙体加上盒子碰撞器、给角色加上一个胶囊体、刚体(取消重力)。最终程序运行结果如下:

点击这里查看动画

大家可以注意到再我松开鼠标的那一瞬间角色自己发生了旋转,而且这个作用一直在持续下去,不过这算是比较圆满的结果了。那么问题呢?问题就是我对碰撞器和刚体的概念越来越模糊。我们可以通过创建Cube、创建Sphere、创建Capsule分别创建对应的碰撞体并且可以通过代码来检测,因为这些组件自带了刚体和碰撞器,但是在我们上面的例子中,没有刚体结构同样可以发生碰撞,那么我们不禁要问一句:刚体和碰撞器究竟是什么关系?为什么没有刚体,碰撞器还能工作?如果没有碰撞器,只有刚体行不行?带着这样的疑问?博主找到了两个表格,希望对大家有所启发吧!

具体内容参见:http://game.ceeger.com/Components/class-BoxCollider.html

我自己都觉得有点晕了,唉,今天就先这样吧,什么时候想清楚了再回来总结吧!

本文出处:http://blog.csdn.net/qinyuanpei/article/details/23709427

Unity3D游戏开发之仿仙剑奇侠传角色控制效果相关推荐

  1. [Unity3D]Unity3D游戏开发之仿仙剑奇侠传角色控制效果

    大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei. 在上一篇文章中,我们从Unity3D为我们提供的相机原型实现了非编码式的小地图,如果结合GU ...

  2. [Unity3D]Unity3D游戏开发之仿仙剑奇侠传仙灵岛机关的实现

    大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei. 在前面的文章中,我们分别实现了一个自定义的角色控制器<[Unity3D]Unity3D ...

  3. [Unity3D]Unity3D游戏开发之仿仙剑奇侠传角色死亡效果实现

    大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei. 感谢对我的支持,在上一篇文章< [Unity3D]Unity3D游戏开发之仿仙剑奇侠传 ...

  4. Unity3D游戏开发之仿仙剑奇侠传仙灵岛机关的实现

    我们继续来做点和仙剑相关的东西,首先我们来看一副图: 这幅图呢,是仙剑奇侠传的第一个迷宫场景--仙灵岛的莲花池.男主角李逍遥为替婶婶求药独闯仙灵岛,在经历了前面的小草妖战斗后,李逍遥就来到了 莲花池, ...

  5. Unity3D游戏开发之仿仙剑奇侠传一2D游戏 (一)

    今天要和大家分享的是基于Unity3D开发2D游戏,博主一直钟爱于国产武侠RPG,这个我在开始写Unity3D游戏开发系列文章的时候就已经说过了,所以我们今天要做的就是利用Unity3D来实现在2D游 ...

  6. [Unity3D]Unity3D游戏开发之仿仙剑奇侠传四角色控制效果

    大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei. 在上一篇文章中,我们从Unity3D为我们提供的相机原型实现了非编码式的小地图,如果结合GU ...

  7. Unity3D游戏开发之仿仙剑奇侠传角色死亡效果实现

    今天和大家分享的是在Unity3D中一个比较重要的组件--布娃娃(Ragdoll).我们首先来看张图片吧! 相信熟悉仙剑的朋友一定知道这样一句话:胜败乃兵家常事,大侠请重新来过.从仙剑一到仙剑五前传, ...

  8. Unity3D游戏开发之仿仙剑奇侠传角色死亡怎么来实现

    今天我想和大家分享的是一个比较重要的组件--布娃娃(Ragdoll).我们首先来看张图片吧! 相信熟悉仙剑的朋友一定知道这样一句话:胜败乃兵家常事,大侠请重新来过.从仙剑一到仙剑五前传,仙剑带给我们的 ...

  9. [Unity3D]Unity3D游戏开发之当仙剑奇侠传遇上Mecanim动画系统

    大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是 blog.csdn.net/qinyuanpei.博主总算赶在这个月底写出了这篇文章.这个月因为期末考试一直没时间研究太多关于技术方面的东西, ...

最新文章

  1. HDU-1459.非常可乐(BFS )
  2. 找对象的过程中,我竟然理解了什么是机器学习!
  3. 一文讲懂图像处理中的低通、高通、带阻和带通滤波器
  4. python利用requests进行爬虫_Python利用Requests库写爬虫(一)
  5. 你的登录接口真的安全吗?快看看你有没有中招!
  6. selenium + python自动化测试unittest框架学习(五)webdriver的二次封装
  7. 像这样的作业调度器,你会怎么设计?
  8. 异常处理——Another app is currently holding the yum lock
  9. GMapbook中文版上线
  10. Blazor 机制初探以及什么是前后端分离,还不赶紧上车?
  11. python全套视频十五期(116G)
  12. Flex及AIR开发资源集合
  13. iOS苹果手机上最好的3个azw3阅读器
  14. scanf函数读取缓冲区数据的问题
  15. ROC曲线下面积为什么越大越好 (TPR FPR)
  16. Emacs入门:重要的基本概念、基本读写操作以及一些常用的快捷键
  17. 二十一世纪大学英语读写教程(第四册)学习笔记(原文)——2 - The Gratitude We Need(我们所需要的感激)
  18. 相亲app开发,解决内存循环引用的问题
  19. DOTA全英雄装备介绍+物品简称[图文]
  20. 鱼眼校正c语言算法,一种鱼眼图像逆向经纬映射的快速校正算法

热门文章

  1. phpstorm奇淫技巧
  2. 基于DE2的VHDL六层电梯控制程序设计
  3. 【181008】VC++猫捉老鼠游戏源代码
  4. 【调剂】山东建筑大学2020年硕士研究生调剂公告
  5. devtool使用介绍(3)
  6. HDFS 文件下载 通过浏览器
  7. gdb 调试多线程 神贴
  8. 金蝶EAS/BOS开发小知识一
  9. Bundle与Intent机制,Intent显式/隐式意图,传递参数及参数序列化,各种跳转(如打开浏览器),Intent的Size
  10. 微型计算机的功能主要由什么决定,微机的功能主要取决于微处理器CPU。