引言

Unity脚本开发是游戏逻辑的核心,但新手常陷入​​生命周期混乱、事件泄漏、协程失控、DOTS入门难​​等问题。本文结合​​鸿蒙5+跨平台特性​​,详解​​C#脚本最佳实践​​、​​事件系统设计模式​​、​​协程高效使用​​、​​面向数据技术栈(DOTS)入门​​,并提供多端协同适配方案。


一、C#脚本生命周期与内存管理:新手最易踩的「坑」

1. 生命周期方法误用

​常见问题​​:
  • Awake()Start()逻辑混淆(如过早访问未初始化的组件)
  • OnDestroy()未正确释放资源(如未取消事件订阅、未停止协程)
​解决方案​​:
  • ​严格遵循初始化顺序​​:
    void Awake() {
        // 仅初始化必要组件(如Rigidbody、Collider)
        rb = GetComponent<Rigidbody>();
    }
    
    void Start() {
        // 依赖其他组件初始化完成后操作(如获取UI引用)
        uiManager = FindObjectOfType<UIManager>();
    }
  • ​生命周期清理清单​​:
    void OnDestroy() {
        // 取消事件订阅
        EventManager.Instance.OnPlayerDead -= OnPlayerDeadHandler;
        // 停止所有协程
        StopAllCoroutines();
        // 释放DOTS实体(若使用)
        if (entity != Entity.Null) {
            EntityManager.DestroyEntity(entity);
        }
    }

2. 内存泄漏:隐藏的性能杀手

​常见问题​​:
  • 未释放的委托引用(如List<Action>未清空)
  • 静态变量持有对象(如static List<GameObject>未清理)
  • 鸿蒙分布式对象未断开连接(如DistributedObject未释放)
​解决方案​​:
  • ​委托管理规范​​:
    // 使用弱引用避免内存泄漏
    public class EventManager {
        private Dictionary<string, WeakReference<Action>> eventDict = new();
    
        public void Subscribe(string key, Action callback) {
            eventDict[key] = new WeakReference<Action>(callback);
        }
    
        public void Unsubscribe(string key, Action callback) {
            if (eventDict.TryGetValue(key, out var weakRef) && weakRef.TryGetTarget(out var target)) {
                // 从列表中移除目标
            }
        }
    }
  • ​鸿蒙分布式对象清理​​:
    // 鸿蒙端断开分布式对象(ArkTS)
    import distributedObject from '@ohos.distributedObject';
    
    export default {
      onDestroy() {
        if (this.distributedObj) {
          this.distributedObj.disconnect(); // 断开连接
          this.distributedObj = null;
        }
      }
    }

二、事件系统设计:观察者模式、委托与UnityEvent的「三角恋」

1. 性能瓶颈:事件滥用

​常见问题​​:
  • 大量事件广播导致帧率下降(如每帧触发100+事件)
  • 事件链过长(A→B→C→D,调试困难)
​解决方案​​:
  • ​事件分级与批处理​​:
    // 鸿蒙端事件优先级定义(ArkTS)
    enum EventPriority {
        High,    // 立即执行(如伤害计算)
        Medium,  // 下一帧执行(如UI更新)
        Low      // 异步执行(如日志上报)
    }
    
    class EventManager {
        private Dictionary<string, List<Action>> highEvents = new();
        private Dictionary<string, List<Action>> mediumEvents = new();
    
        public void SendEvent(string key, Action callback, EventPriority priority = EventPriority.Medium) {
            switch (priority) {
                case EventPriority.High:
                    highEvents[key]?.Add(callback);
                    break;
                case EventPriority.Medium:
                    mediumEvents[key]?.Add(callback);
                    break;
            }
        }
    
        void LateUpdate() {
            // 批量执行Medium事件(避免每帧多次触发)
            foreach (var list in mediumEvents.Values) {
                list.ForEach(cb => cb?.Invoke());
                list.Clear();
            }
        }
    }
  • ​事件可视化调试​​:
    // Unity编辑器扩展(C#)
    #if UNITY_EDITOR
    using UnityEditor;
    
    public class EventDebugWindow : EditorWindow {
        void OnGUI() {
            if (GUILayout.Button("Print All Events")) {
                Debug.Log(EventManager.Instance.GetEventCount()); // 输出当前事件数量
            }
        }
    }
    #endif

2. 跨设备事件同步:鸿蒙分布式挑战

