当前位置:   article > 正文

Unity 3D脚本编程与游戏开发(2.3)

Unity 3D脚本编程与游戏开发(2.3)
3.3.2 功能实现

        从Asset Store中获取的prefab⾃带两个控制其移动的脚本以及与移动⽅式相匹配的动画。⽬前来说,⾓⾊移动已经不⽤再编写代码,可以直接使⽤,⽽在之后添加额外功能时再对这部分代码进⾏修改即可。这个实例的主要⽬的是熟悉物理系统,因此摄像机跟随⾓⾊移动也使⽤现成的⼯具来实现。选择主菜单中的Window→Package Manager,待加载完成后选择Cinemachine插件并安装,如图3-16所⽰。

        安装完成后会在菜单栏内多出⼀个Cinemachine选项卡,打开它后单击Create 2DCamera以创建⼀个2D虚拟摄像机,如图3-17所⽰。

        这个虚拟摄像机组件上有很多可以调整的参数,但这些不必去管,只需要让摄像机跟随⾓⾊移动就可以。把场景中的⾓⾊prefab拖⼊Cinemachine Virtual Camera组件中的Follow栏,如图3-18所⽰。完成后再运⾏游戏,摄像机就可以跟随⾓⾊移动了。

⼩知识
        ⾼级摄像机功能⼤家应该注意到了,虚拟摄像机组件在跟随⾓⾊移动时并⾮⽣硬地同步移动,⽽是有⼀个平滑的过渡,这只是其最基本的功能。
        在第1章中是直接让摄像机作为⾓⾊⼦物体移动,第2章则是编写了⼀个简易的跟随物体移动的脚本。如果是⼀个复杂的3D游戏,可能还会涉及摄像机的视⾓切换、视⾓变化和障碍遮挡等功能,这时再采⽤之前简单的摄像机处理⽅法就很难实现了。
        ⽽Cinemachine是⼀个能满⾜⼤多数游戏需求的摄像机组件,但是限于书本篇幅,⽆法详细介绍。功能⻬全的摄像机实现起来难度较⾼,不适合在⼊门阶段死磕,可作为后续学习的⼀个重点,如作为3D数学的实践练习。

3.3.3 游戏机制

        要想使⽤物理系统实现关卡内的游戏机制,可以从以下3个机制进⾏。
        1. 跷跷板
        新建⼀个Sprite,然后在SpriteRenderer组件上放⼊合适的精灵图素材,如资源包中的PlatformWhiteSprite,最后将其缩放到合适⼤⼩。由于要与⾓⾊进⾏物理上的交互,因此再添加Box Collider 2D和Rigidbody 2D组件,并且勾选Rigidbody 2D组件“Constraints(物理约束)”中的X、Y选项,如图3-19所⽰。

        此时控制⾓⾊跳上去,会发现跷跷板已经完成了。这个平台就像中⼼被钉了⼀个钉⼦⼀样,可以左右倾斜,那么就会有⼀个问题,旋转的⼒矩该怎么调整?结合⽜顿第⼆定律和动量定律可知,⼀个物体的质量越⼤,改变其运动状态越难。因此这⾥只需要适当增加平台的质量和⾓阻尼(AngularDrag)即可,质量和⾓阻尼越⼤,跷跷板就越不容易转动。
        将跷跷板调整到⼀个合适的⼿感后修改精灵图的颜⾊以⽰区分,然后再将其制作为预制体。
2. 蹦床
        复制⼀份场景中的跷跷板,由于蹦床是不会动的,因此它不再需要刚体了,删除Rigidbody 2D组件即可。然后要做的是修改蹦床的弹⼒,可以通过创建⼀个物理材质来实现。在Project窗⼝中创建⼀个Physics Material 2D(2D物理材质),如图3-20所⽰。

        可以看到其上只有两个参数,Friction(摩擦系数)和Bounciness(弹性系数)。修改弹性系数到⼀个合适的值,然后再将物理材质拖曳到蹦床的碰撞体组件的Material属性上(⻅图3-21),最后再换⼀个颜⾊并将其制作成prefab。

⼩提⽰
        弹性系数不宜过⼤弹性系数的值⼀般不宜超过0.9,超过1则表⽰反弹⼒会⽐作⽤⼒本⾝还⼤。不考虑阻⼒的情况下⾓⾊就永远停不下来了,这既不符合现实中的物理理念,⼜容易浪费计算资源,进⽽造成各种bug。
