C# Unity脚本编写:游戏开发的核心技术指南


Unity是全球最流行的游戏引擎之一,而C#是Unity开发的主要编程语言。掌握C#脚本编写是成为Unity开发者的关键一步。本文将全面介绍Unity中C#脚本编写的核心概念、最佳实践和高级技巧,帮助你高效地开发游戏逻辑和交互系统。

1. Unity脚本基础

1.1 创建第一个脚本

  1. 在Unity项目中右键点击Assets窗口
  2. 选择Create > C# Script
  3. 命名为”PlayerController”
  4. 双击脚本在Visual Studio或其他编辑器中打开
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    // 在Inspector中显示的公共变量
    public float moveSpeed = 5f;
    public float jumpForce = 10f;

    private Rigidbody rb;
    private bool isGrounded;

    // 初始化
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // 每帧调用
    void Update()
    {
        float moveInput = Input.GetAxis("Horizontal");
        rb.velocity = new Vector3(moveInput * moveSpeed, rb.velocity.y, 0);

        if (Input.GetButtonDown("Jump") && isGrounded)
        {
            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
            isGrounded = false;
        }
    }

    // 碰撞检测
    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Ground"))
        {
            isGrounded = true;
        }
    }
}

1.2 脚本生命周期

Unity脚本遵循特定的执行顺序:

  1. Awake():脚本实例被创建时调用(即使脚本未启用)
  2. OnEnable():对象启用时调用
  3. Start():第一次Update前调用
  4. FixedUpdate():固定时间间隔调用(物理更新)
  5. Update():每帧调用
  6. LateUpdate():所有Update执行后调用
  7. OnDisable():对象禁用时调用
  8. OnDestroy():对象销毁前调用

2. 常用Unity API

2.1 游戏对象操作

// 创建新游戏对象
GameObject newObj = new GameObject("NewObject");

// 实例化预制体
public GameObject prefab;
Instantiate(prefab, transform.position, Quaternion.identity);

// 查找对象
GameObject player = GameObject.Find("Player");
GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");

// 访问和修改组件
Renderer rend = GetComponent<Renderer>();
rend.material.color = Color.red;

// 激活/禁用对象
gameObject.SetActive(false);

2.2 输入系统

// 键盘输入
float horizontal = Input.GetAxis("Horizontal"); // 平滑输入值[-1,1]
float rawInput = Input.GetAxisRaw("Horizontal"); // 离散值(-1,0,1)

// 鼠标输入
if (Input.GetMouseButtonDown(0)) // 左键点击
{
    Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}

// 新输入系统(需要安装Input System包)
using UnityEngine.InputSystem;

public class PlayerInputExample : MonoBehaviour
{
    private PlayerInputActions inputActions;

    private void Awake()
    {
        inputActions = new PlayerInputActions();
        inputActions.Player.Jump.performed += ctx => Jump();
    }

    private void OnEnable() => inputActions.Enable();
    private void OnDisable() => inputActions.Disable();

    private void Jump()
    {
        // 跳跃逻辑
    }
}

2.3 物理系统

// 施加力
Rigidbody rb = GetComponent<Rigidbody>();
rb.AddForce(Vector3.forward * 10f, ForceMode.Impulse);

// 射线检测
if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, 10f))
{
    Debug.Log($"Hit: {hit.collider.name}");
}

// 2D物理
Rigidbody2D rb2D = GetComponent<Rigidbody2D>();
rb2D.AddForce(Vector2.up * 5f, ForceMode2D.Impulse);

// 碰撞检测
void OnTriggerEnter(Collider other)
{
    if (other.CompareTag("Collectible"))
    {
        Destroy(other.gameObject);
    }
}

3. 高级脚本技术

3.1 协程(Coroutines)

// 简单协程
IEnumerator FadeOut()
{
    Renderer rend = GetComponent<Renderer>();
    Color color = rend.material.color;

    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        color.a = alpha;
        rend.material.color = color;
        yield return new WaitForSeconds(0.1f);
    }

    gameObject.SetActive(false);
}

// 启动协程
StartCoroutine(FadeOut());