​场景​​:手机触发事件,智慧屏同步响应
​解决方案​​:
  • ​事件数据压缩​​:仅同步必要参数(如Vector3压缩为float[3]
  • ​分布式事件队列​​:使用鸿蒙DistributedData同步事件列表
// 鸿蒙分布式事件同步(ArkTS)
import distributedData from '@ohos.distributedData';

export default {
  sendCrossDeviceEvent(key: string, data: any) {
    const event = {
      key: key,
      data: data,
      timestamp: Date.now()
    };
    distributedData.put({
      key: `event_${key}`,
      value: JSON.stringify(event),
      replication: 'sync' // 同步到所有设备
    });
  },

  onCrossDeviceEvent(key: string, callback: (data: any) => void) {
    distributedData.on('dataChanged', (changedKey) => {
      if (changedKey.startsWith(`event_${key}`)) {
        const event = JSON.parse(changedKey.value);
        callback(event.data);
      }
    });
  }
}

三、协程的妙用与陷阱:异步编程的「双刃剑」

1. 协程失控:未正确停止

​常见问题​​:
  • 协程未停止导致逻辑重复执行(如角色死亡后仍播放动画)
  • 跨场景协程未销毁(如从场景A到B,场景A的协程仍在运行)
​解决方案​​:
  • ​协程生命周期绑定​​:
    public class CoroutineManager : MonoBehaviour {
        private List<Coroutine> activeCoroutines = new();
    
        public Coroutine StartManagedCoroutine(IEnumerator routine) {
            var co = StartCoroutine(routine);
            activeCoroutines.Add(co);
            return co;
        }
    
        void OnDestroy() {
            foreach (var co in activeCoroutines) {
                StopCoroutine(co);
            }
            activeCoroutines.Clear();
        }
    }
    
    // 使用示例
    CoroutineManager manager = FindObjectOfType<CoroutineManager>();
    manager.StartManagedCoroutine(PlayAnimation());
  • ​鸿蒙跨场景协程同步​​:
    // 鸿蒙端场景切换时暂停协程(ArkTS)
    import router from '@ohos.router';
    
    export default {
      onSceneLeave() {
        this.coroutineController.pause(); // 暂停当前协程
      },
    
      onSceneEnter() {
        this.coroutineController.resume(); // 恢复协程
      }
    }

2. 协程性能优化:避免过度使用

​常见问题​​:
  • 大量短时间协程导致GC频繁(每帧创建/销毁10+协程)
  • 协程内复杂计算阻塞主线程(如大量数学运算)
​解决方案​​:
  • ​协程池技术​​:复用已停止的协程
    public class CoroutinePool {
        private Queue<IEnumerator> pool = new();
    
        public Coroutine GetCoroutine(IEnumerator routine) {
            if (pool.Count > 0) {
                var co = pool.Dequeue();
                co.Current = routine.Current; // 复用状态
                return StartCoroutine(co);
            }
            return StartCoroutine(routine);
        }
    
        public void ReturnCoroutine(Coroutine co) {
            pool.Enqueue(co);
        }
    }
  • ​异步任务拆分​​:将耗时操作移至Job System(结合DOTS)

四、面向数据的技术栈(DOTS):鸿蒙多端并行的「新范式」

1. DOTS入门:ECS架构的「水土不服」

​常见问题​​:
  • ECS组件与MonoBehaviour逻辑冲突(如MonoBehaviour.Update()System更新顺序)
  • 鸿蒙多端数据同步复杂度高(实体状态需跨设备一致)
​解决方案​​:
  • ​混合架构过渡​​:核心逻辑用ECS,UI/工具用MonoBehaviour
    // 混合架构示例(C#)
    [UpdateInGroup(typeof(InitializationSystemGroup))]
    public partial class PlayerInitSystem : SystemBase {
        protected override void OnCreate() {
            RequireForUpdate<PlayerTag>(); // 仅处理带PlayerTag的实体
        }
    
        protected override void OnUpdate() {
            Entities.ForEach((ref PlayerData data) => {
                data.health = 100; // 初始化玩家数据
            }).Run();
        }
    }
  • ​鸿蒙分布式ECS同步​​:使用DistributedEntity组件同步状态
    // 鸿蒙端分布式实体同步(ArkTS)
    import distributedEntity from '@ohos.distributedEntity';
    
    export default {
      start() {
        this.entity = distributedEntity.createEntity({
          type: 'Player',
          properties: {
            position: { x: 0, y: 0, z: 0 },
            health: 100
          }
        });
      },
    
      updatePosition(pos: Vec3) {
        this.entity.updateProperty('position', pos); // 同步位置到所有设备
      }
    }

2. Job System与Burst编译:性能飞跃的关键

​常见问题​​:
  • Job依赖关系错误(如A Job未完成时B Job读取数据)
  • Burst编译失败(因使用不支持的C#特性)
​解决方案​​:
  • ​依赖可视化工具​​:
    [UpdateAfter(typeof(MovementSystem))]
    public partial class AnimationSystem : SystemBase {
        protected override void OnUpdate() {
            // 仅当MovementSystem完成后执行
        }
    }
  • ​Burst兼容性检查​​:
    using Unity.Burst;
    using Unity.Jobs;
    
    [BurstCompile]
    public struct SpeedJob : IJobParallelFor {
        public NativeArray<float> Velocities;
        public void Execute(int i) {
            Velocities[i] *= 1.1f; // 简单计算,Burst可高效编译
        }
    }

五、鸿蒙5+跨端适配策略

1. 分布式脚本同步

​场景​​:手机端修改玩家属性,智慧屏实时更新
​实现方案​​:

// 鸿蒙分布式属性同步(ArkTS)
import distributedData from '@ohos.distributedData';

export default {
  syncPlayerHealth(health: number) {
    const data = { health: health };
    distributedData.put({
      key: 'player_health',
      value: JSON.stringify(data),
      replication: 'sync'
    });
  },

  onHealthChanged(callback: (health: number) => void) {
    distributedData.on('dataChanged', (key) => {
      if (key === 'player_health') {
        const data = JSON.parse(distributedData.get(key));
        callback(data.health);
      }
    });
  }
}

2. 多端性能分级

​策略​​:

  • ​手机端​​:减少ECS实体数量,使用轻量级MonoBehaviour逻辑
  • ​智慧屏端​​:启用完整ECS架构,利用Burst编译提升计算效率
// 设备性能自适应(C#)
void AdjustForDevice() {
    DeviceInfo device = DeviceInfo.Current;
    if (device.type == DeviceType.Phone) {
        // 手机端:减少系统组更新频率
        playerSystem.updateRate = 30;
    } else if (device.type == DeviceType.SmartScreen) {
        // 智慧屏端:提升系统组更新频率
        playerSystem.updateRate = 60;
    }
}

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