从零开始做一款Unity3D游戏<三>——编写游戏机制(一)

简介: 从零开始做一款Unity3D游戏<三>——编写游戏机制

前面一章,我们专注于通过代码来移动玩家和相机,同时了解了与 Unity 的物理系统相关的一些知识。然而,仅仅控制角色并不足以制作出具有竞争力的游戏:事实上,这只是各种不同游戏中都会存在的主题之一。

游戏的独特性来自游戏的核心机制以及这些机制赋予玩家的力量感与代入感。虚拟环境若不具有任何乐趣和可玩性,游戏便不值得重复玩耍,更不用说带来趣味了。当尝试实现游戏机制时,我们还会进一步学习 C#的编程知识以及一些中级特性


本章将完成 Hero Bor 游戏原型的制作,其中包含如下主题:

  • 通过施加力来添加跳跃。
  • 理解层遮罩。
  • 初始化对象和预制体
  • 理解游戏管理器。
  • 理解get和 set 属性。
  • 计算分数。
  • 编写UI。

添加跳跃


使用 Rigidbody 组件控制玩家移动带来的好处是,添加依赖于施加力的游戏机将变得很容易,例如跳跃。为了使玩家能够跳跃,本节将使用称为枚举的数据类型并且编写第一个工具函数。

提示:

工具函数是用来执行一些杂事的类方法,能使游戏代码不那么混乱。例如,检查玩家是否接触地面,从而进行跳跃(或提示)。

了解枚举


根据定义,枚举是属于同一变量的具名常量的集合。当需要使用一系列不同的值而这些值又属于相同的父类型时,枚举十分有用。

与进行描述相比,直接进行展示能让枚举理解起来更为容易。枚举的语法如下

enum PlayerAction { Attack, Defend,Flee ;

下面分步解释枚举是如何起作用的。

  • 关键字enum声明了后面变量的类型
  • 枚举包含的值位于花括号中,使用逗号分隔(最后一个值除外)
  • 枚举必须以分号结尾,就像之前使用的所有其他类型一样。

例如,使用如下语法就可以声明一个枚举变量:

PlayerAction currentAction = PlayerAction.Defend;
  • 解释如下:
  • 类型是PlayerAction。
  • 枚举变量包含名称并等价于 PlayerAction 的某个值。
  • 每个枚举常量都可以通过点符号来访问。

底层类型

枚举关联着底层类型,这意味着花括号内的每个常量值都有关联值。默认的底层类型是 int,初始值为0,就像数组一样,各个枚举常量按顺序获得下一个更大的值

注意:

并非所有类型都相同。枚举可以使用的底层类型已被限制为 byte、sbyte.short、ushort、int、uint、long 和 ulong.这些类型被称为整型,用来指定变量可以存储的数值的大小。这些内容超出了本书的讨论范围,大部分情况下使用 mt 类型即可。

例如,假设 PlayerAction 枚举的值现在如下所示:

enum PlayerAction f Attack = 0,Defend = 1,Flee = 2 i

这里并无规则限制底层类型的值必须起始于 0;实际上,只需要指定第一个值,C#就会自动递增其余的值:

enumPlayerAction { Attack = 5,Defend,Flee} ;

在以上示例中,Defend自动等于6,Flee自动等于7。但是,如果需要使PlayerAction枚举包含不连续的值,那么需要显式地添加它们:

enum PlayerAction { Attack = 10,Defend = 5,Flee = 0};

你甚至可以改变 PlayerAction 的底层类型至任何支持的类型,只需要在枚举名的后面添加一个冒号即可:

enum PlayerAction : byte {Attack, Defend,Flee };

为了获取枚举的底层类型,需要执行显式的类型转换,我们已经介绍过这些内容因此下面的语法不足为奇

enum PlayerAction {Attack = 10,Defend = 5,Flee = 0};
PlayerAction currentAction = PlayerAction.Attack;
int actionCost = (int)currentAction;

枚举是编程领域中功能极为强大的工具,请一定熟练掌握。

实践:按空格键使玩家跳跃

你现在已经对枚举有了基本了解,下面使用枚举 KeyCode 来获取键盘输入。按如下代码修改 PlayerBehavior脚本,保存并单击 Play 按钮:

public class PlayerBehavior : MonoBehaviour
{
  public float moveSpeed = 10f;
  public float rotateSpeed = 75f;
  public float jumpVelocity = 5f;
  private float vInput;
  private float hInput;
  private Rigidbody rb;
  void Start()
  {
   _rb = GetComponent<Rigidbody>();
  }
  void Update()
 {
   vInput - Input.GetAxis("Vertical") * moveSpeed;
   hInput = Input.GetAxis("Horizontal") * rotateSpeed;
 }
  if(Input.GetKeyDown (KeyCode .Space))
  {
    _rb.AddForce(Vector3.up * jumpVelocity!ForceMode.Impulse);
   }
  //this.transform.Translate(Vector3.forward * vInputTime.deltaTime);
//this.transform.Rotate(Vector3.up * hInputTime.deltaTime);
}
  void FixedUpdate()
 {
   //No changes needed ...
 }
}

