行为树(Behavior Tree, BT)是一种用于AI行为设计的数据结构,广泛应用于游戏开发,包括Unity游戏引擎中。行为树通过节点的组合来表示复杂的决策逻辑,每个节点代表一个行为或条件。下面是对Unity行为树的概要及用法的解释:

行为树概要

节点类型

- 选择器(Selector):会从上到下依次尝试子节点,直到有一个子节点成功执行。

- 序列器(Sequence):会从上到下依次执行子节点,直到有一个子节点失败。

- 条件节点(Condition Node):用于检查某个条件是否为真或假。通常是行为树的叶子节点。

- 动作节点(Action Node):执行某个动作。同样通常是行为树的叶子节点。

- 装饰器节点(Decorator Node):用于修改子节点的行为。可以嵌套在其他节点中,如选择器、序列器或动作节点。

行为树的特点

- 可重用性:节点可以被多次复用,从而简化行为树的设计。

- 模块化:行为树可以被拆分为多个子树,便于管理和维护。

- 并行执行:支持并行执行多个子节点,提高效率。

- 状态记忆:行为树可以记住每个节点的状态,便于从上次中断的地方继续执行。

Unity中的行为树用法

Unity提供了多种工具和库来支持行为树的创建和使用,其中比较流行的包括Behavior Designer和Unity官方的Behavior Tree插件(从Unity 2021.2开始引入)。

阅读全文 »

基本概念

CancellationTokenSource 是一个类,用于创建一个取消令牌 (CancellationToken),并通过该令牌来通知一个或多个异步操作取消请求。CancellationToken 是一个结构体,用于传递取消通知。

创建 CancellationTokenSource

首先,你需要创建一个 CancellationTokenSource 对象。

1
2
3
4
5
6
7
8
9
10
11
using System.Threading;

public class CancellationTokenExample
{
private CancellationTokenSource tokenSource;

public void Initialize()
{
tokenSource = new CancellationTokenSource();
}
}

获取 CancellationToken

从 CancellationTokenSource 获取 CancellationToken 对象。

1
2
3
4
5
6
7
8
9
10
11
12
using System.Threading;

public class CancellationTokenExample
{
private CancellationTokenSource tokenSource;

public void Initialize()
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
}
}

在异步方法中使用 CancellationToken

在异步方法中使用 CancellationToken 来检查取消请求,并在需要时抛出 OperationCanceledException。

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
66
using System;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks; // 如果使用 UniTask

public class CancellationTokenExample : MonoBehaviour
{
private CancellationTokenSource tokenSource;

public async void StartLongRunningTask()
{
Initialize();
CancellationToken token = tokenSource.Token;

try
{
await LongRunningTask(token);
}
catch (OperationCanceledException)
{
Debug.Log("任务已被取消");
}
}

private async UniTask LongRunningTask(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested(); // 检查取消请求并抛出异常

// 模拟长时间运行的任务
await UniTask.Delay(1000, cancellationToken: token);

Debug.Log($"任务进行中: 第 {i + 1} 次");
}
}

private void Initialize()
{
tokenSource = new CancellationTokenSource();
}

private void OnClickCancelBtn()
{
if (tokenSource != null)
{
tokenSource.Cancel();
tokenSource.Dispose();
tokenSource = null;
}
}

void Start()
{
StartLongRunningTask();
}

void OnDestroy()
{
if (tokenSource != null)
{
tokenSource.Cancel();
tokenSource.Dispose();
}
}
}
阅读全文 »

DOTween 库详细用法教程

DOTween 是一个用于 Unity 的强大且灵活的动画库,它使用链式语法来创建和控制动画,使得代码更加简洁和易读。下面详细介绍 DOTween 的用法。

安装 DOTween

你可以通过 Unity 的包管理器来安装 DOTween。步骤如下:

打开 Unity 的 Package Manager(可以通过 Window -> Package Manager 打开)。
点击左上角的 “+” 按钮,然后选择 Add package from git URL。
在弹出的窗口中输入 DOTween 的 Git URL:https://github.com/Demigiant/dotween.git。
点击 Add 按钮完成安装。

基本用法

插值动画(Tweening)

使用 DOTween.To、DOTween.From、DOTween.ToX 等方法来创建动画,这些方法通常需要起始值、结束值、动画持续时间和更新频率。

1
2
DOTween.To(() => ump.fValue, fv => ump.fValue = fv, 1f, 3f);
DiffCopyInsert

