mixin
蓝图mixin
把一个ts类(假设是类A)mixin到一个蓝图类(类B)的能力:
如果A和B都有同样的函数,A的逻辑会替换B的
支持UE的事件(比如:ReceiveBeginPlay)
可新增方法或字段
特点:
安全:如果ts类和蓝图类有同名函数,将会检查两者的兼容性(符合ts的协变逆变规则)
高效:ts类可以调用蓝图类的方法,有代码提示
强大
- ts可新增方法(但蓝图不可见)
- ts能新增字段(但蓝图不可见)
- 支持网络相关方法(RPC)的mixin
- 支持事件mixin并能被回调
- 对象声明周期支持脚本持有和引擎持有
- 支持原生类的BlueprintNativeEvent、BlueprintImplementableEvent方法的mixin
注意事项
- 如果要覆盖UE的事件,要注意被mixin的类中有对应的事件(逻辑可以为空),否则在子类调用时可能会有可能调用不到 ts的逻辑,见: https://github.com/Tencent/puerts/issues/1762
基本用法
本文完整例子看这里,将Start脚本改为UsingMixin即可运行。
加载被mixin的蓝图类
let ucls = UE.Class.Load('/Game/StarterContent/MixinTest.MixinTest_C');
const MixinTest = blueprint.tojs<typeof UE.Game.StarterContent.MixinTest.MixinTest_C>(ucls);
写ts扩展
interface Loggable extends UE.Game.StarterContent.MixinTest.MixinTest_C {};
class Loggable {
//可以覆盖蓝图对应的函数,函数签名和MixinTest_C声明的不兼容(不需要严格一致,能满足协变逆变要求即可)会报错
Log(msg:string): void {
console.log(this.GetName(), msg);
console.log(`1 + 3 = ${this.TsAdd(1, 3)}`);
}
//蓝图没有的纯Ts方法
TsAdd(x : number, y: number): number {
console.log(`Ts Add(${x}, ${y})`)
return x + y;
}
}
执行mixin
const MixinTestWithMixin = blueprint.mixin(MixinTest, Loggable);
使用新类
MixinTestWithMixin即为新类
world.SpawnActor(MixinTestWithMixin.StaticClass(), undefined, UE.ESpawnActorCollisionHandlingMethod.Undefined, undefined, undefined) as Loggable;
进阶用法
前置知识
一个UE对象传入到ts,ts侧会建立一个stub (ts)对象与之相对应(ts调用这个stub对象会被转发到真实的UE原生调用),而且在puerts中他们的生命周期间的关系有两种。
stub对象由js gc管理,stub对象持有ue对象的强引用(下称“stub对象持有ue对象”)
- 如果stub对象在ts无引用,将会被gc,进而释放对ue对象的强引用
- 如果进一步在ue引擎也没有该ue对象,该ue对象会被gc
ue对象由ue gc管理,ue对象持有stub对象的强引用(下称“ue对象持有stub对象”)
- 如果ue对象在ue引擎无引用,该ue对象会被gc,进而释放对stub对象的强引用
- 如果进一步在ts也没有引用该stub对象,该stub对象会被gc
blueprint.mixin的参数3
该参数声明
type MixinConfig = { objectTakeByNative?:boolean, inherit?:boolean, generatedClass?: Class};
objectTakeByNative默认为false,表示“stub对象持有ue对象”,为true表示“ue对象持有stub对象”
inherit和generatedClass是配合使用的,默认为false,表示重定向的是原蓝图类,如果为true的话,将会先动态生成一个继承类,然后重定向生成的类,然后该生成类会通过generatedClass字段返回
super关键字的说明
假设有个蓝图类MixinSuperTestDerived继承了蓝图类MixinSuperTestBase,这两个类都有Foo方法,我们要通过mixin覆盖MixinSuperTestDerived上的Foo,在ts逻辑中需要调用基类(蓝图类)的Foo要怎么处理。
直接在前面介绍的不extends任何类的mixin类中调用super会报错。
如下代码会报语法错误。
class DerivedClassMixin {
Foo():void {
console.log("i am ts mixin");
super.Foo();
}
}
这时可以通过添加个中转类来解决问题
interface MixinSuperTestBasePlaceHold extends UE.Game.StarterContent.MixinSuperTestBase.MixinSuperTestBase_C {};
class MixinSuperTestBasePlaceHold {}
Object.setPrototypeOf(MixinSuperTestBasePlaceHold.prototype, MixinSuperTestBase.prototype);
class DerivedClassMixin extends MixinSuperTestBasePlaceHold {
Foo():void {
console.log("i am ts mixin");
super.Foo();
}
}
新增字段
新增字段其实是存放在stub对象里,因而:
objectTakeByNative为false时,需要保持对stub对象的引用,否则stub对象释放后,ue对象回传将会建立一个新对象,原来的数据就丢失了
objectTakeByNative为true不需要保持stub对象引用,但注意不要期望通过持有stub对象进而引用ue对象,该ue对象应保证被引擎持有
原生类的mixin
只支持BlueprintNativeEvent、BlueprintImplementableEvent方法,比如如下C++声明的函数
class UMainObject : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent)
int32 Mult(int32 a, int32 b) const;
UFUNCTION(BlueprintImplementableEvent)
int32 Div(int32 a, int32 b) const;
int32 Mult_Implementation(int32 a, int32 b) const
{
UE_LOG(LogTemp, Warning, TEXT("wrong implementation div %d %d"), a, b);
return a + b;
}
};
typescript这样mixin
let obj = new UE.MainObject();
console.log('before mixin start....')
obj.Mult(1, 2);
obj.Div(4, 5);
console.log('before mixin end....')
class Calc {
//声明为BlueprintNativeEvent的原生方法
Mult(x: number, y: number) : number
{
console.log(`Ts Mult(${x}, ${y})`)
return x * y;
}
//声明为BlueprintImplementableEvent的方法
Div(x: number, y: number) : number
{
console.log(`Ts Div(${x}, ${y})`)
return x / y;
}
}
interface Calc extends UE.MainObject {};
blueprint.mixin(UE.MainObject, Calc);
console.log('after mixin start....')
obj.Mult(1, 2);
obj.Div(4, 5);
console.log('after mixin end....')
输出
before mixin start....
wrong implementation div 1 2
before mixin end....
after mixin start....
Ts Mult(1, 2)
Ts Div(4, 5)
after mixin end....
可以看到,即使是已经new出来的对象,mixin后调用也是调用到新的ts方法
C++ BlueprintNativeEvent函数bug修复
如果你的C++函数声明为BlueprintNativeEvent的话,如果有bug,可以用该功能替换成正确逻辑。