下面对上述代码进行解释。

创建一个变量来保存施加的跳跃力的大小,可以在Inspector 面板中进行调整

指定的键位被按下后,Input.GetKeyDown 方法将返回一个布尔值

  • GetKeyDown方法接收一个键位参数,可以是字符串或 KeyCode,其中KeyCode 是枚举类型。可使用 KeyCode.Space 方法对指定的键位进行检测。
  • 使用if语句检查 GetKeyDow 方法的返回值。如果返回tue,则执行i语句的语句体。

由于已经保存了 Rigidbody 组件,因此可以将 Vector3 和 ForceMode 参数传RigidbodyAddForce 方法以使玩家跳跃。

  • 向量(或施加的力)应该沿着up 方向并乘以jumpVelocity。
  • ForceMode 参数也是枚举类型,它决定了力是如何施加的。Impulse 表示给对象传递考虑了物体质量的即时力,这对跳跃机制来说很完美。

刚刚发生了什么

如果运行游戏,现在就可以向四周移动并且按下空格键来使玩家跳跃。但是,现在的机制会让玩家无限次地进行跳跃,这不是我们想要的结果。8.1.2 节将使用层遮罩来限制跳跃次数为单次。

使用层遮罩


层遮罩可以理解为用来归类游戏对象的不可见分组,Unity 的物理系统将使用这些分组来决定从寻路到碰撞体相交的一切表现。关于层遮罩的更多使用方式超出了本书的讨论范围,我们将创建并使用一个层级来执行简单的检查一一检查玩家是否触地。

实践:设置对象层级

在检查玩家是否触地前,首先把关卡中的所有对象添加到自定义的层遮罩中。这样就可以利用玩家对象上已有的Capsule Collider 来执行碰撞计算。

()选中Hicrarchy面板中的任意对象并选择 LayerAdd Layer,

image.png

(2)向可用的第一个位置添加一个新的层级,命名为Ground,

image.png

(3)在 Hierarchy 面板中选中父对象 Enviroment,选择 Layer|Ground,当弹出提示框询问是否应用至所有子对象时,单击 Yes 按钮。

image.png

刚刚发生了什么

默认情况下,Unity 引擎使用了层级 07,在剩下的 24 个位置可以自定义层级。这里定义了一个新的名为Ground 的层级并将 Enviroment 对象的所有子对象添加到了这个层级中。之后就可以检查处于 Ground 层级的所有对象是否与某个指定的物体相交了。

实践:限制重复跳跃

由于不想使 Update 方法变得混乱不堪,因此我们将层遮罩的相关计算写到一个工具函数中,并根据结果返回 true 或false。

(1)添加如下代码至PlayerBehavior 脚本并运行游戏:

