State Design Pattern

需求

制作一个游戏中玩家操控的角色的动画控制器

将以上状态变化用代码实现

  1. 需要定义一系列的状态

    1
    2
    3
    4
    5
    public const int Idle = 0;
    public const int Run = 1;
    public const int Attack = 2;
    public const int Rise = 3;
    public const int Fall = 4;

  2. 定义一个存储当前状态的变量

    1
    public int State = Idle;

  3. 根据当前的状态, 执行该动作对应的效果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    /// <summary>
    /// 动作:输入攻击指令
    /// </summary>
    public void InputAttackCommand()
    {
    if (State == Idle)
    {
    Console.WriteLine("播放Attack动画");
    State = Attack;
    }
    else if (State == Run)
    {
    Console.WriteLine("不能在奔跑时攻击");
    }
    else if (State == Attack)
    {
    Console.WriteLine("不能在攻击时攻击");
    }
    else if (State == Rise)
    {
    Console.WriteLine("不能在跳跃中攻击");
    }
    else if (State == Fall)
    {
    Console.WriteLine("不能在下落中攻击");
    }
    }

    /// <summary>
    /// 动作:攻击动作完成
    /// </summary>
    public void AttackActionComplete()
    {
    if (State == Idle)
    {
    Console.WriteLine("当前不处于攻击中");
    }
    else if (State == Run)
    {
    Console.WriteLine("当前不处于攻击中");
    }
    else if (State == Attack)
    {
    Console.WriteLine("完成Idle动画");
    State = Idle;
    }
    else if (State == Rise)
    {
    Console.WriteLine("当前不处于攻击中");
    }
    else if (State == Fall)
    {
    Console.WriteLine("当前不处于攻击中");
    }
    }

    ......

  4. 通过调用对应的动作就完成状态的转换了

    1
    stateMachine.InputAttackCommand();
    但是, 如果需要加入一个新的状态Walk, 则首先需要加入一个状态值
    1
    public const int Walk = 5;

然后需要对所有的动作都需要加入一个新的状态的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/// <summary>
/// 动作:输入攻击指令
/// </summary>
public void InputAttackCommand()
{
if (State == Idle)
{
Console.WriteLine("播放Attack动画");
State = Attack;
}
else if (State == Run)
{
Console.WriteLine("不能在奔跑时攻击");
}
else if (State == Attack)
{
Console.WriteLine("不能在攻击时攻击");
}
else if (State == Rise)
{
Console.WriteLine("不能在跳跃中攻击");
}
else if (State == Fall)
{
Console.WriteLine("不能在下落中攻击");
}
else if (State == Walk)
{
//行走的处理
}
}

/// <summary>
/// 动作:攻击动作完成
/// </summary>
public void AttackActionComplete()
{
if (State == Idle)
{
Console.WriteLine("当前不处于攻击中");
}
else if (State == Run)
{
Console.WriteLine("当前不处于攻击中");
}
else if (State == Attack)
{
Console.WriteLine("完成Idle动画");
State = Idle;
}
else if (State == Rise)
{
Console.WriteLine("当前不处于攻击中");
}
else if (State == Fall)
{
Console.WriteLine("当前不处于攻击中");
}
else if (State == Walk)
{
//行走的处理
}
}

......

很明显这并没有遵守开放-关闭原则, 也不符合面向对象的设计.

改进

因此, 需要改进代码: 1. 定义一个IState接口, 在这个接口内间每个动作都有一个对应的方法. 2. 为每个状态实现一个状态类. 这些类将负责在对应状态下进行的行为. 3. 将状态对应的动作逻辑写入这些方法中.

类图

改进后的设计, 每个状态只需要专注自己的动作, 并且增加状态和动作不会影响到之前的代码, 这符合对扩展开放,对修改关闭.

修改StateMachine类

