一·、创建SWebBrowserView
SWebBrowserView是从SCompoundWidget派生的一个类,因此是一个UI界面相关的类,可以在Slate中作为其他界面的一部分使用。
class WEBBROWSER_API SWebBrowserView
: public SCompoundWidget
{
…
}
SWebBrowserView是实现html页面加载的关键,提供了几个核心的功能.
1,第一个是LoadURL
通过这个方法加载页面,LoadURL有一些类似的方法,比如LoadSting等,有兴趣的可以深入研究一下。
void SWebBrowserView::LoadURL(FString NewURL)
{
AddressBarUrl = FText::FromString(NewURL);
if(BrowserWindow.IsValid())
{
BrowserWindow->LoadURL(NewURL);
}
}
2,第二个是ExecuteJavascript
通过这个方法,可以执行页面中的javascript,比如回调方法。
void SWebBrowserView::ExecuteJavascript(const FString& ScriptText)
{
if(BrowserWindow.IsValid())
{
BrowserWindow->ExecuteJavascript(ScriptText);
}
}
3,第三个是BindUObject
BindUObject这个方法非常关键,是页面中的javascript调用能够传到UE底层的关键。下面我们再详细展开如何使用BindUObject.
void SWebBrowserView::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent)
{
if(BrowserWindow.IsValid())
{
BrowserWindow->BindUObject(Name, Object, bIsPermanent);
}
}
二、绑定监听器,响应JavaScript方法
1、首先创建监听器对象
因为BindUObject需要传入的是一个UObject对象,所以监听器对象需要从UObject派生。
UCLASS()
class ESWEBUIMAIN_API UESWebInterface : public UObject
{
GENERATED_UCLASS_BODY()
public:
。。。
}
这里的UESWebInterface就可以是一个监听器对象
2、给监听器对象添加监听方法
Javascript脚本中要写什么方法,这里就需要添加一个同名的方法,比如triggerUE4EventWithBlank,那么在监听器中就添加一个同名的方法,注意这里必须加UFUNCTION,否则会找不到。
UCLASS()
class ESWEBUIMAIN_API UESWebInterface : public UObject
{
GENERATED_UCLASS_BODY()
public:
UFUNCTION()
void triggerUE4EventWithBlank(FStringjavascriptFunctionsName, FString arguments);
。。。
}
voidUESWebInterface::triggerUE4EventWithBlank(FString javascriptFunctionsName,FString arguments) {
。。。
UE_LOG(LogTemp, Display, TEXT("JAVASCRIPT:%s"),*arguments);
TArray args;
arguments.ParseIntoArray(args, TEXT(".;_|"), true);
。。。
}
有了响应方法,最简单的监听器对象就创建好了。
为了方便蓝图中使用,监听器对象其实也可以从UWidget类派生,这样在蓝图里就是可以看到的一个UI界面对象。并可以创建出来使用了
3、绑定监听器到SWebBrowserView
这一步就是调用BindUObject方法了, 以下代码展示了创建WebBrowserView,loadURL以及绑定监听器的过程
void UESWebInterface::LoadURL(FStringNewURL)
{
TSharedPtr ESWebUI;
FCreateBrowserWindowSettingsbrowserSettings;
browserSettings.bUseTransparency =transparentBackground;
browserSettings.BrowserFrameRate =maxMenuFPS;
browserSettings.bShowErrorMessage = true;
browserSettings.InitialURL = TEXT("about:blank");
TSharedPtrBrowserWindow;
BrowserWindow =IWebBrowserModule::Get().GetSingleton()->CreateBrowserWindow(browserSettings);
//SNew the web browser view
ESWebUI = SNew(SWebBrowserCustom,BrowserWindow)
.InitialURL(TEXT("about:blank"))
.EnableMouseTransparency(bEnableMouseTransparency)
.MouseTransparencyDelay(MouseTransparencyDelay)
.MouseTransparencyThreshold(MouseTransparencyThreshold)
.ShowControls(false)
.ShowErrorMessage(true)
.SupportsTransparency(transparentBackground)
.SupportsThumbMouseButtonNavigation(false)
.ShowInitialThrobber(false);
if (ESWebUI.IsValid())
{
//Load URL
ESWebUI->LoadURL(NewURL);
//Bind UObject
ESWebUI->BindUObject("uecom", this,true);
}
}
这里大家可能有点奇怪,绑定对象的时候输入的name参数为什么是”uecom”,让我们继续往下看。
4、在Javascript中实现方法,能够往监听器发射消息
在js文件中写下如下两个函数, 其中getParameter函数是参数的合并,把多个参数以” .;_|”隔开合并成一个string,是个辅助函数。
function getParameter(parameterArray){
varobj = {
functionsName:'',
id:'',
className:'',
value:'',
args:''
};
vartempArray = new Array();
if(parameterArray.length > 0){
for(var i = 1; i <= parameterArray.length; i++){
if(typeof parameterArray[i] == 'object'){
obj.id = parameterArray[i].id;
obj.className = parameterArray[i].className;
obj.value = parameterArray[i].value;
}
else{
tempArray.push(parameterArray[i]);
}
}
}
obj.functionsName = parameterArray[0];
obj.args = tempArray.join('.;_|');
returnobj;
};
function triggerDoCityEventBlank(){
varobj = this.getParameter((Array.prototype.slice.call(arguments, 0)));
{
if(window.ue && window.ue.uecom) {
console.log("calljs function:", obj);
window.ue.uecom.triggerue4eventwithblank(obj.functionsName,obj.args);
}
else
{
showAlert(DoCityNotFoundMessage);
}
}
};
重点可以看一下,triggerDoCityEventBlank这个函数,这个函数看起来没有输入参数,但是js你知道是很灵活的,函数定义的时候可以没参数,但实际调用的时候是可以给参数的。Example:triggerDoCityEventBlank('myJavascriptFunctionsName','optionalParameter 1',1234, 'Hallo World'); 这里的第一个参数需要有一些特殊,后面会提到,但不属于UE WebUIb本身的核心逻辑。
这里,我们可以看到window.ue.uecom这个域,然后调用了triggerue4eventwithblank这个方法,这就是我们前面绑定的监听器对象名称以及它的方法了,通过这个js的函数调用,就可以实现了向监听器发送消息,相当于调用了UE里面UObject对象的方法。在js调用函数的后,我们可以看到,监听器(UESWebInterface)的triggerue4eventwithblank函数被调用了。这里面的实现过程,感兴趣的可以自己参考源码。
到这来,我们已经实现了加载页面,并且在页面的js脚本中向UE底层对象发送消息。
triggerue4eventwithblank看起来就像它的名字一个,触发了ue监听器里面的一个函数,传入的参数是函数的名称和参数,那要正在实现触发的函数,是需要在监听器triggerue4eventwithblank里面,去调用该函数的实现。举个例子,我要”触发”打开关卡的函数,那么函数名可能是OpenLevel,参数是一个关卡的名称,那么在监听器的triggerue4eventwithblank函数里面,就要解析FString javascriptFunctionsName和FString arguments,去实现真正的操作了。这一段有点绕,可以体会一下trigger的含义。
三、UE c++底层向蓝图发送接收到的js消息
这部分本来不属于WebUI的核心逻辑,不过UE c++底层对象接收到js消息后,经常需要让蓝图层也知道,所以这里也顺便说一下从c++向蓝图发送消息的一种实现方式。
1、 定义动态委托(Dynamic Delegate)
我们知道,C++和蓝图的交互比较好的实现方式是动态委托方式。我们先定义一个对象,再从这个对象暴露几个动态委托。我们假设对象的名称为UJavaScriptBean,从UObject对象派生。
UCLASS(Blueprintable, BlueprintType)
class ESWEBUIMAIN_API UJavascriptBean : public UObject
{
GENERATED_UCLASS_BODY()
public:
//声明委托类型,是带一个参数的动态多播委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FJavascriptEventBlank, const TArray<FString>&, args);
//定义一个委托类型的属性
UPROPERTY(BlueprintAssignable, Category = "WebUI|Events")
FJavascriptEventBlank onJavascriptEventBlank;
}
2、 监听器对象的改造
为了让监听器对象在接收到js发送的消息后向蓝图发送消息,我们对在监听器中添加一些方法。
首先添加一个方面BindJS_Event,这是一个蓝图可用的方法,通过这个方法,向蓝图传递出一个UJavascriptBean对象
UFUNCTION(BlueprintCallable, Category = "WebUI_Library")
void BindJS_Event(const FString JS_Function_Name, UJavascriptBean* &eventTarget);
方法的实现如下:
void UESWebInterface::BindJS_Event(const FString JS_Function_Name, UJavascriptBean*&eventTarget){
if(!ESWebUI.IsValid())
return;
FString lowerName =JS_Function_Name.ToLower();
UPROPERTY()
UJavascriptBean* javascriptBean =NewObject(UJavascriptBean::StaticClass());
javascriptBean->AddToRoot();
javascriptBeans.Add(lowerName,javascriptBean);
eventTarget = javascriptBean;
}
我们看到,其实很简单,就是创建了一个UJavascriptBean对象出来。
其次是修改void UESWebInterface::triggerUE4EventWithBlank函数的实现,添加调用委托的代码:
void UESWebInterface::triggerUE4EventWithBlank(FStringjavascriptFunctionsName, FString arguments) {
FString lowerName =javascriptFunctionsName.ToLower();
UJavascriptBean** javascriptBeanPointer =javascriptBeans.Find(lowerName);
if(javascriptBeanPointer == nullptr) {
return;
}
UJavascriptBean* javascriptBean =*javascriptBeanPointer;
//UE_LOG(LogTemp, Display,TEXT("JAVASCRIPT:%s"), *arguments);
TArray args;
arguments.ParseIntoArray(args, TEXT(".;_|"), true);
AsyncTask(ENamedThreads::GameThread,[javascriptBean, args]() {
javascriptBean->onJavascriptEventBlank.Broadcast(args);
});
}
可以看到,我们BindJS_Event时候是可以指定一个FunctionName的,然后triggerUE4EventWithBlank可以更加函数名寻找相应的javascriptBean,并调用它的Broadcast方法让蓝图接收了。
3、 蓝图里的使用
蓝图里如何接收动态委托,如下图:
首先通过BindJS_Event获取到带动态委托的Javascriptbean对象,然后给javascriptBean对象的委托绑定上监听事件,并处理事件。
至此,我们完成了从网页的js脚本向UE c++对象“触发”消息,并发送消息到蓝图的全过程。Js脚本发送的消息,既可以在C++对象进行处理,也可以在蓝图进行处理。