这行代码的意思是从 ump.fValue 的当前值开始到 1f 的值,持续时间为 3 秒,每帧更新。ump.fValue 是一个属性或字段,fv => ump.fValue = fv 是一个回调函数,用于设置 ump.fValue 的值为动画计算出的新值。

链式语法

DOTween 的动画可以使用链式语法来添加更多的配置,例如 SetTarget、SetLink 等。

1
await tweener.SetTarget(ump).SetLink(fog).GetAwaiter();

这行代码首先设置了动画的目标对象为 ump,然后设置了动画的链接对象为 fog。链接对象的作用是当链接对象被销毁时,动画也会自动停止。GetAwaiter() 用于将动画变为异步等待对象。

等待动画完成

你可以使用 await 来等待动画完成。AwaitForComplete() 是一个扩展方法,用于等待 DOTween 动画完成。

1
await td.AwaitForComplete();

这行代码会暂停当前协程,直到动画 td 完成。

销毁对象

阅读全文 »

C# 集合(Collection)

集合(Collection)类是专门用于数据存储和检索的类。这些类提供了对栈(stack)、队列(queue)、列表(list)和哈希表(hash table)的支持。大多数集合类实现了相同的接口。

集合(Collection)类服务于不同的目的,如为元素动态分配内存,基于索引访问列表项等等。这些类创建 Object 类的对象的集合。在 C# 中,Object 类是所有数据类型的基类。

动态数组(ArrayList)

它代表了可被单独索引的对象的有序集合。
它基本上可以替代一个数组。但是,与数组不同的是,您可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。

基本结构

1
public class ArrayList : ICollection, IEnumerable, IList, ICloneable

实现接口:

  • IList:支持按索引访问元素。

  • ICollection:支持集合基本操作,如添加、删除和计数。

  • IEnumerable:支持通过枚举器遍历元素。

  • ICloneable:支持克隆 ArrayList。

特点

动态扩展

  • ArrayList 的容量可以根据需要自动调整,不需要指定固定大小。

  • 当添加的元素超过当前容量时,ArrayList 会自动增加容量(通常是原容量的两倍)。

非泛型集合

  • ArrayList 是非泛型集合,所有元素被存储为 object 类型。这意味着它可以存储任意类型的对象,但需要注意装箱(boxing)和拆箱(unboxing)的性能影响。

无序操作

  • 虽然元素存储顺序与添加顺序一致,但它并不提供内置排序功能。

线程安全

  • 默认不是线程安全的。如果需要线程安全的 ArrayList,可以使用 ArrayList.Synchronized 方法生成一个线程安全的版本。
    ArrayList 类的方法和属性

下表列出了 ArrayList 类的一些常用的 属性:

属性名称	类型	描述

Count	int	获取 ArrayList 中包含的元素数量。
Capacity	int	获取或设置 ArrayList 的容量(存储空间)。
IsFixedSize	bool	指示 ArrayList 是否具有固定大小。
IsReadOnly	bool	指示 ArrayList 是否为只读。
IsSynchronized	bool	指示 ArrayList 是否线程安全。
SyncRoot	object	获取可用于同步访问的对象。

下表列出了 ArrayList 类的一些常用的 方法:

方法名称 返回类型 描述

添加与插入	
 
Add(object value)	int	将对象添加到 ArrayList 的末尾,返回新元素的索引。
AddRange(ICollection c)	void	将指定集合的所有元素添加到 ArrayList 的末尾。
Insert(int index, object value)	void	在指定索引处插入对象。
InsertRange(int index, ICollection c)	void	在指定索引处插入指定集合的所有元素。

删除		

Remove(object value)	void	移除首次出现的指定对象。
RemoveAt(int index)	void	移除指定索引处的元素。
RemoveRange(int index, int count)	void	移除从指定索引开始的指定数量的元素。
Clear()	void	移除所有元素。

访问与查询		

Contains(object item)	bool	判断 ArrayList 是否包含指定对象。
IndexOf(object value)	int	获取指定对象首次出现的索引。
LastIndexOf(object value)	int	获取指定对象最后一次出现的索引。

排序与复制		

Sort()	void	按照默认顺序排序 ArrayList 中的元素。
Sort(IComparer comparer)	void	按照自定义比较器排序。
Reverse()	void	反转 ArrayList 中元素的顺序。
CopyTo(Array array)	void	将 ArrayList 的元素复制到指定数组中。

