这篇文章来自 人选 Laugh。Laugh 专注游戏研发及管理 15 年,目前在巨人网络担任技术负责人、专家,负责 MMO 类型的架构设计、产线设计及团队建设。他在 Unity 官方开发者社区发布了“主程必杀技”系列文章,探索使用 Source Generator 和 Analyzer 在 Unity 中实现代码规范框架要求的方式,帮助主程更高效地进行技术管理。

点击阅读原文,可以访问 Laugh 在 Unity 官方开发者社区的个人主页,阅读更多技术干货。

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

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

https://developer.unity.cn/u/xiao-wei-58?tab=article

演示代码

获取链接:

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
{
///

/// 错误描述
///
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 SupportedDiagnostics => ImmutableArray.Create(ForbidInheritDescriptor);

///

/// 初始化分析方法
///
///
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeAction(AnalyzeSymbol);
}
///

/// 检测方法
///
///
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 ();
foreach(var cls in classList)
{//遍历语法树中的所有类
var baseClsList = cls.BaseList?.ChildNodes()?.OfType ();
if (baseClsList == null)
{
break;
}
bool isMonoBehavior = false;
bool isFrameDrivable = false;
foreach (var bcls in baseClsList)
{
var idName = bcls.ChildNodes()?.OfType ()?.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 是 人选。Unity 价值专家(UVP)是通过原创作品启发国内创作者的 Unity 专业人员,欢迎提名/自荐。

https://unity.cn/uvp

阔别重逢,终于回来了!

7月23日-25日,Unite 将于上海隆重开启。1 场 Keynote 和多个专场,涵盖团结引擎、游戏生态、数字孪生、智能座舱等多重赛道,我们还准备了小而美的工作坊,覆盖小游戏、开源鸿蒙、Vision Pro三大主题。

走过路过别错过。

扫码购票处

Unity 官方微信

第一时间了解Unity引擎动向,学习进阶开发技能