灵韵 發表於 2021-1-20 15:03:00

UE4 开发之如何创建 iOS 平台插件

<h4 id="前言">前言</h4>
<p>在前俩篇文章中,我与大家分享了 UE4 开发如何配置 Xcode 的调试环境以及如何实现 UE4 引擎的按钮事件的响应,其实写这俩篇文章的目的就是为接下来的文章做一个引子,就好比是高级餐厅的前菜一样,接下来我们就要进入到正餐部分了.</p>
<p>既然 UE4 引擎具有跨平台的特性,那是否真的能像很多跨平台的开发工具所说的一样:Write once, run everywhere 呢!我调研过市面上主流的几个跨平台开发工具,也自己动手搭建过环境并写了几个 Demo,我只想跟大家说:谁用谁知道(捂脸笑)。</p>
<p>每个平台都有自己的特性,要想做到一份代码适配所有平台的难度是非常大的,因为我们不能保证每个功能都做到完美适配,所以怎么去解决当前面临的窘境呢!那就是我们要尽量减少跨平台功能的数量,只保证我们的核心功能在各个平台上能完美的适配,把一些辅助功能模块例如:登录,分享,数据采集等模块做成插件的方式集成到我们的项目中,这些插件都是用各个平台的原生代码开发的,iOS 平台就用 OC 或者 swift 开发,Android就用 java 或者 kotlin 开发,所以完全就不用去考虑它的平台兼容性问题了。</p>
<p>那接下来我们就开始今天的教程吧!</p>
<p><strong>作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:812157648,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!</strong></p>
<h4 id="ue4-开发之如何创建-ios-平台插件">UE4 开发之如何创建 iOS 平台插件</h4>
<p>UE4 为我们广大开发者提供了众多的插件供我们使用,正是有了这些插件的存在,才让游戏开发变得更快更简单,今天我跟大家分享一下如何编写 UE4 插件。</p>
<h5 id="创建-ue4-插件">创建 UE4 插件</h5>
<ul>
<li>在 UE 编辑器中,打开菜单栏中的编辑下拉菜单,选择插件选项</li>
</ul>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-c9137732be5d08cc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<ul>
<li>选择右下角的新插件</li>
</ul>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-0476bffa14461690.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<ul>
<li>选择截图上的 “第三方库”, 并填写插件名称,例如我起的为 “JJLoginNail”,以及填写作者以及描述</li>
</ul>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-6ed307f291a88728.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<ul>
<li>创建完成后,会在你的 Xcode 工程中自动引用一个 Plugins 文件夹</li>
</ul>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-98888a19a41880e8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<h5 id="处理第三方的-framework">处理第三方的 Framework</h5>
<p>用于测试,我自建了一个 iOS 静态库: TestLoginSDK.framework, 里面包含一个显示用户登录界面的接口,以及回调账户名的回调,参考代码如下:</p>
<pre><code>@protocol TestLoginDelegate &lt;NSObject&gt;

- (void)OnLoginResult:(NSString *)result;

@end

@interface JJLoginController : NSObject

+ (void)showLoginView:(UIViewController *)baseView JJCallBack:(id&lt;TestLoginDelegate&gt;) delegate;

@end

