UE4:Android 平台开发实践指南

举报
HelloWorld杰少 发表于 2022/07/30 15:46:33 2022/07/30
【摘要】 前言在使用 UE 开发手游时,我们免不了要跟 Android 和 iOS 进行一些数据的交互,在这种情况下,就需要在代码中添加一些操作,使得在 Android 平台下 C++ 可以调用 Java,Java 可以调用 C++;iOS 平台下也是一样,C++ 可以调用 OC,OC 可以调用 C++。由于上次我已经实现了 UE 和 iOS 之间的互相调用,文章传送门,今天就继续和大家来讲讲 UE...

前言

在使用 UE 开发手游时,我们免不了要跟 Android 和 iOS 进行一些数据的交互,在这种情况下,就需要在代码中添加一些操作,使得在 Android 平台下 C++ 可以调用 Java,Java 可以调用 C++;iOS 平台下也是一样,C++ 可以调用 OC,OC 可以调用 C++。由于上次我已经实现了 UE 和 iOS 之间的互相调用,文章传送门,今天就继续和大家来讲讲 UE 和 Android 之间的互相调用。

Android 环境搭建

根据你安装的 UE 引擎的版本,来安装对应的 Android Studio。我的引擎版本是 4.25.4,所以根据 UE 文档的指示,安装了 Android Studio 3.5.3 版本。

image

安装完成后,查看一下 NDK 的版本,记得勾选右下脚的 “Show Package Detail”。

image

如果 NDK 没有安装为指定的版本,那请先勾选对的版本后,再下载安装。

构建首个 UE 工程

Android 环境配置好了以后,咱们就开始去构建一个 UE 工程,主要的思路是在界面 UI 上添加一个按钮,点击按钮后触发一个事件,该部分我已经在之前的教程中整理出来了,大家可以点击查看

Android 打包配置

接下来,在 UE 中去配置 Android 打包,在 “项目设置” 中找到 “打包” 选项,并设置为 “开发”。

image

如果需要 Release 的包,那就在下拉列表中,设置为“发行”。

image

在 “项目设置” 中找到 “平台” 选项,然后配置 “Android”,将这俩个栏目都设置为同意,接受SDK证书,以及填上安卓包名称。

image

image

在 Android SDK 中填上对应的路径。

image

PS:由于我是用的 Mac,所以在路径配置上和 Windows 的不同。

Android SDK 和 NDK 的的路径可以在 Android Studio 中查看到。

image

根据在 Android Studio 中查看到的 Android SDK 路径,找到对应 NDK 的路径。

image

然后就可以顺利打包了。

image

升级至 AndroidX

在 UE 中完成打包后,用 Android Studio 打开你的 UE 工程目录:Intermediate -> Android -> armv7 -> gradle,

image

在全局的 gradle.properties 加入以下俩个配置:

android.useAndroidX=true
android.enableJetifier=true

用 Android Studio 自带的升级功能,将项目升级至 AndroidX。

image

然后执行一下 gradle sync,最后将整体工程进行编译,编译成功就代表我们的工程已经升级至了 AndroidX。

Android AAR

在使用 UE4 开发 Android 时,经常需要接入第三方的库,于是就做个简单的案例吧!

在上文中我已将 UE 打包出来的 gradle 加载到了 Android Studio 中,然后依次 File -> New Module, 新建一个 AAR 库。

我暂时将这个库命名为 LoginSDK,目录结构如下:

image

这时候一个简单的第三方库就创建好了。在下面的文章中,会继续教大家如何去调用这个第三方库。

C++ 调用 Java

在 UE 中如何通过 C++ 去调用 Java 的函数呢,这时候就需要使用 JNI 调用来实现。在上文中,我们创建的 UE 工程已经实现了一个按钮点击事件,于是可以在这个事件中去调用 Java 函数。

那我们的 Java 函数应该写在哪呢!

UE 在打 Android 包的时候,提供了一个 GameActivity.java 的类,通过这个类,就可以让 UE 去调用 Java 代码。

于是,在 GameActivity.java 中,我们添加一个函数 public void AndroidThunkJava_InitName() 实现如下:

public void AndroidThunkJava_InitName()
{
    Log.debug("AndroidThunkJava_InitName");
}