不再使用整数代表状态, 而是改为状态对象.

  • 原来的代码
    1
    2
    3
    4
    5
    6
    7
    public const int Idle = 0;
    public const int Run = 1;
    public const int Attack = 2;
    public const int Rise = 3;
    public const int Fall = 4;

    public int State = Idle;
  • 修改后的代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public IState IdleState { get; private set; }
    public IState RunState { get; private set; }
    public IState AttackState { get; private set; }
    public IState RiseState { get; private set; }
    public IState FallState { get; private set; }

    public IState State;

    public StateMachine()
    {
    this.IdleState = new IdleState(this);
    this.RunState = new RunState(this);
    this.AttackState = new AttackState(this);
    this.RiseState = new RiseState(this);
    this.FallState = new FallState(this);

    this.State = this.IdleState;
    }

定义

状态模式, 允许对象在内部状态改变时改变它的行为, 对象看起来好像修改了它的类.

构成

Context: 维护对当前状态的引用, 并在状态发生改变的时候切换到新的状态.

State: 定义一个IState接口或者State抽象类, 其中包含状态中的所有行为. 创建特定State类来实现IState接口或继承State抽象类, 在特定State类中实现这些行为(handle).

相关源码

Explicit Dependencies Principle

定义

方法和类应明确要求(通常通过方法参数或构造函数参数)它们所需的任何协作对象, 以便正常运行.

依赖项

如果您的类需要其他类来执行其操作, 那么这些其他类就是依赖项.

隐式依赖项

如果这些依赖项仅存在于类内部的代码中, 而不存在于其公共接口中, 则它们是隐式的.

显式依赖项

对于类的依赖项, 显示依赖项最常出现在对象的构造函数中; 对于更多本地依赖项, 显式依赖项通常出现在特定方法的参数列表中.

隐式依赖的缺点

隐式依赖关系的类比具有显式依赖关系的类的维护成本更高. 它们更难测试, 因为他们与协作者的耦合更紧密. 更难以分析副作用, 因为必须搜索整个类的代码库以查找对象实例化或对静态方法的调用. 与合作者的耦合更紧密, 导致设计更加僵化和脆弱.

显式依赖的优点

非常清楚的说明了执行特定职能所需的条件. 它们倾向于遵循最小意外原则, 减少修改代码影响的范围. 无论是在生产中还是在测试或debug过程中, 显式依赖项都可以轻松的替换为其他实现. 这使得它们更容易维护并且更愿意改变.

示例

示例1-隐式依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class User
{
public string Name;
}

public static class Context
{
public static User CurrentUser;

public static User GetCurrentUser()
{
return CurrentUser;
}
}

public class Client
{
public string Login()
{
return $"用户:{Context.GetCurrentUser().Name} 已登陆!";
}
}

internal class Program
{
public static void Main(string[] args)
{
var user = new User
{
Name = "A"
};

Context.CurrentUser = user;

var client = new Client();
Console.WriteLine(client.Login());
Console.ReadLine();
}
}

示例1中, Client对User对象的依赖是在Context静态类中, 并没有在公共接口中, 是隐式依赖项. 在Main中调用的时候, 并不能直观的了解到client和创建的user有什么联系, 需要查看内部具体的代码, 代码间的隔离性很差.

示例2-显式依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class User
{
public string Name;
}

public class Client
{
public string Login(User user)
{
return $"用户:{user.Name} 已登陆!";
}
}

internal class Program
{
public static void Main(string[] args)
{
var user = new User()
{
Name = "A"
};

var client = new Client();
Console.WriteLine(client2.Login(user));
Console.ReadLine();
}
}

在示例2中, 对于示例1进行了改进. 不再将user对象的引用存储在Context中去隐式传递依赖项, 而是直接将user作为client.Login方法的参数传递依赖项, 能更加直观的看出关联关系, 且对于Login方法内部的代码也更加具有隔离性.

相关源码

应用场景

使用VSCode阅读Markdown文件,希望能够直接内嵌PlantUML,并能在VSCode中阅读时能渲染PlantUML。