</code></pre>
<ul>
<li>在 ThirdParty 文件夹下创建 iosframeworks 文件夹,随后再该文件夹下创建 iosextend.embeddedframework 文件夹,并将第三方的 framework 放入其中</li>
</ul>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-2fd6778ae63260a7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<ul>
<li>将 iosextend.embeddedframework 添加到 .zip 压缩文件</li>
<li>将 iosframeworks 引入到 Xcode 工程中</li>
</ul>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-8963469b70c5b5fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<p>前期准备做好以后,我们现在进入到正式的敲代码阶段.</p>
<h4 id="编码阶段">编码阶段</h4>
<ol>
<li>在 Source 文件夹中插件为我们创建了以下源文件</li>
</ol>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-b542999a5133eed0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<ol start="2">
<li>打开 JJLoginNail.Build.cs,配置我们的第三方 framework 路径,配置信息如下:</li>
</ol>
<pre><code>// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class JJLoginNail : ModuleRules
{
        public JJLoginNail(ReadOnlyTargetRules Target) : base(Target)
        {
                PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;

                PublicIncludePaths.AddRange(
                        new string[] {
                                // ... add public include paths required here ...
                        }
                        );

                PrivateIncludePaths.AddRange(
                        new string[] {
                                // ... add other private include paths required here ...
                        }
                        );

                PublicDependencyModuleNames.AddRange(
                        new string[]
                        {
                                "Core",
                                "JJLoginNailLibrary",
                                "Projects",
                                // ... add other public dependencies that you statically link with here ...
                        }
                        );

                PrivateDependencyModuleNames.AddRange(
                        new string[]
                        {
                                // ... add private dependencies that you statically link with here ...       
                        }
                        );

                DynamicallyLoadedModuleNames.AddRange(
                        new string[]
                        {
                                // ... add any modules that your module loads dynamically here ...
                        }
                        );

      if (Target.Platform == UnrealTargetPlatform.IOS){
            PublicAdditionalFrameworks.Add(
                new Framework(
                  "TestLoginSDK",
                  "../ThirdParty/iosframeworks/iosextend.embeddedframework.zip"
                )
            );
      }
        }
}

</code></pre>
<ol start="3">
<li>打开 JJLoginNail.h 头文件,加入我们调用 API 的逻辑,以及处理回调的逻辑:</li>
</ol>
<pre><code>// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "Modules/ModuleManager.h"

#if PLATFORM_IOS
#import &lt;TestLoginSDK/TestLoginSDK.h&gt;

//用于处理 sdk 返回的回调
@interface FTestLoginResultModule : NSObject&lt;TestLoginDelegate&gt;

@end

#endif

class FJJLoginNailModule : public IModuleInterface
{
public:

        /** IModuleInterface implementation */
        virtual void StartupModule() override;
        virtual void ShutdownModule() override;

    /// 显示登录界面
    void showLogin();

private:
        /** Handle to the test dll we will load */
        void*        ExampleLibraryHandle;
};

</code></pre>
<h5 id="注意点">注意点:</h5>
<p>其中我们的 OC 类别需要用 PLATFORM_IOS 宏包起来,并 import 我们所需要的第三方framework。</p>
<ol start="4">
<li>打开 JJLoginNail.cpp 文件,实现上一步头文件中定义的接口以及在 OC 类中实现回调函数</li>
</ol>
<pre><code>// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#include "JJLoginNail.h"
#include "Core.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IPluginManager.h"
#include "JJLoginNailLibrary/ExampleLibrary.h"

#define LOCTEXT_NAMESPACE "FJJLoginNailModule"

#if PLATFORM_IOS
#include "IOSAppDelegate.h"
@implementation FTestLoginResultModule

- (void)OnLoginResult:(NSString *)result{
    NSLog(@"登录账号为: %@", result);
}

@end
#endif

#if PLATFORM_IOS
static FTestLoginResultModule *loginResult = nil;
#endif

void FJJLoginNailModule::showLogin(){
    #if PLATFORM_IOS
    dispatch_async(dispatch_get_main_queue(), ^{
      .IOSController JJCallBack:];
    });
    #endif
}

void FJJLoginNailModule::StartupModule()
{
        // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module

        // Get the base directory of this plugin
        FString BaseDir = IPluginManager::Get().FindPlugin("JJLoginNail")-&gt;GetBaseDir();

        // Add on the relative location of the third party dll and load it
        FString LibraryPath;
#if PLATFORM_WINDOWS
        LibraryPath = FPaths::Combine(*BaseDir, TEXT("Binaries/ThirdParty/JJLoginNailLibrary/Win64/ExampleLibrary.dll"));
#elif PLATFORM_MAC
    LibraryPath = FPaths::Combine(*BaseDir, TEXT("Source/ThirdParty/JJLoginNailLibrary/Mac/Release/libExampleLibrary.dylib"));
#endif // PLATFORM_WINDOWS

        ExampleLibraryHandle = !LibraryPath.IsEmpty() ? FPlatformProcess::GetDllHandle(*LibraryPath) : nullptr;

        if (ExampleLibraryHandle)
        {
                // Call the test function in the third party library that opens a message box
                ExampleLibraryFunction();
        }
        else
        {
                FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ThirdPartyLibraryError", "Failed to load example third party library"));
        }
}

