跳到主要内容

引擎接口

更新日期:2025-01-05

Z# 内置导出了一些常用的引擎 API。

Core

全局变量

你可以在脚本中访问一些常见的全局变量:

bool isInGameThread = IsInGameThread;
bool isServer = GIsServer;
bool isClient = GIsClient;
bool isEditor = GIsEditor;

这些全局变量来自 CoreGlobals.h,你可以在 ZeroGames.ZSharp.Core.UnrealEngine.CoreGlobals 类型中查看所有 Z# 可见的全局变量。

断言

你可以在脚本中使用虚幻引擎风格的断言 API:

check(IsInGameThread);
ensure(x > 100);
verify(map.TryGetValue(10, out string value));

这些 API 是全局可见的,定义在 ZeroGames.ZSharp.Core.UnrealEngine.AssertionMacros 中,有三个系列,它们的行为和引擎原生 API 类似:

  1. check 系列失败会中断代码的执行并抛出 FatalException,如果上层代码没有捕获这个异常,最终将导致进程终止。 这个系列在发布版本中会被直接无视。
  2. ensure 系列失败会输出一条错误日志并触发断点并继续执行,代码中同一位置的 ensure 在一次 Master ALC 生命周期中只触发一次。 这个系列在发布版本中会被直接无视。
  3. verify 系列的行为与 check 系列相同,但在发布版本中仍然会执行传递给 verify 的表达式。 如果你的断言有副作用,你应该使用 verify 而不是 check,以避免开发版本与发布版本的行为产生差异。

日志

你可以在脚本中使用虚幻引擎风格的日志 API:

UE_LOG(LogTemp, "Hello!");
UE_WARNING(LogTemp, "Warning!");
UE_ERROR(LogTemp, "Error!");
UE_LOG(LogTemp, ELogVerbosity.Log, "Hello!");

这些 API 是全局可见的,定义在 ZeroGames.ZSharp.Core.UnrealEngine.LogMacros 中,它们的行为和引擎原生 API 相同。

托管侧的日志 API 不提供 Fatal 级别,如果你想触发 Fatal Error,可以使用 Thrower.Fatal() 方法。

配置

你可以在脚本中访问 GConfig 对象:

if (GConfig.TryGetArray(GGameIni, "MySection", "MyKey1", out string[]? value1))
{
...
}

string value2 = GConfig.GetStringOrDefault(GGameIni, "MySection", "MyKey2", "MyDefaultValue");

托管侧 GConfig 对象的 API 来自 ZeroGames.ZSharp.Core.UnrealEngine.IConfig 接口。

控制台

你可以在脚本中访问和注册控制台对象:

BoolConsoleVariable cvarEnableIK = BoolConsoleVariable.FromName("p.EnableIK");
cvarEnableIK.OnChanged += cvar =>
{
if (cvar.TryGetBool(out bool enableIK))
{
UE_LOG(LogTemp, $"Enable IK Changed to: {enableIK}");
}
};
if (cvarEnableIK.TryGetValue(out bool enableIK))
{
cvarEnableIK.TrySetValue(!enableIK);
}

ConsoleCommand cmdObjList = ConsoleCommand.FromName("z#.geng");
cmdObjList.TryExecute("assemblies=Game");

using AutoStringConsoleVariable myCVar = new("MyCVar", "Hello!", "My test variable.", cvar =>
{
if (cvar.TryGetString(out string value))
{
UE_LOG(LogTemp, $"MyCVar Changed to: {value}");
}
});
if (myCVar.TryGetValue(out string value))
{
myCVar.TrySetValue(!value);
}

using AutoConsoleCommand myCmd = new("MyCmd", "My test command.", args =>
{
UE_LOG(LogTemp, $"MyCmd: {string.Join(',', args)}");
});

通过 FromName 的方式,你可以创建一个对已存在的控制台变量/命令的引用,你可以读写变量、监听变量的变化、执行命令。 如果对应名字的控制台对象不存在或类型不匹配,Try 方法会返回 false。

通过 new 的方式,你可以注册一个新的控制台变量/命令。 由托管侧注册的控制台变量和普通的控制台变量没有区别; 由托管侧注册的控制台命令实际执行的是托管代码。 由托管侧注册的控制台对象实现了 IDisposable 接口,你可以显式释放它们来解除注册,也可以等到它们被自然回收时自动解除注册。