其他
    
GetRange(int index, int count)	ArrayList	获取从指定索引开始的指定数量的元素子集。
ToArray()	object[]	将 ArrayList 中的元素复制到数组中。
TrimToSize()	void	将容量调整为实际元素数量以节省内存。
阅读全文 »

C# 事件(Event)

C# 事件(Event)是一种成员,用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。

事件(Event)

基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。

C# 中使用事件机制实现线程间的通信。

关键点

  • 声明委托:定义事件将使用的委托类型。委托是一个函数签名。

  • 声明事件:使用 event 关键字声明一个事件。

  • 触发事件:在适当的时候调用事件,通知所有订阅者。

  • 订阅和取消订阅事件:其他类可以通过 += 和 -= 运算符订阅和取消订阅事件。

通过事件使用委托

事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。

发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。

订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。

声明事件(Event)

在类的内部声明事件,首先必须声明该事件的委托类型。例如:

1
public delegate void BoilerLogHandler(string status);

然后,声明事件本身,使用 event 关键字:

1
2
// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;

上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。

以下示例展示了如何在 C# 中使用事件:

> 实例:

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
using System;

namespace EventDemo
{
// 定义一个委托类型,用于事件处理程序
public delegate void NotifyEventHandler(object sender, EventArgs e);

// 发布者类
public class ProcessBusinessLogic
{
// 声明事件
public event NotifyEventHandler ProcessCompleted;

// 触发事件的方法
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}

// 模拟业务逻辑过程并触发事件
public void StartProcess()
{
Console.WriteLine("Process Started!");

// 这里可以加入实际的业务逻辑

// 业务逻辑完成,触发事件
OnProcessCompleted(EventArgs.Empty);
}
}

// 订阅者类
public class EventSubscriber
{
public void Subscribe(ProcessBusinessLogic process)
{
process.ProcessCompleted += Process_ProcessCompleted;
}

private void Process_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
}

class Program
{
static void Main(string[] args)
{
ProcessBusinessLogic process = new ProcessBusinessLogic();
EventSubscriber subscriber = new EventSubscriber();

// 订阅事件
subscriber.Subscribe(process);

// 启动过程
process.StartProcess();

Console.ReadLine();
}
}
}
阅读全文 »

C# 委托(Delegate)

在 C# 中,委托(Delegate) 是一种类型安全的函数指针,它允许将方法作为参数传递给其他方法。

C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量,引用可在运行时被改变。

委托在 C# 中非常常见,用于事件处理、回调函数、LINQ 等操作。

所有的委托(Delegate)都派生自 System.Delegate 类。

声明委托(Delegate)

委托是一个引用类型,它定义了一个方法签名,可以用于存储指向该签名的方法。通过委托,你可以调用其他类中的方法。

委托声明决定了可由该委托引用的方法。委托可指向一个与其具有相同标签的方法。

声明委托的语法如下:

1
public delegate <return type> <delegate-name> <parameter list>

中文格式说明:

1
public delegate 返回类型 委托名(参数类型 参数名, ...);

例如以下代码,我们定义一个接受两个整数并返回一个整数的委托:

1
public delegate int MathOperation(int x, int y);

以下例子的委托可被用于引用任何一个带有一个单一的 string 参数的方法,并返回一个 int 类型变量。

1
public delegate int MyDelegate (string s);

实例化委托(Delegate)

一旦声明了委托类型,委托对象必须使用 new 关键字来创建,且与一个特定的方法有关。当创建委托时,传递到 new 语句的参数就像方法调用一样书写,但是不带有参数。例如:

1
2
3
4
public delegate void printString(string s);
...
printString ps1 = new printString(WriteToScreen);
printString ps2 = new printString(WriteToFile);

下面的实例演示了委托的声明、实例化和使用,该委托可用于引用带有一个整型参数的方法,并返回一个整型值。

> 实例:

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
using System;

delegate int NumberChanger(int n);
namespace DelegateAppl
{
class TestDelegate
{
static int num = 10;
public static int AddNum(int p)
{
num += p;
return num;
}

public static int MultNum(int q)
{
num *= q;
return num;
}
public static int getNum()
{
return num;
}

static void Main(string[] args)
{
// 创建委托实例
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MultNum);
// 使用委托对象调用方法
nc1(25);
Console.WriteLine("Value of Num: {0}", getNum());
nc2(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
Value of Num: 35
Value of Num: 175
阅读全文 »