public class PlayerBehavior : MonoBehaviour
{
 public float moveSpeed = 10f;
public float rotateSpeed = 75f;
public float jumpVelocity = 5f;
public float distanceToGround = 0.1f;
public LayerMask groundlayer;
private float vInput;
private float hInput;
private Rigidbody  _rb;
private CapsuleCollider _col;
void Start()
{
 _rb = GetComponent<Rigidbody>();
 _col = GetComponent<CapsuleCollider>();
}
void Update()
{
  _vInput = Input.GetAxis("Vertical")*moveSpeed;
  _hInput = Input.GetAxis("Horizontal") * rotateSpeed;
  if(IsGrounded() & Input .GetKeyDown (KeyCode.Space))
{
  _rb.AddForce(Vector3.up * jumpVelocity,ForceMode.Impulse);
}
}
void FixedUpdate()
{
   //... No changes needed ..
}
private bool IsGrounded()
{
 Vector3 capsuleBottom = new
}
Vector3( _col.bounds.center.x,_col.bounds .min.y,_col.bounds.center.z);
Bool grounded =Physics.CheckCapsule(_col.bounds.center,capsuleBottom,distanceToGround,groundlayer,QueryTriggerInteraction.Ignore);
return grounded;
}
}

2)在Inspector 面板中设置 Ground Layer 为 Ground.


下面对步骤(2)中的代码进行解释。


创建一个 float 变量来保存任意处于 Ground 层级的对象与 Player 对象的CapsuleCollider 组件之间的距离。


创建一个LayerMask 变量来进行碰撞检测,可以在Inspector 面板中进行设置


创建一个私有变量来保存玩家的 CapsuleCollider 组件


使用GetComponent0方法查找并返回 Player 对象上挂载的 CapsuleCollider组件


修改if语句,在执行跳跃之前检查IsGrounded 方法是否返回 te 以及空格键是否被按下。


声明将会返回一个布尔值的IsGrounded方法。


创建一个 Vector3 局部变量来保存 Player 对象的 CapsuleCollider 组件的底部置,我们将使用该位置判定与 Ground 层级中的对象发生的碰撞。

  • 所有 Collider 组件都包含 bounds 属性,可以通过 min、max 和 center 子属性来
    访问最小点、最大点和中心位置。
  • 碰撞体的底部是指三维空间中的点坐标(center.x,min.y,center.z)。
  • 创建一个布尔局部变量来保存从Physics 类调用的 CheckCapsule 方法的结果该方法接收如下5 个参数:
  • 胶囊的起始位置,可设置为碰撞体的中心位置,因为我们只关心胶囊的底部是否接触地面。
    胶囊的结束位置,可传入已经计算好的 capsuleBottom。
  • 胶囊的半径,可传入 distanceToGround。
  • 想要用来检查碰撞的层遮罩,可传入 Inspector 面板中已经设置好的groundLayer。
  • 触发器的查询行为决定了 CheckCapsule 方法是否忽略设置为触发器的碰体。因为不需要检查触发器,所以使用枚举QueryTriggerInteraction.Ignore

计算结束,返回 grounded 中存储的结果。

刚刚发生了什么

添加至 PlayerBehavior 脚本的方法有些涩难懂,但分解后,我们发现要做的事情只是使用一个来自 Physics类的方法。用简单的语言解释就是,我们向 CheckCapsule方法提供了起点和终点、碰撞半径以及层遮罩。如果终点位置与 Ground 层级中的某个物体之间的距离小于碰撞半径,CheckCapsule 方法就返回 ue,这意味着玩家触地了。若玩家正处于跳跃过程中,CheckCapsule 方法就返回 false。因为每一帧都将在Update方法中使用if语句检查IsGround,因此只有当玩家触地时,才允许进行跳跃

发射投射物


射击机制在游戏中十分常见,第一人称射击游戏中必然包含射击机制的某些变种Hero Bom游戏也不例外。本节将讨论如何在游戏运行时从预制体实例化游戏对象以及利用Unity的物理系统将这些对象向前射出。

实例化对象