CoreUObject

UObject

你可以在脚本中使用 UObject 相关的 API:

MyObject myObject = NewObject<MyObject>();
MyAsset? myAsset = LoadObject<MyAsset>("/Game/MyAsset.MyAsset");
MyObject myObjectCopy = DuplicateObject<MyObject>(myObject, GTransientPackage, "MyObjectCopy");
UnrealClass? actorClass = FindObject<UnrealClass>("/Script/Engine.Actor");
Actor actorCDO = GetDefault<Actor>();

这些 API 是全局可见的,定义在 ZeroGames.ZSharp.UnrealEngine.CoreUObject.UnrealObjectGlobals 中。

Engine

异步加载

你可以在脚本中使用 StreamableManager 相关的 API:

MyAsset myAsset = await IStreamableManager.GlobalInstance.LoadAsync<MyAsset>("/Game/MyAsset.MyAsset");
MyAsset[] myAssets = await IStreamableManager.GlobalInstance.LoadAsync<MyAsset>([ "/Game/MyAsset1.MyAsset1", "/Game/MyAsset2.MyAsset2", "/Game/MyAsset3.MyAsset3" ]);

Actor

你可以在脚本中创建 Actor:

MyPawn owner = ...;
World world = owner.GetWorld();
Transform spawnTransform = new()
{
Translation = new() { X = 10000, Y = 10000, Z = 30000, },
Rotation = new Rotation() { Pitch = 45, Yaw = 90, Roll = 90, },
Scale3D = new() { X = 2, Y = 2, Z = 2, },
};
ActorSpawnParameters spawnParameters = new()
{
Owner = owner,
Instigator = owner,
};

MyPawn pawn1 = world.SpawnActor<MyPawn>(transform, spawnParameters);
MyPawn pawn2 = world.SpawnActorAbsolute<MyPawn>(transform, spawnParameters);
MyPawn pawn3 = world.SpawnActorDeferred<MyPawn>(transform, (MyPawn pawn) =>
{
pawn.Camp = owner.Camp;
UE_LOG(LogTemp, $"Initialize pawn: {pawn.GetName()}");
}, spawnParameters);
MyPawn pawn4 = await world.SpawnActorDeferredAsync<MyPawn>(transform, async (MyPawn pawn) =>
{
pawn.Camp = owner.Camp;
pawn.BirthAnimMontage = await IStreamableManager.GlobalInstance.LoadAsync<AnimMontage>("/Game/AM_Birth.AM_Birth");
UE_LOG(LogTemp, $"Initialize pawn: {pawn.GetName()}");
}, spawnParameters);

子系统

你可以在脚本中访问 Subsystem:

Engine engine = Engine.Instance;
MyEngineSubsystem? myEngineSubsystem = engine.GetSubsystem<MyEngineSubsystem>();

GameInstance gameInstance = ...;
MyGameInstanceSubsystem? myGameInstanceSubsystem = gameInstance.GetSubsystem<MyGameInstanceSubsystem>();

World world = ...;
MyWorldSubsystem? myWorldSubsystem = world.GetSubsystem<MyWorldSubsystem>();

PlayerController pc = ...;
MyLocalPlayerSubsystem? myLocalPlayerSubsystem1 = pc.GetSubsystem<MyLocalPlayerSubsystem>();

LocalPlayer? localPlayer = pc.LocalPlayer;
MyLocalPlayerSubsystem? myLocalPlayerSubsystem2 = localPlayer?.GetSubsystem<MyLocalPlayerSubsystem>();

ensure(myLocalPlayerSubsystem1 == myLocalPlayerSubsystem2);

Z# 在设计上是一个为运行时服务的脚本引擎,因此没有导出 EditorSubsystem; 为了方便,你可以在脚本里通过 PlayerController 对象直接获取 LocalPlayerSubsystem,这和通过对应的 LocalPlayer 对象获取是一样的。

你也可以用 Emit 在脚本中定义继承于 Subsystem 的动态类,由于动态类是 Native 类,引擎可以正确处理它们。 但如果你要在脚本中处理 Subsystem 的生命周期事件,如 ShouldCreateSubsystem()Initialize()Deinitialize() 等, 则需要在 C++ 中创建基类转发到一个 BlueprintImplementableEvent 中。