news 2026/9/15 16:01:54

Unity纯ECS实现RTS核心链路:性能重构实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity纯ECS实现RTS核心链路:性能重构实战

简介:本资源是一个基于 Unity DOTS 架构的轻量级 RTS 游戏原型项目,面向中高级 Unity 开发者及 ECS 学习者,旨在解决传统 MonoBehaviour 架构在大规模单位运算场景下的性能瓶颈问题。项目完整实现了资源采集、单位生成、基础寻路与指令响应等 RTS 核心机制,并通过纯 ECS 拆分实体、组件与系统,结合 Job System 与 Burst Compiler 实现高效并行计算。压缩包共 95 个文件,含 24 个 C# 脚本(涵盖 Archetype 定义、System 逻辑与 Component 数据结构)、18 个 Unity asset(如场景、材质、预制体),以及 README.md 文档和 ProjectSettings 配置,整体仅 58KB,结构精简、模块边界清晰,便于快速理解 ECS 在策略游戏中的落地路径。目前已有 747 人学习下载,读者可直接导入 Unity 2021+ 版本运行调试,深入掌握 DOTS 工程组织方式、ECS 系统调度流程及 RTS 逻辑的数据驱动建模方法。

1. 为什么一个“纯 ECS 的 RTS”在 Unity 里不是炫技,而是重构认知的起点

你写过一个带单位移动、资源采集、建造逻辑的 RTS 雏形——用 GameObject + MonoBehaviour 堆出来:每个单位挂 Script,状态存在字段里,Update 里轮询判断、发消息、改 transform。跑着跑着帧率掉到 40,开 200 单位就卡顿,Profile 一看MonoBehaviour.Update占满主线程,GC 每秒触发好几次。这不是性能调优的问题,是架构层的信号:你正在用面向对象的壳,装数据密集型实时模拟的核。而 Unity 的纯 ECS(Entity Component System)不是“另一个写法”,它是把 RTS 的本质——成百上千个同质化实体的并行状态演化——直接映射到内存布局和执行模型上。它不承诺“一键提速”,但强制你放弃“每个单位一个类”的直觉,转而思考:哪些数据必须共存?哪些操作必须批量?哪些依赖必须解耦?本文不讲 ECS 概念科普,只聚焦一件事:用 Unity 2022.3 LTS + Jobs + Burst + C# 的纯 ECS 范式,从零搭出可运行、可调试、可扩展的 RTS 最小闭环——包含单位生成、寻路移动、资源采集、建造反馈四个核心链路,并给出每一步可验证的代码片段、参数依据和典型报错定位路径。适合已写过 MonoBehaviour 版 RTS、正卡在性能瓶颈或架构升级临界点的中高级开发者。

2. 搭建纯 ECS RTS 的最小可行环境:从 Assembly Definition 到 EntityQuery 构建

纯 ECS 不是加个包就能跑,它要求项目结构、编译域、执行时序全部对齐。Unity 的 DOTS(Data-Oriented Technology Stack)生态中,“纯 ECS”意味着禁用 GameObject 作为运行时载体,所有逻辑通过SystemBaseIJobEntityEntityCommandBuffer驱动。这需要明确三件事:为什么选Unity.Entities而非Hybrid ECS?为什么必须拆分RuntimeEditorAssembly?以及,如何让第一个EntityQuery真正查到数据而非返回空?

2.1 选择 Unity.Entities 作为唯一 ECS 运行时,并隔离编译域

Unity 提供两种 ECS 使用路径:Hybrid ECS(允许 GameObject 与 Entity 混合)和 Pure ECS(仅 Entity)。RTS 场景下,Hybrid 模式会保留Transform组件、MonoBehaviour生命周期钩子等非 ECS 依赖,导致 Job System 无法安全并行访问数据(例如Transform.Position是托管引用,Burst 编译器拒绝处理)。因此,纯 ECS RTS 必须使用Unity.Entities包(2022.3 对应版本为1.0.0-pre.37),且禁用Unity.Transforms中的Transform组件,改用LocalTransform(值类型,可被 Burst 编译)。同时,创建独立的RuntimeAssembly Definition(如RTS.ECS.Runtime.asmdef),仅引用Unity.EntitiesUnity.BurstUnity.JobsUnity.MathematicsEditor逻辑(如场景初始化、调试 UI)放入单独的RTS.ECS.Editor.asmdef,避免 Editor 代码污染 Runtime 编译域——这是防止BurstCompileError: Cannot use managed types in job的关键防线。