实现方法

  1. 在VSCode中安装PlantUML插件。
  2. 进入VSCode设置,选择“在settings.json中编辑”。
  3. 在打开的json文件中末尾加上
    1
    2
    "plantuml.render": "PlantUMLServer",
    "plantuml.server": "http://www.plantuml.com/plantuml"
    • server配置的是官方PlantUML的解析服务器地址。如需要自建PlantUML解析服务器:GitHub项目地址
  4. 在VSCode中按下快捷键Ctrl+Shift+P显示显示命令面板,输入Markdown:Change Preview Security Settings,选择Allow insecure content 允许不安全内容
  5. 在你的Markdown文件中,使用以下格式就能嵌入PlantUML
    1
    2
    3
    4
    5
    6
    7
    ```plantuml
    Class01 <|-- Class02
    Class03 *-- Class04
    Class05 o-- Class06
    Class07 .. Class08
    Class09 -- Class10
    ```

今天在看C#的IEnumerator 接口的官方文档,看到在实现IEnumerable接口的GetEnumerator()方法时额外写一个同名同参的方法。感到有些疑惑,为什么要这样写,这样写的好处在哪里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];

for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}

// Implementation for the GetEnumerator method.
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
}

public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
  • 注:PeopleEnum类实现了IEnumerator接口
  • IEnumerable.GetEnumerator()是显式实现接口成员,必须是默认访问修饰符。

在我实现接口的时候,我通常会直接隐式实现接口成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];

for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}

public IEnumerator GetEnumerator()
{
return new PeopleEnum(_people);
}
}
  • C# 要求隐式实现接口成员必须是public

但是这样做隐式实现接口成员在返回类型是可继承或接口,且外部调用时需要使用派生后的扩展的方法就需要进行类型转换。

1
2
3
People people = new People(null);
PeopleEnum enum1 = (PeopleEnum)people.GetEnumerator();
int enumLength = enum1.Length();

而使用官方文档那样写,调用扩展的方法就不需要进行类型转换。

1
2
3
People people = new People(null);
PeopleEnum enum1 = people.GetEnumerator();
int enumLength = enum1.Length();

安装

  1. 在VS2022中选择菜单项工具->NuGet包管理->管理解决方案的NuGet程序包
  2. 在打开的窗口中选择浏览->搜索BenchmarkDotNet->选择搜索结果中的BenchmarkDotNet->点击安装
  3. 所有弹出框都选择确定
  4. 等待完成安装

使用

  1. 创建一个的类,里面存放需要测试的函数Test1()Test2()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class BenchmarkTest
    {
    public void Test1()
    {
    Console.WriteLine("Test1");
    }

    public void Test2()
    {
    Console.WriteLine("Test2");
    }
    }
  2. 对需要测试的函数增加[Benchmark]特性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class BenchmarkTest
    {
    [Benchmark]
    public void Test1()
    {
    Console.WriteLine("Test1");
    }

    [Benchmark]
    public void Test2()
    {
    Console.WriteLine("Test2");
    }
    }
  3. main函数中使用BenchmarkRunner.Run<BenchmarkTest>(),将会调用BenchmarkTest类中的待测试的函数。
1
2
3
4
static void Main(string[] args)
{
BenchmarkRunner.Run<BenchmarkTest>();
}
  1. (可选)如果需要查看内存使用可以在class加上[MemoryDiagnoser]特性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[MemoryDiagnoser]
public class BenchmarkTest
{
[Benchmark]
public void Test1()
{
Console.WriteLine("Test1");
}

[Benchmark]
public void Test2()
{
Console.WriteLine("Test2");
}
}
  1. 将解决方案配置由Debug改为Release
  2. 点击界面按钮开始执行(不调试)或者快捷键Ctrl+F5,开始执行。

概述

C#中的Struct是值类型数据结构,在使用时常发生值拷贝。

公用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public struct ChildStruct
{
public int Value;
}

public class ChildClass
{
public int Value;
}

public struct ParentStruct
{
public int Value;
public ChildStruct ChildStruct;
public ChildClass ChildClass;
}