// 带参数的协程
IEnumerator MoveToPosition(Vector3 targetPos, float duration)
{
    Vector3 startPos = transform.position;
    float elapsed = 0f;

    while (elapsed < duration)
    {
        transform.position = Vector3.Lerp(startPos, targetPos, elapsed/duration);
        elapsed += Time.deltaTime;
        yield return null;
    }

    transform.position = targetPos;
}

3.2 事件系统

// 自定义事件
public class GameEvents : MonoBehaviour
{
    public static GameEvents current;

    private void Awake()
    {
        current = this;
    }

    public event Action<int> onScoreUpdated;
    public void ScoreUpdated(int score)
    {
        onScoreUpdated?.Invoke(score);
    }
}

// 事件订阅
public class ScoreDisplay : MonoBehaviour
{
    private Text scoreText;

    private void OnEnable()
    {
        GameEvents.current.onScoreUpdated += UpdateScoreDisplay;
    }

    private void OnDisable()
    {
        GameEvents.current.onScoreUpdated -= UpdateScoreDisplay;
    }

    private void UpdateScoreDisplay(int score)
    {
        scoreText.text = $"Score: {score}";
    }
}

3.3 脚本间通信

// 1. 直接引用
public class PlayerHealth : MonoBehaviour
{
    public int health = 100;

    public void TakeDamage(int damage)
    {
        health -= damage;
    }
}

// 在其他脚本中
PlayerHealth playerHealth = FindObjectOfType<PlayerHealth>();
playerHealth.TakeDamage(10);

// 2. 使用SendMessage
gameObject.SendMessage("TakeDamage", 10, SendMessageOptions.DontRequireReceiver);

// 3. 单例模式
public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

4. 编辑器扩展

4.1 自定义Inspector

using UnityEditor;

[CustomEditor(typeof(PlayerController))]
public class PlayerControllerEditor : Editor
{
    public override void OnInspectorGUI()
    {
        PlayerController controller = (PlayerController)target;

        // 默认属性
        DrawDefaultInspector();

        // 自定义按钮
        if (GUILayout.Button("Reset Position"))
        {
            controller.transform.position = Vector3.zero;
        }

        // 条件显示
        if (controller.moveSpeed > 10f)
        {
            EditorGUILayout.HelpBox("高速移动可能导致问题!", MessageType.Warning);
        }
    }
}

4.2 属性特性

// 常用属性特性
[Header("移动设置")]
[Range(0, 20)] public float moveSpeed = 5f;
[Tooltip("跳跃力度")] public float jumpForce = 10f;
[SerializeField] private int health; // 强制显示私有变量
[HideInInspector] public bool debugMode; // 隐藏公共变量
[Space(20)] // 添加空白
[TextArea(3, 5)] public string description;

4.3 自定义Gizmos

// 在场景视图中绘制辅助图形
private void OnDrawGizmos()
{
    Gizmos.color = Color.green;
    Gizmos.DrawWireSphere(transform.position, 1f);

    Gizmos.color = Color.red;
    Gizmos.DrawLine(transform.position, transform.position + transform.forward * 2f);
}

// 仅在选中时绘制
private void OnDrawGizmosSelected()
{
    Gizmos.color = Color.blue;
    Gizmos.DrawWireCube(transform.position, Vector3.one * 2f);
}

5. 性能优化

5.1 缓存组件引用

// 不好的做法:每帧获取组件
void Update()
{
    GetComponent<Rigidbody>().AddForce(Vector3.up * 10f);
}

// 好的做法:缓存引用
private Rigidbody rb;

void Awake()
{
    rb = GetComponent<Rigidbody>();
}

void Update()
{
    rb.AddForce(Vector3.up * 10f);
}

5.2 对象池系统

public class ObjectPool : MonoBehaviour
{
    public GameObject prefab;
    public int poolSize = 10;

    private Queue<GameObject> pool = new Queue<GameObject>();

    private void Awake()
    {
        for (int i = 0; i < poolSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            pool.Enqueue(obj);
        }
    }