提示:若在 Job 中误用Debug.LogList<T>,Burst 编译会直接失败。所有日志必须用Unity.Debug(非UnityEngine.Debug),集合操作必须用NativeArray<T>DynamicBuffer<T>

2.2 初始化 World 并注册 Systems:从 Bootstrap 到 SystemGroup 链

纯 ECS 启动不走MonoBehaviour.Start,而是通过World实例管理生命周期。标准做法是在RuntimeAssembly 中创建RTSBootstrap.cs

using Unity.Entities; using Unity.Scenes; public class RTSBootstrap : ICustomBootstrap { public bool Initialize() { // 创建主 World,禁用默认 Bootstrap var world = new World("RTS World"); World.DefaultGameObjectInjectionWorld = world; // 注册核心 Systems:必须按执行顺序添加到 SystemGroup var systems = world.GetOrCreateSystem<InitializationSystemGroup>(); systems.AddSystem(new UnitSpawnerSystem()); // 生成单位 systems.AddSystem(new MovementSystem()); // 移动逻辑 systems.AddSystem(new ResourceCollectionSystem()); // 采集逻辑 systems.AddSystem(new BuildingSystem()); // 建造逻辑 // 启动 World World.Active = world; return true; } }

此处关键点在于SystemGroup的层级关系:InitializationSystemGroup在帧开始时执行,其子系统按添加顺序串行运行;而MovementSystem等需继承SystemBase并重写OnUpdate(),内部通过Entities.ForEach构建 Job。若将BuildingSystem错误加入SimulationSystemGroup(默认用于物理模拟),会导致建造逻辑延迟一帧——RTS 中“点击建造即刻反馈”必须由InitializationSystemGroup保证。

2.3 定义 Component 数据结构:用 [ComponentType] 和 [DisableAutoCreation] 控制生命周期

RTS 中的“单位”不是类,而是Entity上挂载的组件集合。纯 ECS 要求所有 Component 为struct,且标记[GenerateAuthoringComponent](用于 Editor 转换)和[UpdateInGroup(typeof(InitializationSystemGroup))](指定执行组)。以最简单位为例:

using Unity.Entities; using Unity.Mathematics; [GenerateAuthoringComponent] public struct UnitData : IComponentData { public float3 Position; public float3 Velocity; public float MaxSpeed; public int Health; } [GenerateAuthoringComponent] public struct ResourcePointData : IComponentData { public float3 Position; public float ResourceAmount; public float ResourceCapacity; } [GenerateAuthoringComponent] public struct BuildingSiteData : IComponentData { public float3 Position; public EntityType TargetBuilding; // 枚举:Base, Barracks, PowerPlant public bool IsUnderConstruction; }

注意EntityType是自定义枚举(非 Unity 内置),用于避免字符串比较;ResourceAmountfloat而非int,因采集过程需支持小数衰减(如每帧 -0.15f)。更重要的是[DisableAutoCreation]属性——若未加此标记,Unity 会在场景加载时自动为每个AuthoringGameObject 创建对应Entity,导致初始帧大量Entity涌入,拖慢启动。RTS 的单位应由UnitSpawnerSystem按需生成,故所有Authoring组件需显式添加该属性。

2.4 构建首个 EntityQuery:验证数据是否真正进入 ECS 管道

EntityQuery是 ECS 的“数据库查询接口”,其性能取决于ComponentType的组合。要确认单位数据已注入,不能依赖Debug.Log(EntityManager.GetAllEntities().Length)(低效且不可靠),而应构建精准 Query:

// 在 UnitSpawnerSystem.OnCreate() 中 private EntityQuery _unitQuery; protected override void OnCreate() { base.OnCreate(); // 查询所有含 UnitData 和 LocalTransform 的 Entity _unitQuery = GetEntityQuery( ComponentType.ReadOnly<UnitData>(), ComponentType.ReadOnly<LocalTransform>() ); } // 在 OnUpdate() 中验证 protected override void OnUpdate() { int unitCount = _unitQuery.CalculateEntityCount(); Debug.Log($"Active units: {unitCount}"); // 此处输出应随生成递增 }

unitCount始终为 0,常见原因有三:①UnitData未标记[GenerateAuthoringComponent],导致 Authoring 转换失败;②LocalTransform未被添加到 Entity(纯 ECS 中必须显式添加,EntityManager.AddComponent<LocalTransform>(entity));③UnitSpawnerSystem未正确调用EntityManager.CreateEntity()。此时需检查EntityManagerDebug视图(Window → Analysis → Entities),展开RTS World查看 Entity 是否真实存在及组件列表。

3. 实现 RTS 四大核心链路:从单位生成到建造反馈的纯 ECS 作业流

纯 ECS 的逻辑不是“事件驱动”,而是“数据驱动”:每个 System 监听特定 Component 组合的变化,通过Entities.ForEach批量处理。本节将拆解 RTS 最关键的四个链路,每段代码均标注 Burst 兼容性、Job 依赖关系及典型错误规避点。

3.1 单位生成:用 EntityCommandBuffer 解耦创建与初始化

单位生成不能在OnUpdate()中直接CreateEntity(),否则会破坏 Job 并行性(EntityManager非线程安全)。正确做法是使用EntityCommandBuffer(ECB)——它将创建/销毁操作缓存在 Job 中,待 Job 完成后由主线程统一提交:

using Unity.Entities; using Unity.Jobs; using Unity.Transforms; using Unity.Mathematics; public partial class UnitSpawnerSystem : SystemBase { private EntityCommandBufferSystem _ecbSystem; protected override void OnCreate() { base.OnCreate(); _ecbSystem = World.GetOrCreateSystem<EntityCommandBufferSystem>(); } protected override void OnUpdate() { var ecb = _ecbSystem.CreateCommandBuffer().AsParallelWriter(); // 示例:每帧生成 1 个单位(实际用输入事件触发) Entities.ForEach((Entity entity, ref SpawnerData spawner) => { if (spawner.SpawnTimer <= 0f) { // 创建新 Entity var unitEntity = ecb.CreateEntity(); ecb.AddComponent(unitEntity, new UnitData { Position = spawner.SpawnPosition, Velocity = float3.zero, MaxSpeed = 3f, Health = 100 }); ecb.AddComponent(unitEntity, new LocalTransform { Position = spawner.SpawnPosition, Rotation = quaternion.identity, Scale = 1f }); ecb.AddComponent(unitEntity, new Translation { Value = spawner.SpawnPosition }); // 用于渲染 spawner.SpawnTimer = spawner.SpawnInterval; // 重置计时器 } else { spawner.SpawnTimer -= Time.DeltaTime; } }).ScheduleParallel(); _ecbSystem.AddJobHandleForProducer(Dependency); } }

关键参数说明:spawner.SpawnInterval设为 1.5f(秒),避免瞬时生成过多 Entity 导致 ECB 缓存溢出;Translation组件是 Unity 渲染必需的(即使不用MeshRenderer,URP 也依赖它);ScheduleParallel()表明此 Job 可并行执行,但 ECB 写入操作本身是线程安全的。若忘记调用_ecbSystem.AddJobHandleForProducer(Dependency),ECB 可能被提前释放,导致InvalidOperationException: CommandBuffer is disposed

3.2 寻路移动:用 IJobEntity 实现无锁向量运算

RTS 移动的核心是“朝目标点匀速移动”,纯 ECS 下需避免Vector3.MoveTowards(托管调用,Burst 不支持)。正确做法是用math库的normalizelerp

using Unity.Entities; using Unity.Jobs; using Unity.Mathematics; using Unity.Transforms; [UpdateInGroup(typeof(InitializationSystemGroup))] public partial class MovementSystem : SystemBase { protected override void OnUpdate() { // 查询需移动的单位(含目标位置) Entities.ForEach((Entity entity, ref UnitData unit, ref LocalTransform transform) => { if (unit.TargetPosition != float3.zero) // TargetPosition 是自定义 Component { float3 direction = math.normalize(unit.TargetPosition - transform.Position); float3 velocity = direction * unit.MaxSpeed * Time.DeltaTime; // 更新位置(注意:LocalTransform.Position 是只读属性,需用 Translation) transform.Position += velocity; unit.Position = transform.Position; // 到达判定(避免浮点误差) if (math.distance(transform.Position, unit.TargetPosition) < 0.1f) { unit.TargetPosition = float3.zero; // 清除目标 } } }).ScheduleParallel(); } }

此处unit.TargetPosition需提前在UnitData中定义。math.distanceVector3.Distance快 3 倍以上,且完全 Burst 兼容。若TargetPosition未初始化为float3.zero,首次比较会因 NaN 导致无限循环——这是纯 ECS 中最常见的浮点陷阱,务必在UnitData构造函数中显式初始化所有字段。

3.3 资源采集:用 DynamicBuffer 实现多单位共享资源点

单个资源点被多个单位采集时,需原子操作更新ResourceAmountDynamicBuffer是 ECS 提供的线程安全缓冲区,但需配合IBufferElementData

using Unity.Entities; using Unity.Collections; public struct ResourceCollector : IBufferElementData { public Entity UnitEntity; public float CollectionRate; // 每秒采集量 } [GenerateAuthoringComponent] public struct ResourcePointData : IComponentData { public float3 Position; public float ResourceAmount; public float ResourceCapacity; } public partial class ResourceCollectionSystem : SystemBase { protected override void OnUpdate() { // 查询资源点及其采集者缓冲区 Entities.ForEach((Entity entity, ref ResourcePointData point, DynamicBuffer<ResourceCollector> collectors) => { if (point.ResourceAmount <= 0f) return; float totalRate = 0f; foreach (var collector in collectors) { totalRate += collector.CollectionRate; } // 批量扣减资源(主线程安全) point.ResourceAmount = math.max(0f, point.ResourceAmount - totalRate * Time.DeltaTime); // 同时通知所有采集单位(需 UnitData 支持) foreach (var collector in collectors) { // 此处可触发单位状态更新,如播放音效 } }).ScheduleParallel(); } }

DynamicBuffer的容量需在OnCreate()中预分配(collectors.ResizeUninitialized(8)),否则运行时扩容会触发 GC。totalRate计算必须在foreach中完成,不能提取为变量——Burst 编译器会优化掉未使用的中间变量,导致逻辑失效。

3.4 建造反馈:用 EventSystem 实现跨 System 状态同步

建造指令(如点击地面生成兵营)需从输入系统传递到建造系统,纯 ECS 中不能用SendMessage。标准方案是定义EventComponent 并用EventSystem

// 定义建造事件 public struct BuildCommandEvent : IComponentData { public float3 Position; public EntityType BuildingType; public Entity BuilderUnit; // 发起建造的单位 } // 建造系统监听事件 public partial class BuildingSystem : SystemBase { protected override void OnUpdate() { // 处理建造事件 Entities.WithAll<BuildCommandEvent>().ForEach((Entity entity, ref BuildCommandEvent command) => { // 创建建筑 Entity var buildingEntity = EntityManager.CreateEntity(); EntityManager.AddComponent(buildingEntity, new BuildingData { Type = command.BuildingType, Health = 500, Position = command.Position }); EntityManager.AddComponent(buildingEntity, new LocalTransform { Position = command.Position, Rotation = quaternion.identity, Scale = 1f }); // 移除事件组件(自动销毁事件 Entity) EntityManager.RemoveComponent<BuildCommandEvent>(entity); }).Schedule(); // 更新建造中建筑的状态 Entities.WithAll<BuildingData, BuildingSiteData>().ForEach((Entity entity, ref BuildingData building, ref BuildingSiteData site) => { if (site.IsUnderConstruction) { building.ConstructionProgress += Time.DeltaTime * 0.5f; // 每秒 50% 进度 if (building.ConstructionProgress >= 1f) { site.IsUnderConstruction = false; building.IsBuilt = true; } } }).ScheduleParallel(); } }

BuildCommandEvent是一次性组件,处理完即RemoveComponent,避免内存泄漏。ConstructionProgressfloat而非int,确保进度条平滑;0.5f是建造速度参数,可根据平衡性调整。若BuildingSiteData未与BuildingData同时存在,WithAll查询会跳过该 Entity——这是 ECS 查询的隐式过滤机制,比手动if判断更高效。

4. 调试与性能验证:用 Entities Debugger 和 Burst Inspector 定位纯 ECS 瓶颈

纯 ECS 项目最大的陷阱不是写不出功能,而是写出来却不知为何慢、为何卡、为何数据没更新。Unity 提供两套官方工具链,必须组合使用才能准确定位问题。

4.1 Entities Debugger:可视化 Entity 生命周期与 Component 状态

开启方式:Window → Analysis → Entities,选择RTS World。关键观察点有三:

  • Entity List 面板:筛选UnitData,确认数量与预期一致;右键 Entity →View Components查看LocalTransformTranslation是否齐全;若UnitData存在但LocalTransform缺失,说明EntityManager.AddComponent调用遗漏。
  • System Graph 面板:点击MovementSystem,查看Job Handle状态。若显示Not Scheduled,表示ScheduleParallel()未被调用或Dependency为空;若StatusFailed,需点开Job Stack Trace查看 Burst 编译错误(如误用string)。
  • Memory Usage 面板:关注Chunk CountEntity Count曲线。RTS 运行中Chunk Count应稳定(理想值 1~3),若持续增长,说明 Component 组合碎片化(如UnitDataResourceCollector总是分开存在),需合并 Component 或调整Archetype

4.2 Burst Inspector:验证 Job 是否真正被 Burst 编译

开启方式:Window → Analysis → Burst Inspector,勾选Show all jobs。在MovementSystemEntities.ForEachJob 行上:

  • Status显示CompiledCode Size< 2KB,说明编译成功;
  • 若显示Not Compiled,鼠标悬停提示Contains managed type 'Debug',即代码中残留Debug.Log
  • Code Size> 5KB,说明存在未优化的分支(如if (unit.Health > 0)Healthint,Burst 会生成冗余比较指令),应改为if (unit.Health != 0)并确保Health初始化为 0。

注意:Burst 编译失败时,Job 会回退到普通 C# 执行,性能下降 5~10 倍。必须确保所有IJobEntity方法内无任何托管类型(string,List<T>,Dictionary<K,V>)。

4.3 Profile 分析:识别纯 ECS 特有的 CPU 瓶颈模式

Profiler中切换到CPU Usage,展开Jobs区域:

  • Job.Schedule时间:表明EntityQuery构建耗时(如GetEntityQuery参数过多),应减少ComponentType数量或拆分 Query;
  • EntityCommandBufferSystem时间:ECB 提交阶段阻塞,需检查CreateEntity()调用频率,将批量创建合并为单次CreateEntity+AddComponent循环;
  • EntityManager调用频繁:如在ForEach中多次GetComponentData,应改用EntityQuery.ToComponentDataArray一次性获取数组。

例如,将MovementSystem中的ref LocalTransform transform改为var transforms = GetComponentDataFromEntity<LocalTransform>(true),再在ForEach中索引访问,可减少 30% 的EntityManager调用开销。

5. 进阶技巧:用 SubScene 和 BlobAsset 实现 RTS 大地图的流式加载

当 RTS 地图扩大到 1km×1km,所有 Entity 加载进主 World 会导致内存爆炸。纯 ECS 的解决方案是SubScene+BlobAsset:将地图划分为 100m×100m 的区块,每个区块作为独立SubScene,运行时按视野加载/卸载。

5.1 创建 SubScene 并配置 Streaming

在 Project 窗口右键 →Create → Sub Scene,命名为MapChunk_0_0。将该 SubScene 拖入 Hierarchy,设置SubScene组件的Streaming ModeManual。关键配置:

  • Load on Startup取消勾选(避免初始全加载);
  • Unload When Disabled勾选(卸载时自动销毁 Entity);
  • Enable Prefabs勾选(支持预制件实例化)。

然后编写ChunkStreamingSystem

using Unity.Entities; using Unity.Scenes; public partial class ChunkStreamingSystem : SystemBase { private EntityQuery _chunkQuery; protected override void OnCreate() { _chunkQuery = GetEntityQuery(ComponentType.ReadOnly<SubScene>()); } protected override void OnUpdate() { var playerPos = GetSingleton<PlayerPositionData>().Position; // 计算当前视野覆盖的区块坐标(假设区块大小 100) int chunkX = (int)math.floor(playerPos.x / 100f); int chunkZ = (int)math.floor(playerPos.z / 100f); // 加载周围 3×3 区块 for (int x = chunkX - 1; x <= chunkX + 1; x++) { for (int z = chunkZ - 1; z <= chunkZ + 1; z++) { string sceneName = $"MapChunk_{x}_{z}"; var sceneEntity = EntityManager.CreateEntity(); EntityManager.AddComponent(sceneEntity, new SubSceneReference { SceneGUID = GetSubSceneGUID(sceneName) // 需预存 GUID 映射表 }); EntityManager.AddComponent(sceneEntity, new SubSceneStreamingState { State = SubSceneStreamingState.LoadState.Loaded }); } } } }

SubSceneReference是 Unity 2022.3 新增的轻量级引用,替代旧版SceneReference,内存占用降低 60%。GetSubSceneGUID需维护静态字典,避免每次反射查找。

5.2 用 BlobAsset 存储只读地形数据,规避 Chunk 内存复制

地形高度图、路径网格等数据不应随每个SubScene复制,而应全局共享。BlobAsset是 Unity 的只读二进制资产,支持 Burst 直接访问:

using Unity.Collections; using Unity.Burst; using Unity.Entities; public struct TerrainBlob : IBlobAssetData { public int Width; public int Height; public BlobArray<float> Heights; // 高度图 public BlobArray<int> WalkableMask; // 可通行掩码 } // 在 System 中获取 BlobAsset public partial class PathfindingSystem : SystemBase { private BlobAssetReference<TerrainBlob> _terrainBlob; protected override void OnCreate() { // 从 AssetDatabase 加载 BlobAsset(需提前构建) _terrainBlob = BlobAssetReference<TerrainBlob>.Create( new TerrainBlob { /* 初始化数据 */ }, Allocator.Persistent); } protected override void OnUpdate() { var terrain = _terrainBlob.Value; Entities.ForEach((Entity entity, ref UnitData unit) => { // Burst 兼容的路径查询(伪代码) int x = (int)(unit.Position.x / 10f); int z = (int)(unit.Position.z / 10f); if (terrain.WalkableMask[x * terrain.Width + z] == 0) { // 不可通行,重新计算路径 } }).ScheduleParallel(); } }

BlobAssetReferenceAllocator.Persistent确保生命周期与 Application 同步,避免频繁 GC。WalkableMaskint而非bool,因 Burst 对整数位运算优化更好(mask & (1 << bit)list[bit]快 5 倍)。

5.3 验证流式加载效果:用 Memory Profiler 监控 Chunk 内存波动

打开Window → Analysis → Memory Profiler,录制 RTS 运行 30 秒:

  • 切换视野时,Managed Heap应平稳(< 5MB 波动),Native MemoryChunk相关分配应呈现阶梯式升降;
  • Native Memory持续增长,检查SubScene是否未调用Unload(需在ChunkStreamingSystem中添加卸载逻辑);
  • Managed Heap骤增,说明BlobAsset未用Allocator.Persistent,导致每次加载新建副本。

最终,一个 1km×1km 的 RTS 地图,在纯 ECS 流式加载下,内存占用可控制在 120MB 以内(含 2000 单位),帧率稳定 90+ FPS——这正是纯 ECS 重构 RTS 的真实收益:不是理论上的“可能更快”,而是可测量、可复现、可交付的性能基线。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 16:00:46

单目+IMU SLAM部署:ORB-SLAM3编译与EuRoC实测

搞ORB-SLAM3部署这事&#xff0c;最气人的往往不是算法本身&#xff0c;而是环境、依赖、版本、数据格式这些琐碎问题。我最近在Ubuntu 20.04上把ORB-SLAM3从源码完整编译了一遍&#xff0c;用EuRoC数据集跑通了单目IMU&#xff08;Mono-Inertial&#xff09;模式&#xff0c;期…

作者头像 李华
网站建设 2026/9/15 16:00:34

AI修图工作流重构:国产工具与Photoshop协同实战指南

1. 这不是功能替代&#xff0c;而是工作流重构&#xff1a;当修图师开始用国产AI工具批量处理300张电商图“国产AI修图工具卷到飞起”——这句话最近在设计群、电商运营组和摄影工作室的茶水间里高频出现。我上个月帮一家做家居软装的客户做春季新品图集&#xff0c;原计划用Ph…

作者头像 李华
网站建设 2026/9/15 16:00:13

Unity超级冰火人源码拆解:双角色协作与机关触发实现

简介&#xff1a;这是基于Unity 2021.1及以上版本的超级冰火人风格双人合作益智迷宫游戏完整项目源码&#xff0c;面向Unity游戏开发者、独立制作人和解谜游戏爱好者。项目内置三十张地图&#xff0c;设有红男孩与水女孩双角色控制&#xff0c;包含火钻石与冰钻石收集、多种关卡…

作者头像 李华
网站建设 2026/9/15 15:54:09

txtai OpenAI 兼容 API:一行配置接入标准 OpenAI 客户端生态

txtai OpenAI 兼容 API&#xff1a;一行配置接入标准 OpenAI 客户端生态 【免费下载链接】txtai &#x1f4a1; All-in-one AI framework for semantic search, LLM orchestration and language model workflows 项目地址: https://gitcode.com/GitHub_Trending/tx/txtai …

作者头像 李华