作为参数进行值拷贝

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
{
ParentStruct parentStruct1 = new ParentStruct();
parentStruct1.Value = 1;
Console.WriteLine(parentStruct1.Value);

CopyValue1(parentStruct1);

Console.WriteLine(parentStruct1.Value);
}

public static void CopyValue1(ParentStruct parentStruct)
{
parentStruct.Value = 2;
}

输出结果:

1
2
1
1

分析

函数CopyValue1中对传入参数parentStruct的修改,并不能影响函数外parentStruct1的值。所以认为Struct作为传入参数发生了值拷贝,函数内修改的与函数修改的Struct并非同一个引用。

改良

使用ref进行传参,这样不再传递Struct的值,而是传递Struct的引用来避免值拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
{
ParentStruct parentStruct1 = new ParentStruct();
parentStruct1.Value = 1;
Console.WriteLine(parentStruct1.Value);

CopyValue1(ref parentStruct1);

Console.WriteLine(parentStruct1.Value);
}

public static void CopyValue1(ref ParentStruct parentStruct)
{
parentStruct.Value = 2;
}
1
2
1
2

return进行值拷贝

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void Main(string[] args)
{
ParentStruct parentStruct1 = new ParentStruct();
parentStruct1.Value = 1;

ParentStruct parentStruct2 = CopyValue2(ref parentStruct1);
parentStruct2.Value = 3;

Console.WriteLine(parentStruct1.Value);

Console.WriteLine(parentStruct2.Value);
}

public static ParentStruct CopyValue2(ref ParentStruct parentStruct)
{
parentStruct.Value = 2;
return parentStruct;
}

输出结果

1
2
2
3

分析

从之前的示例可以得知ref传参传递的是引用,因此认为函数内的parentStruct变量的与函数外的parentStruct1变量是同一个引用。

变量parentStruct2接收CopyValue2的返回值,并对parentStruct2中的值进行修改,但输出的结果发现parentStruct1中的值并未发生修改。

可以得出结论:函数的返回值也是值传递,并非引用传递。

改良

使用ref将返回的类型为引用类型。接收的变量也需要使用ref修饰。来避免值拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void Main(string[] args)
{
ParentStruct parentStruct1 = new ParentStruct();
parentStruct1.Value = 1;

ref ParentStruct parentStruct2 = ref CopyValue1(ref parentStruct1);
parentStruct2.Value = 3;

Console.WriteLine(parentStruct1.Value);

Console.WriteLine(parentStruct2.Value);
}

public static ref ParentStruct CopyValue1(ref ParentStruct parentStruct)
{
parentStruct.Value = 2;
return ref parentStruct;
}

1
2
3
3

Struct类型数组元素存为变量时进行值拷贝

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Main(string[] args)
{
ParentStruct[] structs = new ParentStruct[1];


structs[0] = new ParentStruct();
structs[0].Value = 1;

Console.WriteLine(structs[0].Value);

ParentStruct parentStruct0 = structs[0];

parentStruct0.Value = 2;

Console.WriteLine(structs[0].Value);
Console.WriteLine(parentStruct0.Value);
}

输出结果

1
2
3
1
1
2

分析

由输出的结果可以发现对于将Struct数组的元素存为变量parentStruct0后,对于parentStruct0的修改并不会改变数组中元素内的值,所以拷贝的是值而不是引用。

改良

使用ref修饰变量,这样变量存储的是数组元素的引用来避免值拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Main(string[] args)
{
ParentStruct[] structs = new ParentStruct[1];


structs[0] = new ParentStruct();
structs[0].Value = 1;

Console.WriteLine(structs[0].Value);

ref ParentStruct parentStruct0 = ref structs[0];

parentStruct0.Value = 2;

Console.WriteLine(structs[0].Value);
Console.WriteLine(parentStruct0.Value);
}

函数内创建并返回的Struct避免值拷贝

示例代码

1
2
3
4
5
6
7
8
9
10
static void Main(string[] args)
{
ParentStruct parentStruct1 = CreateParentStruct();
}

public static ParentStruct CreateParentStruct()
{
ParentStruct parentStruct = new ParentStruct();
return parentStruct;
}