    public GameObject GetObject()
    {
        if (pool.Count > 0)
        {
            GameObject obj = pool.Dequeue();
            obj.SetActive(true);
            return obj;
        }

        // 必要时扩展池
        GameObject newObj = Instantiate(prefab);
        return newObj;
    }

    public void ReturnObject(GameObject obj)
    {
        obj.SetActive(false);
        pool.Enqueue(obj);
    }
}

5.3 优化Update方法

// 使用Time.deltaTime进行帧率无关的移动
void Update()
{
    transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
}

// 减少不必要的每帧计算
private float nextActionTime;
public float interval = 1f;

void Update()
{
    if (Time.time > nextActionTime)
    {
        nextActionTime = Time.time + interval;
        PerformExpensiveOperation();
    }
}

6. 常用设计模式

6.1 状态模式

public interface IPlayerState
{
    void EnterState(PlayerController player);
    void Update(PlayerController player);
    void ExitState(PlayerController player);
}

public class IdleState : IPlayerState
{
    public void EnterState(PlayerController player)
    {
        player.animator.Play("Idle");
    }

    public void Update(PlayerController player)
    {
        if (Input.GetAxis("Horizontal") != 0)
        {
            player.ChangeState(new RunState());
        }
    }

    public void ExitState(PlayerController player) { }
}

public class PlayerController : MonoBehaviour
{
    private IPlayerState currentState;

    private void Start()
    {
        ChangeState(new IdleState());
    }

    private void Update()
    {
        currentState.Update(this);
    }

    public void ChangeState(IPlayerState newState)
    {
        currentState?.ExitState(this);
        currentState = newState;
        currentState.EnterState(this);
    }
}

6.2 观察者模式

public class AchievementSystem : MonoBehaviour
{
    private void OnEnable()
    {
        PlayerHealth.onPlayerDamaged += CheckDamageAchievement;
        Enemy.onEnemyKilled += CheckKillAchievement;
    }

    private void OnDisable()
    {
        PlayerHealth.onPlayerDamaged -= CheckDamageAchievement;
        Enemy.onEnemyKilled -= CheckKillAchievement;
    }

    private void CheckDamageAchievement(float damage)
    {
        // 检查伤害相关成就
    }

    private void CheckKillAchievement(Enemy enemy)
    {
        // 检查击杀相关成就
    }
}

7. 脚本调试技巧

7.1 Debug工具

// 基本调试输出
Debug.Log("普通信息");
Debug.LogWarning("警告信息");
Debug.LogError("错误信息");

// 条件编译
[System.Diagnostics.Conditional("UNITY_EDITOR")]
void DebugMessage(string message)
{
    Debug.Log(message);
}

// 可视化调试
Debug.DrawRay(transform.position, transform.forward * 10f, Color.red, 1f);
Debug.DrawLine(startPos, endPos, Color.green, 2f);

7.2 断点调试

  1. 在Visual Studio中点击代码行号左侧设置断点
  2. 在Unity中点击”Attach to Unity”按钮
  3. 运行游戏,当执行到断点时会暂停
  4. 使用调试工具查看变量值和调用堆栈

7.3 性能分析

// 代码块性能测量
void Update()
{
    using (new UnityEngine.Profiling.ProfilerMarker("CustomUpdate").Auto())
    {
        // 需要测量的代码
    }
}

// 在Profiler窗口中查看结果

8. 总结

本文全面介绍了Unity中C#脚本编写的关键知识和技术:

  1. 基础结构:理解脚本生命周期和核心Unity API
  2. 交互系统:掌握输入处理和物理交互
  3. 高级技术:协程、事件系统和脚本通信
  4. 编辑器扩展:自定义Inspector和开发工具
  5. 性能优化:对象池、缓存和Update优化
  6. 设计模式:状态模式和观察者模式的应用
  7. 调试技巧:日志输出和性能分析

通过掌握这些技术,你将能够编写高效、可维护的Unity游戏脚本。记住,优秀的Unity开发者不仅需要理解编程概念,还需要熟悉Unity引擎的特性和最佳实践。不断实践和探索新的技术,你的游戏开发技能将不断提升。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注