在游戏中实例化游戏对象的概念与实例化类相同一一都需要某个初始值,这样C#才知道需要创建什么对象以及在何处创建。在场景中实例化游戏对象时,可以使用Instantiate 方法简化整个流程,只需要提供预制体对象、起始位置以及朝向即可。实际上,也可以使用 Unity 创建包含所需脚本和组件的对象,使之朝向指定的方向,然后在3D空间中按需进行调整。

实践:创建投射物预制体

在射击任何投射物之前,首先需要创建预制体。

1)在Hierarchy 面板中使用 Createl3D Obiect  Sphere 创建一个球体,命名为Bullet。然后修改Transform组件的各个轴的缩放值均为0.15


(2)单击Add Component 按钮,查找并添加 Rigidbody 组件,保留默认设置即可。


(3)使用 Create|Material 在 Materials 文件夹中创建一个新的材质,命名为Orb Mat。

  • 修改AIbedo 属性为深黄色。
  • 将Orb Mat 材质拖曳至 Bullet 对象上。

(4)拖放 Bullet对象至 Prefabs 文件夹

c028fe61cfea4bdbbdcf856ae1857de4.png

刚刚发生了什么

我们创建并配置了 Bullet 预制体,这个预制体在游戏中可以实例化任意多次,并且可按需进行修改。

实践:添加射击机制

现在已经有可用的预制体了,在任何时候,当按下鼠标左键进行射击时,都可实例化并移动预制体的副本。

(1)按如下代码修改 PlayerBehavior 脚本:

public class PlayerBehavior : MonoBehaviour
{
  public float moveSpeed = 10f;
  public float rotateSpeed = 75;
  public float jumpVelocity = 5f;
  public float distanceToGround=0.1f;
  public LayerMask groundLayer;
  public GameObject bullet;
  public float bulletSpeed = 100f;
  private float _vInput;
  private float _hInput;
  private Rigidbody _rb;
  private CapsuleCollider _col;
  void Start()
  {
    // ... No changes needed.
  }
  void Update()
  {
  // ... No changes needed ...
  }
  void FixedUpdate()
  {
    Vector3 rotation = Vector3.up * _hInput * Time.fixedDeltaTime;
    Quaternion deltaRotation = Quaternion.Euler(rotation);
    _rb.MovePosition(this.transform.position +this.transform.forward *_vInput * Time.fixedDeltaTime);
    _rb.MoveRotation( rb.rotation * deltaRotation);
    if (Input.GetMouseButtonDown(0))
     {
     GameObject newBullet = Instantiate(bullet,this.transform.position,this.transform.rotation) as GameObject;
     Rigidbody bulletRB = newBullet.GetComponent<Rigidbody>();
     bulletRB.velocity = this.transform.forward * bulletSpeed;
  }
}
  private bool IsGrounded()
{
  // .. No changes needed ..
}
}

)拖动 Bullet 预制体到 PlayerBehavior 脚本的 Inspector 面板中的 Bullet 属性上


(3)运行游戏并使用鼠标左键向玩家开火!

下面对步骤2)中的代码进行解释。


创建两个公共变量:一个用来保存 Bullet 预制体;另一个用来保存子弹的速度


使用f语句检查 Input.GetMouseButtonDown 方法是否返回 true,就像之前查InputGetKeyDown方法一样。GetMouseButtonDown方法接收一个int类型的参这个参数的值决定了想要检测的鼠标按键: 0 表示左键,1表示右键,2 表示中滚轮。


每当鼠标左键被按下时,就创建一个 GameObiect 局部变量。

使用Instantiate 方法为 newBullet 变量赋值,向该方法传入 Bullet预制体,并以胶囊的位置和旋转作为起始值。


添加as GameObiect 以显式地转换所返回对象的类型,从而与 newBullet 的类型一致。


调用GetComponent 方法以返回 newBullet 上的 Rigidbody 组件并保存。


设置 Rigidbody 组件的 velocity 属性为玩家的 tranform.forward 万向乘以bulletSpeed。通过直接修改 velocity 而不是使用 AddForce 方法,可以确保开火时重力不会使弹道下坠为弧形。

