-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
981 lines (706 loc) · 115 KB
/
index.html
File metadata and controls
981 lines (706 loc) · 115 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
<!DOCTYPE html>
<html lang="zh-CN,zh-TW,en,default">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
<link rel="mask-icon" href="/images/logo.svg" color="#222">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/fancyapps/fancybox@3/dist/jquery.fancybox.min.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/pace-js@1/themes/blue/pace-theme-minimal.css">
<script src="//cdn.jsdelivr.net/npm/pace-js@1/pace.min.js"></script>
<script id="hexo-configurations">
var NexT = window.NexT || {};
var CONFIG = {"hostname":"c209.github.io","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":true,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
</script>
<meta property="og:type" content="website">
<meta property="og:title" content="C209's Blog">
<meta property="og:url" content="https://c209.github.io/index.html">
<meta property="og:site_name" content="C209's Blog">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="C209">
<meta name="twitter:card" content="summary">
<link rel="canonical" href="https://c209.github.io/">
<script id="page-configurations">
// https://hexo.io/docs/variables.html
CONFIG.page = {
sidebar: "",
isHome : true,
isPost : false,
lang : 'zh-CN'
};
</script>
<title>C209's Blog</title>
<noscript>
<style>
.use-motion .brand,
.use-motion .menu-item,
.sidebar-inner,
.use-motion .post-block,
.use-motion .pagination,
.use-motion .comments,
.use-motion .post-header,
.use-motion .post-body,
.use-motion .collection-header { opacity: initial; }
.use-motion .site-title,
.use-motion .site-subtitle {
opacity: initial;
top: initial;
}
.use-motion .logo-line-before i { left: initial; }
.use-motion .logo-line-after i { right: initial; }
</style>
</noscript>
</head>
<body itemscope itemtype="http://schema.org/WebPage">
<div class="container use-motion">
<div class="headband"></div>
<header class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-container">
<div class="site-nav-toggle">
<div class="toggle" aria-label="切换导航栏">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
</div>
<div class="site-meta">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<h1 class="site-title">C209's Blog</h1>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<div class="site-nav-right">
<div class="toggle popup-trigger">
</div>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="main-menu menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>
</li>
</ul>
</nav>
</div>
</header>
<div class="back-to-top">
<i class="fa fa-arrow-up"></i>
<span>0%</span>
</div>
<main class="main">
<div class="main-inner">
<div class="content-wrap">
<div class="content index posts-expand">
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN,zh-TW,en,default">
<link itemprop="mainEntityOfPage" href="https://c209.github.io/2021/10/22/UnrealEngine/UE4%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%85%A8%E9%9D%A2%E5%B1%8F%E9%80%82%E9%85%8D/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="C209">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="C209's Blog">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2021/10/22/UnrealEngine/UE4%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%85%A8%E9%9D%A2%E5%B1%8F%E9%80%82%E9%85%8D/" class="post-title-link" itemprop="url">UE4移动端全面屏适配</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2021-10-22 01:02:01 / 修改时间:01:02:51" itemprop="dateCreated datePublished" datetime="2021-10-22T01:02:01+08:00">2021-10-22</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Unreal-Engine/" itemprop="url" rel="index"><span itemprop="name">Unreal Engine</span></a>
</span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Unreal-Engine/Platform/" itemprop="url" rel="index"><span itemprop="name">Platform</span></a>
</span>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-comment"></i>
</span>
<span class="post-meta-item-text">Valine:</span>
<a title="valine" href="/2021/10/22/UnrealEngine/UE4%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%85%A8%E9%9D%A2%E5%B1%8F%E9%80%82%E9%85%8D/#valine-comments" itemprop="discussionUrl">
<span class="post-comments-count valine-comment-count" data-xid="/2021/10/22/UnrealEngine/UE4%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%85%A8%E9%9D%A2%E5%B1%8F%E9%80%82%E9%85%8D/" itemprop="commentCount"></span>
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>[本文档编写时使用UE4.21.0引擎]</p>
<p>可以在Config/DefaultDeviceProfiles.ini中,按照机型名对r.CustomUnsafeZones进行配置;经实际调试与测试,该参数仅用于编辑器中的显示与临时表现。可以根据平台,从配置中获得SafeZone.Landscape值,运行时引擎底层FDisplayMetrics::RebuildDisplayMetrics的Platform实现中查找配置计算SafeZone进行显示。 </p>
<p>适配工作中,美术的工作是在UMG中使用SafeZone控件进行边界偏移设置。SafeZone控件的本质是利用从平台相关的配置中读取四个边界的偏移数,对其子控件的边界进行Padding操作;它是严格按照偏移数据,强制进行像素偏移的Padding,而不管其中的子控件是否真的属于全屏贴边类型。(所以如果SafeZone下一层再嵌套一个SafeZone,你会发现偏移了两倍的距离。)一般SafeZone只加在全屏界面的最上层。</p>
<h1 id="iOS配置SafeZone"><a href="#iOS配置SafeZone" class="headerlink" title="iOS配置SafeZone"></a>iOS配置SafeZone</h1><p>对于iPhone X等机型,只需要在UserWidget中使用SafeZone控件,即可实现自适应。而SafeZone的各个方向是否使用可以在控件属性中进行设置。这里的SafeZone属性默认是从系统查询得到的安全区信息,如果需要配置,则可以在对应机型的DeviceProfile配置中,设置SafeZone.Landscape.Left/Top/Right/Bottom值。</p>
<p>iOS机型的DeviceProfile可以在{EngineDirection}/Config/BaseDeviceProfiles.ini中找到,可以使用{EngineDirection}/DefaultDeviceProfiles.ini中的值覆盖它:在编辑器中,Window-DeveloperTools-DeviceProfiles,点击SaveAsDefault即可将信息导出。然后在其中找到对应机型的DeviceProfile,添加形如如下字段:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">+CVars=SafeZone.Landscape.Left=30</span><br></pre></td></tr></table></figure>
<p>可以将其中的”left”替换为Top/Right/Bottom,它表示Landscape模式下到各方向的Padding。</p>
<h1 id="Android配置SafeZone"><a href="#Android配置SafeZone" class="headerlink" title="Android配置SafeZone"></a>Android配置SafeZone</h1><p>安卓要使用SafeZone读取的是UE4.20新增的安卓配置规则系统(AndroidConfigRulesSystem)中的配置值:</p>
<ul>
<li>首先,在{ProjectDirection,即项目目录}/Build/Android/目录下,创建configrules.txt文件。</li>
<li>在configrules.txt文件中,增加如下字段:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// version:1</span><br><span class="line">condition:((SourceType=SRC_DeviceModel,CompareType=CMP_Equal,MatchString="LYA-AL00")),(SafeZone_Landscape="50,0,0,0")</span><br></pre></td></tr></table></figure>
其中,第一行的version是必需的,它用于进行配置的版本比较,会使用版本号更新的版本。而第二行为适配示例,它表示当机型为”LYA-AL00”时,使用该SafeZone配置。配置中的各个值表示Landscape(横屏模式,竖屏为Portrait)下,到左/上/右/下的padding值。</li>
<li>创建xml文件:在{ProjectDirection}/Source/{ProjectName}/目录下,新建”MyGame_UPL.xml”文件。</li>
<li>在MyGame_UPL.xml文件中,添加如下代码:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="utf-8"?></span><br><span class="line"><root xmlns:android="http://schemas.android.com/apk/res/android"></span><br><span class="line"> </span><br><span class="line"> <init></span><br><span class="line"> </span><br><span class="line"> <setString result="ConfigRulesKey" value="This is my encryption key"/></span><br><span class="line"> </init></span><br><span class="line"> </span><br><span class="line"> <gradleCopies></span><br><span class="line"> <copyFile src="$S(BuildDir)/configrules.txt"</span><br><span class="line"> dst="$S(BuildDir)/gradle/app/configrules.txt"/></span><br><span class="line"> </gradleCopies></span><br><span class="line"> <gradleProperties></span><br><span class="line"> <insertValue value="CONFIGRULESTOOL_KEY=$S(ConfigRulesKey)"/></span><br><span class="line"> <insertNewline/></span><br><span class="line"> <insertValue value="CONFIGRULESTOOL_JAR=$S(AbsEngineDir)/Build/Android/Prebuilt/ConfigRulesTool/bin/ConfigRulesTool.jar"/></span><br><span class="line"> <insertNewline/></span><br><span class="line"> </gradleProperties></span><br><span class="line"> <gameActivityClassAdditions></span><br><span class="line"> <insertValue value="public String CONFIGRULES_KEY = &quot;$S(ConfigRulesKey)&quot;;"/></span><br><span class="line"> <insertNewline/></span><br><span class="line"> </gameActivityClassAdditions></span><br><span class="line"> <buildGradleAdditions></span><br><span class="line"> <insert></span><br><span class="line"> <![CDATA[</span><br><span class="line">task ProcessConfigRules(type: JavaExec) {</span><br><span class="line"> description 'Produces compressed and encrypted configules.bin.png in assets'</span><br><span class="line"> inputs.file file('configrules.txt')</span><br><span class="line"> outputs.file file('src/main/assets/configrules.bin.png')</span><br><span class="line"> main = "-jar"</span><br><span class="line"> args = [</span><br><span class="line"> "${CONFIGRULESTOOL_JAR}",</span><br><span class="line"> 'c',</span><br><span class="line"> 'configrules.txt',</span><br><span class="line"> 'src/main/assets/configrules.bin.png',</span><br><span class="line"> "${CONFIGRULESTOOL_KEY}"</span><br><span class="line"> ]</span><br><span class="line">}</span><br><span class="line">tasks.whenTaskAdded { task -></span><br><span class="line"> if (CONFIGRULESTOOL_JAR != null) {</span><br><span class="line"> if (task.name == 'assembleRelease') {</span><br><span class="line"> task.dependsOn 'ProcessConfigRules'</span><br><span class="line"> }</span><br><span class="line"> if (task.name == 'assembleDebug') {</span><br><span class="line"> task.dependsOn 'ProcessConfigRules'</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> ]]></span><br><span class="line"> </insert></span><br><span class="line"> </buildGradleAdditions></span><br><span class="line"></root></span><br></pre></td></tr></table></figure>
其中,ConfigRulesKey为加密秘钥。</li>
<li>修改项目中的{ProjectName}.Build.cs文件,位于{ProjectDirection}/Source/{ProjectName}/中,在其中的添加如下代码:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">if (Target.Platform == UnrealTargetPlatform.Android)</span><br><span class="line">{</span><br><span class="line"> // Add UPL to add configrules.txt to our APK</span><br><span class="line"> string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath);</span><br><span class="line"> AdditionalPropertiesForReceipt.Add("AndroidPlugin", System.IO.Path.Combine(PluginPath, "MyGame_UPL.xml"));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
</ul>
<h2 id="安卓Manifest配置启用全屏设置"><a href="#安卓Manifest配置启用全屏设置" class="headerlink" title="安卓Manifest配置启用全屏设置"></a>安卓Manifest配置启用全屏设置</h2><p>AndroidManifest.xml是引擎根据ProjectSettings中的设置生成的,它位于于{ProjectDirection}/Intermediate/Android/APK/中。</p>
<p>对于安卓机型,需要在ProjectSettings-Platforms-Android中进行配置:</p>
<p>首先勾选Enable FullScreen Immersive。<br>之后,配置Extra Settings for <application> section,添加对应机型需要的meta-data。例如:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><meta-data android:name="notch.config" android:value="portrait|landscape" /></span><br><span class="line"><meta-data android:name="android.notch_support" android:value="true" /></span><br></pre></td></tr></table></figure>
<p>然后,在Extra Tags for GameActivity node中添加以下结点:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">//如果该值为true,则下面的ratio没有必要配置</span><br><span class="line">android:resizeableActivity="false" </span><br><span class="line"></span><br><span class="line">android:maxAspectRatio="2.5"</span><br></pre></td></tr></table></figure>
<p>事实上还需要设置一个mata-data,引擎已经帮我们在Manifest中添加了。它是实现安卓全屏的关键,在此特意强调一下:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><meta-data android:name="android.max_aspect" android:value="2.50" /></span><br></pre></td></tr></table></figure>
<h2 id="AndroidManifest-xml配置中的坑"><a href="#AndroidManifest-xml配置中的坑" class="headerlink" title="AndroidManifest.xml配置中的坑"></a>AndroidManifest.xml配置中的坑</h2><p>当targetSdkVersion<=23时,会无法适配全面屏,而targetSdkVersion>23时候不会出现这种情况。而targetSdkVersion是在ProjectSettings-Platform-Android中可以进行配置。</p>
<p>要为 Android 8.0(API 级别 26)和更高版本设置最大长宽比,需要在 <activity> 标记中使用 android:MaxAspectRatio 声明最大比例。 以下示例演示了如何声明 2.4 的最大长宽比:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><!-- Render on full screen up to screen aspect ratio of 2.4 --></span><br><span class="line"><!-- Use a letterbox on screens larger than 2.4 --></span><br><span class="line"><activity android:maxAspectRatio="2.4"></span><br><span class="line"> ...</span><br><span class="line"></activity></span><br></pre></td></tr></table></figure>
<p>对于 Android 7.1 及更低版本,则是要在 <application> 元素中添加一个 名为 android.max_aspect 的 <meta-data> 元素, 如下所示:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><!-- Render on full screen up to screen aspect ratio of 2.4 --></span><br><span class="line"><!-- Use a letterbox on screens larger than 2.4 --></span><br><span class="line"><meta-data android:name="android.max_aspect" android:value="2.4" /></span><br></pre></td></tr></table></figure>
<p><strong>注意:如果设置了最大长宽比,请勿忘记同时设置 android:resizeableActivity false。 否则,最大长宽比没有任何作用。</strong></p>
<h3 id="额外的坑"><a href="#额外的坑" class="headerlink" title="额外的坑"></a>额外的坑</h3><p>由于此次适配使用的引擎版本是4.21.0,当targetSDKVersion版本号为28(即对应Android9)时,会出现四指召唤出的引擎自带控制台无法执行输入操作。需要降低targetSDKVersion版本。</p>
<h1 id="完成上述工作之后的维护工作"><a href="#完成上述工作之后的维护工作" class="headerlink" title="完成上述工作之后的维护工作"></a>完成上述工作之后的维护工作</h1><h3 id="获取设备型号名称"><a href="#获取设备型号名称" class="headerlink" title="获取设备型号名称"></a>获取设备型号名称</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"> FString GetDeviceInfo()</span><br><span class="line"> {</span><br><span class="line">#if PLATFORM_IOS</span><br><span class="line"> FString ret = FIOSPlatformMisc::GetDefaultDeviceProfileName();</span><br><span class="line">#elif PLATFORM_ANDROID</span><br><span class="line"> FString ret = FAndroidMisc::GetDeviceModel();</span><br><span class="line">#else</span><br><span class="line"> FString ret = FPlatformMisc::GetDeviceMakeAndModel();</span><br><span class="line">#endif</span><br><span class="line"> return ret;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h2 id="iOS机型扩展"><a href="#iOS机型扩展" class="headerlink" title="iOS机型扩展"></a>iOS机型扩展</h2><p>按理说引擎升级的时候也会将最新的iOS设备的相关配置信息更新进去,但考虑到实际项目中很难频繁更新引擎,所以针对机型的更新还是需要自己来完成。iOS设备的SafeZone信息是配置在DeviceProfiles.ini中的,参照IOSDeviceProfileSelectorModule中有关选择使用Profile信息的源码,使用的是iOS设备的设备名作为索引进行查询的。如需扩展,则需要修改其中调用的GetDefaultDeviceProfileName以获取合适的设备名,因为其中目前是使用硬编码实现的;之后在DeviceProfile.ini中按照对应的设备名进行添加即可。<br>按照BaseDeviceProfiles.ini中的格式,在DefaultDeviceProfiles.ini中添加新设备的DeviceProfile,并在其中添加SafeZone.Landscape.XXX值即可。注意,可以添加r.CustomUnsafeZones来提供编辑器中的显示需求。</p>
<h2 id="Android机型扩展"><a href="#Android机型扩展" class="headerlink" title="Android机型扩展"></a>Android机型扩展</h2><p>如果需要在编辑器中显示出UnsafeZones,也可以像上述iOS设备那样,在DefaultDeviceProfiles.ini中添加该机型的DeviceProfile,并添加r.CUstomUnsafeZones。注意,建议使用编辑器中DeveloperTools中的DeviceProfile工具来添加和编辑新机型。<br>在Build/Android/configrules.txt中,添加新机型的SafeZone信息。具体配置格式见官方文档关于AndroidConfigRulesSystem中的相关内容。</p>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN,zh-TW,en,default">
<link itemprop="mainEntityOfPage" href="https://c209.github.io/2021/10/22/UnrealEngine/UE4%E8%B7%A8%E5%B9%B3%E5%8F%B0%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="C209">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="C209's Blog">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2021/10/22/UnrealEngine/UE4%E8%B7%A8%E5%B9%B3%E5%8F%B0%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/" class="post-title-link" itemprop="url">UE4跨平台插件开发</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2021-10-22 00:33:59 / 修改时间:01:02:44" itemprop="dateCreated datePublished" datetime="2021-10-22T00:33:59+08:00">2021-10-22</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Unreal-Engine/" itemprop="url" rel="index"><span itemprop="name">Unreal Engine</span></a>
</span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Unreal-Engine/Platform/" itemprop="url" rel="index"><span itemprop="name">Platform</span></a>
</span>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-comment"></i>
</span>
<span class="post-meta-item-text">Valine:</span>
<a title="valine" href="/2021/10/22/UnrealEngine/UE4%E8%B7%A8%E5%B9%B3%E5%8F%B0%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/#valine-comments" itemprop="discussionUrl">
<span class="post-comments-count valine-comment-count" data-xid="/2021/10/22/UnrealEngine/UE4%E8%B7%A8%E5%B9%B3%E5%8F%B0%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/" itemprop="commentCount"></span>
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="前置基础学习"><a href="#前置基础学习" class="headerlink" title="前置基础学习"></a>前置基础学习</h1><p>这部分内容可以简单快速浏览一下。也可以先略过此部分内容,在之后的内容中涉及到相关的部分再回过来查看相关细节。</p>
<h3 id="引擎工具"><a href="#引擎工具" class="headerlink" title="引擎工具"></a>引擎工具</h3><ul>
<li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/107270501">UE4模块详解,了解如何创建模块</a></li>
<li><a target="_blank" rel="noopener" href="https://docs.unrealengine.com/zh-CN/Programming/BuildTools/UnrealBuildTool/ThirdPartyLibraries/index.html">第三方库官方文档</a></li>
<li><a target="_blank" rel="noopener" href="https://www.ue4community.wiki/Legacy/Linking_Dlls">Linking_Dlls</a></li>
<li><a target="_blank" rel="noopener" href="https://docs.unrealengine.com/zh-CN/Platforms/Mobile/Android/AndroidConfigRulesSystem/index.html">安卓配置规则系统</a></li>
<li><a target="_blank" rel="noopener" href="https://docs.unrealengine.com/zh-CN/Platforms/Mobile/UnrealPluginLanguage/index.html">UnrealPluginLanguage</a></li>
<li><a target="_blank" rel="noopener" href="https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/UnrealBuildTool/System/UnrealPluginLanguage.cs">UPL源码</a>:其中的注释即上面的文档,非常全面</li>
<li><a target="_blank" rel="noopener" href="https://qiita.com/shiena/items/fe0e4cc1de4ddbaa60f0">虚幻插件语言参考</a></li>
</ul>
<h3 id="SDK接入相关"><a href="#SDK接入相关" class="headerlink" title="SDK接入相关"></a>SDK接入相关</h3><ul>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/lunweiwangxi3/article/details/83187840">UE4中使用第三方库</a></li>
<li><a target="_blank" rel="noopener" href="https://gcloud.qq.com/document/5bf2a669e974d4497c62f6ca">腾讯GVoice接入文档</a>:用于参考其中的API设计</li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/lqpgfz/article/details/52236159">虚幻4安卓平台SDK的接入</a>:可以参考其中cpp调用java的方式</li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/or_7r_ccl/article/details/72899036">虚幻4中,android第三方类库的接入</a>:很直观的安卓接入教程</li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/Jingsongmaru/article/details/104588434">UE4接入Android第三方库</a></li>
<li><a target="_blank" rel="noopener" href="https://imzlp.me/notes/">UE4学习笔记</a> :其中关于“跨平台”部分的博客笔记</li>
</ul>
<h3 id="基础相关"><a href="#基础相关" class="headerlink" title="基础相关"></a>基础相关</h3><ul>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/zxw136511485/article/details/52777286">Android中aar和jar文件的认识</a></li>
<li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/aba734d5b5cd">JNI基础</a></li>
</ul>
<h1 id="实现流程"><a href="#实现流程" class="headerlink" title="实现流程"></a>实现流程</h1><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul>
<li>==<strong>debug用的lib和dll切勿发布出去</strong>==</li>
<li>如果是使用Git作为版本控制工具,那么建议注意将dll、exe之类的较大文件使用LFS进行管理</li>
</ul>
<h2 id="一-创建项目插件"><a href="#一-创建项目插件" class="headerlink" title="一. 创建项目插件"></a>一. 创建项目插件</h2><p>使用引擎的标准创建插件流程,创建一个第三方库插件。其中会带有Example的工程和库,直接删掉。</p>
<h3 id="1-配置插件类型"><a href="#1-配置插件类型" class="headerlink" title="1. 配置插件类型"></a>1. 配置插件类型</h3><p>将插件中的uplugin类型,设置为需要的类型,这里是”Runtime”。</p>
<h3 id="2-配置插件的build-cs"><a href="#2-配置插件的build-cs" class="headerlink" title="2. 配置插件的build.cs"></a>2. 配置插件的build.cs</h3><p>需要将插件目录中的待include项添加至IncludePaths。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"> PublicIncludePaths.AddRange(</span><br><span class="line"> new string[] {</span><br><span class="line"> string.Format("{0}/Public", ModuleDirectory),</span><br><span class="line"> // ... add public include paths required here ...</span><br><span class="line"> }</span><br><span class="line"> );</span><br><span class="line"> </span><br><span class="line"></span><br><span class="line">PrivateIncludePaths.AddRange(</span><br><span class="line"> new string[] {</span><br><span class="line"> string.Format("{0}/Private", ModuleDirectory),</span><br><span class="line"> // ... add other private include paths required here ...</span><br><span class="line"> }</span><br><span class="line"> );</span><br></pre></td></tr></table></figure>
<p>PublicDependencyModuleNames也是需要引入ThirdParty下的那个模块的。如果使用的是引擎的插件创建流程,那么这里已经帮你添加好了。</p>
<h2 id="二-接入Win64平台库"><a href="#二-接入Win64平台库" class="headerlink" title="二. 接入Win64平台库"></a>二. 接入Win64平台库</h2><p>目前接入的第三方库是.dll+.lib+.h的标准隐式链接流程,然后有一个进程exe用来实现具体功能,dll负责进行进程间通信。</p>
<h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><ul>
<li>关于lib和dll文件的基础知识,可以参考这篇文档:<a target="_blank" rel="noopener" href="https://hellocode.blog.csdn.net/article/details/52252478">lib和dll文件的区别和联系</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/nodeathphoenix/article/details/7550546">Windows编程 MD(d)、MT(d)编译选项的区别</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/lunweiwangxi3/article/details/87184775">C++编译动态库第三方库及使用</a></li>
<li><a target="_blank" rel="noopener" href="https://www.cnblogs.com/sevenyuan/p/7161516.html">UE4静态库与动态库的导入与使用</a>:最后引入库的流程不够标准</li>
</ul>
<h3 id="1-导入dll和exe文件"><a href="#1-导入dll和exe文件" class="headerlink" title="1. 导入dll和exe文件"></a>1. 导入dll和exe文件</h3><p>在插件source目录下,创建ThirdParty/xxxLibrary/Windows/x64/Release目录,其中放置dll文件。</p>
<p>创建ThirdParty/xxxLibrary/Windows/x64/bin目录,其中放置exe和相关的dll文件。</p>
<h3 id="2-配置ThirdParty的build-cs"><a href="#2-配置ThirdParty的build-cs" class="headerlink" title="2. 配置ThirdParty的build.cs"></a>2. 配置ThirdParty的build.cs</h3><p>ThirdParty目录下是有build.cs的,在它里面配置的是库的相关路径和名称等依赖信息。</p>
<p>针对WIN64平台,配置库依赖:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"> if (Target.Platform == UnrealTargetPlatform.Win64 || Target.Platform == UnrealTargetPlatform.Win32)</span><br><span class="line"> {</span><br><span class="line"> // Get the paths</span><br><span class="line">string basePlatformPath = Path.Combine(ModuleDirectory, "Windows");</span><br><span class="line"> string architectureName = Target.Platform == UnrealTargetPlatform.Win64 ? "x64" : "x86";</span><br><span class="line"> string platformPath = Path.Combine(basePlatformPath, architectureName);</span><br><span class="line"> string libPath = Path.Combine(platformPath, "Release"); // We shouldn't use Debug</span><br><span class="line"></span><br><span class="line"> // Add the include folder</span><br><span class="line"> PublicIncludePaths.Add(Path.Combine(basePlatformPath, "include"));</span><br><span class="line"></span><br><span class="line"> // Add the lib</span><br><span class="line"> string libName = "xxx.lib";</span><br><span class="line"> //PublicLibraryPaths.Add(libPath);</span><br><span class="line"> PublicAdditionalLibraries.Add(Path.Combine(libPath, libName));</span><br><span class="line"></span><br><span class="line"> // Copy dll to binary dir, for auto load by engine with FPlatformProcess::GetDllHandle</span><br><span class="line"> string dllName = "xxx.dll";</span><br><span class="line"> string dllPath = Path.Combine(libPath, dllName);</span><br><span class="line"> //PublicDelayLoadDLLs.Add(dllName);</span><br><span class="line"> string targetPath = "$(ProjectDir)/Binaries/" + Target.Platform.ToString() + "/ThirdParty/xxxLibrary";</span><br><span class="line"> RuntimeDependencies.Add(Path.Combine(targetPath, dllName), dllPath);</span><br><span class="line"> /* // Old methods</span><br><span class="line">const string binariesDir = Path.Combine(ModuleDirectory, "../../Binaries", Target.Platform.ToString());</span><br><span class="line"> System.Console.WriteLine("src dll=" + dllPath + " dst dir=" + binariesDir);</span><br><span class="line"> if (!Directory.Exists(binariesDir))</span><br><span class="line"> Directory.CreateDirectory(binariesDir);</span><br><span class="line"> try</span><br><span class="line"> {</span><br><span class="line"> File.Copy(dllPath, Path.Combine(binariesDir, dllName), true);</span><br><span class="line"> }</span><br><span class="line"> catch (Exception e)</span><br><span class="line"> {</span><br><span class="line"> System.Console.WriteLine("copy dll exception,maybe have exists,err=", e.ToString());</span><br><span class="line"> }</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line"> // Copy the external exe file</span><br><span class="line"> RuntimeDependencies.Add(Path.Combine(targetPath, "bin/..."), Path.Combine(basePlatformPath, "bin/...")); // default as StagedFileType.NonUFS</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>这里RuntimeDependencies.Add的重载版本,将dll自动复制到了targetPath目录下,便于加载;如果使用默认版本,则是仅在打包时将dll拷贝到可执行文件旁[注意,这里官方文档翻译是说在旁边,然而实际上却是直接按指定的路径进行拷贝]。</p>
<p>需要注意,==这里有个坑==:如果用的是$BinaryOutputDir作为目标路径,那么它指的是当前编译环境下的输出路径,如果是编辑器,那么这里就是Plugins/xxPlugin/Binaries/Win64,因为现在做的是插件,所以自然就在插件的binary输出路径下。但是当打包时,该路径就变为了exe所在目录,即ProjectDir/Binaries/Win64。所以代码里不能直接写死了从插件目录中加载dll。</p>
<p>注:一些博客里,开头会获取一下ThirdParty目录。这里不需要这样做,因为现在库直接就是ThirdParty下具体的一个module了,所以ModuleDirectory就是ThirdParty目录。另外,如果是lib库,那么要把该库名加入至PublicAdditionalLibraries以及PublicIncludePaths。</p>
<h3 id="3-引入库中的内容"><a href="#3-引入库中的内容" class="headerlink" title="3. 引入库中的内容"></a>3. 引入库中的内容</h3><p>首先替换生成的插件中,模块的.h和.cpp中关于ExampleLibrary的相关内容。使用FPlatformProcess::GetDllHandle(*LibraryPath)方法来对dll进行加载,同时记得使用FPlatformProcess::FreeDllHandle()清理dll句柄。</p>
<p>过程中如果需要用到WindowsApi相关的方法和对象,则可以通过以下方式引入:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#include "Windows/WindowsHWrapper.h"</span><br><span class="line"></span><br><span class="line">// 如果一定要使用一些上述头文件未支持的api和宏</span><br><span class="line">#include "AllowWindowsPlatformTypes.h"</span><br><span class="line">#include "shellapi.h"</span><br><span class="line">// ... Something using Windows.h</span><br><span class="line">#include "HideWindowsPlatformTypes.h"</span><br></pre></td></tr></table></figure>
<h2 id="三-接入Android平台库"><a href="#三-接入Android平台库" class="headerlink" title="三. 接入Android平台库"></a>三. 接入Android平台库</h2><p>安卓平台的库使用方式为调用源生android接口。需要接入jar和so两个文件。总体流程参考这篇:<a target="_blank" rel="noopener" href="https://blog.csdn.net/JMcc_/article/details/105512351">UE4 Android第三方库导入,JNI调用详解</a>,写的可以说是非常清晰了。</p>
<h3 id="1-导入-jar和-so文件"><a href="#1-导入-jar和-so文件" class="headerlink" title="1. 导入.jar和.so文件"></a>1. 导入.jar和.so文件</h3><p>在插件source目录下,创建ThirdParty/xxxLibrary/Android目录,其中放置jar和各个架构(按目录)下的.so文件。关于文件类型的一些细节,可以参考这篇:<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/8a06bce66380">so、arr、jar的理解和引用</a></p>
<h3 id="2-配置ThirdParty的build-cs-1"><a href="#2-配置ThirdParty的build-cs-1" class="headerlink" title="2. 配置ThirdParty的build.cs"></a>2. 配置ThirdParty的build.cs</h3><p>针对安卓平台:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"> else if (Target.Platform == UnrealTargetPlatform.Android)</span><br><span class="line"> {</span><br><span class="line">// For using JNI</span><br><span class="line"> PublicDependencyModuleNames.AddRange(new string[] { "Launch" });</span><br><span class="line"></span><br><span class="line"> string basePlatformPath = Path.Combine(ModuleDirectory, "Android");</span><br><span class="line"> string[] architectureNames =</span><br><span class="line"> {</span><br><span class="line"> "arm64-v8a", //arm64</span><br><span class="line"> "armeabi-v7a", //armv7</span><br><span class="line"> "x86",</span><br><span class="line"> "x86_64",</span><br><span class="line"> };</span><br><span class="line"> string[] libNames = {</span><br><span class="line"> "libxxx1.so",</span><br><span class="line"> "libxxx2.so",</span><br><span class="line"> "libxxx3.so",</span><br><span class="line"> "libxxx4.so",</span><br><span class="line"> }; // lib开头是so库的固定格式,第三方库给你的肯定都会是这样的名称</span><br><span class="line">foreach(var archName in architectureNames)</span><br><span class="line">{</span><br><span class="line"> foreach(var libName in libNames)</span><br><span class="line"> {</span><br><span class="line"> PublicAdditionalLibraries.Add(Path.Combine(basePlatformPath, archName, libName));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath);</span><br><span class="line"> AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(PluginPath, "XXX_APL.xml"));</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="3-编写UPL脚本"><a href="#3-编写UPL脚本" class="headerlink" title="3. 编写UPL脚本"></a>3. 编写UPL脚本</h3><p>看build.cs最后两句,我们需要为安卓平台编写对应的UPL脚本文件,来执行向AndroidManifest写入信息等工作。具体细节可以参考这篇:<a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/34374244">虚幻插件语言参考</a></p>
<p>以下是示例实现,这里暂不方便放出项目代码,就在GVoice的基础上加了一些示例,如为自己的game activity添加java函数等。当然这个文件照抄腾讯的GVoice没毛病,自己在需要的稍微改两下即可。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="utf-8"?></span><br><span class="line"></span><br><span class="line"><!--LogicCore additions--></span><br><span class="line"><root xmlns:android="http://schemas.android.com/apk/res/android"></span><br><span class="line"> <!-- 初始化--></span><br><span class="line"> <init></span><br><span class="line"> <log text="GVoiceSDK init"/></span><br><span class="line"> </init></span><br><span class="line"></span><br><span class="line"> <!--在ndk-build编译之前,从Intermediate/Android/APK中拷贝或删除文件--></span><br><span class="line"> <prebuildCopies></span><br><span class="line"> <log text="GVoice SDK Copy Dirs Begin. PluginDir is $S(PluginDir), BuildDir is $S(BuildDir)"/></span><br><span class="line"></span><br><span class="line"> <!--copyDir src="$S(PluginDir)/../ThirdParty/GCloudSDKLibrary/Android/" dst="$S(BuildDir)"/></span><br><span class="line"> <copyDir src="$S(PluginDir)/../../Build/Android/" dst="$S(BuildDir)"/ --></span><br><span class="line"> </prebuildCopies></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <!-- optional updates applied to AndroidManifest.xml --></span><br><span class="line"> <androidManifestUpdates></span><br><span class="line"> <log text="androidManifestUpdates begin" /></span><br><span class="line"></span><br><span class="line"> <log text="addPermission begin" /></span><br><span class="line"> <!-- TODO SDK接入必须权限模块 START --></span><br><span class="line"> <addPermission android:name="android.permission.ACCESS_NETWORK_STATE" /></span><br><span class="line"> <addPermission android:name="android.permission.ACCESS_WIFI_STATE" /></span><br><span class="line"> </span><br><span class="line"> <!--GVoice Permission start--></span><br><span class="line"> <addPermission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/></span><br><span class="line"> <addPermission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/></span><br><span class="line"> <addPermission android:name="android.permission.RECORD_AUDIO"/></span><br><span class="line"> <addPermission android:name="android.permission.BLUETOOTH" /></span><br><span class="line"> <addPermission android:name="android.permission.BLUETOOTH_ADMIN"/></span><br><span class="line"> <addPermission android:name="android.permission.READ_PHONE_STATE"/></span><br><span class="line"> <!--GVoice Permission end--></span><br><span class="line"> </androidManifestUpdates></span><br><span class="line"> </span><br><span class="line"> <!--拷贝文件或目录到Intermediate/Android/APK --></span><br><span class="line"> <resourceCopies></span><br><span class="line"> <log text="GVoice SDK Copy Files Begin"/></span><br><span class="line"> </span><br><span class="line"> <log text="Start copy libs..." /></span><br><span class="line"> <copyDir src="$S(PluginDir)/../ThirdParty/GVoiceSDKLibrary/Android/GCloudVoice/libs//" dst="$S(BuildDir)/libs/"/></span><br><span class="line"> <log text="Start copy res..." /></span><br><span class="line"> <copyDir src="$S(PluginDir)/../ThirdParty/GVoiceSDKLibrary/Android/assets/" dst="$S(BuildDir)/assets"/></span><br><span class="line"> </span><br><span class="line"> </resourceCopies></span><br><span class="line"></span><br><span class="line"> <!-- GameActivity.java中导入类 --></span><br><span class="line"> <gameActivityImportAdditions></span><br><span class="line"> <insert></span><br><span class="line"> import com.tencent.gcloud.voice.GCloudVoiceEngine;</span><br><span class="line"> </insert></span><br><span class="line"> </gameActivityImportAdditions></span><br><span class="line"></span><br><span class="line"> <!--GameActivity.java导完类之后要做的事情 --></span><br><span class="line"> <gameActivityPostImportAdditions></span><br><span class="line"> </gameActivityPostImportAdditions></span><br><span class="line"></span><br><span class="line"> <!-- GameActivity.java类中添加代码,这里参考的是Vivox的写法,不是GVoice的 --></span><br><span class="line"> <gameActivityClassAdditions></span><br><span class="line"> <insert></span><br><span class="line"> public void AndroidThunkJava_Vivox_Init()</span><br><span class="line"> {</span><br><span class="line"> JniHelpers.init(getApplicationContext());</span><br><span class="line"> }</span><br><span class="line"> </insert></span><br><span class="line"> </gameActivityClassAdditions></span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <!--GameActivity.java onCreate添加代码 --></span><br><span class="line"> <gameActivityOnCreateAdditions></span><br><span class="line"> <insert></span><br><span class="line"> </span><br><span class="line"> GCloudVoiceEngine.getInstance().init(getApplicationContext(), this); </span><br><span class="line"> android.util.Log.i("GVoiceJava", "after java device init");</span><br><span class="line"></span><br><span class="line"> </insert></span><br><span class="line"> </gameActivityOnCreateAdditions></span><br><span class="line"></span><br><span class="line"> <!--GameActivity.java onDestroy添加代码--></span><br><span class="line"> <gameActivityOnDestroyAdditions></span><br><span class="line"> </gameActivityOnDestroyAdditions></span><br><span class="line"> </span><br><span class="line"> <!--GameActivity.java onStart添加代码--></span><br><span class="line"> <gameActivityOnStartAdditions></span><br><span class="line"> </gameActivityOnStartAdditions></span><br><span class="line"> </span><br><span class="line"> <!--GameActivity.java OnRestart添加代码--></span><br><span class="line"> <gameActivityOnRestartAdditions></span><br><span class="line"> </gameActivityOnRestartAdditions></span><br><span class="line"> </span><br><span class="line"> <!--GameActivity.java onStop添加代码--></span><br><span class="line"> <gameActivityOnStopAdditions></span><br><span class="line"> </gameActivityOnStopAdditions></span><br><span class="line"> </span><br><span class="line"> <!--GameActivity.java onPause添加代码--></span><br><span class="line"> <gameActivityOnPauseAdditions></span><br><span class="line"> <insert> </span><br><span class="line"> try</span><br><span class="line"> {</span><br><span class="line"> android.util.Log.i("GVoiceJava", "call gvoice pause interface");</span><br><span class="line"> GCloudVoiceEngine.getInstance().Pause(); </span><br><span class="line"> }catch(Exception e)</span><br><span class="line"> {</span><br><span class="line"> }</span><br><span class="line"> </insert></span><br><span class="line"> </gameActivityOnPauseAdditions></span><br><span class="line"> </span><br><span class="line"> <!--GameActivity.java onResume添加代码--></span><br><span class="line"> <gameActivityOnResumeAdditions></span><br><span class="line"> <insert> </span><br><span class="line"> try</span><br><span class="line"> {</span><br><span class="line"> android.util.Log.i("GVoiceJava", "call gvoice resume interface");</span><br><span class="line"> GCloudVoiceEngine.getInstance().Resume(); </span><br><span class="line"> }catch(Exception e)</span><br><span class="line"> {</span><br><span class="line"> }</span><br><span class="line"> </insert></span><br><span class="line"> </gameActivityOnResumeAdditions></span><br><span class="line"> </span><br><span class="line"> <!-- GameActivity.java onActivityResult添加代码 --></span><br><span class="line"> <gameActivityOnActivityResultAdditions></span><br><span class="line"> </gameActivityOnActivityResultAdditions></span><br><span class="line"> </span><br><span class="line"> <!--GameActivity.java OnNewIntent添加代码--></span><br><span class="line"> <gameActivityOnNewIntentAdditions></span><br><span class="line"> </gameActivityOnNewIntentAdditions></span><br><span class="line"></span><br><span class="line"> <!--在libUE4.so库加载之前需要加载的库--></span><br><span class="line"> <soLoadLibrary></span><br><span class="line"> <loadLibrary name="GCloudVoice" failmsg="gcloudvoice library not loaded and required!"/></span><br><span class="line"> </soLoadLibrary></span><br><span class="line"></span><br><span class="line"> <proguardAdditions></span><br><span class="line"></span><br><span class="line"> <insert></span><br><span class="line"> -keep class com.tencent.apollo.** {</span><br><span class="line"> *;</span><br><span class="line"> }</span><br><span class="line"> -keep class com.tencent.gcloud.voice.** {</span><br><span class="line"> *;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> #------------------ 下方是共性的排除项目 ----------------</span><br><span class="line"> # 方法名中含有“JNI”字符的,认定是Java Native Interface方法,自动排除</span><br><span class="line"> # 方法名中含有“JRI”字符的,认定是Java Reflection Interface方法,自动排除</span><br><span class="line"></span><br><span class="line"> -keepclasseswithmembers class * {</span><br><span class="line"> ... *JNI*(...);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> -keepclasseswithmembernames class * {</span><br><span class="line"> ... *JRI*(...);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> -keep class **JNI* {*;}</span><br><span class="line"> -keep class android.app.** {</span><br><span class="line"> *;</span><br><span class="line"> }</span><br><span class="line"> </insert></span><br><span class="line"> </proguardAdditions></span><br><span class="line"></root></span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>如果使用的是aar的话,则无需拷贝aar至build目录了,但是需要指定一下依赖:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"> <baseBuildGradleAdditions></span><br><span class="line"> <insert></span><br><span class="line"> allprojects {</span><br><span class="line"> repositories {</span><br><span class="line"> repositories {</span><br><span class="line"> flatDir {</span><br><span class="line"> </insert></span><br><span class="line"> <insertValue value="dirs '$S(AbsPluginDir)/Libs/Android'"/></span><br><span class="line"> <insertNewline/></span><br><span class="line"> <insert></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </insert></span><br><span class="line"></baseBuildGradleAdditions></span><br><span class="line"></span><br><span class="line"><buildGradleAdditions></span><br><span class="line"> <insert></span><br><span class="line"> dependencies.implementation(name: 'Testaar', ext: 'aar')</span><br><span class="line"> </insert></span><br><span class="line"></buildGradleAdditions></span><br></pre></td></tr></table></figure>
<p>如果遇到内容需要用到如<符号之类的xml关键符号,记得用转义符。</p>
<p>注意,$S(PluginDir)指的是xml所在的目录,所以如果xml直接就是在ThirdParty目录下的话,就不要像GVoice那样../向上找了,因为上一层并没有ThirdParty目录,而是再上一层;而且也没那个必要。</p>
<p>另外,对于需要在C++层调用的接口,可以在gameActivityClassAdditions中创建方法进行转发。上述代码是以vivox插件的init为例,其他部分均取自GVoice。</p>
<h4 id="jar的引入"><a href="#jar的引入" class="headerlink" title="jar的引入"></a>jar的引入</h4><p>如上述代码所示,参考GoogleVRController_APL.xml的实现,里面拷贝了common_library目录,其中就有需要引用的jar包。</p>
<h3 id="4-调用JNI接口"><a href="#4-调用JNI接口" class="headerlink" title="4. 调用JNI接口"></a>4. 调用JNI接口</h3><p>参考资料可以看一下这几个博客:<a target="_blank" rel="noopener" href="https://blog.csdn.net/maxiaosheng521/article/details/106353542">UE4 Jni调用</a>、<a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/294966901">UPL与JNI调用的最佳实践</a>。具体调用的代码可以参考Vivox插件模块:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">// hopefully this is early enough; we don't have a way add into JNI_OnLoad in AndroidJNI.cpp</span><br><span class="line"> vx_jni_set_java_vm(GJavaVM);</span><br><span class="line"></span><br><span class="line"> // do not call any Vivox SDK functions before this</span><br><span class="line"> if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())</span><br><span class="line"> {</span><br><span class="line"> static jmethodID InitVivoxMethod = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "AndroidThunkJava_Vivox_Init", "()V", false);</span><br><span class="line"> if (InitVivoxMethod == 0)</span><br><span class="line"> {</span><br><span class="line"> UE_LOG(VivoxCore, Warning, TEXT("Failed to find AndroidThunkJava_Vivox_Init. Vivox voice chat will not work."));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, InitVivoxMethod);</span><br><span class="line"> if (Env->ExceptionCheck())</span><br><span class="line"> {</span><br><span class="line"> Env->ExceptionDescribe();</span><br><span class="line"> Env->ExceptionClear();</span><br><span class="line"> UE_LOG(VivoxCore, Warning, TEXT("Exception encountered calling AndroidThunkJava_Vivox_Init. Vivox voice chat will not work."));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> else</span><br><span class="line"> {</span><br><span class="line"> UE_LOG(VivoxCore, Warning, TEXT("Unable to get Java environment. Vivox voice chat will not work."));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>其中,FJavaWrapper::FindMethod()使用字符串的模式来描述方法的类型,具体写法抄引擎的其他模块就可以了。可以参考这篇:<a target="_blank" rel="noopener" href="https://blog.csdn.net/jmcc_/article/details/105490727">UE4 JNI FindMethod参数说明</a>。</p>
<h4 id="如何引用java中的object类型"><a href="#如何引用java中的object类型" class="headerlink" title="如何引用java中的object类型"></a>如何引用java中的object类型</h4><p>对于FindMathod()中,需要引用java object类型的话,参考引擎的CameraPlayer14$FrameUpdateInfo类调用即可:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GetVideoLastFrameDataMethod(GetClassMethod("getVideoLastFrameData", "()Lcom/epicgames/ue4/CameraPlayer14$FrameUpdateInfo;"))</span><br></pre></td></tr></table></figure>
<p>它对应的UPL是AndroidCamera_UPL.xml。</p>
<h4 id="如何访问object类型中的域"><a href="#如何访问object类型中的域" class="headerlink" title="如何访问object类型中的域"></a>如何访问object类型中的域</h4><p>首先要获取对应的jclass和jfield:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"> FrameUpdateInfoClass = FAndroidApplication::FindJavaClassGlobalRef("com/epicgames/ue4/CameraPlayer14$FrameUpdateInfo");</span><br><span class="line">FrameUpdateInfo_Buffer = FindField(JEnv, FrameUpdateInfoClass, "Buffer", "Ljava/nio/Buffer;", false);</span><br><span class="line">FrameUpdateInfo_CurrentPosition = FindField(JEnv, FrameUpdateInfoClass, "CurrentPosition", "I", false);</span><br></pre></td></tr></table></figure>
<p>注意,类似”Ljava/nio/Buffer;”这种object类型,里面最后的分号不要漏了,不然会运行时找不到类型然后崩溃。</p>
<p>接下来,就可以对获取到的object,执行GetField了:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 这里是通过调用函数得到的返回对象</span><br><span class="line">auto Result = NewScopedJavaObject(JEnv, JEnv->CallObjectMethod(Object, GetVideoLastFrameDataMethod.Method));</span><br><span class="line"></span><br><span class="line">auto buffer = NewScopedJavaObject(JEnv, JEnv->GetObjectField(*Result, FrameUpdateInfo_Buffer));</span><br><span class="line">int32 CurrentPosition = (int32)JEnv->GetIntField(*Result, FrameUpdateInfo_CurrentPosition);</span><br></pre></td></tr></table></figure>
<h3 id="5-动态获取相关权限"><a href="#5-动态获取相关权限" class="headerlink" title="5. 动态获取相关权限"></a>5. 动态获取相关权限</h3><p>高版本的AndroidSDK,要求一些权限是由APP动态向用户申请权限的,例如录音、定位等。这需要在开发时,留意一下所需的权限是否是动态申请的。引擎提供了查询和获取的接口,以语音权限为例:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> if (!UAndroidPermissionFunctionLibrary::CheckPermission(TEXT("android.permission.RECORD_AUDIO")))</span><br><span class="line">{</span><br><span class="line"> TArray<FString> Permissions = { TEXT("android.permission.RECORD_AUDIO") };</span><br><span class="line"> UAndroidPermissionFunctionLibrary::AcquirePermissions(Permissions);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>【注意】这些权限项,也依然必须写入AndroidManifest中(通过引擎的配置或者APL脚本注入即可)。</p>
<h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul>
<li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/c3c08a4a231e">Android权限之动态权限</a></li>
<li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/30cb93984c04?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation">Android 运行时权限详解</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/JMcc_/article/details/105512351">UE4 Android第三方库导入,JNI调用详解</a></li>
<li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/8a06bce66380">Android Studio 完美引用 so、arr、jar 包</a></li>
<li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/34374244">虚幻插件语言参考</a></li>
<li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/8a06bce66380">so、arr、jar的理解和引用</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/jmcc_/article/details/105490727">UE4 JNI FindMethod参数说明</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/maxiaosheng521/article/details/106353542">UE4 Jni调用</a></li>
<li><a target="_blank" rel="noopener" href="https://answers.unrealengine.com/questions/896712/how-to-require-android-permission-in-c.html">How to require android permission in C++</a></li>
<li><a target="_blank" rel="noopener" href="https://docs.unrealengine.com/en-US/API/Plugins/AndroidPermission/UAndroidPermissionFunctionLibrar-/index.html">UAndroidPermissionFunctionLibrary</a></li>
</ul>
<p>关于安卓调试:</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/jq656021898/article/details/72954223">Android studio查看应用的日志和内存</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/littlefishvc/article/details/80521058">android studio 3.1 Android Device Monitor 新的启动方式</a></li>
</ul>
<h2 id="四-接入iOS平台库"><a href="#四-接入iOS平台库" class="headerlink" title="四. 接入iOS平台库"></a>四. 接入iOS平台库</h2><p>iOS平台的库使用方式为.h + .framework。</p>
<h3 id="1-准备库文件"><a href="#1-准备库文件" class="headerlink" title="1. 准备库文件"></a>1. 准备库文件</h3><p>将ios生成的xxx.framework文件夹放至新建的xxx.embeddedframework目录中,并将该目录压缩为.zip文件。</p>
<h3 id="2-在build-cs中添加库依赖"><a href="#2-在build-cs中添加库依赖" class="headerlink" title="2. 在build.cs中添加库依赖"></a>2. 在build.cs中添加库依赖</h3><p>这次作为例子的库是没有.a文件的,所以可能和其他例子不太一样,但规则一致。以下为示例:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">else if (Target.Platform == UnrealTargetPlatform.IOS)</span><br><span class="line">{</span><br><span class="line"> // These are iOS system libraries that Facebook depends on (FBAudienceNetwork, FBNotifications)</span><br><span class="line"> PublicFrameworks.AddRange(</span><br><span class="line"> new string[] {</span><br><span class="line"> "AVFoundation",</span><br><span class="line"> "CoreTelephony",</span><br><span class="line"> "Security",</span><br><span class="line"> "SystemConfiguration",</span><br><span class="line"> "AudioToolbox",</span><br><span class="line"> "CoreAudio",</span><br><span class="line"> "Foundation",</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> string frameworkZipName = "xxx.embeddedframework.zip";</span><br><span class="line"> string frameworkZipPath = Path.Combine("iOS", frameworkZipName);</span><br><span class="line"> PublicAdditionalFrameworks.Add(new Framework("xxx", frameworkZipPath));</span><br><span class="line"> System.Console.WriteLine("---> framework path:" + frameworkZipPath);</span><br><span class="line"></span><br><span class="line"> PublicAdditionalLibraries.AddRange(</span><br><span class="line"> new string[] {</span><br><span class="line"> "c++",</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="3-C-调用库中接口"><a href="#3-C-调用库中接口" class="headerlink" title="3. C++调用库中接口"></a>3. C++调用库中接口</h3><p>UE4启用了混合编译,可以直接将C++代码和OC代码混合使用,可以直接调用OC接口。</p>
<p>注意,这里有一个坑:从oc接口得到的字符串,虽然可以直接存储进char[]中,但由它直接初始化FString会存在一定的问题(例如编码问题等);毕竟FString本身是支持FString::FString(const CharType* Src)这种形式的初始化的。但经过实践,这种方式初始化的json字符串是无法完成反序列化得到JsonObject的。应该使用以下初始化方式:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> constexpr int BUFF_SIZE = 1024;</span><br><span class="line">static char buffer[BUFF_SIZE] = {};</span><br><span class="line">// do stuff to fill buffer</span><br><span class="line">NSString* nsStr = [NSString stringWithFormat : @"%s", buffer];</span><br><span class="line">FString strJson{ nsStr };</span><br></pre></td></tr></table></figure>
<p>可以参考NSString和其他常用类型之间的转换:<a target="_blank" rel="noopener" href="https://www.cnblogs.com/sevenyuan/p/4004061.html">NSString / NSData / char* 类型之间的转换</a><br>。</p>
<h3 id="4-权限管理"><a href="#4-权限管理" class="headerlink" title="4. 权限管理"></a>4. 权限管理</h3><p>一些涉及到用户权限的功能,例如麦克风、相册(媒体库)等,需要在info.plist中注册权限申请描述。可以直接在引擎的plist配置中进行添加。如果在其中没有配置如NSMicrophoneUsageDiscription的话,理论上会出现崩溃的情况。</p>
<h3 id="5-踩坑记录"><a href="#5-踩坑记录" class="headerlink" title="5. 踩坑记录"></a>5. 踩坑记录</h3><p>使用音频sdk实现语音功能时,启用语音输入功能时,首先弹出了语音权限提示框,点击确认之后出现了崩溃的现象。排查.crash和游戏log之后,发现sdk在执行时,崩在了一个第三方库中,但经过确认,该库并未被sdk所使用;因此怀疑是符号冲突造成以外进入了别的地方引入的这个第三方库中的函数。经过排查,发现之前使用过一个语音插件,并未将其删除而是单纯在build.cs中去除了依赖。因此这里将该弃用的插件彻底从项目中删除即可。注意:不能仅仅在项目中去除对该库的依赖就完事,因为framework和一些动态库的拷贝工作是在编译模块的时候发生的,而即使你不依赖该模块,也仍然改变不了该模块会被编译的事实,于是这些库就因为模块编译而拷贝至打包目录中了,就依然会生效,所以必须删干净。</p>
<h3 id="参考资料-1"><a href="#参考资料-1" class="headerlink" title="参考资料"></a>参考资料</h3><ul>
<li><a target="_blank" rel="noopener" href="https://imzlp.me/notes/">UE4笔记中关于UPL、.Framework、查看ios日志的部分</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/lvxiangan/article/details/43115131">iOS库 .a与.framework区别</a></li>
<li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/df318a92bfe3">UE4中使用iOS静态库</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/u011047958/article/details/78300086">UE4 IOS打包详解</a></li>
<li><a target="_blank" rel="noopener" href="http://blog.sina.com.cn/s/blog_7c5fd2e90102uwht.html">Unreal4 IOS上使用第三方库和C++11 特性问题解决</a></li>
<li><a target="_blank" rel="noopener" href="https://forums.unrealengine.com/development-discussion/ios-development/5179-accessing-ios-sdk-possible">Accessing iOS SDK</a></li>
</ul>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN,zh-TW,en,default">
<link itemprop="mainEntityOfPage" href="https://c209.github.io/2021/09/27/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="C209">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="C209's Blog">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2021/09/27/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/" class="post-title-link" itemprop="url">博客搭建</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2021-09-27 13:52:19" itemprop="dateCreated datePublished" datetime="2021-09-27T13:52:19+08:00">2021-09-27</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2021-09-28 21:33:09" itemprop="dateModified" datetime="2021-09-28T21:33:09+08:00">2021-09-28</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/%E6%9D%82%E9%A1%B9/" itemprop="url" rel="index"><span itemprop="name">杂项</span></a>
</span>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-comment"></i>
</span>
<span class="post-meta-item-text">Valine:</span>
<a title="valine" href="/2021/09/27/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/#valine-comments" itemprop="discussionUrl">
<span class="post-comments-count valine-comment-count" data-xid="/2021/09/27/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/" itemprop="commentCount"></span>
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h1><p>本文讨论使用GitHub Pages作为博客页,使用markdown作为博客的记录形式,利用hexo来完成相应的生成工作。</p>
<h2 id="GitHub-Pages的组织逻辑"><a href="#GitHub-Pages的组织逻辑" class="headerlink" title="GitHub Pages的组织逻辑"></a>GitHub Pages的组织逻辑</h2><p>GitHub Pages有两种组织形式: </p>
<ul>
<li>个人主页: 对应repo为{UserName}.github.io的master分支</li>
<li>项目主页: 对应各个repo下的gh-pages分支。</li>
</ul>
<p>本文暂且只讨论个人主页的搭建。</p>
<h1 id="创建git-repo"><a href="#创建git-repo" class="headerlink" title="创建git repo"></a>创建git repo</h1><p>在GitHub上新建一个repo,名称为UserName.github.io,其中UserName为你的GitHub用户名。<br>这个repo的master分支就用来作为博客的页面存储了。</p>
<h1 id="hexo相关的环境准备"><a href="#hexo相关的环境准备" class="headerlink" title="hexo相关的环境准备"></a>hexo相关的环境准备</h1><h2 id="1-安装Node-js"><a href="#1-安装Node-js" class="headerlink" title="1. 安装Node.js"></a>1. 安装Node.js</h2><p><a target="_blank" rel="noopener" href="https://nodejs.org/en/download/">下载地址在这里</a>,安装完成后在控制台中输入以下命令来检查是否成功:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">node -v</span><br><span class="line">npm -v</span><br></pre></td></tr></table></figure>
<h2 id="2-安装hexo"><a href="#2-安装hexo" class="headerlink" title="2. 安装hexo"></a>2. 安装hexo</h2><p>使用npm进行安装:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g hexo-cli </span><br></pre></td></tr></table></figure>
<h1 id="创建本地博客项目"><a href="#创建本地博客项目" class="headerlink" title="创建本地博客项目"></a>创建本地博客项目</h1><h2 id="1-初始化博客目录"><a href="#1-初始化博客目录" class="headerlink" title="1. 初始化博客目录"></a>1. 初始化博客目录</h2><p>在你想要创建项目的地方,使用以下指令初始化一个指定名称的博客目录:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo init MyBlog</span><br></pre></td></tr></table></figure>
<p>其中MyBlog为创建的博客目录名,可以任意指定。</p>
<h2 id="2-安装Git部署插件"><a href="#2-安装Git部署插件" class="headerlink" title="2. 安装Git部署插件"></a>2. 安装Git部署插件</h2><p>稍后执行部署的时候,需要告诉hexo将博客部署到哪里,这里我们希望将其部署至GitHub仓库。所以使用以下指令进行安装:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-deployer-git --save</span><br></pre></td></tr></table></figure>
<h2 id="3-修改一些配置"><a href="#3-修改一些配置" class="headerlink" title="3. 修改一些配置"></a>3. 修改一些配置</h2><p>我们需要对配置文件(MyBlog/_config.yml)进行一些修改。</p>
<p>首先配置一下博客页的标题、作者、语言、时区等信息:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># Site</span><br><span class="line">title: C209's Blog</span><br><span class="line">subtitle: ''</span><br><span class="line">description: ''</span><br><span class="line">keywords:</span><br><span class="line">author: C209</span><br><span class="line">language: </span><br><span class="line">- zh-CN</span><br><span class="line">- zh-TW</span><br><span class="line">- en</span><br><span class="line">timezone: Asia/Shanghai</span><br></pre></td></tr></table></figure>
<p>接着是最底下的deploy信息:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># Deployment</span><br><span class="line">## Docs: https://hexo.io/docs/one-command-deployment</span><br><span class="line">deploy:</span><br><span class="line"> type: git</span><br><span class="line"> repo: https://github.com/UserName/UserName.github.io.git</span><br><span class="line"> branch: master</span><br></pre></td></tr></table></figure>
<p>其中UserName为你的GitHub用户名。</p>
<h1 id="将博客项目存储至repo"><a href="#将博客项目存储至repo" class="headerlink" title="将博客项目存储至repo"></a>将博客项目存储至repo</h1><p>至此为止,MyBlog目录下的文件其实都是只在本地的,之后hexo执行部署时并不会将它们也推送上去(这其中也包括source/_posts下的md源文件)。假如以后想要换个机器接着写博客的话,这些东西丢了还是很麻烦的。所以希望利用这个repo也将这些内容一并存储起来,之后只需要在别处把项目clone下来,就可以继续写博客了。对这个不感兴趣的话,本步骤可以跳过。</p>
<h2 id="1-将博客项目关联至repo"><a href="#1-将博客项目关联至repo" class="headerlink" title="1. 将博客项目关联至repo"></a>1. 将博客项目关联至repo</h2><p>在MyBlog目录下,执行以下命令:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git init</span><br><span class="line">git remote add origin https://github.com/UserName/UserName.github.io</span><br></pre></td></tr></table></figure>
<p>其中UserName为你的GitHub用户名。</p>
<h2 id="2-创建并推送至新的分支"><a href="#2-创建并推送至新的分支" class="headerlink" title="2. 创建并推送至新的分支"></a>2. 创建并推送至新的分支</h2><p>使用以下命令将当前博客项目中的文件推送至新建的source分支:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b source</span><br><span class="line">git add .</span><br><span class="line">git commit -m 'add source'</span><br><span class="line">git push origin source</span><br></pre></td></tr></table></figure>
<p>之后在其他机器上,只需要clone这个repo并切换至source分支,并利用之前步骤的指令完成hexo相关的环境准备即可。md的编写和提交也直接在source分支上进行。</p>
<h1 id="安装主题"><a href="#安装主题" class="headerlink" title="安装主题"></a>安装主题</h1><p>可以找一个自己喜欢的主题,这里以<a target="_blank" rel="noopener" href="https://github.com/theme-next/hexo-theme-next">next</a>为例。</p>
<h2 id="1-下载主题"><a href="#1-下载主题" class="headerlink" title="1. 下载主题"></a>1. 下载主题</h2><p>在MyBlog下执行以下指令即可:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/theme-next/hexo-theme-next themes/next</span><br></pre></td></tr></table></figure>
<p>如果有参考之前的步骤,将博客项目提交至source分支的话,这里也可以将该主题直接作为我们自己repo的一个submodule,这样比较简洁清晰。这一步纯粹看个人喜好。如果有需要针对主题进行的个人改动,建议以自己的fork作为submodule。</p>
<h2 id="2-在配置中启用主题"><a href="#2-在配置中启用主题" class="headerlink" title="2. 在配置中启用主题"></a>2. 在配置中启用主题</h2><p>依旧修改配置文件(MyBlog/_config.yml):</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># Extensions</span><br><span class="line">## Plugins: https://hexo.io/plugins/</span><br><span class="line">## Themes: https://hexo.io/themes/</span><br><span class="line">theme: next</span><br></pre></td></tr></table></figure>
<h2 id="3-美化主题"><a href="#3-美化主题" class="headerlink" title="3. 美化主题"></a>3. 美化主题</h2><ul>
<li><a target="_blank" rel="noopener" href="https://github.com/iissnan/hexo-theme-next/wiki/%E5%88%9B%E5%BB%BA%E5%88%86%E7%B1%BB%E9%A1%B5%E9%9D%A2">创建分类页面</a>。</li>
<li><a target="_blank" rel="noopener" href="http://www.360doc.com/content/19/0420/09/22888630_830052585.shtml">其他美化效果</a></li>
</ul>
<h1 id="编写博客"><a href="#编写博客" class="headerlink" title="编写博客"></a>编写博客</h1><p>在source/_posts下,编写markdown作为博客源文件,也可以使用hexo n “NewBlog”指令为你新建一篇md。hexo在这里已经为我们创建了一个hello-world作为模板。</p>
<p>博客的开头可以附加例如标题、时间等信息,例如:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: // 文章标题</span><br><span class="line">date: // 创建日期</span><br><span class="line">updated: // 更新日期</span><br><span class="line">tags: </span><br><span class="line"> - a</span><br><span class="line"> - b // 标签</span><br><span class="line">categories: </span><br><span class="line"> - a</span><br><span class="line"> - b // 分类</span><br><span class="line">keywords: // 关键字</span><br><span class="line">---</span><br></pre></td></tr></table></figure>
<h1 id="测试并部署"><a href="#测试并部署" class="headerlink" title="测试并部署"></a>测试并部署</h1><h2 id="1-使用hexo进行生成"><a href="#1-使用hexo进行生成" class="headerlink" title="1. 使用hexo进行生成"></a>1. 使用hexo进行生成</h2><p>使用以下指令完成生成工作:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo g</span><br></pre></td></tr></table></figure>
<h2 id="2-测试"><a href="#2-测试" class="headerlink" title="2. 测试"></a>2. 测试</h2><p>使用以下指令开启本地测试:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo s</span><br></pre></td></tr></table></figure>
<p>此时在浏览器中输入localhost:4000可以预览博客页面效果。</p>
<h2 id="3-部署"><a href="#3-部署" class="headerlink" title="3. 部署"></a>3. 部署</h2><p>使用以下指令将生成的博客部署至master分支:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo d</span><br></pre></td></tr></table></figure>
<p>至此,刚刚创建的博客已经完成部署和发布工作,可以至 <a target="_blank" rel="noopener" href="https://username.github.io/">https://UserName.github.io</a> 页面浏览实际的博客页面,其中UserName为你的GitHub用户名。</p>
<h3 id="部署至多个平台"><a href="#部署至多个平台" class="headerlink" title="部署至多个平台"></a>部署至多个平台</h3><p>如果想要同时部署至多个平台,可以参考<a target="_blank" rel="noopener" href="https://blog.csdn.net/weixin_45254208/article/details/109186355">这篇博客</a>。</p>
<h1 id="TroubleShooting"><a href="#TroubleShooting" class="headerlink" title="TroubleShooting"></a>TroubleShooting</h1><h2 id="hexo-d部署授权失败"><a href="#hexo-d部署授权失败" class="headerlink" title="hexo d部署授权失败"></a>hexo d部署授权失败</h2><p>执行hexo d指令时,出现下述错误:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.</span><br></pre></td></tr></table></figure>
<p>需要改为使用Token的方式,具体参考<a target="_blank" rel="noopener" href="https://blog.csdn.net/weixin_41010198/article/details/119698015">这篇博客</a>即可,这里就不详述了。</p>
<h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul>
<li><a target="_blank" rel="noopener" href="https://hexo.io/zh-cn/docs/">官方文档</a></li>
<li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/26625249">GitHub+Hexo 搭建个人网站详细教程</a></li>
<li><a target="_blank" rel="noopener" href="https://imzlp.com/posts/58952/">Github Pages+Hexo 博客搭建</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/zyxhangiian123456789/article/details/101797464">使用github搭建Markdown博客系统</a></li>
<li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/4bcf2848b3fc">多台电脑使用Hexo</a></li>
<li><a target="_blank" rel="noopener" href="https://www.cnblogs.com/MuYunyun/p/6082359.html">如何用Github的gh-pages分支展示自己的项目</a></li>
<li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/9a9b35c819c3">Hexo使用、Markdown语法笔记</a></li>
<li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/3fe88ef479dd">Hexo+Next 添加菜单分类页面</a></li>
<li><a target="_blank" rel="noopener" href="https://github.com/iissnan/hexo-theme-next/wiki/%E5%88%9B%E5%BB%BA%E5%88%86%E7%B1%BB%E9%A1%B5%E9%9D%A2">创建分类页面</a></li>
<li><a target="_blank" rel="noopener" href="http://yuchen-lea.github.io/2016-01-23-display-hexo-category-in-hierarchy/">Hexo多级类别的层级显示</a></li>
<li><a target="_blank" rel="noopener" href="https://www.cnblogs.com/cscshi/p/15196122.html">Hexo-NexT 分类多层级描述</a></li>
<li><a target="_blank" rel="noopener" href="http://www.360doc.com/content/19/0420/09/22888630_830052585.shtml">Hexo框架下用NexT(v7.0+)主题美化博客</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/Awt_FuDongLai/article/details/107430942">next主题添加版权声明</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/weixin_45254208/article/details/109186355">Hexo博客镜像(github和gitee双部署)</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/blue_zy/article/details/79071414">为你的Hexo加上评论系统-Valine</a></li>
</ul>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN,zh-TW,en,default">
<link itemprop="mainEntityOfPage" href="https://c209.github.io/2021/09/27/UnrealEngine/%E5%9C%A8%E5%B7%A5%E7%A8%8B%E7%9B%AE%E5%BD%95%E4%B8%8B%E5%88%9B%E5%BB%BAStandaloneProgram/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="C209">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="C209's Blog">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2021/09/27/UnrealEngine/%E5%9C%A8%E5%B7%A5%E7%A8%8B%E7%9B%AE%E5%BD%95%E4%B8%8B%E5%88%9B%E5%BB%BAStandaloneProgram/" class="post-title-link" itemprop="url">在工程目录下创建StandaloneProgram</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2021-09-27 13:52:19 / 修改时间:17:33:50" itemprop="dateCreated datePublished" datetime="2021-09-27T13:52:19+08:00">2021-09-27</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Unreal-Engine/" itemprop="url" rel="index"><span itemprop="name">Unreal Engine</span></a>
</span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Unreal-Engine/Program/" itemprop="url" rel="index"><span itemprop="name">Program</span></a>
</span>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-comment"></i>
</span>
<span class="post-meta-item-text">Valine:</span>
<a title="valine" href="/2021/09/27/UnrealEngine/%E5%9C%A8%E5%B7%A5%E7%A8%8B%E7%9B%AE%E5%BD%95%E4%B8%8B%E5%88%9B%E5%BB%BAStandaloneProgram/#valine-comments" itemprop="discussionUrl">
<span class="post-comments-count valine-comment-count" data-xid="/2021/09/27/UnrealEngine/%E5%9C%A8%E5%B7%A5%E7%A8%8B%E7%9B%AE%E5%BD%95%E4%B8%8B%E5%88%9B%E5%BB%BAStandaloneProgram/" itemprop="commentCount"></span>
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="一-创建基础文件"><a href="#一-创建基础文件" class="headerlink" title="一. 创建基础文件"></a>一. 创建基础文件</h1><h2 id="1-创建-Target-cs"><a href="#1-创建-Target-cs" class="headerlink" title="1. 创建.Target.cs"></a>1. 创建.Target.cs</h2><p>在项目的Source目录下直接创建Program的Target.cs文件,建议直接复制某个现成的Program。</p>
<p>注意,要么直接在Source里面这一级放所有的Target.cs,要么都放进目录层级里面去,不能有的在里面有的在外面。UBT的搜索逻辑是如果搜索到了其中一个,则不会再往下进行搜索了。所以有的项目的做法是把它们整理进不同的文件目录,如Runtime和Program,这样也清晰一些。这里因为项目仅有这么一个特殊的Program,挪其他的代码位置的话,git提交记录会有一堆rename,不太方便,所以就和项目的Game.Target.cs这些直接放在一块了。</p>
<h2 id="2-启动模块"><a href="#2-启动模块" class="headerlink" title="2. 启动模块"></a>2. 启动模块</h2><p>独立Program必然有至少一个模块,该模块提供启动的入口,负责各平台的main函数入口。建议是各平台的main函数调用自己实现的一个Main,真正的启动逻辑在这个里面,这样可以实现平台无关性。</p>
<p>我们可以将我们所需的模块直接放进项目Source中的某一级目录,不必和Target.cs在一个层级下。它们可以多个模块在同一个目录下,无需彼此相互独立。</p>
<h2 id="3-配置-Target-cs"><a href="#3-配置-Target-cs" class="headerlink" title="3. 配置.Target.cs"></a>3. 配置.Target.cs</h2><h3 id="指定生成的project的目录"><a href="#指定生成的project的目录" class="headerlink" title="指定生成的project的目录"></a>指定生成的project的目录</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SolutionDirectory = "MyPrograms";</span><br></pre></td></tr></table></figure>
<p>将一会生成的project在sln中显示在MyPrograms下,否则统一在Programs中很难找到。</p>
<h1 id="二-对项目目录下的uproject执行generate-sln"><a href="#二-对项目目录下的uproject执行generate-sln" class="headerlink" title="二. 对项目目录下的uproject执行generate sln"></a>二. 对项目目录下的uproject执行generate sln</h1><p>这将在项目工程的sln里的MyPrograms中新增一个project,后续的改动就可以直接在sln中修改了。</p>
<h1 id="三-修改编译相关属性"><a href="#三-修改编译相关属性" class="headerlink" title="三. 修改编译相关属性"></a>三. 修改编译相关属性</h1><p>建议将Target.cs中的LinkType这样修改:<code>LinkType = TargetLinkType.Monolithic;</code></p>
<p>如果是Modular的话,就会将各个依赖的模块编译为独立的dll进来,然后在运行起来的时候链接起来。虽然这和Editor的启动方式一致,似乎也并不会出现编译问题,但是在项目目录的Binaries下出现一堆dll其实也挺麻烦的。下次想找项目build出来的exe和dll的时候也不太方便,干脆编进一个整体比较简单。</p>
<h1 id="四-修改运行时的项目路径"><a href="#四-修改运行时的项目路径" class="headerlink" title="四. 修改运行时的项目路径"></a>四. 修改运行时的项目路径</h1><p>在你的Main函数入口处,覆盖ProjectDir:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FGenericPlatformMisc::SetOverrideProjectDir(FString::Printf(TEXT("../../Programs/%s/"), FApp::GetProjectName()));</span><br></pre></td></tr></table></figure>
<p>因为默认的路径是Engine/Programs/,这样找起来不太方便。调整之后就会出现在项目目录下的Programs目录里了。</p>
<h1 id="五-配置模块"><a href="#五-配置模块" class="headerlink" title="五. 配置模块"></a>五. 配置模块</h1><h3 id="1-指定Program需要的模块"><a href="#1-指定Program需要的模块" class="headerlink" title="1. 指定Program需要的模块"></a>1. 指定Program需要的模块</h3><p>在刚刚创建的Target.cs中,配置LaunchModuleName为提供入口的那个模块,并将其他依赖的模块添加至ExtraModuleNames。这些额外依赖的模块后续仍然需要手动加载,暂时不表。</p>
<h3 id="2-手动加载其他依赖的模块"><a href="#2-手动加载其他依赖的模块" class="headerlink" title="2. 手动加载其他依赖的模块"></a>2. 手动加载其他依赖的模块</h3><p>在启动的时候,适当的地方主动调用FModuleManager::Get().LoadModuleChecked()进行加载。</p>
<h1 id="六-编译"><a href="#六-编译" class="headerlink" title="六. 编译"></a>六. 编译</h1><p>设置刚刚生成出来的project为start up project,编译并运行它即可。</p>
<h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul>
<li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/147598518">Standalone Application</a></li>
<li><a target="_blank" rel="noopener" href="https://imzlp.com/posts/31962/">Create A Standalone Application</a></li>
<li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/145633340">Program 类型工程的限制和解决方法</a></li>
</ul>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
</div>
<script>
window.addEventListener('tabs:register', () => {
let { activeClass } = CONFIG.comments;
if (CONFIG.comments.storage) {
activeClass = localStorage.getItem('comments_active') || activeClass;
}
if (activeClass) {
let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
if (activeTab) {
activeTab.click();
}
}
});
if (CONFIG.comments.storage) {
window.addEventListener('tabs:click', event => {
if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
let commentClass = event.target.classList[1];
localStorage.setItem('comments_active', commentClass);
});
}
</script>
</div>
<div class="toggle sidebar-toggle">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
<aside class="sidebar">
<div class="sidebar-inner">
<ul class="sidebar-nav motion-element">
<li class="sidebar-nav-toc">
文章目录
</li>
<li class="sidebar-nav-overview">
站点概览
</li>
</ul>
<!--noindex-->
<div class="post-toc-wrap sidebar-panel">
</div>
<!--/noindex-->
<div class="site-overview-wrap sidebar-panel">
<div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
<p class="site-author-name" itemprop="name">C209</p>
<div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
<nav class="site-state">
<div class="site-state-item site-state-posts">
<a href="/archives/">
<span class="site-state-item-count">4</span>
<span class="site-state-item-name">日志</span>
</a>
</div>
<div class="site-state-item site-state-categories">
<a href="/categories/">
<span class="site-state-item-count">4</span>
<span class="site-state-item-name">分类</span></a>
</div>
</nav>
</div>
</div>
</div>
</aside>
<div id="sidebar-dimmer"></div>
</div>
</main>
<footer class="footer">
<div class="footer-inner">
<div class="copyright">
©
<span itemprop="copyrightYear">2021</span>
<span class="with-love">
<i class="fa fa-heart"></i>
</span>
<span class="author" itemprop="copyrightHolder">C209</span>
</div>
<div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://pisces.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Pisces</a> 强力驱动
</div>
<div class="busuanzi-count">
<script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
<span class="post-meta-item-icon">
<i class="fa fa-user"></i>
</span>
<span class="site-uv" title="总访客量">
<span id="busuanzi_value_site_uv"></span>
</span>
</span>
<span class="post-meta-divider">|</span>
<span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
<span class="post-meta-item-icon">
<i class="fa fa-eye"></i>
</span>
<span class="site-pv" title="总访问量">
<span id="busuanzi_value_site_pv"></span>
</span>
</span>
</div>
</div>
</footer>
</div>
<script src="/lib/anime.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js"></script>
<script src="//cdn.jsdelivr.net/gh/fancyapps/fancybox@3/dist/jquery.fancybox.min.js"></script>
<script src="/lib/velocity/velocity.min.js"></script>
<script src="/lib/velocity/velocity.ui.min.js"></script>
<script src="/js/utils.js"></script>
<script src="/js/motion.js"></script>
<script src="/js/schemes/pisces.js"></script>
<script src="/js/next-boot.js"></script>
<script defer src="//cdn.jsdelivr.net/gh/theme-next/theme-next-three@1/three.min.js"></script>
<script defer src="/lib/three/three-waves.min.js"></script>
<script>
NexT.utils.loadComments(document.querySelector('#valine-comments'), () => {
NexT.utils.getScript('//unpkg.com/valine/dist/Valine.min.js', () => {
var GUEST = ['nick', 'mail', 'link'];
var guest = 'nick,mail,link';
guest = guest.split(',').filter(item => {
return GUEST.includes(item);
});
new Valine({
el : '#valine-comments',
verify : false,
notify : false,
appId : 'AcXNR0sOiSeMXPX1RGUDliSk-gzGzoHsz',
appKey : 'jTIchKj5EW06nTxTBjmqEifS',
placeholder: "写点什么吧~",
avatar : 'mm',
meta : guest,
pageSize : '10' || 10,
visitor : false,
lang : 'en, zh-cn' || 'zh-cn',
path : location.pathname,
recordIP : false,
serverURLs : ''
});
}, window.Valine);
});
</script>
</body>
</html>