二、C# 脚本编程 二、C# 脚本编程 在 Unity3D 的世界中,C# 脚本编程是游戏逻辑实现的基石。掌握 C# 脚本,您将能够赋予游戏对象生命,控制游戏行为,并与 Unity 引擎进行深度交互。本章节将引导您从零开始,逐步深入 C# 脚本编程的核心概念和实践技巧,让您能够自如地运用 C# 脚本构建各种精彩的游戏功能。 2.1 C# 脚本基础 在深入 Unity 之前,我们需要先了解一些 C# 编程的基础知识。如果您已经熟悉 C# 或其他编程语言,可以快速回顾本节内容。 2.1.1 变量与数据类型 变量是存储数据的容器,而数据类型则定义了变量可以存储的数据种类。C# 是一种强类型语言,这意味着您在声明变量时必须指定其数据类型。
二、C# 脚本编程
在 Unity3D 的世界中,C# 脚本编程是游戏逻辑实现的基石。掌握 C# 脚本,您将能够赋予游戏对象生命,控制游戏行为,并与 Unity 引擎进行深度交互。本章节将引导您从零开始,逐步深入 C# 脚本编程的核心概念和实践技巧,让您能够自如地运用 C# 脚本构建各种精彩的游戏功能。
2.1 C# 脚本基础
在深入 Unity 之前,我们需要先了解一些 C# 编程的基础知识。如果您已经熟悉 C# 或其他编程语言,可以快速回顾本节内容。
2.1.1 变量与数据类型
变量是存储数据的容器,而数据类型则定义了变量可以存储的数据种类。C# 是一种强类型语言,这意味着您在声明变量时必须指定其数据类型。
常见数据类型:
int: 整型,用于存储整数,例如:int score = 100;
float: 浮点型,用于存储单精度浮点数(小数),例如:float speed = 3.5f; (注意 f 后缀)
double: 双精度浮点型,用于存储更高精度的浮点数,例如:double pi = 3.1415926;
bool: 布尔型,用于存储真或假的值,例如:bool isGameOver = false;
string: 字符串型,用于存储文本,例如:string playerName = "Player1";
char: 字符型,用于存储单个字符,例如:char initial = 'J';
GameObject: Unity 特有的数据类型,代表游戏场景中的游戏对象。例如:GameObject playerObject;
Transform: Unity 特有的数据类型,代表游戏对象的变换组件(位置、旋转、缩放)。例如:Transform playerTransform;
Vector3: Unity 特有的数据类型,代表三维向量(x, y, z)。例如:Vector3 direction = new Vector3(1, 0, 0);
Quaternion: Unity 特有的数据类型,代表旋转。例如:Quaternion rotation = Quaternion.identity;
变量声明与初始化:
数据类型 变量名; // 声明变量 数据类型 变量名 = 初始值; // 声明并初始化变量
例如:
int enemyCount; // 声明整型变量 enemyCount float jumpForce = 5.0f; // 声明并初始化浮点型变量 jumpForce string message = "Hello Unity!"; // 声明并初始化字符串变量 message
2.1.2 运算符
运算符用于对变量和值执行操作。C# 提供了丰富的运算符,包括:
算术运算符: + (加), - (减), * (乘), / (除), % (取余)
赋值运算符: = (赋值), +=, -=, *=, /=, %= (复合赋值)
比较运算符: == (等于), != (不等于), > (大于), < (小于), >= (大于等于), <= (小于等于)
逻辑运算符: && (逻辑与), || (逻辑或), ! (逻辑非)
递增/递减运算符: ++ (递增), -- (递减)
代码示例:
int a = 10; int b = 5; int sum = a + b; // 加法 int difference = a - b; // 减法 int product = a * b; // 乘法 float quotient = (float)a / b; // 除法 (需要强制类型转换为 float 才能得到浮点结果) int remainder = a % b; // 取余 bool isEqual = (a == b); // 等于比较 bool isGreater = (a > b); // 大于比较 a++; // 递增 a (a 变为 11) b--; // 递减 b (b 变为 4)
2.1.3 控制流语句
控制流语句允许您根据条件或循环执行不同的代码块,或者重复执行代码。
条件语句:
if 语句: 根据条件判断是否执行代码块。
if (condition) { // 条件为真时执行的代码 }
if...else 语句: 条件为真时执行一个代码块,否则执行另一个代码块。
if (condition) { // 条件为真时执行的代码 } else { // 条件为假时执行的代码 }
if...else if...else 语句: 多条件判断。
if (condition1) { // 条件 1 为真时执行的代码 } else if (condition2) { // 条件 2 为真时执行的代码 } else { // 所有条件都不为真时执行的代码 }
switch 语句: 根据变量的值选择执行不同的代码块。
switch (variable) { case value1: // variable 等于 value1 时执行的代码 break; case value2: // variable 等于 value2 时执行的代码 break; default: // variable 不等于任何 case 值时执行的代码 break; }
循环语句:
for 循环: 在指定次数内重复执行代码块。
for (初始化; 条件; 迭代器) { // 循环体代码 }
while 循环: 只要条件为真就重复执行代码块。
while (condition) { // 循环体代码 }
do...while 循环: 先执行一次代码块,然后只要条件为真就重复执行。
do { // 循环体代码 } while (condition);
foreach 循环: 遍历集合(例如数组或列表)中的元素。
foreach (数据类型 元素变量名 in 集合) { // 针对每个元素执行的代码 }
代码示例 (控制流):
int health = 50; if (health <= 0) { Debug.Log("Game Over!"); // 使用 Debug.Log 在 Unity 控制台输出信息 } else if (health <= 20) { Debug.Log("Health is low!"); } else { Debug.Log("Health is normal."); } for (int i = 0; i < 10; i++) { Debug.Log("计数器: " + i); } int count = 0; while (count < 5) { Debug.Log("While 循环计数: " + count); count++; }
2.1.4 函数 (方法)
函数是一段组织好的、可重复使用的代码块,用于执行特定的任务。函数可以提高代码的模块化和可重用性。
函数定义:
访问修饰符 返回类型 函数名(参数列表) { // 函数体代码 return 返回值; // 如果有返回值 }
访问修饰符: 例如 public, private, protected (控制函数的访问权限,稍后Unity部分会详细介绍)。
返回类型: 函数执行后返回的数据类型。如果函数不返回任何值,则使用 void。
函数名: 函数的标识符,遵循命名规范。
参数列表: 函数接受的输入参数,可以为空或包含多个参数,每个参数由数据类型和参数名组成。
函数体: 包含函数执行的代码。
返回值: 使用 return 语句返回函数的结果 (如果返回类型不是 void)。
函数调用:
函数名(参数值列表);
代码示例 (函数):
public class ExampleScript : MonoBehaviour // MonoBehaviour 是 Unity 脚本的基础类 { public int AddNumbers(int num1, int num2) // 定义一个公有函数 AddNumbers { int sum = num1 + num2; return sum; // 返回两数之和 } void Start() // Unity 生命周期函数,在脚本启动时执行一次 { int result = AddNumbers(5, 3); // 调用 AddNumbers 函数 Debug.Log("5 + 3 = " + result); // 输出结果 } }
2.2 Unity 中的 C# 脚本
现在,我们将重点转向 Unity 中 C# 脚本的应用。Unity 使用 C# 作为主要的脚本语言,用于控制游戏逻辑、交互和行为。
2.2.1 MonoBehaviour 类
在 Unity 中创建的 C# 脚本通常会继承自 MonoBehaviour 类。MonoBehaviour 是 Unity 脚本的基础类,它提供了许多内置的生命周期函数和属性,允许您与 Unity 引擎进行交互。
创建 C# 脚本:
在 Unity 项目窗口中,选择 "Assets" 文件夹 (或其他您希望存放脚本的文件夹)。
右键单击,选择 "Create" -> "C# Script"。
输入脚本名称 (例如 "PlayerController"),按 Enter 键。
双击脚本文件,Unity 将使用默认的代码编辑器 (通常是 Visual Studio 或 Visual Studio Code) 打开脚本。
默认脚本结构:
新创建的 C# 脚本通常包含以下结构:
using UnityEngine; // 引入 UnityEngine 命名空间,包含 Unity 核心 API public class 脚本名称 : MonoBehaviour // 继承自 MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }
常用的 MonoBehaviour 生命周期函数:
Awake(): 在脚本实例被创建后立即调用,无论脚本是否启用。用于初始化变量和设置初始状态。在 Start() 之前执行,且只执行一次。
OnEnable(): 在脚本启用时调用(例如,当 GameObject 被激活或脚本组件被启用时)。可能多次执行,每次脚本被启用时都会调用。
Start(): 在脚本第一次启用时在 Update() 函数之前调用。只执行一次,用于初始化需要在游戏开始时设置的内容。
FixedUpdate(): 以固定的时间间隔调用,独立于帧率。用于物理相关的更新,例如刚体运动。
Update(): 每帧调用一次。用于大多数游戏逻辑的更新,例如玩家输入、游戏状态检查等。
LateUpdate(): 在 Update() 之后每帧调用一次。用于相机跟随、后期处理等需要在所有 Update() 完成后执行的操作。
OnGUI(): 用于绘制 GUI 元素(不推荐用于现代 Unity UI 系统)。每帧多次调用,用于旧版 GUI 系统。
OnDisable(): 在脚本禁用时调用(例如,当 GameObject 被禁用或脚本组件被禁用时)。可能多次执行,每次脚本被禁用时都会调用。
OnDestroy(): 在脚本实例被销毁之前调用。只执行一次,用于清理资源。
2.2.2 GameObject 和 Component
Unity 场景由 GameObject (游戏对象) 组成,每个 GameObject 都可以附加不同的 Component (组件) 来实现不同的功能。C# 脚本本身也是一种 Component。
GameObject: 场景中的基本实体,可以理解为游戏中的 "物体"。
Component: 附加到 GameObject 上的模块,提供特定的功能,例如:
Transform: 控制 GameObject 的位置、旋转和缩放。
Renderer: 控制 GameObject 的渲染外观(例如 Mesh Renderer, Sprite Renderer)。
Collider: 定义 GameObject 的碰撞形状,用于物理碰撞检测。
Rigidbody: 使 GameObject 具有物理属性,可以受力、重力影响。
AudioSource: 播放声音。
Light: 提供光源。
Camera: 定义场景的视角。
UI 组件: 例如 Button, Text, Image (用于构建用户界面)。
自定义 C# 脚本: 您自己编写的 C# 脚本也是 Component。
获取和操作 Component:
您可以使用 GetComponent<>() 方法从 GameObject 中获取其他 Component,并在脚本中操作它们。
public class AccessComponents : MonoBehaviour { void Start() { // 获取当前 GameObject 的 Transform 组件 Transform transformComponent = GetComponent<Transform>(); if (transformComponent != null) // 确保组件存在 { // 修改 Transform 组件的位置 transformComponent.position = new Vector3(1, 2, 3); } else { Debug.LogError("Transform component not found!"); } // 获取当前 GameObject 的 Renderer 组件 (假设存在) Renderer rendererComponent = GetComponent<Renderer>(); if (rendererComponent != null) { // 禁用 Renderer 组件,使物体不可见 rendererComponent.enabled = false; } } }
公共变量 (Public Variables) 和 Inspector 面板:
在 MonoBehaviour 脚本中声明为 public 的变量,会在 Unity 编辑器的 Inspector 面板中显示出来。您可以在 Inspector 面板中直接修改这些变量的值,而无需重新编译脚本。这使得在编辑器中调整游戏参数非常方便。
public class PublicVariablesExample : MonoBehaviour { public float moveSpeed = 5f; // 公共浮点型变量 moveSpeed public GameObject targetObject; // 公共 GameObject 变量 targetObject public bool isJumping = false; // 公共布尔型变量 isJumping }
将此脚本附加到 GameObject 后,您将在 Inspector 面板中看到 Move Speed, Target Object, 和 Is Jumping 这些可编辑的属性。
2.2.3 常用 Unity API
Unity 提供了丰富的 API (应用程序编程接口) 供 C# 脚本使用,用于控制游戏逻辑、访问引擎功能等。以下是一些常用的 API:
Transform API: 用于操作 GameObject 的变换 (位置、旋转、缩放)。
transform.position: 获取或设置 GameObject 的世界坐标位置 (Vector3)。
transform.localPosition: 获取或设置 GameObject 相对于父物体的局部坐标位置 (Vector3)。
transform.rotation: 获取或设置 GameObject 的世界坐标旋转 (Quaternion)。
transform.localRotation: 获取或设置 GameObject 相对于父物体的局部坐标旋转 (Quaternion)。
transform.localScale: 获取或设置 GameObject 的局部缩放 (Vector3)。
transform.Translate(Vector3 translation): 按指定向量移动 GameObject。
transform.Rotate(Vector3 eulerAngles): 按指定欧拉角旋转 GameObject。
transform.LookAt(Transform target): 使 GameObject 面向目标物体。
Input API: 用于获取用户输入 (键盘、鼠标、触摸等)。
Input.GetKey(KeyCode key): 检测按键是否被按下 (持续按下)。
Input.GetKeyDown(KeyCode key): 检测按键是否被按下 (按下瞬间)。
Input.GetKeyUp(KeyCode key): 检测按键是否被释放 (释放瞬间)。
Input.GetAxis(string axisName): 获取轴输入值 (例如 "Horizontal" 水平轴, "Vertical" 垂直轴,通常用于方向键或摇杆)。
Input.GetMouseButton(int button): 检测鼠标按键是否被按下 (0: 左键, 1: 右键, 2: 中键)。
Input.GetMouseButtonDown(int button): 检测鼠标按键是否被按下 (按下瞬间)。
Input.GetMouseButtonUp(int button): 检测鼠标按键是否被释放 (释放瞬间)。
Input.mousePosition: 获取鼠标屏幕坐标 (Vector3)。
Debug API: 用于在 Unity 控制台输出调试信息。
Debug.Log(object message): 输出普通信息。
Debug.LogWarning(object message): 输出警告信息。
Debug.LogError(object message): 输出错误信息。
Time API: 用于获取时间相关信息。
Time.deltaTime: 上一帧到当前帧所用的时间 (以秒为单位)。非常重要,用于实现帧率独立性,使游戏在不同帧率下运行速度一致。
Time.time: 游戏开始运行到现在的时间 (以秒为单位)。
Time.fixedDeltaTime: FixedUpdate() 函数调用的固定时间间隔。
2.3 C# 脚本实践
为了更好地理解 C# 脚本在 Unity 中的应用,我们进行一些代码实践。
2.3.1 简单的移动脚本
创建一个名为 "MoveObject" 的 C# 脚本,并将其附加到一个 3D 对象 (例如 Cube)。
using UnityEngine; public class MoveObject : MonoBehaviour { public float moveSpeed = 5f; // 可在 Inspector 面板调整移动速度 void Update() { // 获取水平方向输入 (A, D 键或左右方向键) float horizontalInput = Input.GetAxis("Horizontal"); // 获取垂直方向输入 (W, S 键或上下方向键) float verticalInput = Input.GetAxis("Vertical"); // 创建移动方向向量 Vector3 moveDirection = new Vector3(horizontalInput, 0, verticalInput); // 归一化向量,使对角线移动速度与其他方向一致 moveDirection.Normalize(); // 使用 Translate 方法移动物体,并乘以 Time.deltaTime 实现帧率独立性 transform.Translate(moveDirection * moveSpeed * Time.deltaTime); } }
代码详解:
public float moveSpeed = 5f;: 声明一个公共的浮点型变量 moveSpeed,用于控制移动速度,并在 Inspector 面板中显示。
Input.GetAxis("Horizontal") 和 Input.GetAxis("Vertical"): 获取 Unity 默认配置的水平和垂直轴输入值。这些轴默认绑定到 A/D, 左右方向键 和 W/S, 上下方向键。返回值范围为 -1 到 1,表示输入强度和方向。
Vector3 moveDirection = new Vector3(horizontalInput, 0, verticalInput);: 根据水平和垂直输入值创建一个三维向量 moveDirection,表示移动方向。Y 轴设置为 0,表示只在水平面移动。
moveDirection.Normalize();: 将 moveDirection 向量归一化。归一化后的向量长度为 1,方向不变。这样做是为了确保对角线移动速度与水平或垂直移动速度一致。如果不归一化,对角线移动速度会更快。
transform.Translate(moveDirection * moveSpeed * Time.deltaTime);: 使用 transform.Translate() 方法移动物体。
moveDirection * moveSpeed: 计算每秒移动的距离向量。
* Time.deltaTime: 将每秒移动距离转换为每帧移动距离,实现帧率独立性。
实践步骤:
在 Unity 中创建一个 3D Cube 对象。
创建一个新的 C# 脚本 "MoveObject",并将上述代码复制粘贴到脚本中。
将 "MoveObject" 脚本拖拽到 Cube 对象上,使其成为 Cube 的一个组件。
运行游戏。您可以使用 A/D, W/S 或方向键控制 Cube 在水平面移动。
在 Inspector 面板中调整 Cube 的 "Move Speed" 属性,观察移动速度的变化。
2.3.2 简单的交互脚本 (点击物体)
创建一个名为 "ClickObject" 的 C# 脚本,并将其附加到一个 3D 对象 (例如 Sphere)。
using UnityEngine; public class ClickObject : MonoBehaviour { void Update() { // 检测鼠标左键是否被按下 if (Input.GetMouseButtonDown(0)) { // 从鼠标位置发射射线 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hitInfo; // 进行射线检测 if (Physics.Raycast(ray, out hitInfo)) { // 检测到碰撞物体 if (hitInfo.collider.gameObject == gameObject) // 判断碰撞物体是否是当前物体 { Debug.Log("Clicked on: " + gameObject.name); // 输出点击信息到控制台 // 在这里添加点击后的逻辑,例如改变颜色、播放声音等 GetComponent<Renderer>().material.color = Color.red; // 改变物体颜色为红色 } } } } }
代码详解:
Input.GetMouseButtonDown(0): 检测鼠标左键是否被按下 (按下瞬间)。
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);: 从主摄像机 (Camera.main) 的屏幕位置 (鼠标位置) 发射一条射线。射线用于模拟鼠标点击在 3D 空间中的方向。
Physics.Raycast(ray, out hitInfo): 进行射线检测。
ray: 要检测的射线。
out hitInfo: 输出参数,存储射线碰撞到的物体信息 (如果碰撞到)。
Physics.Raycast() 返回 true 如果射线碰撞到物体,否则返回 false。
if (hitInfo.collider.gameObject == gameObject): 判断射线碰撞到的 GameObject 是否是当前脚本所附加的 GameObject。
GetComponent<Renderer>().material.color = Color.red;: 获取当前 GameObject 的 Renderer 组件,并将其材质的颜色设置为红色,实现点击物体后改变颜色的效果。
实践步骤:
在 Unity 中创建一个 3D Sphere 对象。
创建一个新的 C# 脚本 "ClickObject",并将上述代码复制粘贴到脚本中。
将 "ClickObject" 脚本拖拽到 Sphere 对象上。
确保场景中有一个 Camera 对象 (通常默认场景中已经存在 Main Camera)。
运行游戏。点击 Sphere 对象,Sphere 的颜色会变为红色,并在控制台输出点击信息。
2.4 高级主题 (简要介绍)
C# 脚本编程在 Unity 中还有许多高级主题,例如:
协程 (Coroutines): 用于实现异步操作,例如延迟执行、动画序列等,避免阻塞主线程。
事件 (Events) 和委托 (Delegates): 用于实现对象之间的松耦合通信,例如 UI 事件处理、自定义事件触发等。
命名空间 (Namespaces): 用于组织代码,避免命名冲突,提高代码可维护性。
面向对象编程 (OOP) 原则: 例如封装、继承、多态,用于构建更复杂、可扩展的游戏系统。
数据结构和算法: 例如列表、字典、排序、搜索等,用于高效地处理游戏数据。
性能优化: 例如代码优化、资源管理、批处理等,提高游戏运行效率。
这些高级主题将在后续章节或更深入的学习中逐步探索。
2.5 最佳实践
代码可读性: 编写清晰、易懂的代码,使用有意义的变量名、函数名,添加必要的注释。
模块化编程: 将代码分解成小的、独立的函数或类,提高代码可重用性和可维护性。
注释: 适当地添加注释,解释代码的功能和逻辑,方便自己和他人理解。
错误处理: 考虑可能出现的错误情况,并进行适当的处理,例如使用 if 语句检查空引用、使用 try-catch 语句捕获异常。
性能意识: 编写高效的代码,避免不必要的计算和资源消耗,尤其是在 Update() 等每帧调用的函数中。
版本控制: 使用版本控制系统 (例如 Git) 管理代码,方便代码协作和版本回溯。
总结
本章节详细介绍了 Unity3D 中的 C# 脚本编程基础,包括 C# 基础语法、Unity 特性结合、代码实践和一些最佳实践建议。通过学习本章节,您应该已经掌握了 C# 脚本编程的入门知识,并能够编写简单的 Unity 脚本来实现基本的游戏功能。
C# 脚本编程是 Unity 游戏开发的强大工具,不断学习和实践将使您能够构建更复杂、更精彩的游戏世界。希望本章节能为您打开 Unity 游戏开发的大门!