void FJJLoginNailModule::ShutdownModule()
{
        // This function may be called during shutdown to clean up your module.For modules that support dynamic reloading,
        // we call this function before unloading the module.

        // Free the dll handle
        FPlatformProcess::FreeDllHandle(ExampleLibraryHandle);
        ExampleLibraryHandle = nullptr;
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FJJLoginNailModule, JJLoginNail)

</code></pre>
<h5 id="注意点-1">注意点:</h5>
<p>其中需要注意的是:</p>
<ul>
<li>在调用 SDK 接口的时候需要切换到主线程上,不然 App 会 Crash;</li>
<li>在使用 OC 代码的时候需要用 PLATFORM_IOS 进行包裹;</li>
<li>因为我们在实现逻辑中用到了 IOSAppDelegate 获取 RootView,所以我们需要在 JJLoginNail.Build.cs 加入一个配置 “ApplicationCore”,不然会提示 IOSAppDelegate.h 头文件找不到,配置如下:</li>
</ul>
<pre><code>PublicDependencyModuleNames.AddRange(
                        new string[]
                        {
                                "Core",
                                "JJLoginNailLibrary",
                                "Projects",
                "ApplicationCore"
                                // ... add other public dependencies that you statically link with here ...
                        }
                        );

</code></pre>
<ol start="5">
<li>回到我们插件文件夹上层文件夹的 Source 下,打开 UE 为我们自动创建的 Hydroger.Build.cs 文件,并在里面配置我们的插件名称 “JJLoginNail”, 代码如下:</li>
</ol>
<pre><code>// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class Hydroger : ModuleRules
{
        public Hydroger(ReadOnlyTargetRules Target) : base(Target)
        {
                PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

                PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "JJLoginNail"});

                PrivateDependencyModuleNames.AddRange(new string[] {});

                // Uncomment if you are using Slate UI
                // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

                // Uncomment if you are using online features
                // PrivateDependencyModuleNames.Add("OnlineSubsystem");

                // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
        }
}

</code></pre>
<ol start="6">
<li>在工程中打开我们在 UE 中创建的蓝图类 MyUserWidget.cpp,并在点击事件中加入调用插件接口的逻辑:</li>
</ol>
<pre><code>#include "MyUserWidget.h"
#include "JJLoginNail.h"

void UMyUserWidget::callStartFunction()
{
//    FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT("start"), TEXT("callStartFunction"));
    #if PLATFORM_IOS
    FJJLoginNailModule *module = new FJJLoginNailModule();
    module-&gt;showLogin();
    #endif
}

</code></pre>
<h5 id="注意点-2">注意点</h5>
<ol>
<li>别忘了在 Hydroger.Build.cs 中配置插件名称</li>
<li>include 插件头文件,例如:#include “JJLoginNail.h”</li>
<li>逻辑用 PLATFORM_IOS 包裹</li>
</ol>
<p>最后,插上真机设备,在工程中设置好相应的签名证书,CMD+R 将工程跑在真机设备上:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-4b25789190332957.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<ul>
<li>触发按钮事件,调用 SDK 里面的接口</li>
</ul>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-2e3adf997c6227bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<ul>
<li>获取回调信息,并打印</li>
</ul>
<p><img src="https://upload-images.jianshu.io/upload_images/25330009-2fcea3d7992cfd5c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<h2 id="结尾">结尾</h2>
<p>到这里,UE4 引擎创建 iOS 插件步骤就结束了,其实并不是很难,就是配置的环节比较多,如果中间某一个环节掉了链子,那我们创建的插件就无法被工程所引用,所以在插件的使用过程中,需要仔细的去检查我们的配置。</p>
<p>好了,本篇教程到这里就结束了,如果遇到问题可通过留言的方式与我交流,希望本篇文章对大家有所帮助,蟹蟹。</p>
<p>原文作者: HelloWord杰少<br>
原文地址:http://002ii.cn/PrTPz</p><br><br>
来源:https://www.cnblogs.com/fadaijun/p/14302952.html
頁: [1]
查看完整版本: UE4 开发之如何创建 iOS 平台插件