Roll-A-Ball
一、游戏规则
球在一个平面上滚动,可以捡到悬浮的物体,捡完游戏结束。
二、包含三个C#脚本
1. CameraController 控制摄像机跟随玩家移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
// 玩家对象的引用
public GameObject player;
// 摄像机和玩家之间的初始偏移量
private Vector3 offset;
// 在游戏开始时调用,用于初始化
void Start()
{
// 计算摄像机和玩家之间的初始偏移量
offset = transform.position - player.transform.position;
}
// 在每一帧的最后调用,用于确保摄像机跟随玩家移动
void LateUpdate()
{
// 设置摄像机的位置为玩家的位置加上初始偏移量
transform.position = player.transform.position + offset;
}
}
question:
1.1 为什么这样可以实现玩家对象的引用?(public GameObject player)
这行代码创建了一个公有的 GameObject
类型的变量,允许在Unity编辑器中为这个变量指定一个游戏对象。
2. Rotater 每帧旋转挂载此脚本的游戏对象
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotater : MonoBehaviour
{
// Update is called once per frame
void Update()
{
// 每帧按照给定的旋转角速度旋转游戏对象
transform.Rotate(new Vector3(15, 30, 45) * Time.deltaTime);
}
}
3. PlayerController 用于控制玩家对象的移动,并通过收集物体来增加计数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
public class PlayerController : MonoBehaviour
{
// 玩家移动速度
public float speed = 0;
// 用于显示计数的TextMeshProUGUI组件
public TextMeshProUGUI countText;
// 游戏胜利时显示的对象
public GameObject winTextObject;
// 玩家的刚体组件
private Rigidbody rb;
// 计数
private int count;
// 玩家在X轴和Y轴上的移动输入
private float movementX;
private float movementY;
// 在游戏对象被实例化时调用,用于初始化
void Start()
{
// 获取玩家的刚体组件
rb = GetComponent<Rigidbody>();
// 初始化计数为0
count = 0;
// 设置计数文本和隐藏胜利文本对象
SetCountText();
winTextObject.SetActive(false);
}
// InputSystem的事件处理方法,当玩家有移动输入时调用
void OnMove(InputValue movementValue)
{
// 从输入值中获取一个包含X和Y轴输入的Vector2
Vector2 movementVector = movementValue.Get<Vector2>();
// 将X轴和Y轴的输入分别存储在变量中
movementX = movementVector.x;
movementY = movementVector.y;
}
// 更新计数文本和检查是否达到胜利条件
void SetCountText()
{
// 更新计数文本
countText.text = "Count: " + count.ToString();
// 如果计数达到8,显示胜利文本
if (count >= 8)
{
winTextObject.SetActive(true);
}
}
// 在固定时间间隔内更新物理逻辑,用于移动玩家
void FixedUpdate()
{
// 创建一个Vector3用于存储玩家的移动方向,只影响X和Z轴
Vector3 movement = new Vector3(movementX, 0.0f, movementY);
// 通过给刚体施加力来移动玩家
rb.AddForce(movement * speed);
}
// 当玩家碰撞到触发器时调用
private void OnTriggerEnter(Collider other)
{
// 如果碰到的物体的标签是"PickUp"
if (other.gameObject.CompareTag("PickUp"))
{
// 隐藏碰到的物体
other.gameObject.SetActive(false);
// 增加计数
count = count + 1;
// 更新计数文本和检查是否达到胜利条件
SetCountText();
}
}
}
question:
3.1 OnMove函数会接收到什么样的值呢?
OnMove
函数接收的是InputValue
类型的参数,InputValue
是 Unity Input System 中的一个类,用于表示输入系统接收到的原始输入数据,可以包含各种不同类型的输入数据,如单一的数字、向量、颜色等,具体取决于输入的类型。该类的目的是提供一种通用的方式来处理不同类型的输入数据,无论是来自键盘、鼠标、手柄还是其他输入设备。
3.2 为什么胜利文本要用GameObject声明而不是TextMeshProUGUI?
在脚本中,winTextObject
是一个 GameObject
类型的引用,而不是 TextMeshProUGUI
。这是因为 GameObject
是 Unity 中所有游戏对象的基类,而 TextMeshProUGUI
是 GameObject
的一个组件。
当声明 winTextObject
为 GameObject
类型时,它可以引用场景中的任何游戏对象,而不仅仅是 TextMeshProUGUI
。这样的设计允许你在 Unity 编辑器中将任何对象(包括空对象或包含其他组件的对象)指定为 winTextObject
。这是一个很灵活的做法,因为你可能希望在游戏胜利时不仅仅显示一个文本,还可能需要其他的效果、动画等。
3.3 为什么start() 要调用 SetCountText() 呢?
游戏开始时,计数通常是初始值,可能为零。通过调用 SetCountText()
,可以确保计数文本在游戏开始时反映正确的状态,而不是等到发生碰撞事件后再更新。
三、Prefab
Prefab是一种存储GameObject以及其组件、属性值和子对象的可重用资源的方式。允许创建、配置并将GameObject作为可重用资源存储。这样,就可以在场景中多次实例化(创建实例)相同的对象,保持一致性。
1. prefab实例
预制体实例是预制体的副本,存在于场景中。它保留与其预制体资源的连接,并且在预制体修改时可以更新。可以通过将预制体资源拖到场景中或使用脚本命令在场景中实例化预制体。
2. 预制体工作流程
修改预制体资源时,场景中的所有实例都可以更新以匹配更改。这使得在多个相同对象实例之间保持一致性变得容易。
3. 预制体变体
预制体变体允许创建预制体的不同版本,而不修改原始预制体。变体继承自其基本预制体,但可以具有独特的组件、属性或甚至覆盖。
四、制作收集物品
1. 创建收集物品
在场景中创建一个3D或2D对象,该对象将充当您的收集物品。设置对象的形状、外观和任何可能的动画效果。
2. 设置标签(Tag)
为收集物品对象设置一个特定的标签,以便在代码中更容易地识别它。例如,可以使用标签 “PickUp”。
3. 添加脚本
在收集物品对象上附加一个脚本,用于处理收集逻辑。在脚本中,需要检测与玩家的碰撞,并在碰撞发生时执行相应的处理。
// 当玩家碰撞到触发器时调用
private void OnTriggerEnter(Collider other)
{
// 如果碰到的物体的标签是"PickUp"
if (other.gameObject.CompareTag("PickUp"))
{
// 隐藏碰到的物体
other.gameObject.SetActive(false);
// 增加计数
count = count + 1;
// 更新计数文本和检查是否达到胜利条件
SetCountText();
}
}
五、创建UI显示文本
Create->UI->Text,命名为Count Text,再创建一个Win Text,会生成如下结构
操作完成后,编写相应的c#脚本。
六、c#相关类、函数解释
1.GameObject
在Unity中,GameObject
是游戏中所有实体的基本构建块。每个物体都是GameObject
类的实例。GameObject
是一个容器,可以包含组件(Components),例如渲染器(Renderer)、碰撞器(Collider)、脚本(Script)等,用于定义物体的外观和行为。
1.1 常见属性:
name(string): 物体的名称
transform(Transform):描述物体的位置、旋转和缩放信息。Transform
包含了物体在场景中的空间变换属性。
!!transform是一个关键字,用于访问脚本所附加的游戏对象的 Transform
组件。Transform
是 Unity 引擎中的一个类,位于 UnityEngine
命名空间下。这个类用于表示游戏对象的位置、旋转和缩放信息。Transform
类包含一系列方法和属性,用于操纵和查询对象的变换属性。
activeSelf(bool):指示物体是否处于激活状态。如果为 true
,则物体处于激活状态;如果为 false
,则物体处于非激活状态。
1.2 常见方法:
GetComponent():获取物体上附加的指定类型的组件。例如,GetComponent<Renderer>()
会返回与该物体关联的渲染器组件。
SetActive(bool value):激活或禁用物体。
SendMessage(string methodName):向物体上所有带有指定方法名称的脚本发送消息。这是一种在脚本之间通信的方式。
transform.Translate(Vector3 translation):移动物体。translation
是一个 Vector3
,表示物体在三维空间中的移动方向和距离。
2.Vector3
Vector3 常用于表示三维空间中的位置、方向、位移等信息。
2.1 常见属性:
x, y, z(float):分别表示向量在 x、y、z 轴上的分量。
magnitude(float):向量的长度,即该向量从原点到其表示的点之间的距离。
normalized(Vector3):描述:返回一个长度为1的向量,方向与原始向量相同,用于表示方向。
2.2 常见方法:
Vector3(float x, float y, float z):构造函数,用给定的 x、y、z 分量创建一个 Vector3。
Normalize():返回一个长度为1的向量,方向与原始向量相同。
magnitude():返回向量的长度。
Cross(Vector3 n1, Vector3 n2):描述:返回两个向量的叉积,即垂直于这两个向量的向量。
Dot(Vector3 n1, Vector3 n2):返回两个向量的点积,即这两个向量之间夹角的余弦值乘以它们的长度。
3. Transform
每个 Unity 的游戏对象都附带有一个 Transform
组件,它负责定义对象在世界中的空间位置和方向。
3.1 常见属性:
position(Vector3):游戏对象在世界空间中的位置。
rotation(Quaternion):游戏对象的旋转信息。
localPosition(Vector3):游戏对象在其父对象坐标系中的位置。
localRotation(Quaternion):游戏对象在其父对象坐标系中的旋转。
forward、right、up(Vector3):游戏对象的前向、右向、上向向量。
3.2 常见方法:
Translate(Vector3 translation):移动游戏对象。
Rotate(Vector3 eulerAngles):绕自身坐标系旋转游戏对象。
LookAt(Transform target):使游戏对象朝向目标对象。
SetParent(Transform parent, bool worldPositionStays):设置游戏对象的父对象。
GetComponent():获取游戏对象上附加的指定类型的组件。
4. Rigidbody
Rigidbody是 Unity 中的一个组件,用于处理物体的物理运动。具体而言,Rigidbody
组件使物体能够受到物理引擎的影响,包括重力、碰撞、施加力等。使用 Rigidbody
组件可以让物体在游戏中表现得更真实,并且更容易处理与其他物体之间的物理交互。
4.1 常见属性:
mass(float):物体的质量,影响其对重力的响应以及碰撞时的反应。
drag(float):物体在运动中的阻力,影响其减速。
angularDrag(float):物体在旋转中的阻力。
useGravity(bool):是否启用重力对物体的影响。
isKinematic(bool):如果启用,物体将完全由脚本控制,而不受物理引擎的影响。
4.2 常见方法:
AddForce(Vector3 force):向物体施加力。
AddTorque(Vector3 torque):向物体施加扭矩。
MovePosition(Vector3 position):通过设置物体的位置,实现平滑的运动。
MoveRotation(Quaternion rotation):通过设置物体的旋转,实现平滑的旋转。
5. OnTriggerEnter(Collider other);
OnTriggerEnter() 是 Unity 中 MonoBehaviour 的一个方法,用于在游戏对象进入另一个物体的触发器时被调用。这个方法通常用于处理触发器(Collider)之间的交互和触发特定的事件。
tips:
- 触发器的设置: 为了使
OnTriggerEnter
被调用,确保触发器的 Is Trigger 属性已启用。 - 碰撞体类型: 这个方法仅在两个 Collider 中至少有一个是触发器(Is Trigger 启用)的情况下被调用。
- 标签的使用: 在检查进入触发器的物体时,通常使用标签(Tag)来判断物体的类型。标签是 Unity 中用于标识游戏对象的字符串,可以在 Inspector 窗口中为物体设置。
- 使用
OnTriggerEnter
可以方便地处理触发器的交互,例如检测玩家进入特定区域、触发特效、改变游戏状态等。
6.Time.deltaTime
Time.deltaTime
是 Unity 中的一个变量,用于获取两个连续帧之间的时间间隔。它表示每一帧的时间,以秒为单位。使用 Time.deltaTime
可以使游戏逻辑在不同帧率下保持相对一致,因为它将时间与每一帧的持续时间相结合。
使用 Time.deltaTime
是为了将运动、动画和其他时间相关的操作与实际时间进行关联,从而使这些操作在不同的硬件上和不同的帧率下表现一致。
Tips:
1. Update、FixedUpdate、LateUpdate异同
Update
方法:每一帧都会调用,用于处理大多数的帧相关的逻辑。
FixedUpdate
方法:固定的时间间隔(由物理引擎决定)调用,用于处理物理相关的逻辑,比如刚体运动、物体的物理碰撞等。
LateUpdate
方法:在所有 Update
和 FixedUpdate
方法执行完毕后调用,用于处理在这些方法之后的逻辑。通常用于摄像机跟随、动画逻辑等。
2. AddForce和Translate异同
Translate 方法:用于直接修改物体的位置,使其在空间中移动。
工作方式: Translate
方法将物体的当前位置按照指定的位移值进行修改。
不受物理引擎控制: 使用 Translate
方法时,物体的移动是直接应用到 Transform 上的,不受物理引擎的控制。
**用法:**transform.Translate(Vector3.forward * Time.deltaTime);
` AddForce方法:用于在物理引擎中模拟受力的作用,产生运动效果。
工作方式: AddForce
方法通过给物体的 Rigidbody
施加力来模拟物理运动。
受物理引擎控制: 使用 AddForce
方法时,物体的运动受到物理引擎的影响,包括质量、重力、阻力等。
**用法:**rb.AddForce(Vector3.forward * forceAmount);
-
Translate
直接修改物体的位置,而AddForce
通过施加力来影响物体的运动。 -
物理引擎影响:
Translate
不受物理引擎的控制,而AddForce
受到物理引擎的控制,考虑了物理规律的影响。 -
用途: 如果需要直接控制物体的位置,比如玩家的输入移动,可以使用
Translate
;如果需要模拟物理力的影响,例如受力的推动,可以使用AddForce
。 通常,如果涉及到物体的物理运动,使用
AddForce
更为合适,特别是在涉及到碰撞、惯性、重力等物理效果的情况下。如果只是简单的平移或根据用户输入进行移动,Translate
可能更方便。
**用法:**rb.AddForce(Vector3.forward * forceAmount);
-
Translate
直接修改物体的位置,而AddForce
通过施加力来影响物体的运动。 -
物理引擎影响:
Translate
不受物理引擎的控制,而AddForce
受到物理引擎的控制,考虑了物理规律的影响。 -
用途: 如果需要直接控制物体的位置,比如玩家的输入移动,可以使用
Translate
;如果需要模拟物理力的影响,例如受力的推动,可以使用AddForce
。 通常,如果涉及到物体的物理运动,使用
AddForce
更为合适,特别是在涉及到碰撞、惯性、重力等物理效果的情况下。如果只是简单的平移或根据用户输入进行移动,Translate
可能更方便。