UVP 价值专家 | 主程必杀技:Unity C# 代码整洁之道(四)

文摘   游戏   2024-06-07 18:48   上海  
这篇文章来自 2024 年度 Unity 价值专家提名人选 Laugh。Laugh 专注游戏研发及管理 15 年,目前在巨人网络担任技术负责人、专家,负责 MMO 类型的架构设计、产线设计及团队建设。他在 Unity 官方开发者社区发布了“主程必杀技”系列文章,探索使用 Source Generator 和 Analyzer 在 Unity 中实现代码规范框架要求的方式,帮助主程更高效地进行技术管理。
点击阅读原文,可以访问 Laugh 在 Unity 官方开发者社区的个人主页,阅读更多技术干货。

在本系列的前三篇文章中,我们通过开发 Analyzer 尝试了强制使用大括号包括逻辑分支 (if...else...) 的写法,并实现了强制加注释、自动化执行命名规范检查等要求,不符合要求的代码会直接报 Error 且无法编译,方便主程进行代码规范管理和框架要求,而不再需要花费大量时间 Review。

本文为大家带来 Laugh 的“主程必杀技”系列文章第四篇《Unity C# 自动化执行代码规范-检查继承关系更多技术分享,请访问 Laugh 的 Unity 官方开发者社区主页,持续关注学习。

演示代码

获取链接:https://gitee.com/palang/unity-sharp-code-regulator.git

C# Code Analyzer 入门系列文章
 Analyzer 初体验:强制使用大括号包括逻辑分支 (if...else...)
 强制加注释(上)类注释 | 强制加注释(下)方法注释
 命名规范检查(一)命名空间和类名
 命名规范检查(二)public 和 private 方法名称规范检查
 命名规范检查(三)成员变量命名规范检查
 命名规范检查(四)临时变量命名检查

 检查继承关系

Unity C# 自动化执行代码规范

检查继承关系

Unity 中,很多类,我们不希望继承自 MonoBehaviour(主要考虑到性能和自己控制生命周期)。比如我们的 MVC 中的 Controller,或者战斗系统中的 SkillLogic,希望脱离 Unity 的 Monobehavior 及其生命周期的控制,通常会自定义帧驱动逻辑。
看本篇文章之前,请先阅读第一篇教程,环境准备工作也与前几篇保持一致。

实现目标

为了实现自己控制的帧驱动逻辑,我在项目定义了 IFrameDrivable 接口(其中有OnFrameUpdate 等方法,不是这里的重点,可以忽略)。

我希望实现 IFrameDrivable 的类,不能继承自 MonoBehaviour 类。

编写 Analyzer 分析器

首先在类 DianogsticIDs 中定义新的错误 ID:

public const string FORCE_NAMING_CONVENTIONS_ID= "FERR1004";
然后在 AnalyzerReleases.Unshipped.md 文件添加该规则,如下:

定义分析器类

新建分析器类 ForbidInheritFromMonoBehavor.cs,内容如下(具体步骤请看代码中的注释)

using Microsoft.CodeAnalysis.Diagnostics;using Microsoft.CodeAnalysis;using System;using System.Collections.Generic;using System.Text;using System.Collections.Immutable;using Analyzer;using Microsoft.CodeAnalysis.CSharp.Syntax;using System.Linq;
namespace CdeAnalyzer{ /** * Author: Laugh(笑微) * https://developer.unity.cn/projects/65937455edbc2a001cbd8102 */ [DiagnosticAnalyzer(LanguageNames.CSharp)] internal class ForbidInheritFromMonoBehavor : DiagnosticAnalyzer { /// <summary> /// 错误描述 /// </summary> private static readonly DiagnosticDescriptor ForbidInheritDescriptor = new DiagnosticDescriptor( DianogsticIDs.FORCE_FORBID_INHERIT_MONOBEHAVIOR_ID, // ID "实现了IFrameDrivable接口的类,不能继承自MonoBehavior", // Title "实现了IFrameDrivable接口的类,不能继承自MonoBehavior", // Message format DiagnosticCategories.Criterion, // Category DiagnosticSeverity.Error, // Severity isEnabledByDefault: true // Enabled by default ); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(ForbidInheritDescriptor);
/// <summary> /// 初始化分析方法 /// </summary> /// <param name="context"></param> public override void Initialize(AnalysisContext context) { context.RegisterSyntaxTreeAction(AnalyzeSymbol); } /// <summary> /// 检测方法 /// </summary> /// <param name="context"></param> private static void AnalyzeSymbol(SyntaxTreeAnalysisContext context) { //找到文档的语法根树 var root = context.Tree.GetRoot(context.CancellationToken); if (ConstraintDefinition.ExcludeAnalize(context.Tree.FilePath)) {//排除特殊目录 return; }
var classList = root.DescendantNodes()?.OfType<ClassDeclarationSyntax>(); foreach(var cls in classList) {//遍历语法树中的所有类 var baseClsList = cls.BaseList?.ChildNodes()?.OfType<SimpleBaseTypeSyntax>(); if (baseClsList == null) { break; } bool isMonoBehavior = false; bool isFrameDrivable = false; foreach (var bcls in baseClsList) { var idName = bcls.ChildNodes()?.OfType<IdentifierNameSyntax>()?.First(); var bname = idName.ToString(); if(bname == "IFrameDrivable") {//检查是否实现了接口IFrameDrivable isFrameDrivable = true; } else if (bname == "ArrayList") {//检查是否继承自MonoBehavior isMonoBehavior = true; } } if (isFrameDrivable && isMonoBehavior) { //报错 var diagnostic = Diagnostic.Create(ForbidInheritDescriptor, cls.GetFirstToken().GetLocation()); context.ReportDiagnostic(diagnostic); } }
} }}


Laugh 是 2024 年度 Unity 价值专家提名人选。Unity 价值专家(UVP)是通过原创作品启发国内创作者的 Unity 专业人员,点击这里提名/自荐
长按关注
Unity 官方开发者服务平台
第一时间了解 Unity 社区动向,学习开发技巧

 点击“阅读原文”,访问 Laugh 的社区主页 



Unity官方开发者服务平台
Unity引擎官方开发者服务平台,分享技术干货、学习课程、产品信息、前沿案例、活动资讯、直播信息等内容。
 最新文章