3. 秋千
        要实现秋千⼀样的效果,⾸先就得有绳索,这⾥使⽤Unity的Joint(关节)组件来实现。新建⼀个精灵,选择⾃带的konb图⽚作为精灵图素材,并添加Rigidbody 2D和HingeJoint 2D组件,如图3-22所⽰。然后将物体命名为Node并复制出若⼲个,个数取决于绳⼦⻓度。然后让节点保持⼀定且统⼀的间距,再向最上⾯的节点的Hinge Joint 2D组件的Connected Rigid Body栏拖⼊第2个节点物体的Rigidbody 2D组件。最后按照上述⽅法使第2个节点连接第3个,第3个连接第4个,以此类推,就好似⼀个锁链⼀样,如图3-23所⽰。

                                图3-22 添加Rigidbody 2D和Hinge Joint 2D

        新建⼀个⻓条状平台精灵作为最后⼀个节点连接的⽬标,要注意给平台添加刚体。由于绳⼦的⼀端应该是固定的,因此将最上⾯的节点的刚体设置为Static。完成后在场景中新建⼀个空物体,把除平台外的所有节点设为其⼦物体,并给空物体起名为String。调整空物体⾓度并镜像复制⼀份,然后将其整个保存为预制体,如图3-24所⽰。

        现在游戏中的3个基本机制就完成了,已经⾜以⽤它们制作⼀个关卡,下⼀⼩节再对游戏进⾏⼀些有趣的改动。

3.3.4 完成和完善游戏

        跳跃次数重置点
        多数平台跳跃类游戏跳跃的⽅式与⼿感都会是开发者关注的重点。⼿感调整这⼀部分的内容⽐较主观且较为烦琐,本书篇幅⼜有限,因此这⾥添加⼀个常⻅的扩展功能:⼆段跳或跳跃次数重置点。这两个功能从原理上来说是⼀回事,都是通过添加变量来控制跳跃的重置,这需要阅读⾓⾊控制部分的代码来确定修改的位置。
(1)修改跳跃条件。
        在CharacterRobotBoy上可以找到PlatformerCharacter2D和Platformer2DUserControl两个脚本,通过阅读代码可知整个控制移动部分的代码都在前者⾥,接着就是修改代码以实现需要的功能。额外添加⼀个布尔变量m_JumpReset作为跳跃重置的条件,在⾓⾊触地检测部分添加⼀个对跳跃重置物的检测,使⽤⼀个新的Tag作为其标签。然后在Move⽅法内修改允许跳跃的条件,并把起跳后的m_JumpReset赋值为false。