分析

从之前的结论可以得知函数返回值是值拷贝,所以使用out关键词来避免值拷贝

改良

1
2
3
4
5
6
7
8
9
10
static void Main(string[] args)
{
ParentStruct parentStruct1;
CreateParentStruct(out parentStruct1);
}

public static void CreateParentStruct(out ParentStruct parentStruct)
{
parentStruct = new ParentStruct();
}

Struct内的Struct类型成员与Class类型成员

ParentStruct是个Struct,内部有Struct类型成员变量ChildStructClass类型成员变量ChildClass。分别创建这两种类型的变量,初始化Value的值,并赋值给ParentStruct变量对应的成员。两种类型的变量的Value值,观察ParentStruct变量内部对应成员值的变化。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void Main(string[] args)
{
ParentStruct parentStruct1 = new ParentStruct();

ChildStruct childStruct1 = new ChildStruct();
childStruct1.Value = 1;

parentStruct1.ChildStruct = childStruct1;

Console.WriteLine($"childStruct1.Value:{childStruct1.Value}");

childStruct1.Value = 2;
Console.WriteLine("-------childStruct1.Value的值修改后-------");

Console.WriteLine($"childStruct1.Value:{childStruct1.Value}");
Console.WriteLine($"parentStruct1.ChildStruct.Value:{parentStruct1.ChildStruct.Value}");

Console.WriteLine("\n");

ChildClass childClass1 = new ChildClass();
childClass1.Value = 3;

parentStruct1.ChildClass = childClass1;

Console.WriteLine($"childClass1.Value:{childClass1.Value}");

childClass1.Value = 4;
Console.WriteLine("-------childClass1.Value的值修改后-------");

Console.WriteLine($"childClass1.Value:{childClass1.Value}");
Console.WriteLine($"parentStruct1.ChildClass.Value:{parentStruct1.ChildClass.Value}");
}

输出结果

1
2
3
4
5
6
7
8
9
10
childStruct1.Value:1
-------childStruct1.Value的值修改后-------
childStruct1.Value:2
parentStruct1.ChildStruct.Value:1


childClass1.Value:3
-------childClass1.Value的值修改后-------
childClass1.Value:4
parentStruct1.ChildClass.Value:4

分析

Struct的Struct成员存储的是成员内部的值,而Struct的Class成员存储的是成员的引用。并且在C#11之前的版本没法使用ref成员。因此Struct类型的成员没法使用享元模式。因为每次都会拷贝。

疑问

对于C#的ref关键词,个人认为ref在修饰参数时和作用与类型上时是不一样的。 修饰参数时表示参数是通过引用的方式来传递的,但没法传递一个数组的元素的引用。只能传递数组元素的值拷贝,在内部对引用的变更并不会影响到外部。对此感到疑惑,因此需要慎重在方法内对于ref或out修饰的参数的引用进行修改。