相关文章
|
4月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
224 6
|
4月前
|
图形学 缓存 算法
掌握这五大绝招,让您的Unity游戏瞬间加载完毕,从此告别漫长等待,大幅提升玩家首次体验的满意度与留存率!
【8月更文挑战第31天】游戏的加载时间是影响玩家初次体验的关键因素,特别是在移动设备上。本文介绍了几种常见的Unity游戏加载优化方法,包括资源的预加载与异步加载、使用AssetBundles管理动态资源、纹理和模型优化、合理利用缓存系统以及脚本优化。通过具体示例代码展示了如何实现异步加载场景,并提出了针对不同资源的优化策略。综合运用这些技术可以显著缩短加载时间,提升玩家满意度。
301 5
|
3月前
|
测试技术 C# 图形学
掌握Unity调试与测试的终极指南:从内置调试工具到自动化测试框架,全方位保障游戏品质不踩坑,打造流畅游戏体验的必备技能大揭秘!
【9月更文挑战第1天】在开发游戏时,Unity 引擎让创意变为现实。但软件开发中难免遇到 Bug,若不解决,将严重影响用户体验。调试与测试成为确保游戏质量的最后一道防线。本文介绍如何利用 Unity 的调试工具高效排查问题,并通过 Profiler 分析性能瓶颈。此外,Unity Test Framework 支持自动化测试,提高开发效率。结合单元测试与集成测试,确保游戏逻辑正确无误。对于在线游戏,还需进行压力测试以验证服务器稳定性。总之,调试与测试贯穿游戏开发全流程,确保最终作品既好玩又稳定。
167 4
|
3月前
|
前端开发 图形学 开发者
【独家揭秘】那些让你的游戏瞬间鲜活起来的Unity UI动画技巧:从零开始打造动态按钮,提升玩家交互体验的绝招大公开!
【9月更文挑战第1天】在游戏开发领域,Unity 是最受欢迎的游戏引擎之一,其强大的跨平台发布能力和丰富的功能集让开发者能够迅速打造出高质量的游戏。优秀的 UI 设计对于游戏至关重要,尤其是在手游市场,出色的 UI 能给玩家留下深刻的第一印象。Unity 的 UGUI 系统提供了一整套解决方案,包括 Canvas、Image 和 Button 等组件,支持添加各种动画效果。
169 3
|
3月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
221 3
|
4月前
|
图形学 开发者 存储
超越基础教程:深度拆解Unity地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
193 3
|
3月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
299 0
|
3月前
|
vr&ar 图形学 API
Unity与VR控制器交互全解:从基础配置到力反馈应用,多角度提升虚拟现实游戏的真实感与沉浸体验大揭秘
【8月更文挑战第31天】虚拟现实(VR)技术迅猛发展,Unity作为主流游戏开发引擎,支持多种VR硬件并提供丰富的API,尤其在VR控制器交互设计上具备高度灵活性。本文详细介绍了如何在Unity中配置VR支持、设置控制器、实现按钮交互及力反馈,结合碰撞检测和物理引擎提升真实感,助力开发者创造沉浸式体验。
194 0
|
3月前
|
图形学 开发者
【独家揭秘】Unity游戏开发秘籍:从基础到进阶,掌握材质与纹理的艺术,打造超现实游戏视效的全过程剖析——案例教你如何让每一面墙都会“说话”
【8月更文挑战第31天】Unity 是全球领先的跨平台游戏开发引擎,以其高效性能和丰富的工具集著称,尤其在提升游戏视觉效果方面表现突出。本文通过具体案例分析,介绍如何利用 Unity 中的材质与纹理技术打造逼真且具艺术感的游戏世界。材质定义物体表面属性,如颜色、光滑度等;纹理则用于模拟真实细节。结合使用两者可显著增强场景真实感。以 FPS 游戏为例,通过调整材质参数和编写脚本动态改变属性,可实现自然视觉效果。此外,Unity 还提供了多种高级技术和优化方法供开发者探索。
59 0
|
4月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
155 0