(2)重置点制作。
        新建⼀个Sprite,选择素材⾥的Button图⽚作为精灵图,然后添加⼀个CircleCollider2D组件。添加⼀个新Tag(JumpPoint),并将该物体修改为这个Tag,如图3-25所⽰。

        按此⽅法修改完之后测试效果,如果前⾯步骤都对,依然可能会发现在跳跃重置之后的⼆段跳时⼿感会有些奇怪,特别是⾓⾊下落后触碰到重置点时。阅读Move()⽅法内的跳跃相关部分,会发现跳跃的实现⽅式是对刚体添加⼒,⽽⾓⾊在下落时其向下的动量会与跳跃的⼒相抵消,从⽽造成奇怪的⼿感。解决的办法是在跳跃时把⾓⾊沿y轴⽅向的速度置零,修改后的完整代码如下。(代码⼤部分内容是主⾓⾃带的PlatformerCharacter2D脚本,读者可以整体阅读,重点是Move()函数的跳跃部分。

  1. using System;
  2. using UnityEngine;
  3. namespace UnityStandardAssets. _ 2D
  4. {
  5. public class PlatformerCharacter2D : MonoBehaviour
  6. {
  7. [SerializeField] private float m _ MaxSpeed = 10f;
  8. [SerializeField] private float m _ JumpForce = 400f;
  9. [Range(0, 1)] [SerializeField] private float m _ CrouchSpeed = .36f;
  10. [SerializeField] private bool m _ AirControl = false;
  11. [SerializeField] private LayerMask m _ WhatIsGround;
  12. private Transform m _ GroundCheck;
  13. const float k _ GroundedRadius = .2f;
  14. private bool m _ Grounded;
  15. private Transform m _ CeilingCheck;
  16. const float k _ CeilingRadius = .01f;
  17. private Animator m _ Anim;
  18. private Rigidbody2D m _ Rigidbody2D;
  19. private bool m _ FacingRight = true;
  20. private bool m _ JumpReset=false;
  21. private void Awake()
  22. {
  23. m _ GroundCheck = transform.Find("GroundCheck");
  24. m _ CeilingCheck = transform.Find("CeilingCheck");
  25. m _ Anim = GetComponent<Animator>();
  26. m _ Rigidbody2D = GetComponent<Rigidbody2D>();
  27. }
  28. private void FixedUpdate()
  29. {
  30. m _ Grounded = false;
  31. Collider2D[] colliders = Physics2D.OverlapCircleAll(m_GroundCheck.
  32. position, k _ GroundedRadius, m _ WhatIsGround);
  33. for (int i = 0; i < colliders.Length; i++)
  34. {
  35. if (colliders[i].gameObject != gameObject)
  36. {
  37. if(colliders[i].gameObject.tag=="JumpPoint")
  38. {
  39. m _ JumpReset = true;
  40. Destroy(colliders[i].gameObject);
  41. }
  42. else
  43. {
  44. m _ Grounded = true;
  45. }
  46. }
  47. }
  48. m _ Anim.SetBool("Ground", m _ Grounded);
  49. m _ Anim.SetFloat("vSpeed", m _ Rigidbody2D.velocity.y);
  50. }
  51. public void Move(float move, bool crouch, bool jump)
  52. {
  53. if (!crouch && m _ Anim.GetBool("Crouch")){
  54. if (Physics2D.OverlapCircle(m_CeilingCheck.position, k_
  55. CeilingRadius, m _ WhatIsGround))
  56. {
  57. crouch = true;
  58. }
  59. }
  60. m _ Anim.SetBool("Crouch", crouch);
  61. if (m _ Grounded || m _ AirControl)
  62. {
  63. move = (crouch ? move*m _ CrouchSpeed : move);
  64. m _ Anim.SetFloat("Speed", Mathf.Abs(move));
  65. m _ Rigidbody2D.velocity = new Vector2(move*m _ MaxSpeed, m
  66. _
  67. Rigidbody2D.velocity.y);
  68. if (move > 0 && !m _ FacingRight)
  69. {
  70. Flip();
  71. }
  72. else if (move < 0 && m _ FacingRight)
  73. {
  74. Flip();
  75. }
  76. }
  77. if ((m _ Grounded||m _ JumpReset) && jump)
  78. {
  79. m _ Grounded = false;
  80. m _ JumpReset = false;
  81. m _ Anim.SetBool("Ground", false);
  82. Vector2 resetVelocity = m _ Rigidbody2D.velocity;
  83. // 这种写法可以保留沿Y轴的正向速度,如果不需要也可以直接改为0
  84. resetVelocity.y = Mathf.Max(0, resetVelocity.y);
  85. m _ Rigidbody2D.velocity = resetVelocity;
  86. m _ Rigidbody2D.AddForce(new Vector2(0f, m _ JumpForce));
  87. }
  88. }
  89. private void Flip()
  90. {
  91. m _ FacingRight = !m _ FacingRight;
  92. Vector3 theScale = transform.localScale;
  93. theScale.x *= -1;
  94. transform.localScale = theScale;
  95. }
  96. }
  97. }

第4章 游戏开发数学基础

        伽利略说:“数学是上帝⽤来书写宇宙的⽂字。”这句话⽤在游戏开发领域再合适不过了——游戏引擎这个虚拟的⼩宇宙,就是以纯粹的数学为基础搭建⽽成的。本章就重点来讲解⼀下游戏开发⽅⾯的数学基础,内容有点枯燥,但是⼜必不可少。

4.1 数学对游戏的重要性

        以3D游戏为例,原始的3D模型是⼀系列顶点和顶点之间的连接关系,模型制作者将坐标等数据以纯数字形式保存到⽂件⾥,游戏引擎的任务则是将数字转化成模型展现在⽤户⾯前。游戏引擎从模型的原始数据开始,逐步加⼊场景、摄像机、光照等因素,按⼀套标准的流程进⾏计算,最终将模型展现出来,如图4-1所⽰。

        在渲染的基本流程中,有很多“坐标系转换”的⼯作。“坐标系转换”有着深刻的数学原理,但从实⽤层⾯来看并不复杂,仅仅是⼀系列向量和矩阵的乘法运算⽽已,如图4-2所⽰。

        在3D游戏的“开荒”时代,以《毁灭战⼠》《雷神之锤》为代表的3D游戏将当时最前沿的图形学技术进⾏改造和优化,达成了在⼀秒内渲染数⼗次的⽬标,奠定了现代3D游戏的基础。到了今天,游戏引擎得到了充分的发展,已经不需要再从计算顶点、渲染像素开始从头制作游戏了。
        虽然如此,当需要制作⼀款游戏时,依然有很多⾄关重要的问题需要考虑:玩家的输⼊如何转化为⾓⾊的⾏动,⾓⾊的⾏动和动画如何拟合,摄像机如何配合⾓⾊的移动等。这些问题关系到了游戏的⼿感、表现⼒和游戏性。⽽要完美地实现所需要的效果,也需要有完备的数学算法的⽀持。
        因此,数学不仅构建了游戏引擎这个⼩宇宙,⽽且要进⼀步填充这个⼩宇宙、创造丰富多彩的世界,依然离不开数学⼯具的⽀持。⽽值得庆幸的是,现在只需要掌握最基本的3D数学知识,如坐标、⽅向、位移和旋转等概念,就⾜以实现⼤部分游戏玩法了。本章会引领读者逐步掌握必要的基础知识,为实践扫平障碍。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/105988?site
推荐阅读
相关标签
  

闽ICP备14008679号