通过 C++ 去调用 Java,首先需要知道,所要调用的 Java 函数的签名,关于这一知识点,我在这里就不多说了。回到我们的 C++ 代码中的按钮点击事件中,加入如下代码。

void UMyUserWidget::callLoginFunction()
{
#if PLATFORM_ANDROID
    if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
    {
        jmethodID GetPackageNameMethodID = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "AndroidThunkJava_InitName", "()V;", false);
        FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis,GetPackageNameMethodID);
    }
#endif
}

由于我们的 C++ 代码被修改过了,所以需要重新打包安卓项目,打包完成以后,在我们的 Android Studio 上跑起来,点击按钮,控制台中就会打印相应的日志。

C++ 调用 Java 代码已经成功实现了,但是在上文中,我们新建的一个第三方库的内容还没有讲完,那就继续来讲如何去调用安卓第三方库中的函数方法。

首先,在上文创建的 LoginSDK 库中,去实现一些逻辑:

  1. 创建一个 LoginActivity,并加上 EditText 和 Button 俩个控件
  2. 在 GameActivity 中跳转到 LoginActivity
  3. 点击 LoginActivity 中的按钮后,将 EditText 控件中的值回调给 GameActivity

在 GameActivity.java 中的 AndroidThunkJava_InitName() 函数中去调用 LoginSDK,代码如下。

public void AndroidThunkJava_InitGame()
	{
		Log.debug("AndroidThunkJava_InitGame");

		// call loginsdk
		Intent intent = new Intent(this, LoginActivity.class);

		String message = "UE4";
		intent.putExtra("com.example.MESSAGE", message);
		startActivityForResult(intent, 998);
	}

在 LoginActivity 中实现如下代码:

public class LoginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        Intent intent = getIntent();
        String message = intent.getStringExtra("com.example.MESSAGE");
        Log.d("message", message);
    }

    public void sendMessage(View view) {
        EditText editText = (EditText) findViewById(R.id.editText);
        String value = editText.getText().toString();

        // to C++
        Intent intent = new Intent();
        intent.putExtra("LOGIN", value);
        setResult(998, intent);
        finish();
    }
}

在 GameActivity 里的 AndroidThunkJava_InitGame 函数中,设置了 startActivityForResult(intent, 998) 的 RequestCode 是 998,所以在回调函数 protected void onActivityResult(int requestCode, int resultCode, Intent data) 中只要去监听 RequestCode = 998 就可获取回调值,代码如下:

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data)
	{
		if( requestCode == DOWNLOAD_ACTIVITY_ID)
		{
            // ...
		}
		else if (requestCode == 998){
			android.util.Log.d("test", "Received data from LoginSDK");
			String result = data.getStringExtra("LOGIN");
			android.util.Log.d("test", result);
			//nativeOnLoginCallBack(result);
		}
		else if( IapStoreHelper != null )
		{
			super.onActivityResult(requestCode, resultCode, data);
		}
		else
		{
			super.onActivityResult(requestCode, resultCode, data);
		}

		if(InitCompletedOK)
		{
			nativeOnActivityResult(this, requestCode, resultCode, data);
		}
	}

ps: RequestCode 的值你可以自定义

Java 调用 C++

UE 给我们的游戏生成的 GameActivity 中也声明了很多的 native 函数,例如:

public native int nativeGetCPUFamily();
	public native boolean nativeSupportsNEON();
	public native void nativeSetAffinityInfo(boolean bEnableAffinity, int bigCoreMask, int littleCoreMask);
	public native void nativeSetConfigRulesVariables(String[] KeyValuePairs);

	public native boolean nativeIsShippingBuild();
	public native void nativeSetAndroidStartupState(boolean bDebuggerAttached);
	public native void nativeSetGlobalActivity(boolean bUseExternalFilesDir, boolean bPublicLogFiles, String internalFilePath, String externalFilePath, boolean bOBBInAPK, String APKPath);
	public native void nativeSetObbFilePaths(String OBBMainFilePath, String OBBPatchFilePath);
	public native void nativeSetWindowInfo(boolean bIsPortrait, int DepthBufferPreference);
	public native void nativeSetObbInfo(String ProjectName, String PackageName, int Version, int PatchVersion, String AppType);
	public native void nativeSetAndroidVersionInformation( String AndroidVersion, String PhoneMake, String PhoneModel, String PhoneBuildNumber, String OSLanguage );

	public native void nativeSetSurfaceViewInfo(int width, int height);
	public native void nativeSetSafezoneInfo(boolean bIsPortrait, float left, float top, float right, float bottom);

	public native void nativeConsoleCommand(String commandString);
	public native void nativeVirtualKeyboardChanged(String contents);
	public native void nativeVirtualKeyboardResult(boolean update, String contents);
	public native void nativeVirtualKeyboardSendKey(int keyCode);
	public native void nativeVirtualKeyboardSendTextSelection(String contents, int selStart, int selEnd);
	public native void nativeVirtualKeyboardSendSelection(int selStart, int selEnd);

	public native void nativeInitHMDs();

	public native void nativeResumeMainInit();

	public native void nativeOnActivityResult(GameActivity activity, int requestCode, int resultCode, Intent data);

	public native void nativeGoogleClientConnectCompleted(boolean bSuccess, String accessToken);

	public native void nativeVirtualKeyboardShown(int left, int top, int right, int bottom);
	public native void nativeVirtualKeyboardVisible(boolean bShown);

	public native void nativeOnConfigurationChanged(boolean bPortrait);

	public native void nativeOnInitialDownloadStarted();
	public native void nativeOnInitialDownloadCompleted();

这些函数是在 C++ 中实现的,在 Java 中执行到这些函数会自动调用到引擎的 C++ 代码中, 我们可以自己在 GameActivity 添加自定义的 native 的函数。

我这里写一个简单的例子,往 GameActivity 添加一个 native 函数,并在 C++ 端实现。

public native void nativeOnLoginCallBack(String msg);

在 MyUserWidget 类的 C++ 代码中实现一个这样的函数即可:

#if PLATFORM_ANDROID
JNI_METHOD void Java_com_epicgames_ue4_GameActivity_nativeOnLoginCallBack(JNIEnv* jenv, jobject thiz, jstring msg)
{
    FString message;
    message = FJavaHelper::FStringFromParam(jenv, msg);
    UE_LOG(LogTemp, Log, TEXT("Java_com_epicgames_ue4_GameActivity_nativeOnLoginCallBack=[%s]"), *message);
}
#endif

ps: PLATFORM_ANDROID 宏不可少

com.epicgames.ue4 是 UE 生成的 GameActivity.java 的包名(package com.epicgames.ue4;)。

可以看到,在 C++ 中实现 JNIMETHOD 的函数名是根据以下的规则:

RType Java_PACKAGENAME_CLASSNAME_FUNCNAME(JNIEnv*,jobject thiz,Oher...)

注意:这个实现函数是可以放在任意的 C++ 中的

然后,我们就可以在 Java 端去执行 C++ 的逻辑了,我在 GameActivity 中收到 LoginActivity 的回调后,去调用 public native void nativeOnLoginCallBack(String msg);

代码如下:

if (requestCode == 998){
			android.util.Log.d("test", "Received data from LoginSDK");
			String result = data.getStringExtra("LOGIN");
			android.util.Log.d("test", result);
			nativeOnLoginCallBack(result);
		}

通过打印的日志就可以看到,Java 去调用 C++ 已经成功了。

image

那到这里,整个调用的流程就结束了。

总结

最后总结一下在这整个开发流程里面,我们需要关注的点:

  • Android Studio 版本
  • UE 编辑器中 sdk 路径配置
  • 打包:每次打包后 gradle 文件夹都会重置,记得第一次打包后先将 gradle 另存为,以后再打包就只要替换资源和 so 库即可
  • C++ to Java: JNI, GameActivity.java
  • Java to C++: native 函数,JNIMETHOD,PLATFORM_ANDROID 宏

我是杰少,如果您觉的我写的不错,那请给我 点赞+评论+收藏 后再走哦!

请你喝杯 ☕️ 点赞 + 关注哦~

  1. 阅读完记得给我点个赞哦,有👍 有动力
  2. 关注公众号— HelloWorld杰少,第一时间推送新姿势

最后,创作不易,如果对大家有所帮助,希望大家点赞支持,有什么问题也可以在评论区里讨论😄~**

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。