Unity中如何基于属性自动添加预处理指令实现客户端/服务端代码剥离?
问题描述
我开发了一个极简的多人游戏网络代码框架,客户端与服务端代码共存于同一类中,类实例同时运行在服务端与客户端。这些类中需同步的属性可通过可配置的[SynchronizeOverNetwork]标记:
[SynchronizeOverNetwork(Authority = Authority.Server)] public ReconcileMoveData ReconcileMoveData { // Code should not appear on server, but on client get => new ReconcileMoveData { Position = transform.position, Rotation = transform.rotation.eulerAngles.z, LinearVelocity = _body.velocity, AngularVelocity = _body.angularVelocity, }; // Code should not appear on client, but on server set { transform.position = value.Position; transform.rotation = Quaternion.Euler(new Vector3(0, 0, value.Rotation)); _body.velocity = value.LinearVelocity; _body.angularVelocity = value.AngularVelocity; } }
在游戏初始化阶段我会识别这些属性并进行全局管理,根据Authority的设置(Authority.Server或Authority.Client)决定属性的get/set方法仅在服务端或客户端执行。为避免代码分析问题(服务端代码永不发布)并减小客户端代码体积,我希望在客户端构建时剥离服务端代码,服务端构建时剥离客户端代码。Unity提供了UNITY_SERVER预处理指令用于标记服务端专属代码,但手动为每个get/set添加该指令易出错、繁琐且冗余。请问是否可通过预处理构建步骤(或其他方式),基于属性配置自动添加这些指令(不修改源文件,仅处理编译前的代码),或通过其他方式标记代码以实现自动剥离?
解决方案
以下是几种可行的实现思路,按推荐优先级排序:
1. Roslyn代码分析器(编译阶段自动注入指令)
这是最精准且无侵入的方案,利用Roslyn的编译时语法树修改能力,完全不需要手动修改源文件:
- 编写一个自定义Roslyn分析器,扫描项目中带有
[SynchronizeOverNetwork]属性的属性成员; - 根据属性的
Authority参数,自动给对应的get/set块包裹预处理指令:- 当
Authority = Authority.Server时,给get块包裹#if !UNITY_SERVER/#endif,给set块包裹#if UNITY_SERVER/#endif; - 当
Authority = Authority.Client时,逻辑反过来,get块用服务端指令,set块用客户端指令;
- 当
- 分析器会在Unity编译代码时动态修改语法树,最终编译后的代码会自动包含这些指令,源文件完全不受影响。
2. 构建前代码生成工具
写一个轻量的C#工具,作为Unity构建的预处理步骤运行:
- 工具用Roslyn语法分析API遍历项目中目标代码文件,识别带有
[SynchronizeOverNetwork]标记的属性; - 在对应的get/set块前后插入预处理指令,生成临时的修改后代码文件;
- 配置Unity的
PreBuild事件(比如用[InitializeOnLoad]或者构建管线回调),在编译前触发这个工具,让Unity用临时文件进行编译,编译完成后自动清理临时文件; - 注意要给临时文件设置忽略规则,避免被版本控制工具捕获。
3. IL织入(编译后代码裁剪)
用IL织入工具(比如Mono.Cecil)在编译后直接修改IL代码,彻底移除不需要的代码:
- 编写一个PostBuild处理工具,扫描编译后的程序集,找到带有
[SynchronizeOverNetwork]属性的属性; - 根据当前构建目标(Server/Client),直接移除对应平台不需要的get或set方法的IL指令,甚至可以直接删除整个方法体;
- 通过Unity的
PostProcessBuild事件触发这个工具,确保构建完成后输出的程序集已经裁剪掉冗余代码。
4. 自定义宏简化手动标记(低成本替代方案)
如果不想搞复杂的工具链,可以定义一套简化的宏来减少手动标记的工作量和错误率:
- 在项目的
Assembly-CSharp.csproj或者全局宏定义中添加:#define SERVER_AUTH_GET #if !UNITY_SERVER #define SERVER_AUTH_GET_END #endif #define SERVER_AUTH_SET #if UNITY_SERVER #define SERVER_AUTH_SET_END #endif #define CLIENT_AUTH_GET #if UNITY_SERVER #define CLIENT_AUTH_GET_END #endif #define CLIENT_AUTH_SET #if !UNITY_SERVER #define CLIENT_AUTH_SET_END #endif - 然后在属性中直接使用这些宏:
[SynchronizeOverNetwork(Authority = Authority.Server)] public ReconcileMoveData ReconcileMoveData { SERVER_AUTH_GET get => new ReconcileMoveData { ... }; SERVER_AUTH_GET_END SERVER_AUTH_SET set { ... }; SERVER_AUTH_SET_END } - 这种方式比直接写原生#if指令简洁很多,也降低了手动写错的概率。
内容的提问来源于stack exchange,提问作者Sebastian Barth
相关产品推荐
相关产品推荐