AI绘画-Stable Diffusion环境搭建

  1. 下载git

    https://git-scm.com/download/win

  2. 下载python 3.10.6(其他版本的python可能会出现报错)

    https://www.python.org/ftp/python/3.10.6/python-3.10.6-amd64.exe

    安装时需要勾选Add Python 3.10 to PATH。

  3. 下载项目

    https://github.com/AUTOMATIC1111/stable-diffusion-webui.git

  4. 进入下载的目录

    打开stable-diffusion-webui\webui-user.bat文件。

    配置PYTHON值为python安装目录和VENV_DIR为取一个你想要保存模型的文件夹名称,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    @echo off

    set PYTHON=C:\Users\10246\AppData\Local\Programs\Python\Python310\python.exe
    set GIT=
    set VENV_DIR=venv
    set COMMANDLINE_ARGS=

    call webui.bat

  5. 可以使用dev-sidecar来加速。

    https://github.com/docmirror/dev-sidecar

  6. 运行python -m pip install --upgrade pip升级pip版本。

  7. 运行stable-diffusion-webui\webui-user.bat文件。

  8. 然后根据依赖信息(https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies)下载sd-v1-4.ckpt文件。

    1
    magnet:?xt=urn:btih:3a4a612d75ed088ea542acac52f9f45987488d1c&dn=sd-v1-4.ckpt&tr=udp%3a%2f%2ftracker.openbittorrent.com%3a6969%2fannounce&tr=udp%3a%2f%2ftracker.opentrackr.org%3a1337

  9. 将该文件放入stable-diffusion-webui\models\Stable-diffusion目录下,并重命名为model.ckpt

  10. 如果出现No module 'xformers'。或者显存不够CUDA out of memory,修改stable-diffusion-webui\webui-user.bat文件,COMMANDLINE_ARGS后加入--medvram --reinstall-xformers --xformers

    1
    2
    3
    4
    5
    6
    7
    8
    @echo off

    set PYTHON=C:\Users\10246\AppData\Local\Programs\Python\Python310\python.exe
    set GIT=
    set VENV_DIR=venv
    set COMMANDLINE_ARGS=--medvram --reinstall-xformers --xformers

    call webui.bat
  11. 没有报错,出现To create a public link, set share=True in launch().,代表安装成功。

  12. 在浏览器的地址栏输入http://127.0.0.1:7860,进入ui管理界面。

  13. 设置中文:

    1. 点击Extensions
    2. 点击Available
    3. localization的勾去掉。
    4. 往下翻,找到zh_CN Localization,点击Install进行安装。
    5. 安装完,点击Settings,再点击左侧菜单的User interface
    6. 从右侧内容区域找到Localization,点击右侧的刷新按钮,点击下拉框,选择zh_CN
    7. 点击顶部的Apply settings,再点击Reload UI, 就完成中文设置。

生成窗口

  1. Editor的目录下创建一个C#脚本。写入如下的代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
       public class FloorManagerWindow : EditorWindow
    {
    [MenuItem("数据管理/地板")]
    public static void ShowWindow()
    {
    var window = EditorWindow.GetWindow(typeof(FloorManagerWindow), false, "地板数据编辑器");
    window.minSize = new Vector2(1100, 600);
    window.Show();
    }
    }
  2. 创建一个编辑器窗口,需要该脚本继承UnityEditor.EditorWindows类。
  3. 需要编写一个任意方法名的静态方法,如:public static void ShowWindow() { }。并给该方法增加一个[MenuItem]特性,如:[MenuItem("数据管理/地板")],该特性中的参数代表Unity编辑器的菜单的路径。该特性可以按照传入的参数在Unity编辑器上创建对应的菜单。并在该菜单项的点击事件触发时调用被特性标记的方法。
  4. 调用EditorWindow.GetWindow(typeof(FloorManagerWindow))方法会根据类型创建一个窗口。
  5. Show()方法会显示窗口。PS:EditorWindow.GetWindow()方法本身就会去调用Show()方法,猜想可能是为了在EditorWindow.GetWindow()之后修改了某些值,因此需要再手动调用Show()方法,以更新修改的值。

ScriptObject

在Unity的论坛中对于获取IMGUI的窗口宽度的帖子的示例中使用Screen.width,但是在不同分辨率下会出现值不准确。建议使用EditorWindow.position中的width值。

const

修饰的变量只能在声明的时候赋值进行初始化。

const默认是static的,不能手动为const添加一个static修饰符。

本质上是在编译阶段将使用const的代码处用常量值进行替换,类似c语言中的#define

readonly

readonly变量是运行时变量,可以在声明或构造函数中初始化。

readonly在第一次赋值后不能修改它的值。

区别

  1. const是一个编译时常量,readonly是一个运行时常量。
  2. const只能修饰基元类型,枚举类型或字符串类型,readonly没有限制。

建议

  1. 当值绝对不变的时候使用const,例如一周的天数是7。
  2. 当想要对类的不同实例使用不同的常量,使用readonly
  3. 当在项目的配置文件中配置的项目中的可能会变化的常量,使用static readonly
0%