不同的硬件制造商为用户带来了不同尺寸和体验的设备。 因此,我们一直在努力帮助开发者将他们的游戏展示到尽可能多的设备上,让开发过程更加高效、更加轻松。 本文将向您介绍许多新的游戏开发工具以及游戏调试、打包和分发技术。 如果您想通过视频了解本文内容,请点击下方:
△ 游戏开发工具重大升级
更高效的游戏开发工具
工欲善其事,必先利其器。 我们对开发工具做了很多优化。 游戏引擎主要用C和C++语言编写,大多数API被设计为由此类托管代码调用。 所以我们实现了同时调试C/C++代码和托管代码的能力,这样就可以在C/C++代码中设置如下所示的断点,然后跟踪两个编程环境中的执行情况。 健康)状况。 您甚至可以在托管代码和本机代码之间跳转以观察执行情况。
△ 演示托管代码和C/C++的同时调试
另一方面,自动代码补全功能可以用来加快你的代码编写速度,还支持你快速插入JNI函数原型,以便在两种不同的环境下进行编程。 此外,构建过程使用 Cmake 和 Cmake,这使您可以更好地利用可移植构建。
对于在操作系统上编写 C/C++ 跨平台游戏的开发者,我们在 2021 年 7 月推出的(Game Kit)中提供了游戏开发扩展(AGDE、Game)。它进一步简化了您的开发流程,帮助您直接构建使用一系列支持 C 和 C++ 的调试器和分析工具来调试您环境中的设备。 并且AGDE可以轻松地与各种构建系统集成,也可以使用虚幻引擎轻松集成到您的工作流程中。 这样,您就不需要分别为桌面设备、游戏机和设备使用一个。 一组工具集和构建系统。
配置AGDE的过程也非常简单。 安装扩展后,切换到您的项目并添加 APK 模板。 工具栏上可以访问各种常用的开发工具,例如SDK和NDK管理器、虚拟设备管理器、设备文件管理器、性能分析器等,如下图所示:
△ 工具栏
在项目中配置构建目标后,您只需像开发标准桌面目标时一样进行操作即可。 所有与构建、部署和调试相关的操作都是相同的。 您可以轻松地在调试器中设置断点,切换到反汇编代码以查看寄存器和内存块中的值,并通过并行堆栈查看并发性。
△使用调试构建目标
此外,您不仅可以在专用面板中搜索日志输出并按类型过滤,还可以快速访问原来通过AGDE提供的独立CPU和内存分析器,如下图右侧所示:
△可搜索面板(左);
独立 CPU 和内存分析器(右)
我们为这些工具设计了一个新的界面,增强了多项功能,并支持本机内存采样。 结合游戏开发扩展工具,为您提供丰富的工具来高效开发游戏。
游戏开发套件
俗话说,好马配好鞍。 拥有这些方便的工具还不够,我们还需要将这些工具集成到托管代码 API 中。 为此,我们在 (AGDK) 中提供了一个新的 C/C++ 库供您使用。
它几乎是本机方式的标准实现(C/C++)。 它与库以及各种接口库配合良好。 这允许您完全用 C/C++ 编写游戏循环,同时利用各种基于构建的库。 另外, 会被渲染成 ,因此您可以轻松混合界面元素,例如 、 、 以及广告 SDK 等各种服务所需的视图。 这样,你只需要在托管代码中使用一个简单加载C/C++游戏模块的类就可以完成游戏循环逻辑的集成。 相关代码如下:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content">
△需要显示的视图
public class MyGameActivity extends GameActivity {
static {
// 加载您的游戏库
System.loadLibrary("game");
}
}
△最少的托管代码
您需要修改 .xml 文件中的元数据以告知哪个库需要开始执行游戏循环。
<activity android:name=".MyGameActivity" android:label="@string/app_name">
<meta-data android:name="android.app.lib_name" android:value="game"/>
△修改.xml
为您提供了多种匹配生命周期事件的原生回调方法。 这些事件回调可以轻松集成到您的游戏循环中。 您可以查看 s 的使用参考以获取更多信息:
△生命周期对应的回调方法
下面是一段非常简单的代码,我们将向您展示一个基于库的示例。 该库提供了一种不寻常的执行模式。
void android_main(struct android_app *app) {
// 您的游戏引擎
NativeEngine *engine = new NativeEngine(app);
// 您的游戏循环
engine->GameLoop();
delete engine;
}
△基于实例
这里,()函数会在与主线程不同的新线程中被调用。 您可以获取与线程关联的生命周期事件,如下所示:
while(1) {
int events;
struct android_poll_source *source;
// 如果没有动画发生,阻塞进程直到捕获某个事件
while ((ALooper_pollAll(IsAnimating() ? 0: -1, Null, &events, (void **) &source)) >= 0) {
// 处理事件
if (source != NULL) {
source->process(mApp, source);
}
}
}
△用于获取并处理生命周期事件
除了捕获生命周期事件外,还可以使用该方法来监听文件描述符(file)。
框架API
帧数调步(frame)API可以帮助开发者解决较短游戏帧带来的延迟,避免较长游戏帧带来的延迟。 如果设备支持刷新率选择功能,可以为玩家提供更加灵活、流畅的显示效果。
实施后,您可以选择使用 /ES 或将内容渲染到表面。 无论您选择哪个 API,Frame 库都会帮助您正确处理渲染过程。 它将游戏的逻辑和渲染循环与显示子系统和底层显示相关硬件同步,以实现更流畅的渲染。
有关详细信息,请参阅框架库:
双簧管API
△双簧管API
一款制作精良的游戏离不开优秀的音效,这就是Oboe音频库所提供的。 您可以在 4.1 及更高版本的系统上使用其 API。 在 API 级别 27 的设备上,Oboe 将通过在设备上尽可能协调硬件和软件来实现最低的音频延迟。 对于较低版本的设备,Oboe 会使用 ES 来尽可能保证兼容性。
虽然 Oboe 引入了许多新功能,例如重采样、格式转换和高性能通道数转换,但它还针对一些已知的音频问题提供了内置解决方案。
游戏输入
该游戏开发套件还提供了两个可互操作的库,用于处理游戏中的软键盘和控制器输入信号。
游戏文字输入
它在后台做了很多复杂的工作,将系统的软键盘连接到游戏中的文本编辑器,包括显示和隐藏软键盘。
△游戏中的文字输入逻辑
如果与该库配合使用,无论使用与否,系统都可以自动完成相关配置。 查看下面的代码,该库会将输入状态传递给您的游戏,以便您的文本编辑器正确反映 IME 的状态。
/**
* 获取最后收到的文本输入状态
*/
void GameActivity_getTextInputState(
GameActivity *activity,
GameTextInputGetStateCallback callback,
void* context);
△修改.xml文件
游戏手柄输入
游戏中另一个常见的输入设备是游戏控制器(游戏)。 您可以通过使用游戏库来充分利用物理游戏控制器。 当控制器连接到设备或从设备断开连接时,此库会向您的游戏发送通知,并提供有关按钮布局、方向轴和其他关键控制器元数据的信息。
底层也封装了Game库,让你可以无缝、轻松地连接各种控制器,无需复杂的实现,甚至可以接受鼠标作为输入设备。
支持大屏游戏
在智能电视上玩游戏
如果您的游戏可以使用方向键控制并在横向模式下正常运行,那么它可以在许多电视设备上运行。
△在智能电视上玩游戏
为了支持在智能电视上运行,需要对.xml文件进行一些修改:
// 您需要在多个 uses-feature 标签内声明这些权限:
android.hardware.touchscreen
android.hardware.faketouch
android.hardware.telephony
android.hardware.camera
android.hardware.nfc
android.hardware.location.gps
android.hardware.microphone
android.hardware.sensor
// 如果有必要的话,请添加 android.required="false"
△修改.xml文件
您需要在清单中将这些权限声明为非必需的,即“false”。 这是因为您提到的许多权限和功能不是必需的,并且在智能电视上往往不支持,例如触摸屏、摄像头、加速度传感器等。您可以通过以下方式声明您的游戏支持使用遥控器作为控制器添加以下代码:
<uses-feature
android:name="android.hardware.gamepad"
android:required="false"/>