From 3d5aff07b2937849d633dfc28af3df11a194c862 Mon Sep 17 00:00:00 2001 From: Recogerous <2737936634@qq.com> Date: Sat, 23 Nov 2024 00:31:20 +0800 Subject: [PATCH 1/9] optimize readme and doc again - Use Chinese punctuation for Chinese text and English punctuation for English text. - Comments in code blocks: Add spaces around the comment symbols. - Indentation and alignment in code blocks: Adjusted for consistency. - Included the missing modifications from the previous PR. --- README.md | 68 +- ...ook.md => PainterEngine_the_book_zh-CN.md} | 913 +++++++++--------- 2 files changed, 492 insertions(+), 489 deletions(-) rename documents/{PainterEngine the book.md => PainterEngine_the_book_zh-CN.md} (59%) diff --git a/README.md b/README.md index 6d22c483..69879fa7 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,30 @@

-PainterEngine 是一个由 C 语言编写的跨平台图形引擎, 支持 Windows/Linux/iOS/Android/WebAssembly 甚至无操作系统的裸嵌入式平台, 它基于组件化的设计模式, 即使是 C 语言初学者, 也可以在几分钟内掌握它的使用, [PainterEngine Make](https://www.painterengine.com/) 允许您一键将您的 PainterEngine 项目编译到多个平台. -它涵盖了基础数据结构、图形学、声学、数字信号处理、编译原理、虚拟机系统、密码学、人机交互、游戏引擎、FPGA-GPU 图形 IP 设计等多个领域, 你既可以用它制作微应用, 也可以将它作为学习项目。 +PainterEngine 是一个由 C 语言编写的跨平台图形引擎,支持 Windows/Linux/iOS/Android/WebAssembly 甚至无操作系统的裸嵌入式平台,它基于组件化的设计模式,即使是 C 语言初学者,也可以在几分钟内掌握它的使用,[PainterEngine Make](https://www.painterengine.com/) 允许您一键将您的 PainterEngine 项目编译到多个平台。 +它涵盖了基础数据结构、图形学、声学、数字信号处理、编译原理、虚拟机系统、密码学、人机交互、游戏引擎、FPGA-GPU 图形 IP 设计等多个领域,你既可以用它制作微应用,也可以将它作为学习项目。 -现在了解并使用 PainterEngine----> -*__[PainterEngine 快速入门手册(中文版)](./documents/PainterEngine%20the%20book.md)__* +现在了解并使用 PainterEngine $\longrightarrow$ +*__[PainterEngine 快速入门手册(中文版)](./documents/PainterEngine_the_book_zh-CN.md)__* -PainterEngine is a cross-platform graphics engine written in C language, with support for Windows, Linux, iOS, Android, WebAssembly, and even bare-metal embedded platforms without OS. It is built on a component-based design pattern, making it accessible to even C language beginners . [PainterEngine Make](https://www.painterengine.com/) enables you to compile your PainterEngine project for multiple platforms with just one click. +PainterEngine is a cross-platform graphics engine written in C language, with support for Windows, Linux, iOS, Android, WebAssembly, and even bare-metal embedded platforms without OS. It is built on a component-based design pattern, making it accessible to even C language beginners. [PainterEngine Make](https://www.painterengine.com/) enables you to compile your PainterEngine project for multiple platforms with just one click. It covers various fields including basic data structures, graphics, acoustics, digital signal processing, compiler design, virtual machine systems, cryptography, human-computer interaction, game engines, FPGA-GPU graphics acceleration, and more. You can use it to create mini-applications or as a learning project for acquiring knowledge. +Now, learn and use PainterEngine $\longrightarrow$ +*__[PainterEngine Quick Start Guide (English Version)](./documents/PainterEngine_the_book_en.md)__* + ## 30 秒速览 PainterEngine ## 30-Second Quick Start Guide to PainterEngine -将 PainterEngine 引入到您的 C/C++ 项目中, 仅仅需要 `#include "PainterEngine.h"`. +将 PainterEngine 引入到您的 C/C++ 项目中,仅仅需要 `#include "PainterEngine.h"`。 To incorporate PainterEngine into your project, all you need is: ```c #include "PainterEngine.h" ``` -使用 `PainterEngine_Initialize`, 快速创建一个图形化的交互式界面: +使用 `PainterEngine_Initialize`,快速创建一个图形化的交互式界面: Utilize `PainterEngine_Initialize` to swiftly create a graphical interactive interface: ```c @@ -36,7 +39,7 @@ int main() } ``` -创建组件, 或者...创造自己的组件: +创建组件,或者……创造自己的组件: Create components or even craft your own: @@ -56,7 +59,7 @@ int main() PainterEngine firework

-使用 [PainterEngine Make](https://www.painterengine.com/) 快速将您的项目编译到 Windows, Linux, WebAssembly, Android 等任意平台, 一键编译部署, 源码无需修改, 零成本移植. +使用 [PainterEngine Make](https://www.painterengine.com/) 快速将您的项目编译到 Windows、Linux、WebAssembly、Android 等任意平台,一键编译部署,源码无需修改,零成本移植。 Use ["PainterEngine Make"](https://www.painterengine.com/) to quickly compile and deploy your project to various platforms such as Windows, Linux, WebAssembly, Android, and more. One-click compilation and deployment, with no need to modify the source code, enabling seamless portability at zero cost. @@ -72,20 +75,21 @@ Use ["PainterEngine Make"](https://www.painterengine.com/) to quickly compile an

-## 快速开发, 无缝迁移 +## 快速开发,无缝迁移 + ## Swift development and smooth transitions -如果您不需要 PainterEngine Make 提供的一键编译功能, 希望使用自己常用的 IDE 开发 PainterEngine 程序或组件, 您只需要: +如果您不需要 PainterEngine Make 提供的一键编译功能,希望使用自己常用的 IDE 开发 PainterEngine 程序或组件,您只需要: -1. 将 "PainterEngine/core", "PainterEngine/kernel", "PainterEngine/runtime" 的所有代码, 添加到您的项目中. +1. 将 "PainterEngine/core"、"PainterEngine/kernel"、"PainterEngine/runtime" 的所有代码,添加到您的项目中。 -2. 在 "PainterEngine/platform" 中选择您的工作平台(例如 Windows 中选择 "PainterEngine/platform/windows"), 并将对应文件夹中的所有代码添加到您的项目中. +2. 在 "PainterEngine/platform" 中选择您的工作平台(例如 Windows 中选择 "PainterEngine/platform/windows"),并将对应文件夹中的所有代码添加到您的项目中。 -3. 将 PainterEngine 所在目录, 添加到包含目录中. +3. 将 PainterEngine 所在目录添加到包含目录中。 -4. 将您的代码添加进项目中. +4. 将您的代码添加进项目中。 -即可使用您的 IDE 完成 PainterEngine 的编译, PainterEngine 库将尽力保证所有平台的的运行结果一致性, 在 Windows 上开发, 同样在 Android/web/Linux/iOS/... 中能够得到一致的结果. +即可使用您的 IDE 完成 PainterEngine 的编译,PainterEngine 库将尽力保证所有平台的的运行结果一致性,在 Windows 上开发,同样在 Android/web/Linux/iOS/…… 中能够得到一致的结果。 If you don't need the one-key compilation feature provided by PainterEngine Make and prefer to develop PainterEngine programs or components using your preferred IDE, you just need to: @@ -99,35 +103,33 @@ If you don't need the one-key compilation feature provided by PainterEngine Make You can now use your IDE to compile PainterEngine with these steps. PainterEngine library will strive to ensure consistent results across all platforms. What you develop on Windows will yield consistent results on Android, web, Linux, iOS, and more. -## 不仅是图形库, 更是应用程序框架 +## 不仅是图形库,更是应用程序框架 -## Not just a graphics library but also an application framework. +## Not just a graphics library but also an application framework | Functions | Support | Description | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ | | 内存池 | alloc/free | 平台无关的内存池实现 | | 数学库 | sin/cos/tan/arcsin/log/exp/relu/... | 绝大部分 C 标准数学库的完整实现 | -| 信号处理 | dft/dct/fft/dwt/window functions/mfcc/... | 傅里叶/余弦/小波变换, 常用窗函数, mfcc 等特征采集算法...等等信号处理相关基础函数及上层特征采集算法 | +| 信号处理 | dft/dct/fft/dwt/window functions/mfcc/... | 傅里叶/余弦/小波变换、常用窗函数、mfcc 等特征采集算法……等等信号处理相关基础函数及上层特征采集算法 | | 数据结构 | string/vector/list/map/stack/fifo/circular-buffer/... | 平台无关的数据结构算法实现 | | 密码学 | curve25519/AES/SHAx/MD5/... | 包含常用的密钥对称算法及密钥协商算法 | | 图片支持 | PNG/JPG/GIF/BMP | 支持 PNG/JPG/GIF/BMP 解码及 PNG 编码 | -| 音频支持 | WAV/MP3 | 支持 Wav, Mp3 解码及 Wav 编码| -| 字模支持 | ttf | 支持 ttf 字模文件(由 stb_truetype.c 移植而来)| +| 音频支持 | WAV/MP3 | 支持 Wav、Mp3 解码及 Wav 编码| +| 字模支持 | ttf | 支持 ttf 字模文件(由 stb_truetype.c 移植而来)| | 几何绘制 | Line/Triangle/Rectangle/Circle/Ring/Sector/Rounded/... | 常用几何光栅化实现 | | 渲染器 | 2D/3D | 2D/3D 渲染器实现及一个高质量制图引擎 | | 动画 | 2dx/live2D | 2D 动画和一个类 Live2D 骨骼动画系统 | -| 声学模型 | mixer/piano/ks | 包含一个混音器实现, 一个相位声码器, 一个物理建模的钢琴及 karplus-strong 合成的拨弦模型, 直接合成 PCM 音频流 | -| 脚本引擎 | Compiler/VM/Debugger | 一个完整的脚本引擎, 包含编译器虚拟机调试器 | +| 声学模型 | mixer/piano/ks | 包含一个混音器实现、一个相位声码器、一个物理建模的钢琴及 karplus-strong 合成的拨弦模型,直接合成 PCM 音频流 | +| 脚本引擎 | Compiler/VM/Debugger | 一个完整的脚本引擎,包含编译器虚拟机调试器 | | UI 框架 | button/radio/image/edit/label/list/... | UI 框架实现 | | 协议 | MQTT/MODBUS/Game-network-synchronization | 常用的通讯协议 | | 游戏引擎 | | 集成一个游戏世界框架 | -| FPGA-GPU |2D accelerator | 实现了基于 FPGA 的 GPU 图形加速器, 能够为 PainterEngine 提供不低于 50Mpps 的 2D Blender 及图元光栅化加速, 支持 HDMI 输出, 目前已在 zynq7000 系列 Soc 上完成验证| - -还有更多探索... - -/////////////////////////////////////////////////////////////////////////////// +| FPGA-GPU |2D accelerator | 实现了基于 FPGA 的 GPU 图形加速器,能够为 PainterEngine 提供不低于 50Mpps 的 2D Blender 及图元光栅化加速,支持 HDMI 输出,目前已在 zynq7000 系列 Soc 上完成验证| +还有更多探索…… +--- | functions | support | Description | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ | @@ -155,7 +157,7 @@ Many more to explore... ## The FPGA-GPU Graphics Acceleration Solution -提供一个基于 FPGA 的 GPU IP 核, 已在 Zynq7020 上完成功能验证, 提供不低于 50Mpps 的 2D Blender 图形渲染加速, 支持 HDMI 输出, 目前已在 zynq7000 系列 Soc 上完成验证. +提供一个基于 FPGA 的 GPU IP 核,已在 Zynq7020 上完成功能验证,提供不低于 50Mpps 的 2D Blender 图形渲染加速,支持 HDMI 输出,目前已在 zynq7000 系列 SoC 上完成验证。 A GPU IP core based on FPGA has been developed and functionally verified on the Zynq 7020 platform. It provides 2D Blender graphics rendering acceleration with a performance of no less than 50Mpps (Million pixels per second) and supports HDMI output. This solution has been successfully validated on the Zynq 7000 series SoC. @@ -165,13 +167,13 @@ A GPU IP core based on FPGA has been developed and functionally verified on the

PainterEngine designer

-## 组件化开发, 支持设计器模式, 简单的不能再简单 +## 组件化开发,支持设计器模式,简单的不能再简单 ## Component-based development with support for a designer mode, making it as simple as it gets

PainterEngine designer

-## 海量组件, 创意无界 +## 海量组件,创意无界 ## An abundance of components to unleash your creativity without limitations. @@ -183,9 +185,9 @@ A GPU IP core based on FPGA has been developed and functionally verified on the

PainterEngine market

-### 现在, 访问 PainterEngine.com, 参与建设 +### 现在,访问 PainterEngine.com,参与建设 -### Now, join the PainterEngine.com contribute to its development. +### Now, join the PainterEngine.com contribute to its development

logo diff --git a/documents/PainterEngine the book.md b/documents/PainterEngine_the_book_zh-CN.md similarity index 59% rename from documents/PainterEngine the book.md rename to documents/PainterEngine_the_book_zh-CN.md index d719e457..ea817dff 100644 --- a/documents/PainterEngine the book.md +++ b/documents/PainterEngine_the_book_zh-CN.md @@ -1,24 +1,24 @@ -# The Book Of PainterEngine +# PainterEngine 快速入门手册 -## 导言 +## 导言 -欢迎开始我们的 PainterEngine 的第一课, 但在此之前相信大家并不清楚 PainterEngine 到底是一个做什么的, 也许您已经在互联网上对它有些许印象, 它或许是一个图形库, 或者是游戏引擎, 你可能已经看过了基于它的关于声学、密码学、神经网络、数字信号处理、编译器、虚拟机等等相关内容,甚至还有一个基于 FPGA 的 GPU IP 核。所以你可能认为它是一个集合了各式各样程式库的大混合体。 +欢迎开始我们的 PainterEngine 的第一课,但在此之前相信大家并不清楚 PainterEngine 到底是一个做什么的,也许您已经在互联网上对它有些许印象,它或许是一个图形库,或者是游戏引擎,你可能已经看过了基于它的关于声学、密码学、神经网络、数字信号处理、编译器、虚拟机等等相关内容,甚至还有一个基于 FPGA 的 GPU IP 核。所以你可能认为它是一个集合了各式各样程式库的大混合体。 以上都对,但总的来说,我更倾向于将 PainterEngine 认为是一个应用程序框架,它诞生之初的最终原因,是为了解决程序开发过程中极其麻烦的三方库(甚至是标准库)依赖问题,最大程度简化程序的平台移植和编译难度。 因此如你所见,PainterEngine 的编译不会让你陷入各式各样的三方依赖中,目前它可以在几乎所有提供 C 语言编译环境的平台上运行,它没有操作系统及文件系统依赖,可以运行在裸 MCU 环境中,甚至 PainterEngine 的官网首页也是由 PainterEngine 开发的。 -PainterEngine 始终遵循着最简设计原则,而 PainterEngine 使用了 C 语言作为其主要开发语言,而其内置的脚本引擎,同样最大程度的兼容 C 语言语法,并对 C 语言的类型做了少量的抽象及泛化,进一步减少它的上手及使用门槛。C 语言作为一门历史悠久的语言,如今几乎是各大工科类专业必学的一门课程,其在计算机编程开发的发展中,始终保持着强大的竞争力及广泛认可,并成为了几乎所有硬件平台,所需要并提供支持的事实标准。C 语言在学习与开发成本维持着一个微妙的平衡,因此你可以在很短的时间学习并上手 C 语言,配合 PainterEngine,你就能将你的程序运行在全平台,并深刻感受编程艺术所带来的魅力。 +PainterEngine 始终遵循着最简设计原则,而 PainterEngine 使用了 C 语言作为其主要开发语言,而其内置的脚本引擎同样最大程度的兼容 C 语言语法,并对 C 语言的类型做了少量的抽象及泛化,进一步减少它的上手及使用门槛。C 语言作为一门历史悠久的语言,如今几乎是各大工科类专业必学的一门课程,其在计算机编程开发的发展中,始终保持着强大的竞争力及广泛认可,并成为了几乎所有硬件平台所需要并提供支持的事实标准。C 语言在学习与开发成本维持着一个微妙的平衡,因此你可以在很短的时间学习并上手 C 语言,配合 PainterEngine,你就能将你的程序运行在全平台,并深刻感受编程艺术所带来的魅力。 PainterEngine 同样经历了近乎十年的发展,但在很长的一段时间,其作为一个私用库较少在公开领域正式推广,一个是在其迭代的过程中,很多的接口和函数仍然未稳定,我们必须在长期的实践中,保证接口设计的合理性和易用性,区分哪些是“真正有用且好用”的,哪些只是“灵光一闪看上去光鲜亮丽,其实没啥用的”。因此 PainterEngine 在很长一段时间,都没有详细且稳定的文档,而经过那么多年的迭代,我们最终可以将那些稳定、好用、简单易学的设计公布出来,并最终给大家带来这篇文档。 -最后,我并不希望将导言写的太长,是时候马上切入主题了,我们将从 PainterEngine 的环境搭建开始 PainterEngine 的第一课,如果你有相关问题或发现了 bug,你可以在 PainterEngine 论坛中提问,或者直接将问题发送到 matrixcascade@gmail.com, 我将在第一时间给你反馈。 +最后,我并不希望将导言写的太长,是时候马上切入主题了,我们将从 PainterEngine 的环境搭建开始 PainterEngine 的第一课,如果你有相关问题或发现了 bug,你可以在 PainterEngine 论坛中提问,或者直接将问题发送到 matrixcascade@gmail.com,我将在第一时间给你反馈。 ![](assets/mini/1.png) ## 1. 一个最简单的 PainterEngine 程序 -在搭建开发环境之前,让我们先编写一个最简单的 PainterEngine 程序,让我们新建一个 `main.c` 文件(文件名可以任意取), 然后在其中输入以下代码: +在搭建开发环境之前,让我们先编写一个最简单的 PainterEngine 程序,让我们新建一个 `main.c` 文件(文件名可以任意取),然后在其中输入以下代码: ```c #include "PainterEngine.h" @@ -29,25 +29,25 @@ int main() } ``` -这是一个相当简单的 PainterEngine 程序, 简单来说, 在第一行我们使用 `include` 将 PainterEngine 的头文件包含了进来, 在 `main` 函数中, 我们使用 `PainterEngine_Initialize` 对 PainterEngine 进行初始化操作, `PainterEngine_Initialize` 接受 2 个参数, 分别是窗口(或者是屏幕)的宽度和高度, 在程序运行后, 你大概能看到这样的结果: +这是一个相当简单的 PainterEngine 程序,简单来说,在第一行我们使用 `include` 将 PainterEngine 的头文件包含了进来,在 `main` 函数中,我们使用 `PainterEngine_Initialize` 对 PainterEngine 进行初始化操作,`PainterEngine_Initialize` 接受 2 个参数,分别是窗口(或者是屏幕)的宽度和高度,在程序运行后,你大概能看到这样的结果: ![](assets/img/1.1.png) -当然, 现在我们还没有使用 PainterEngine 在窗口上绘制任何东西, 因此你看到的是一个空白的画面。需要注意的是, 在 PainterEngine 的框架中, `main` 函数返回后, 程序并不会立即结束, 实际上在 `PainterEngine.h` 中, `main` 函数被替换成了 `px_main`, 真正的 `main` 函数在 `px_main.c` 中实现, 但用户目前可以用不着关心这个, 只需要记住 `main` 函数返回后, 程序仍然是在正常运行的。如果你希望退出程序, 你可以自行调用 C 语言的 `exit` 函数, 但在 PainterEngine 中的很多时候你并不需要这么做, 因为像在嵌入式单片机、网页、驱动程序中,大部分是用不到退出这个概念的,哪怕是在 Android,iOS 平台,绝大部分时候也不需要你在程序中设计退出功能。 +当然,现在我们还没有使用 PainterEngine 在窗口上绘制任何东西,因此你看到的是一个空白的画面。需要注意的是,在 PainterEngine 的框架中,`main` 函数返回后,程序并不会立即结束,实际上在 `PainterEngine.h` 中,`main` 函数被替换成了 `px_main`,真正的 `main` 函数在 `px_main.c` 中实现,但用户目前可以用不着关心这个,只需要记住 `main` 函数返回后,程序仍然是在正常运行的。如果你希望退出程序,你可以自行调用 C 语言的 `exit` 函数,但在 PainterEngine 中的很多时候你并不需要这么做,因为像在嵌入式单片机、网页、驱动程序中,大部分是用不到退出这个概念的,哪怕是在 Android,iOS 平台,绝大部分时候也不需要你在程序中设计退出功能。 ## 2. 编译 PainterEngine 程序 ### 使用 PainterEngine Make 编译 -如果你想要编译 PainterEngine 的项目文件,最简单的做法是使用 PainterEngine Make,这是一款你能够在 PainterEngine.com 中下载到的一款编译工具, 你可以在主页下方找到它的下载按钮: +如果你想要编译 PainterEngine 的项目文件,最简单的做法是使用 PainterEngine Make,这是一款你能够在 PainterEngine.com 中下载到的一款编译工具,你可以在主页下方找到它的下载按钮: ![](assets/img/2.1.png) -解压缩以后, 直接运行 PainterEngine make.exe, 你就可以看到如下界面: +解压缩以后,直接运行 PainterEngine make.exe,你就可以看到如下界面: ![](assets/img/2.2.png) -然后选择你需要编译的平台, 然后选中我们之前创建的那个 C 语言代码文件: +然后选择你需要编译的平台,然后选中我们之前创建的那个 C 语言代码文件: ![](assets/img/2.3.png) @@ -57,13 +57,13 @@ int main() ### 使用 Visual Studio Code 编译 -要使用 Visual Studio Code 进行编译, 你需要确保你已经安装好了 Visual Studio Code 的 C 语言开发环境, 这块步骤我们略过, 因为在互联网上已经有相当多这样的教程了。 +要使用 Visual Studio Code 进行编译,你需要确保你已经安装好了 Visual Studio Code 的 C 语言开发环境,这块步骤我们略过,因为在互联网上已经有相当多这样的教程了。 然后请到 PainterEngine 中,下载 PainterEngine 的源码: ![](assets/img/2.5.png) -将下载好的源码解压到计算机的某个目录下,然后你需要记录这个目录的位置,并在 Windows 的环境变量中新建一个名叫 `PainterEnginePath` 的变量, 并将它赋值为 PainterEngine 库所在目录: +将下载好的源码解压到计算机的某个目录下,然后你需要记录这个目录的位置,并在 Windows 的环境变量中新建一个名叫 `PainterEnginePath` 的变量,并将它赋值为 PainterEngine 库所在目录: ![](assets/img/2.6.png) @@ -75,7 +75,7 @@ int main() ![](assets/img/2.9.png) -然后请用 Visual Studio Code 打开 `main.c` 文件, 然后你就可以进行编译运行了: +然后请用 Visual Studio Code 打开 `main.c` 文件,然后你就可以进行编译运行了: ![](assets/img/2.10.png) @@ -83,7 +83,7 @@ int main() ### 使用 Visual Studio 编译 -当然, 如果你需要使用完整的 IDE 开发体验, 那么仍然建议使用 Visual Studio 进行开发编译。要使用 Visual Studio 开发 PainterEngine,你需要打开 Visual Studio 创建一个空项目: +当然,如果你需要使用完整的 IDE 开发体验,那么仍然建议使用 Visual Studio 进行开发编译。要使用 Visual Studio 开发 PainterEngine,你需要打开 Visual Studio 创建一个空项目: ![](assets/img/2.12.png) @@ -103,7 +103,7 @@ int main() ![](assets/img/2.17.png) -打开 `项目` → `属性` → `VC++目录` 中, 把 PainterEngine 的所在目录包含进来: +打开 `项目` → `属性` → `VC++目录` 中,把 PainterEngine 的所在目录包含进来: ![](assets/img/2.18.png) @@ -111,73 +111,74 @@ int main() ![](assets/img/2.20.png) -特别需要注意检查配置和 Visual Stdio 的当前配置是否是一致的, 这是一个很容易出错的点: +特别需要注意检查配置和 Visual Stdio 的当前配置是否是一致的,这是一个很容易出错的点: ![](assets/img/2.21.png) -最后, 你就可以编译运行调试了: +最后,你就可以编译运行调试了: ![](assets/img/2.22.png) ## 3. PainterEngine 第一课,输出文字“Hello PainterEngine” -正如你所见,PainterEngine 是一个图形化应用程序框架, 但依据传统, 我们的第一课仍然是如何用 PainterEngine 输出文字, 但多数时候, 与其说是输出文字不如说是绘制文字。使用 PainterEngine 绘制文字非常简单, 查看如下代码: +正如你所见,PainterEngine 是一个图形化应用程序框架,但依据传统,我们的第一课仍然是如何用 PainterEngine 输出文字,但多数时候,与其说是输出文字不如说是绘制文字。使用 PainterEngine 绘制文字非常简单,查看如下代码: ```c #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); -//PainterEngine_DrawText -//参数1:x坐标 -//参数2:y坐标 -//参数3:文本内容 -//参数4:对齐方式 -//参数5:颜色 + PainterEngine_Initialize(800, 480); + // PainterEngine_DrawText + // 参数1:x坐标 + // 参数2:y坐标 + // 参数3:文本内容 + // 参数4:对齐方式 + // 参数5:颜色 PainterEngine_DrawText(400, 240, "Hello PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); return 1; } ``` -我们主要看 `PainterEngine_DrawText` 函数, 这是一个文本绘制函数, 它有 5 个参数, 当你运行程序后, 你将看到这个结果: + +我们主要看 `PainterEngine_DrawText` 函数,这是一个文本绘制函数,它有 5 个参数,当你运行程序后,你将看到这个结果: ![](assets/img/3.1.png) -整个函数非常好理解, 但这里我们详细解释一下 `对齐方式` 和 `颜色` 这两个参数的意义, 因为在后续的教程中, 这两个概念会经常被提及: +整个函数非常好理解,但这里我们详细解释一下 `对齐方式` 和 `颜色` 这两个参数的意义,因为在后续的教程中,这两个概念会经常被提及: -其中, `对齐方式` 就是对应内容绘制在屏幕上的对齐方式, PainterEngine 中的对齐方式包含以下几种格式: +其中,`对齐方式` 就是对应内容绘制在屏幕上的对齐方式,PainterEngine 中的对齐方式包含以下几种格式: ```c typedef enum { - PX_ALIGN_LEFTTOP = 7,//左上角对齐 - PX_ALIGN_MIDTOP = 8,//中上对齐 - PX_ALIGN_RIGHTTOP = 9,//右上角对齐 - PX_ALIGN_LEFTMID = 4,//左中对齐 - PX_ALIGN_CENTER = 5,//居中对齐 - PX_ALIGN_RIGHTMID = 6,//右中对齐 - PX_ALIGN_LEFTBOTTOM = 1,//左下角对齐 - PX_ALIGN_MIDBOTTOM = 2,//中底对齐 - PX_ALIGN_RIGHTBOTTOM = 3,//右下角对齐 + PX_ALIGN_LEFTTOP = 7, // 左上角对齐 + PX_ALIGN_MIDTOP = 8, // 中上对齐 + PX_ALIGN_RIGHTTOP = 9, // 右上角对齐 + PX_ALIGN_LEFTMID = 4, // 左中对齐 + PX_ALIGN_CENTER = 5, // 居中对齐 + PX_ALIGN_RIGHTMID = 6, // 右中对齐 + PX_ALIGN_LEFTBOTTOM = 1, // 左下角对齐 + PX_ALIGN_MIDBOTTOM = 2, // 中底对齐 + PX_ALIGN_RIGHTBOTTOM = 3, // 右下角对齐 }PX_ALIGN; ``` -这个对齐方式的枚举类型是设计过的, 你可以直接看看你数字小键盘, 其对齐方式和数字小键盘的数值是对应关系。 +这个对齐方式的枚举类型是设计过的,你可以直接看看你数字小键盘,其对齐方式和数字小键盘的数值是对应关系。 -而 `颜色格式` 则是一个被定义为 `px_color` 的结构体, 这个结构体有 4 个字节, 内部有 4 个成员变量 a、r、g、b,分别代表颜色的透明度、红色通道、绿色通道和蓝色通道,每一个分量的取值范围都是 0-255,例如红色,这个数值越大,这个颜色就越红。 +而 `颜色格式` 则是一个被定义为 `px_color` 的结构体,这个结构体有 4 个字节,内部有 4 个成员变量 a、r、g、b,分别代表颜色的透明度、红色通道、绿色通道和蓝色通道,每一个分量的取值范围都是 0-255,例如红色,这个数值越大,这个颜色就越红。 -因此,你可以看到,在上面的示范代码中,我们绘制了一个红色的文本 `Hello PainterEngine`。现在让我们试试中文, 修改上面的代码, 改为下面这种格式: +因此,你可以看到,在上面的示范代码中,我们绘制了一个红色的文本 `Hello PainterEngine`。现在让我们试试中文,修改上面的代码,改为下面这种格式: ```c #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); -//PainterEngine_DrawText -//参数1:x坐标 -//参数2:y坐标 -//参数3:文本内容 -//参数4:对齐方式 -//参数5:颜色 + PainterEngine_Initialize(800, 480); + // PainterEngine_DrawText + // 参数1:x坐标 + // 参数2:y坐标 + // 参数3:文本内容 + // 参数4:对齐方式 + // 参数5:颜色 PainterEngine_DrawText(400, 240, "你好PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); return 1; } @@ -185,14 +186,14 @@ int main() ![](assets/img/3.2.png) -但是, 中文却不能正确显示, 这是因为 PainterEngine 中, 默认只有英文的字模, 如果我们想要支持中文怎么办呢? -这仍然不困难, 为此, 我们需要先准备一个 ttf 字模文件, 例如在这里, 我准备了一个幼圆字体, 那么, 我只需要将这个字体加载进来就可以了: +但是,中文却不能正确显示,这是因为 PainterEngine 中,默认只有英文的字模,如果我们想要支持中文怎么办呢? +这仍然不困难,为此,我们需要先准备一个 TTF 字模文件,例如在这里,我准备了一个幼圆字体,那么,我只需要将这个字体加载进来就可以了: ```c #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); + PainterEngine_Initialize(800, 480); PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 24); PainterEngine_DrawText(400, 240, "你好 PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); return 1; @@ -201,7 +202,7 @@ int main() ![](assets/img/3.3.png) -`PainterEngine_LoadFontModule` 的函数的第一个参数是 TTF 字体文件的路径, 相对路径是以 exe 文件所在路径相对的。第二个参数是字符集,在默认情况下, Visual Studio 代码使用的是 GBK 字符集。如果你使用 Visual Studio Code, 那么默认是 UTF8 编码, 第二个参数应该换成 `PX_FONTMODULE_CODEPAGE_GBK`。最后一个参数是字体的大小。 +`PainterEngine_LoadFontModule` 的函数的第一个参数是 TTF 字体文件的路径,相对路径是以 exe 文件所在路径相对的。第二个参数是字符集,在默认情况下,Visual Studio 代码使用的是 GBK 字符集。如果你使用 Visual Studio Code,那么默认是 UTF8 编码,第二个参数应该换成 `PX_FONTMODULE_CODEPAGE_GBK`。最后一个参数是字体的大小。 ## 4. 使用 PainterEngine 绘制几何图形 @@ -209,10 +210,10 @@ int main() `px_void PainterEngine_DrawLine(px_int x1, px_int y1, px_int x2, px_int y2, px_int linewidth, px_color color);` 这个函数用于绘制一条线段。 -* x1, y1: 线段的起点坐标。 -* x2, y2: 线段的终点坐标。 -* linewidth: 线段的宽度。 -* color: 线段的颜色。 +* x1, y1:线段的起点坐标。 +* x2, y2:线段的终点坐标。 +* linewidth:线段的宽度。 +* color:线段的颜色。 ```c #include "PainterEngine.h" @@ -240,10 +241,10 @@ int main() `px_void PainterEngine_DrawRect(px_int x, px_int y, px_int width, px_int height, px_color color);` 这个函数用于绘制一个矩形。 -* x, y: 矩形的左上角坐标。 -* width: 矩形的宽度。 -* height: 矩形的高度。 -* color: 矩形的颜色。 +* x, y:矩形的左上角坐标。 +* width:矩形的宽度。 +* height:矩形的高度。 +* color:矩形的颜色。 ![](assets/img/3.5.png) @@ -272,10 +273,10 @@ int main() `px_void PainterEngine_DrawCircle(px_int x, px_int y, px_int radius, px_int linewidth, px_color color);` 这个函数用于绘制一个圆环。 -* x, y: 圆心的坐标。 -* radius: 圆的半径。 -* linewidth: 圆的线宽。 -* color: 圆的颜色。 +* x, y:圆心的坐标。 +* radius:圆的半径。 +* linewidth:圆的线宽。 +* color:圆的颜色。 ```c #include "PainterEngine.h" @@ -302,9 +303,9 @@ int main() `px_void PainterEngine_DrawSolidCircle(px_int x, px_int y, px_int radius, px_color color);` 这个函数用于绘制一个实心圆。 -* x, y: 圆心的坐标。 -* radius: 圆的半径。 -* color: 圆的颜色。 +* x, y:圆心的坐标。 +* radius:圆的半径。 +* color:圆的颜色。 ```c #include "PainterEngine.h" @@ -334,12 +335,12 @@ int main() `px_void PainterEngine_DrawSector(px_int x, px_int y, px_int inside_radius,px_int outside_radius, px_int start_angle, px_int end_angle, px_color color);` 这个函数用于绘制一个扇形。 参数说明: -* x, y: 扇形的圆心坐标。 -* inside_radius: 扇形的内半径。 -* inside_radius: 扇形的外半径。 -* start_angle: 扇形的起始角度(以度为单位, 支持负角度)。 -* end_angle: 扇形的结束角度(以度为单位, 支持负角度)。 -* color: 扇形的颜色。 +* x, y:扇形的圆心坐标。 +* inside_radius:扇形的内半径。 +* inside_radius:扇形的外半径。 +* start_angle:扇形的起始角度(以度为单位,支持负角度)。 +* end_angle:扇形的结束角度(以度为单位,支持负角度)。 +* color:扇形的颜色。 ```c #include "PainterEngine.h" @@ -367,18 +368,18 @@ int main() `px_void PainterEngine_DrawPixel(px_int x, px_int y, px_color color);` 这个函数用于绘制一个像素点。 -* x, y: 像素点的坐标。 -* color: 像素点的颜色。 +* x, y:像素点的坐标。 +* color:像素点的颜色。 -这只是绘制一个像素点, 就不放示例图了。 +这只是绘制一个像素点,就不放示例图了。 ## 5. 使用 PainterEngine 绘制图像 -使用 PainterEngine 绘制图像仍然很简单, 但在绘制图像之前, 我们需要先加载图片。 +使用 PainterEngine 绘制图像仍然很简单,但在绘制图像之前,我们需要先加载图片。 PainterEngine 可以直接从文件中加载图片,它原生支持 PNG、JPG、BMP、TRAW 四种静态图片格式的加载,为了存储加载的图片,我们需要用到一个叫纹理的结构体。 -在 PainterEngine 中,纹理用 `px_texture` 结构体进行描述,因此为了加载纹理,我们需要 `PX_LoadTextureFromFile` 函数, 这个函数是一个三参数的图片文件加载函数。第一个参数是内存池, 在后面的章节, 我将会更详细地讲解 PainterEngine 内存池的内容。在默认情况下, PainterEngine 提供 2 个默认内存池, 一个是 `mp`,一个是 `mp_static`, 其中, 前面的内存池一般用于需要频繁分配释放的元素, 后面的则用于静态资源的存储。在这里因为图片一般是静态资源, 因此填写 `mp_static` 就可以了。第二个参数则是我们纹理结构体的指针,在图片成功加载后,将会初始化这个结构体,并用于保存图片数据。最后一个参数,则是图片文件的所在路径。 +在 PainterEngine 中,纹理用 `px_texture` 结构体进行描述,因此为了加载纹理,我们需要 `PX_LoadTextureFromFile` 函数,这个函数是一个三参数的图片文件加载函数。第一个参数是内存池,在后面的章节,我将会更详细地讲解 PainterEngine 内存池的内容。在默认情况下,PainterEngine 提供 2 个默认内存池,一个是 `mp`,一个是 `mp_static`。其中,前面的内存池一般用于需要频繁分配释放的元素,后面的则用于静态资源的存储。在这里因为图片一般是静态资源,因此填写 `mp_static` 就可以了。第二个参数则是我们纹理结构体的指针,在图片成功加载后,将会初始化这个结构体,并用于保存图片数据。最后一个参数,则是图片文件的所在路径。 在加载文件成功后,我们使用 `PainterEngine_DrawTexture` 函数绘制出来。这是一个四参数的函数: @@ -390,13 +391,13 @@ PainterEngine 可以直接从文件中加载图片,它原生支持 PNG、JPG ```c #include "PainterEngine.h" -px_texture mytexture;//纹理 +px_texture mytexture; // 纹理 int main() { PainterEngine_Initialize(512, 512); if(!PX_LoadTextureFromFile(mp_static,&mytexture,"assets/demo.png")) { - //加载纹理失败 + // 加载纹理失败 return 0; } PainterEngine_DrawTexture(&mytexture, 0, 0, PX_ALIGN_LEFTTOP); @@ -410,7 +411,7 @@ int main() ## 6. PainterEngine 内存池管理机制 -因为 PainterEngine 是无系统及标准库依赖的, 因此在 PainterEngine 中, 必须独立于系统及标准库的内存管理机制, 实现 PainterEngine 内部的内存管理系统。因此 PainterEngine 使用了内存池作为动态的内存管理系统。 +因为 PainterEngine 是无系统及标准库依赖的,因此在 PainterEngine 中,必须独立于系统及标准库的内存管理机制,实现 PainterEngine 内部的内存管理系统。因此 PainterEngine 使用了内存池作为动态的内存管理系统。 PainterEngine 内存池实现方式同样很简洁,为了使用内存,你必须预先准备一段可用的内存空间,作为内存池管理的内存空间,例如在下面的代码中,我们可以用 C 语言定义一个较大的全局数组,然后用这个数组空间作为内存池的分配空间: @@ -421,13 +422,13 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//创建内存池 - myalloc=MP_Malloc(&mp, 1024);//在内存池中分配1024字节 + mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + myalloc=MP_Malloc(&mp, 1024); // 在内存池中分配1024字节 return 1; } ``` -需要注意的是, **_使用内存池分配的空间, 会略小于分配给内存池的空间。如果你分配的空间超出了内存池, 将会导致一个停机错误。_** +需要注意的是,**_使用内存池分配的空间,会略小于分配给内存池的空间。如果你分配的空间超出了内存池,将会导致一个停机错误。_** ```c #include "PainterEngine.h" @@ -436,15 +437,15 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//创建内存池 - myalloc=MP_Malloc(&mp, 1024*1024);//在内存池中分配1024*1024字节,但内存池实际容量小于分配给内存池容量,因此这里内存不足,这里将会进入中断 + mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + myalloc=MP_Malloc(&mp, 1024*1024); // 在内存池中分配1024*1024字节,但内存池实际容量小于分配给内存池容量,因此这里内存不足,这里将会进入中断 return 1; } ``` -如果你不希望因为内存池不足导致停机错误, 你可以使用以下两种方式: +如果你不希望因为内存池不足导致停机错误,你可以使用以下两种方式: -1.你可以设置错误回调, 自行处理内存池的错误: +1.你可以设置错误回调,自行处理内存池的错误: ```c #include "PainterEngine.h" @@ -471,14 +472,14 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//创建内存池 - MP_ErrorCatch(&mp, my_memory_cache_error,0);//设置错误回调 - myalloc=MP_Malloc(&mp, 1024*1024);//在内存池中分配1024*1024字节 + mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + MP_ErrorCatch(&mp, my_memory_cache_error,0); // 设置错误回调 + myalloc=MP_Malloc(&mp, 1024*1024); // 在内存池中分配1024*1024字节 return 1; } ``` -2.或者你也可以直接关闭内存池的错误异常处理, 那么当内存池无法正常分配足够内存时, 将会直接返回 `NULL`: +2.或者你也可以直接关闭内存池的错误异常处理,那么当内存池无法正常分配足够内存时,将会直接返回 `NULL`: ```c #include "PainterEngine.h" @@ -488,14 +489,14 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//创建内存池 - MP_NoCatchError(&mp, PX_TRUE);//设置内存池不捕获错误 - myalloc=MP_Malloc(&mp, 1024*1024);//在内存池中分配1024*1024字节,但内存池不捕获错误,所以会直接返回NULL + mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + MP_NoCatchError(&mp, PX_TRUE); // 设置内存池不捕获错误 + myalloc=MP_Malloc(&mp, 1024*1024); // 在内存池中分配1024*1024字节,但内存池不捕获错误,所以会直接返回NULL return 1; } ``` -在 PainterEngine 中, 有 2 个系统默认的内存池, 它们分别是 `mp` 和 `mp_static`, 你可以打开 `PainterEngine_Application.h` 文件, 在当中找到这两个内存池的定义, 但我们最需要关注的, 还是下面的代码: +在 PainterEngine 中,有 2 个系统默认的内存池,它们分别是 `mp` 和 `mp_static`,你可以打开 `PainterEngine_Application.h` 文件,在当中找到这两个内存池的定义,但我们最需要关注的,还是下面的代码: ```c #define PX_APPLICATION_NAME "PainterEngine" @@ -504,16 +505,16 @@ int main() #define PX_APPLICATION_MEMORYPOOL_SPACE_SIZE (1024*1024*16) ``` -这是这两个内存池的直接相关配置宏, 其中 `PX_APPLICATION_MEMORYPOOL_STATIC_SIZE` 表示 `mp_static` 内存池的内存分配大小, 而 `PX_APPLICATION_MEMORYPOOL_DYNAMIC_SIZE` 则是 `mp` 内存池的内存分配大小, `PX_APPLICATION_MEMORYPOOL_SPACE_SIZE` 则是系统其它资源。PainterEngine 程序运行开始, 就至少会占用这三个宏累加起来的内存, 之后的内存分配, 都围绕在这几个内存池中进行。如果你发现 PainterEngine 运行的内存不够了, 你可以自己手动拓展内存池的大小。当然, 如果你希望节约点内存, 你也可以手动将它们改小。 +这是这两个内存池的直接相关配置宏,其中 `PX_APPLICATION_MEMORYPOOL_STATIC_SIZE` 表示 `mp_static` 内存池的内存分配大小,而 `PX_APPLICATION_MEMORYPOOL_DYNAMIC_SIZE` 则是 `mp` 内存池的内存分配大小,`PX_APPLICATION_MEMORYPOOL_SPACE_SIZE` 则是系统其它资源。PainterEngine 程序运行开始,就至少会占用这三个宏累加起来的内存,之后的内存分配都围绕在这几个内存池中进行。如果你发现 PainterEngine 运行的内存不够了,你可以自己手动拓展内存池的大小。当然,如果你希望节约点内存,你也可以手动将它们改小。 ## 7. 使用 PainterEngine 创建 GUI 按钮 -在本章节中, 我们将第一次接触 PainterEngine 的组件。现在, 我们将使用 PainterEngine 创建一个经典 GUI 组件——按钮。 +在本章节中,我们将第一次接触 PainterEngine 的组件。现在,我们将使用 PainterEngine 创建一个经典 GUI 组件——按钮。 -在 PainterEngine 中, 所有的组件都是由 `PX_Object` 结构体进行描述的, 创建组件返回的都是一个 `PX_Object *` 类型的指针。 +在 PainterEngine 中,所有的组件都是由 `PX_Object` 结构体进行描述的,创建组件返回的都是一个 `PX_Object *` 类型的指针。 -但在本章节中, 我们并不需要考虑的那么复杂, 我们只需要创建一个按钮出来即可。在 PainterEngine 中, 最常用的按钮是 `PX_Object_PushButton` 类型。 +但在本章节中,我们并不需要考虑的那么复杂,我们只需要创建一个按钮出来即可。在 PainterEngine 中,最常用的按钮是 `PX_Object_PushButton` 类型。 ```c #include "PainterEngine.h" @@ -528,9 +529,9 @@ int main() ``` ![](assets/img/7.1.gif) -现在, 我们来详细看看 `PX_Object_PushButtonCreate` 函数。其中, 第一个参数是一个内存池, 在之前我们说过 PainterEngine 有 2 个系统默认的内存池,其实这里填 `mp` 或者 `mp_static` 都是没有问题的, 但考虑到界面可能会变动设计对象分配与销毁, 所以我们还是选择 `mp` 内存池。 +现在,我们来详细看看 `PX_Object_PushButtonCreate` 函数。其中,第一个参数是一个内存池,在之前我们说过 PainterEngine 有 2 个系统默认的内存池,其实这里填 `mp` 或者 `mp_static` 都是没有问题的,但考虑到界面可能会变动设计对象分配与销毁,所以我们还是选择 `mp` 内存池。 -第二个参数 `root` 是 PainterEngine 的根对象, PainterEngine 对象管理机制我们将在之后讨论。在这里, 你只需要理解为, 这里填 `root` 的意思是 **_创建一个按钮对象作为根对象的子对象_**。这样按钮就能链接到系统对象树中, 进行事件响应和渲染。 +第二个参数 `root` 是 PainterEngine 的根对象,PainterEngine 对象管理机制我们将在之后讨论。在这里,你只需要理解为,这里填 `root` 的意思是 **_创建一个按钮对象作为根对象的子对象_**。这样按钮就能链接到系统对象树中,进行事件响应和渲染。 然后是按钮的 x,y,width,height,也就是位置和宽度高度等信息。 @@ -538,29 +539,29 @@ int main() ## 8. PainterEngine 对象传递机制 -在上一个章节中,我们初略了解了根对象 `root`, 那么在本章节, 我们将学习一下 PainterEngine 的对象管理机制。 +在上一个章节中,我们初略了解了根对象 `root`,那么在本章节,我们将学习一下 PainterEngine 的对象管理机制。 -正如我们之前所说的,在 PainterEngine 中, 所有的组件都是由 `PX_Object` 结构体进行描述的, PainterEngine 的对象是以树的形式存在的: +正如我们之前所说的,在 PainterEngine 中,所有的组件都是由 `PX_Object` 结构体进行描述的,PainterEngine 的对象是以树的形式存在的: ![](assets/img/8.1.png) -每一个 `PX_Object` 都是这个树中的一个节点, 都可以有自己的子节点(可能多个)和自己的父节点(只能有一个)。同时, 每一个 `PX_Object` 都有以下四个基本功能函数: +每一个 `PX_Object` 都是这个树中的一个节点,都可以有自己的子节点(可能多个)和自己的父节点(只能有一个)。同时,每一个 `PX_Object` 都有以下四个基本功能函数: -`Create`:对象创建函数,或者说是对象初始化函数, 在 PainterEngine 中它一般是 `PX_Object_xxxxxCreate` 这种形式的, 其中 `xxxxx` 就是这个对象的名称, 比如上一章节的 `PushButton`, `Create` 函数一般是对象的一些初始化处理,并会将自己连接到对象树中。 +`Create`:对象创建函数,或者说是对象初始化函数,在 PainterEngine 中它一般是 `PX_Object_xxxxxCreate` 这种形式的,其中 `xxxxx` 就是这个对象的名称,比如上一章节的 `PushButton`,`Create` 函数一般是对象的一些初始化处理,并会将自己连接到对象树中。 -`Update`:对象的物理信息更新工作基本在这个函数中完成,一般会处理对象的一些物理信息, 比如位置大小速度等,常见于游戏设计中的物体,在 GUI 对象中则比较少见,其设计是与之后的 `Render` 也就是绘制函数进行区分, 因为在例如游戏服务端中, 对象并不需要进行绘制, 且绘制是非常消耗性能的。 +`Update`:对象的物理信息更新工作基本在这个函数中完成,一般会处理对象的一些物理信息,比如位置大小速度等,常见于游戏设计中的物体,在 GUI 对象中则比较少见,其设计是与之后的 `Render` 也就是绘制函数进行区分,因为在例如游戏服务端中,对象并不需要进行绘制,且绘制是非常消耗性能的。 -`Render`: 对象的绘制工作基本在这个函数中完成, 用于 `PX_Object` 的绘制功能, 将图像数据渲染到屏幕上, 当然有些情况下物理信息也会在这个函数中做, 是因为这个对象的物理信息并不会影响游戏的实际运行结果, 例如一些特效和粒子效果, 多数的 GUI 组件也几乎只用得到 `Render` 函数。 +`Render`:对象的绘制工作基本在这个函数中完成,用于 `PX_Object` 的绘制功能,将图像数据渲染到屏幕上,当然有些情况下物理信息也会在这个函数中做,是因为这个对象的物理信息并不会影响游戏的实际运行结果,例如一些特效和粒子效果,多数的 GUI 组件也几乎只用得到 `Render` 函数。 `Free`:对象的释放工作基本在这个函数中完成,例如在 `Create` 中加载了纹理,或者申请了内存,在这个函数中应该被释放。 以上 `Update`、`Render`、`Free` 函数具有传递的特性,也就是说: -* 如果某个对象节点执行了 `Update`, 那么它的所有子对象也会执行 `Update` -* 如果某个对象节点执行了 `Render`, 那么它的所有子对象也会执行 `Render` -* 如果某个对象节点执行了 `Free`, 那么它的所有子对象也会执行 `Free`, 父对象被删除了, 它的子节点也会被删除, 并且将会一直迭代到以这个节点为根节点的所有子节点都被删除。 +* 如果某个对象节点执行了 `Update`,那么它的所有子对象也会执行 `Update` +* 如果某个对象节点执行了 `Render`,那么它的所有子对象也会执行 `Render` +* 如果某个对象节点执行了 `Free`,那么它的所有子对象也会执行 `Free`,父对象被删除了,它的子节点也会被删除,并且将会一直迭代到以这个节点为根节点的所有子节点都被删除。 -因此,在上一章节我们创建了按钮,并将它连接到了 `root` 节点, 那么我们是不需要自己再手动执行 `Update`、`Render`、`Free` 函数的(在 `PX_Object_PushButton.c` 中它们已经被写好了), 因为根节点 `root` 是被自动更新渲染和释放的, 我们只需要负责 `Create` 就可以了。 +因此,在上一章节我们创建了按钮,并将它连接到了 `root` 节点,那么我们是不需要自己再手动执行 `Update`、`Render`、`Free` 函数的(在 `PX_Object_PushButton.c` 中它们已经被写好了),因为根节点 `root` 是被自动更新渲染和释放的,我们只需要负责 `Create` 就可以了。 当然,如果你希望删除这个对象的话,你只需要调用 `PX_ObjectDelayDelete` 或者 `PX_ObjectDelete` 就可以了: @@ -572,12 +573,12 @@ int main() PainterEngine_Initialize(800, 480); PainterEngine_LoadFontModule("assets/font.ttf",PX_FONTMODULE_CODEPAGE_GBK,20); myButtonObject=PX_Object_PushButtonCreate(mp,root,300,200,200,80,"我是一个按钮", PainterEngine_GetFontModule()); - PX_ObjectDelayDelete(myButtonObject);//删除对象 + PX_ObjectDelayDelete(myButtonObject); // 删除对象 return 1; } ``` -这两个函数的功能和参数都是一样的, 但是 `PX_ObjectDelayDelete` 会在更新和渲染完成后才执行删除, `PX_ObjectDelete` 则是立即删除,我建议使用 `PX_ObjectDelayDelete`,这样你就可以避免在某些情况下因为对象被立即删除了,而其它对象仍然引用了这个对象的数据,这会导致其访问失效内存。 +这两个函数的功能和参数都是一样的,但是 `PX_ObjectDelayDelete` 会在更新和渲染完成后才执行删除,`PX_ObjectDelete` 则是立即删除,我建议使用 `PX_ObjectDelayDelete`,这样你就可以避免在某些情况下因为对象被立即删除了,而其它对象仍然引用了这个对象的数据,这会导致其访问失效内存。 ## 9. PainterEngine 消息机制 @@ -605,15 +606,15 @@ int main() ![](assets/img/9.1.gif) -其中, `PX_OBJECT_EVENT_FUNCTION` 是一个宏, 因为事件响应函数是一个固定的格式, 因此非常建议你使用宏的方式来申明它, 它的定义原型如下: +其中,`PX_OBJECT_EVENT_FUNCTION` 是一个宏,因为事件响应函数是一个固定的格式,因此非常建议你使用宏的方式来申明它,它的定义原型如下: ```c #define PX_OBJECT_EVENT_FUNCTION(name) px_void name(PX_Object *pObject,PX_Object_Event e,px_void * ptr) ``` -可以看到, 这个回调函数有 3 个参数, 第一个是响应时间的对象的指针, 因为是按钮点击被触发了, 所以这个指针指向的就是这个按钮对象;第二个参数是事件类型 `e`,它是触发的事件类型;最后一个参数则是用户传递来的指针,它在注册时间响应函数 `PX_ObjectRegisterEvent` 被调用时就被传递进来了。 +可以看到,这个回调函数有 3 个参数,第一个是响应时间的对象的指针,因为是按钮点击被触发了,所以这个指针指向的就是这个按钮对象;第二个参数是事件类型 `e`,它是触发的事件类型;最后一个参数则是用户传递来的指针,它在注册时间响应函数 `PX_ObjectRegisterEvent` 被调用时就被传递进来了。 -事件类型有以下几种: +事件类型有以下几种: ```c #define PX_OBJECT_EVENT_ANY 0 //任意事件 @@ -627,7 +628,7 @@ int main() #define PX_OBJECT_EVENT_CURSORWHEEL 8 //鼠标滚轮 #define PX_OBJECT_EVENT_CURSORCLICK 9 //鼠标左键点击 #define PX_OBJECT_EVENT_CURSORDRAG 10 //鼠标拖拽 -#define PX_OBJECT_EVENT_STRING 11 //字符串事件(输入法输入) +#define PX_OBJECT_EVENT_STRING 11 //字符串事件(输入法输入) #define PX_OBJECT_EVENT_EXECUTE 12 //执行事件,不同组件有不同的执行方式 #define PX_OBJECT_EVENT_VALUECHANGED 13 //值改变事件,例如滑动条的值改变,或者文本框的值改变,或者列表框的选中项改变 #define PX_OBJECT_EVENT_DRAGFILE 14 //拖拽文件 @@ -649,34 +650,34 @@ int main() #define PX_OBJECT_EVENT_DAMAGE 30 //伤害事件 ``` -以上事件并非全部都是任何组件都会响应的, 例如在上面例子中的 `PX_OBJECT_EVENT_EXECUTE`, 它是按钮被单击时会被触发的事件, 或者是文本框中按下回车会触发的事件, 但有些例如滚动条和进度条, 并不会触发这个事件。也就是说有些事件是专属的。 +以上事件并非全部都是任何组件都会响应的,例如在上面例子中的 `PX_OBJECT_EVENT_EXECUTE`,它是按钮被单击时会被触发的事件,或者是文本框中按下回车会触发的事件,但有些例如滚动条和进度条,并不会触发这个事件。也就是说有些事件是专属的。 -但是类似于带有 `CURSOR` 或 `KEY` 的事件,是所有连接在 `root` 节点的组件都会收到的事件(但不一定响应)。需要注意的是, 类似于鼠标或触摸屏的 `CURSOR` 事件, 并非只有鼠标或触摸屏移动到组件所在位置与范围时才会触发, 只要有这类事件投递到 `root` 节点, 他就会逐层传递给它的所有子节点。如果你希望实现类似于按钮中的 "仅在鼠标点击到按钮时" 才触发, 你必须自行实现范围判断。 +但是类似于带有 `CURSOR` 或 `KEY` 的事件,是所有连接在 `root` 节点的组件都会收到的事件(但不一定响应)。需要注意的是,类似于鼠标或触摸屏的 `CURSOR` 事件,并非只有鼠标或触摸屏移动到组件所在位置与范围时才会触发,只要有这类事件投递到 `root` 节点,他就会逐层传递给它的所有子节点。如果你希望实现类似于按钮中的 "仅在鼠标点击到按钮时" 才触发,你必须自行实现范围判断。 你可以使用 ```c -px_float PX_Object_Event_GetCursorX(PX_Object_Event e);//获取cursor事件的x坐标 -px_float PX_Object_Event_GetCursorY(PX_Object_Event e);//获取cursor事件的y坐标 -px_float PX_Object_Event_GetCursorZ(PX_Object_Event e);//获取cursor事件的z坐标,一般用于鼠标中键滚轮 +px_float PX_Object_Event_GetCursorX(PX_Object_Event e); // 获取cursor事件的x坐标 +px_float PX_Object_Event_GetCursorY(PX_Object_Event e); // 获取cursor事件的y坐标 +px_float PX_Object_Event_GetCursorZ(PX_Object_Event e); // 获取cursor事件的z坐标,一般用于鼠标中键滚轮 ``` 来获取 `cursor` 事件中类似于 "鼠标现在在哪里" 的功能。 -让我们回到源代码 `OnButtonClick` 中做的很简单, 就是用 `PX_Object_PushButtonSetText` 改变了按钮文本的内容。 +让我们回到源代码 `OnButtonClick` 中做的很简单,就是用 `PX_Object_PushButtonSetText` 改变了按钮文本的内容。 -最后让我们来到 `PX_ObjectRegisterEvent` 函数,这个函数用于将事件与 C 语言函数绑定在一起,第一个参数是我们之前创建好的按钮组件的指针,第二个参数是我们想要绑定的事件类型,这里的 `PX_OBJECT_EVENT_EXECUTE` 就是按钮被点击时触发的, 第三个则是用户指针, 它会被传递到回调函数中, 如果你用不到, 你可以直接填 `PX_NULL`。 +最后让我们来到 `PX_ObjectRegisterEvent` 函数,这个函数用于将事件与 C 语言函数绑定在一起,第一个参数是我们之前创建好的按钮组件的指针,第二个参数是我们想要绑定的事件类型,这里的 `PX_OBJECT_EVENT_EXECUTE` 就是按钮被点击时触发的,第三个则是用户指针,它会被传递到回调函数中,如果你用不到,你可以直接填 `PX_NULL`。 ## 10. 小例子,用 PainterEngine 实现一个电子相册 -现在,让我们用一个小例子来开启 PainterEngine 组件化开发的第一步。在本例程中, 我将使用按钮和图片框组件, 开发一个电子相册功能。本文中的美术资源, 你可以在 `documents/logo` 中找到。 +现在,让我们用一个小例子来开启 PainterEngine 组件化开发的第一步。在本例程中,我将使用按钮和图片框组件,开发一个电子相册功能。本文中的美术资源,你可以在 `documents/logo` 中找到。 ```c #include "PainterEngine.h" PX_Object* Previous, * Next, * Image; -px_texture my_texture[10];//存放图片的数组 -px_int index = 0;//当前图片的索引 +px_texture my_texture[10]; // 存放图片的数组 +px_int index = 0; // 当前图片的索引 PX_OBJECT_EVENT_FUNCTION(OnButtonPreClick) { @@ -685,7 +686,7 @@ PX_OBJECT_EVENT_FUNCTION(OnButtonPreClick) { index = 9; } - PX_Object_ImageSetTexture(Image, &my_texture[index]);//设置图片 + PX_Object_ImageSetTexture(Image, &my_texture[index]); // 设置图片 } PX_OBJECT_EVENT_FUNCTION(OnButtonNextClick) @@ -701,54 +702,54 @@ PX_OBJECT_EVENT_FUNCTION(OnButtonNextClick) int main() { px_int i; - PainterEngine_Initialize(512, 560);//初始化 + PainterEngine_Initialize(512, 560); // 初始化 for(i=0;i<10;i++) { px_char path[256]; PX_sprintf1(path,256, "assets/%1.png", PX_STRINGFORMAT_INT(i+1)); - if(!PX_LoadTextureFromFile(mp_static, &my_texture[i],path))//加载图片 + if(!PX_LoadTextureFromFile(mp_static, &my_texture[i],path)) // 加载图片 { - //加载失败 + // 加载失败 printf("加载失败"); return 0; } } - PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20);//加载字体 - Image = PX_Object_ImageCreate(mp, root, 0, 0, 512, 512, 0);//创建图片对象 - Previous= PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "上一张",PainterEngine_GetFontModule());//创建按钮对象 - Next = PX_Object_PushButtonCreate(mp, root, 256, 512, 256, 48, "下一张", PainterEngine_GetFontModule());//创建按钮对象 - PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL);//注册按钮事件 - PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL);//注册按钮事件 + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); // 加载字体 + Image = PX_Object_ImageCreate(mp, root, 0, 0, 512, 512, 0); // 创建图片对象 + Previous= PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "上一张",PainterEngine_GetFontModule()); // 创建按钮对象 + Next = PX_Object_PushButtonCreate(mp, root, 256, 512, 256, 48, "下一张", PainterEngine_GetFontModule()); // 创建按钮对象 + PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL); // 注册按钮事件 + PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL); // 注册按钮事件 return 1; } ``` -在上述代码中 `OnButtonPreClick` 和 `OnButtonNextClick` 分别是上一张和下一张按钮的回调函数, 我们使用 `PX_Object_ImageSetTexture` 函数, 对图片框进行切换。 +在上述代码中 `OnButtonPreClick` 和 `OnButtonNextClick` 分别是上一张和下一张按钮的回调函数,我们使用 `PX_Object_ImageSetTexture` 函数,对图片框进行切换。 -而在 `main` 函数中, 我们先加载了 ttf 字体, 然后用 `PX_Object_ImageCreate` 创建了一个图片组件, 之后我们创建了 2 个按钮, 并用 `PX_ObjectRegisterEvent` 绑定了事件回调函数。最后, 看看运行结果: +而在 `main` 函数中,我们先加载了 ttf 字体,然后用 `PX_Object_ImageCreate` 创建了一个图片组件,之后我们创建了 2 个按钮,并用 `PX_ObjectRegisterEvent` 绑定了事件回调函数。最后,看看运行结果: ![](assets/img/10.1.gif) ## 11. 更多常用的 PainterEngine 组件 -你可以在 `PainterEngine/kernel` 的文件中, 找到 PainterEngine 的内置组件, 所有的组件名称都是以 `PX_Object_XXXXX` 开头的, 在这里, 我为你列举一些常用的组件及示范代码: +你可以在 `PainterEngine/kernel` 的文件中,找到 PainterEngine 的内置组件,所有的组件名称都是以 `PX_Object_XXXXX` 开头的,在这里,我为你列举一些常用的组件及示范代码: -* 文本框: +* 文本框: ```c #include "PainterEngine.h" PX_OBJECT_EVENT_FUNCTION(PX_Object_EditOnTextChanged) { - //文本改变后后这里会被执行 + // 文本改变后后这里会被执行 } int main() { PX_Object* pObject; PainterEngine_Initialize(600, 400); - //创建文本框 + // 创建文本框 pObject=PX_Object_EditCreate(mp,root,200,180,200,40,0); - //注册编辑框文本改变事件 + // 注册编辑框文本改变事件 PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_VALUECHANGED, PX_Object_EditOnTextChanged,PX_NULL); return 0; } @@ -766,21 +767,21 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_OnMyListItemRender) px_float objx,objy,objWidth,objHeight; PX_Object_ListItem *pItem=PX_Object_GetListItem(pObject); PX_OBJECT_INHERIT_CODE(pObject,objx, objy, objWidth, objHeight); - //绘制出其文本 + // 绘制出其文本 PX_FontModuleDrawText(psurface, 0, (px_int)objx + 3, (px_int)objy + 3, PX_ALIGN_LEFTTOP, (const px_char *)pItem->pdata, PX_COLOR_WHITE); } PX_OBJECT_LIST_ITEM_CREATE_FUNCTION(PX_Object_OnMyListItemCreate) { - //绑定ListItem的渲染函数 + // 绑定ListItem的渲染函数 ItemObject->Func_ObjectRender[0]=PX_Object_OnMyListItemRender; return PX_TRUE; } PX_OBJECT_EVENT_FUNCTION(PX_Object_ListOnSelectChanged) { - //当选中项改变时 + // 当选中项改变时 return; } @@ -789,7 +790,7 @@ int main() PX_Object* pObject; PainterEngine_Initialize(600, 400); - //创建list + // 创建list pObject = PX_Object_ListCreate(mp,root,100,100,400,200,24,PX_Object_OnMyListItemCreate,0); PX_Object_ListAdd(pObject, "Item1"); PX_Object_ListAdd(pObject, "Item2"); @@ -810,7 +811,7 @@ int main() PX_OBJECT_EVENT_FUNCTION(SliderChanged) { - //垂直滑动条值改变后执行这里的代码 + // 垂直滑动条值改变后执行这里的代码 return; } @@ -818,9 +819,9 @@ int main() { PX_Object* pObject; PainterEngine_Initialize(600, 400); - //水平滑动条 + // 水平滑动条 PX_Object_SliderBarCreate(mp, root, 200, 50, 200,24,PX_OBJECT_SLIDERBAR_TYPE_HORIZONTAL,PX_OBJECT_SLIDERBAR_STYLE_BOX); - //垂直滑动条 + // 垂直滑动条 pObject=PX_Object_SliderBarCreate(mp, root, 200, 100, 24, 200, PX_OBJECT_SLIDERBAR_TYPE_VERTICAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, SliderChanged, 0); return 0; @@ -853,7 +854,7 @@ int main() ```c #include "PainterEngine.h" -//必须是生存域内有效可访问的数据,这里定义为全局变量 +// 必须是生存域内有效可访问的数据,这里定义为全局变量 px_double data_x[100]; px_double data_y[100]; @@ -865,7 +866,7 @@ int main() px_int i; PainterEngine_Initialize(600, 600); - //初始化一个测试数据 + // 初始化一个测试数据 for (i = 0; i < 100; i++) { data_x[i] = i; @@ -874,26 +875,26 @@ int main() pObject = PX_Object_OscilloscopeCreate(mp, root, 0, 0, 600, 600, 0); - //设置水平坐标最小值最大值 + // 设置水平坐标最小值最大值 PX_Object_OscilloscopeSetHorizontalMin(pObject, 0); PX_Object_OscilloscopeSetHorizontalMax(pObject, 100); - //设置垂直坐标(左边)最小值最大值0-100 + // 设置垂直坐标(左边)最小值最大值0-100 PX_Object_OscilloscopeSetLeftVerticalMin(pObject, 0); PX_Object_OscilloscopeSetLeftVerticalMax(pObject, 100); - //数据类型 - data.Color=PX_COLOR(255,192,255,128);//数据颜色 + // 数据类型 + data.Color=PX_COLOR(255,192,255,128); // 数据颜色 data.ID = 0; - data.linewidth = 3;//数据线宽 - data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT;//数据映射到左边垂直坐标 - data.MapHorizontalArray = data_x;//数据水平坐标 - data.MapVerticalArray = data_y;//数据垂直坐标 - data.Size = 100;//数据大小 - data.Visibled = PX_TRUE;//数据可见 - data.Normalization = 1;//数据归一化系数为1 + data.linewidth = 3; // 数据线宽 + data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT; // 数据映射到左边垂直坐标 + data.MapHorizontalArray = data_x; // 数据水平坐标 + data.MapVerticalArray = data_y; // 数据垂直坐标 + data.Size = 100; // 数据大小 + data.Visibled = PX_TRUE; // 数据可见 + data.Normalization = 1; // 数据归一化系数为1 - //添加数据 + // 添加数据 PX_Object_OscilloscopeAddData(pObject, data); return 0; } @@ -901,7 +902,7 @@ int main() ![](assets/img/11.5.gif) -因为实在太多了, 我无法为你列举所有的组件, 如果你希望知道某个组件的具体用法和某个组件到底是做什么的, 你可以访问 PainterEngine 的 [组件市场](https://market.painterengine.com/), 在那里你可以找到 PainterEngine 内置组件和三方组件的说明和示例代码。 +因为实在太多了,我无法为你列举所有的组件,如果你希望知道某个组件的具体用法和某个组件到底是做什么的,你可以访问 PainterEngine 的 [组件市场](https://market.painterengine.com/),在那里你可以找到 PainterEngine 内置组件和三方组件的说明和示例代码。 ![](assets/img/11.6.png) @@ -909,11 +910,11 @@ int main() PainterEngine 鼓励组件式的开发架构。也就是说,不论是游戏还是 GUI 交互程序,甚至是程序功能,我们都可以用组件的形式去开发它。 -组件式开发有点类似于 C++中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数, 你可以参考 [前面的对象传递机制](#8painterengine-对象传递机制) 这一章节。 +组件式开发有点类似于 C++中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数,你可以参考 [前面的对象传递机制](#8painterengine-对象传递机制) 这一章节。 为了演示这一点,让我们来实现一个“可控拖动旋转图片组件”,即我们可以用鼠标拖动图片在界面的位置,并用鼠标中键来旋转它。 -为了实现这一个功能, 让我们一步一步完成这个步骤。首先, 为了创建一个组件, 我们需要一个结构体来描述我们的组件。我们需要绘制图片, 所以我们需要一个 `px_texture` 类型。同时, 我们还需要旋转图片, 因此它还有一个 `rotation` 用于描述旋转的角度: +为了实现这一个功能,让我们一步一步完成这个步骤。首先,为了创建一个组件,我们需要一个结构体来描述我们的组件。我们需要绘制图片,所以我们需要一个 `px_texture` 类型。同时,我们还需要旋转图片,因此它还有一个 `rotation` 用于描述旋转的角度: ```c #include "PainterEngine.h" @@ -930,7 +931,7 @@ px_int main() } ``` -之后, 我们需要定义我们的 `Create`、`Update`、`Render` 和 `Free` 函数, 其中 `Update`、`Render`、`Free` 有对应的格式, 它们都有一个宏来简化我们的定义过程: +之后,我们需要定义我们的 `Create`、`Update`、`Render` 和 `Free` 函数,其中 `Update`、`Render`、`Free` 有对应的格式,它们都有一个宏来简化我们的定义过程: ```c #define PX_OBJECT_RENDER_FUNCTION(name) px_void name(px_surface *psurface,PX_Object *pObject,px_int idesc,px_dword elapsed) @@ -938,7 +939,7 @@ px_int main() #define PX_OBJECT_FREE_FUNCTION(name) px_void name(PX_Object *pObject,px_int idesc) ``` -那么, 在主函数中, 我们就可以这样定义我们的这几个函数: +那么,在主函数中,我们就可以这样定义我们的这几个函数: ```c #include "PainterEngine.h" @@ -971,7 +972,7 @@ px_int main() } ``` -其中, 因为我们不需要更新一些物理信息, 所以 `MyObjectUpdate` 函数中我们可以什么都不写, 在 `MyObjectRender` 中我们只需要把图片绘制出来就可以了, 这里我们先使用 `PX_ObjectGetDesc` 函数获得我们定义好的结构体指针, 它的第一个参数是结构体类型, 第二个参数则是函数传递进来的 `pObject` 指针, 然后我们只需要用 `PX_TextureRenderEx` 函数把图片绘制出来就可以了。 +其中,因为我们不需要更新一些物理信息,所以 `MyObjectUpdate` 函数中我们可以什么都不写,在 `MyObjectRender` 中我们只需要把图片绘制出来就可以了,这里我们先使用 `PX_ObjectGetDesc` 函数获得我们定义好的结构体指针,它的第一个参数是结构体类型,第二个参数则是函数传递进来的 `pObject` 指针,然后我们只需要用 `PX_TextureRenderEx` 函数把图片绘制出来就可以了。 多提一句,`PX_TextureRenderEx` 函数用于在指定的表面上渲染纹理,并提供了对齐、混合、缩放和旋转等扩展选项。其中: * `psurface`:指向要渲染纹理的表面的指针。 @@ -983,26 +984,26 @@ px_int main() * `scale`:纹理的缩放因子(1.0 表示不缩放)。 * `Angle`:纹理的旋转角度,以度为单位。 -最后, 是时候编写创建新对象的函数了, 这里我们需要用到 `PX_ObjectCreateEx` 函数, `PX_ObjectCreateEx` 函数用于创建一个扩展对象,并初始化其属性和回调函数。它的参数说明如下: +最后,是时候编写创建新对象的函数了,这里我们需要用到 `PX_ObjectCreateEx` 函数,`PX_ObjectCreateEx` 函数用于创建一个扩展对象,并初始化其属性和回调函数。它的参数说明如下: * `mp`:指向内存池的指针,用于分配对象所需的内存。 * `Parent`:指向父对象的指针,如果没有父对象则为 `NULL`。 * `x`:对象在 x 轴上的初始位置。 * `y`:对象在 y 轴上的初始位置。 -* `z`:对象在 z 轴上的初始位置, z 坐标会影响其渲染的先后顺序。 +* `z`:对象在 z 轴上的初始位置,z 坐标会影响其渲染的先后顺序。 * `Width`:对象的宽度。 * `Height`:对象的高度。 -* `Lenght`:对象的长度,2D 对象, 一般可以是 0。 +* `Lenght`:对象的长度,2D 对象,一般可以是 0。 * `type`:对象的类型。 * `Func_ObjectUpdate`:指向对象更新函数的指针。 * `Func_ObjectRender`:指向对象渲染函数的指针。 * `Func_ObjectFree`:指向对象释放函数的指针。 -* `desc`:指向对象描述数据的指针。你可以设置为 0, 创建时会把这个对象类型的数据填充为 0。 -* `size`:描述数据的大小, 就是你定义的对象结构体类型的大小,创建对象函数会在内存池申请一段内存空间,并用于存储你的对象结构体。 +* `desc`:指向对象描述数据的指针。你可以设置为 0,创建时会把这个对象类型的数据填充为 0。 +* `size`:描述数据的大小,就是你定义的对象结构体类型的大小,创建对象函数会在内存池申请一段内存空间,并用于存储你的对象结构体。 -在创建好一个空对象后, 我们使用 `PX_ObjectGetDescIndex` 将对象中的对象结构体指针取出来, 这是一个三参数的函数, 第一个参数是对象结构体类型, 第二个参数则是 `PX_Object *` 指针类型, 因为一个 `PX_Object` 可以将多个对象结构体组合在一起, 这个组合结构体我们将在之后的教程中会进一步描述, 但现在我们只需要知道, 调用 `PX_ObjectCreateEx` 函数后, 其第一个存储的对象结构体索引是 0 就可以了。 +在创建好一个空对象后,我们使用 `PX_ObjectGetDescIndex` 将对象中的对象结构体指针取出来,这是一个三参数的函数,第一个参数是对象结构体类型,第二个参数则是 `PX_Object *` 指针类型,因为一个 `PX_Object` 可以将多个对象结构体组合在一起,这个组合结构体我们将在之后的教程中会进一步描述,但现在我们只需要知道,调用 `PX_ObjectCreateEx` 函数后,其第一个存储的对象结构体索引是 0 就可以了。 -取出结构体指针后, 我们对其进行一系列初始化, 比如加载图片和初始化旋转角度, 最后在 `main` 函数中我们创建这个对象: +取出结构体指针后,我们对其进行一系列初始化,比如加载图片和初始化旋转角度,最后在 `main` 函数中我们创建这个对象: ```c #include "PainterEngine.h" @@ -1019,23 +1020,23 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation);//渲染图片 + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation); // 渲染图片 } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image);//释放图片 + PX_TextureFree(&pMyObject->image); // 释放图片 } PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject));//创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0);//取得自定义对象数据 + PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0); // 取得自定义对象数据 pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png"))//加载图片 + if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png")) // 加载图片 { - PX_ObjectDelete(pObject);//加载失败则删除对象 + PX_ObjectDelete(pObject); // 加载失败则删除对象 return PX_NULL; } return pObject; @@ -1044,16 +1045,16 @@ PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_flo px_int main() { PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240);//创建一个自定义对象 + PX_Object_MyObjectCreate(mp,root,400,240); // 创建一个自定义对象 return PX_TRUE; } ``` -那么它的运行效果是这样的: +那么它的运行效果是这样的: ![](assets/img/12.1.png) -但现在还没有结束, 我们怎么让我们的组件, 响应鼠标中键实现旋转呢?还记得我们之前在 [PushButton](#8painterengine-对象传递机制) 中的对象传递机制么?现在, 我们也要让我们的组件响应鼠标中键的信息, 因此我们给它注册一个 `PX_OBJECT_EVENT_CURSORWHEEL` 事件的回调函数, 代码如下: +但现在还没有结束,我们怎么让我们的组件,响应鼠标中键实现旋转呢?还记得我们之前在 [PushButton](#8painterengine-对象传递机制) 中的对象传递机制么?现在,我们也要让我们的组件响应鼠标中键的信息,因此我们给它注册一个 `PX_OBJECT_EVENT_CURSORWHEEL` 事件的回调函数,代码如下: ```c #include "PainterEngine.h" @@ -1070,62 +1071,62 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation);//渲染图片 + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation); // 渲染图片 } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image);//释放图片 + PX_TextureFree(&pMyObject->image); // 释放图片 } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) { PX_Object_MyObject *pMyObject=PX_ObjectGetDescIndex(PX_Object_MyObject,pObject,0); - if(PX_ObjectIsCursorInRegion(pObject,e))//Object是鼠标位置是否选中当前组件,e是事件 + if(PX_ObjectIsCursorInRegion(pObject,e)) // Object是鼠标位置是否选中当前组件,e是事件 pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; } PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject));//创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0);//取得自定义对象数据 + PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0); // 取得自定义对象数据 pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png"))//加载图片 + if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png")) // 加载图片 { - PX_ObjectDelete(pObject);//加载失败则删除对象 + PX_ObjectDelete(pObject); // 加载失败则删除对象 return PX_NULL; } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0);//注册鼠标滚轮事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0); // 注册鼠标滚轮事件 return pObject; } px_int main() { PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240);//创建一个自定义对象 + PX_Object_MyObjectCreate(mp,root,400,240); // 创建一个自定义对象 return PX_TRUE; } ``` -运行结果如下: +运行结果如下: ![](assets/img/12.2.gif) -如果你觉得旋转图的质量不好, 有很多锯齿, 这是因为 `PX_TextureRenderEx` 旋转时是对原图直接采样的。如果你想要高质量的旋转图, 你可以用 `PX_TextureRenderRotation` 函数来替换原函数: +如果你觉得旋转图的质量不好,有很多锯齿,这是因为 `PX_TextureRenderEx` 旋转时是对原图直接采样的。如果你想要高质量的旋转图,你可以用 `PX_TextureRenderRotation` 函数来替换原函数: ```c PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, pMyObject->rotation);//渲染图片 + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, pMyObject->rotation); // 渲染图片 } ``` ![](assets/img/12.3.gif) -那么, 我们如何实现拖动效果呢?想要做到拖动效果, 我们需要在对象结构体中, 新增 `float` 类型的变量 `x`, `y`, 用来记录当鼠标选中图片时的位置, 同时我们加入了 `bool` 类型的变量 `bselect`, 表示当前的图标是否被选中。当鼠标点击我们的图标以后, 我们就可以监听 `PX_OBJECT_EVENT_CURSORDRAG` 事件, 这是鼠标在屏幕上拖动时会产生的事件, 我们通过坐标的偏移, 移动我们的组件。最后, 不论鼠标非拖动时的移动或鼠标左键抬起, 都会取消我们组件的选中状态, 在对应处理函数中取消选中状态即可。 +那么,我们如何实现拖动效果呢?想要做到拖动效果,我们需要在对象结构体中,新增 `float` 类型的变量 `x`、`y`,用来记录当鼠标选中图片时的位置,同时我们加入了 `bool` 类型的变量 `bselect`,表示当前的图标是否被选中。当鼠标点击我们的图标以后,我们就可以监听 `PX_OBJECT_EVENT_CURSORDRAG` 事件,这是鼠标在屏幕上拖动时会产生的事件,我们通过坐标的偏移,移动我们的组件。最后,不论鼠标非拖动时的移动或鼠标左键抬起,都会取消我们组件的选中状态,在对应处理函数中取消选中状态即可。 ```c #include "PainterEngine.h" @@ -1144,26 +1145,26 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, (px_int)pMyObject->rotation);//渲染图片 + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, (px_int)pMyObject->rotation); // 渲染图片 } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image);//释放图片 + PX_TextureFree(&pMyObject->image); // 释放图片 } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) { PX_Object_MyObject *pMyObject=PX_ObjectGetDescIndex(PX_Object_MyObject,pObject,0); - if(PX_ObjectIsCursorInRegionAlign(pObject,e,PX_ALIGN_CENTER))//Object是鼠标位置是否选中当前组件,e是事件 + if(PX_ObjectIsCursorInRegionAlign(pObject,e,PX_ALIGN_CENTER)) // Object是鼠标位置是否选中当前组件,e是事件 pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDown) { PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER))//Object是鼠标位置是否选中当前组件,e是事件 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Object是鼠标位置是否选中当前组件,e是事件 { pMyObject->bselect = PX_TRUE; pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); @@ -1191,38 +1192,38 @@ PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDrag) PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject));//创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0);//取得自定义对象数据 + PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0); // 取得自定义对象数据 pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png"))//加载图片 + if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png")) // 加载图片 { - PX_ObjectDelete(pObject);//加载失败则删除对象 + PX_ObjectDelete(pObject); // 加载失败则删除对象 return PX_NULL; } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0);//注册鼠标滚轮事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDRAG,MyObjectOnCursorDrag,0);//注册鼠标拖拽事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,MyObjectOnCursorDown,0);//注册鼠标按下事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORUP,MyObjectOnCursorRelease,0);//注册鼠标释放事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0);//注册鼠标释放事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0); // 注册鼠标滚轮事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDRAG,MyObjectOnCursorDrag,0); // 注册鼠标拖拽事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,MyObjectOnCursorDown,0); // 注册鼠标按下事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORUP,MyObjectOnCursorRelease,0); // 注册鼠标释放事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0); // 注册鼠标释放事件 return pObject; } px_int main() { PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240);//创建一个自定义对象 + PX_Object_MyObjectCreate(mp,root,400,240); // 创建一个自定义对象 return PX_TRUE; } ``` ![](assets/img/12.4.gif) -当然, 你可以调用 `PX_Object_MyObjectCreate` 多次, 创建多个组件对象, 它们的功能都是一样的: +当然,你可以调用 `PX_Object_MyObjectCreate` 多次,创建多个组件对象,它们的功能都是一样的: ![](assets/img/12.5.gif) ## 13. 组合式组件设计 -PainterEngine 的组件允许同时拥有多种组件类型, 例如, 当我们将一个图片框组件和一个按钮进行组合, 我们就可以得到一个组合式组件图片按钮。 +PainterEngine 的组件允许同时拥有多种组件类型,例如,当我们将一个图片框组件和一个按钮进行组合,我们就可以得到一个组合式组件图片按钮。 参考如下代码: @@ -1233,8 +1234,8 @@ PX_Object* image; PX_OBJECT_EVENT_FUNCTION(ButtonEvent) { - PX_Object_Image *pImage=PX_Object_GetImage(pObject);//取得Image对象数据 - PX_Object_Button *pButton=PX_Object_GetButton(pObject);//取得Button对象数据 + PX_Object_Image *pImage=PX_Object_GetImage(pObject); // 取得Image对象数据 + PX_Object_Button *pButton=PX_Object_GetButton(pObject); // 取得Button对象数据 if (pImage->pTexture==&tex1) { PX_Object_ImageSetTexture(pObject,&tex2); @@ -1248,30 +1249,30 @@ PX_OBJECT_EVENT_FUNCTION(ButtonEvent) px_int main() { PainterEngine_Initialize(800, 480); - if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0;//加载纹理1 - if(!PX_LoadTextureFromFile(mp_static,&tex2,"assets/2.png")) return 0;//加载纹理2 - image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1);//创建Image对象 - PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255));//将一个Button对象类型组合到Image对象上 - PX_ObjectRegisterEvent(image,PX_OBJECT_EVENT_EXECUTE,ButtonEvent,0);//这里实际上是注册Button对象的事件 + if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0; // 加载纹理1 + if(!PX_LoadTextureFromFile(mp_static,&tex2,"assets/2.png")) return 0; // 加载纹理2 + image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1); // 创建Image对象 + PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255)); // 将一个Button对象类型组合到Image对象上 + PX_ObjectRegisterEvent(image,PX_OBJECT_EVENT_EXECUTE,ButtonEvent,0); // 这里实际上是注册Button对象的事件 return 1; } ``` -我们创建了一个 Image 图像框类型, 然后将一个 Button 对象类型组合上去, 这样我们就获得了一个图片按钮: +我们创建了一个 Image 图像框类型,然后将一个 Button 对象类型组合上去,这样我们就获得了一个图片按钮: ![](assets/img/13.1.gif) -那么, 我们如何设计我们自己的可组合对象呢?回到我们的第十二章节, 现在, 我们就将 "可拖拽" 这个功能设计成一个组合式组件。 +那么,我们如何设计我们自己的可组合对象呢?回到我们的第十二章节,现在,我们就将 "可拖拽" 这个功能设计成一个组合式组件。 -首先,仍然是定义一个组件对象结构体,为实现拖拽功能,我们需要鼠标按下时的 x, y 坐标, 同时需要一个 bool 类型记录是否是选中状态, 然后我们需要注册 `CURSOR` 事件, 这些事件在上一章节我们已经写过了, 最后, 我们用 `PX_ObjectCreateDesc` 函数创建一个对象结构体,并将它 Attach 到我们的对象上。 +首先,仍然是定义一个组件对象结构体,为实现拖拽功能,我们需要鼠标按下时的 x, y 坐标,同时需要一个 bool 类型记录是否是选中状态,然后我们需要注册 `CURSOR` 事件,这些事件在上一章节我们已经写过了,最后,我们用 `PX_ObjectCreateDesc` 函数创建一个对象结构体,并将它 Attach 到我们的对象上。 -`PX_ObjectCreateDesc` 是一个对象结构体创建函数, 它的定义原型如下: +`PX_ObjectCreateDesc` 是一个对象结构体创建函数,它的定义原型如下: ```c px_void* PX_ObjectCreateDesc(PX_Object* pObject, px_int idesc, px_int type, Function_ObjectUpdate Func_ObjectUpdate, Function_ObjectRender Func_ObjectRender, Function_ObjectFree Func_ObjectFree, px_void* pDesc, px_int descSize) ``` -第一个参数是需要 Attach 的对象, 第二个参数是 Attach 到的对象索引。还记得我们之前提到的对象数据索引么, 使用 `PX_ObjectCreateEx` 默认使用的是索引 0, 因此, 如果我们要附加到一个对象上, 我们应该选 1, 当然如果 1 也被占用了, 它就是 2, 以此类推。第三个参数是对象类型, 我们使用 `PX_ObjectGetDescByType` 时, 可以通过对象类型取出对应的指针, 然后就是我们熟悉的 `Update`、`Render`、`Free` 三件套了, 最后一个参数给出其结构体描述和结构体大小。请参阅下面的代码: +第一个参数是需要 Attach 的对象,第二个参数是 Attach 到的对象索引。还记得我们之前提到的对象数据索引么,使用 `PX_ObjectCreateEx` 默认使用的是索引 0,因此,如果我们要附加到一个对象上,我们应该选 1,当然如果 1 也被占用了,它就是 2,以此类推。第三个参数是对象类型,我们使用 `PX_ObjectGetDescByType` 时,可以通过对象类型取出对应的指针,然后就是我们熟悉的 `Update`、`Render`、`Free` 三件套了,最后一个参数给出其结构体描述和结构体大小。请参阅下面的代码: ```c #include "PainterEngine.h" @@ -1335,21 +1336,21 @@ PX_Object* image; px_int main() { PainterEngine_Initialize(800, 480); - if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0;//加载纹理1 - image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1);//创建Image对象 - PX_Object_DragAttachObject(image, 1);//将一个Drag对象类型组合到Image对象上 + if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0; // 加载纹理1 + image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1); // 创建Image对象 + PX_Object_DragAttachObject(image, 1); // 将一个Drag对象类型组合到Image对象上 return 1; } ``` -运行结果如下: +运行结果如下: ![](assets/img/13.2.gif) ## 14. 粒子系统 -PainterEngine 提供了一个粒子系统实现, 下面是一个粒子系统的示范程序: +PainterEngine 提供了一个粒子系统实现,下面是一个粒子系统的示范程序: ```c #include "PainterEngine.h" @@ -1372,7 +1373,7 @@ px_int main() ![](assets/img/14.1.gif) -这是一个用组件包装起来的粒子系统实现, 另外一种是提供了更加详细的粒子系统参数配置: +这是一个用组件包装起来的粒子系统实现,另外一种是提供了更加详细的粒子系统参数配置: ```c #include "PainterEngine.h" @@ -1513,22 +1514,22 @@ PainterEngine 内置了对 wav 及 mp3 格式音乐的原生支持,使用 Pain ```c #include "PainterEngine.h" -PX_SoundData sounddata;//定义音乐格式 +PX_SoundData sounddata; // 定义音乐格式 int main() { PX_Object* pObject; PainterEngine_Initialize(600, 400); - PainterEngine_InitializeAudio();//初始化混音器及音乐设备 - if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav"))return PX_FALSE;//加载音乐,支持wav及mp3格式 - PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE));//播放音乐 - pObject = PX_Object_SoundViewCreate(mp,root,0,0,600,400,soundplay);//音乐频谱可视化组件,可选 + PainterEngine_InitializeAudio(); // 初始化混音器及音乐设备 + if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav"))return PX_FALSE; // 加载音乐,支持wav及mp3格式 + PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE)); // 播放音乐 + pObject = PX_Object_SoundViewCreate(mp,root,0,0,600,400,soundplay); // 音乐频谱可视化组件,可选 return 0; } ``` ![](assets/img/15.1.gif) -其中, `PX_LoadSoundFromFile` 函数从文件中加载音乐, 并解码成 `sounddata` 类型。`PX_SoundCreate` 可以用 `sounddata` 创建一个播放实例, 第二个参数表示这个实例是否循环播放, 最后使用 `PX_SoundPlayAdd` 将播放实例送入混音器中, 即可完成音乐播放。 +其中,`PX_LoadSoundFromFile` 函数从文件中加载音乐,并解码成 `sounddata` 类型。`PX_SoundCreate` 可以用 `sounddata` 创建一个播放实例,第二个参数表示这个实例是否循环播放,最后使用 `PX_SoundPlayAdd` 将播放实例送入混音器中,即可完成音乐播放。 ## 16. PainterEngine Live2D 动画系统 @@ -1550,12 +1551,12 @@ int main() PX_IO_Data iodata; PainterEngine_Initialize(600, 600); - //加载模型数据 + // 加载模型数据 iodata = PX_LoadFileToIOData("assets/release.live"); if (iodata.size == 0)return PX_FALSE; PX_LiveFrameworkImport(mp_static, &liveframework, iodata.buffer, iodata.size); PX_FreeIOData(&iodata); - //创建Live2D对象 + // 创建Live2D对象 pObject = PX_Object_Live2DCreate(mp,root,300,300,&liveframework); PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, onClick, PX_NULL); @@ -1572,13 +1573,13 @@ int main() PX_Object* PX_Object_Live2DCreate(px_memorypool* mp, PX_Object* Parent, px_int x, px_int y, PX_LiveFramework *pLiveFramework); ``` -- **描述**: 创建一个 Live2D 模型预览器对象,用于在图形界面中显示和交互 Live2D 模型。 -- **参数**: - - `mp`: 内存池指针,用于分配内存。 - - `Parent`: 父对象,Live2D 模型预览器对象将作为其子对象。 - - `x`, `y`: Live2D 模型预览器对象的位置坐标。 - - `pLiveFramework`: Live2D 模型框架的指针,包括模型数据、纹理等信息。 -- **返回值**: 创建的 Live2D 模型预览器对象的指针。 +- **描述**:创建一个 Live2D 模型预览器对象,用于在图形界面中显示和交互 Live2D 模型。 +- **参数**: + - `mp`:内存池指针,用于分配内存。 + - `Parent`:父对象,Live2D 模型预览器对象将作为其子对象。 + - `x`, `y`:Live2D 模型预览器对象的位置坐标。 + - `pLiveFramework`:Live2D 模型框架的指针,包括模型数据、纹理等信息。 +- **返回值**:创建的 Live2D 模型预览器对象的指针。 `PX_Object_Live2DPlayAnimation` @@ -1586,11 +1587,11 @@ PX_Object* PX_Object_Live2DCreate(px_memorypool* mp, PX_Object* Parent, px_int x px_void PX_Object_Live2DPlayAnimation(PX_Object *pObject, px_char *name); ``` -- **描述**: 播放指定名称的 Live2D 模型动画。 -- **参数**: - - `pObject`: Live2D 模型预览器对象的指针。 - - `name`: 动画名称。 -- **返回值**: 无。 +- **描述**:播放指定名称的 Live2D 模型动画。 +- **参数**: + - `pObject`:Live2D 模型预览器对象的指针。 + - `name`:动画名称。 +- **返回值**:无。 `PX_Object_Live2DPlayAnimationRandom` @@ -1598,10 +1599,10 @@ px_void PX_Object_Live2DPlayAnimation(PX_Object *pObject, px_char *name); px_void PX_Object_Live2DPlayAnimationRandom(PX_Object* pObject); ``` -- **描述**: 随机播放 Live2D 模型的动画。 -- **参数**: - - `pObject`: Live2D 模型预览器对象的指针。 -- **返回值**: 无。 +- **描述**:随机播放 Live2D 模型的动画。 +- **参数**: + - `pObject`:Live2D 模型预览器对象的指针。 +- **返回值**:无。 `PX_Object_Live2DPlayAnimationIndex` @@ -1609,11 +1610,11 @@ px_void PX_Object_Live2DPlayAnimationRandom(PX_Object* pObject); px_void PX_Object_Live2DPlayAnimationIndex(PX_Object* pObject, px_int index); ``` -- **描述**: 播放 Live2D 模型的指定索引处的动画。 -- **参数**: - - `pObject`: Live2D 模型预览器对象的指针。 - - `index`: 动画的索引。 -- **返回值**: 无。 +- **描述**:播放 Live2D 模型的指定索引处的动画。 +- **参数**: + - `pObject`:Live2D 模型预览器对象的指针。 + - `index`:动画的索引。 +- **返回值**:无。 这些函数用于创建、配置和管理 Live2D 模型预览器对象,以在图形用户界面中显示和交互 Live2D 模型。可以使用这些函数播放 Live2D 模型的动画,包括指定名称、随机选择和指定索引处的动画。 @@ -1623,14 +1624,14 @@ px_void PX_Object_Live2DPlayAnimationIndex(PX_Object* pObject, px_int index); PainterEngine 内置了一个平台无关的脚本引擎系统,集成了编译,运行,调试等功能,你可以很轻松地在脚本之上,实现并行调度功能。PainterEngine Script 的设计,最大程度和 C 语言保持一致性,并对一些类型进行的拓展和简化。 -例如在脚本中,支持 `int`, `float`, `string`, `memory` 四种类型, `int` 类型是一个 32 位的有符号整数, `float` 是一个浮点数类型, 这个和 C 语言的类型保持了一致。`string` 类型类似于 C++的 `string`, 它允许直接用 `+` 法运算符进行字符串拼接, 使用 `strlen` 来获取其字符串长度, 而 `memory` 是一个二进制数据存储类型, 同样支持 `+` 运算进行拼接。 +例如在脚本中,支持 `int`、`float`、`string`、`memory` 四种类型,`int` 类型是一个 32 位的有符号整数,`float` 是一个浮点数类型,这个和 C 语言的类型保持了一致。`string` 类型类似于 C++的 `string`,它允许直接用 `+` 法运算符进行字符串拼接,使用 `strlen` 来获取其字符串长度,而 `memory` 是一个二进制数据存储类型,同样支持 `+` 运算进行拼接。 -在脚本中如果需要调用 C 语言函数,应该使用 `PX_VM_HOST_FUNCTION` 宏进行定义声明。和组件回调函数一样, `PX_VM_HOST_FUNCTION` 的定义如下: +在脚本中如果需要调用 C 语言函数,应该使用 `PX_VM_HOST_FUNCTION` 宏进行定义声明。和组件回调函数一样,`PX_VM_HOST_FUNCTION` 的定义如下: ```c #define PX_VM_HOST_FUNCTION(name) px_bool name(PX_VM *Ins,px_void *userptr) ``` -在下面的内容中, 我将以一个简单的脚本实例作为范例, 为你演示如何使用 PainterEngine 的脚本引擎: +在下面的内容中,我将以一个简单的脚本实例作为范例,为你演示如何使用 PainterEngine 的脚本引擎: ```c const px_char shellcode[] = "\ @@ -1670,15 +1671,15 @@ PX_VM_HOST_FUNCTION(host_sleep) ``` -首先, `shellcode` 数组中存储着一个输出九九乘法表的程序, 其中需要调用两个 `host` 函数(脚本调用 C 语言函数称为 host call, 因此 host 函数实际就是专门提供给脚本调用的 C 语言函数), 一个是 `print` 函数, 一个是 `sleep` 函数。因此在下面, 我们定义了两个 `host` 函数, `PX_VM_HOSTPARAM` 用于取得脚本传递过来的参数。在这里, 我们需要判断传递过来的参数类型是否符合我们的调用规则, 像 `host_print` 函数, 作用是在 PainterEngine 中输出字符串, 而 `sleep` 函数, 则是用来延迟一段时间。 +首先,`shellcode` 数组中存储着一个输出九九乘法表的程序,其中需要调用两个 `host` 函数(脚本调用 C 语言函数称为 host call,因此 host 函数实际就是专门提供给脚本调用的 C 语言函数),一个是 `print` 函数,一个是 `sleep` 函数。因此在下面,我们定义了两个 `host` 函数,`PX_VM_HOSTPARAM` 用于取得脚本传递过来的参数。在这里,我们需要判断传递过来的参数类型是否符合我们的调用规则,像 `host_print` 函数,作用是在 PainterEngine 中输出字符串,而 `sleep` 函数,则是用来延迟一段时间。 -现在,PainterEngine Script 是一个编译型脚本, 我们需要将上面的代码编译成二进制形式, 然后将它送入虚拟机中运行, 观察以下代码: +现在,PainterEngine Script 是一个编译型脚本,我们需要将上面的代码编译成二进制形式,然后将它送入虚拟机中运行,观察以下代码: ```c PX_VM vm; PX_OBJECT_UPDATE_FUNCTION(VMUpdate) { - PX_VMRun(&vm, 0xffff, elapsed);//运行虚拟机 + PX_VMRun(&vm, 0xffff, elapsed); // 运行虚拟机 } px_int main() @@ -1687,26 +1688,26 @@ px_int main() px_memory bin; PainterEngine_Initialize(800, 600); PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler);//初始化编译器 - PX_CompilerAddSource(&compiler, shellcode);//编译器中添加代码 - PX_MemoryInitialize(mp, &bin);//初始化内存/用于存储编译后的结果 + PX_CompilerInitialize(mp, &compiler); // 初始化编译器 + PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 + PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 if (!PX_CompilerCompile(&compiler, &bin, 0, "main")) { - //编译失败 + // 编译失败 return 0; } - PX_CompilerFree(&compiler);//释放编译器 - PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize);//初始化虚拟机 - PX_VMRegisterHostFunction(&vm, "print", host_print,0);//注册主机函数print - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0);//注册主机函数sleep - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0);//开始运行虚拟机函数 - PX_ObjectSetUpdateFunction(root, VMUpdate, 0);//设置更新函数 + PX_CompilerFree(&compiler); // 释放编译器 + PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize); // 初始化虚拟机 + PX_VMRegisterHostFunction(&vm, "print", host_print,0); // 注册主机函数print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0); // 注册主机函数sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 + PX_ObjectSetUpdateFunction(root, VMUpdate, 0); // 设置更新函数 return 0; } ``` -首先我们用 `PX_Compiler` 编译我们的脚本, 然后我们注册我们的 host call, `PX_VMBeginThreadFunction` 的功能是 C 语言调用脚本语言中函数, 在这里我们调用脚本中的 `main` 开始运行我们的脚本函数, 最后我们将一个 `Update` 函数绑定到 root 节点, 以循环更新虚拟机, 来执行脚本。 +首先我们用 `PX_Compiler` 编译我们的脚本,然后我们注册我们的 host call,`PX_VMBeginThreadFunction` 的功能是 C 语言调用脚本语言中函数,在这里我们调用脚本中的 `main` 开始运行我们的脚本函数,最后我们将一个 `Update` 函数绑定到 root 节点,以循环更新虚拟机,来执行脚本。 最后,看看运行的结果。 @@ -1722,19 +1723,19 @@ px_int main() PainterEngine_Initialize(800, 480); PX_VMDebuggerMapInitialize(mp,&debugmap); PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler);//初始化编译器 - PX_CompilerAddSource(&compiler, shellcode);//编译器中添加代码 - PX_MemoryInitialize(mp, &bin);//初始化内存/用于存储编译后的结果 + PX_CompilerInitialize(mp, &compiler); // 初始化编译器 + PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 + PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 if (!PX_CompilerCompile(&compiler, &bin, &debugmap, "main")) { - //编译失败 + // 编译失败 return 0; } - PX_CompilerFree(&compiler);//释放编译器 - PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize);//初始化虚拟机 - PX_VMRegisterHostFunction(&vm, "print", host_print,0);//注册主机函数print - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0);//注册主机函数sleep - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0);//开始运行虚拟机函数 + PX_CompilerFree(&compiler); // 释放编译器 + PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize); // 初始化虚拟机 + PX_VMRegisterHostFunction(&vm, "print", host_print,0); // 注册主机函数print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0); // 注册主机函数sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 PX_Object *pDbgObject = PX_Object_AsmDebuggerCreate(mp, root, 0, 0, 800, 480, 0); pDbgObject->Visible = PX_TRUE; PX_Object_AsmDebuggerAttach(pDbgObject, &debugmap, &vm); @@ -1746,15 +1747,15 @@ px_int main() ## 18. 使用 PainterEngine 快速创作一个小游戏 -为了更好地演示 PainterEngine 的使用, 我将用 PainterEngine 创作一个简单的小游戏, 你可以在 documents/demo/game 下找到有关这个游戏的所有源码及原始素材。得益于 PainterEngine 的全平台可移植性,你也可以在 [PainterEngine 在线应用 APP--打地鼠](https://www.painterengine.com/main/app/documentgame/) 中, 直接玩到这个在线小游戏。 +为了更好地演示 PainterEngine 的使用,我将用 PainterEngine 创作一个简单的小游戏,你可以在 documents/demo/game 下找到有关这个游戏的所有源码及原始素材。得益于 PainterEngine 的全平台可移植性,你也可以在 [PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) 中,直接玩到这个在线小游戏。 在这个小游戏中,我将充分为你展示,如何使用 PainterEngine 的组件化开发模式,快速创建一个 App Game。 -让我们先开始游戏创作的第一步,我们先准备好所需的美术资源及素材: +让我们先开始游戏创作的第一步,我们先准备好所需的美术资源及素材: ![](assets/img/18.1.png) -这是一个简单的游戏背景素材,然后我们就可以开始创建我们的 `main.c` 源代码文件, 在 PainterEngine 中我们输入下面的代码: +这是一个简单的游戏背景素材,然后我们就可以开始创建我们的 `main.c` 源代码文件,在 PainterEngine 中我们输入下面的代码: ```c px_int main() @@ -1786,23 +1787,23 @@ px_int main() } ``` -在代码的开始阶段, 我们初始化了一个 800x480 的窗口, 然后我们初始化了字模, 并用 `PX_FontModuleSetCodepage` 函数设置了其为 GBK 字符集, 再后面, 我们就是把资源加载进 PainterEngine 的资源管理器中了。 +在代码的开始阶段,我们初始化了一个 800x480 的窗口,然后我们初始化了字模,并用 `PX_FontModuleSetCodepage` 函数设置了其为 GBK 字符集,再后面,我们就是把资源加载进 PainterEngine 的资源管理器中了。 ### 加载资源及设置背景 -PainterEngine 内置了一个资源管理器,它在 `PainterEngine_Initialize` 中就被初始化了, 使用的是 `mp_static` 内存池。资源管理器的作用是像数据库一样, 将图片、音频、脚本等等素材加载到内存中, 并将它映射为一个 `key`, 之后对资源的访问都是通过 `key` 进行的。资源管理器的映射做了专门的优化, 因此你不必太担心映射查询带来的性能损耗问题。 +PainterEngine 内置了一个资源管理器,它在 `PainterEngine_Initialize` 中就被初始化了,使用的是 `mp_static` 内存池。资源管理器的作用是像数据库一样,将图片、音频、脚本等等素材加载到内存中,并将它映射为一个 `key`,之后对资源的访问都是通过 `key` 进行的。资源管理器的映射做了专门的优化,因此你不必太担心映射查询带来的性能损耗问题。 -`PX_LoadTextureToResource` 函数用于将一个文件系统的资源加载到资源管理器中, 第一个参数是这个资源管理器的实例指针, PainterEngine 在初始化阶段会默认创建一个这样的管理器实例, 因此你可以直接用 `PainterEngine_GetResourceLibrary` 获得它。第二个参数是需要加载文件的所在路径,第三个参数则是我们想映射的 `key` 了。 +`PX_LoadTextureToResource` 函数用于将一个文件系统的资源加载到资源管理器中,第一个参数是这个资源管理器的实例指针,PainterEngine 在初始化阶段会默认创建一个这样的管理器实例,因此你可以直接用 `PainterEngine_GetResourceLibrary` 获得它。第二个参数是需要加载文件的所在路径,第三个参数则是我们想映射的 `key` 了。 -在代码的下一步, 我们使用 `PX_LoadTextureToResource` 加载了若干图片, `PX_LoadAnimationToResource` 加载了一个 2dx 动画(请到应用市场查看 2DX 动画详细说明)。最后,在游戏里我们并没有使用 TTF 字模文件,我们循环加载了 `0.png` 到 `9.png`, 并将这些纹理作为图片插入到字模中, 这样这个字模绘制数字时, 实际显示的就是我们的图片。 +在代码的下一步,我们使用 `PX_LoadTextureToResource` 加载了若干图片,`PX_LoadAnimationToResource` 加载了一个 2dx 动画(请到应用市场查看 2DX 动画详细说明)。最后,在游戏里我们并没有使用 TTF 字模文件,我们循环加载了 `0.png` 到 `9.png`,并将这些纹理作为图片插入到字模中,这样这个字模绘制数字时,实际显示的就是我们的图片。 -同时我们还调用了 `PainterEngine_SetBackgroundTexture` 设置 PainterEngine 界面的背景, 请注意 `PX_ResourceLibraryGetTexture` 函数, 它的作用是使用一个查询 `key`, 从资源管理器中取得这个图片的数据结构指针。以上完成后你将可以看到这样的界面: +同时我们还调用了 `PainterEngine_SetBackgroundTexture` 设置 PainterEngine 界面的背景,请注意 `PX_ResourceLibraryGetTexture` 函数,它的作用是使用一个查询 `key`,从资源管理器中取得这个图片的数据结构指针。以上完成后你将可以看到这样的界面: ![](assets/img/18.2.png) ### 设计游戏对象 -我们先来设计第一个游戏对象,就是 `开始游戏按钮`。这一部分我们并不要写太多的代码, 因为 PainterEngine 内置就有这种按钮的功能: +我们先来设计第一个游戏对象,就是 `开始游戏按钮`。这一部分我们并不要写太多的代码,因为 PainterEngine 内置就有这种按钮的功能: ```c startgame = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 90, "Start Game", 0); @@ -1812,33 +1813,33 @@ PX_Object_PushButtonSetPushColor(startgame, PX_COLOR(224, 255, 255, 255)); PX_Object_PushButtonSetCursorColor(startgame, PX_COLOR(168, 255, 255, 255)); ``` -我们使用了一系列函数, 改变了按钮的背景颜色、鼠标悬停颜色和鼠标按下的颜色, 因此你可以看到这样的情况: +我们使用了一系列函数,改变了按钮的背景颜色、鼠标悬停颜色和鼠标按下的颜色,因此你可以看到这样的情况: ![](assets/img/18.3.png) -然后我们需要创建我们的游戏里的地鼠对象, 这是游戏里最复杂的对象, 我贴上详细代码, 以逐步解释它们: +然后我们需要创建我们的游戏里的地鼠对象,这是游戏里最复杂的对象,我贴上详细代码,以逐步解释它们: ```c typedef enum { - PX_OBJECT_FOX_STATE_IDLE,//狐狸还在洞里 - PX_OBJECT_FOX_STATE_RASING,//狐狸正在升起 - PX_OBJECT_FOX_STATE_TAUNT,//狐狸在嘲讽 - PX_OBJECT_FOX_STATE_ESCAPE,//狐狸逃跑 - PX_OBJECT_FOX_STATE_BEAT,//狐狸被打 - PX_OBJECT_FOX_STATE_HURT,//狐狸受伤后逃跑 + PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 + PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 + PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 + PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 + PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 + PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 }PX_OBJECT_FOX_STATE; typedef struct { - PX_OBJECT_FOX_STATE state;//狐狸状态 - px_dword elapsed;//状态持续时间 - px_float texture_render_offset;//纹理渲染偏移 - px_dword gen_rand_time;//生成随机时间 - px_float rasing_down_speed;//升起速度 - px_texture render_target;//渲染目标 - px_texture* pcurrent_display_texture;//当前显示的纹理 - px_texture* ptexture_mask;//遮罩 + PX_OBJECT_FOX_STATE state; // 狐狸状态 + px_dword elapsed; // 状态持续时间 + px_float texture_render_offset; // 纹理渲染偏移 + px_dword gen_rand_time; // 生成随机时间 + px_float rasing_down_speed; // 升起速度 + px_texture render_target; // 渲染目标 + px_texture* pcurrent_display_texture; // 当前显示的纹理 + px_texture* ptexture_mask; // 遮罩 }PX_Object_Fox; PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) @@ -1850,18 +1851,18 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) { if (pfox->gen_rand_time ==0) { - pfox->gen_rand_time = PX_rand() % 3000 + 1000;//狐狸在洞里的时间,时间到了就升起来 + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 } else { - if (pfox->gen_rand_time gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; pfox->elapsed = 0; pfox->gen_rand_time = 0; pfox->texture_render_offset = pObject->Height; - //改变纹理 + // 改变纹理 pfox->pcurrent_display_texture= PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); } else @@ -1871,42 +1872,42 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) } } break; - case PX_OBJECT_FOX_STATE_RASING://狐狸升起 + case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 { pfox->elapsed += elapsed; - //升起纹理偏移量 + // 升起纹理偏移量 pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; if (pfox->texture_render_offset <= 0) { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT;//升起后嘲讽 + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_TAUNT://狐狸嘲讽 + case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 { pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500)//嘲讽时间 + if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt");//嘲讽纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 } - else if (pfox->elapsed>1500)//嘲讽结束 + else if (pfox->elapsed>1500) // 嘲讽结束 { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//逃跑 - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape");//逃跑纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_BEAT://狐狸被打 + case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 { pfox->elapsed += elapsed; if (pfox->elapsed>800) { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt");//受伤纹理 - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 } } break; @@ -1917,8 +1918,8 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) if (pfox->texture_render_offset >= pObject->Height) { pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE;//逃跑结束 - pfox->elapsed = 0;//重置时间 + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 + pfox->elapsed = 0; // 重置时间 pfox->pcurrent_display_texture = PX_NULL; } } @@ -1933,12 +1934,12 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); px_float x,y,width,height; PX_OBJECT_INHERIT_CODE(pObject,x,y,width,height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE);//清空渲染目标 + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 if (pfox->pcurrent_display_texture) { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL);//渲染狐狸 + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL);//以遮罩形式绘制纹理 + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 } @@ -1948,12 +1949,12 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) PX_TextureFree(&pfox->render_target); } -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick)//狐狸被点击 +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 { PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING)//狐狸嘲讽或者升起时点击有效 + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM))//点击有效区域 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 { px_int x= (px_int)PX_Object_Event_GetCursorX(e); px_int y= (px_int)PX_Object_Event_GetCursorY(e); @@ -1985,57 +1986,57 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) PX_Object *PX_Object_FoxCreate(px_memorypool *mp,PX_Object *parent,px_float x,px_float y) { PX_Object_Fox* pfox; - px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing");//从资源管理器中获取纹理 + px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing"); // 从资源管理器中获取纹理 PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - pfox->state= PX_OBJECT_FOX_STATE_IDLE;//狐狸状态 - pfox->rasing_down_speed = 512;//升起速度 - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask");//遮罩 + pfox->state= PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 + pfox->rasing_down_speed = 512; // 升起速度 + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 if(!PX_TextureCreate(mp,&pfox->render_target,ptexture->width,ptexture->height)) { PX_ObjectDelete(pObject); return 0; } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0);//注册点击事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0);//注册重置事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0); // 注册点击事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0); // 注册重置事件 return pObject; } ``` -* 首先是 `PX_Object_FoxOnUpdate`, 这是对象三件套中的 `update` 函数, 在这个函数中, 我们判断当前这个 `地鼠` 的状态, 到底是升起、嘲讽, 还是缩回去。 -* 然后是 `PX_Object_FoxOnRender`, 这是执行 `render` 的函数, 我们通过偏移量把纹理绘制出来, 当然在这里我们调用了 `PX_TextureRenderMask` 函数, 这是一个带纹理遮罩的绘制函数。 -* `PX_Object_FoxFree` 函数中, 主要是对临时渲染表面的释放处理, 虽然在本项目中并没有用到。 -* `PX_Object_FoxOnClick` 函数, 表示当前的地鼠被击打了, 其中是一些命中范围的判断, 如果被击中了, 应该把状态设置为受伤。 -* `PX_Object_FoxOnReset` 用于执行复位, 即游戏结束后, 所有地鼠都应该是重置状态, 这是一个 `PX_OBJECT_EVENT_RESET` 的回调, 你可以在 `PX_Object_FoxCreate` 中找到它。 -* 最后是 `PX_Object_FoxCreate` 函数, 在这个函数中我们做了一些初始化工作, 为 `地鼠` 注册了事件回调, 最终完成这个组件的开发设计。 +* 首先是 `PX_Object_FoxOnUpdate`,这是对象三件套中的 `update` 函数,在这个函数中,我们判断当前这个 `地鼠` 的状态,到底是升起、嘲讽,还是缩回去。 +* 然后是 `PX_Object_FoxOnRender`,这是执行 `render` 的函数,我们通过偏移量把纹理绘制出来,当然在这里我们调用了 `PX_TextureRenderMask` 函数,这是一个带纹理遮罩的绘制函数。 +* `PX_Object_FoxFree` 函数中,主要是对临时渲染表面的释放处理,虽然在本项目中并没有用到。 +* `PX_Object_FoxOnClick` 函数,表示当前的地鼠被击打了,其中是一些命中范围的判断,如果被击中了,应该把状态设置为受伤。 +* `PX_Object_FoxOnReset` 用于执行复位,即游戏结束后,所有地鼠都应该是重置状态,这是一个 `PX_OBJECT_EVENT_RESET` 的回调,你可以在 `PX_Object_FoxCreate` 中找到它。 +* 最后是 `PX_Object_FoxCreate` 函数,在这个函数中我们做了一些初始化工作,为 `地鼠` 注册了事件回调,最终完成这个组件的开发设计。 ![](assets/img/18.4.gif) -然后,我们需要创建一个 `锤子` 对象来改变我们鼠标的样式。锤子对象的设计很简单, 它只有 2 个纹理, 一个是鼠标没有按下时的状态,一个是按下时的状态。不同的状态对应不同的纹理: +然后,我们需要创建一个 `锤子` 对象来改变我们鼠标的样式。锤子对象的设计很简单,它只有 2 个纹理,一个是鼠标没有按下时的状态,一个是按下时的状态。不同的状态对应不同的纹理: ```c typedef struct { - px_texture ham01;//锤子纹理1,没有按下 - px_texture ham02;//锤子纹理2,按下 - px_bool bHit;//是否按下 + px_texture ham01; // 锤子纹理1,没有按下 + px_texture ham02; // 锤子纹理2,按下 + px_bool bHit; // 是否按下 }PX_Object_Hammer; -PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender)//锤子渲染 +PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); px_float x, y, width, height; PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); if (phammer->bHit) { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//按下 + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 } else { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//未按下 + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 } } @@ -2049,20 +2050,20 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) { - pObject->x=PX_Object_Event_GetCursorX(e);//锤子跟随鼠标移动 + pObject->x=PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 pObject->y=PX_Object_Event_GetCursorY(e); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_TRUE;//按下 + phammer->bHit = PX_TRUE; // 按下 } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE;//抬起 + phammer->bHit = PX_FALSE; // 抬起 } PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) @@ -2073,24 +2074,24 @@ PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) phammer->bHit = PX_FALSE; if (!PX_LoadTextureFromFile(mp_static,&phammer->ham01, "assets/ham1.png")) return PX_NULL; if (!PX_LoadTextureFromFile(mp_static,&phammer->ham02, "assets/ham2.png")) return PX_NULL; - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL);//注册移动事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL);//注册拖拽事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL);//注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL);//注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL);//注册抬起事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 return pObject; } ``` -最后则是一个倒计时框, 它中间其实是一个 2dx 的动画对象(PainterEngine 直接支持 gif 动画, 其实 gif 也可以), 外围是一个环, 环形的弧度不断减少, 以实现一个 `倒计时` 的显示效果: +最后则是一个倒计时框,它中间其实是一个 2dx 的动画对象(PainterEngine 直接支持 gif 动画,其实 gif 也可以),外围是一个环,环形的弧度不断减少,以实现一个 `倒计时` 的显示效果: ```c typedef struct { - PX_Animation animation;//动画 - px_dword time;//倒计时时间 - px_dword elapsed;//倒计时开始后已经过去的时间 + PX_Animation animation; // 动画 + px_dword time; // 倒计时时间 + px_dword elapsed; // 倒计时开始后已经过去的时间 }PX_Object_Clock; @@ -2101,7 +2102,7 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) if (clock->elapsed >= clock->time) { clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET));//重置狐狸状态,给game对象发送重置事件 + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给game对象发送重置事件 game->Visible = PX_FALSE; game->Enabled = PX_FALSE; startgame->Visible = PX_TRUE; @@ -2114,11 +2115,11 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) { PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed);//更新动画 - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL);//绘制动画 - //draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK);//绘制倒计时环边框 - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time)));//绘制倒计时环 + PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 + // draw ring + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 } PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) @@ -2127,7 +2128,7 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) PX_AnimationFree(&clock->animation); } -px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time)//开始倒计时 +px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 { PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); clock->time = time; @@ -2142,7 +2143,7 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); clock->time = 0; clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song")))//从资源管理器中获取动画 + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 { PX_ObjectDelete(pObject); return PX_NULL; @@ -2153,12 +2154,12 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float } ``` -### 放置对象, 完成游戏 +### 放置对象,完成游戏 -在 `main` 函数中, 我们将上述对象一一创建, 并放置在游戏场景中, 最终完成这个游戏: +在 `main` 函数中,我们将上述对象一一创建,并放置在游戏场景中,最终完成这个游戏: ```c -//创建地鼠 +// 创建地鼠 game=PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); PX_Object_FoxCreate(mp, game, 173, 326); PX_Object_FoxCreate(mp, game, 401, 326); @@ -2169,14 +2170,14 @@ PX_Object_FoxCreate(mp, game, 636, 476); game->Visible=PX_FALSE; game->Enabled=PX_FALSE; -//创建锤子 +// 创建锤子 PX_Object_HammerCreate(mp, root); scorePanel = PX_Object_ScorePanelCreate(mp, root, 400, 60, &score_fm, 100); -//创建倒计时框 +// 创建倒计时框 gameclock=PX_Object_ClockCreate(mp,root,680,60); ``` -在这里, 我放上整个游戏的完整代码: +在这里,我放上整个游戏的完整代码: ```c #include "PainterEngine.h" @@ -2191,38 +2192,38 @@ PX_Object* game,*startgame,*gameclock; typedef enum { - PX_OBJECT_FOX_STATE_IDLE,//狐狸还在洞里 - PX_OBJECT_FOX_STATE_RASING,//狐狸正在升起 - PX_OBJECT_FOX_STATE_TAUNT,//狐狸在嘲讽 - PX_OBJECT_FOX_STATE_ESCAPE,//狐狸逃跑 - PX_OBJECT_FOX_STATE_BEAT,//狐狸被打 - PX_OBJECT_FOX_STATE_HURT,//狐狸受伤后逃跑 + PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 + PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 + PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 + PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 + PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 + PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 }PX_OBJECT_FOX_STATE; typedef struct { - PX_OBJECT_FOX_STATE state;//狐狸状态 - px_dword elapsed;//状态持续时间 - px_float texture_render_offset;//纹理渲染偏移 - px_dword gen_rand_time;//生成随机时间 - px_float rasing_down_speed;//升起速度 - px_texture render_target;//渲染目标 - px_texture* pcurrent_display_texture;//当前显示的纹理 - px_texture* ptexture_mask;//遮罩 + PX_OBJECT_FOX_STATE state; // 狐狸状态 + px_dword elapsed; // 状态持续时间 + px_float texture_render_offset; // 纹理渲染偏移 + px_dword gen_rand_time; // 生成随机时间 + px_float rasing_down_speed; // 升起速度 + px_texture render_target; // 渲染目标 + px_texture* pcurrent_display_texture; // 当前显示的纹理 + px_texture* ptexture_mask; // 遮罩 }PX_Object_Fox; typedef struct { - px_texture ham01;//锤子纹理1,没有按下 - px_texture ham02;//锤子纹理2,按下 - px_bool bHit;//是否按下 + px_texture ham01; // 锤子纹理1,没有按下 + px_texture ham02; // 锤子纹理2,按下 + px_bool bHit; // 是否按下 }PX_Object_Hammer; typedef struct { - PX_Animation animation;//动画 - px_dword time;//倒计时时间 - px_dword elapsed;//倒计时开始后已经过去的时间 + PX_Animation animation; // 动画 + px_dword time; // 倒计时时间 + px_dword elapsed; // 倒计时开始后已经过去的时间 }PX_Object_Clock; @@ -2233,7 +2234,7 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) if (clock->elapsed >= clock->time) { clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET));//重置狐狸状态,给game对象发送重置事件 + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给game对象发送重置事件 game->Visible = PX_FALSE; game->Enabled = PX_FALSE; startgame->Visible = PX_TRUE; @@ -2246,11 +2247,11 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) { PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed);//更新动画 - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL);//绘制动画 - //draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK);//绘制倒计时环边框 - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time)));//绘制倒计时环 + PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 + // draw ring + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 } PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) @@ -2259,7 +2260,7 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) PX_AnimationFree(&clock->animation); } -px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time)//开始倒计时 +px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 { PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); clock->time = time; @@ -2274,7 +2275,7 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); clock->time = 0; clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song")))//从资源管理器中获取动画 + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 { PX_ObjectDelete(pObject); return PX_NULL; @@ -2293,18 +2294,18 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) { if (pfox->gen_rand_time ==0) { - pfox->gen_rand_time = PX_rand() % 3000 + 1000;//狐狸在洞里的时间,时间到了就升起来 + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 } else { - if (pfox->gen_rand_time gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; pfox->elapsed = 0; pfox->gen_rand_time = 0; pfox->texture_render_offset = pObject->Height; - //改变纹理 + // 改变纹理 pfox->pcurrent_display_texture= PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); } else @@ -2314,42 +2315,42 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) } } break; - case PX_OBJECT_FOX_STATE_RASING://狐狸升起 + case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 { pfox->elapsed += elapsed; - //升起纹理偏移量 + // 升起纹理偏移量 pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; if (pfox->texture_render_offset <= 0) { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT;//升起后嘲讽 + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_TAUNT://狐狸嘲讽 + case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 { pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500)//嘲讽时间 + if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt");//嘲讽纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 } - else if (pfox->elapsed>1500)//嘲讽结束 + else if (pfox->elapsed>1500) // 嘲讽结束 { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//逃跑 - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape");//逃跑纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_BEAT://狐狸被打 + case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 { pfox->elapsed += elapsed; if (pfox->elapsed>800) { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt");//受伤纹理 - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 } } break; @@ -2360,8 +2361,8 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) if (pfox->texture_render_offset >= pObject->Height) { pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE;//逃跑结束 - pfox->elapsed = 0;//重置时间 + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 + pfox->elapsed = 0; // 重置时间 pfox->pcurrent_display_texture = PX_NULL; } } @@ -2376,12 +2377,12 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); px_float x,y,width,height; PX_OBJECT_INHERIT_CODE(pObject,x,y,width,height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE);//清空渲染目标 + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 if (pfox->pcurrent_display_texture) { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL);//渲染狐狸 + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL);//以遮罩形式绘制纹理 + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 } PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) @@ -2390,12 +2391,12 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) PX_TextureFree(&pfox->render_target); } -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick)//狐狸被点击 +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 { PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING)//狐狸嘲讽或者升起时点击有效 + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM))//点击有效区域 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 { px_int x= (px_int)PX_Object_Event_GetCursorX(e); px_int y= (px_int)PX_Object_Event_GetCursorY(e); @@ -2427,34 +2428,34 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) PX_Object *PX_Object_FoxCreate(px_memorypool *mp,PX_Object *parent,px_float x,px_float y) { PX_Object_Fox* pfox; - px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing");//从资源管理器中获取纹理 + px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing"); // 从资源管理器中获取纹理 PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - pfox->state= PX_OBJECT_FOX_STATE_IDLE;//狐狸状态 - pfox->rasing_down_speed = 512;//升起速度 - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask");//遮罩 + pfox->state= PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 + pfox->rasing_down_speed = 512; // 升起速度 + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 if(!PX_TextureCreate(mp,&pfox->render_target,ptexture->width,ptexture->height)) { PX_ObjectDelete(pObject); return 0; } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0);//注册点击事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0);//注册重置事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0); // 注册点击事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0); // 注册重置事件 return pObject; } -PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender)//锤子渲染 +PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); px_float x, y, width, height; PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); if (phammer->bHit) { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//按下 + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 } else { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//未按下 + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 } } @@ -2468,20 +2469,20 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) { - pObject->x=PX_Object_Event_GetCursorX(e);//锤子跟随鼠标移动 + pObject->x=PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 pObject->y=PX_Object_Event_GetCursorY(e); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_TRUE;//按下 + phammer->bHit = PX_TRUE; // 按下 } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE;//抬起 + phammer->bHit = PX_FALSE; // 抬起 } PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) @@ -2492,11 +2493,11 @@ PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) phammer->bHit = PX_FALSE; if (!PX_LoadTextureFromFile(mp_static,&phammer->ham01, "assets/ham1.png")) return PX_NULL; if (!PX_LoadTextureFromFile(mp_static,&phammer->ham02, "assets/ham2.png")) return PX_NULL; - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL);//注册移动事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL);//注册拖拽事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL);//注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL);//注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL);//注册抬起事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 return pObject; } @@ -2507,7 +2508,7 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_StartGameOnClick) startgame->Visible = PX_FALSE; game->Enabled = PX_TRUE; PX_Object_ScorePanelSetScore(scorePanel, 0); - PX_Object_ClockBegin(gameclock, 30000);//开始游戏,游戏时间30秒 + PX_Object_ClockBegin(gameclock, 30000); // 开始游戏,游戏时间30秒 } @@ -2569,9 +2570,9 @@ px_int main() } ``` -你可以在 documents/demo/game 中找到这个游戏的完整资源, 并用 PainterEngine 直接编译。 +你可以在 documents/demo/game 中找到这个游戏的完整资源,并用 PainterEngine 直接编译。 ![](assets/img/18.5.gif) -在线试玩: [PainterEngine 在线应用 APP--打地鼠](https://www.painterengine.com/main/app/documentgame/) +在线试玩:[PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) From 964c7c2e18470e8ec080629b755f8d5b1afaaccc Mon Sep 17 00:00:00 2001 From: Recogerous <2737936634@qq.com> Date: Sat, 23 Nov 2024 00:35:20 +0800 Subject: [PATCH 2/9] optimize readme and doc again - Use Chinese punctuation for Chinese text and English punctuation for English text. - Add spaces around the comment symbols in markdown code blocks. - Adjust indent and alignment of some code and comments in code blocks. - Included the missing modifications from the previous PR. --- README.md | 68 +- ...ook.md => PainterEngine_the_book_zh-CN.md} | 913 +++++++++--------- 2 files changed, 492 insertions(+), 489 deletions(-) rename documents/{PainterEngine the book.md => PainterEngine_the_book_zh-CN.md} (59%) diff --git a/README.md b/README.md index 6d22c483..69879fa7 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,30 @@

-PainterEngine 是一个由 C 语言编写的跨平台图形引擎, 支持 Windows/Linux/iOS/Android/WebAssembly 甚至无操作系统的裸嵌入式平台, 它基于组件化的设计模式, 即使是 C 语言初学者, 也可以在几分钟内掌握它的使用, [PainterEngine Make](https://www.painterengine.com/) 允许您一键将您的 PainterEngine 项目编译到多个平台. -它涵盖了基础数据结构、图形学、声学、数字信号处理、编译原理、虚拟机系统、密码学、人机交互、游戏引擎、FPGA-GPU 图形 IP 设计等多个领域, 你既可以用它制作微应用, 也可以将它作为学习项目。 +PainterEngine 是一个由 C 语言编写的跨平台图形引擎,支持 Windows/Linux/iOS/Android/WebAssembly 甚至无操作系统的裸嵌入式平台,它基于组件化的设计模式,即使是 C 语言初学者,也可以在几分钟内掌握它的使用,[PainterEngine Make](https://www.painterengine.com/) 允许您一键将您的 PainterEngine 项目编译到多个平台。 +它涵盖了基础数据结构、图形学、声学、数字信号处理、编译原理、虚拟机系统、密码学、人机交互、游戏引擎、FPGA-GPU 图形 IP 设计等多个领域,你既可以用它制作微应用,也可以将它作为学习项目。 -现在了解并使用 PainterEngine----> -*__[PainterEngine 快速入门手册(中文版)](./documents/PainterEngine%20the%20book.md)__* +现在了解并使用 PainterEngine $\longrightarrow$ +*__[PainterEngine 快速入门手册(中文版)](./documents/PainterEngine_the_book_zh-CN.md)__* -PainterEngine is a cross-platform graphics engine written in C language, with support for Windows, Linux, iOS, Android, WebAssembly, and even bare-metal embedded platforms without OS. It is built on a component-based design pattern, making it accessible to even C language beginners . [PainterEngine Make](https://www.painterengine.com/) enables you to compile your PainterEngine project for multiple platforms with just one click. +PainterEngine is a cross-platform graphics engine written in C language, with support for Windows, Linux, iOS, Android, WebAssembly, and even bare-metal embedded platforms without OS. It is built on a component-based design pattern, making it accessible to even C language beginners. [PainterEngine Make](https://www.painterengine.com/) enables you to compile your PainterEngine project for multiple platforms with just one click. It covers various fields including basic data structures, graphics, acoustics, digital signal processing, compiler design, virtual machine systems, cryptography, human-computer interaction, game engines, FPGA-GPU graphics acceleration, and more. You can use it to create mini-applications or as a learning project for acquiring knowledge. +Now, learn and use PainterEngine $\longrightarrow$ +*__[PainterEngine Quick Start Guide (English Version)](./documents/PainterEngine_the_book_en.md)__* + ## 30 秒速览 PainterEngine ## 30-Second Quick Start Guide to PainterEngine -将 PainterEngine 引入到您的 C/C++ 项目中, 仅仅需要 `#include "PainterEngine.h"`. +将 PainterEngine 引入到您的 C/C++ 项目中,仅仅需要 `#include "PainterEngine.h"`。 To incorporate PainterEngine into your project, all you need is: ```c #include "PainterEngine.h" ``` -使用 `PainterEngine_Initialize`, 快速创建一个图形化的交互式界面: +使用 `PainterEngine_Initialize`,快速创建一个图形化的交互式界面: Utilize `PainterEngine_Initialize` to swiftly create a graphical interactive interface: ```c @@ -36,7 +39,7 @@ int main() } ``` -创建组件, 或者...创造自己的组件: +创建组件,或者……创造自己的组件: Create components or even craft your own: @@ -56,7 +59,7 @@ int main() PainterEngine firework

-使用 [PainterEngine Make](https://www.painterengine.com/) 快速将您的项目编译到 Windows, Linux, WebAssembly, Android 等任意平台, 一键编译部署, 源码无需修改, 零成本移植. +使用 [PainterEngine Make](https://www.painterengine.com/) 快速将您的项目编译到 Windows、Linux、WebAssembly、Android 等任意平台,一键编译部署,源码无需修改,零成本移植。 Use ["PainterEngine Make"](https://www.painterengine.com/) to quickly compile and deploy your project to various platforms such as Windows, Linux, WebAssembly, Android, and more. One-click compilation and deployment, with no need to modify the source code, enabling seamless portability at zero cost. @@ -72,20 +75,21 @@ Use ["PainterEngine Make"](https://www.painterengine.com/) to quickly compile an

-## 快速开发, 无缝迁移 +## 快速开发,无缝迁移 + ## Swift development and smooth transitions -如果您不需要 PainterEngine Make 提供的一键编译功能, 希望使用自己常用的 IDE 开发 PainterEngine 程序或组件, 您只需要: +如果您不需要 PainterEngine Make 提供的一键编译功能,希望使用自己常用的 IDE 开发 PainterEngine 程序或组件,您只需要: -1. 将 "PainterEngine/core", "PainterEngine/kernel", "PainterEngine/runtime" 的所有代码, 添加到您的项目中. +1. 将 "PainterEngine/core"、"PainterEngine/kernel"、"PainterEngine/runtime" 的所有代码,添加到您的项目中。 -2. 在 "PainterEngine/platform" 中选择您的工作平台(例如 Windows 中选择 "PainterEngine/platform/windows"), 并将对应文件夹中的所有代码添加到您的项目中. +2. 在 "PainterEngine/platform" 中选择您的工作平台(例如 Windows 中选择 "PainterEngine/platform/windows"),并将对应文件夹中的所有代码添加到您的项目中。 -3. 将 PainterEngine 所在目录, 添加到包含目录中. +3. 将 PainterEngine 所在目录添加到包含目录中。 -4. 将您的代码添加进项目中. +4. 将您的代码添加进项目中。 -即可使用您的 IDE 完成 PainterEngine 的编译, PainterEngine 库将尽力保证所有平台的的运行结果一致性, 在 Windows 上开发, 同样在 Android/web/Linux/iOS/... 中能够得到一致的结果. +即可使用您的 IDE 完成 PainterEngine 的编译,PainterEngine 库将尽力保证所有平台的的运行结果一致性,在 Windows 上开发,同样在 Android/web/Linux/iOS/…… 中能够得到一致的结果。 If you don't need the one-key compilation feature provided by PainterEngine Make and prefer to develop PainterEngine programs or components using your preferred IDE, you just need to: @@ -99,35 +103,33 @@ If you don't need the one-key compilation feature provided by PainterEngine Make You can now use your IDE to compile PainterEngine with these steps. PainterEngine library will strive to ensure consistent results across all platforms. What you develop on Windows will yield consistent results on Android, web, Linux, iOS, and more. -## 不仅是图形库, 更是应用程序框架 +## 不仅是图形库,更是应用程序框架 -## Not just a graphics library but also an application framework. +## Not just a graphics library but also an application framework | Functions | Support | Description | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ | | 内存池 | alloc/free | 平台无关的内存池实现 | | 数学库 | sin/cos/tan/arcsin/log/exp/relu/... | 绝大部分 C 标准数学库的完整实现 | -| 信号处理 | dft/dct/fft/dwt/window functions/mfcc/... | 傅里叶/余弦/小波变换, 常用窗函数, mfcc 等特征采集算法...等等信号处理相关基础函数及上层特征采集算法 | +| 信号处理 | dft/dct/fft/dwt/window functions/mfcc/... | 傅里叶/余弦/小波变换、常用窗函数、mfcc 等特征采集算法……等等信号处理相关基础函数及上层特征采集算法 | | 数据结构 | string/vector/list/map/stack/fifo/circular-buffer/... | 平台无关的数据结构算法实现 | | 密码学 | curve25519/AES/SHAx/MD5/... | 包含常用的密钥对称算法及密钥协商算法 | | 图片支持 | PNG/JPG/GIF/BMP | 支持 PNG/JPG/GIF/BMP 解码及 PNG 编码 | -| 音频支持 | WAV/MP3 | 支持 Wav, Mp3 解码及 Wav 编码| -| 字模支持 | ttf | 支持 ttf 字模文件(由 stb_truetype.c 移植而来)| +| 音频支持 | WAV/MP3 | 支持 Wav、Mp3 解码及 Wav 编码| +| 字模支持 | ttf | 支持 ttf 字模文件(由 stb_truetype.c 移植而来)| | 几何绘制 | Line/Triangle/Rectangle/Circle/Ring/Sector/Rounded/... | 常用几何光栅化实现 | | 渲染器 | 2D/3D | 2D/3D 渲染器实现及一个高质量制图引擎 | | 动画 | 2dx/live2D | 2D 动画和一个类 Live2D 骨骼动画系统 | -| 声学模型 | mixer/piano/ks | 包含一个混音器实现, 一个相位声码器, 一个物理建模的钢琴及 karplus-strong 合成的拨弦模型, 直接合成 PCM 音频流 | -| 脚本引擎 | Compiler/VM/Debugger | 一个完整的脚本引擎, 包含编译器虚拟机调试器 | +| 声学模型 | mixer/piano/ks | 包含一个混音器实现、一个相位声码器、一个物理建模的钢琴及 karplus-strong 合成的拨弦模型,直接合成 PCM 音频流 | +| 脚本引擎 | Compiler/VM/Debugger | 一个完整的脚本引擎,包含编译器虚拟机调试器 | | UI 框架 | button/radio/image/edit/label/list/... | UI 框架实现 | | 协议 | MQTT/MODBUS/Game-network-synchronization | 常用的通讯协议 | | 游戏引擎 | | 集成一个游戏世界框架 | -| FPGA-GPU |2D accelerator | 实现了基于 FPGA 的 GPU 图形加速器, 能够为 PainterEngine 提供不低于 50Mpps 的 2D Blender 及图元光栅化加速, 支持 HDMI 输出, 目前已在 zynq7000 系列 Soc 上完成验证| - -还有更多探索... - -/////////////////////////////////////////////////////////////////////////////// +| FPGA-GPU |2D accelerator | 实现了基于 FPGA 的 GPU 图形加速器,能够为 PainterEngine 提供不低于 50Mpps 的 2D Blender 及图元光栅化加速,支持 HDMI 输出,目前已在 zynq7000 系列 Soc 上完成验证| +还有更多探索…… +--- | functions | support | Description | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ | @@ -155,7 +157,7 @@ Many more to explore... ## The FPGA-GPU Graphics Acceleration Solution -提供一个基于 FPGA 的 GPU IP 核, 已在 Zynq7020 上完成功能验证, 提供不低于 50Mpps 的 2D Blender 图形渲染加速, 支持 HDMI 输出, 目前已在 zynq7000 系列 Soc 上完成验证. +提供一个基于 FPGA 的 GPU IP 核,已在 Zynq7020 上完成功能验证,提供不低于 50Mpps 的 2D Blender 图形渲染加速,支持 HDMI 输出,目前已在 zynq7000 系列 SoC 上完成验证。 A GPU IP core based on FPGA has been developed and functionally verified on the Zynq 7020 platform. It provides 2D Blender graphics rendering acceleration with a performance of no less than 50Mpps (Million pixels per second) and supports HDMI output. This solution has been successfully validated on the Zynq 7000 series SoC. @@ -165,13 +167,13 @@ A GPU IP core based on FPGA has been developed and functionally verified on the

PainterEngine designer

-## 组件化开发, 支持设计器模式, 简单的不能再简单 +## 组件化开发,支持设计器模式,简单的不能再简单 ## Component-based development with support for a designer mode, making it as simple as it gets

PainterEngine designer

-## 海量组件, 创意无界 +## 海量组件,创意无界 ## An abundance of components to unleash your creativity without limitations. @@ -183,9 +185,9 @@ A GPU IP core based on FPGA has been developed and functionally verified on the

PainterEngine market

-### 现在, 访问 PainterEngine.com, 参与建设 +### 现在,访问 PainterEngine.com,参与建设 -### Now, join the PainterEngine.com contribute to its development. +### Now, join the PainterEngine.com contribute to its development

logo diff --git a/documents/PainterEngine the book.md b/documents/PainterEngine_the_book_zh-CN.md similarity index 59% rename from documents/PainterEngine the book.md rename to documents/PainterEngine_the_book_zh-CN.md index d719e457..ea817dff 100644 --- a/documents/PainterEngine the book.md +++ b/documents/PainterEngine_the_book_zh-CN.md @@ -1,24 +1,24 @@ -# The Book Of PainterEngine +# PainterEngine 快速入门手册 -## 导言 +## 导言 -欢迎开始我们的 PainterEngine 的第一课, 但在此之前相信大家并不清楚 PainterEngine 到底是一个做什么的, 也许您已经在互联网上对它有些许印象, 它或许是一个图形库, 或者是游戏引擎, 你可能已经看过了基于它的关于声学、密码学、神经网络、数字信号处理、编译器、虚拟机等等相关内容,甚至还有一个基于 FPGA 的 GPU IP 核。所以你可能认为它是一个集合了各式各样程式库的大混合体。 +欢迎开始我们的 PainterEngine 的第一课,但在此之前相信大家并不清楚 PainterEngine 到底是一个做什么的,也许您已经在互联网上对它有些许印象,它或许是一个图形库,或者是游戏引擎,你可能已经看过了基于它的关于声学、密码学、神经网络、数字信号处理、编译器、虚拟机等等相关内容,甚至还有一个基于 FPGA 的 GPU IP 核。所以你可能认为它是一个集合了各式各样程式库的大混合体。 以上都对,但总的来说,我更倾向于将 PainterEngine 认为是一个应用程序框架,它诞生之初的最终原因,是为了解决程序开发过程中极其麻烦的三方库(甚至是标准库)依赖问题,最大程度简化程序的平台移植和编译难度。 因此如你所见,PainterEngine 的编译不会让你陷入各式各样的三方依赖中,目前它可以在几乎所有提供 C 语言编译环境的平台上运行,它没有操作系统及文件系统依赖,可以运行在裸 MCU 环境中,甚至 PainterEngine 的官网首页也是由 PainterEngine 开发的。 -PainterEngine 始终遵循着最简设计原则,而 PainterEngine 使用了 C 语言作为其主要开发语言,而其内置的脚本引擎,同样最大程度的兼容 C 语言语法,并对 C 语言的类型做了少量的抽象及泛化,进一步减少它的上手及使用门槛。C 语言作为一门历史悠久的语言,如今几乎是各大工科类专业必学的一门课程,其在计算机编程开发的发展中,始终保持着强大的竞争力及广泛认可,并成为了几乎所有硬件平台,所需要并提供支持的事实标准。C 语言在学习与开发成本维持着一个微妙的平衡,因此你可以在很短的时间学习并上手 C 语言,配合 PainterEngine,你就能将你的程序运行在全平台,并深刻感受编程艺术所带来的魅力。 +PainterEngine 始终遵循着最简设计原则,而 PainterEngine 使用了 C 语言作为其主要开发语言,而其内置的脚本引擎同样最大程度的兼容 C 语言语法,并对 C 语言的类型做了少量的抽象及泛化,进一步减少它的上手及使用门槛。C 语言作为一门历史悠久的语言,如今几乎是各大工科类专业必学的一门课程,其在计算机编程开发的发展中,始终保持着强大的竞争力及广泛认可,并成为了几乎所有硬件平台所需要并提供支持的事实标准。C 语言在学习与开发成本维持着一个微妙的平衡,因此你可以在很短的时间学习并上手 C 语言,配合 PainterEngine,你就能将你的程序运行在全平台,并深刻感受编程艺术所带来的魅力。 PainterEngine 同样经历了近乎十年的发展,但在很长的一段时间,其作为一个私用库较少在公开领域正式推广,一个是在其迭代的过程中,很多的接口和函数仍然未稳定,我们必须在长期的实践中,保证接口设计的合理性和易用性,区分哪些是“真正有用且好用”的,哪些只是“灵光一闪看上去光鲜亮丽,其实没啥用的”。因此 PainterEngine 在很长一段时间,都没有详细且稳定的文档,而经过那么多年的迭代,我们最终可以将那些稳定、好用、简单易学的设计公布出来,并最终给大家带来这篇文档。 -最后,我并不希望将导言写的太长,是时候马上切入主题了,我们将从 PainterEngine 的环境搭建开始 PainterEngine 的第一课,如果你有相关问题或发现了 bug,你可以在 PainterEngine 论坛中提问,或者直接将问题发送到 matrixcascade@gmail.com, 我将在第一时间给你反馈。 +最后,我并不希望将导言写的太长,是时候马上切入主题了,我们将从 PainterEngine 的环境搭建开始 PainterEngine 的第一课,如果你有相关问题或发现了 bug,你可以在 PainterEngine 论坛中提问,或者直接将问题发送到 matrixcascade@gmail.com,我将在第一时间给你反馈。 ![](assets/mini/1.png) ## 1. 一个最简单的 PainterEngine 程序 -在搭建开发环境之前,让我们先编写一个最简单的 PainterEngine 程序,让我们新建一个 `main.c` 文件(文件名可以任意取), 然后在其中输入以下代码: +在搭建开发环境之前,让我们先编写一个最简单的 PainterEngine 程序,让我们新建一个 `main.c` 文件(文件名可以任意取),然后在其中输入以下代码: ```c #include "PainterEngine.h" @@ -29,25 +29,25 @@ int main() } ``` -这是一个相当简单的 PainterEngine 程序, 简单来说, 在第一行我们使用 `include` 将 PainterEngine 的头文件包含了进来, 在 `main` 函数中, 我们使用 `PainterEngine_Initialize` 对 PainterEngine 进行初始化操作, `PainterEngine_Initialize` 接受 2 个参数, 分别是窗口(或者是屏幕)的宽度和高度, 在程序运行后, 你大概能看到这样的结果: +这是一个相当简单的 PainterEngine 程序,简单来说,在第一行我们使用 `include` 将 PainterEngine 的头文件包含了进来,在 `main` 函数中,我们使用 `PainterEngine_Initialize` 对 PainterEngine 进行初始化操作,`PainterEngine_Initialize` 接受 2 个参数,分别是窗口(或者是屏幕)的宽度和高度,在程序运行后,你大概能看到这样的结果: ![](assets/img/1.1.png) -当然, 现在我们还没有使用 PainterEngine 在窗口上绘制任何东西, 因此你看到的是一个空白的画面。需要注意的是, 在 PainterEngine 的框架中, `main` 函数返回后, 程序并不会立即结束, 实际上在 `PainterEngine.h` 中, `main` 函数被替换成了 `px_main`, 真正的 `main` 函数在 `px_main.c` 中实现, 但用户目前可以用不着关心这个, 只需要记住 `main` 函数返回后, 程序仍然是在正常运行的。如果你希望退出程序, 你可以自行调用 C 语言的 `exit` 函数, 但在 PainterEngine 中的很多时候你并不需要这么做, 因为像在嵌入式单片机、网页、驱动程序中,大部分是用不到退出这个概念的,哪怕是在 Android,iOS 平台,绝大部分时候也不需要你在程序中设计退出功能。 +当然,现在我们还没有使用 PainterEngine 在窗口上绘制任何东西,因此你看到的是一个空白的画面。需要注意的是,在 PainterEngine 的框架中,`main` 函数返回后,程序并不会立即结束,实际上在 `PainterEngine.h` 中,`main` 函数被替换成了 `px_main`,真正的 `main` 函数在 `px_main.c` 中实现,但用户目前可以用不着关心这个,只需要记住 `main` 函数返回后,程序仍然是在正常运行的。如果你希望退出程序,你可以自行调用 C 语言的 `exit` 函数,但在 PainterEngine 中的很多时候你并不需要这么做,因为像在嵌入式单片机、网页、驱动程序中,大部分是用不到退出这个概念的,哪怕是在 Android,iOS 平台,绝大部分时候也不需要你在程序中设计退出功能。 ## 2. 编译 PainterEngine 程序 ### 使用 PainterEngine Make 编译 -如果你想要编译 PainterEngine 的项目文件,最简单的做法是使用 PainterEngine Make,这是一款你能够在 PainterEngine.com 中下载到的一款编译工具, 你可以在主页下方找到它的下载按钮: +如果你想要编译 PainterEngine 的项目文件,最简单的做法是使用 PainterEngine Make,这是一款你能够在 PainterEngine.com 中下载到的一款编译工具,你可以在主页下方找到它的下载按钮: ![](assets/img/2.1.png) -解压缩以后, 直接运行 PainterEngine make.exe, 你就可以看到如下界面: +解压缩以后,直接运行 PainterEngine make.exe,你就可以看到如下界面: ![](assets/img/2.2.png) -然后选择你需要编译的平台, 然后选中我们之前创建的那个 C 语言代码文件: +然后选择你需要编译的平台,然后选中我们之前创建的那个 C 语言代码文件: ![](assets/img/2.3.png) @@ -57,13 +57,13 @@ int main() ### 使用 Visual Studio Code 编译 -要使用 Visual Studio Code 进行编译, 你需要确保你已经安装好了 Visual Studio Code 的 C 语言开发环境, 这块步骤我们略过, 因为在互联网上已经有相当多这样的教程了。 +要使用 Visual Studio Code 进行编译,你需要确保你已经安装好了 Visual Studio Code 的 C 语言开发环境,这块步骤我们略过,因为在互联网上已经有相当多这样的教程了。 然后请到 PainterEngine 中,下载 PainterEngine 的源码: ![](assets/img/2.5.png) -将下载好的源码解压到计算机的某个目录下,然后你需要记录这个目录的位置,并在 Windows 的环境变量中新建一个名叫 `PainterEnginePath` 的变量, 并将它赋值为 PainterEngine 库所在目录: +将下载好的源码解压到计算机的某个目录下,然后你需要记录这个目录的位置,并在 Windows 的环境变量中新建一个名叫 `PainterEnginePath` 的变量,并将它赋值为 PainterEngine 库所在目录: ![](assets/img/2.6.png) @@ -75,7 +75,7 @@ int main() ![](assets/img/2.9.png) -然后请用 Visual Studio Code 打开 `main.c` 文件, 然后你就可以进行编译运行了: +然后请用 Visual Studio Code 打开 `main.c` 文件,然后你就可以进行编译运行了: ![](assets/img/2.10.png) @@ -83,7 +83,7 @@ int main() ### 使用 Visual Studio 编译 -当然, 如果你需要使用完整的 IDE 开发体验, 那么仍然建议使用 Visual Studio 进行开发编译。要使用 Visual Studio 开发 PainterEngine,你需要打开 Visual Studio 创建一个空项目: +当然,如果你需要使用完整的 IDE 开发体验,那么仍然建议使用 Visual Studio 进行开发编译。要使用 Visual Studio 开发 PainterEngine,你需要打开 Visual Studio 创建一个空项目: ![](assets/img/2.12.png) @@ -103,7 +103,7 @@ int main() ![](assets/img/2.17.png) -打开 `项目` → `属性` → `VC++目录` 中, 把 PainterEngine 的所在目录包含进来: +打开 `项目` → `属性` → `VC++目录` 中,把 PainterEngine 的所在目录包含进来: ![](assets/img/2.18.png) @@ -111,73 +111,74 @@ int main() ![](assets/img/2.20.png) -特别需要注意检查配置和 Visual Stdio 的当前配置是否是一致的, 这是一个很容易出错的点: +特别需要注意检查配置和 Visual Stdio 的当前配置是否是一致的,这是一个很容易出错的点: ![](assets/img/2.21.png) -最后, 你就可以编译运行调试了: +最后,你就可以编译运行调试了: ![](assets/img/2.22.png) ## 3. PainterEngine 第一课,输出文字“Hello PainterEngine” -正如你所见,PainterEngine 是一个图形化应用程序框架, 但依据传统, 我们的第一课仍然是如何用 PainterEngine 输出文字, 但多数时候, 与其说是输出文字不如说是绘制文字。使用 PainterEngine 绘制文字非常简单, 查看如下代码: +正如你所见,PainterEngine 是一个图形化应用程序框架,但依据传统,我们的第一课仍然是如何用 PainterEngine 输出文字,但多数时候,与其说是输出文字不如说是绘制文字。使用 PainterEngine 绘制文字非常简单,查看如下代码: ```c #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); -//PainterEngine_DrawText -//参数1:x坐标 -//参数2:y坐标 -//参数3:文本内容 -//参数4:对齐方式 -//参数5:颜色 + PainterEngine_Initialize(800, 480); + // PainterEngine_DrawText + // 参数1:x坐标 + // 参数2:y坐标 + // 参数3:文本内容 + // 参数4:对齐方式 + // 参数5:颜色 PainterEngine_DrawText(400, 240, "Hello PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); return 1; } ``` -我们主要看 `PainterEngine_DrawText` 函数, 这是一个文本绘制函数, 它有 5 个参数, 当你运行程序后, 你将看到这个结果: + +我们主要看 `PainterEngine_DrawText` 函数,这是一个文本绘制函数,它有 5 个参数,当你运行程序后,你将看到这个结果: ![](assets/img/3.1.png) -整个函数非常好理解, 但这里我们详细解释一下 `对齐方式` 和 `颜色` 这两个参数的意义, 因为在后续的教程中, 这两个概念会经常被提及: +整个函数非常好理解,但这里我们详细解释一下 `对齐方式` 和 `颜色` 这两个参数的意义,因为在后续的教程中,这两个概念会经常被提及: -其中, `对齐方式` 就是对应内容绘制在屏幕上的对齐方式, PainterEngine 中的对齐方式包含以下几种格式: +其中,`对齐方式` 就是对应内容绘制在屏幕上的对齐方式,PainterEngine 中的对齐方式包含以下几种格式: ```c typedef enum { - PX_ALIGN_LEFTTOP = 7,//左上角对齐 - PX_ALIGN_MIDTOP = 8,//中上对齐 - PX_ALIGN_RIGHTTOP = 9,//右上角对齐 - PX_ALIGN_LEFTMID = 4,//左中对齐 - PX_ALIGN_CENTER = 5,//居中对齐 - PX_ALIGN_RIGHTMID = 6,//右中对齐 - PX_ALIGN_LEFTBOTTOM = 1,//左下角对齐 - PX_ALIGN_MIDBOTTOM = 2,//中底对齐 - PX_ALIGN_RIGHTBOTTOM = 3,//右下角对齐 + PX_ALIGN_LEFTTOP = 7, // 左上角对齐 + PX_ALIGN_MIDTOP = 8, // 中上对齐 + PX_ALIGN_RIGHTTOP = 9, // 右上角对齐 + PX_ALIGN_LEFTMID = 4, // 左中对齐 + PX_ALIGN_CENTER = 5, // 居中对齐 + PX_ALIGN_RIGHTMID = 6, // 右中对齐 + PX_ALIGN_LEFTBOTTOM = 1, // 左下角对齐 + PX_ALIGN_MIDBOTTOM = 2, // 中底对齐 + PX_ALIGN_RIGHTBOTTOM = 3, // 右下角对齐 }PX_ALIGN; ``` -这个对齐方式的枚举类型是设计过的, 你可以直接看看你数字小键盘, 其对齐方式和数字小键盘的数值是对应关系。 +这个对齐方式的枚举类型是设计过的,你可以直接看看你数字小键盘,其对齐方式和数字小键盘的数值是对应关系。 -而 `颜色格式` 则是一个被定义为 `px_color` 的结构体, 这个结构体有 4 个字节, 内部有 4 个成员变量 a、r、g、b,分别代表颜色的透明度、红色通道、绿色通道和蓝色通道,每一个分量的取值范围都是 0-255,例如红色,这个数值越大,这个颜色就越红。 +而 `颜色格式` 则是一个被定义为 `px_color` 的结构体,这个结构体有 4 个字节,内部有 4 个成员变量 a、r、g、b,分别代表颜色的透明度、红色通道、绿色通道和蓝色通道,每一个分量的取值范围都是 0-255,例如红色,这个数值越大,这个颜色就越红。 -因此,你可以看到,在上面的示范代码中,我们绘制了一个红色的文本 `Hello PainterEngine`。现在让我们试试中文, 修改上面的代码, 改为下面这种格式: +因此,你可以看到,在上面的示范代码中,我们绘制了一个红色的文本 `Hello PainterEngine`。现在让我们试试中文,修改上面的代码,改为下面这种格式: ```c #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); -//PainterEngine_DrawText -//参数1:x坐标 -//参数2:y坐标 -//参数3:文本内容 -//参数4:对齐方式 -//参数5:颜色 + PainterEngine_Initialize(800, 480); + // PainterEngine_DrawText + // 参数1:x坐标 + // 参数2:y坐标 + // 参数3:文本内容 + // 参数4:对齐方式 + // 参数5:颜色 PainterEngine_DrawText(400, 240, "你好PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); return 1; } @@ -185,14 +186,14 @@ int main() ![](assets/img/3.2.png) -但是, 中文却不能正确显示, 这是因为 PainterEngine 中, 默认只有英文的字模, 如果我们想要支持中文怎么办呢? -这仍然不困难, 为此, 我们需要先准备一个 ttf 字模文件, 例如在这里, 我准备了一个幼圆字体, 那么, 我只需要将这个字体加载进来就可以了: +但是,中文却不能正确显示,这是因为 PainterEngine 中,默认只有英文的字模,如果我们想要支持中文怎么办呢? +这仍然不困难,为此,我们需要先准备一个 TTF 字模文件,例如在这里,我准备了一个幼圆字体,那么,我只需要将这个字体加载进来就可以了: ```c #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); + PainterEngine_Initialize(800, 480); PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 24); PainterEngine_DrawText(400, 240, "你好 PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); return 1; @@ -201,7 +202,7 @@ int main() ![](assets/img/3.3.png) -`PainterEngine_LoadFontModule` 的函数的第一个参数是 TTF 字体文件的路径, 相对路径是以 exe 文件所在路径相对的。第二个参数是字符集,在默认情况下, Visual Studio 代码使用的是 GBK 字符集。如果你使用 Visual Studio Code, 那么默认是 UTF8 编码, 第二个参数应该换成 `PX_FONTMODULE_CODEPAGE_GBK`。最后一个参数是字体的大小。 +`PainterEngine_LoadFontModule` 的函数的第一个参数是 TTF 字体文件的路径,相对路径是以 exe 文件所在路径相对的。第二个参数是字符集,在默认情况下,Visual Studio 代码使用的是 GBK 字符集。如果你使用 Visual Studio Code,那么默认是 UTF8 编码,第二个参数应该换成 `PX_FONTMODULE_CODEPAGE_GBK`。最后一个参数是字体的大小。 ## 4. 使用 PainterEngine 绘制几何图形 @@ -209,10 +210,10 @@ int main() `px_void PainterEngine_DrawLine(px_int x1, px_int y1, px_int x2, px_int y2, px_int linewidth, px_color color);` 这个函数用于绘制一条线段。 -* x1, y1: 线段的起点坐标。 -* x2, y2: 线段的终点坐标。 -* linewidth: 线段的宽度。 -* color: 线段的颜色。 +* x1, y1:线段的起点坐标。 +* x2, y2:线段的终点坐标。 +* linewidth:线段的宽度。 +* color:线段的颜色。 ```c #include "PainterEngine.h" @@ -240,10 +241,10 @@ int main() `px_void PainterEngine_DrawRect(px_int x, px_int y, px_int width, px_int height, px_color color);` 这个函数用于绘制一个矩形。 -* x, y: 矩形的左上角坐标。 -* width: 矩形的宽度。 -* height: 矩形的高度。 -* color: 矩形的颜色。 +* x, y:矩形的左上角坐标。 +* width:矩形的宽度。 +* height:矩形的高度。 +* color:矩形的颜色。 ![](assets/img/3.5.png) @@ -272,10 +273,10 @@ int main() `px_void PainterEngine_DrawCircle(px_int x, px_int y, px_int radius, px_int linewidth, px_color color);` 这个函数用于绘制一个圆环。 -* x, y: 圆心的坐标。 -* radius: 圆的半径。 -* linewidth: 圆的线宽。 -* color: 圆的颜色。 +* x, y:圆心的坐标。 +* radius:圆的半径。 +* linewidth:圆的线宽。 +* color:圆的颜色。 ```c #include "PainterEngine.h" @@ -302,9 +303,9 @@ int main() `px_void PainterEngine_DrawSolidCircle(px_int x, px_int y, px_int radius, px_color color);` 这个函数用于绘制一个实心圆。 -* x, y: 圆心的坐标。 -* radius: 圆的半径。 -* color: 圆的颜色。 +* x, y:圆心的坐标。 +* radius:圆的半径。 +* color:圆的颜色。 ```c #include "PainterEngine.h" @@ -334,12 +335,12 @@ int main() `px_void PainterEngine_DrawSector(px_int x, px_int y, px_int inside_radius,px_int outside_radius, px_int start_angle, px_int end_angle, px_color color);` 这个函数用于绘制一个扇形。 参数说明: -* x, y: 扇形的圆心坐标。 -* inside_radius: 扇形的内半径。 -* inside_radius: 扇形的外半径。 -* start_angle: 扇形的起始角度(以度为单位, 支持负角度)。 -* end_angle: 扇形的结束角度(以度为单位, 支持负角度)。 -* color: 扇形的颜色。 +* x, y:扇形的圆心坐标。 +* inside_radius:扇形的内半径。 +* inside_radius:扇形的外半径。 +* start_angle:扇形的起始角度(以度为单位,支持负角度)。 +* end_angle:扇形的结束角度(以度为单位,支持负角度)。 +* color:扇形的颜色。 ```c #include "PainterEngine.h" @@ -367,18 +368,18 @@ int main() `px_void PainterEngine_DrawPixel(px_int x, px_int y, px_color color);` 这个函数用于绘制一个像素点。 -* x, y: 像素点的坐标。 -* color: 像素点的颜色。 +* x, y:像素点的坐标。 +* color:像素点的颜色。 -这只是绘制一个像素点, 就不放示例图了。 +这只是绘制一个像素点,就不放示例图了。 ## 5. 使用 PainterEngine 绘制图像 -使用 PainterEngine 绘制图像仍然很简单, 但在绘制图像之前, 我们需要先加载图片。 +使用 PainterEngine 绘制图像仍然很简单,但在绘制图像之前,我们需要先加载图片。 PainterEngine 可以直接从文件中加载图片,它原生支持 PNG、JPG、BMP、TRAW 四种静态图片格式的加载,为了存储加载的图片,我们需要用到一个叫纹理的结构体。 -在 PainterEngine 中,纹理用 `px_texture` 结构体进行描述,因此为了加载纹理,我们需要 `PX_LoadTextureFromFile` 函数, 这个函数是一个三参数的图片文件加载函数。第一个参数是内存池, 在后面的章节, 我将会更详细地讲解 PainterEngine 内存池的内容。在默认情况下, PainterEngine 提供 2 个默认内存池, 一个是 `mp`,一个是 `mp_static`, 其中, 前面的内存池一般用于需要频繁分配释放的元素, 后面的则用于静态资源的存储。在这里因为图片一般是静态资源, 因此填写 `mp_static` 就可以了。第二个参数则是我们纹理结构体的指针,在图片成功加载后,将会初始化这个结构体,并用于保存图片数据。最后一个参数,则是图片文件的所在路径。 +在 PainterEngine 中,纹理用 `px_texture` 结构体进行描述,因此为了加载纹理,我们需要 `PX_LoadTextureFromFile` 函数,这个函数是一个三参数的图片文件加载函数。第一个参数是内存池,在后面的章节,我将会更详细地讲解 PainterEngine 内存池的内容。在默认情况下,PainterEngine 提供 2 个默认内存池,一个是 `mp`,一个是 `mp_static`。其中,前面的内存池一般用于需要频繁分配释放的元素,后面的则用于静态资源的存储。在这里因为图片一般是静态资源,因此填写 `mp_static` 就可以了。第二个参数则是我们纹理结构体的指针,在图片成功加载后,将会初始化这个结构体,并用于保存图片数据。最后一个参数,则是图片文件的所在路径。 在加载文件成功后,我们使用 `PainterEngine_DrawTexture` 函数绘制出来。这是一个四参数的函数: @@ -390,13 +391,13 @@ PainterEngine 可以直接从文件中加载图片,它原生支持 PNG、JPG ```c #include "PainterEngine.h" -px_texture mytexture;//纹理 +px_texture mytexture; // 纹理 int main() { PainterEngine_Initialize(512, 512); if(!PX_LoadTextureFromFile(mp_static,&mytexture,"assets/demo.png")) { - //加载纹理失败 + // 加载纹理失败 return 0; } PainterEngine_DrawTexture(&mytexture, 0, 0, PX_ALIGN_LEFTTOP); @@ -410,7 +411,7 @@ int main() ## 6. PainterEngine 内存池管理机制 -因为 PainterEngine 是无系统及标准库依赖的, 因此在 PainterEngine 中, 必须独立于系统及标准库的内存管理机制, 实现 PainterEngine 内部的内存管理系统。因此 PainterEngine 使用了内存池作为动态的内存管理系统。 +因为 PainterEngine 是无系统及标准库依赖的,因此在 PainterEngine 中,必须独立于系统及标准库的内存管理机制,实现 PainterEngine 内部的内存管理系统。因此 PainterEngine 使用了内存池作为动态的内存管理系统。 PainterEngine 内存池实现方式同样很简洁,为了使用内存,你必须预先准备一段可用的内存空间,作为内存池管理的内存空间,例如在下面的代码中,我们可以用 C 语言定义一个较大的全局数组,然后用这个数组空间作为内存池的分配空间: @@ -421,13 +422,13 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//创建内存池 - myalloc=MP_Malloc(&mp, 1024);//在内存池中分配1024字节 + mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + myalloc=MP_Malloc(&mp, 1024); // 在内存池中分配1024字节 return 1; } ``` -需要注意的是, **_使用内存池分配的空间, 会略小于分配给内存池的空间。如果你分配的空间超出了内存池, 将会导致一个停机错误。_** +需要注意的是,**_使用内存池分配的空间,会略小于分配给内存池的空间。如果你分配的空间超出了内存池,将会导致一个停机错误。_** ```c #include "PainterEngine.h" @@ -436,15 +437,15 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//创建内存池 - myalloc=MP_Malloc(&mp, 1024*1024);//在内存池中分配1024*1024字节,但内存池实际容量小于分配给内存池容量,因此这里内存不足,这里将会进入中断 + mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + myalloc=MP_Malloc(&mp, 1024*1024); // 在内存池中分配1024*1024字节,但内存池实际容量小于分配给内存池容量,因此这里内存不足,这里将会进入中断 return 1; } ``` -如果你不希望因为内存池不足导致停机错误, 你可以使用以下两种方式: +如果你不希望因为内存池不足导致停机错误,你可以使用以下两种方式: -1.你可以设置错误回调, 自行处理内存池的错误: +1.你可以设置错误回调,自行处理内存池的错误: ```c #include "PainterEngine.h" @@ -471,14 +472,14 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//创建内存池 - MP_ErrorCatch(&mp, my_memory_cache_error,0);//设置错误回调 - myalloc=MP_Malloc(&mp, 1024*1024);//在内存池中分配1024*1024字节 + mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + MP_ErrorCatch(&mp, my_memory_cache_error,0); // 设置错误回调 + myalloc=MP_Malloc(&mp, 1024*1024); // 在内存池中分配1024*1024字节 return 1; } ``` -2.或者你也可以直接关闭内存池的错误异常处理, 那么当内存池无法正常分配足够内存时, 将会直接返回 `NULL`: +2.或者你也可以直接关闭内存池的错误异常处理,那么当内存池无法正常分配足够内存时,将会直接返回 `NULL`: ```c #include "PainterEngine.h" @@ -488,14 +489,14 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//创建内存池 - MP_NoCatchError(&mp, PX_TRUE);//设置内存池不捕获错误 - myalloc=MP_Malloc(&mp, 1024*1024);//在内存池中分配1024*1024字节,但内存池不捕获错误,所以会直接返回NULL + mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + MP_NoCatchError(&mp, PX_TRUE); // 设置内存池不捕获错误 + myalloc=MP_Malloc(&mp, 1024*1024); // 在内存池中分配1024*1024字节,但内存池不捕获错误,所以会直接返回NULL return 1; } ``` -在 PainterEngine 中, 有 2 个系统默认的内存池, 它们分别是 `mp` 和 `mp_static`, 你可以打开 `PainterEngine_Application.h` 文件, 在当中找到这两个内存池的定义, 但我们最需要关注的, 还是下面的代码: +在 PainterEngine 中,有 2 个系统默认的内存池,它们分别是 `mp` 和 `mp_static`,你可以打开 `PainterEngine_Application.h` 文件,在当中找到这两个内存池的定义,但我们最需要关注的,还是下面的代码: ```c #define PX_APPLICATION_NAME "PainterEngine" @@ -504,16 +505,16 @@ int main() #define PX_APPLICATION_MEMORYPOOL_SPACE_SIZE (1024*1024*16) ``` -这是这两个内存池的直接相关配置宏, 其中 `PX_APPLICATION_MEMORYPOOL_STATIC_SIZE` 表示 `mp_static` 内存池的内存分配大小, 而 `PX_APPLICATION_MEMORYPOOL_DYNAMIC_SIZE` 则是 `mp` 内存池的内存分配大小, `PX_APPLICATION_MEMORYPOOL_SPACE_SIZE` 则是系统其它资源。PainterEngine 程序运行开始, 就至少会占用这三个宏累加起来的内存, 之后的内存分配, 都围绕在这几个内存池中进行。如果你发现 PainterEngine 运行的内存不够了, 你可以自己手动拓展内存池的大小。当然, 如果你希望节约点内存, 你也可以手动将它们改小。 +这是这两个内存池的直接相关配置宏,其中 `PX_APPLICATION_MEMORYPOOL_STATIC_SIZE` 表示 `mp_static` 内存池的内存分配大小,而 `PX_APPLICATION_MEMORYPOOL_DYNAMIC_SIZE` 则是 `mp` 内存池的内存分配大小,`PX_APPLICATION_MEMORYPOOL_SPACE_SIZE` 则是系统其它资源。PainterEngine 程序运行开始,就至少会占用这三个宏累加起来的内存,之后的内存分配都围绕在这几个内存池中进行。如果你发现 PainterEngine 运行的内存不够了,你可以自己手动拓展内存池的大小。当然,如果你希望节约点内存,你也可以手动将它们改小。 ## 7. 使用 PainterEngine 创建 GUI 按钮 -在本章节中, 我们将第一次接触 PainterEngine 的组件。现在, 我们将使用 PainterEngine 创建一个经典 GUI 组件——按钮。 +在本章节中,我们将第一次接触 PainterEngine 的组件。现在,我们将使用 PainterEngine 创建一个经典 GUI 组件——按钮。 -在 PainterEngine 中, 所有的组件都是由 `PX_Object` 结构体进行描述的, 创建组件返回的都是一个 `PX_Object *` 类型的指针。 +在 PainterEngine 中,所有的组件都是由 `PX_Object` 结构体进行描述的,创建组件返回的都是一个 `PX_Object *` 类型的指针。 -但在本章节中, 我们并不需要考虑的那么复杂, 我们只需要创建一个按钮出来即可。在 PainterEngine 中, 最常用的按钮是 `PX_Object_PushButton` 类型。 +但在本章节中,我们并不需要考虑的那么复杂,我们只需要创建一个按钮出来即可。在 PainterEngine 中,最常用的按钮是 `PX_Object_PushButton` 类型。 ```c #include "PainterEngine.h" @@ -528,9 +529,9 @@ int main() ``` ![](assets/img/7.1.gif) -现在, 我们来详细看看 `PX_Object_PushButtonCreate` 函数。其中, 第一个参数是一个内存池, 在之前我们说过 PainterEngine 有 2 个系统默认的内存池,其实这里填 `mp` 或者 `mp_static` 都是没有问题的, 但考虑到界面可能会变动设计对象分配与销毁, 所以我们还是选择 `mp` 内存池。 +现在,我们来详细看看 `PX_Object_PushButtonCreate` 函数。其中,第一个参数是一个内存池,在之前我们说过 PainterEngine 有 2 个系统默认的内存池,其实这里填 `mp` 或者 `mp_static` 都是没有问题的,但考虑到界面可能会变动设计对象分配与销毁,所以我们还是选择 `mp` 内存池。 -第二个参数 `root` 是 PainterEngine 的根对象, PainterEngine 对象管理机制我们将在之后讨论。在这里, 你只需要理解为, 这里填 `root` 的意思是 **_创建一个按钮对象作为根对象的子对象_**。这样按钮就能链接到系统对象树中, 进行事件响应和渲染。 +第二个参数 `root` 是 PainterEngine 的根对象,PainterEngine 对象管理机制我们将在之后讨论。在这里,你只需要理解为,这里填 `root` 的意思是 **_创建一个按钮对象作为根对象的子对象_**。这样按钮就能链接到系统对象树中,进行事件响应和渲染。 然后是按钮的 x,y,width,height,也就是位置和宽度高度等信息。 @@ -538,29 +539,29 @@ int main() ## 8. PainterEngine 对象传递机制 -在上一个章节中,我们初略了解了根对象 `root`, 那么在本章节, 我们将学习一下 PainterEngine 的对象管理机制。 +在上一个章节中,我们初略了解了根对象 `root`,那么在本章节,我们将学习一下 PainterEngine 的对象管理机制。 -正如我们之前所说的,在 PainterEngine 中, 所有的组件都是由 `PX_Object` 结构体进行描述的, PainterEngine 的对象是以树的形式存在的: +正如我们之前所说的,在 PainterEngine 中,所有的组件都是由 `PX_Object` 结构体进行描述的,PainterEngine 的对象是以树的形式存在的: ![](assets/img/8.1.png) -每一个 `PX_Object` 都是这个树中的一个节点, 都可以有自己的子节点(可能多个)和自己的父节点(只能有一个)。同时, 每一个 `PX_Object` 都有以下四个基本功能函数: +每一个 `PX_Object` 都是这个树中的一个节点,都可以有自己的子节点(可能多个)和自己的父节点(只能有一个)。同时,每一个 `PX_Object` 都有以下四个基本功能函数: -`Create`:对象创建函数,或者说是对象初始化函数, 在 PainterEngine 中它一般是 `PX_Object_xxxxxCreate` 这种形式的, 其中 `xxxxx` 就是这个对象的名称, 比如上一章节的 `PushButton`, `Create` 函数一般是对象的一些初始化处理,并会将自己连接到对象树中。 +`Create`:对象创建函数,或者说是对象初始化函数,在 PainterEngine 中它一般是 `PX_Object_xxxxxCreate` 这种形式的,其中 `xxxxx` 就是这个对象的名称,比如上一章节的 `PushButton`,`Create` 函数一般是对象的一些初始化处理,并会将自己连接到对象树中。 -`Update`:对象的物理信息更新工作基本在这个函数中完成,一般会处理对象的一些物理信息, 比如位置大小速度等,常见于游戏设计中的物体,在 GUI 对象中则比较少见,其设计是与之后的 `Render` 也就是绘制函数进行区分, 因为在例如游戏服务端中, 对象并不需要进行绘制, 且绘制是非常消耗性能的。 +`Update`:对象的物理信息更新工作基本在这个函数中完成,一般会处理对象的一些物理信息,比如位置大小速度等,常见于游戏设计中的物体,在 GUI 对象中则比较少见,其设计是与之后的 `Render` 也就是绘制函数进行区分,因为在例如游戏服务端中,对象并不需要进行绘制,且绘制是非常消耗性能的。 -`Render`: 对象的绘制工作基本在这个函数中完成, 用于 `PX_Object` 的绘制功能, 将图像数据渲染到屏幕上, 当然有些情况下物理信息也会在这个函数中做, 是因为这个对象的物理信息并不会影响游戏的实际运行结果, 例如一些特效和粒子效果, 多数的 GUI 组件也几乎只用得到 `Render` 函数。 +`Render`:对象的绘制工作基本在这个函数中完成,用于 `PX_Object` 的绘制功能,将图像数据渲染到屏幕上,当然有些情况下物理信息也会在这个函数中做,是因为这个对象的物理信息并不会影响游戏的实际运行结果,例如一些特效和粒子效果,多数的 GUI 组件也几乎只用得到 `Render` 函数。 `Free`:对象的释放工作基本在这个函数中完成,例如在 `Create` 中加载了纹理,或者申请了内存,在这个函数中应该被释放。 以上 `Update`、`Render`、`Free` 函数具有传递的特性,也就是说: -* 如果某个对象节点执行了 `Update`, 那么它的所有子对象也会执行 `Update` -* 如果某个对象节点执行了 `Render`, 那么它的所有子对象也会执行 `Render` -* 如果某个对象节点执行了 `Free`, 那么它的所有子对象也会执行 `Free`, 父对象被删除了, 它的子节点也会被删除, 并且将会一直迭代到以这个节点为根节点的所有子节点都被删除。 +* 如果某个对象节点执行了 `Update`,那么它的所有子对象也会执行 `Update` +* 如果某个对象节点执行了 `Render`,那么它的所有子对象也会执行 `Render` +* 如果某个对象节点执行了 `Free`,那么它的所有子对象也会执行 `Free`,父对象被删除了,它的子节点也会被删除,并且将会一直迭代到以这个节点为根节点的所有子节点都被删除。 -因此,在上一章节我们创建了按钮,并将它连接到了 `root` 节点, 那么我们是不需要自己再手动执行 `Update`、`Render`、`Free` 函数的(在 `PX_Object_PushButton.c` 中它们已经被写好了), 因为根节点 `root` 是被自动更新渲染和释放的, 我们只需要负责 `Create` 就可以了。 +因此,在上一章节我们创建了按钮,并将它连接到了 `root` 节点,那么我们是不需要自己再手动执行 `Update`、`Render`、`Free` 函数的(在 `PX_Object_PushButton.c` 中它们已经被写好了),因为根节点 `root` 是被自动更新渲染和释放的,我们只需要负责 `Create` 就可以了。 当然,如果你希望删除这个对象的话,你只需要调用 `PX_ObjectDelayDelete` 或者 `PX_ObjectDelete` 就可以了: @@ -572,12 +573,12 @@ int main() PainterEngine_Initialize(800, 480); PainterEngine_LoadFontModule("assets/font.ttf",PX_FONTMODULE_CODEPAGE_GBK,20); myButtonObject=PX_Object_PushButtonCreate(mp,root,300,200,200,80,"我是一个按钮", PainterEngine_GetFontModule()); - PX_ObjectDelayDelete(myButtonObject);//删除对象 + PX_ObjectDelayDelete(myButtonObject); // 删除对象 return 1; } ``` -这两个函数的功能和参数都是一样的, 但是 `PX_ObjectDelayDelete` 会在更新和渲染完成后才执行删除, `PX_ObjectDelete` 则是立即删除,我建议使用 `PX_ObjectDelayDelete`,这样你就可以避免在某些情况下因为对象被立即删除了,而其它对象仍然引用了这个对象的数据,这会导致其访问失效内存。 +这两个函数的功能和参数都是一样的,但是 `PX_ObjectDelayDelete` 会在更新和渲染完成后才执行删除,`PX_ObjectDelete` 则是立即删除,我建议使用 `PX_ObjectDelayDelete`,这样你就可以避免在某些情况下因为对象被立即删除了,而其它对象仍然引用了这个对象的数据,这会导致其访问失效内存。 ## 9. PainterEngine 消息机制 @@ -605,15 +606,15 @@ int main() ![](assets/img/9.1.gif) -其中, `PX_OBJECT_EVENT_FUNCTION` 是一个宏, 因为事件响应函数是一个固定的格式, 因此非常建议你使用宏的方式来申明它, 它的定义原型如下: +其中,`PX_OBJECT_EVENT_FUNCTION` 是一个宏,因为事件响应函数是一个固定的格式,因此非常建议你使用宏的方式来申明它,它的定义原型如下: ```c #define PX_OBJECT_EVENT_FUNCTION(name) px_void name(PX_Object *pObject,PX_Object_Event e,px_void * ptr) ``` -可以看到, 这个回调函数有 3 个参数, 第一个是响应时间的对象的指针, 因为是按钮点击被触发了, 所以这个指针指向的就是这个按钮对象;第二个参数是事件类型 `e`,它是触发的事件类型;最后一个参数则是用户传递来的指针,它在注册时间响应函数 `PX_ObjectRegisterEvent` 被调用时就被传递进来了。 +可以看到,这个回调函数有 3 个参数,第一个是响应时间的对象的指针,因为是按钮点击被触发了,所以这个指针指向的就是这个按钮对象;第二个参数是事件类型 `e`,它是触发的事件类型;最后一个参数则是用户传递来的指针,它在注册时间响应函数 `PX_ObjectRegisterEvent` 被调用时就被传递进来了。 -事件类型有以下几种: +事件类型有以下几种: ```c #define PX_OBJECT_EVENT_ANY 0 //任意事件 @@ -627,7 +628,7 @@ int main() #define PX_OBJECT_EVENT_CURSORWHEEL 8 //鼠标滚轮 #define PX_OBJECT_EVENT_CURSORCLICK 9 //鼠标左键点击 #define PX_OBJECT_EVENT_CURSORDRAG 10 //鼠标拖拽 -#define PX_OBJECT_EVENT_STRING 11 //字符串事件(输入法输入) +#define PX_OBJECT_EVENT_STRING 11 //字符串事件(输入法输入) #define PX_OBJECT_EVENT_EXECUTE 12 //执行事件,不同组件有不同的执行方式 #define PX_OBJECT_EVENT_VALUECHANGED 13 //值改变事件,例如滑动条的值改变,或者文本框的值改变,或者列表框的选中项改变 #define PX_OBJECT_EVENT_DRAGFILE 14 //拖拽文件 @@ -649,34 +650,34 @@ int main() #define PX_OBJECT_EVENT_DAMAGE 30 //伤害事件 ``` -以上事件并非全部都是任何组件都会响应的, 例如在上面例子中的 `PX_OBJECT_EVENT_EXECUTE`, 它是按钮被单击时会被触发的事件, 或者是文本框中按下回车会触发的事件, 但有些例如滚动条和进度条, 并不会触发这个事件。也就是说有些事件是专属的。 +以上事件并非全部都是任何组件都会响应的,例如在上面例子中的 `PX_OBJECT_EVENT_EXECUTE`,它是按钮被单击时会被触发的事件,或者是文本框中按下回车会触发的事件,但有些例如滚动条和进度条,并不会触发这个事件。也就是说有些事件是专属的。 -但是类似于带有 `CURSOR` 或 `KEY` 的事件,是所有连接在 `root` 节点的组件都会收到的事件(但不一定响应)。需要注意的是, 类似于鼠标或触摸屏的 `CURSOR` 事件, 并非只有鼠标或触摸屏移动到组件所在位置与范围时才会触发, 只要有这类事件投递到 `root` 节点, 他就会逐层传递给它的所有子节点。如果你希望实现类似于按钮中的 "仅在鼠标点击到按钮时" 才触发, 你必须自行实现范围判断。 +但是类似于带有 `CURSOR` 或 `KEY` 的事件,是所有连接在 `root` 节点的组件都会收到的事件(但不一定响应)。需要注意的是,类似于鼠标或触摸屏的 `CURSOR` 事件,并非只有鼠标或触摸屏移动到组件所在位置与范围时才会触发,只要有这类事件投递到 `root` 节点,他就会逐层传递给它的所有子节点。如果你希望实现类似于按钮中的 "仅在鼠标点击到按钮时" 才触发,你必须自行实现范围判断。 你可以使用 ```c -px_float PX_Object_Event_GetCursorX(PX_Object_Event e);//获取cursor事件的x坐标 -px_float PX_Object_Event_GetCursorY(PX_Object_Event e);//获取cursor事件的y坐标 -px_float PX_Object_Event_GetCursorZ(PX_Object_Event e);//获取cursor事件的z坐标,一般用于鼠标中键滚轮 +px_float PX_Object_Event_GetCursorX(PX_Object_Event e); // 获取cursor事件的x坐标 +px_float PX_Object_Event_GetCursorY(PX_Object_Event e); // 获取cursor事件的y坐标 +px_float PX_Object_Event_GetCursorZ(PX_Object_Event e); // 获取cursor事件的z坐标,一般用于鼠标中键滚轮 ``` 来获取 `cursor` 事件中类似于 "鼠标现在在哪里" 的功能。 -让我们回到源代码 `OnButtonClick` 中做的很简单, 就是用 `PX_Object_PushButtonSetText` 改变了按钮文本的内容。 +让我们回到源代码 `OnButtonClick` 中做的很简单,就是用 `PX_Object_PushButtonSetText` 改变了按钮文本的内容。 -最后让我们来到 `PX_ObjectRegisterEvent` 函数,这个函数用于将事件与 C 语言函数绑定在一起,第一个参数是我们之前创建好的按钮组件的指针,第二个参数是我们想要绑定的事件类型,这里的 `PX_OBJECT_EVENT_EXECUTE` 就是按钮被点击时触发的, 第三个则是用户指针, 它会被传递到回调函数中, 如果你用不到, 你可以直接填 `PX_NULL`。 +最后让我们来到 `PX_ObjectRegisterEvent` 函数,这个函数用于将事件与 C 语言函数绑定在一起,第一个参数是我们之前创建好的按钮组件的指针,第二个参数是我们想要绑定的事件类型,这里的 `PX_OBJECT_EVENT_EXECUTE` 就是按钮被点击时触发的,第三个则是用户指针,它会被传递到回调函数中,如果你用不到,你可以直接填 `PX_NULL`。 ## 10. 小例子,用 PainterEngine 实现一个电子相册 -现在,让我们用一个小例子来开启 PainterEngine 组件化开发的第一步。在本例程中, 我将使用按钮和图片框组件, 开发一个电子相册功能。本文中的美术资源, 你可以在 `documents/logo` 中找到。 +现在,让我们用一个小例子来开启 PainterEngine 组件化开发的第一步。在本例程中,我将使用按钮和图片框组件,开发一个电子相册功能。本文中的美术资源,你可以在 `documents/logo` 中找到。 ```c #include "PainterEngine.h" PX_Object* Previous, * Next, * Image; -px_texture my_texture[10];//存放图片的数组 -px_int index = 0;//当前图片的索引 +px_texture my_texture[10]; // 存放图片的数组 +px_int index = 0; // 当前图片的索引 PX_OBJECT_EVENT_FUNCTION(OnButtonPreClick) { @@ -685,7 +686,7 @@ PX_OBJECT_EVENT_FUNCTION(OnButtonPreClick) { index = 9; } - PX_Object_ImageSetTexture(Image, &my_texture[index]);//设置图片 + PX_Object_ImageSetTexture(Image, &my_texture[index]); // 设置图片 } PX_OBJECT_EVENT_FUNCTION(OnButtonNextClick) @@ -701,54 +702,54 @@ PX_OBJECT_EVENT_FUNCTION(OnButtonNextClick) int main() { px_int i; - PainterEngine_Initialize(512, 560);//初始化 + PainterEngine_Initialize(512, 560); // 初始化 for(i=0;i<10;i++) { px_char path[256]; PX_sprintf1(path,256, "assets/%1.png", PX_STRINGFORMAT_INT(i+1)); - if(!PX_LoadTextureFromFile(mp_static, &my_texture[i],path))//加载图片 + if(!PX_LoadTextureFromFile(mp_static, &my_texture[i],path)) // 加载图片 { - //加载失败 + // 加载失败 printf("加载失败"); return 0; } } - PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20);//加载字体 - Image = PX_Object_ImageCreate(mp, root, 0, 0, 512, 512, 0);//创建图片对象 - Previous= PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "上一张",PainterEngine_GetFontModule());//创建按钮对象 - Next = PX_Object_PushButtonCreate(mp, root, 256, 512, 256, 48, "下一张", PainterEngine_GetFontModule());//创建按钮对象 - PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL);//注册按钮事件 - PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL);//注册按钮事件 + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); // 加载字体 + Image = PX_Object_ImageCreate(mp, root, 0, 0, 512, 512, 0); // 创建图片对象 + Previous= PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "上一张",PainterEngine_GetFontModule()); // 创建按钮对象 + Next = PX_Object_PushButtonCreate(mp, root, 256, 512, 256, 48, "下一张", PainterEngine_GetFontModule()); // 创建按钮对象 + PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL); // 注册按钮事件 + PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL); // 注册按钮事件 return 1; } ``` -在上述代码中 `OnButtonPreClick` 和 `OnButtonNextClick` 分别是上一张和下一张按钮的回调函数, 我们使用 `PX_Object_ImageSetTexture` 函数, 对图片框进行切换。 +在上述代码中 `OnButtonPreClick` 和 `OnButtonNextClick` 分别是上一张和下一张按钮的回调函数,我们使用 `PX_Object_ImageSetTexture` 函数,对图片框进行切换。 -而在 `main` 函数中, 我们先加载了 ttf 字体, 然后用 `PX_Object_ImageCreate` 创建了一个图片组件, 之后我们创建了 2 个按钮, 并用 `PX_ObjectRegisterEvent` 绑定了事件回调函数。最后, 看看运行结果: +而在 `main` 函数中,我们先加载了 ttf 字体,然后用 `PX_Object_ImageCreate` 创建了一个图片组件,之后我们创建了 2 个按钮,并用 `PX_ObjectRegisterEvent` 绑定了事件回调函数。最后,看看运行结果: ![](assets/img/10.1.gif) ## 11. 更多常用的 PainterEngine 组件 -你可以在 `PainterEngine/kernel` 的文件中, 找到 PainterEngine 的内置组件, 所有的组件名称都是以 `PX_Object_XXXXX` 开头的, 在这里, 我为你列举一些常用的组件及示范代码: +你可以在 `PainterEngine/kernel` 的文件中,找到 PainterEngine 的内置组件,所有的组件名称都是以 `PX_Object_XXXXX` 开头的,在这里,我为你列举一些常用的组件及示范代码: -* 文本框: +* 文本框: ```c #include "PainterEngine.h" PX_OBJECT_EVENT_FUNCTION(PX_Object_EditOnTextChanged) { - //文本改变后后这里会被执行 + // 文本改变后后这里会被执行 } int main() { PX_Object* pObject; PainterEngine_Initialize(600, 400); - //创建文本框 + // 创建文本框 pObject=PX_Object_EditCreate(mp,root,200,180,200,40,0); - //注册编辑框文本改变事件 + // 注册编辑框文本改变事件 PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_VALUECHANGED, PX_Object_EditOnTextChanged,PX_NULL); return 0; } @@ -766,21 +767,21 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_OnMyListItemRender) px_float objx,objy,objWidth,objHeight; PX_Object_ListItem *pItem=PX_Object_GetListItem(pObject); PX_OBJECT_INHERIT_CODE(pObject,objx, objy, objWidth, objHeight); - //绘制出其文本 + // 绘制出其文本 PX_FontModuleDrawText(psurface, 0, (px_int)objx + 3, (px_int)objy + 3, PX_ALIGN_LEFTTOP, (const px_char *)pItem->pdata, PX_COLOR_WHITE); } PX_OBJECT_LIST_ITEM_CREATE_FUNCTION(PX_Object_OnMyListItemCreate) { - //绑定ListItem的渲染函数 + // 绑定ListItem的渲染函数 ItemObject->Func_ObjectRender[0]=PX_Object_OnMyListItemRender; return PX_TRUE; } PX_OBJECT_EVENT_FUNCTION(PX_Object_ListOnSelectChanged) { - //当选中项改变时 + // 当选中项改变时 return; } @@ -789,7 +790,7 @@ int main() PX_Object* pObject; PainterEngine_Initialize(600, 400); - //创建list + // 创建list pObject = PX_Object_ListCreate(mp,root,100,100,400,200,24,PX_Object_OnMyListItemCreate,0); PX_Object_ListAdd(pObject, "Item1"); PX_Object_ListAdd(pObject, "Item2"); @@ -810,7 +811,7 @@ int main() PX_OBJECT_EVENT_FUNCTION(SliderChanged) { - //垂直滑动条值改变后执行这里的代码 + // 垂直滑动条值改变后执行这里的代码 return; } @@ -818,9 +819,9 @@ int main() { PX_Object* pObject; PainterEngine_Initialize(600, 400); - //水平滑动条 + // 水平滑动条 PX_Object_SliderBarCreate(mp, root, 200, 50, 200,24,PX_OBJECT_SLIDERBAR_TYPE_HORIZONTAL,PX_OBJECT_SLIDERBAR_STYLE_BOX); - //垂直滑动条 + // 垂直滑动条 pObject=PX_Object_SliderBarCreate(mp, root, 200, 100, 24, 200, PX_OBJECT_SLIDERBAR_TYPE_VERTICAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, SliderChanged, 0); return 0; @@ -853,7 +854,7 @@ int main() ```c #include "PainterEngine.h" -//必须是生存域内有效可访问的数据,这里定义为全局变量 +// 必须是生存域内有效可访问的数据,这里定义为全局变量 px_double data_x[100]; px_double data_y[100]; @@ -865,7 +866,7 @@ int main() px_int i; PainterEngine_Initialize(600, 600); - //初始化一个测试数据 + // 初始化一个测试数据 for (i = 0; i < 100; i++) { data_x[i] = i; @@ -874,26 +875,26 @@ int main() pObject = PX_Object_OscilloscopeCreate(mp, root, 0, 0, 600, 600, 0); - //设置水平坐标最小值最大值 + // 设置水平坐标最小值最大值 PX_Object_OscilloscopeSetHorizontalMin(pObject, 0); PX_Object_OscilloscopeSetHorizontalMax(pObject, 100); - //设置垂直坐标(左边)最小值最大值0-100 + // 设置垂直坐标(左边)最小值最大值0-100 PX_Object_OscilloscopeSetLeftVerticalMin(pObject, 0); PX_Object_OscilloscopeSetLeftVerticalMax(pObject, 100); - //数据类型 - data.Color=PX_COLOR(255,192,255,128);//数据颜色 + // 数据类型 + data.Color=PX_COLOR(255,192,255,128); // 数据颜色 data.ID = 0; - data.linewidth = 3;//数据线宽 - data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT;//数据映射到左边垂直坐标 - data.MapHorizontalArray = data_x;//数据水平坐标 - data.MapVerticalArray = data_y;//数据垂直坐标 - data.Size = 100;//数据大小 - data.Visibled = PX_TRUE;//数据可见 - data.Normalization = 1;//数据归一化系数为1 + data.linewidth = 3; // 数据线宽 + data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT; // 数据映射到左边垂直坐标 + data.MapHorizontalArray = data_x; // 数据水平坐标 + data.MapVerticalArray = data_y; // 数据垂直坐标 + data.Size = 100; // 数据大小 + data.Visibled = PX_TRUE; // 数据可见 + data.Normalization = 1; // 数据归一化系数为1 - //添加数据 + // 添加数据 PX_Object_OscilloscopeAddData(pObject, data); return 0; } @@ -901,7 +902,7 @@ int main() ![](assets/img/11.5.gif) -因为实在太多了, 我无法为你列举所有的组件, 如果你希望知道某个组件的具体用法和某个组件到底是做什么的, 你可以访问 PainterEngine 的 [组件市场](https://market.painterengine.com/), 在那里你可以找到 PainterEngine 内置组件和三方组件的说明和示例代码。 +因为实在太多了,我无法为你列举所有的组件,如果你希望知道某个组件的具体用法和某个组件到底是做什么的,你可以访问 PainterEngine 的 [组件市场](https://market.painterengine.com/),在那里你可以找到 PainterEngine 内置组件和三方组件的说明和示例代码。 ![](assets/img/11.6.png) @@ -909,11 +910,11 @@ int main() PainterEngine 鼓励组件式的开发架构。也就是说,不论是游戏还是 GUI 交互程序,甚至是程序功能,我们都可以用组件的形式去开发它。 -组件式开发有点类似于 C++中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数, 你可以参考 [前面的对象传递机制](#8painterengine-对象传递机制) 这一章节。 +组件式开发有点类似于 C++中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数,你可以参考 [前面的对象传递机制](#8painterengine-对象传递机制) 这一章节。 为了演示这一点,让我们来实现一个“可控拖动旋转图片组件”,即我们可以用鼠标拖动图片在界面的位置,并用鼠标中键来旋转它。 -为了实现这一个功能, 让我们一步一步完成这个步骤。首先, 为了创建一个组件, 我们需要一个结构体来描述我们的组件。我们需要绘制图片, 所以我们需要一个 `px_texture` 类型。同时, 我们还需要旋转图片, 因此它还有一个 `rotation` 用于描述旋转的角度: +为了实现这一个功能,让我们一步一步完成这个步骤。首先,为了创建一个组件,我们需要一个结构体来描述我们的组件。我们需要绘制图片,所以我们需要一个 `px_texture` 类型。同时,我们还需要旋转图片,因此它还有一个 `rotation` 用于描述旋转的角度: ```c #include "PainterEngine.h" @@ -930,7 +931,7 @@ px_int main() } ``` -之后, 我们需要定义我们的 `Create`、`Update`、`Render` 和 `Free` 函数, 其中 `Update`、`Render`、`Free` 有对应的格式, 它们都有一个宏来简化我们的定义过程: +之后,我们需要定义我们的 `Create`、`Update`、`Render` 和 `Free` 函数,其中 `Update`、`Render`、`Free` 有对应的格式,它们都有一个宏来简化我们的定义过程: ```c #define PX_OBJECT_RENDER_FUNCTION(name) px_void name(px_surface *psurface,PX_Object *pObject,px_int idesc,px_dword elapsed) @@ -938,7 +939,7 @@ px_int main() #define PX_OBJECT_FREE_FUNCTION(name) px_void name(PX_Object *pObject,px_int idesc) ``` -那么, 在主函数中, 我们就可以这样定义我们的这几个函数: +那么,在主函数中,我们就可以这样定义我们的这几个函数: ```c #include "PainterEngine.h" @@ -971,7 +972,7 @@ px_int main() } ``` -其中, 因为我们不需要更新一些物理信息, 所以 `MyObjectUpdate` 函数中我们可以什么都不写, 在 `MyObjectRender` 中我们只需要把图片绘制出来就可以了, 这里我们先使用 `PX_ObjectGetDesc` 函数获得我们定义好的结构体指针, 它的第一个参数是结构体类型, 第二个参数则是函数传递进来的 `pObject` 指针, 然后我们只需要用 `PX_TextureRenderEx` 函数把图片绘制出来就可以了。 +其中,因为我们不需要更新一些物理信息,所以 `MyObjectUpdate` 函数中我们可以什么都不写,在 `MyObjectRender` 中我们只需要把图片绘制出来就可以了,这里我们先使用 `PX_ObjectGetDesc` 函数获得我们定义好的结构体指针,它的第一个参数是结构体类型,第二个参数则是函数传递进来的 `pObject` 指针,然后我们只需要用 `PX_TextureRenderEx` 函数把图片绘制出来就可以了。 多提一句,`PX_TextureRenderEx` 函数用于在指定的表面上渲染纹理,并提供了对齐、混合、缩放和旋转等扩展选项。其中: * `psurface`:指向要渲染纹理的表面的指针。 @@ -983,26 +984,26 @@ px_int main() * `scale`:纹理的缩放因子(1.0 表示不缩放)。 * `Angle`:纹理的旋转角度,以度为单位。 -最后, 是时候编写创建新对象的函数了, 这里我们需要用到 `PX_ObjectCreateEx` 函数, `PX_ObjectCreateEx` 函数用于创建一个扩展对象,并初始化其属性和回调函数。它的参数说明如下: +最后,是时候编写创建新对象的函数了,这里我们需要用到 `PX_ObjectCreateEx` 函数,`PX_ObjectCreateEx` 函数用于创建一个扩展对象,并初始化其属性和回调函数。它的参数说明如下: * `mp`:指向内存池的指针,用于分配对象所需的内存。 * `Parent`:指向父对象的指针,如果没有父对象则为 `NULL`。 * `x`:对象在 x 轴上的初始位置。 * `y`:对象在 y 轴上的初始位置。 -* `z`:对象在 z 轴上的初始位置, z 坐标会影响其渲染的先后顺序。 +* `z`:对象在 z 轴上的初始位置,z 坐标会影响其渲染的先后顺序。 * `Width`:对象的宽度。 * `Height`:对象的高度。 -* `Lenght`:对象的长度,2D 对象, 一般可以是 0。 +* `Lenght`:对象的长度,2D 对象,一般可以是 0。 * `type`:对象的类型。 * `Func_ObjectUpdate`:指向对象更新函数的指针。 * `Func_ObjectRender`:指向对象渲染函数的指针。 * `Func_ObjectFree`:指向对象释放函数的指针。 -* `desc`:指向对象描述数据的指针。你可以设置为 0, 创建时会把这个对象类型的数据填充为 0。 -* `size`:描述数据的大小, 就是你定义的对象结构体类型的大小,创建对象函数会在内存池申请一段内存空间,并用于存储你的对象结构体。 +* `desc`:指向对象描述数据的指针。你可以设置为 0,创建时会把这个对象类型的数据填充为 0。 +* `size`:描述数据的大小,就是你定义的对象结构体类型的大小,创建对象函数会在内存池申请一段内存空间,并用于存储你的对象结构体。 -在创建好一个空对象后, 我们使用 `PX_ObjectGetDescIndex` 将对象中的对象结构体指针取出来, 这是一个三参数的函数, 第一个参数是对象结构体类型, 第二个参数则是 `PX_Object *` 指针类型, 因为一个 `PX_Object` 可以将多个对象结构体组合在一起, 这个组合结构体我们将在之后的教程中会进一步描述, 但现在我们只需要知道, 调用 `PX_ObjectCreateEx` 函数后, 其第一个存储的对象结构体索引是 0 就可以了。 +在创建好一个空对象后,我们使用 `PX_ObjectGetDescIndex` 将对象中的对象结构体指针取出来,这是一个三参数的函数,第一个参数是对象结构体类型,第二个参数则是 `PX_Object *` 指针类型,因为一个 `PX_Object` 可以将多个对象结构体组合在一起,这个组合结构体我们将在之后的教程中会进一步描述,但现在我们只需要知道,调用 `PX_ObjectCreateEx` 函数后,其第一个存储的对象结构体索引是 0 就可以了。 -取出结构体指针后, 我们对其进行一系列初始化, 比如加载图片和初始化旋转角度, 最后在 `main` 函数中我们创建这个对象: +取出结构体指针后,我们对其进行一系列初始化,比如加载图片和初始化旋转角度,最后在 `main` 函数中我们创建这个对象: ```c #include "PainterEngine.h" @@ -1019,23 +1020,23 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation);//渲染图片 + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation); // 渲染图片 } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image);//释放图片 + PX_TextureFree(&pMyObject->image); // 释放图片 } PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject));//创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0);//取得自定义对象数据 + PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0); // 取得自定义对象数据 pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png"))//加载图片 + if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png")) // 加载图片 { - PX_ObjectDelete(pObject);//加载失败则删除对象 + PX_ObjectDelete(pObject); // 加载失败则删除对象 return PX_NULL; } return pObject; @@ -1044,16 +1045,16 @@ PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_flo px_int main() { PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240);//创建一个自定义对象 + PX_Object_MyObjectCreate(mp,root,400,240); // 创建一个自定义对象 return PX_TRUE; } ``` -那么它的运行效果是这样的: +那么它的运行效果是这样的: ![](assets/img/12.1.png) -但现在还没有结束, 我们怎么让我们的组件, 响应鼠标中键实现旋转呢?还记得我们之前在 [PushButton](#8painterengine-对象传递机制) 中的对象传递机制么?现在, 我们也要让我们的组件响应鼠标中键的信息, 因此我们给它注册一个 `PX_OBJECT_EVENT_CURSORWHEEL` 事件的回调函数, 代码如下: +但现在还没有结束,我们怎么让我们的组件,响应鼠标中键实现旋转呢?还记得我们之前在 [PushButton](#8painterengine-对象传递机制) 中的对象传递机制么?现在,我们也要让我们的组件响应鼠标中键的信息,因此我们给它注册一个 `PX_OBJECT_EVENT_CURSORWHEEL` 事件的回调函数,代码如下: ```c #include "PainterEngine.h" @@ -1070,62 +1071,62 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation);//渲染图片 + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation); // 渲染图片 } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image);//释放图片 + PX_TextureFree(&pMyObject->image); // 释放图片 } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) { PX_Object_MyObject *pMyObject=PX_ObjectGetDescIndex(PX_Object_MyObject,pObject,0); - if(PX_ObjectIsCursorInRegion(pObject,e))//Object是鼠标位置是否选中当前组件,e是事件 + if(PX_ObjectIsCursorInRegion(pObject,e)) // Object是鼠标位置是否选中当前组件,e是事件 pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; } PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject));//创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0);//取得自定义对象数据 + PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0); // 取得自定义对象数据 pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png"))//加载图片 + if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png")) // 加载图片 { - PX_ObjectDelete(pObject);//加载失败则删除对象 + PX_ObjectDelete(pObject); // 加载失败则删除对象 return PX_NULL; } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0);//注册鼠标滚轮事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0); // 注册鼠标滚轮事件 return pObject; } px_int main() { PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240);//创建一个自定义对象 + PX_Object_MyObjectCreate(mp,root,400,240); // 创建一个自定义对象 return PX_TRUE; } ``` -运行结果如下: +运行结果如下: ![](assets/img/12.2.gif) -如果你觉得旋转图的质量不好, 有很多锯齿, 这是因为 `PX_TextureRenderEx` 旋转时是对原图直接采样的。如果你想要高质量的旋转图, 你可以用 `PX_TextureRenderRotation` 函数来替换原函数: +如果你觉得旋转图的质量不好,有很多锯齿,这是因为 `PX_TextureRenderEx` 旋转时是对原图直接采样的。如果你想要高质量的旋转图,你可以用 `PX_TextureRenderRotation` 函数来替换原函数: ```c PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, pMyObject->rotation);//渲染图片 + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, pMyObject->rotation); // 渲染图片 } ``` ![](assets/img/12.3.gif) -那么, 我们如何实现拖动效果呢?想要做到拖动效果, 我们需要在对象结构体中, 新增 `float` 类型的变量 `x`, `y`, 用来记录当鼠标选中图片时的位置, 同时我们加入了 `bool` 类型的变量 `bselect`, 表示当前的图标是否被选中。当鼠标点击我们的图标以后, 我们就可以监听 `PX_OBJECT_EVENT_CURSORDRAG` 事件, 这是鼠标在屏幕上拖动时会产生的事件, 我们通过坐标的偏移, 移动我们的组件。最后, 不论鼠标非拖动时的移动或鼠标左键抬起, 都会取消我们组件的选中状态, 在对应处理函数中取消选中状态即可。 +那么,我们如何实现拖动效果呢?想要做到拖动效果,我们需要在对象结构体中,新增 `float` 类型的变量 `x`、`y`,用来记录当鼠标选中图片时的位置,同时我们加入了 `bool` 类型的变量 `bselect`,表示当前的图标是否被选中。当鼠标点击我们的图标以后,我们就可以监听 `PX_OBJECT_EVENT_CURSORDRAG` 事件,这是鼠标在屏幕上拖动时会产生的事件,我们通过坐标的偏移,移动我们的组件。最后,不论鼠标非拖动时的移动或鼠标左键抬起,都会取消我们组件的选中状态,在对应处理函数中取消选中状态即可。 ```c #include "PainterEngine.h" @@ -1144,26 +1145,26 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, (px_int)pMyObject->rotation);//渲染图片 + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, (px_int)pMyObject->rotation); // 渲染图片 } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image);//释放图片 + PX_TextureFree(&pMyObject->image); // 释放图片 } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) { PX_Object_MyObject *pMyObject=PX_ObjectGetDescIndex(PX_Object_MyObject,pObject,0); - if(PX_ObjectIsCursorInRegionAlign(pObject,e,PX_ALIGN_CENTER))//Object是鼠标位置是否选中当前组件,e是事件 + if(PX_ObjectIsCursorInRegionAlign(pObject,e,PX_ALIGN_CENTER)) // Object是鼠标位置是否选中当前组件,e是事件 pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDown) { PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER))//Object是鼠标位置是否选中当前组件,e是事件 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Object是鼠标位置是否选中当前组件,e是事件 { pMyObject->bselect = PX_TRUE; pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); @@ -1191,38 +1192,38 @@ PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDrag) PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject));//创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0);//取得自定义对象数据 + PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0); // 取得自定义对象数据 pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png"))//加载图片 + if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png")) // 加载图片 { - PX_ObjectDelete(pObject);//加载失败则删除对象 + PX_ObjectDelete(pObject); // 加载失败则删除对象 return PX_NULL; } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0);//注册鼠标滚轮事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDRAG,MyObjectOnCursorDrag,0);//注册鼠标拖拽事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,MyObjectOnCursorDown,0);//注册鼠标按下事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORUP,MyObjectOnCursorRelease,0);//注册鼠标释放事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0);//注册鼠标释放事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0); // 注册鼠标滚轮事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDRAG,MyObjectOnCursorDrag,0); // 注册鼠标拖拽事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,MyObjectOnCursorDown,0); // 注册鼠标按下事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORUP,MyObjectOnCursorRelease,0); // 注册鼠标释放事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0); // 注册鼠标释放事件 return pObject; } px_int main() { PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240);//创建一个自定义对象 + PX_Object_MyObjectCreate(mp,root,400,240); // 创建一个自定义对象 return PX_TRUE; } ``` ![](assets/img/12.4.gif) -当然, 你可以调用 `PX_Object_MyObjectCreate` 多次, 创建多个组件对象, 它们的功能都是一样的: +当然,你可以调用 `PX_Object_MyObjectCreate` 多次,创建多个组件对象,它们的功能都是一样的: ![](assets/img/12.5.gif) ## 13. 组合式组件设计 -PainterEngine 的组件允许同时拥有多种组件类型, 例如, 当我们将一个图片框组件和一个按钮进行组合, 我们就可以得到一个组合式组件图片按钮。 +PainterEngine 的组件允许同时拥有多种组件类型,例如,当我们将一个图片框组件和一个按钮进行组合,我们就可以得到一个组合式组件图片按钮。 参考如下代码: @@ -1233,8 +1234,8 @@ PX_Object* image; PX_OBJECT_EVENT_FUNCTION(ButtonEvent) { - PX_Object_Image *pImage=PX_Object_GetImage(pObject);//取得Image对象数据 - PX_Object_Button *pButton=PX_Object_GetButton(pObject);//取得Button对象数据 + PX_Object_Image *pImage=PX_Object_GetImage(pObject); // 取得Image对象数据 + PX_Object_Button *pButton=PX_Object_GetButton(pObject); // 取得Button对象数据 if (pImage->pTexture==&tex1) { PX_Object_ImageSetTexture(pObject,&tex2); @@ -1248,30 +1249,30 @@ PX_OBJECT_EVENT_FUNCTION(ButtonEvent) px_int main() { PainterEngine_Initialize(800, 480); - if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0;//加载纹理1 - if(!PX_LoadTextureFromFile(mp_static,&tex2,"assets/2.png")) return 0;//加载纹理2 - image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1);//创建Image对象 - PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255));//将一个Button对象类型组合到Image对象上 - PX_ObjectRegisterEvent(image,PX_OBJECT_EVENT_EXECUTE,ButtonEvent,0);//这里实际上是注册Button对象的事件 + if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0; // 加载纹理1 + if(!PX_LoadTextureFromFile(mp_static,&tex2,"assets/2.png")) return 0; // 加载纹理2 + image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1); // 创建Image对象 + PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255)); // 将一个Button对象类型组合到Image对象上 + PX_ObjectRegisterEvent(image,PX_OBJECT_EVENT_EXECUTE,ButtonEvent,0); // 这里实际上是注册Button对象的事件 return 1; } ``` -我们创建了一个 Image 图像框类型, 然后将一个 Button 对象类型组合上去, 这样我们就获得了一个图片按钮: +我们创建了一个 Image 图像框类型,然后将一个 Button 对象类型组合上去,这样我们就获得了一个图片按钮: ![](assets/img/13.1.gif) -那么, 我们如何设计我们自己的可组合对象呢?回到我们的第十二章节, 现在, 我们就将 "可拖拽" 这个功能设计成一个组合式组件。 +那么,我们如何设计我们自己的可组合对象呢?回到我们的第十二章节,现在,我们就将 "可拖拽" 这个功能设计成一个组合式组件。 -首先,仍然是定义一个组件对象结构体,为实现拖拽功能,我们需要鼠标按下时的 x, y 坐标, 同时需要一个 bool 类型记录是否是选中状态, 然后我们需要注册 `CURSOR` 事件, 这些事件在上一章节我们已经写过了, 最后, 我们用 `PX_ObjectCreateDesc` 函数创建一个对象结构体,并将它 Attach 到我们的对象上。 +首先,仍然是定义一个组件对象结构体,为实现拖拽功能,我们需要鼠标按下时的 x, y 坐标,同时需要一个 bool 类型记录是否是选中状态,然后我们需要注册 `CURSOR` 事件,这些事件在上一章节我们已经写过了,最后,我们用 `PX_ObjectCreateDesc` 函数创建一个对象结构体,并将它 Attach 到我们的对象上。 -`PX_ObjectCreateDesc` 是一个对象结构体创建函数, 它的定义原型如下: +`PX_ObjectCreateDesc` 是一个对象结构体创建函数,它的定义原型如下: ```c px_void* PX_ObjectCreateDesc(PX_Object* pObject, px_int idesc, px_int type, Function_ObjectUpdate Func_ObjectUpdate, Function_ObjectRender Func_ObjectRender, Function_ObjectFree Func_ObjectFree, px_void* pDesc, px_int descSize) ``` -第一个参数是需要 Attach 的对象, 第二个参数是 Attach 到的对象索引。还记得我们之前提到的对象数据索引么, 使用 `PX_ObjectCreateEx` 默认使用的是索引 0, 因此, 如果我们要附加到一个对象上, 我们应该选 1, 当然如果 1 也被占用了, 它就是 2, 以此类推。第三个参数是对象类型, 我们使用 `PX_ObjectGetDescByType` 时, 可以通过对象类型取出对应的指针, 然后就是我们熟悉的 `Update`、`Render`、`Free` 三件套了, 最后一个参数给出其结构体描述和结构体大小。请参阅下面的代码: +第一个参数是需要 Attach 的对象,第二个参数是 Attach 到的对象索引。还记得我们之前提到的对象数据索引么,使用 `PX_ObjectCreateEx` 默认使用的是索引 0,因此,如果我们要附加到一个对象上,我们应该选 1,当然如果 1 也被占用了,它就是 2,以此类推。第三个参数是对象类型,我们使用 `PX_ObjectGetDescByType` 时,可以通过对象类型取出对应的指针,然后就是我们熟悉的 `Update`、`Render`、`Free` 三件套了,最后一个参数给出其结构体描述和结构体大小。请参阅下面的代码: ```c #include "PainterEngine.h" @@ -1335,21 +1336,21 @@ PX_Object* image; px_int main() { PainterEngine_Initialize(800, 480); - if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0;//加载纹理1 - image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1);//创建Image对象 - PX_Object_DragAttachObject(image, 1);//将一个Drag对象类型组合到Image对象上 + if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0; // 加载纹理1 + image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1); // 创建Image对象 + PX_Object_DragAttachObject(image, 1); // 将一个Drag对象类型组合到Image对象上 return 1; } ``` -运行结果如下: +运行结果如下: ![](assets/img/13.2.gif) ## 14. 粒子系统 -PainterEngine 提供了一个粒子系统实现, 下面是一个粒子系统的示范程序: +PainterEngine 提供了一个粒子系统实现,下面是一个粒子系统的示范程序: ```c #include "PainterEngine.h" @@ -1372,7 +1373,7 @@ px_int main() ![](assets/img/14.1.gif) -这是一个用组件包装起来的粒子系统实现, 另外一种是提供了更加详细的粒子系统参数配置: +这是一个用组件包装起来的粒子系统实现,另外一种是提供了更加详细的粒子系统参数配置: ```c #include "PainterEngine.h" @@ -1513,22 +1514,22 @@ PainterEngine 内置了对 wav 及 mp3 格式音乐的原生支持,使用 Pain ```c #include "PainterEngine.h" -PX_SoundData sounddata;//定义音乐格式 +PX_SoundData sounddata; // 定义音乐格式 int main() { PX_Object* pObject; PainterEngine_Initialize(600, 400); - PainterEngine_InitializeAudio();//初始化混音器及音乐设备 - if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav"))return PX_FALSE;//加载音乐,支持wav及mp3格式 - PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE));//播放音乐 - pObject = PX_Object_SoundViewCreate(mp,root,0,0,600,400,soundplay);//音乐频谱可视化组件,可选 + PainterEngine_InitializeAudio(); // 初始化混音器及音乐设备 + if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav"))return PX_FALSE; // 加载音乐,支持wav及mp3格式 + PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE)); // 播放音乐 + pObject = PX_Object_SoundViewCreate(mp,root,0,0,600,400,soundplay); // 音乐频谱可视化组件,可选 return 0; } ``` ![](assets/img/15.1.gif) -其中, `PX_LoadSoundFromFile` 函数从文件中加载音乐, 并解码成 `sounddata` 类型。`PX_SoundCreate` 可以用 `sounddata` 创建一个播放实例, 第二个参数表示这个实例是否循环播放, 最后使用 `PX_SoundPlayAdd` 将播放实例送入混音器中, 即可完成音乐播放。 +其中,`PX_LoadSoundFromFile` 函数从文件中加载音乐,并解码成 `sounddata` 类型。`PX_SoundCreate` 可以用 `sounddata` 创建一个播放实例,第二个参数表示这个实例是否循环播放,最后使用 `PX_SoundPlayAdd` 将播放实例送入混音器中,即可完成音乐播放。 ## 16. PainterEngine Live2D 动画系统 @@ -1550,12 +1551,12 @@ int main() PX_IO_Data iodata; PainterEngine_Initialize(600, 600); - //加载模型数据 + // 加载模型数据 iodata = PX_LoadFileToIOData("assets/release.live"); if (iodata.size == 0)return PX_FALSE; PX_LiveFrameworkImport(mp_static, &liveframework, iodata.buffer, iodata.size); PX_FreeIOData(&iodata); - //创建Live2D对象 + // 创建Live2D对象 pObject = PX_Object_Live2DCreate(mp,root,300,300,&liveframework); PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, onClick, PX_NULL); @@ -1572,13 +1573,13 @@ int main() PX_Object* PX_Object_Live2DCreate(px_memorypool* mp, PX_Object* Parent, px_int x, px_int y, PX_LiveFramework *pLiveFramework); ``` -- **描述**: 创建一个 Live2D 模型预览器对象,用于在图形界面中显示和交互 Live2D 模型。 -- **参数**: - - `mp`: 内存池指针,用于分配内存。 - - `Parent`: 父对象,Live2D 模型预览器对象将作为其子对象。 - - `x`, `y`: Live2D 模型预览器对象的位置坐标。 - - `pLiveFramework`: Live2D 模型框架的指针,包括模型数据、纹理等信息。 -- **返回值**: 创建的 Live2D 模型预览器对象的指针。 +- **描述**:创建一个 Live2D 模型预览器对象,用于在图形界面中显示和交互 Live2D 模型。 +- **参数**: + - `mp`:内存池指针,用于分配内存。 + - `Parent`:父对象,Live2D 模型预览器对象将作为其子对象。 + - `x`, `y`:Live2D 模型预览器对象的位置坐标。 + - `pLiveFramework`:Live2D 模型框架的指针,包括模型数据、纹理等信息。 +- **返回值**:创建的 Live2D 模型预览器对象的指针。 `PX_Object_Live2DPlayAnimation` @@ -1586,11 +1587,11 @@ PX_Object* PX_Object_Live2DCreate(px_memorypool* mp, PX_Object* Parent, px_int x px_void PX_Object_Live2DPlayAnimation(PX_Object *pObject, px_char *name); ``` -- **描述**: 播放指定名称的 Live2D 模型动画。 -- **参数**: - - `pObject`: Live2D 模型预览器对象的指针。 - - `name`: 动画名称。 -- **返回值**: 无。 +- **描述**:播放指定名称的 Live2D 模型动画。 +- **参数**: + - `pObject`:Live2D 模型预览器对象的指针。 + - `name`:动画名称。 +- **返回值**:无。 `PX_Object_Live2DPlayAnimationRandom` @@ -1598,10 +1599,10 @@ px_void PX_Object_Live2DPlayAnimation(PX_Object *pObject, px_char *name); px_void PX_Object_Live2DPlayAnimationRandom(PX_Object* pObject); ``` -- **描述**: 随机播放 Live2D 模型的动画。 -- **参数**: - - `pObject`: Live2D 模型预览器对象的指针。 -- **返回值**: 无。 +- **描述**:随机播放 Live2D 模型的动画。 +- **参数**: + - `pObject`:Live2D 模型预览器对象的指针。 +- **返回值**:无。 `PX_Object_Live2DPlayAnimationIndex` @@ -1609,11 +1610,11 @@ px_void PX_Object_Live2DPlayAnimationRandom(PX_Object* pObject); px_void PX_Object_Live2DPlayAnimationIndex(PX_Object* pObject, px_int index); ``` -- **描述**: 播放 Live2D 模型的指定索引处的动画。 -- **参数**: - - `pObject`: Live2D 模型预览器对象的指针。 - - `index`: 动画的索引。 -- **返回值**: 无。 +- **描述**:播放 Live2D 模型的指定索引处的动画。 +- **参数**: + - `pObject`:Live2D 模型预览器对象的指针。 + - `index`:动画的索引。 +- **返回值**:无。 这些函数用于创建、配置和管理 Live2D 模型预览器对象,以在图形用户界面中显示和交互 Live2D 模型。可以使用这些函数播放 Live2D 模型的动画,包括指定名称、随机选择和指定索引处的动画。 @@ -1623,14 +1624,14 @@ px_void PX_Object_Live2DPlayAnimationIndex(PX_Object* pObject, px_int index); PainterEngine 内置了一个平台无关的脚本引擎系统,集成了编译,运行,调试等功能,你可以很轻松地在脚本之上,实现并行调度功能。PainterEngine Script 的设计,最大程度和 C 语言保持一致性,并对一些类型进行的拓展和简化。 -例如在脚本中,支持 `int`, `float`, `string`, `memory` 四种类型, `int` 类型是一个 32 位的有符号整数, `float` 是一个浮点数类型, 这个和 C 语言的类型保持了一致。`string` 类型类似于 C++的 `string`, 它允许直接用 `+` 法运算符进行字符串拼接, 使用 `strlen` 来获取其字符串长度, 而 `memory` 是一个二进制数据存储类型, 同样支持 `+` 运算进行拼接。 +例如在脚本中,支持 `int`、`float`、`string`、`memory` 四种类型,`int` 类型是一个 32 位的有符号整数,`float` 是一个浮点数类型,这个和 C 语言的类型保持了一致。`string` 类型类似于 C++的 `string`,它允许直接用 `+` 法运算符进行字符串拼接,使用 `strlen` 来获取其字符串长度,而 `memory` 是一个二进制数据存储类型,同样支持 `+` 运算进行拼接。 -在脚本中如果需要调用 C 语言函数,应该使用 `PX_VM_HOST_FUNCTION` 宏进行定义声明。和组件回调函数一样, `PX_VM_HOST_FUNCTION` 的定义如下: +在脚本中如果需要调用 C 语言函数,应该使用 `PX_VM_HOST_FUNCTION` 宏进行定义声明。和组件回调函数一样,`PX_VM_HOST_FUNCTION` 的定义如下: ```c #define PX_VM_HOST_FUNCTION(name) px_bool name(PX_VM *Ins,px_void *userptr) ``` -在下面的内容中, 我将以一个简单的脚本实例作为范例, 为你演示如何使用 PainterEngine 的脚本引擎: +在下面的内容中,我将以一个简单的脚本实例作为范例,为你演示如何使用 PainterEngine 的脚本引擎: ```c const px_char shellcode[] = "\ @@ -1670,15 +1671,15 @@ PX_VM_HOST_FUNCTION(host_sleep) ``` -首先, `shellcode` 数组中存储着一个输出九九乘法表的程序, 其中需要调用两个 `host` 函数(脚本调用 C 语言函数称为 host call, 因此 host 函数实际就是专门提供给脚本调用的 C 语言函数), 一个是 `print` 函数, 一个是 `sleep` 函数。因此在下面, 我们定义了两个 `host` 函数, `PX_VM_HOSTPARAM` 用于取得脚本传递过来的参数。在这里, 我们需要判断传递过来的参数类型是否符合我们的调用规则, 像 `host_print` 函数, 作用是在 PainterEngine 中输出字符串, 而 `sleep` 函数, 则是用来延迟一段时间。 +首先,`shellcode` 数组中存储着一个输出九九乘法表的程序,其中需要调用两个 `host` 函数(脚本调用 C 语言函数称为 host call,因此 host 函数实际就是专门提供给脚本调用的 C 语言函数),一个是 `print` 函数,一个是 `sleep` 函数。因此在下面,我们定义了两个 `host` 函数,`PX_VM_HOSTPARAM` 用于取得脚本传递过来的参数。在这里,我们需要判断传递过来的参数类型是否符合我们的调用规则,像 `host_print` 函数,作用是在 PainterEngine 中输出字符串,而 `sleep` 函数,则是用来延迟一段时间。 -现在,PainterEngine Script 是一个编译型脚本, 我们需要将上面的代码编译成二进制形式, 然后将它送入虚拟机中运行, 观察以下代码: +现在,PainterEngine Script 是一个编译型脚本,我们需要将上面的代码编译成二进制形式,然后将它送入虚拟机中运行,观察以下代码: ```c PX_VM vm; PX_OBJECT_UPDATE_FUNCTION(VMUpdate) { - PX_VMRun(&vm, 0xffff, elapsed);//运行虚拟机 + PX_VMRun(&vm, 0xffff, elapsed); // 运行虚拟机 } px_int main() @@ -1687,26 +1688,26 @@ px_int main() px_memory bin; PainterEngine_Initialize(800, 600); PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler);//初始化编译器 - PX_CompilerAddSource(&compiler, shellcode);//编译器中添加代码 - PX_MemoryInitialize(mp, &bin);//初始化内存/用于存储编译后的结果 + PX_CompilerInitialize(mp, &compiler); // 初始化编译器 + PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 + PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 if (!PX_CompilerCompile(&compiler, &bin, 0, "main")) { - //编译失败 + // 编译失败 return 0; } - PX_CompilerFree(&compiler);//释放编译器 - PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize);//初始化虚拟机 - PX_VMRegisterHostFunction(&vm, "print", host_print,0);//注册主机函数print - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0);//注册主机函数sleep - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0);//开始运行虚拟机函数 - PX_ObjectSetUpdateFunction(root, VMUpdate, 0);//设置更新函数 + PX_CompilerFree(&compiler); // 释放编译器 + PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize); // 初始化虚拟机 + PX_VMRegisterHostFunction(&vm, "print", host_print,0); // 注册主机函数print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0); // 注册主机函数sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 + PX_ObjectSetUpdateFunction(root, VMUpdate, 0); // 设置更新函数 return 0; } ``` -首先我们用 `PX_Compiler` 编译我们的脚本, 然后我们注册我们的 host call, `PX_VMBeginThreadFunction` 的功能是 C 语言调用脚本语言中函数, 在这里我们调用脚本中的 `main` 开始运行我们的脚本函数, 最后我们将一个 `Update` 函数绑定到 root 节点, 以循环更新虚拟机, 来执行脚本。 +首先我们用 `PX_Compiler` 编译我们的脚本,然后我们注册我们的 host call,`PX_VMBeginThreadFunction` 的功能是 C 语言调用脚本语言中函数,在这里我们调用脚本中的 `main` 开始运行我们的脚本函数,最后我们将一个 `Update` 函数绑定到 root 节点,以循环更新虚拟机,来执行脚本。 最后,看看运行的结果。 @@ -1722,19 +1723,19 @@ px_int main() PainterEngine_Initialize(800, 480); PX_VMDebuggerMapInitialize(mp,&debugmap); PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler);//初始化编译器 - PX_CompilerAddSource(&compiler, shellcode);//编译器中添加代码 - PX_MemoryInitialize(mp, &bin);//初始化内存/用于存储编译后的结果 + PX_CompilerInitialize(mp, &compiler); // 初始化编译器 + PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 + PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 if (!PX_CompilerCompile(&compiler, &bin, &debugmap, "main")) { - //编译失败 + // 编译失败 return 0; } - PX_CompilerFree(&compiler);//释放编译器 - PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize);//初始化虚拟机 - PX_VMRegisterHostFunction(&vm, "print", host_print,0);//注册主机函数print - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0);//注册主机函数sleep - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0);//开始运行虚拟机函数 + PX_CompilerFree(&compiler); // 释放编译器 + PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize); // 初始化虚拟机 + PX_VMRegisterHostFunction(&vm, "print", host_print,0); // 注册主机函数print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0); // 注册主机函数sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 PX_Object *pDbgObject = PX_Object_AsmDebuggerCreate(mp, root, 0, 0, 800, 480, 0); pDbgObject->Visible = PX_TRUE; PX_Object_AsmDebuggerAttach(pDbgObject, &debugmap, &vm); @@ -1746,15 +1747,15 @@ px_int main() ## 18. 使用 PainterEngine 快速创作一个小游戏 -为了更好地演示 PainterEngine 的使用, 我将用 PainterEngine 创作一个简单的小游戏, 你可以在 documents/demo/game 下找到有关这个游戏的所有源码及原始素材。得益于 PainterEngine 的全平台可移植性,你也可以在 [PainterEngine 在线应用 APP--打地鼠](https://www.painterengine.com/main/app/documentgame/) 中, 直接玩到这个在线小游戏。 +为了更好地演示 PainterEngine 的使用,我将用 PainterEngine 创作一个简单的小游戏,你可以在 documents/demo/game 下找到有关这个游戏的所有源码及原始素材。得益于 PainterEngine 的全平台可移植性,你也可以在 [PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) 中,直接玩到这个在线小游戏。 在这个小游戏中,我将充分为你展示,如何使用 PainterEngine 的组件化开发模式,快速创建一个 App Game。 -让我们先开始游戏创作的第一步,我们先准备好所需的美术资源及素材: +让我们先开始游戏创作的第一步,我们先准备好所需的美术资源及素材: ![](assets/img/18.1.png) -这是一个简单的游戏背景素材,然后我们就可以开始创建我们的 `main.c` 源代码文件, 在 PainterEngine 中我们输入下面的代码: +这是一个简单的游戏背景素材,然后我们就可以开始创建我们的 `main.c` 源代码文件,在 PainterEngine 中我们输入下面的代码: ```c px_int main() @@ -1786,23 +1787,23 @@ px_int main() } ``` -在代码的开始阶段, 我们初始化了一个 800x480 的窗口, 然后我们初始化了字模, 并用 `PX_FontModuleSetCodepage` 函数设置了其为 GBK 字符集, 再后面, 我们就是把资源加载进 PainterEngine 的资源管理器中了。 +在代码的开始阶段,我们初始化了一个 800x480 的窗口,然后我们初始化了字模,并用 `PX_FontModuleSetCodepage` 函数设置了其为 GBK 字符集,再后面,我们就是把资源加载进 PainterEngine 的资源管理器中了。 ### 加载资源及设置背景 -PainterEngine 内置了一个资源管理器,它在 `PainterEngine_Initialize` 中就被初始化了, 使用的是 `mp_static` 内存池。资源管理器的作用是像数据库一样, 将图片、音频、脚本等等素材加载到内存中, 并将它映射为一个 `key`, 之后对资源的访问都是通过 `key` 进行的。资源管理器的映射做了专门的优化, 因此你不必太担心映射查询带来的性能损耗问题。 +PainterEngine 内置了一个资源管理器,它在 `PainterEngine_Initialize` 中就被初始化了,使用的是 `mp_static` 内存池。资源管理器的作用是像数据库一样,将图片、音频、脚本等等素材加载到内存中,并将它映射为一个 `key`,之后对资源的访问都是通过 `key` 进行的。资源管理器的映射做了专门的优化,因此你不必太担心映射查询带来的性能损耗问题。 -`PX_LoadTextureToResource` 函数用于将一个文件系统的资源加载到资源管理器中, 第一个参数是这个资源管理器的实例指针, PainterEngine 在初始化阶段会默认创建一个这样的管理器实例, 因此你可以直接用 `PainterEngine_GetResourceLibrary` 获得它。第二个参数是需要加载文件的所在路径,第三个参数则是我们想映射的 `key` 了。 +`PX_LoadTextureToResource` 函数用于将一个文件系统的资源加载到资源管理器中,第一个参数是这个资源管理器的实例指针,PainterEngine 在初始化阶段会默认创建一个这样的管理器实例,因此你可以直接用 `PainterEngine_GetResourceLibrary` 获得它。第二个参数是需要加载文件的所在路径,第三个参数则是我们想映射的 `key` 了。 -在代码的下一步, 我们使用 `PX_LoadTextureToResource` 加载了若干图片, `PX_LoadAnimationToResource` 加载了一个 2dx 动画(请到应用市场查看 2DX 动画详细说明)。最后,在游戏里我们并没有使用 TTF 字模文件,我们循环加载了 `0.png` 到 `9.png`, 并将这些纹理作为图片插入到字模中, 这样这个字模绘制数字时, 实际显示的就是我们的图片。 +在代码的下一步,我们使用 `PX_LoadTextureToResource` 加载了若干图片,`PX_LoadAnimationToResource` 加载了一个 2dx 动画(请到应用市场查看 2DX 动画详细说明)。最后,在游戏里我们并没有使用 TTF 字模文件,我们循环加载了 `0.png` 到 `9.png`,并将这些纹理作为图片插入到字模中,这样这个字模绘制数字时,实际显示的就是我们的图片。 -同时我们还调用了 `PainterEngine_SetBackgroundTexture` 设置 PainterEngine 界面的背景, 请注意 `PX_ResourceLibraryGetTexture` 函数, 它的作用是使用一个查询 `key`, 从资源管理器中取得这个图片的数据结构指针。以上完成后你将可以看到这样的界面: +同时我们还调用了 `PainterEngine_SetBackgroundTexture` 设置 PainterEngine 界面的背景,请注意 `PX_ResourceLibraryGetTexture` 函数,它的作用是使用一个查询 `key`,从资源管理器中取得这个图片的数据结构指针。以上完成后你将可以看到这样的界面: ![](assets/img/18.2.png) ### 设计游戏对象 -我们先来设计第一个游戏对象,就是 `开始游戏按钮`。这一部分我们并不要写太多的代码, 因为 PainterEngine 内置就有这种按钮的功能: +我们先来设计第一个游戏对象,就是 `开始游戏按钮`。这一部分我们并不要写太多的代码,因为 PainterEngine 内置就有这种按钮的功能: ```c startgame = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 90, "Start Game", 0); @@ -1812,33 +1813,33 @@ PX_Object_PushButtonSetPushColor(startgame, PX_COLOR(224, 255, 255, 255)); PX_Object_PushButtonSetCursorColor(startgame, PX_COLOR(168, 255, 255, 255)); ``` -我们使用了一系列函数, 改变了按钮的背景颜色、鼠标悬停颜色和鼠标按下的颜色, 因此你可以看到这样的情况: +我们使用了一系列函数,改变了按钮的背景颜色、鼠标悬停颜色和鼠标按下的颜色,因此你可以看到这样的情况: ![](assets/img/18.3.png) -然后我们需要创建我们的游戏里的地鼠对象, 这是游戏里最复杂的对象, 我贴上详细代码, 以逐步解释它们: +然后我们需要创建我们的游戏里的地鼠对象,这是游戏里最复杂的对象,我贴上详细代码,以逐步解释它们: ```c typedef enum { - PX_OBJECT_FOX_STATE_IDLE,//狐狸还在洞里 - PX_OBJECT_FOX_STATE_RASING,//狐狸正在升起 - PX_OBJECT_FOX_STATE_TAUNT,//狐狸在嘲讽 - PX_OBJECT_FOX_STATE_ESCAPE,//狐狸逃跑 - PX_OBJECT_FOX_STATE_BEAT,//狐狸被打 - PX_OBJECT_FOX_STATE_HURT,//狐狸受伤后逃跑 + PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 + PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 + PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 + PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 + PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 + PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 }PX_OBJECT_FOX_STATE; typedef struct { - PX_OBJECT_FOX_STATE state;//狐狸状态 - px_dword elapsed;//状态持续时间 - px_float texture_render_offset;//纹理渲染偏移 - px_dword gen_rand_time;//生成随机时间 - px_float rasing_down_speed;//升起速度 - px_texture render_target;//渲染目标 - px_texture* pcurrent_display_texture;//当前显示的纹理 - px_texture* ptexture_mask;//遮罩 + PX_OBJECT_FOX_STATE state; // 狐狸状态 + px_dword elapsed; // 状态持续时间 + px_float texture_render_offset; // 纹理渲染偏移 + px_dword gen_rand_time; // 生成随机时间 + px_float rasing_down_speed; // 升起速度 + px_texture render_target; // 渲染目标 + px_texture* pcurrent_display_texture; // 当前显示的纹理 + px_texture* ptexture_mask; // 遮罩 }PX_Object_Fox; PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) @@ -1850,18 +1851,18 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) { if (pfox->gen_rand_time ==0) { - pfox->gen_rand_time = PX_rand() % 3000 + 1000;//狐狸在洞里的时间,时间到了就升起来 + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 } else { - if (pfox->gen_rand_time gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; pfox->elapsed = 0; pfox->gen_rand_time = 0; pfox->texture_render_offset = pObject->Height; - //改变纹理 + // 改变纹理 pfox->pcurrent_display_texture= PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); } else @@ -1871,42 +1872,42 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) } } break; - case PX_OBJECT_FOX_STATE_RASING://狐狸升起 + case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 { pfox->elapsed += elapsed; - //升起纹理偏移量 + // 升起纹理偏移量 pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; if (pfox->texture_render_offset <= 0) { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT;//升起后嘲讽 + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_TAUNT://狐狸嘲讽 + case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 { pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500)//嘲讽时间 + if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt");//嘲讽纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 } - else if (pfox->elapsed>1500)//嘲讽结束 + else if (pfox->elapsed>1500) // 嘲讽结束 { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//逃跑 - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape");//逃跑纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_BEAT://狐狸被打 + case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 { pfox->elapsed += elapsed; if (pfox->elapsed>800) { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt");//受伤纹理 - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 } } break; @@ -1917,8 +1918,8 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) if (pfox->texture_render_offset >= pObject->Height) { pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE;//逃跑结束 - pfox->elapsed = 0;//重置时间 + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 + pfox->elapsed = 0; // 重置时间 pfox->pcurrent_display_texture = PX_NULL; } } @@ -1933,12 +1934,12 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); px_float x,y,width,height; PX_OBJECT_INHERIT_CODE(pObject,x,y,width,height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE);//清空渲染目标 + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 if (pfox->pcurrent_display_texture) { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL);//渲染狐狸 + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL);//以遮罩形式绘制纹理 + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 } @@ -1948,12 +1949,12 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) PX_TextureFree(&pfox->render_target); } -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick)//狐狸被点击 +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 { PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING)//狐狸嘲讽或者升起时点击有效 + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM))//点击有效区域 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 { px_int x= (px_int)PX_Object_Event_GetCursorX(e); px_int y= (px_int)PX_Object_Event_GetCursorY(e); @@ -1985,57 +1986,57 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) PX_Object *PX_Object_FoxCreate(px_memorypool *mp,PX_Object *parent,px_float x,px_float y) { PX_Object_Fox* pfox; - px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing");//从资源管理器中获取纹理 + px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing"); // 从资源管理器中获取纹理 PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - pfox->state= PX_OBJECT_FOX_STATE_IDLE;//狐狸状态 - pfox->rasing_down_speed = 512;//升起速度 - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask");//遮罩 + pfox->state= PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 + pfox->rasing_down_speed = 512; // 升起速度 + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 if(!PX_TextureCreate(mp,&pfox->render_target,ptexture->width,ptexture->height)) { PX_ObjectDelete(pObject); return 0; } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0);//注册点击事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0);//注册重置事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0); // 注册点击事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0); // 注册重置事件 return pObject; } ``` -* 首先是 `PX_Object_FoxOnUpdate`, 这是对象三件套中的 `update` 函数, 在这个函数中, 我们判断当前这个 `地鼠` 的状态, 到底是升起、嘲讽, 还是缩回去。 -* 然后是 `PX_Object_FoxOnRender`, 这是执行 `render` 的函数, 我们通过偏移量把纹理绘制出来, 当然在这里我们调用了 `PX_TextureRenderMask` 函数, 这是一个带纹理遮罩的绘制函数。 -* `PX_Object_FoxFree` 函数中, 主要是对临时渲染表面的释放处理, 虽然在本项目中并没有用到。 -* `PX_Object_FoxOnClick` 函数, 表示当前的地鼠被击打了, 其中是一些命中范围的判断, 如果被击中了, 应该把状态设置为受伤。 -* `PX_Object_FoxOnReset` 用于执行复位, 即游戏结束后, 所有地鼠都应该是重置状态, 这是一个 `PX_OBJECT_EVENT_RESET` 的回调, 你可以在 `PX_Object_FoxCreate` 中找到它。 -* 最后是 `PX_Object_FoxCreate` 函数, 在这个函数中我们做了一些初始化工作, 为 `地鼠` 注册了事件回调, 最终完成这个组件的开发设计。 +* 首先是 `PX_Object_FoxOnUpdate`,这是对象三件套中的 `update` 函数,在这个函数中,我们判断当前这个 `地鼠` 的状态,到底是升起、嘲讽,还是缩回去。 +* 然后是 `PX_Object_FoxOnRender`,这是执行 `render` 的函数,我们通过偏移量把纹理绘制出来,当然在这里我们调用了 `PX_TextureRenderMask` 函数,这是一个带纹理遮罩的绘制函数。 +* `PX_Object_FoxFree` 函数中,主要是对临时渲染表面的释放处理,虽然在本项目中并没有用到。 +* `PX_Object_FoxOnClick` 函数,表示当前的地鼠被击打了,其中是一些命中范围的判断,如果被击中了,应该把状态设置为受伤。 +* `PX_Object_FoxOnReset` 用于执行复位,即游戏结束后,所有地鼠都应该是重置状态,这是一个 `PX_OBJECT_EVENT_RESET` 的回调,你可以在 `PX_Object_FoxCreate` 中找到它。 +* 最后是 `PX_Object_FoxCreate` 函数,在这个函数中我们做了一些初始化工作,为 `地鼠` 注册了事件回调,最终完成这个组件的开发设计。 ![](assets/img/18.4.gif) -然后,我们需要创建一个 `锤子` 对象来改变我们鼠标的样式。锤子对象的设计很简单, 它只有 2 个纹理, 一个是鼠标没有按下时的状态,一个是按下时的状态。不同的状态对应不同的纹理: +然后,我们需要创建一个 `锤子` 对象来改变我们鼠标的样式。锤子对象的设计很简单,它只有 2 个纹理,一个是鼠标没有按下时的状态,一个是按下时的状态。不同的状态对应不同的纹理: ```c typedef struct { - px_texture ham01;//锤子纹理1,没有按下 - px_texture ham02;//锤子纹理2,按下 - px_bool bHit;//是否按下 + px_texture ham01; // 锤子纹理1,没有按下 + px_texture ham02; // 锤子纹理2,按下 + px_bool bHit; // 是否按下 }PX_Object_Hammer; -PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender)//锤子渲染 +PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); px_float x, y, width, height; PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); if (phammer->bHit) { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//按下 + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 } else { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//未按下 + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 } } @@ -2049,20 +2050,20 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) { - pObject->x=PX_Object_Event_GetCursorX(e);//锤子跟随鼠标移动 + pObject->x=PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 pObject->y=PX_Object_Event_GetCursorY(e); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_TRUE;//按下 + phammer->bHit = PX_TRUE; // 按下 } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE;//抬起 + phammer->bHit = PX_FALSE; // 抬起 } PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) @@ -2073,24 +2074,24 @@ PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) phammer->bHit = PX_FALSE; if (!PX_LoadTextureFromFile(mp_static,&phammer->ham01, "assets/ham1.png")) return PX_NULL; if (!PX_LoadTextureFromFile(mp_static,&phammer->ham02, "assets/ham2.png")) return PX_NULL; - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL);//注册移动事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL);//注册拖拽事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL);//注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL);//注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL);//注册抬起事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 return pObject; } ``` -最后则是一个倒计时框, 它中间其实是一个 2dx 的动画对象(PainterEngine 直接支持 gif 动画, 其实 gif 也可以), 外围是一个环, 环形的弧度不断减少, 以实现一个 `倒计时` 的显示效果: +最后则是一个倒计时框,它中间其实是一个 2dx 的动画对象(PainterEngine 直接支持 gif 动画,其实 gif 也可以),外围是一个环,环形的弧度不断减少,以实现一个 `倒计时` 的显示效果: ```c typedef struct { - PX_Animation animation;//动画 - px_dword time;//倒计时时间 - px_dword elapsed;//倒计时开始后已经过去的时间 + PX_Animation animation; // 动画 + px_dword time; // 倒计时时间 + px_dword elapsed; // 倒计时开始后已经过去的时间 }PX_Object_Clock; @@ -2101,7 +2102,7 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) if (clock->elapsed >= clock->time) { clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET));//重置狐狸状态,给game对象发送重置事件 + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给game对象发送重置事件 game->Visible = PX_FALSE; game->Enabled = PX_FALSE; startgame->Visible = PX_TRUE; @@ -2114,11 +2115,11 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) { PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed);//更新动画 - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL);//绘制动画 - //draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK);//绘制倒计时环边框 - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time)));//绘制倒计时环 + PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 + // draw ring + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 } PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) @@ -2127,7 +2128,7 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) PX_AnimationFree(&clock->animation); } -px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time)//开始倒计时 +px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 { PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); clock->time = time; @@ -2142,7 +2143,7 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); clock->time = 0; clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song")))//从资源管理器中获取动画 + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 { PX_ObjectDelete(pObject); return PX_NULL; @@ -2153,12 +2154,12 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float } ``` -### 放置对象, 完成游戏 +### 放置对象,完成游戏 -在 `main` 函数中, 我们将上述对象一一创建, 并放置在游戏场景中, 最终完成这个游戏: +在 `main` 函数中,我们将上述对象一一创建,并放置在游戏场景中,最终完成这个游戏: ```c -//创建地鼠 +// 创建地鼠 game=PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); PX_Object_FoxCreate(mp, game, 173, 326); PX_Object_FoxCreate(mp, game, 401, 326); @@ -2169,14 +2170,14 @@ PX_Object_FoxCreate(mp, game, 636, 476); game->Visible=PX_FALSE; game->Enabled=PX_FALSE; -//创建锤子 +// 创建锤子 PX_Object_HammerCreate(mp, root); scorePanel = PX_Object_ScorePanelCreate(mp, root, 400, 60, &score_fm, 100); -//创建倒计时框 +// 创建倒计时框 gameclock=PX_Object_ClockCreate(mp,root,680,60); ``` -在这里, 我放上整个游戏的完整代码: +在这里,我放上整个游戏的完整代码: ```c #include "PainterEngine.h" @@ -2191,38 +2192,38 @@ PX_Object* game,*startgame,*gameclock; typedef enum { - PX_OBJECT_FOX_STATE_IDLE,//狐狸还在洞里 - PX_OBJECT_FOX_STATE_RASING,//狐狸正在升起 - PX_OBJECT_FOX_STATE_TAUNT,//狐狸在嘲讽 - PX_OBJECT_FOX_STATE_ESCAPE,//狐狸逃跑 - PX_OBJECT_FOX_STATE_BEAT,//狐狸被打 - PX_OBJECT_FOX_STATE_HURT,//狐狸受伤后逃跑 + PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 + PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 + PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 + PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 + PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 + PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 }PX_OBJECT_FOX_STATE; typedef struct { - PX_OBJECT_FOX_STATE state;//狐狸状态 - px_dword elapsed;//状态持续时间 - px_float texture_render_offset;//纹理渲染偏移 - px_dword gen_rand_time;//生成随机时间 - px_float rasing_down_speed;//升起速度 - px_texture render_target;//渲染目标 - px_texture* pcurrent_display_texture;//当前显示的纹理 - px_texture* ptexture_mask;//遮罩 + PX_OBJECT_FOX_STATE state; // 狐狸状态 + px_dword elapsed; // 状态持续时间 + px_float texture_render_offset; // 纹理渲染偏移 + px_dword gen_rand_time; // 生成随机时间 + px_float rasing_down_speed; // 升起速度 + px_texture render_target; // 渲染目标 + px_texture* pcurrent_display_texture; // 当前显示的纹理 + px_texture* ptexture_mask; // 遮罩 }PX_Object_Fox; typedef struct { - px_texture ham01;//锤子纹理1,没有按下 - px_texture ham02;//锤子纹理2,按下 - px_bool bHit;//是否按下 + px_texture ham01; // 锤子纹理1,没有按下 + px_texture ham02; // 锤子纹理2,按下 + px_bool bHit; // 是否按下 }PX_Object_Hammer; typedef struct { - PX_Animation animation;//动画 - px_dword time;//倒计时时间 - px_dword elapsed;//倒计时开始后已经过去的时间 + PX_Animation animation; // 动画 + px_dword time; // 倒计时时间 + px_dword elapsed; // 倒计时开始后已经过去的时间 }PX_Object_Clock; @@ -2233,7 +2234,7 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) if (clock->elapsed >= clock->time) { clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET));//重置狐狸状态,给game对象发送重置事件 + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给game对象发送重置事件 game->Visible = PX_FALSE; game->Enabled = PX_FALSE; startgame->Visible = PX_TRUE; @@ -2246,11 +2247,11 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) { PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed);//更新动画 - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL);//绘制动画 - //draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK);//绘制倒计时环边框 - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time)));//绘制倒计时环 + PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 + // draw ring + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 } PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) @@ -2259,7 +2260,7 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) PX_AnimationFree(&clock->animation); } -px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time)//开始倒计时 +px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 { PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); clock->time = time; @@ -2274,7 +2275,7 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); clock->time = 0; clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song")))//从资源管理器中获取动画 + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 { PX_ObjectDelete(pObject); return PX_NULL; @@ -2293,18 +2294,18 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) { if (pfox->gen_rand_time ==0) { - pfox->gen_rand_time = PX_rand() % 3000 + 1000;//狐狸在洞里的时间,时间到了就升起来 + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 } else { - if (pfox->gen_rand_time gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; pfox->elapsed = 0; pfox->gen_rand_time = 0; pfox->texture_render_offset = pObject->Height; - //改变纹理 + // 改变纹理 pfox->pcurrent_display_texture= PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); } else @@ -2314,42 +2315,42 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) } } break; - case PX_OBJECT_FOX_STATE_RASING://狐狸升起 + case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 { pfox->elapsed += elapsed; - //升起纹理偏移量 + // 升起纹理偏移量 pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; if (pfox->texture_render_offset <= 0) { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT;//升起后嘲讽 + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_TAUNT://狐狸嘲讽 + case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 { pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500)//嘲讽时间 + if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt");//嘲讽纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 } - else if (pfox->elapsed>1500)//嘲讽结束 + else if (pfox->elapsed>1500) // 嘲讽结束 { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//逃跑 - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape");//逃跑纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_BEAT://狐狸被打 + case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 { pfox->elapsed += elapsed; if (pfox->elapsed>800) { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt");//受伤纹理 - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 } } break; @@ -2360,8 +2361,8 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) if (pfox->texture_render_offset >= pObject->Height) { pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE;//逃跑结束 - pfox->elapsed = 0;//重置时间 + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 + pfox->elapsed = 0; // 重置时间 pfox->pcurrent_display_texture = PX_NULL; } } @@ -2376,12 +2377,12 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); px_float x,y,width,height; PX_OBJECT_INHERIT_CODE(pObject,x,y,width,height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE);//清空渲染目标 + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 if (pfox->pcurrent_display_texture) { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL);//渲染狐狸 + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL);//以遮罩形式绘制纹理 + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 } PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) @@ -2390,12 +2391,12 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) PX_TextureFree(&pfox->render_target); } -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick)//狐狸被点击 +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 { PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING)//狐狸嘲讽或者升起时点击有效 + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM))//点击有效区域 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 { px_int x= (px_int)PX_Object_Event_GetCursorX(e); px_int y= (px_int)PX_Object_Event_GetCursorY(e); @@ -2427,34 +2428,34 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) PX_Object *PX_Object_FoxCreate(px_memorypool *mp,PX_Object *parent,px_float x,px_float y) { PX_Object_Fox* pfox; - px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing");//从资源管理器中获取纹理 + px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing"); // 从资源管理器中获取纹理 PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - pfox->state= PX_OBJECT_FOX_STATE_IDLE;//狐狸状态 - pfox->rasing_down_speed = 512;//升起速度 - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask");//遮罩 + pfox->state= PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 + pfox->rasing_down_speed = 512; // 升起速度 + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 if(!PX_TextureCreate(mp,&pfox->render_target,ptexture->width,ptexture->height)) { PX_ObjectDelete(pObject); return 0; } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0);//注册点击事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0);//注册重置事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0); // 注册点击事件 + PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0); // 注册重置事件 return pObject; } -PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender)//锤子渲染 +PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); px_float x, y, width, height; PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); if (phammer->bHit) { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//按下 + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 } else { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//未按下 + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 } } @@ -2468,20 +2469,20 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) { - pObject->x=PX_Object_Event_GetCursorX(e);//锤子跟随鼠标移动 + pObject->x=PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 pObject->y=PX_Object_Event_GetCursorY(e); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_TRUE;//按下 + phammer->bHit = PX_TRUE; // 按下 } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE;//抬起 + phammer->bHit = PX_FALSE; // 抬起 } PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) @@ -2492,11 +2493,11 @@ PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) phammer->bHit = PX_FALSE; if (!PX_LoadTextureFromFile(mp_static,&phammer->ham01, "assets/ham1.png")) return PX_NULL; if (!PX_LoadTextureFromFile(mp_static,&phammer->ham02, "assets/ham2.png")) return PX_NULL; - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL);//注册移动事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL);//注册拖拽事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL);//注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL);//注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL);//注册抬起事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 return pObject; } @@ -2507,7 +2508,7 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_StartGameOnClick) startgame->Visible = PX_FALSE; game->Enabled = PX_TRUE; PX_Object_ScorePanelSetScore(scorePanel, 0); - PX_Object_ClockBegin(gameclock, 30000);//开始游戏,游戏时间30秒 + PX_Object_ClockBegin(gameclock, 30000); // 开始游戏,游戏时间30秒 } @@ -2569,9 +2570,9 @@ px_int main() } ``` -你可以在 documents/demo/game 中找到这个游戏的完整资源, 并用 PainterEngine 直接编译。 +你可以在 documents/demo/game 中找到这个游戏的完整资源,并用 PainterEngine 直接编译。 ![](assets/img/18.5.gif) -在线试玩: [PainterEngine 在线应用 APP--打地鼠](https://www.painterengine.com/main/app/documentgame/) +在线试玩:[PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) From a4af119bd5a1e82c535e2471fcd0302f27d30f9e Mon Sep 17 00:00:00 2001 From: Recogerous <2737936634@qq.com> Date: Sat, 23 Nov 2024 09:06:45 +0800 Subject: [PATCH 3/9] optimize code style in md code block and ulist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add spaces between: - `if` and the left parenthesis, - before `return` in `if-return` statements, - around some operators, - around comments, - and between parameters. - Standardize punctuation in comments to use Chinese punctuation. - Replace `\t` tab indents with four spaces. In the document part outside the code blocks, make the following changes which were overlooked in the previous PR: - Add a space after `C++`. - Change unordered list syntax from `*` to `-`. --- - 在 if 和左括号之间、if-return 的 return 前面、部分运算符两边、 注释的中英文之间、参数之间添加空格; - 将注释中的标点统一为中文标点; - 将 `\t` 缩进换为四个空格缩进。 在代码块以外的文档部分中,作出如下修改,这是之前的 PR 中所遗漏的: - `C++` 右边添加空格; - 将无序列表的语法 `*` 改成 `-`。 --- documents/PainterEngine_the_book_zh-CN.md | 2144 ++++++++++----------- 1 file changed, 1071 insertions(+), 1073 deletions(-) diff --git a/documents/PainterEngine_the_book_zh-CN.md b/documents/PainterEngine_the_book_zh-CN.md index ea817dff..e3d12b5e 100644 --- a/documents/PainterEngine_the_book_zh-CN.md +++ b/documents/PainterEngine_the_book_zh-CN.md @@ -24,7 +24,7 @@ PainterEngine 同样经历了近乎十年的发展,但在很长的一段时间 #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); + PainterEngine_Initialize(800, 480); return 1; } ``` @@ -128,12 +128,12 @@ int main() int main() { PainterEngine_Initialize(800, 480); - // PainterEngine_DrawText - // 参数1:x坐标 - // 参数2:y坐标 - // 参数3:文本内容 - // 参数4:对齐方式 - // 参数5:颜色 + // PainterEngine_DrawText + // 参数1:x 坐标 + // 参数2:y 坐标 + // 参数3:文本内容 + // 参数4:对齐方式 + // 参数5:颜色 PainterEngine_DrawText(400, 240, "Hello PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); return 1; } @@ -150,15 +150,15 @@ int main() ```c typedef enum { - PX_ALIGN_LEFTTOP = 7, // 左上角对齐 - PX_ALIGN_MIDTOP = 8, // 中上对齐 - PX_ALIGN_RIGHTTOP = 9, // 右上角对齐 - PX_ALIGN_LEFTMID = 4, // 左中对齐 - PX_ALIGN_CENTER = 5, // 居中对齐 - PX_ALIGN_RIGHTMID = 6, // 右中对齐 - PX_ALIGN_LEFTBOTTOM = 1, // 左下角对齐 - PX_ALIGN_MIDBOTTOM = 2, // 中底对齐 - PX_ALIGN_RIGHTBOTTOM = 3, // 右下角对齐 + PX_ALIGN_LEFTTOP = 7, // 左上角对齐 + PX_ALIGN_MIDTOP = 8, // 中上对齐 + PX_ALIGN_RIGHTTOP = 9, // 右上角对齐 + PX_ALIGN_LEFTMID = 4, // 左中对齐 + PX_ALIGN_CENTER = 5, // 居中对齐 + PX_ALIGN_RIGHTMID = 6, // 右中对齐 + PX_ALIGN_LEFTBOTTOM = 1, // 左下角对齐 + PX_ALIGN_MIDBOTTOM = 2, // 中底对齐 + PX_ALIGN_RIGHTBOTTOM = 3, // 右下角对齐 }PX_ALIGN; ``` @@ -174,8 +174,8 @@ int main() { PainterEngine_Initialize(800, 480); // PainterEngine_DrawText - // 参数1:x坐标 - // 参数2:y坐标 + // 参数1:x 坐标 + // 参数2:y 坐标 // 参数3:文本内容 // 参数4:对齐方式 // 参数5:颜色 @@ -210,16 +210,16 @@ int main() `px_void PainterEngine_DrawLine(px_int x1, px_int y1, px_int x2, px_int y2, px_int linewidth, px_color color);` 这个函数用于绘制一条线段。 -* x1, y1:线段的起点坐标。 -* x2, y2:线段的终点坐标。 -* linewidth:线段的宽度。 -* color:线段的颜色。 +- x1, y1:线段的起点坐标。 +- x2, y2:线段的终点坐标。 +- linewidth:线段的宽度。 +- color:线段的颜色。 ```c #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); + PainterEngine_Initialize(800, 480); // 设置起点和终点坐标 px_int x1 = 50; px_int y1 = 50; @@ -241,10 +241,10 @@ int main() `px_void PainterEngine_DrawRect(px_int x, px_int y, px_int width, px_int height, px_color color);` 这个函数用于绘制一个矩形。 -* x, y:矩形的左上角坐标。 -* width:矩形的宽度。 -* height:矩形的高度。 -* color:矩形的颜色。 +- x, y:矩形的左上角坐标。 +- width:矩形的宽度。 +- height:矩形的高度。 +- color:矩形的颜色。 ![](assets/img/3.5.png) @@ -252,7 +252,7 @@ int main() #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); + PainterEngine_Initialize(800, 480); // 设置矩形的左上角坐标 px_int x = 100; px_int y = 100; @@ -262,7 +262,7 @@ int main() px_int height = 100; // 设置矩形的颜色 - px_color color = PX_COLOR(255, 0, 255,0 ); // 绿色 + px_color color = PX_COLOR(255, 0, 255, 0); // 绿色 // 绘制矩形 PainterEngine_DrawRect(x, y, width, height, color); @@ -273,16 +273,16 @@ int main() `px_void PainterEngine_DrawCircle(px_int x, px_int y, px_int radius, px_int linewidth, px_color color);` 这个函数用于绘制一个圆环。 -* x, y:圆心的坐标。 -* radius:圆的半径。 -* linewidth:圆的线宽。 -* color:圆的颜色。 +- x, y:圆心的坐标。 +- radius:圆的半径。 +- linewidth:圆的线宽。 +- color:圆的颜色。 ```c #include "PainterEngine.h" int main() { - PainterEngine_Initialize(800,480); + PainterEngine_Initialize(800, 480); // 设置圆心的坐标 px_int x = 200; px_int y = 200; @@ -303,9 +303,9 @@ int main() `px_void PainterEngine_DrawSolidCircle(px_int x, px_int y, px_int radius, px_color color);` 这个函数用于绘制一个实心圆。 -* x, y:圆心的坐标。 -* radius:圆的半径。 -* color:圆的颜色。 +- x, y:圆心的坐标。 +- radius:圆的半径。 +- color:圆的颜色。 ```c #include "PainterEngine.h" @@ -332,15 +332,15 @@ int main() ![](assets/img/3.7.png) -`px_void PainterEngine_DrawSector(px_int x, px_int y, px_int inside_radius,px_int outside_radius, px_int start_angle, px_int end_angle, px_color color);` +`px_void PainterEngine_DrawSector(px_int x, px_int y, px_int inside_radius, px_int outside_radius, px_int start_angle, px_int end_angle, px_color color);` 这个函数用于绘制一个扇形。 参数说明: -* x, y:扇形的圆心坐标。 -* inside_radius:扇形的内半径。 -* inside_radius:扇形的外半径。 -* start_angle:扇形的起始角度(以度为单位,支持负角度)。 -* end_angle:扇形的结束角度(以度为单位,支持负角度)。 -* color:扇形的颜色。 +- x, y:扇形的圆心坐标。 +- inside_radius:扇形的内半径。 +- inside_radius:扇形的外半径。 +- start_angle:扇形的起始角度(以度为单位,支持负角度)。 +- end_angle:扇形的结束角度(以度为单位,支持负角度)。 +- color:扇形的颜色。 ```c #include "PainterEngine.h" @@ -368,8 +368,8 @@ int main() `px_void PainterEngine_DrawPixel(px_int x, px_int y, px_color color);` 这个函数用于绘制一个像素点。 -* x, y:像素点的坐标。 -* color:像素点的颜色。 +- x, y:像素点的坐标。 +- color:像素点的颜色。 这只是绘制一个像素点,就不放示例图了。 @@ -383,9 +383,9 @@ PainterEngine 可以直接从文件中加载图片,它原生支持 PNG、JPG 在加载文件成功后,我们使用 `PainterEngine_DrawTexture` 函数绘制出来。这是一个四参数的函数: -* 第一个参数是我们之前的纹理结构体指针; -* 第二第三是需要绘制到的 x, y 坐标; -* 第四个则是之前我们说过的对齐方式。 +- 第一个参数是我们之前的纹理结构体指针; +- 第二第三是需要绘制到的 x, y 坐标; +- 第四个则是之前我们说过的对齐方式。 参考如下代码: @@ -395,11 +395,11 @@ px_texture mytexture; // 纹理 int main() { PainterEngine_Initialize(512, 512); - if(!PX_LoadTextureFromFile(mp_static,&mytexture,"assets/demo.png")) - { + if (!PX_LoadTextureFromFile(mp_static, &mytexture, "assets/demo.png")) + { // 加载纹理失败 - return 0; - } + return 0; + } PainterEngine_DrawTexture(&mytexture, 0, 0, PX_ALIGN_LEFTTOP); return 1; @@ -422,8 +422,8 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 - myalloc=MP_Malloc(&mp, 1024); // 在内存池中分配1024字节 + mp = PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + myalloc = MP_Malloc(&mp, 1024); // 在内存池中分配 1024 字节 return 1; } ``` @@ -437,15 +437,15 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 - myalloc=MP_Malloc(&mp, 1024*1024); // 在内存池中分配1024*1024字节,但内存池实际容量小于分配给内存池容量,因此这里内存不足,这里将会进入中断 + mp = PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + myalloc = MP_Malloc(&mp, 1024*1024); // 在内存池中分配 1024*1024 字节,但内存池实际容量小于分配给内存池容量,因此这里内存不足,将会进入中断 return 1; } ``` 如果你不希望因为内存池不足导致停机错误,你可以使用以下两种方式: -1.你可以设置错误回调,自行处理内存池的错误: +1. 你可以设置错误回调,自行处理内存池的错误: ```c #include "PainterEngine.h" @@ -453,33 +453,33 @@ unsigned char my_memory_cache[1024 * 1024]; PX_MEMORYPOOL_ERROR_FUNCTION(my_memory_cache_error) { - switch (error) - { - case PX_MEMORYPOOL_ERROR_OUTOFMEMORY: - printf("内存访问错误\n"); - break; - case PX_MEMORYPOOL_ERROR_INVALID_ACCESS: - printf("无法访问内存\n"); - break; - case PX_MEMORYPOOL_ERROR_INVALID_ADDRESS: - printf("无效的内存地址(UAF or double free)\n"); - break; - default: - break; - } + switch (error) + { + case PX_MEMORYPOOL_ERROR_OUTOFMEMORY: + printf("内存访问错误\n"); + break; + case PX_MEMORYPOOL_ERROR_INVALID_ACCESS: + printf("无法访问内存\n"); + break; + case PX_MEMORYPOOL_ERROR_INVALID_ADDRESS: + printf("无效的内存地址(UAF or double free)\n"); + break; + default: + break; + } } int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 - MP_ErrorCatch(&mp, my_memory_cache_error,0); // 设置错误回调 - myalloc=MP_Malloc(&mp, 1024*1024); // 在内存池中分配1024*1024字节 + mp = PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + MP_ErrorCatch(&mp, my_memory_cache_error, 0); // 设置错误回调 + myalloc = MP_Malloc(&mp, 1024*1024); // 在内存池中分配 1024*1024 字节 return 1; } ``` -2.或者你也可以直接关闭内存池的错误异常处理,那么当内存池无法正常分配足够内存时,将会直接返回 `NULL`: +2. 或者你也可以直接关闭内存池的错误异常处理,那么当内存池无法正常分配足够内存时,将会直接返回 `NULL`: ```c #include "PainterEngine.h" @@ -489,9 +489,9 @@ int main() { px_memorypool mp; px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 - MP_NoCatchError(&mp, PX_TRUE); // 设置内存池不捕获错误 - myalloc=MP_Malloc(&mp, 1024*1024); // 在内存池中分配1024*1024字节,但内存池不捕获错误,所以会直接返回NULL + mp = PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // 创建内存池 + MP_NoCatchError(&mp, PX_TRUE); // 设置内存池不捕获错误 + myalloc = MP_Malloc(&mp, 1024*1024); // 在内存池中分配 1024*1024 字节,但内存池不捕获错误,所以会直接返回 NULL return 1; } ``` @@ -522,8 +522,8 @@ int main() { PX_Object* myButtonObject; PainterEngine_Initialize(800, 480); - PainterEngine_LoadFontModule("assets/font.ttf",PX_FONTMODULE_CODEPAGE_GBK,20); - myButtonObject=PX_Object_PushButtonCreate(mp,root,300,200,200,80,"我是一个按钮", PainterEngine_GetFontModule()); + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); + myButtonObject = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 80, "我是一个按钮", PainterEngine_GetFontModule()); return 1; } ``` @@ -557,9 +557,9 @@ int main() 以上 `Update`、`Render`、`Free` 函数具有传递的特性,也就是说: -* 如果某个对象节点执行了 `Update`,那么它的所有子对象也会执行 `Update` -* 如果某个对象节点执行了 `Render`,那么它的所有子对象也会执行 `Render` -* 如果某个对象节点执行了 `Free`,那么它的所有子对象也会执行 `Free`,父对象被删除了,它的子节点也会被删除,并且将会一直迭代到以这个节点为根节点的所有子节点都被删除。 +- 如果某个对象节点执行了 `Update`,那么它的所有子对象也会执行 `Update` +- 如果某个对象节点执行了 `Render`,那么它的所有子对象也会执行 `Render` +- 如果某个对象节点执行了 `Free`,那么它的所有子对象也会执行 `Free`,父对象被删除了,它的子节点也会被删除,并且将会一直迭代到以这个节点为根节点的所有子节点都被删除。 因此,在上一章节我们创建了按钮,并将它连接到了 `root` 节点,那么我们是不需要自己再手动执行 `Update`、`Render`、`Free` 函数的(在 `PX_Object_PushButton.c` 中它们已经被写好了),因为根节点 `root` 是被自动更新渲染和释放的,我们只需要负责 `Create` 就可以了。 @@ -571,8 +571,8 @@ int main() { PX_Object* myButtonObject; PainterEngine_Initialize(800, 480); - PainterEngine_LoadFontModule("assets/font.ttf",PX_FONTMODULE_CODEPAGE_GBK,20); - myButtonObject=PX_Object_PushButtonCreate(mp,root,300,200,200,80,"我是一个按钮", PainterEngine_GetFontModule()); + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); + myButtonObject = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 80, "我是一个按钮", PainterEngine_GetFontModule()); PX_ObjectDelayDelete(myButtonObject); // 删除对象 return 1; } @@ -589,16 +589,16 @@ int main() PX_OBJECT_EVENT_FUNCTION(OnButtonClick) { - PX_Object_PushButtonSetText(pObject,"我被点击了"); + PX_Object_PushButtonSetText(pObject, "我被点击了"); } int main() { PX_Object* myButtonObject; PainterEngine_Initialize(800, 480); - PainterEngine_LoadFontModule("assets/font.ttf",PX_FONTMODULE_CODEPAGE_GBK,20); - myButtonObject=PX_Object_PushButtonCreate(mp,root,300,200,200,80,"我是一个按钮", PainterEngine_GetFontModule()); - PX_ObjectRegisterEvent(myButtonObject,PX_OBJECT_EVENT_EXECUTE,OnButtonClick,0); + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); + myButtonObject = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 80, "我是一个按钮", PainterEngine_GetFontModule()); + PX_ObjectRegisterEvent(myButtonObject, PX_OBJECT_EVENT_EXECUTE, OnButtonClick, 0); return 1; } @@ -609,7 +609,7 @@ int main() 其中,`PX_OBJECT_EVENT_FUNCTION` 是一个宏,因为事件响应函数是一个固定的格式,因此非常建议你使用宏的方式来申明它,它的定义原型如下: ```c -#define PX_OBJECT_EVENT_FUNCTION(name) px_void name(PX_Object *pObject,PX_Object_Event e,px_void * ptr) +#define PX_OBJECT_EVENT_FUNCTION(name) px_void name(PX_Object *pObject, PX_Object_Event e, px_void * ptr) ``` 可以看到,这个回调函数有 3 个参数,第一个是响应时间的对象的指针,因为是按钮点击被触发了,所以这个指针指向的就是这个按钮对象;第二个参数是事件类型 `e`,它是触发的事件类型;最后一个参数则是用户传递来的指针,它在注册时间响应函数 `PX_ObjectRegisterEvent` 被调用时就被传递进来了。 @@ -617,37 +617,37 @@ int main() 事件类型有以下几种: ```c -#define PX_OBJECT_EVENT_ANY 0 //任意事件 -#define PX_OBJECT_EVENT_CURSORMOVE 1 //鼠标移动 -#define PX_OBJECT_EVENT_CURSORUP 2 //鼠标左键弹起或触摸屏弹起 -#define PX_OBJECT_EVENT_CURSORRDOWN 3 //鼠标右键按下 -#define PX_OBJECT_EVENT_CURSORDOWN 4 //鼠标左键按下或触摸屏按下 -#define PX_OBJECT_EVENT_CURSORRUP 5 //鼠标右键弹起 -#define PX_OBJECT_EVENT_CURSOROVER 6 //鼠标进入范围 -#define PX_OBJECT_EVENT_CURSOROUT 7 //鼠标离开范围 -#define PX_OBJECT_EVENT_CURSORWHEEL 8 //鼠标滚轮 -#define PX_OBJECT_EVENT_CURSORCLICK 9 //鼠标左键点击 -#define PX_OBJECT_EVENT_CURSORDRAG 10 //鼠标拖拽 -#define PX_OBJECT_EVENT_STRING 11 //字符串事件(输入法输入) -#define PX_OBJECT_EVENT_EXECUTE 12 //执行事件,不同组件有不同的执行方式 -#define PX_OBJECT_EVENT_VALUECHANGED 13 //值改变事件,例如滑动条的值改变,或者文本框的值改变,或者列表框的选中项改变 -#define PX_OBJECT_EVENT_DRAGFILE 14 //拖拽文件 -#define PX_OBJECT_EVENT_KEYDOWN 15 //键盘按下 -#define PX_OBJECT_EVENT_KEYUP 16 //键盘弹起 -#define PX_OBJECT_EVENT_IMPACT 17 //碰撞事件 -#define PX_OBJECT_EVENT_SCALE 18 //缩放事件 -#define PX_OBJECT_EVENT_WINDOWRESIZE 19 //窗口大小改变 -#define PX_OBJECT_EVENT_ONFOCUS 20 //获得焦点 -#define PX_OBJECT_EVENT_LOSTFOCUS 21 //失去焦点 -#define PX_OBJECT_EVENT_CANCEL 22 //取消事件 -#define PX_OBJECT_EVENT_CLOSE 23 //关闭事件 -#define PX_OBJECT_EVENT_CURSORMUP 24 //鼠标中键弹起 -#define PX_OBJECT_EVENT_CURSORMDOWN 25 //鼠标中键按下 -#define PX_OBJECT_EVENT_REQUESTDATA 26 //请求数据 -#define PX_OBJECT_EVENT_OPEN 27 //打开事件 -#define PX_OBJECT_EVENT_SAVE 28 //保存事件 -#define PX_OBJECT_EVENT_TIMEOUT 29 //超时事件 -#define PX_OBJECT_EVENT_DAMAGE 30 //伤害事件 +#define PX_OBJECT_EVENT_ANY 0 // 任意事件 +#define PX_OBJECT_EVENT_CURSORMOVE 1 // 鼠标移动 +#define PX_OBJECT_EVENT_CURSORUP 2 // 鼠标左键弹起或触摸屏弹起 +#define PX_OBJECT_EVENT_CURSORRDOWN 3 // 鼠标右键按下 +#define PX_OBJECT_EVENT_CURSORDOWN 4 // 鼠标左键按下或触摸屏按下 +#define PX_OBJECT_EVENT_CURSORRUP 5 // 鼠标右键弹起 +#define PX_OBJECT_EVENT_CURSOROVER 6 // 鼠标进入范围 +#define PX_OBJECT_EVENT_CURSOROUT 7 // 鼠标离开范围 +#define PX_OBJECT_EVENT_CURSORWHEEL 8 // 鼠标滚轮 +#define PX_OBJECT_EVENT_CURSORCLICK 9 // 鼠标左键点击 +#define PX_OBJECT_EVENT_CURSORDRAG 10 // 鼠标拖拽 +#define PX_OBJECT_EVENT_STRING 11 // 字符串事件(输入法输入) +#define PX_OBJECT_EVENT_EXECUTE 12 // 执行事件,不同组件有不同的执行方式 +#define PX_OBJECT_EVENT_VALUECHANGED 13 // 值改变事件,例如滑动条的值改变,或者文本框的值改变,或者列表框的选中项改变 +#define PX_OBJECT_EVENT_DRAGFILE 14 // 拖拽文件 +#define PX_OBJECT_EVENT_KEYDOWN 15 // 键盘按下 +#define PX_OBJECT_EVENT_KEYUP 16 // 键盘弹起 +#define PX_OBJECT_EVENT_IMPACT 17 // 碰撞事件 +#define PX_OBJECT_EVENT_SCALE 18 // 缩放事件 +#define PX_OBJECT_EVENT_WINDOWRESIZE 19 // 窗口大小改变 +#define PX_OBJECT_EVENT_ONFOCUS 20 // 获得焦点 +#define PX_OBJECT_EVENT_LOSTFOCUS 21 // 失去焦点 +#define PX_OBJECT_EVENT_CANCEL 22 // 取消事件 +#define PX_OBJECT_EVENT_CLOSE 23 // 关闭事件 +#define PX_OBJECT_EVENT_CURSORMUP 24 // 鼠标中键弹起 +#define PX_OBJECT_EVENT_CURSORMDOWN 25 // 鼠标中键按下 +#define PX_OBJECT_EVENT_REQUESTDATA 26 // 请求数据 +#define PX_OBJECT_EVENT_OPEN 27 // 打开事件 +#define PX_OBJECT_EVENT_SAVE 28 // 保存事件 +#define PX_OBJECT_EVENT_TIMEOUT 29 // 超时事件 +#define PX_OBJECT_EVENT_DAMAGE 30 // 伤害事件 ``` 以上事件并非全部都是任何组件都会响应的,例如在上面例子中的 `PX_OBJECT_EVENT_EXECUTE`,它是按钮被单击时会被触发的事件,或者是文本框中按下回车会触发的事件,但有些例如滚动条和进度条,并不会触发这个事件。也就是说有些事件是专属的。 @@ -657,9 +657,9 @@ int main() 你可以使用 ```c -px_float PX_Object_Event_GetCursorX(PX_Object_Event e); // 获取cursor事件的x坐标 -px_float PX_Object_Event_GetCursorY(PX_Object_Event e); // 获取cursor事件的y坐标 -px_float PX_Object_Event_GetCursorZ(PX_Object_Event e); // 获取cursor事件的z坐标,一般用于鼠标中键滚轮 +px_float PX_Object_Event_GetCursorX(PX_Object_Event e); // 获取cursor事件的 x 坐标 +px_float PX_Object_Event_GetCursorY(PX_Object_Event e); // 获取cursor事件的 y 坐标 +px_float PX_Object_Event_GetCursorZ(PX_Object_Event e); // 获取cursor事件的 z 坐标,一般用于鼠标中键滚轮 ``` 来获取 `cursor` 事件中类似于 "鼠标现在在哪里" 的功能。 @@ -682,44 +682,44 @@ px_int index = 0; // 当前图片的索引 PX_OBJECT_EVENT_FUNCTION(OnButtonPreClick) { index--; - if(index < 0) - { - index = 9; - } - PX_Object_ImageSetTexture(Image, &my_texture[index]); // 设置图片 + if (index < 0) + { + index = 9; + } + PX_Object_ImageSetTexture(Image, &my_texture[index]); // 设置图片 } PX_OBJECT_EVENT_FUNCTION(OnButtonNextClick) { - index++; - if(index > 9) - { - index = 0; - } - PX_Object_ImageSetTexture(Image, &my_texture[index]); + index++; + if (index > 9) + { + index = 0; + } + PX_Object_ImageSetTexture(Image, &my_texture[index]); } int main() { px_int i; PainterEngine_Initialize(512, 560); // 初始化 - for(i=0;i<10;i++) - { + for(i = 0; i < 10; i++) + { px_char path[256]; - PX_sprintf1(path,256, "assets/%1.png", PX_STRINGFORMAT_INT(i+1)); - if(!PX_LoadTextureFromFile(mp_static, &my_texture[i],path)) // 加载图片 - { + PX_sprintf1(path, 256, "assets/%1.png", PX_STRINGFORMAT_INT(i+1)); + if (!PX_LoadTextureFromFile(mp_static, &my_texture[i], path)) // 加载图片 + { // 加载失败 printf("加载失败"); - return 0; - } - } + return 0; + } + } PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); // 加载字体 Image = PX_Object_ImageCreate(mp, root, 0, 0, 512, 512, 0); // 创建图片对象 - Previous= PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "上一张",PainterEngine_GetFontModule()); // 创建按钮对象 + Previous = PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "上一张", PainterEngine_GetFontModule()); // 创建按钮对象 Next = PX_Object_PushButtonCreate(mp, root, 256, 512, 256, 48, "下一张", PainterEngine_GetFontModule()); // 创建按钮对象 - PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL); // 注册按钮事件 - PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL); // 注册按钮事件 + PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL); // 注册按钮事件 + PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL); // 注册按钮事件 return 1; } ``` @@ -734,169 +734,169 @@ int main() 你可以在 `PainterEngine/kernel` 的文件中,找到 PainterEngine 的内置组件,所有的组件名称都是以 `PX_Object_XXXXX` 开头的,在这里,我为你列举一些常用的组件及示范代码: -* 文本框: +- 文本框: ```c #include "PainterEngine.h" PX_OBJECT_EVENT_FUNCTION(PX_Object_EditOnTextChanged) { - // 文本改变后后这里会被执行 + // 文本改变后后这里会被执行 } int main() { - PX_Object* pObject; - PainterEngine_Initialize(600, 400); - // 创建文本框 - pObject=PX_Object_EditCreate(mp,root,200,180,200,40,0); - // 注册编辑框文本改变事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_VALUECHANGED, PX_Object_EditOnTextChanged,PX_NULL); - return 0; + PX_Object* pObject; + PainterEngine_Initialize(600, 400); + // 创建文本框 + pObject = PX_Object_EditCreate(mp, root, 200, 180, 200, 40, 0); + // 注册编辑框文本改变事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, PX_Object_EditOnTextChanged, PX_NULL); + return 0; } ``` ![](assets/img/11.1.gif) -* 列表框: +- 列表框: ```c #include "PainterEngine.h" PX_OBJECT_RENDER_FUNCTION(PX_Object_OnMyListItemRender) { - px_float objx,objy,objWidth,objHeight; - PX_Object_ListItem *pItem=PX_Object_GetListItem(pObject); - PX_OBJECT_INHERIT_CODE(pObject,objx, objy, objWidth, objHeight); - // 绘制出其文本 - PX_FontModuleDrawText(psurface, 0, (px_int)objx + 3, (px_int)objy + 3, PX_ALIGN_LEFTTOP, (const px_char *)pItem->pdata, PX_COLOR_WHITE); + px_float objx, objy, objWidth, objHeight; + PX_Object_ListItem *pItem = PX_Object_GetListItem(pObject); + PX_OBJECT_INHERIT_CODE(pObject, objx, objy, objWidth, objHeight); + // 绘制出其文本 + PX_FontModuleDrawText(psurface, 0, (px_int)objx + 3, (px_int)objy + 3, PX_ALIGN_LEFTTOP, (const px_char *)pItem->pdata, PX_COLOR_WHITE); } PX_OBJECT_LIST_ITEM_CREATE_FUNCTION(PX_Object_OnMyListItemCreate) { - // 绑定ListItem的渲染函数 - ItemObject->Func_ObjectRender[0]=PX_Object_OnMyListItemRender; - return PX_TRUE; + // 绑定 ListItem 的渲染函数 + ItemObject->Func_ObjectRender[0] = PX_Object_OnMyListItemRender; + return PX_TRUE; } PX_OBJECT_EVENT_FUNCTION(PX_Object_ListOnSelectChanged) { - // 当选中项改变时 - return; + // 当选中项改变时 + return; } int main() { - PX_Object* pObject; - PainterEngine_Initialize(600, 400); + PX_Object* pObject; + PainterEngine_Initialize(600, 400); - // 创建list - pObject = PX_Object_ListCreate(mp,root,100,100,400,200,24,PX_Object_OnMyListItemCreate,0); - PX_Object_ListAdd(pObject, "Item1"); - PX_Object_ListAdd(pObject, "Item2"); - PX_Object_ListAdd(pObject, "Item3"); - PX_Object_ListAdd(pObject, "Item4"); - PX_Object_ListAdd(pObject, "Item5"); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, PX_Object_ListOnSelectChanged, 0); - return 0; + // 创建 list + pObject = PX_Object_ListCreate(mp, root, 100, 100, 400, 200, 24, PX_Object_OnMyListItemCreate, 0); + PX_Object_ListAdd(pObject, "Item1"); + PX_Object_ListAdd(pObject, "Item2"); + PX_Object_ListAdd(pObject, "Item3"); + PX_Object_ListAdd(pObject, "Item4"); + PX_Object_ListAdd(pObject, "Item5"); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, PX_Object_ListOnSelectChanged, 0); + return 0; } ``` ![](assets/img/11.2.gif) -* 滑动条: +- 滑动条: ```c #include "PainterEngine.h" PX_OBJECT_EVENT_FUNCTION(SliderChanged) { - // 垂直滑动条值改变后执行这里的代码 - return; + // 垂直滑动条值改变后执行这里的代码 + return; } int main() { - PX_Object* pObject; - PainterEngine_Initialize(600, 400); - // 水平滑动条 - PX_Object_SliderBarCreate(mp, root, 200, 50, 200,24,PX_OBJECT_SLIDERBAR_TYPE_HORIZONTAL,PX_OBJECT_SLIDERBAR_STYLE_BOX); - // 垂直滑动条 - pObject=PX_Object_SliderBarCreate(mp, root, 200, 100, 24, 200, PX_OBJECT_SLIDERBAR_TYPE_VERTICAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, SliderChanged, 0); - return 0; + PX_Object* pObject; + PainterEngine_Initialize(600, 400); + // 水平滑动条 + PX_Object_SliderBarCreate(mp, root, 200, 50, 200, 24, PX_OBJECT_SLIDERBAR_TYPE_HORIZONTAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); + // 垂直滑动条 + pObject = PX_Object_SliderBarCreate(mp, root, 200, 100, 24, 200, PX_OBJECT_SLIDERBAR_TYPE_VERTICAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, SliderChanged, 0); + return 0; } ``` ![](assets/img/11.3.gif) -* 下拉框: +- 下拉框: ```c #include "PainterEngine.h" int main() { - PX_Object* pObject; - PainterEngine_Initialize(600, 400); - pObject = PX_Object_SelectBarCreate(mp, root, 200, 150, 200,24,0); - PX_Object_SelectBarAddItem(pObject, "Item1"); - PX_Object_SelectBarAddItem(pObject, "Item2"); - PX_Object_SelectBarAddItem(pObject, "Item3"); - PX_Object_SelectBarAddItem(pObject, "Item4"); - PX_Object_SelectBarAddItem(pObject, "Item5"); - return 0; + PX_Object* pObject; + PainterEngine_Initialize(600, 400); + pObject = PX_Object_SelectBarCreate(mp, root, 200, 150, 200, 24, 0); + PX_Object_SelectBarAddItem(pObject, "Item1"); + PX_Object_SelectBarAddItem(pObject, "Item2"); + PX_Object_SelectBarAddItem(pObject, "Item3"); + PX_Object_SelectBarAddItem(pObject, "Item4"); + PX_Object_SelectBarAddItem(pObject, "Item5"); + return 0; } ``` ![](assets/img/11.4.gif) -* 示波器: +- 示波器: ```c #include "PainterEngine.h" -// 必须是生存域内有效可访问的数据,这里定义为全局变量 +// 必须是生存域内有效可访问的数据,这里定义为全局变量 px_double data_x[100]; px_double data_y[100]; int main() { - PX_Object_OscilloscopeData data; - PX_Object* pObject; - - px_int i; - PainterEngine_Initialize(600, 600); - - // 初始化一个测试数据 - for (i = 0; i < 100; i++) - { - data_x[i] = i; - data_y[i] = i+PX_randRange(-10,10); - } - - pObject = PX_Object_OscilloscopeCreate(mp, root, 0, 0, 600, 600, 0); - - // 设置水平坐标最小值最大值 - PX_Object_OscilloscopeSetHorizontalMin(pObject, 0); - PX_Object_OscilloscopeSetHorizontalMax(pObject, 100); - - // 设置垂直坐标(左边)最小值最大值0-100 - PX_Object_OscilloscopeSetLeftVerticalMin(pObject, 0); - PX_Object_OscilloscopeSetLeftVerticalMax(pObject, 100); - - // 数据类型 - data.Color=PX_COLOR(255,192,255,128); // 数据颜色 - data.ID = 0; - data.linewidth = 3; // 数据线宽 - data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT; // 数据映射到左边垂直坐标 - data.MapHorizontalArray = data_x; // 数据水平坐标 - data.MapVerticalArray = data_y; // 数据垂直坐标 - data.Size = 100; // 数据大小 - data.Visibled = PX_TRUE; // 数据可见 - data.Normalization = 1; // 数据归一化系数为1 - - // 添加数据 - PX_Object_OscilloscopeAddData(pObject, data); - return 0; + PX_Object_OscilloscopeData data; + PX_Object* pObject; + + px_int i; + PainterEngine_Initialize(600, 600); + + // 初始化一个测试数据 + for (i = 0; i < 100; i++) + { + data_x[i] = i; + data_y[i] = i+PX_randRange(-10, 10); + } + + pObject = PX_Object_OscilloscopeCreate(mp, root, 0, 0, 600, 600, 0); + + // 设置水平坐标最小值最大值 + PX_Object_OscilloscopeSetHorizontalMin(pObject, 0); + PX_Object_OscilloscopeSetHorizontalMax(pObject, 100); + + // 设置垂直坐标(左边)最小值最大值 0-100 + PX_Object_OscilloscopeSetLeftVerticalMin(pObject, 0); + PX_Object_OscilloscopeSetLeftVerticalMax(pObject, 100); + + // 数据类型 + data.Color = PX_COLOR(255, 192, 255, 128); // 数据颜色 + data.ID = 0; + data.linewidth = 3; // 数据线宽 + data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT; // 数据映射到左边垂直坐标 + data.MapHorizontalArray = data_x; // 数据水平坐标 + data.MapVerticalArray = data_y; // 数据垂直坐标 + data.Size = 100; // 数据大小 + data.Visibled = PX_TRUE; // 数据可见 + data.Normalization = 1; // 数据归一化系数为 1 + + // 添加数据 + PX_Object_OscilloscopeAddData(pObject, data); + return 0; } ``` @@ -910,7 +910,7 @@ int main() PainterEngine 鼓励组件式的开发架构。也就是说,不论是游戏还是 GUI 交互程序,甚至是程序功能,我们都可以用组件的形式去开发它。 -组件式开发有点类似于 C++中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数,你可以参考 [前面的对象传递机制](#8painterengine-对象传递机制) 这一章节。 +组件式开发有点类似于 C++ 中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数,你可以参考 [前面的对象传递机制](#8painterengine-对象传递机制) 这一章节。 为了演示这一点,让我们来实现一个“可控拖动旋转图片组件”,即我们可以用鼠标拖动图片在界面的位置,并用鼠标中键来旋转它。 @@ -920,23 +920,23 @@ PainterEngine 鼓励组件式的开发架构。也就是说,不论是游戏还 #include "PainterEngine.h" typedef struct { - px_texture image; - px_int rotation; + px_texture image; + px_int rotation; }PX_Object_MyObject; px_int main() { - PainterEngine_Initialize(800, 480); - return PX_TRUE; + PainterEngine_Initialize(800, 480); + return PX_TRUE; } ``` 之后,我们需要定义我们的 `Create`、`Update`、`Render` 和 `Free` 函数,其中 `Update`、`Render`、`Free` 有对应的格式,它们都有一个宏来简化我们的定义过程: ```c -#define PX_OBJECT_RENDER_FUNCTION(name) px_void name(px_surface *psurface,PX_Object *pObject,px_int idesc,px_dword elapsed) -#define PX_OBJECT_UPDATE_FUNCTION(name) px_void name(PX_Object *pObject,px_int idesc,px_dword elapsed) -#define PX_OBJECT_FREE_FUNCTION(name) px_void name(PX_Object *pObject,px_int idesc) +#define PX_OBJECT_RENDER_FUNCTION(name) px_void name(px_surface *psurface, PX_Object *pObject, px_int idesc, px_dword elapsed) +#define PX_OBJECT_UPDATE_FUNCTION(name) px_void name(PX_Object *pObject, px_int idesc, px_dword elapsed) +#define PX_OBJECT_FREE_FUNCTION(name) px_void name(PX_Object *pObject, px_int idesc) ``` 那么,在主函数中,我们就可以这样定义我们的这几个函数: @@ -945,8 +945,8 @@ px_int main() #include "PainterEngine.h" typedef struct { - px_texture image; - px_int rotation; + px_texture image; + px_int rotation; }PX_Object_MyObject; PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) @@ -955,51 +955,51 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation); + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image); + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureFree(&pMyObject->image); } px_int main() { - PainterEngine_Initialize(800, 480); - return PX_TRUE; + PainterEngine_Initialize(800, 480); + return PX_TRUE; } ``` 其中,因为我们不需要更新一些物理信息,所以 `MyObjectUpdate` 函数中我们可以什么都不写,在 `MyObjectRender` 中我们只需要把图片绘制出来就可以了,这里我们先使用 `PX_ObjectGetDesc` 函数获得我们定义好的结构体指针,它的第一个参数是结构体类型,第二个参数则是函数传递进来的 `pObject` 指针,然后我们只需要用 `PX_TextureRenderEx` 函数把图片绘制出来就可以了。 多提一句,`PX_TextureRenderEx` 函数用于在指定的表面上渲染纹理,并提供了对齐、混合、缩放和旋转等扩展选项。其中: - * `psurface`:指向要渲染纹理的表面的指针。 - * `resTexture`:指向要渲染的纹理资源的指针。 - * `x`:在表面上绘制纹理的 x 坐标。 - * `y`:在表面上绘制纹理的 y 坐标。 - * `refPoint`:对齐的参考点(例如,中心,左上角等)。 - * `blend`:指向混合选项结构的指针(如果不需要混合,可以为 `NULL`)。 - * `scale`:纹理的缩放因子(1.0 表示不缩放)。 - * `Angle`:纹理的旋转角度,以度为单位。 + - `psurface`:指向要渲染纹理的表面的指针。 + - `resTexture`:指向要渲染的纹理资源的指针。 + - `x`:在表面上绘制纹理的 x 坐标。 + - `y`:在表面上绘制纹理的 y 坐标。 + - `refPoint`:对齐的参考点(例如,中心,左上角等)。 + - `blend`:指向混合选项结构的指针(如果不需要混合,可以为 `NULL`)。 + - `scale`:纹理的缩放因子(1.0 表示不缩放)。 + - `Angle`:纹理的旋转角度,以度为单位。 最后,是时候编写创建新对象的函数了,这里我们需要用到 `PX_ObjectCreateEx` 函数,`PX_ObjectCreateEx` 函数用于创建一个扩展对象,并初始化其属性和回调函数。它的参数说明如下: -* `mp`:指向内存池的指针,用于分配对象所需的内存。 -* `Parent`:指向父对象的指针,如果没有父对象则为 `NULL`。 -* `x`:对象在 x 轴上的初始位置。 -* `y`:对象在 y 轴上的初始位置。 -* `z`:对象在 z 轴上的初始位置,z 坐标会影响其渲染的先后顺序。 -* `Width`:对象的宽度。 -* `Height`:对象的高度。 -* `Lenght`:对象的长度,2D 对象,一般可以是 0。 -* `type`:对象的类型。 -* `Func_ObjectUpdate`:指向对象更新函数的指针。 -* `Func_ObjectRender`:指向对象渲染函数的指针。 -* `Func_ObjectFree`:指向对象释放函数的指针。 -* `desc`:指向对象描述数据的指针。你可以设置为 0,创建时会把这个对象类型的数据填充为 0。 -* `size`:描述数据的大小,就是你定义的对象结构体类型的大小,创建对象函数会在内存池申请一段内存空间,并用于存储你的对象结构体。 +- `mp`:指向内存池的指针,用于分配对象所需的内存。 +- `Parent`:指向父对象的指针,如果没有父对象则为 `NULL`。 +- `x`:对象在 x 轴上的初始位置。 +- `y`:对象在 y 轴上的初始位置。 +- `z`:对象在 z 轴上的初始位置,z 坐标会影响其渲染的先后顺序。 +- `Width`:对象的宽度。 +- `Height`:对象的高度。 +- `Lenght`:对象的长度,2D 对象,一般可以是 0。 +- `type`:对象的类型。 +- `Func_ObjectUpdate`:指向对象更新函数的指针。 +- `Func_ObjectRender`:指向对象渲染函数的指针。 +- `Func_ObjectFree`:指向对象释放函数的指针。 +- `desc`:指向对象描述数据的指针。你可以设置为 0,创建时会把这个对象类型的数据填充为 0。 +- `size`:描述数据的大小,就是你定义的对象结构体类型的大小,创建对象函数会在内存池申请一段内存空间,并用于存储你的对象结构体。 在创建好一个空对象后,我们使用 `PX_ObjectGetDescIndex` 将对象中的对象结构体指针取出来,这是一个三参数的函数,第一个参数是对象结构体类型,第二个参数则是 `PX_Object *` 指针类型,因为一个 `PX_Object` 可以将多个对象结构体组合在一起,这个组合结构体我们将在之后的教程中会进一步描述,但现在我们只需要知道,调用 `PX_ObjectCreateEx` 函数后,其第一个存储的对象结构体索引是 0 就可以了。 @@ -1009,8 +1009,8 @@ px_int main() #include "PainterEngine.h" typedef struct { - px_texture image; - px_int rotation; + px_texture image; + px_int rotation; }PX_Object_MyObject; PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) @@ -1019,34 +1019,34 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation); // 渲染图片 + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); // 渲染图片 } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image); // 释放图片 + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureFree(&pMyObject->image); // 释放图片 } PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0); // 取得自定义对象数据 - pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png")) // 加载图片 - { - PX_ObjectDelete(pObject); // 加载失败则删除对象 - return PX_NULL; - } - return pObject; + PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // 取得自定义对象数据 + pMyObject->rotation = 0; + if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // 加载图片 + { + PX_ObjectDelete(pObject); // 加载失败则删除对象 + return PX_NULL; + } + return pObject; } px_int main() { - PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240); // 创建一个自定义对象 - return PX_TRUE; + PainterEngine_Initialize(800, 480); + PX_Object_MyObjectCreate(mp, root, 400, 240); // 创建一个自定义对象 + return PX_TRUE; } ``` @@ -1060,8 +1060,8 @@ px_int main() #include "PainterEngine.h" typedef struct { - px_texture image; - px_float rotation; + px_texture image; + px_float rotation; }PX_Object_MyObject; PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) @@ -1070,42 +1070,42 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation); // 渲染图片 + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); // 渲染图片 } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image); // 释放图片 + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureFree(&pMyObject->image); // 释放图片 } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDescIndex(PX_Object_MyObject,pObject,0); - if(PX_ObjectIsCursorInRegion(pObject,e)) // Object是鼠标位置是否选中当前组件,e是事件 - pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; + PX_Object_MyObject *pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + if (PX_ObjectIsCursorInRegion(pObject, e)) // Object 是鼠标位置是否选中当前组件,e 是事件 + pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; } PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0); // 取得自定义对象数据 - pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png")) // 加载图片 - { - PX_ObjectDelete(pObject); // 加载失败则删除对象 - return PX_NULL; - } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0); // 注册鼠标滚轮事件 - return pObject; + PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // 取得自定义对象数据 + pMyObject->rotation = 0; + if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // 加载图片 + { + PX_ObjectDelete(pObject); // 加载失败则删除对象 + return PX_NULL; + } + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORWHEEL, MyObjectOnCursorWheel, 0); // 注册鼠标滚轮事件 + return pObject; } px_int main() { - PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240); // 创建一个自定义对象 - return PX_TRUE; + PainterEngine_Initialize(800, 480); + PX_Object_MyObjectCreate(mp, root, 400, 240); // 创建一个自定义对象 + return PX_TRUE; } ``` @@ -1118,8 +1118,8 @@ px_int main() ```c PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, pMyObject->rotation); // 渲染图片 + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, pMyObject->rotation); // 渲染图片 } ``` @@ -1132,10 +1132,10 @@ PX_OBJECT_RENDER_FUNCTION(MyObjectRender) #include "PainterEngine.h" typedef struct { - px_float last_cursorx, last_cursory; - px_bool bselect; - px_texture image; - px_float rotation; + px_float last_cursorx, last_cursory; + px_bool bselect; + px_texture image; + px_float rotation; }PX_Object_MyObject; PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) @@ -1144,75 +1144,75 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, (px_int)pMyObject->rotation); // 渲染图片 + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, (px_int)pMyObject->rotation); // 渲染图片 } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image); // 释放图片 + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureFree(&pMyObject->image); // 释放图片 } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) { - PX_Object_MyObject *pMyObject=PX_ObjectGetDescIndex(PX_Object_MyObject,pObject,0); - if(PX_ObjectIsCursorInRegionAlign(pObject,e,PX_ALIGN_CENTER)) // Object是鼠标位置是否选中当前组件,e是事件 - pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; + PX_Object_MyObject *pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Object 是鼠标位置是否选中当前组件,e 是事件 + pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDown) { - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Object是鼠标位置是否选中当前组件,e是事件 - { - pMyObject->bselect = PX_TRUE; - pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); - pMyObject->last_cursory = PX_Object_Event_GetCursorY(e); - } + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Object 是鼠标位置是否选中当前组件,e 是事件 + { + pMyObject->bselect = PX_TRUE; + pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); + pMyObject->last_cursory = PX_Object_Event_GetCursorY(e); + } } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorRelease) { - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - pMyObject->bselect = PX_FALSE; + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + pMyObject->bselect = PX_FALSE; } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDrag) { - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - if (pMyObject->bselect) - { - pObject->x += PX_Object_Event_GetCursorX(e) - pMyObject->last_cursorx; - pObject->y += PX_Object_Event_GetCursorY(e) - pMyObject->last_cursory; - } - pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); - pMyObject->last_cursory = PX_Object_Event_GetCursorY(e); + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + if (pMyObject->bselect) + { + pObject->x += PX_Object_Event_GetCursorX(e) - pMyObject->last_cursorx; + pObject->y += PX_Object_Event_GetCursorY(e) - pMyObject->last_cursory; + } + pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); + pMyObject->last_cursory = PX_Object_Event_GetCursorY(e); } PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0); // 取得自定义对象数据 - pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png")) // 加载图片 - { - PX_ObjectDelete(pObject); // 加载失败则删除对象 - return PX_NULL; - } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0); // 注册鼠标滚轮事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDRAG,MyObjectOnCursorDrag,0); // 注册鼠标拖拽事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,MyObjectOnCursorDown,0); // 注册鼠标按下事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORUP,MyObjectOnCursorRelease,0); // 注册鼠标释放事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0); // 注册鼠标释放事件 - return pObject; + PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // 取得自定义对象数据 + pMyObject->rotation = 0; + if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // 加载图片 + { + PX_ObjectDelete(pObject); // 加载失败则删除对象 + return PX_NULL; + } + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORWHEEL, MyObjectOnCursorWheel, 0); // 注册鼠标滚轮事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, MyObjectOnCursorDrag, 0); // 注册鼠标拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, MyObjectOnCursorDown, 0); // 注册鼠标按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, MyObjectOnCursorRelease, 0); // 注册鼠标释放事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0); // 注册鼠标释放事件 + return pObject; } px_int main() { - PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240); // 创建一个自定义对象 - return PX_TRUE; + PainterEngine_Initialize(800, 480); + PX_Object_MyObjectCreate(mp, root, 400, 240); // 创建一个自定义对象 + return PX_TRUE; } ``` ![](assets/img/12.4.gif) @@ -1234,27 +1234,27 @@ PX_Object* image; PX_OBJECT_EVENT_FUNCTION(ButtonEvent) { - PX_Object_Image *pImage=PX_Object_GetImage(pObject); // 取得Image对象数据 - PX_Object_Button *pButton=PX_Object_GetButton(pObject); // 取得Button对象数据 - if (pImage->pTexture==&tex1) - { - PX_Object_ImageSetTexture(pObject,&tex2); - } - else - { - PX_Object_ImageSetTexture(pObject,&tex1); - } + PX_Object_Image *pImage = PX_Object_GetImage(pObject); // 取得 Image 对象数据 + PX_Object_Button *pButton = PX_Object_GetButton(pObject); // 取得 Button 对象数据 + if (pImage->pTexture == &tex1) + { + PX_Object_ImageSetTexture(pObject, &tex2); + } + else + { + PX_Object_ImageSetTexture(pObject, &tex1); + } } px_int main() { - PainterEngine_Initialize(800, 480); - if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0; // 加载纹理1 - if(!PX_LoadTextureFromFile(mp_static,&tex2,"assets/2.png")) return 0; // 加载纹理2 - image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1); // 创建Image对象 - PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255)); // 将一个Button对象类型组合到Image对象上 - PX_ObjectRegisterEvent(image,PX_OBJECT_EVENT_EXECUTE,ButtonEvent,0); // 这里实际上是注册Button对象的事件 - return 1; + PainterEngine_Initialize(800, 480); + if (!PX_LoadTextureFromFile(mp_static, &tex1, "assets/1.png")) return 0; // 加载纹理 1 + if (!PX_LoadTextureFromFile(mp_static, &tex2, "assets/2.png")) return 0; // 加载纹理 2 + image = PX_Object_ImageCreate(mp, root, 300, 140, 200, 200, &tex1); // 创建 Image 对象 + PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255)); // 将一个 Button 对象类型组合到 Image 对象上 + PX_ObjectRegisterEvent(image, PX_OBJECT_EVENT_EXECUTE, ButtonEvent, 0); // 这里实际上是注册 Button 对象的事件 + return 1; } ``` @@ -1278,56 +1278,56 @@ px_void* PX_ObjectCreateDesc(PX_Object* pObject, px_int idesc, px_int type, Func #include "PainterEngine.h" typedef struct { - px_float last_cursorx, last_cursory; - px_bool bselect; + px_float last_cursorx, last_cursory; + px_bool bselect; }PX_Object_Drag; PX_OBJECT_EVENT_FUNCTION(PX_Object_DragOnCursorDown) { - PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_LEFTTOP)) - { - pPX_Object_Drag->bselect = PX_TRUE; - pPX_Object_Drag->last_cursorx = PX_Object_Event_GetCursorX(e); - pPX_Object_Drag->last_cursory = PX_Object_Event_GetCursorY(e); - } + PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_LEFTTOP)) + { + pPX_Object_Drag->bselect = PX_TRUE; + pPX_Object_Drag->last_cursorx = PX_Object_Event_GetCursorX(e); + pPX_Object_Drag->last_cursory = PX_Object_Event_GetCursorY(e); + } } PX_OBJECT_EVENT_FUNCTION(PX_Object_DragOnCursorRelease) { - PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); - pPX_Object_Drag->bselect = PX_FALSE; + PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); + pPX_Object_Drag->bselect = PX_FALSE; } PX_OBJECT_EVENT_FUNCTION(PX_Object_DragOnCursorDrag) { - PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); - if (pPX_Object_Drag->bselect) - { - pObject->x += PX_Object_Event_GetCursorX(e) - pPX_Object_Drag->last_cursorx; - pObject->y += PX_Object_Event_GetCursorY(e) - pPX_Object_Drag->last_cursory; - } - pPX_Object_Drag->last_cursorx = PX_Object_Event_GetCursorX(e); - pPX_Object_Drag->last_cursory = PX_Object_Event_GetCursorY(e); + PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); + if (pPX_Object_Drag->bselect) + { + pObject->x += PX_Object_Event_GetCursorX(e) - pPX_Object_Drag->last_cursorx; + pObject->y += PX_Object_Event_GetCursorY(e) - pPX_Object_Drag->last_cursory; + } + pPX_Object_Drag->last_cursorx = PX_Object_Event_GetCursorX(e); + pPX_Object_Drag->last_cursory = PX_Object_Event_GetCursorY(e); } PX_Object* PX_Object_DragAttachObject(PX_Object* pObject, px_int attachIndex) { - PX_Object_Drag* pDesc; + PX_Object_Drag* pDesc; - PX_ASSERTIF(pObject == PX_NULL); - PX_ASSERTIF(attachIndex < 0 || attachIndex >= PX_COUNTOF(pObject->pObjectDesc)); - PX_ASSERTIF(pObject->pObjectDesc[attachIndex] != PX_NULL); - pDesc = (PX_Object_Drag*)PX_ObjectCreateDesc(pObject, attachIndex, PX_OBJECT_TYPE_DRAG, 0, 0, 0, 0, sizeof(PX_Object_Drag)); - PX_ASSERTIF(pDesc == PX_NULL); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_DragOnCursorDrag, 0); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_DragOnCursorDown, 0); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_DragOnCursorRelease, 0); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_DragOnCursorRelease, 0); - return pObject; + PX_ASSERTIF(pObject == PX_NULL); + PX_ASSERTIF(attachIndex < 0 || attachIndex >= PX_COUNTOF(pObject->pObjectDesc)); + PX_ASSERTIF(pObject->pObjectDesc[attachIndex] != PX_NULL); + pDesc = (PX_Object_Drag*)PX_ObjectCreateDesc(pObject, attachIndex, PX_OBJECT_TYPE_DRAG, 0, 0, 0, 0, sizeof(PX_Object_Drag)); + PX_ASSERTIF(pDesc == PX_NULL); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_DragOnCursorDrag, 0); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_DragOnCursorDown, 0); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_DragOnCursorRelease, 0); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_DragOnCursorRelease, 0); + return pObject; } px_texture tex1; @@ -1335,11 +1335,11 @@ PX_Object* image; px_int main() { - PainterEngine_Initialize(800, 480); - if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0; // 加载纹理1 - image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1); // 创建Image对象 - PX_Object_DragAttachObject(image, 1); // 将一个Drag对象类型组合到Image对象上 - return 1; + PainterEngine_Initialize(800, 480); + if (!PX_LoadTextureFromFile(mp_static, &tex1, "assets/1.png")) return 0; // 加载纹理 1 + image = PX_Object_ImageCreate(mp, root, 300, 140, 200, 200, &tex1); // 创建 Image 对象 + PX_Object_DragAttachObject(image, 1); // 将一个 Drag 对象类型组合到 Image 对象上 + return 1; } ``` @@ -1357,17 +1357,17 @@ PainterEngine 提供了一个粒子系统实现,下面是一个粒子系统的 PX_OBJECT_EVENT_FUNCTION(MyClick) { - px_float x = PX_Object_Event_GetCursorX(e); - px_float y = PX_Object_Event_GetCursorY(e); + px_float x = PX_Object_Event_GetCursorX(e); + px_float y = PX_Object_Event_GetCursorY(e); - PX_Object_Explosion05Create(mp, root, x, y, 10, 20); + PX_Object_Explosion05Create(mp, root, x, y, 10, 20); } px_int main() { - PainterEngine_Initialize(800, 600); - PX_ObjectRegisterEvent(root, PX_OBJECT_EVENT_CURSORDOWN, MyClick, PX_NULL); - return 0; + PainterEngine_Initialize(800, 600); + PX_ObjectRegisterEvent(root, PX_OBJECT_EVENT_CURSORDOWN, MyClick, PX_NULL); + return 0; } ``` @@ -1381,37 +1381,37 @@ px_texture texture; int main() { - PX_Object* pObject; - PX_ParticalLauncher_InitializeInfo ParticalInfo; - PainterEngine_Initialize(600, 400); - PX_LoadTextureFromFile(mp_static, &texture, "assets/star.traw"); - - PX_ParticalLauncherInitializeDefaultInfo(&ParticalInfo); - ParticalInfo.deviation_rangAngle = 360; - ParticalInfo.deviation_velocity_max = 50; - ParticalInfo.deviation_velocity_min = -50; - ParticalInfo.direction = PX_POINT(0, -1, 0); - ParticalInfo.generateDuration = 100; - ParticalInfo.launchCount = -1; - ParticalInfo.maxCount = 100; - ParticalInfo.position = PX_POINT(0, 0, 0); - ParticalInfo.tex = &texture; - ParticalInfo.velocity = 100; - ParticalInfo.alive = 5000; - ParticalInfo.rotation = 180; - ParticalInfo.deviation_rotation = 180; - ParticalInfo.atomsize = 0.7f; - ParticalInfo.deviation_atomsize_max = 0.7f; - ParticalInfo.deviation_atomsize_min = -0.5f; - ParticalInfo.alpha = 0.8f; - ParticalInfo.deviation_alpha = 0.3f; - ParticalInfo.deviation_hdrR = 0.5f; - ParticalInfo.deviation_hdrG = 0.5f; - ParticalInfo.deviation_hdrB = 0.5f; - ParticalInfo.alphaincrease = -0.2f; - - pObject=PX_Object_ParticalCreate(mp,root,300,200,ParticalInfo); - return 0; + PX_Object* pObject; + PX_ParticalLauncher_InitializeInfo ParticalInfo; + PainterEngine_Initialize(600, 400); + PX_LoadTextureFromFile(mp_static, &texture, "assets/star.traw"); + + PX_ParticalLauncherInitializeDefaultInfo(&ParticalInfo); + ParticalInfo.deviation_rangAngle = 360; + ParticalInfo.deviation_velocity_max = 50; + ParticalInfo.deviation_velocity_min = -50; + ParticalInfo.direction = PX_POINT(0, -1, 0); + ParticalInfo.generateDuration = 100; + ParticalInfo.launchCount = -1; + ParticalInfo.maxCount = 100; + ParticalInfo.position = PX_POINT(0, 0, 0); + ParticalInfo.tex = &texture; + ParticalInfo.velocity = 100; + ParticalInfo.alive = 5000; + ParticalInfo.rotation = 180; + ParticalInfo.deviation_rotation = 180; + ParticalInfo.atomsize = 0.7f; + ParticalInfo.deviation_atomsize_max = 0.7f; + ParticalInfo.deviation_atomsize_min = -0.5f; + ParticalInfo.alpha = 0.8f; + ParticalInfo.deviation_alpha = 0.3f; + ParticalInfo.deviation_hdrR = 0.5f; + ParticalInfo.deviation_hdrG = 0.5f; + ParticalInfo.deviation_hdrB = 0.5f; + ParticalInfo.alphaincrease = -0.2f; + + pObject = PX_Object_ParticalCreate(mp, root, 300, 200, ParticalInfo); + return 0; } ``` @@ -1435,7 +1435,7 @@ int main() 9. 针对 `ParticalInfo` 的各个属性进行了具体的配置,包括粒子的位置、速度、寿命、大小、旋转等。这些属性决定了粒子的外观和行为。 -10. `pObject=PX_Object_ParticalCreate(mp,root,300,200,ParticalInfo);`:使用配置好的 `ParticalInfo` 创建一个粒子系统对象,并将其存储在 `pObject` 中。这个粒子系统对象将会在窗口中的位置 (300, 200) 处发射粒子。 +10. `pObject = PX_Object_ParticalCreate(mp, root, 300, 200, ParticalInfo);`:使用配置好的 `ParticalInfo` 创建一个粒子系统对象,并将其存储在 `pObject` 中。这个粒子系统对象将会在窗口中的位置 (300, 200) 处发射粒子。 其中 `PX_ParticalLauncher_InitializeInfo` 用于配置粒子发射器的初始化信息,即在创建粒子系统时,可以通过填充这个结构体来指定粒子系统的各种属性和行为。以下是该结构体的各个成员的说明: @@ -1517,13 +1517,13 @@ PainterEngine 内置了对 wav 及 mp3 格式音乐的原生支持,使用 Pain PX_SoundData sounddata; // 定义音乐格式 int main() { - PX_Object* pObject; - PainterEngine_Initialize(600, 400); - PainterEngine_InitializeAudio(); // 初始化混音器及音乐设备 - if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav"))return PX_FALSE; // 加载音乐,支持wav及mp3格式 - PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE)); // 播放音乐 - pObject = PX_Object_SoundViewCreate(mp,root,0,0,600,400,soundplay); // 音乐频谱可视化组件,可选 - return 0; + PX_Object* pObject; + PainterEngine_Initialize(600, 400); + PainterEngine_InitializeAudio(); // 初始化混音器及音乐设备 + if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav")) return PX_FALSE; // 加载音乐,支持 wav 及 mp3 格式 + PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE)); // 播放音乐 + pObject = PX_Object_SoundViewCreate(mp, root, 0, 0, 600, 400, soundplay); // 音乐频谱可视化组件,可选 + return 0; } ``` @@ -1543,24 +1543,24 @@ PX_Object* pObject; PX_OBJECT_EVENT_FUNCTION(onClick) { - PX_Object_Live2DPlayAnimationRandom(pObject); + PX_Object_Live2DPlayAnimationRandom(pObject); } int main() { - - PX_IO_Data iodata; - PainterEngine_Initialize(600, 600); - // 加载模型数据 - iodata = PX_LoadFileToIOData("assets/release.live"); - if (iodata.size == 0)return PX_FALSE; - PX_LiveFrameworkImport(mp_static, &liveframework, iodata.buffer, iodata.size); - PX_FreeIOData(&iodata); - // 创建Live2D对象 - pObject = PX_Object_Live2DCreate(mp,root,300,300,&liveframework); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, onClick, PX_NULL); + + PX_IO_Data iodata; + PainterEngine_Initialize(600, 600); + // 加载模型数据 + iodata = PX_LoadFileToIOData("assets/release.live"); + if (iodata.size == 0) return PX_FALSE; + PX_LiveFrameworkImport(mp_static, &liveframework, iodata.buffer, iodata.size); + PX_FreeIOData(&iodata); + // 创建 Live2D 对象 + pObject = PX_Object_Live2DCreate(mp, root, 300, 300, &liveframework); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, onClick, PX_NULL); - return 0; + return 0; } ``` @@ -1624,12 +1624,12 @@ px_void PX_Object_Live2DPlayAnimationIndex(PX_Object* pObject, px_int index); PainterEngine 内置了一个平台无关的脚本引擎系统,集成了编译,运行,调试等功能,你可以很轻松地在脚本之上,实现并行调度功能。PainterEngine Script 的设计,最大程度和 C 语言保持一致性,并对一些类型进行的拓展和简化。 -例如在脚本中,支持 `int`、`float`、`string`、`memory` 四种类型,`int` 类型是一个 32 位的有符号整数,`float` 是一个浮点数类型,这个和 C 语言的类型保持了一致。`string` 类型类似于 C++的 `string`,它允许直接用 `+` 法运算符进行字符串拼接,使用 `strlen` 来获取其字符串长度,而 `memory` 是一个二进制数据存储类型,同样支持 `+` 运算进行拼接。 +例如在脚本中,支持 `int`、`float`、`string`、`memory` 四种类型,`int` 类型是一个 32 位的有符号整数,`float` 是一个浮点数类型,这个和 C 语言的类型保持了一致。`string` 类型类似于 C++ 的 `string`,它允许直接用 `+` 法运算符进行字符串拼接,使用 `strlen` 来获取其字符串长度,而 `memory` 是一个二进制数据存储类型,同样支持 `+` 运算进行拼接。 在脚本中如果需要调用 C 语言函数,应该使用 `PX_VM_HOST_FUNCTION` 宏进行定义声明。和组件回调函数一样,`PX_VM_HOST_FUNCTION` 的定义如下: ```c -#define PX_VM_HOST_FUNCTION(name) px_bool name(PX_VM *Ins,px_void *userptr) +#define PX_VM_HOST_FUNCTION(name) px_bool name(PX_VM *Ins, px_void *userptr) ``` 在下面的内容中,我将以一个简单的脚本实例作为范例,为你演示如何使用 PainterEngine 的脚本引擎: @@ -1640,7 +1640,7 @@ host void print(string s);\n\ host void sleep(int ms);\n\ int main()\n\ {\n\ - int i,j;\n\ + int i, j;\n\ for(i = 1; i <= 9; i++)\n\ {\n\ for(j = 1; j <= i; j++)\n\ @@ -1653,20 +1653,20 @@ int main()\n\ PX_VM_HOST_FUNCTION(host_print) { - if (PX_VM_HOSTPARAM(Ins, 0).type == PX_VARIABLE_TYPE_STRING) - { - PainterEngine_Print(PX_VM_HOSTPARAM(Ins, 0)._string.buffer); - } - return PX_TRUE; + if (PX_VM_HOSTPARAM(Ins, 0).type == PX_VARIABLE_TYPE_STRING) + { + PainterEngine_Print(PX_VM_HOSTPARAM(Ins, 0)._string.buffer); + } + return PX_TRUE; } PX_VM_HOST_FUNCTION(host_sleep) { - if (PX_VM_HOSTPARAM(Ins, 0).type == PX_VARIABLE_TYPE_INT) - { - PX_VM_Sleep(Ins,PX_VM_HOSTPARAM(Ins, 0)._int); - } - return PX_TRUE; + if (PX_VM_HOSTPARAM(Ins, 0).type == PX_VARIABLE_TYPE_INT) + { + PX_VM_Sleep(Ins, PX_VM_HOSTPARAM(Ins, 0)._int); + } + return PX_TRUE; } ``` @@ -1679,31 +1679,31 @@ PX_VM_HOST_FUNCTION(host_sleep) PX_VM vm; PX_OBJECT_UPDATE_FUNCTION(VMUpdate) { - PX_VMRun(&vm, 0xffff, elapsed); // 运行虚拟机 + PX_VMRun(&vm, 0xffff, elapsed); // 运行虚拟机 } px_int main() { - PX_Compiler compiler; - px_memory bin; - PainterEngine_Initialize(800, 600); - PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler); // 初始化编译器 - PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 - PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 - if (!PX_CompilerCompile(&compiler, &bin, 0, "main")) - { - // 编译失败 - return 0; - } - PX_CompilerFree(&compiler); // 释放编译器 - PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize); // 初始化虚拟机 - PX_VMRegisterHostFunction(&vm, "print", host_print,0); // 注册主机函数print - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0); // 注册主机函数sleep - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 - PX_ObjectSetUpdateFunction(root, VMUpdate, 0); // 设置更新函数 - - return 0; + PX_Compiler compiler; + px_memory bin; + PainterEngine_Initialize(800, 600); + PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); + PX_CompilerInitialize(mp, &compiler); // 初始化编译器 + PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 + PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 + if (!PX_CompilerCompile(&compiler, &bin, 0, "main")) + { + // 编译失败 + return 0; + } + PX_CompilerFree(&compiler); // 释放编译器 + PX_VMInitialize(&vm, mp, bin.buffer, bin.usedsize); // 初始化虚拟机 + PX_VMRegisterHostFunction(&vm, "print", host_print, 0); // 注册主机函数 print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep, 0); // 注册主机函数 sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 + PX_ObjectSetUpdateFunction(root, VMUpdate, 0); // 设置更新函数 + + return 0; } ``` @@ -1718,28 +1718,28 @@ px_int main() ```c px_int main() { - PX_Compiler compiler; - px_memory bin; - PainterEngine_Initialize(800, 480); - PX_VMDebuggerMapInitialize(mp,&debugmap); - PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler); // 初始化编译器 - PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 - PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 - if (!PX_CompilerCompile(&compiler, &bin, &debugmap, "main")) - { - // 编译失败 - return 0; - } - PX_CompilerFree(&compiler); // 释放编译器 - PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize); // 初始化虚拟机 - PX_VMRegisterHostFunction(&vm, "print", host_print,0); // 注册主机函数print - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0); // 注册主机函数sleep - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 - PX_Object *pDbgObject = PX_Object_AsmDebuggerCreate(mp, root, 0, 0, 800, 480, 0); - pDbgObject->Visible = PX_TRUE; - PX_Object_AsmDebuggerAttach(pDbgObject, &debugmap, &vm); - return 0; + PX_Compiler compiler; + px_memory bin; + PainterEngine_Initialize(800, 480); + PX_VMDebuggerMapInitialize(mp, &debugmap); + PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); + PX_CompilerInitialize(mp, &compiler); // 初始化编译器 + PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 + PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 + if (!PX_CompilerCompile(&compiler, &bin, &debugmap, "main")) + { + // 编译失败 + return 0; + } + PX_CompilerFree(&compiler); // 释放编译器 + PX_VMInitialize(&vm, mp, bin.buffer, bin.usedsize); // 初始化虚拟机 + PX_VMRegisterHostFunction(&vm, "print", host_print, 0); // 注册主机函数 print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep, 0); // 注册主机函数 sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 + PX_Object *pDbgObject = PX_Object_AsmDebuggerCreate(mp, root, 0, 0, 800, 480, 0); + pDbgObject->Visible = PX_TRUE; + PX_Object_AsmDebuggerAttach(pDbgObject, &debugmap, &vm); + return 0; } ``` @@ -1760,30 +1760,30 @@ px_int main() ```c px_int main() { - px_int i; - PainterEngine_Initialize(800, 480); - PX_FontModuleInitialize(mp_static,&score_fm); - PX_FontModuleSetCodepage(&score_fm, PX_FONTMODULE_CODEPAGE_GBK); - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/rasing.png", "fox_rasing")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/taunt.png", "fox_taunt")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/escape.png", "fox_escape")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/beat.png", "fox_beat")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/hurt.png", "fox_hurt")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/mask.png", "fox_mask")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/background.png", "background")) return 0; - if (!PX_LoadAnimationToResource(PainterEngine_GetResourceLibrary(), "assets/song.2dx", "song"))return 0; - PainterEngine_SetBackgroundTexture(PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "background")); - for (i = 0; i <= 9; i++) - { - px_texture tex; - px_char path[64]; - PX_sprintf1(path,64, "assets/%1.png", PX_STRINGFORMAT_INT(i)); - if (PX_LoadTextureFromFile(mp,&tex,path)) - { - PX_FontModuleAddNewTextureCharacter(&score_fm, '0' + i, &tex); - } - PX_TextureFree(&tex); - } + px_int i; + PainterEngine_Initialize(800, 480); + PX_FontModuleInitialize(mp_static, &score_fm); + PX_FontModuleSetCodepage(&score_fm, PX_FONTMODULE_CODEPAGE_GBK); + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/rasing.png", "fox_rasing")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/taunt.png", "fox_taunt")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/escape.png", "fox_escape")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/beat.png", "fox_beat")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/hurt.png", "fox_hurt")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/mask.png", "fox_mask")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/background.png", "background")) return 0; + if (!PX_LoadAnimationToResource(PainterEngine_GetResourceLibrary(), "assets/song.2dx", "song")) return 0; + PainterEngine_SetBackgroundTexture(PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "background")); + for (i = 0; i <= 9; i++) + { + px_texture tex; + px_char path[64]; + PX_sprintf1(path, 64, "assets/%1.png", PX_STRINGFORMAT_INT(i)); + if (PX_LoadTextureFromFile(mp, &tex, path)) + { + PX_FontModuleAddNewTextureCharacter(&score_fm, '0' + i, &tex); + } + PX_TextureFree(&tex); + } } ``` @@ -1822,194 +1822,193 @@ PX_Object_PushButtonSetCursorColor(startgame, PX_COLOR(168, 255, 255, 255)); ```c typedef enum { - PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 - PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 - PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 - PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 - PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 - PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 + PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 + PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 + PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 + PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 + PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 + PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 }PX_OBJECT_FOX_STATE; typedef struct { - PX_OBJECT_FOX_STATE state; // 狐狸状态 - px_dword elapsed; // 状态持续时间 - px_float texture_render_offset; // 纹理渲染偏移 - px_dword gen_rand_time; // 生成随机时间 - px_float rasing_down_speed; // 升起速度 - px_texture render_target; // 渲染目标 - px_texture* pcurrent_display_texture; // 当前显示的纹理 - px_texture* ptexture_mask; // 遮罩 + PX_OBJECT_FOX_STATE state; // 狐狸状态 + px_dword elapsed; // 状态持续时间 + px_float texture_render_offset; // 纹理渲染偏移 + px_dword gen_rand_time; // 生成随机时间 + px_float rasing_down_speed; // 升起速度 + px_texture render_target; // 渲染目标 + px_texture* pcurrent_display_texture; // 当前显示的纹理 + px_texture* ptexture_mask; // 遮罩 }PX_Object_Fox; PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) { - PX_Object_Fox* pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - switch (pfox->state) - { - case PX_OBJECT_FOX_STATE_IDLE: - { - if (pfox->gen_rand_time ==0) - { - pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 - } - else - { - if (pfox->gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; - pfox->elapsed = 0; - pfox->gen_rand_time = 0; - pfox->texture_render_offset = pObject->Height; - // 改变纹理 - pfox->pcurrent_display_texture= PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); - } - else - { - pfox->gen_rand_time -= elapsed; - } - } - } - break; - case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 - { - pfox->elapsed += elapsed; - // 升起纹理偏移量 - pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; - if (pfox->texture_render_offset <= 0) - { - pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 - pfox->elapsed = 0; - } - } - break; - case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 - { - pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 - } - else if (pfox->elapsed>1500) // 嘲讽结束 - { - pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 - pfox->elapsed = 0; - } - } - break; - case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 - { - pfox->elapsed += elapsed; - if (pfox->elapsed>800) - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 - } - } - break; - case PX_OBJECT_FOX_STATE_ESCAPE: - { - pfox->elapsed += elapsed; - pfox->texture_render_offset+=pfox->rasing_down_speed * elapsed / 1000; - if (pfox->texture_render_offset >= pObject->Height) - { - pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 - pfox->elapsed = 0; // 重置时间 - pfox->pcurrent_display_texture = PX_NULL; - } - } - break; - default: - break; - } + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + switch (pfox->state) + { + case PX_OBJECT_FOX_STATE_IDLE: + { + if (pfox->gen_rand_time == 0) + { + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 + } + else + { + if (pfox->gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; + pfox->elapsed = 0; + pfox->gen_rand_time = 0; + pfox->texture_render_offset = pObject->Height; + // 改变纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); + } + else + { + pfox->gen_rand_time -= elapsed; + } + } + } + break; + case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 + { + pfox->elapsed += elapsed; + // 升起纹理偏移量 + pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; + if (pfox->texture_render_offset <= 0) + { + pfox->texture_render_offset = 0; + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 + pfox->elapsed = 0; + } + } + break; + case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 + { + pfox->elapsed += elapsed; + if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 + } + else if (pfox->elapsed>1500) // 嘲讽结束 + { + pfox->texture_render_offset = 0; + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 + pfox->elapsed = 0; + } + } + break; + case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 + { + pfox->elapsed += elapsed; + if (pfox->elapsed>800) + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + } + } + break; + case PX_OBJECT_FOX_STATE_ESCAPE: + { + pfox->elapsed += elapsed; + pfox->texture_render_offset += pfox->rasing_down_speed * elapsed / 1000; + if (pfox->texture_render_offset >= pObject->Height) + { + pfox->texture_render_offset = pObject->Height; + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 + pfox->elapsed = 0; // 重置时间 + pfox->pcurrent_display_texture = PX_NULL; + } + } + break; + default: + break; + } } PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) { - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - px_float x,y,width,height; - PX_OBJECT_INHERIT_CODE(pObject,x,y,width,height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 - if (pfox->pcurrent_display_texture) - { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 - } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + px_float x, y, width, height; + PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 + if (pfox->pcurrent_display_texture) + { + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 + } + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 } PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) { - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - PX_TextureFree(&pfox->render_target); + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + PX_TextureFree(&pfox->render_target); } PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 { - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 - { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 - { - px_int x= (px_int)PX_Object_Event_GetCursorX(e); - px_int y= (px_int)PX_Object_Event_GetCursorY(e); - x=(px_int)(x-(pObject->x-pObject->Width/2)); - y= (px_int)(y-(pObject->y - pObject->Height)); - if (x>32&&y<128) - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); - pfox->state = PX_OBJECT_FOX_STATE_BEAT; - pfox->elapsed = 0; - PX_Object_ScorePanelAddScore(scorePanel, 100); - } - - } - } + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 + { + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 + { + px_int x = (px_int)PX_Object_Event_GetCursorX(e); + px_int y = (px_int)PX_Object_Event_GetCursorY(e); + x = (px_int)(x - (pObject->x - pObject->Width/2)); + y = (px_int)(y - (pObject->y - pObject->Height)); + if (x>32&&y<128) + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); + pfox->state = PX_OBJECT_FOX_STATE_BEAT; + pfox->elapsed = 0; + PX_Object_ScorePanelAddScore(scorePanel, 100); + } + } + } } PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) { - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - pfox->state = PX_OBJECT_FOX_STATE_IDLE; - pfox->elapsed = 0; - pfox->texture_render_offset = pObject->Height; - pfox->gen_rand_time = 0; - pfox->pcurrent_display_texture = PX_NULL; + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + pfox->state = PX_OBJECT_FOX_STATE_IDLE; + pfox->elapsed = 0; + pfox->texture_render_offset = pObject->Height; + pfox->gen_rand_time = 0; + pfox->pcurrent_display_texture = PX_NULL; } -PX_Object *PX_Object_FoxCreate(px_memorypool *mp,PX_Object *parent,px_float x,px_float y) +PX_Object *PX_Object_FoxCreate(px_memorypool *mp, PX_Object *parent, px_float x, px_float y) { - PX_Object_Fox* pfox; - px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing"); // 从资源管理器中获取纹理 - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); - pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - pfox->state= PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 - pfox->rasing_down_speed = 512; // 升起速度 - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 - if(!PX_TextureCreate(mp,&pfox->render_target,ptexture->width,ptexture->height)) - { - PX_ObjectDelete(pObject); - return 0; - } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0); // 注册点击事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0); // 注册重置事件 - return pObject; + PX_Object_Fox* pfox; + px_texture *ptexture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); // 从资源管理器中获取纹理 + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); + pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 + pfox->rasing_down_speed = 512; // 升起速度 + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 + if (!PX_TextureCreate(mp, &pfox->render_target, ptexture->width, ptexture->height)) + { + PX_ObjectDelete(pObject); + return 0; + } + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_FoxOnClick, 0); // 注册点击事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_RESET, PX_Object_FoxOnReset, 0); // 注册重置事件 + return pObject; } ``` -* 首先是 `PX_Object_FoxOnUpdate`,这是对象三件套中的 `update` 函数,在这个函数中,我们判断当前这个 `地鼠` 的状态,到底是升起、嘲讽,还是缩回去。 -* 然后是 `PX_Object_FoxOnRender`,这是执行 `render` 的函数,我们通过偏移量把纹理绘制出来,当然在这里我们调用了 `PX_TextureRenderMask` 函数,这是一个带纹理遮罩的绘制函数。 -* `PX_Object_FoxFree` 函数中,主要是对临时渲染表面的释放处理,虽然在本项目中并没有用到。 -* `PX_Object_FoxOnClick` 函数,表示当前的地鼠被击打了,其中是一些命中范围的判断,如果被击中了,应该把状态设置为受伤。 -* `PX_Object_FoxOnReset` 用于执行复位,即游戏结束后,所有地鼠都应该是重置状态,这是一个 `PX_OBJECT_EVENT_RESET` 的回调,你可以在 `PX_Object_FoxCreate` 中找到它。 -* 最后是 `PX_Object_FoxCreate` 函数,在这个函数中我们做了一些初始化工作,为 `地鼠` 注册了事件回调,最终完成这个组件的开发设计。 +- 首先是 `PX_Object_FoxOnUpdate`,这是对象三件套中的 `update` 函数,在这个函数中,我们判断当前这个 `地鼠` 的状态,到底是升起、嘲讽,还是缩回去。 +- 然后是 `PX_Object_FoxOnRender`,这是执行 `render` 的函数,我们通过偏移量把纹理绘制出来,当然在这里我们调用了 `PX_TextureRenderMask` 函数,这是一个带纹理遮罩的绘制函数。 +- `PX_Object_FoxFree` 函数中,主要是对临时渲染表面的释放处理,虽然在本项目中并没有用到。 +- `PX_Object_FoxOnClick` 函数,表示当前的地鼠被击打了,其中是一些命中范围的判断,如果被击中了,应该把状态设置为受伤。 +- `PX_Object_FoxOnReset` 用于执行复位,即游戏结束后,所有地鼠都应该是重置状态,这是一个 `PX_OBJECT_EVENT_RESET` 的回调,你可以在 `PX_Object_FoxCreate` 中找到它。 +- 最后是 `PX_Object_FoxCreate` 函数,在这个函数中我们做了一些初始化工作,为 `地鼠` 注册了事件回调,最终完成这个组件的开发设计。 ![](assets/img/18.4.gif) @@ -2020,67 +2019,67 @@ PX_Object *PX_Object_FoxCreate(px_memorypool *mp,PX_Object *parent,px_float x,px ```c typedef struct { - px_texture ham01; // 锤子纹理1,没有按下 - px_texture ham02; // 锤子纹理2,按下 - px_bool bHit; // 是否按下 + px_texture ham01; // 锤子纹理 1,没有按下 + px_texture ham02; // 锤子纹理 2,按下 + px_bool bHit; // 是否按下 }PX_Object_Hammer; PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 { - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - px_float x, y, width, height; - PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); - if (phammer->bHit) - { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 - } - else - { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 - } - + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + px_float x, y, width, height; + PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); + if (phammer->bHit) + { + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 + } + else + { + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 + } + } PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) { - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - PX_TextureFree(&phammer->ham01); - PX_TextureFree(&phammer->ham02); + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + PX_TextureFree(&phammer->ham01); + PX_TextureFree(&phammer->ham02); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) { - pObject->x=PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 - pObject->y=PX_Object_Event_GetCursorY(e); + pObject->x = PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 + pObject->y = PX_Object_Event_GetCursorY(e); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) { - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_TRUE; // 按下 + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_TRUE; // 按下 } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) { - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE; // 抬起 + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_FALSE; // 抬起 } PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) { - PX_Object_Hammer* phammer; - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, 0, 0, 0, 0, 0, 0, PX_OBJECT_TYPE_HAMMER, 0, PX_Object_HammerRender, PX_Object_HammerFree, 0, sizeof(PX_Object_Hammer)); - phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE; - if (!PX_LoadTextureFromFile(mp_static,&phammer->ham01, "assets/ham1.png")) return PX_NULL; - if (!PX_LoadTextureFromFile(mp_static,&phammer->ham02, "assets/ham2.png")) return PX_NULL; - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 + PX_Object_Hammer* phammer; + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, 0, 0, 0, 0, 0, 0, PX_OBJECT_TYPE_HAMMER, 0, PX_Object_HammerRender, PX_Object_HammerFree, 0, sizeof(PX_Object_Hammer)); + phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_FALSE; + if (!PX_LoadTextureFromFile(mp_static, &phammer->ham01, "assets/ham1.png")) return PX_NULL; + if (!PX_LoadTextureFromFile(mp_static, &phammer->ham02, "assets/ham2.png")) return PX_NULL; + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 - return pObject; + return pObject; } ``` @@ -2089,68 +2088,68 @@ PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) ```c typedef struct { - PX_Animation animation; // 动画 - px_dword time; // 倒计时时间 - px_dword elapsed; // 倒计时开始后已经过去的时间 + PX_Animation animation; // 动画 + px_dword time; // 倒计时时间 + px_dword elapsed; // 倒计时开始后已经过去的时间 }PX_Object_Clock; PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) { - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - clock->elapsed += elapsed; - if (clock->elapsed >= clock->time) - { - clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给game对象发送重置事件 - game->Visible = PX_FALSE; - game->Enabled = PX_FALSE; - startgame->Visible = PX_TRUE; - pObject->Visible = PX_FALSE; - pObject->Enabled = PX_FALSE; - } + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + clock->elapsed += elapsed; + if (clock->elapsed >= clock->time) + { + clock->elapsed = 0; + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给 game 对象发送重置事件 + game->Visible = PX_FALSE; + game->Enabled = PX_FALSE; + startgame->Visible = PX_TRUE; + pObject->Visible = PX_FALSE; + pObject->Enabled = PX_FALSE; + } } PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) { - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 - // draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 + // draw ring + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128, 192, 255, 32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 } PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) { - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationFree(&clock->animation); + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + PX_AnimationFree(&clock->animation); } px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 { - PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); - clock->time = time; - pClock->Visible = PX_TRUE; - pClock->Enabled = PX_TRUE; + PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); + clock->time = time; + pClock->Visible = PX_TRUE; + pClock->Enabled = PX_TRUE; } PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object_Clock* clock; - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 0, 0, 0, PX_OBJECT_TYPE_CLOCK, PX_Object_ClockUpdate, PX_Object_ClockRender, PX_Object_ClockFree, 0, sizeof(PX_Object_Clock)); - clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - clock->time = 0; - clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 - { - PX_ObjectDelete(pObject); - return PX_NULL; - } - pObject->Enabled = PX_FALSE; - pObject->Visible = PX_FALSE; - return pObject; + PX_Object_Clock* clock; + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 0, 0, 0, PX_OBJECT_TYPE_CLOCK, PX_Object_ClockUpdate, PX_Object_ClockRender, PX_Object_ClockFree, 0, sizeof(PX_Object_Clock)); + clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + clock->time = 0; + clock->elapsed = 0; + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 + { + PX_ObjectDelete(pObject); + return PX_NULL; + } + pObject->Enabled = PX_FALSE; + pObject->Visible = PX_FALSE; + return pObject; } ``` @@ -2160,21 +2159,21 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float ```c // 创建地鼠 -game=PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); +game = PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); PX_Object_FoxCreate(mp, game, 173, 326); PX_Object_FoxCreate(mp, game, 401, 326); PX_Object_FoxCreate(mp, game, 636, 326); PX_Object_FoxCreate(mp, game, 173, 476); PX_Object_FoxCreate(mp, game, 401, 476); PX_Object_FoxCreate(mp, game, 636, 476); -game->Visible=PX_FALSE; -game->Enabled=PX_FALSE; +game->Visible = PX_FALSE; +game->Enabled = PX_FALSE; // 创建锤子 PX_Object_HammerCreate(mp, root); scorePanel = PX_Object_ScorePanelCreate(mp, root, 400, 60, &score_fm, 100); // 创建倒计时框 -gameclock=PX_Object_ClockCreate(mp,root,680,60); +gameclock = PX_Object_ClockCreate(mp, root, 680, 60); ``` 在这里,我放上整个游戏的完整代码: @@ -2182,333 +2181,332 @@ gameclock=PX_Object_ClockCreate(mp,root,680,60); ```c #include "PainterEngine.h" -#define PX_OBJECT_TYPE_FOX 24103001 -#define PX_OBJECT_TYPE_HAMMER 24103002 -#define PX_OBJECT_TYPE_CLOCK 24103003 +#define PX_OBJECT_TYPE_FOX 24103001 +#define PX_OBJECT_TYPE_HAMMER 24103002 +#define PX_OBJECT_TYPE_CLOCK 24103003 PX_FontModule score_fm; PX_Object* scorePanel; -PX_Object* game,*startgame,*gameclock; +PX_Object* game, *startgame, *gameclock; typedef enum { - PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 - PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 - PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 - PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 - PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 - PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 + PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 + PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 + PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 + PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 + PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 + PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 }PX_OBJECT_FOX_STATE; typedef struct { - PX_OBJECT_FOX_STATE state; // 狐狸状态 - px_dword elapsed; // 状态持续时间 - px_float texture_render_offset; // 纹理渲染偏移 - px_dword gen_rand_time; // 生成随机时间 - px_float rasing_down_speed; // 升起速度 - px_texture render_target; // 渲染目标 - px_texture* pcurrent_display_texture; // 当前显示的纹理 - px_texture* ptexture_mask; // 遮罩 + PX_OBJECT_FOX_STATE state; // 狐狸状态 + px_dword elapsed; // 状态持续时间 + px_float texture_render_offset; // 纹理渲染偏移 + px_dword gen_rand_time; // 生成随机时间 + px_float rasing_down_speed; // 升起速度 + px_texture render_target; // 渲染目标 + px_texture* pcurrent_display_texture; // 当前显示的纹理 + px_texture* ptexture_mask; // 遮罩 }PX_Object_Fox; typedef struct { - px_texture ham01; // 锤子纹理1,没有按下 - px_texture ham02; // 锤子纹理2,按下 - px_bool bHit; // 是否按下 + px_texture ham01; // 锤子纹理 1,没有按下 + px_texture ham02; // 锤子纹理 2,按下 + px_bool bHit; // 是否按下 }PX_Object_Hammer; typedef struct { - PX_Animation animation; // 动画 - px_dword time; // 倒计时时间 - px_dword elapsed; // 倒计时开始后已经过去的时间 + PX_Animation animation; // 动画 + px_dword time; // 倒计时时间 + px_dword elapsed; // 倒计时开始后已经过去的时间 }PX_Object_Clock; PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) { - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - clock->elapsed += elapsed; - if (clock->elapsed >= clock->time) - { - clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给game对象发送重置事件 - game->Visible = PX_FALSE; - game->Enabled = PX_FALSE; - startgame->Visible = PX_TRUE; - pObject->Visible = PX_FALSE; - pObject->Enabled = PX_FALSE; - } + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + clock->elapsed += elapsed; + if (clock->elapsed >= clock->time) + { + clock->elapsed = 0; + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给 game 对象发送重置事件 + game->Visible = PX_FALSE; + game->Enabled = PX_FALSE; + startgame->Visible = PX_TRUE; + pObject->Visible = PX_FALSE; + pObject->Enabled = PX_FALSE; + } } PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) { - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 - // draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 + // draw ring + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128, 192, 255, 32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 } PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) { - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationFree(&clock->animation); + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + PX_AnimationFree(&clock->animation); } px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 { - PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); - clock->time = time; - pClock->Visible = PX_TRUE; - pClock->Enabled = PX_TRUE; + PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); + clock->time = time; + pClock->Visible = PX_TRUE; + pClock->Enabled = PX_TRUE; } PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object_Clock* clock; - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 0, 0, 0, PX_OBJECT_TYPE_CLOCK, PX_Object_ClockUpdate, PX_Object_ClockRender, PX_Object_ClockFree, 0, sizeof(PX_Object_Clock)); - clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - clock->time = 0; - clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 - { - PX_ObjectDelete(pObject); - return PX_NULL; - } - pObject->Enabled = PX_FALSE; - pObject->Visible = PX_FALSE; - return pObject; + PX_Object_Clock* clock; + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 0, 0, 0, PX_OBJECT_TYPE_CLOCK, PX_Object_ClockUpdate, PX_Object_ClockRender, PX_Object_ClockFree, 0, sizeof(PX_Object_Clock)); + clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + clock->time = 0; + clock->elapsed = 0; + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 + { + PX_ObjectDelete(pObject); + return PX_NULL; + } + pObject->Enabled = PX_FALSE; + pObject->Visible = PX_FALSE; + return pObject; } PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) { - PX_Object_Fox* pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - switch (pfox->state) - { - case PX_OBJECT_FOX_STATE_IDLE: - { - if (pfox->gen_rand_time ==0) - { - pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 - } - else - { - if (pfox->gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; - pfox->elapsed = 0; - pfox->gen_rand_time = 0; - pfox->texture_render_offset = pObject->Height; - // 改变纹理 - pfox->pcurrent_display_texture= PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); - } - else - { - pfox->gen_rand_time -= elapsed; - } - } - } - break; - case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 - { - pfox->elapsed += elapsed; - // 升起纹理偏移量 - pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; - if (pfox->texture_render_offset <= 0) - { - pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 - pfox->elapsed = 0; - } - } - break; - case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 - { - pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 - } - else if (pfox->elapsed>1500) // 嘲讽结束 - { - pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 - pfox->elapsed = 0; - } - } - break; - case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 - { - pfox->elapsed += elapsed; - if (pfox->elapsed>800) - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 - } - } - break; - case PX_OBJECT_FOX_STATE_ESCAPE: - { - pfox->elapsed += elapsed; - pfox->texture_render_offset+=pfox->rasing_down_speed * elapsed / 1000; - if (pfox->texture_render_offset >= pObject->Height) - { - pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 - pfox->elapsed = 0; // 重置时间 - pfox->pcurrent_display_texture = PX_NULL; - } - } - break; - default: - break; - } + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + switch (pfox->state) + { + case PX_OBJECT_FOX_STATE_IDLE: + { + if (pfox->gen_rand_time == 0) + { + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 + } + else + { + if (pfox->gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; + pfox->elapsed = 0; + pfox->gen_rand_time = 0; + pfox->texture_render_offset = pObject->Height; + // 改变纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); + } + else + { + pfox->gen_rand_time -= elapsed; + } + } + } + break; + case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 + { + pfox->elapsed += elapsed; + // 升起纹理偏移量 + pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; + if (pfox->texture_render_offset <= 0) + { + pfox->texture_render_offset = 0; + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 + pfox->elapsed = 0; + } + } + break; + case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 + { + pfox->elapsed += elapsed; + if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 + } + else if (pfox->elapsed>1500) // 嘲讽结束 + { + pfox->texture_render_offset = 0; + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 + pfox->elapsed = 0; + } + } + break; + case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 + { + pfox->elapsed += elapsed; + if (pfox->elapsed>800) + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + } + } + break; + case PX_OBJECT_FOX_STATE_ESCAPE: + { + pfox->elapsed += elapsed; + pfox->texture_render_offset += pfox->rasing_down_speed * elapsed / 1000; + if (pfox->texture_render_offset >= pObject->Height) + { + pfox->texture_render_offset = pObject->Height; + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 + pfox->elapsed = 0; // 重置时间 + pfox->pcurrent_display_texture = PX_NULL; + } + } + break; + default: + break; + } } PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) { - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - px_float x,y,width,height; - PX_OBJECT_INHERIT_CODE(pObject,x,y,width,height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 - if (pfox->pcurrent_display_texture) - { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 - } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + px_float x, y, width, height; + PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 + if (pfox->pcurrent_display_texture) + { + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 + } + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 } PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) { - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - PX_TextureFree(&pfox->render_target); + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + PX_TextureFree(&pfox->render_target); } PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 { - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 - { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 - { - px_int x= (px_int)PX_Object_Event_GetCursorX(e); - px_int y= (px_int)PX_Object_Event_GetCursorY(e); - x=(px_int)(x-(pObject->x-pObject->Width/2)); - y= (px_int)(y-(pObject->y - pObject->Height)); - if (x>32&&y<128) - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); - pfox->state = PX_OBJECT_FOX_STATE_BEAT; - pfox->elapsed = 0; - PX_Object_ScorePanelAddScore(scorePanel, 100); - } - - } - } + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 + { + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 + { + px_int x = (px_int)PX_Object_Event_GetCursorX(e); + px_int y = (px_int)PX_Object_Event_GetCursorY(e); + x = (px_int)(x - (pObject->x - pObject->Width/2)); + y = (px_int)(y - (pObject->y - pObject->Height)); + if (x>32&&y<128) + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); + pfox->state = PX_OBJECT_FOX_STATE_BEAT; + pfox->elapsed = 0; + PX_Object_ScorePanelAddScore(scorePanel, 100); + } + } + } } PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) { - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - pfox->state = PX_OBJECT_FOX_STATE_IDLE; - pfox->elapsed = 0; - pfox->texture_render_offset = pObject->Height; - pfox->gen_rand_time = 0; - pfox->pcurrent_display_texture = PX_NULL; + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + pfox->state = PX_OBJECT_FOX_STATE_IDLE; + pfox->elapsed = 0; + pfox->texture_render_offset = pObject->Height; + pfox->gen_rand_time = 0; + pfox->pcurrent_display_texture = PX_NULL; } -PX_Object *PX_Object_FoxCreate(px_memorypool *mp,PX_Object *parent,px_float x,px_float y) +PX_Object *PX_Object_FoxCreate(px_memorypool *mp, PX_Object *parent, px_float x, px_float y) { - PX_Object_Fox* pfox; - px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing"); // 从资源管理器中获取纹理 - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); - pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - pfox->state= PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 - pfox->rasing_down_speed = 512; // 升起速度 - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 - if(!PX_TextureCreate(mp,&pfox->render_target,ptexture->width,ptexture->height)) - { - PX_ObjectDelete(pObject); - return 0; - } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0); // 注册点击事件 - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0); // 注册重置事件 - return pObject; + PX_Object_Fox* pfox; + px_texture *ptexture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); // 从资源管理器中获取纹理 + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); + pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 + pfox->rasing_down_speed = 512; // 升起速度 + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 + if (!PX_TextureCreate(mp, &pfox->render_target, ptexture->width, ptexture->height)) + { + PX_ObjectDelete(pObject); + return 0; + } + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_FoxOnClick, 0); // 注册点击事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_RESET, PX_Object_FoxOnReset, 0); // 注册重置事件 + return pObject; } PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 { - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - px_float x, y, width, height; - PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); - if (phammer->bHit) - { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 - } - else - { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 - } - + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + px_float x, y, width, height; + PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); + if (phammer->bHit) + { + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 + } + else + { + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 + } + } PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) { - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - PX_TextureFree(&phammer->ham01); - PX_TextureFree(&phammer->ham02); + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + PX_TextureFree(&phammer->ham01); + PX_TextureFree(&phammer->ham02); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) { - pObject->x=PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 - pObject->y=PX_Object_Event_GetCursorY(e); + pObject->x = PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 + pObject->y = PX_Object_Event_GetCursorY(e); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) { - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_TRUE; // 按下 + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_TRUE; // 按下 } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) { - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE; // 抬起 + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_FALSE; // 抬起 } PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) { - PX_Object_Hammer* phammer; - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, 0, 0, 0, 0, 0, 0, PX_OBJECT_TYPE_HAMMER, 0, PX_Object_HammerRender, PX_Object_HammerFree, 0, sizeof(PX_Object_Hammer)); - phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE; - if (!PX_LoadTextureFromFile(mp_static,&phammer->ham01, "assets/ham1.png")) return PX_NULL; - if (!PX_LoadTextureFromFile(mp_static,&phammer->ham02, "assets/ham2.png")) return PX_NULL; - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 + PX_Object_Hammer* phammer; + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, 0, 0, 0, 0, 0, 0, PX_OBJECT_TYPE_HAMMER, 0, PX_Object_HammerRender, PX_Object_HammerFree, 0, sizeof(PX_Object_Hammer)); + phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_FALSE; + if (!PX_LoadTextureFromFile(mp_static, &phammer->ham01, "assets/ham1.png")) return PX_NULL; + if (!PX_LoadTextureFromFile(mp_static, &phammer->ham02, "assets/ham2.png")) return PX_NULL; + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 - return pObject; + return pObject; } PX_OBJECT_EVENT_FUNCTION(PX_Object_StartGameOnClick) { - game->Visible = PX_TRUE; - startgame->Visible = PX_FALSE; - game->Enabled = PX_TRUE; - PX_Object_ScorePanelSetScore(scorePanel, 0); - PX_Object_ClockBegin(gameclock, 30000); // 开始游戏,游戏时间30秒 + game->Visible = PX_TRUE; + startgame->Visible = PX_FALSE; + game->Enabled = PX_TRUE; + PX_Object_ScorePanelSetScore(scorePanel, 0); + PX_Object_ClockBegin(gameclock, 30000); // 开始游戏,游戏时间 30 秒 } @@ -2516,57 +2514,57 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_StartGameOnClick) px_int main() { - px_int i; - PainterEngine_Initialize(800, 480); - PX_FontModuleInitialize(mp_static,&score_fm); - PX_FontModuleSetCodepage(&score_fm, PX_FONTMODULE_CODEPAGE_GBK); - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/rasing.png", "fox_rasing")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/taunt.png", "fox_taunt")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/escape.png", "fox_escape")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/beat.png", "fox_beat")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/hurt.png", "fox_hurt")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/mask.png", "fox_mask")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/background.png", "background")) return 0; - if (!PX_LoadAnimationToResource(PainterEngine_GetResourceLibrary(), "assets/song.2dx", "song"))return 0; - PainterEngine_SetBackgroundTexture(PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "background")); - for (i = 0; i <= 9; i++) - { - px_texture tex; - px_char path[64]; - PX_sprintf1(path,64, "assets/%1.png", PX_STRINGFORMAT_INT(i)); - if (PX_LoadTextureFromFile(mp,&tex,path)) - { - PX_FontModuleAddNewTextureCharacter(&score_fm, '0' + i, &tex); - } - PX_TextureFree(&tex); - } - - startgame = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 90, "Start Game", 0); - startgame->Visible = PX_TRUE; - PX_Object_PushButtonSetBackgroundColor(startgame, PX_COLOR(96, 255, 255, 255)); - PX_Object_PushButtonSetPushColor(startgame, PX_COLOR(224, 255, 255, 255)); - PX_Object_PushButtonSetCursorColor(startgame, PX_COLOR(168, 255, 255, 255)); - PX_ObjectRegisterEvent(startgame, PX_OBJECT_EVENT_EXECUTE, PX_Object_StartGameOnClick, 0); - - - - game=PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); - PX_Object_FoxCreate(mp, game, 173, 326); - PX_Object_FoxCreate(mp, game, 401, 326); - PX_Object_FoxCreate(mp, game, 636, 326); - PX_Object_FoxCreate(mp, game, 173, 476); - PX_Object_FoxCreate(mp, game, 401, 476); - PX_Object_FoxCreate(mp, game, 636, 476); - game->Visible=PX_FALSE; - game->Enabled=PX_FALSE; - - - PX_Object_HammerCreate(mp, root); - scorePanel = PX_Object_ScorePanelCreate(mp, root, 400, 60, &score_fm, 100); - - gameclock=PX_Object_ClockCreate(mp,root,680,60); - - return PX_TRUE; + px_int i; + PainterEngine_Initialize(800, 480); + PX_FontModuleInitialize(mp_static, &score_fm); + PX_FontModuleSetCodepage(&score_fm, PX_FONTMODULE_CODEPAGE_GBK); + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/rasing.png", "fox_rasing")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/taunt.png", "fox_taunt")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/escape.png", "fox_escape")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/beat.png", "fox_beat")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/hurt.png", "fox_hurt")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/mask.png", "fox_mask")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/background.png", "background")) return 0; + if (!PX_LoadAnimationToResource(PainterEngine_GetResourceLibrary(), "assets/song.2dx", "song")) return 0; + PainterEngine_SetBackgroundTexture(PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "background")); + for (i = 0; i <= 9; i++) + { + px_texture tex; + px_char path[64]; + PX_sprintf1(path, 64, "assets/%1.png", PX_STRINGFORMAT_INT(i)); + if (PX_LoadTextureFromFile(mp, &tex, path)) + { + PX_FontModuleAddNewTextureCharacter(&score_fm, '0' + i, &tex); + } + PX_TextureFree(&tex); + } + + startgame = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 90, "Start Game", 0); + startgame->Visible = PX_TRUE; + PX_Object_PushButtonSetBackgroundColor(startgame, PX_COLOR(96, 255, 255, 255)); + PX_Object_PushButtonSetPushColor(startgame, PX_COLOR(224, 255, 255, 255)); + PX_Object_PushButtonSetCursorColor(startgame, PX_COLOR(168, 255, 255, 255)); + PX_ObjectRegisterEvent(startgame, PX_OBJECT_EVENT_EXECUTE, PX_Object_StartGameOnClick, 0); + + + + game = PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); + PX_Object_FoxCreate(mp, game, 173, 326); + PX_Object_FoxCreate(mp, game, 401, 326); + PX_Object_FoxCreate(mp, game, 636, 326); + PX_Object_FoxCreate(mp, game, 173, 476); + PX_Object_FoxCreate(mp, game, 401, 476); + PX_Object_FoxCreate(mp, game, 636, 476); + game->Visible = PX_FALSE; + game->Enabled = PX_FALSE; + + + PX_Object_HammerCreate(mp, root); + scorePanel = PX_Object_ScorePanelCreate(mp, root, 400, 60, &score_fm, 100); + + gameclock = PX_Object_ClockCreate(mp, root, 680, 60); + + return PX_TRUE; } ``` From ab2238ddbbd030cec80f278a1d3ac69feb439e2e Mon Sep 17 00:00:00 2001 From: Recogerous <2737936634@qq.com> Date: Sat, 23 Nov 2024 09:35:03 +0800 Subject: [PATCH 4/9] fix readme The link of the badge would lead us to the fork of the repository, not the original repository. Also add some paragraph that omitted in the last commit. --- README.md | 2 +- README(ENGLISH).md => README_en.md | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) rename README(ENGLISH).md => README_en.md (93%) diff --git a/README.md b/README.md index a3002eed..0b8f6047 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

-[![ENGLISH](https://img.shields.io/badge/language-English-red.svg)](README(ENGLISH).md) +[![ENGLISH](https://img.shields.io/badge/language-English-red.svg)](README_en.md) PainterEngine 是一个由 C 语言编写的跨平台图形引擎,支持 Windows/Linux/iOS/Android/WebAssembly 甚至无操作系统的裸嵌入式平台,它基于组件化的设计模式,即使是 C 语言初学者,也可以在几分钟内掌握它的使用,[PainterEngine Make](https://www.painterengine.com/) 允许您一键将您的 PainterEngine 项目编译到多个平台。 它涵盖了基础数据结构、图形学、声学、数字信号处理、编译原理、虚拟机系统、密码学、人机交互、游戏引擎、FPGA-GPU 图形 IP 设计等多个领域,你既可以用它制作微应用,也可以将它作为学习项目。 diff --git a/README(ENGLISH).md b/README_en.md similarity index 93% rename from README(ENGLISH).md rename to README_en.md index 5a7f6e9a..cc1606c9 100644 --- a/README(ENGLISH).md +++ b/README_en.md @@ -4,11 +4,14 @@

-[![中文](https://img.shields.io/badge/语言-中文-brightgreen)](https://github.com/QuantumCipherMaster/PainterEngine/blob/master/README.md) +[![中文](https://img.shields.io/badge/语言-中文-brightgreen)](README.md) PainterEngine is a cross-platform graphics engine written in C language, with support for Windows, Linux, iOS, Android, WebAssembly, and even bare-metal embedded platforms without OS. It is built on a component-based design pattern, making it accessible to even C language beginners. [PainterEngine Make](https://www.painterengine.com/) enables you to compile your PainterEngine project for multiple platforms with just one click. It covers various fields including basic data structures, graphics, acoustics, digital signal processing, compiler design, virtual machine systems, cryptography, human-computer interaction, game engines, FPGA-GPU graphics acceleration, and more. You can use it to create mini-applications or as a learning project for acquiring knowledge. +Now, learn and use PainterEngine $\longrightarrow$ +*__[PainterEngine Quick Start Guide (English Version)](./documents/PainterEngine_the_book_en.md)__* + ## 30-Second Quick Start Guide to PainterEngine To incorporate PainterEngine into your project, all you need is: @@ -33,9 +36,9 @@ Create components or even craft your own: int main() { PainterEngine_Initialize(800, 600); - PX_Object_Firework01Create(mp, root,200,600); - PX_Object_Firework01Create(mp, root,400,600); - PX_Object_Firework01Create(mp, root,600,600); + PX_Object_Firework01Create(mp, root, 200, 600); + PX_Object_Firework01Create(mp, root, 400, 600); + PX_Object_Firework01Create(mp, root, 600, 600); return 1; } ``` @@ -46,8 +49,6 @@ int main() Use ["PainterEngine Make"](https://www.painterengine.com/) to quickly compile and deploy your project to various platforms such as Windows, Linux, WebAssembly, Android, and more. One-click compilation and deployment, with no need to modify the source code, enabling seamless portability at zero cost. - -

PainterEngine make @@ -66,6 +67,8 @@ If you don't need the one-key compilation feature provided by PainterEngine Make 4. Add your code to the project. +You can now use your IDE to compile PainterEngine with these steps. PainterEngine library will strive to ensure consistent results across all platforms. What you develop on Windows will yield consistent results on Android, web, Linux, iOS, and more. + ## Not just a graphics library but also an application framework. | Functions | Support | Description | @@ -88,6 +91,8 @@ If you don't need the one-key compilation feature provided by PainterEngine Make | Game framework | | PainterEngine Game Framework | | FPGA-GPU | 2D accelerator | Implemented an FPGA-based GPU graphics accelerator that provides at least 50 million pixels per second (Mpps) 2D Blender acceleration for PainterEngine and supports HDMI output. Validation has been completed on the Zynq-7000 series SoC| +Many more to explore... + ## The FPGA-GPU Graphics Acceleration Solution A GPU IP core based on FPGA has been developed and functionally verified on the Zynq 7020 platform. It provides 2D Blender graphics rendering acceleration with a performance of no less than 50Mpps (Million pixels per second) and supports HDMI output. This solution has been successfully validated on the Zynq 7000 series SoC. From c94de788a8cd982d48abbdfc16b5ec9f97bf888f Mon Sep 17 00:00:00 2001 From: Recogerous <2737936634@qq.com> Date: Sat, 23 Nov 2024 09:50:30 +0800 Subject: [PATCH 5/9] traslate the doc to english 1-6 --- documents/PainterEngine_the_book_en.md | 2573 ++++++++++++++++++++++++ 1 file changed, 2573 insertions(+) create mode 100644 documents/PainterEngine_the_book_en.md diff --git a/documents/PainterEngine_the_book_en.md b/documents/PainterEngine_the_book_en.md new file mode 100644 index 00000000..0d494c38 --- /dev/null +++ b/documents/PainterEngine_the_book_en.md @@ -0,0 +1,2573 @@ +# The Book Of PainterEngine + +## Preface + +Welcome to our first lesson of PainterEngine, but before that, I believe you are not clear about what PainterEngine is. Maybe you already have some impression of it on the Internet. It may be a graphics library or a game engine. You may have seen something based on it: acoustics, cryptography, neural networks, digital signal processing, compilers, virtual machines, etc., even a GPU IP core based on FPGA. So you may think it is a large hybrid of various libraries. + +In fact, all of the above are correct, but overall, I am more inclined to think of PainterEngine as an application framework. The ultimate reason for its birth is to solve the extremely troublesome third-party library (even standard library) dependency problem in the program development process, and to simplify the platform migration and compilation difficulty of the program to the greatest extent. + +Therefore, as you can see, the compilation of PainterEngine will not let you fall into various third-party dependencies. At present, it can run on almost all platforms that provide C language compilation environment. It does not depend on the operating system and file system, and can run in the bare MCU environment. Even the homepage of PainterEngine is developed by PainterEngine. + +PainterEngine has always followed the most simple design principle, and PainterEngine uses C language as its main development language. The built-in script engine of PainterEngine also maximizes compatibility with C language syntax, and makes a small amount of abstraction and generalization of C language types, further reducing its ease of use and use. C language, as a long-standing language, is almost a compulsory course for all major engineering disciplines. It has always maintained strong competitiveness and wide recognition in the development of computer programming, and has become a fact standard that almost all hardware platforms need and provide support. C language maintains a delicate balance between learning and development costs, so you can learn and get started with C language in a short time. With PainterEngine, you can run your program on all platforms and deeply feel the charm of programming art. + +PainterEngine has also experienced nearly ten years of development, but for a long time, it has been less officially promoted in the public domain as a private library. One is that in the process of its iteration, many interfaces and functions are still unstable, and we must ensure the rationality and ease of use of the interface design in long-term practice, distinguishing which is "really useful and easy to use" and which is "a flash of inspiration that looks bright and beautiful, but actually useless". Therefore, for a long time, PainterEngine did not have detailed and stable documents, and after so many years of iteration, we finally published those stable, easy-to-use, and simple-to-learn designs, and finally brought them to you in this document. + +Finally, I don't want to write the preface too long, and it's time to get to the point. We will start with the environment construction of PainterEngine, the first lesson of PainterEngine. If you have any questions or find bugs, you can ask questions in the PainterEngine forum, or directly send the questions to matrixcascade@gmail.com, and I will give you feedback as soon as possible. + +![](assets/mini/1.png) + +## 1. A Simplest PainterEngine Program + +Before setting up the development environment, let's write the simplest PainterEngine program. Let's create a `main.c` file (the file name can be arbitrary), and then enter the following code in it: + +```c +#include "PainterEngine.h" +int main() +{ + PainterEngine_Initialize(800, 480); + return 1; +} +``` + +This is a fairly simple PainterEngine program. Simply put, in the first line, we use `include` to include the PainterEngine header file. In the `main` function, we use `PainterEngine_Initialize` to initialize PainterEngine. `PainterEngine_Initialize` takes 2 parameters, which are the width and height of the window (or screen). After running the program, you will probably see the following result: + +![](assets/img/1.1.png) + +Of course, we haven't used PainterEngine to draw anything on the window yet, so you see a blank screen. It is worth noting that in the PainterEngine framework, when the `main` function returns, the program does not end immediately. In fact, in `PainterEngine.h`, the `main` function is replaced by `px_main`, and the real `main` function is implemented in `px_main.c`. However, you don't need to worry about this for now. Just remember that after the `main` function returns, the program is still running normally. If you want to exit the program, you can call the C language `exit` function yourself, but in many cases in PainterEngine, you don't need to do this. Because in embedded microcontrollers, web pages, driver programs, most of the time you don't need to design an exit function in the program, even on Android or iOS platforms, most of the time you don't need to design an exit function in the program. + +## 2. Compiling PainterEngine Programs + +### Compiling with PainterEngine Make + +If you want to compile PainterEngine project files, the simplest way is to use PainterEngine Make, a compilation tool that you can download from PainterEngine.com. You can find the download button at the bottom of the homepage: + +![](assets/img/2.1.png) + +After unzipping it, run PainterEngine make.exe, and you will see the following interface: + +![](assets/img/2.2.png) + +Then select the platform you want to compile, and select the C language code file we created earlier: + +![](assets/img/2.3.png) + +Then just wait for the compilation to complete: + +![](assets/img/2.4.png) + +### Compiling with Visual Studio Code + +If you want to use Visual Studio Code for compilation, you need to make sure that you have installed the C language development environment for Visual Studio Code. We will skip this step because there are already many tutorials on the Internet. + +Then go to PainterEngine and download the source code of PainterEngine: + +![](assets/img/2.5.png) + +Extract the downloaded source code to a directory on your computer, then you need to note the location of this directory and create a new environment variable named `PainterEnginePath` in the Windows environment variables, and set it to the directory where the PainterEngine library is located: + +![](assets/img/2.6.png) + +![](assets/img/2.7.png) + +Please copy the PainterEngine/Platform/.vscode directory to the side of the source code file: + +![](assets/img/2.8.png) + +![](assets/img/2.9.png) + +Then open the `main.c` file with Visual Studio Code, and you can compile and run it: + +![](assets/img/2.10.png) + +![](assets/img/2.11.png) + +### Compiling with Visual Studio + +Of course, if you need a complete IDE development experience, it is still recommended to use Visual Studio for development and compilation. To develop PainterEngine using Visual Studio, you need to open Visual Studio and create an empty project: + +![](assets/img/2.12.png) + +![](assets/img/2.13.png) + +After creating the project, I strongly recommend that you create a new filter: + +![](assets/img/2.14.png) + +Then add all the files in the `core`, `kernel`, `runtime`, and `platform/windows` directories of the PainterEngine directory to this filter: + +![](assets/img/2.15.png) + +![](assets/img/2.16.png) + +Then add the `main.c` file we wrote earlier to the project: + +![](assets/img/2.17.png) + +Open `Project` → `Properties` → `VC++ Directories`, include the directory where PainterEngine is located: + +![](assets/img/2.18.png) + +![](assets/img/2.19.png) + +![](assets/img/2.20.png) + +It is particularly important to check whether the configuration is consistent with the current configuration of Visual Studio, which is a point that is easy to go wrong: + +![](assets/img/2.21.png) + +Finally, you can compile, run, and debug: + +![](assets/img/2.22.png) + +## 3. PainterEngine Lesson One: Outputting the Text "Hello PainterEngine" + +As you can see, PainterEngine is a graphical application framework, but according to tradition, our first lesson is still how to output text using PainterEngine; however, it's more accurate to say that we're drawing text rather than just outputting it. Using PainterEngine to draw text is very simple; check out the following code: + +```c +#include "PainterEngine.h" +int main() +{ + PainterEngine_Initialize(800, 480); + // PainterEngine_DrawText + // Parameter 1: x-coordinate + // Parameter 2: y-coordinate + // Parameter 3: text content + // Parameter 4: alignment + // Parameter 5: color + PainterEngine_DrawText(400, 240, "Hello PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); + return 1; +} +``` + +Let's take a look at the `PainterEngine_DrawText` function, which is a text drawing function with 5 parameters. When you run the program, you will see this result: + +![](assets/img/3.1.png) + +The entire function is quite easy to understand, but here we will explain in detail the meaning of the `alignment` and `color` parameters, as these two concepts will often be mentioned in subsequent tutorials: + +Among them, the `alignment` is the alignment of the content when it is drawn on the screen. The alignment methods in PainterEngine include the following formats: + +```c +typedef enum +{ + PX_ALIGN_LEFTTOP = 7, // left top alignment + PX_ALIGN_MIDTOP = 8, // middle top alignment + PX_ALIGN_RIGHTTOP = 9, // right top alignment + PX_ALIGN_LEFTMID = 4, // left middle alignment + PX_ALIGN_CENTER = 5, // center alignment + PX_ALIGN_RIGHTMID = 6, // right middle alignment + PX_ALIGN_LEFTBOTTOM = 1, // left bottom alignment + PX_ALIGN_MIDBOTTOM = 2, // middle bottom alignment + PX_ALIGN_RIGHTBOTTOM = 3, // right bottom alignment +}PX_ALIGN; +``` + +This alignment method enumeration is designed so that you can directly look at your numeric keypad; its alignment method corresponds to the values on the numeric keypad. + +The `color format` is defined as a structure called `px_color`, which has 4 bytes and contains 4 member variables a, r, g, b, representing the transparency, red channel, green channel, and blue channel of the color, respectively. Each component has a value range of 0-255, for example, red, the higher this value, the redder the color. + +Therefore, you can see that in the above demonstration code, we drew a red text `Hello PainterEngine`. Now let's try Chinese characters, modify the code above to the following format: + +```c +#include "PainterEngine.h" +int main() +{ + PainterEngine_Initialize(800, 480); + // PainterEngine_DrawText + // Parameter 1: x-coordinate + // Parameter 2: y-coordinate + // Parameter 3: text content + // Parameter 4: alignment + // Parameter 5: color + PainterEngine_DrawText(400, 240, "你好PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); + return 1; +} +``` + +![](assets/img/3.2.png) + +However, the Chinese characters do not display correctly. This is because PainterEngine, by default, only has English font models. What should we do if we want to support Chinese? It's not difficult either. For this, we need to prepare a TTF font file first. For example, here I have prepared a YouYuan font, so I just need to load this font: + +```c +#include "PainterEngine.h" +int main() +{ + PainterEngine_Initialize(800, 480); + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 24); + PainterEngine_DrawText(400, 240, "你好 PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); + return 1; +} +``` + +![](assets/img/3.3.png) + +The first parameter of the `PainterEngine_LoadFontModule` function is the path of the TTF font file, while the relative paths are relative to the location of the exe file. The second parameter is the character set. By default, Visual Studio uses the GBK character set. If you use Visual Studio Code, then the default is UTF8 encoding, and the second parameter should be changed to `PX_FONTMODULE_CODEPAGE_GBK`. The last parameter is the size of the font. + +## 4. Drawing Geometric Shapes Using PainterEngine + +In addition to drawing text, PainterEngine can also directly draw the following geometric shapes: + +`px_void PainterEngine_DrawLine(px_int x1, px_int y1, px_int x2, px_int y2, px_int linewidth, px_color color);` +This function is used to draw a line segment. +- x1, y1: The starting coordinates of the line segment. +- x2, y2: The ending coordinates of the line segment. +- linewidth: The width of the line segment. +- color: The color of the line segment. + +```c +#include "PainterEngine.h" +int main() +{ + PainterEngine_Initialize(800, 480); + // Set the starting and ending coordinates + px_int x1 = 50; + px_int y1 = 50; + px_int x2 = 200; + px_int y2 = 200; + + // Set the line width and color + px_int linewidth = 5; + px_color color = PX_COLOR(255, 0, 0, 255); // Red + + // Draw the line segment + PainterEngine_DrawLine(x1, y1, x2, y2, linewidth, color); + return 1; +} +``` + +![](assets/img/3.4.png) + + +`px_void PainterEngine_DrawRect(px_int x, px_int y, px_int width, px_int height, px_color color);` +This function is used to draw a rectangle. +- x, y: The top-left corner coordinates of the rectangle. +- width: The width of the rectangle. +- height: The height of the rectangle. +- color: The color of the rectangle. + +![](assets/img/3.5.png) + +```c +#include "PainterEngine.h" +int main() +{ + PainterEngine_Initialize(800, 480); + // Set the top-left corner coordinates of the rectangle + px_int x = 100; + px_int y = 100; + + // Set the width and height of the rectangle + px_int width = 150; + px_int height = 100; + + // Set the color of the rectangle + px_color color = PX_COLOR(255, 0, 255, 0); // Green + + // Draw the rectangle + PainterEngine_DrawRect(x, y, width, height, color); + return 1; +} +``` + + +`px_void PainterEngine_DrawCircle(px_int x, px_int y, px_int radius, px_int linewidth, px_color color);` +This function is used to draw a circle (ring). +- x, y: The center coordinates of the circle. +- radius: The radius of the circle. +- linewidth: The line width of the circle. +- color: The color of the circle. + +```c +#include "PainterEngine.h" +int main() +{ + PainterEngine_Initialize(800, 480); + // Set the center coordinates of the circle + px_int x = 200; + px_int y = 200; + // Set the radius of the circle + px_int radius = 50; + // Set the line width of the circle + px_int linewidth = 5; + // Set the color of the circle + px_color color = PX_COLOR(255, 0, 0, 255); // Blue + // Draw the circle + PainterEngine_DrawCircle(x, y, radius, linewidth, color); + return 1; +} +``` + +![](assets/img/3.6.png) + + +`px_void PainterEngine_DrawSolidCircle(px_int x, px_int y, px_int radius, px_color color);` +This function is used to draw a solid circle. +- x, y: The center coordinates of the circle. +- radius: The radius of the circle. +- color: The color of the circle. + +```c +#include "PainterEngine.h" +int main() +{ + PainterEngine_Initialize(800, 480); + + // Set the center coordinates of the circle + px_int x = 100; + px_int y = 100; + + // Set the radius of the circle + px_int radius = 50; + + // Set the color of the circle + px_color color = PX_COLOR(255, 255, 0, 255); + + // Draw the solid circle + PainterEngine_DrawSolidCircle(x, y, radius, color); + return 1; +} +``` + +![](assets/img/3.7.png) + + +`px_void PainterEngine_DrawSector(px_int x, px_int y, px_int inside_radius, px_int outside_radius, px_int start_angle, px_int end_angle, px_color color);` +This function is used to draw a sector. +Parameter description: +- x, y: The center coordinates of the sector. +- inside_radius: The inner radius of the sector. +- outside_radius: The outer radius of the sector. +- start_angle: The starting angle of the sector (in degrees, negative angles are supported). +- end_angle: The ending angle of the sector (in degrees, negative angles are supported). +- color: The color of the sector. + +```c +#include "PainterEngine.h" +int main() +{ + PainterEngine_Initialize(800, 480); + // Set the center coordinates of the sector + px_int x = 100; + px_int y = 100; + // Set the radii of the sector + px_int inside_radius = 50; + px_int outside_radius = 100; + // Set the starting and ending angles of the sector + px_int start_angle = 0; + px_int end_angle = 135; + // Set the color of the sector + px_color color = PX_COLOR(255, 255, 0, 0); // Red + // Draw the sector + PainterEngine_DrawSector(x, y, inside_radius, outside_radius, start_angle, end_angle, color); + return 1; +} +``` +![](assets/img/3.8.png) + + +`px_void PainterEngine_DrawPixel(px_int x, px_int y, px_color color);` +This function is used to draw a pixel. +- x, y: The coordinates of the pixel. +- color: The color of the pixel. + +This is just for drawing a single pixel, so no example image is shown. + +## 5. Drawing Images Using PainterEngine + +Using PainterEngine to draw images is still quite simple, but before drawing an image, we need to load it first. + +PainterEngine can directly load images from files, and it natively supports loading four types of static image formats: PNG, JPG, BMP, and TRAW. To store the loaded image, we need a structure called "texture". + +In PainterEngine, textures are described using the `px_texture` structure, so to load a texture, we need the `PX_LoadTextureFromFile` function, which is a three-parameter image file loading function. The first parameter is the memory pool, which I will explain in more detail in later sections. By default, PainterEngine provides two default memory pools: one is `mp`, and the other is `mp_static`. The former is generally used for elements that require frequent allocation and deallocation, while the latter is used for storing static resources. Since images are usually static resources, filling in `mp_static` is sufficient. The second parameter is a pointer to our texture structure; after the image is successfully loaded, this structure will be initialized and used to save the image data. The last parameter is the path to the image file. + +After successfully loading the file, we use the `PainterEngine_DrawTexture` function to draw it. This is a four-parameter function: + +- The first parameter is the pointer to our texture structure; +- The second and third parameters are the x and y coordinates where the image needs to be drawn; +- The fourth parameter is the alignment method we discussed earlier. + +Refer to the following code: + +```c +#include "PainterEngine.h" +px_texture mytexture; // Texture +int main() +{ + PainterEngine_Initialize(512, 512); + if (!PX_LoadTextureFromFile(mp_static, &mytexture, "assets/demo.png")) + { + // Failed to load texture + return 0; + } + PainterEngine_DrawTexture(&mytexture, 0, 0, PX_ALIGN_LEFTTOP); + + return 1; +} +``` + +![](assets/img/4.1.png) + +## 6. PainterEngine Memory Pool Management Mechanism + +Since PainterEngine has no dependency on system or standard libraries, an independent memory management mechanism, separate from the system and standard library, must be implemented within PainterEngine to manage its internal memory. Therefore, PainterEngine uses a memory pool as a dynamic memory management system. + +The implementation of the PainterEngine memory pool is also very straightforward. To use memory, you must prepare a segment of available memory space in advance, which will be managed as the memory space of the memory pool. For example, in the following code, we can define a large global array in C language, then use this array space as the allocation space for the memory pool: + +```c +#include "PainterEngine.h" +unsigned char my_memory_cache[1024 * 1024]; +int main() +{ + px_memorypool mp; + px_void* myalloc; + mp = PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // Create memory pool + myalloc = MP_Malloc(&mp, 1024); // Allocate 1024 bytes in the memory pool + return 1; +} +``` + +It should be noted that, **_the space allocated from the memory pool will be slightly less than the space allocated to the memory pool. If the space you allocate exceeds the memory pool, it will result in a system halt error._** + +```c +#include "PainterEngine.h" +unsigned char my_memory_cache[1024 * 1024]; +int main() +{ + px_memorypool mp; + px_void* myalloc; + mp = PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // Create memory pool + myalloc = MP_Malloc(&mp, 1024*1024); // Allocate 1024*1024 bytes in the memory pool, but the actual capacity of the memory pool is less than the allocated capacity, so here there is not enough memory, and it will enter an interruption here + return 1; +} +``` + +If you do not want a system halt error due to insufficient memory pool, you can use the following two methods: + +1. You can set up an error callback to handle the memory pool errors yourself: + +```c +#include "PainterEngine.h" +unsigned char my_memory_cache[1024 * 1024]; + +PX_MEMORYPOOL_ERROR_FUNCTION(my_memory_cache_error) +{ + switch (error) + { + case PX_MEMORYPOOL_ERROR_OUTOFMEMORY: + printf("Memory access error\n"); + break; + case PX_MEMORYPOOL_ERROR_INVALID_ACCESS: + printf("Unable to access memory\n"); + break; + case PX_MEMORYPOOL_ERROR_INVALID_ADDRESS: + printf("Invalid memory address (UAF or double free)\n"); + break; + default: + break; + } +} +int main() +{ + px_memorypool mp; + px_void* myalloc; + mp = PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // Create memory pool + MP_ErrorCatch(&mp, my_memory_cache_error, 0); // Set error callback + myalloc = MP_Malloc(&mp, 1024*1024); // Allocate 1024*1024 bytes in the memory pool + return 1; +} +``` + +2. Or you can directly turn off the error exception handling of the memory pool, so when the memory pool cannot allocate enough memory normally, it will directly return `NULL`: + +```c +#include "PainterEngine.h" +unsigned char my_memory_cache[1024 * 1024]; + +int main() +{ + px_memorypool mp; + px_void* myalloc; + mp = PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); // Create memory pool + MP_NoCatchError(&mp, PX_TRUE); // Set the memory pool not to catch errors + myalloc = MP_Malloc(&mp, 1024*1024); // Allocate 1024*1024 bytes in the memory pool, but since the memory pool does not catch errors, it will directly return NULL + return 1; +} +``` + +In PainterEngine, there are 2 system default memory pools, they are `mp` and `mp_static`. You can open the `PainterEngine_Application.h` file and find the definitions of these two memory pools inside, but what we need to pay most attention to is the following code: + +```c +#define PX_APPLICATION_NAME "PainterEngine" +#define PX_APPLICATION_MEMORYPOOL_STATIC_SIZE (1024*1024*64) +#define PX_APPLICATION_MEMORYPOOL_DYNAMIC_SIZE (1024*1024*32) +#define PX_APPLICATION_MEMORYPOOL_SPACE_SIZE (1024*1024*16) +``` + +These are the configuration macros directly related to these two memory pools, where `PX_APPLICATION_MEMORYPOOL_STATIC_SIZE` represents the memory allocation size of the `mp_static` memory pool, and `PX_APPLICATION_MEMORYPOOL_DYNAMIC_SIZE` is the memory allocation size of the `mp` memory pool, while `PX_APPLICATION_MEMORYPOOL_SPACE_SIZE` refers to other system resources. When the PainterEngine program starts running, it will at least occupy the memory accumulated by these three macros, and subsequent memory allocations will revolve around these few memory pools. If you find that the memory available for PainterEngine's operation is not enough, you can manually expand the size of the memory pool. Of course, if you hope to save some memory, you can also manually reduce their sizes. + +## 7. 使用 PainterEngine 创建 GUI 按钮 + +在本章节中,我们将第一次接触 PainterEngine 的组件。现在,我们将使用 PainterEngine 创建一个经典 GUI 组件——按钮。 + +在 PainterEngine 中,所有的组件都是由 `PX_Object` 结构体进行描述的,创建组件返回的都是一个 `PX_Object *` 类型的指针。 + +但在本章节中,我们并不需要考虑的那么复杂,我们只需要创建一个按钮出来即可。在 PainterEngine 中,最常用的按钮是 `PX_Object_PushButton` 类型。 + +```c +#include "PainterEngine.h" +int main() +{ + PX_Object* myButtonObject; + PainterEngine_Initialize(800, 480); + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); + myButtonObject = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 80, "我是一个按钮", PainterEngine_GetFontModule()); + return 1; +} +``` +![](assets/img/7.1.gif) + +现在,我们来详细看看 `PX_Object_PushButtonCreate` 函数。其中,第一个参数是一个内存池,在之前我们说过 PainterEngine 有 2 个系统默认的内存池,其实这里填 `mp` 或者 `mp_static` 都是没有问题的,但考虑到界面可能会变动设计对象分配与销毁,所以我们还是选择 `mp` 内存池。 + +第二个参数 `root` 是 PainterEngine 的根对象,PainterEngine 对象管理机制我们将在之后讨论。在这里,你只需要理解为,这里填 `root` 的意思是 **_创建一个按钮对象作为根对象的子对象_**。这样按钮就能链接到系统对象树中,进行事件响应和渲染。 + +然后是按钮的 x,y,width,height,也就是位置和宽度高度等信息。 + +最后一个是字模指针,也就是之前我们加载的 ttf 字模文件,如果没有它,我们的按钮就不能显示中文汉字了。当然你可以选择其他的字体,以实现不同的风格。 + +## 8. PainterEngine 对象传递机制 + +在上一个章节中,我们初略了解了根对象 `root`,那么在本章节,我们将学习一下 PainterEngine 的对象管理机制。 + +正如我们之前所说的,在 PainterEngine 中,所有的组件都是由 `PX_Object` 结构体进行描述的,PainterEngine 的对象是以树的形式存在的: + +![](assets/img/8.1.png) + +每一个 `PX_Object` 都是这个树中的一个节点,都可以有自己的子节点(可能多个)和自己的父节点(只能有一个)。同时,每一个 `PX_Object` 都有以下四个基本功能函数: + +`Create`:对象创建函数,或者说是对象初始化函数,在 PainterEngine 中它一般是 `PX_Object_xxxxxCreate` 这种形式的,其中 `xxxxx` 就是这个对象的名称,比如上一章节的 `PushButton`,`Create` 函数一般是对象的一些初始化处理,并会将自己连接到对象树中。 + +`Update`:对象的物理信息更新工作基本在这个函数中完成,一般会处理对象的一些物理信息,比如位置大小速度等,常见于游戏设计中的物体,在 GUI 对象中则比较少见,其设计是与之后的 `Render` 也就是绘制函数进行区分,因为在例如游戏服务端中,对象并不需要进行绘制,且绘制是非常消耗性能的。 + +`Render`:对象的绘制工作基本在这个函数中完成,用于 `PX_Object` 的绘制功能,将图像数据渲染到屏幕上,当然有些情况下物理信息也会在这个函数中做,是因为这个对象的物理信息并不会影响游戏的实际运行结果,例如一些特效和粒子效果,多数的 GUI 组件也几乎只用得到 `Render` 函数。 + +`Free`:对象的释放工作基本在这个函数中完成,例如在 `Create` 中加载了纹理,或者申请了内存,在这个函数中应该被释放。 + +以上 `Update`、`Render`、`Free` 函数具有传递的特性,也就是说: + +- 如果某个对象节点执行了 `Update`,那么它的所有子对象也会执行 `Update` +- 如果某个对象节点执行了 `Render`,那么它的所有子对象也会执行 `Render` +- 如果某个对象节点执行了 `Free`,那么它的所有子对象也会执行 `Free`,父对象被删除了,它的子节点也会被删除,并且将会一直迭代到以这个节点为根节点的所有子节点都被删除。 + +因此,在上一章节我们创建了按钮,并将它连接到了 `root` 节点,那么我们是不需要自己再手动执行 `Update`、`Render`、`Free` 函数的(在 `PX_Object_PushButton.c` 中它们已经被写好了),因为根节点 `root` 是被自动更新渲染和释放的,我们只需要负责 `Create` 就可以了。 + +当然,如果你希望删除这个对象的话,你只需要调用 `PX_ObjectDelayDelete` 或者 `PX_ObjectDelete` 就可以了: + +```c +#include "PainterEngine.h" +int main() +{ + PX_Object* myButtonObject; + PainterEngine_Initialize(800, 480); + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); + myButtonObject = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 80, "我是一个按钮", PainterEngine_GetFontModule()); + PX_ObjectDelayDelete(myButtonObject); // 删除对象 + return 1; +} +``` + +这两个函数的功能和参数都是一样的,但是 `PX_ObjectDelayDelete` 会在更新和渲染完成后才执行删除,`PX_ObjectDelete` 则是立即删除,我建议使用 `PX_ObjectDelayDelete`,这样你就可以避免在某些情况下因为对象被立即删除了,而其它对象仍然引用了这个对象的数据,这会导致其访问失效内存。 + +## 9. PainterEngine 消息机制 + +现在,虽然我们创建了一个按钮,但我们却没办法响应它,为了响应按钮事件,我们需要将按钮控件和消息进行绑定,请查看以下代码: + +```c +#include "PainterEngine.h" + +PX_OBJECT_EVENT_FUNCTION(OnButtonClick) +{ + PX_Object_PushButtonSetText(pObject, "我被点击了"); +} + +int main() +{ + PX_Object* myButtonObject; + PainterEngine_Initialize(800, 480); + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); + myButtonObject = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 80, "我是一个按钮", PainterEngine_GetFontModule()); + PX_ObjectRegisterEvent(myButtonObject, PX_OBJECT_EVENT_EXECUTE, OnButtonClick, 0); + + return 1; +} +``` + +![](assets/img/9.1.gif) + +其中,`PX_OBJECT_EVENT_FUNCTION` 是一个宏,因为事件响应函数是一个固定的格式,因此非常建议你使用宏的方式来申明它,它的定义原型如下: + +```c +#define PX_OBJECT_EVENT_FUNCTION(name) px_void name(PX_Object *pObject, PX_Object_Event e, px_void * ptr) +``` + +可以看到,这个回调函数有 3 个参数,第一个是响应时间的对象的指针,因为是按钮点击被触发了,所以这个指针指向的就是这个按钮对象;第二个参数是事件类型 `e`,它是触发的事件类型;最后一个参数则是用户传递来的指针,它在注册时间响应函数 `PX_ObjectRegisterEvent` 被调用时就被传递进来了。 + +事件类型有以下几种: + +```c +#define PX_OBJECT_EVENT_ANY 0 // 任意事件 +#define PX_OBJECT_EVENT_CURSORMOVE 1 // 鼠标移动 +#define PX_OBJECT_EVENT_CURSORUP 2 // 鼠标左键弹起或触摸屏弹起 +#define PX_OBJECT_EVENT_CURSORRDOWN 3 // 鼠标右键按下 +#define PX_OBJECT_EVENT_CURSORDOWN 4 // 鼠标左键按下或触摸屏按下 +#define PX_OBJECT_EVENT_CURSORRUP 5 // 鼠标右键弹起 +#define PX_OBJECT_EVENT_CURSOROVER 6 // 鼠标进入范围 +#define PX_OBJECT_EVENT_CURSOROUT 7 // 鼠标离开范围 +#define PX_OBJECT_EVENT_CURSORWHEEL 8 // 鼠标滚轮 +#define PX_OBJECT_EVENT_CURSORCLICK 9 // 鼠标左键点击 +#define PX_OBJECT_EVENT_CURSORDRAG 10 // 鼠标拖拽 +#define PX_OBJECT_EVENT_STRING 11 // 字符串事件(输入法输入) +#define PX_OBJECT_EVENT_EXECUTE 12 // 执行事件,不同组件有不同的执行方式 +#define PX_OBJECT_EVENT_VALUECHANGED 13 // 值改变事件,例如滑动条的值改变,或者文本框的值改变,或者列表框的选中项改变 +#define PX_OBJECT_EVENT_DRAGFILE 14 // 拖拽文件 +#define PX_OBJECT_EVENT_KEYDOWN 15 // 键盘按下 +#define PX_OBJECT_EVENT_KEYUP 16 // 键盘弹起 +#define PX_OBJECT_EVENT_IMPACT 17 // 碰撞事件 +#define PX_OBJECT_EVENT_SCALE 18 // 缩放事件 +#define PX_OBJECT_EVENT_WINDOWRESIZE 19 // 窗口大小改变 +#define PX_OBJECT_EVENT_ONFOCUS 20 // 获得焦点 +#define PX_OBJECT_EVENT_LOSTFOCUS 21 // 失去焦点 +#define PX_OBJECT_EVENT_CANCEL 22 // 取消事件 +#define PX_OBJECT_EVENT_CLOSE 23 // 关闭事件 +#define PX_OBJECT_EVENT_CURSORMUP 24 // 鼠标中键弹起 +#define PX_OBJECT_EVENT_CURSORMDOWN 25 // 鼠标中键按下 +#define PX_OBJECT_EVENT_REQUESTDATA 26 // 请求数据 +#define PX_OBJECT_EVENT_OPEN 27 // 打开事件 +#define PX_OBJECT_EVENT_SAVE 28 // 保存事件 +#define PX_OBJECT_EVENT_TIMEOUT 29 // 超时事件 +#define PX_OBJECT_EVENT_DAMAGE 30 // 伤害事件 +``` + +以上事件并非全部都是任何组件都会响应的,例如在上面例子中的 `PX_OBJECT_EVENT_EXECUTE`,它是按钮被单击时会被触发的事件,或者是文本框中按下回车会触发的事件,但有些例如滚动条和进度条,并不会触发这个事件。也就是说有些事件是专属的。 + +但是类似于带有 `CURSOR` 或 `KEY` 的事件,是所有连接在 `root` 节点的组件都会收到的事件(但不一定响应)。需要注意的是,类似于鼠标或触摸屏的 `CURSOR` 事件,并非只有鼠标或触摸屏移动到组件所在位置与范围时才会触发,只要有这类事件投递到 `root` 节点,他就会逐层传递给它的所有子节点。如果你希望实现类似于按钮中的 "仅在鼠标点击到按钮时" 才触发,你必须自行实现范围判断。 + +你可以使用 + +```c +px_float PX_Object_Event_GetCursorX(PX_Object_Event e); // 获取cursor事件的 x 坐标 +px_float PX_Object_Event_GetCursorY(PX_Object_Event e); // 获取cursor事件的 y 坐标 +px_float PX_Object_Event_GetCursorZ(PX_Object_Event e); // 获取cursor事件的 z 坐标,一般用于鼠标中键滚轮 +``` + +来获取 `cursor` 事件中类似于 "鼠标现在在哪里" 的功能。 + +让我们回到源代码 `OnButtonClick` 中做的很简单,就是用 `PX_Object_PushButtonSetText` 改变了按钮文本的内容。 + +最后让我们来到 `PX_ObjectRegisterEvent` 函数,这个函数用于将事件与 C 语言函数绑定在一起,第一个参数是我们之前创建好的按钮组件的指针,第二个参数是我们想要绑定的事件类型,这里的 `PX_OBJECT_EVENT_EXECUTE` 就是按钮被点击时触发的,第三个则是用户指针,它会被传递到回调函数中,如果你用不到,你可以直接填 `PX_NULL`。 + +## 10. 小例子,用 PainterEngine 实现一个电子相册 + +现在,让我们用一个小例子来开启 PainterEngine 组件化开发的第一步。在本例程中,我将使用按钮和图片框组件,开发一个电子相册功能。本文中的美术资源,你可以在 `documents/logo` 中找到。 + +```c +#include "PainterEngine.h" + +PX_Object* Previous, * Next, * Image; +px_texture my_texture[10]; // 存放图片的数组 +px_int index = 0; // 当前图片的索引 + +PX_OBJECT_EVENT_FUNCTION(OnButtonPreClick) +{ + index--; + if (index < 0) + { + index = 9; + } + PX_Object_ImageSetTexture(Image, &my_texture[index]); // 设置图片 +} + +PX_OBJECT_EVENT_FUNCTION(OnButtonNextClick) +{ + index++; + if (index > 9) + { + index = 0; + } + PX_Object_ImageSetTexture(Image, &my_texture[index]); +} + +int main() +{ + px_int i; + PainterEngine_Initialize(512, 560); // 初始化 + for(i = 0; i < 10; i++) + { + px_char path[256]; + PX_sprintf1(path, 256, "assets/%1.png", PX_STRINGFORMAT_INT(i+1)); + if (!PX_LoadTextureFromFile(mp_static, &my_texture[i], path)) // 加载图片 + { + // 加载失败 + printf("加载失败"); + return 0; + } + } + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); // 加载字体 + Image = PX_Object_ImageCreate(mp, root, 0, 0, 512, 512, 0); // 创建图片对象 + Previous = PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "上一张", PainterEngine_GetFontModule()); // 创建按钮对象 + Next = PX_Object_PushButtonCreate(mp, root, 256, 512, 256, 48, "下一张", PainterEngine_GetFontModule()); // 创建按钮对象 + PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL); // 注册按钮事件 + PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL); // 注册按钮事件 + return 1; +} +``` + +在上述代码中 `OnButtonPreClick` 和 `OnButtonNextClick` 分别是上一张和下一张按钮的回调函数,我们使用 `PX_Object_ImageSetTexture` 函数,对图片框进行切换。 + +而在 `main` 函数中,我们先加载了 ttf 字体,然后用 `PX_Object_ImageCreate` 创建了一个图片组件,之后我们创建了 2 个按钮,并用 `PX_ObjectRegisterEvent` 绑定了事件回调函数。最后,看看运行结果: + +![](assets/img/10.1.gif) + +## 11. 更多常用的 PainterEngine 组件 + +你可以在 `PainterEngine/kernel` 的文件中,找到 PainterEngine 的内置组件,所有的组件名称都是以 `PX_Object_XXXXX` 开头的,在这里,我为你列举一些常用的组件及示范代码: + +- 文本框: + +```c +#include "PainterEngine.h" +PX_OBJECT_EVENT_FUNCTION(PX_Object_EditOnTextChanged) +{ + // 文本改变后后这里会被执行 +} + +int main() +{ + PX_Object* pObject; + PainterEngine_Initialize(600, 400); + // 创建文本框 + pObject = PX_Object_EditCreate(mp, root, 200, 180, 200, 40, 0); + // 注册编辑框文本改变事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, PX_Object_EditOnTextChanged, PX_NULL); + return 0; +} +``` + +![](assets/img/11.1.gif) + +- 列表框: + +```c +#include "PainterEngine.h" + +PX_OBJECT_RENDER_FUNCTION(PX_Object_OnMyListItemRender) +{ + px_float objx, objy, objWidth, objHeight; + PX_Object_ListItem *pItem = PX_Object_GetListItem(pObject); + PX_OBJECT_INHERIT_CODE(pObject, objx, objy, objWidth, objHeight); + // 绘制出其文本 + PX_FontModuleDrawText(psurface, 0, (px_int)objx + 3, (px_int)objy + 3, PX_ALIGN_LEFTTOP, (const px_char *)pItem->pdata, PX_COLOR_WHITE); +} + + +PX_OBJECT_LIST_ITEM_CREATE_FUNCTION(PX_Object_OnMyListItemCreate) +{ + // 绑定 ListItem 的渲染函数 + ItemObject->Func_ObjectRender[0] = PX_Object_OnMyListItemRender; + return PX_TRUE; +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_ListOnSelectChanged) +{ + // 当选中项改变时 + return; +} + +int main() +{ + PX_Object* pObject; + PainterEngine_Initialize(600, 400); + + // 创建 list + pObject = PX_Object_ListCreate(mp, root, 100, 100, 400, 200, 24, PX_Object_OnMyListItemCreate, 0); + PX_Object_ListAdd(pObject, "Item1"); + PX_Object_ListAdd(pObject, "Item2"); + PX_Object_ListAdd(pObject, "Item3"); + PX_Object_ListAdd(pObject, "Item4"); + PX_Object_ListAdd(pObject, "Item5"); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, PX_Object_ListOnSelectChanged, 0); + return 0; +} +``` + +![](assets/img/11.2.gif) + +- 滑动条: + +```c +#include "PainterEngine.h" + +PX_OBJECT_EVENT_FUNCTION(SliderChanged) +{ + // 垂直滑动条值改变后执行这里的代码 + return; +} + +int main() +{ + PX_Object* pObject; + PainterEngine_Initialize(600, 400); + // 水平滑动条 + PX_Object_SliderBarCreate(mp, root, 200, 50, 200, 24, PX_OBJECT_SLIDERBAR_TYPE_HORIZONTAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); + // 垂直滑动条 + pObject = PX_Object_SliderBarCreate(mp, root, 200, 100, 24, 200, PX_OBJECT_SLIDERBAR_TYPE_VERTICAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, SliderChanged, 0); + return 0; +} +``` +![](assets/img/11.3.gif) + +- 下拉框: + +```c +#include "PainterEngine.h" + +int main() +{ + PX_Object* pObject; + PainterEngine_Initialize(600, 400); + pObject = PX_Object_SelectBarCreate(mp, root, 200, 150, 200, 24, 0); + PX_Object_SelectBarAddItem(pObject, "Item1"); + PX_Object_SelectBarAddItem(pObject, "Item2"); + PX_Object_SelectBarAddItem(pObject, "Item3"); + PX_Object_SelectBarAddItem(pObject, "Item4"); + PX_Object_SelectBarAddItem(pObject, "Item5"); + return 0; +} +``` +![](assets/img/11.4.gif) + +- 示波器: + +```c +#include "PainterEngine.h" + +// 必须是生存域内有效可访问的数据,这里定义为全局变量 +px_double data_x[100]; +px_double data_y[100]; + +int main() +{ + PX_Object_OscilloscopeData data; + PX_Object* pObject; + + px_int i; + PainterEngine_Initialize(600, 600); + + // 初始化一个测试数据 + for (i = 0; i < 100; i++) + { + data_x[i] = i; + data_y[i] = i+PX_randRange(-10, 10); + } + + pObject = PX_Object_OscilloscopeCreate(mp, root, 0, 0, 600, 600, 0); + + // 设置水平坐标最小值最大值 + PX_Object_OscilloscopeSetHorizontalMin(pObject, 0); + PX_Object_OscilloscopeSetHorizontalMax(pObject, 100); + + // 设置垂直坐标(左边)最小值最大值 0-100 + PX_Object_OscilloscopeSetLeftVerticalMin(pObject, 0); + PX_Object_OscilloscopeSetLeftVerticalMax(pObject, 100); + + // 数据类型 + data.Color = PX_COLOR(255, 192, 255, 128); // 数据颜色 + data.ID = 0; + data.linewidth = 3; // 数据线宽 + data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT; // 数据映射到左边垂直坐标 + data.MapHorizontalArray = data_x; // 数据水平坐标 + data.MapVerticalArray = data_y; // 数据垂直坐标 + data.Size = 100; // 数据大小 + data.Visibled = PX_TRUE; // 数据可见 + data.Normalization = 1; // 数据归一化系数为 1 + + // 添加数据 + PX_Object_OscilloscopeAddData(pObject, data); + return 0; +} +``` + +![](assets/img/11.5.gif) + +因为实在太多了,我无法为你列举所有的组件,如果你希望知道某个组件的具体用法和某个组件到底是做什么的,你可以访问 PainterEngine 的 [组件市场](https://market.painterengine.com/),在那里你可以找到 PainterEngine 内置组件和三方组件的说明和示例代码。 + +![](assets/img/11.6.png) + +## 12. 实现自己的 PainterEngine 组件 + +PainterEngine 鼓励组件式的开发架构。也就是说,不论是游戏还是 GUI 交互程序,甚至是程序功能,我们都可以用组件的形式去开发它。 + +组件式开发有点类似于 C++ 中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数,你可以参考 [前面的对象传递机制](#8painterengine-对象传递机制) 这一章节。 + +为了演示这一点,让我们来实现一个“可控拖动旋转图片组件”,即我们可以用鼠标拖动图片在界面的位置,并用鼠标中键来旋转它。 + +为了实现这一个功能,让我们一步一步完成这个步骤。首先,为了创建一个组件,我们需要一个结构体来描述我们的组件。我们需要绘制图片,所以我们需要一个 `px_texture` 类型。同时,我们还需要旋转图片,因此它还有一个 `rotation` 用于描述旋转的角度: + +```c +#include "PainterEngine.h" +typedef struct +{ + px_texture image; + px_int rotation; +}PX_Object_MyObject; + +px_int main() +{ + PainterEngine_Initialize(800, 480); + return PX_TRUE; +} +``` + +之后,我们需要定义我们的 `Create`、`Update`、`Render` 和 `Free` 函数,其中 `Update`、`Render`、`Free` 有对应的格式,它们都有一个宏来简化我们的定义过程: + +```c +#define PX_OBJECT_RENDER_FUNCTION(name) px_void name(px_surface *psurface, PX_Object *pObject, px_int idesc, px_dword elapsed) +#define PX_OBJECT_UPDATE_FUNCTION(name) px_void name(PX_Object *pObject, px_int idesc, px_dword elapsed) +#define PX_OBJECT_FREE_FUNCTION(name) px_void name(PX_Object *pObject, px_int idesc) +``` + +那么,在主函数中,我们就可以这样定义我们的这几个函数: + +```c +#include "PainterEngine.h" +typedef struct +{ + px_texture image; + px_int rotation; +}PX_Object_MyObject; + +PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) +{ +} + +PX_OBJECT_RENDER_FUNCTION(MyObjectRender) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); +} + +PX_OBJECT_FREE_FUNCTION(MyObjectFree) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureFree(&pMyObject->image); +} + +px_int main() +{ + PainterEngine_Initialize(800, 480); + return PX_TRUE; +} +``` + +其中,因为我们不需要更新一些物理信息,所以 `MyObjectUpdate` 函数中我们可以什么都不写,在 `MyObjectRender` 中我们只需要把图片绘制出来就可以了,这里我们先使用 `PX_ObjectGetDesc` 函数获得我们定义好的结构体指针,它的第一个参数是结构体类型,第二个参数则是函数传递进来的 `pObject` 指针,然后我们只需要用 `PX_TextureRenderEx` 函数把图片绘制出来就可以了。 + +多提一句,`PX_TextureRenderEx` 函数用于在指定的表面上渲染纹理,并提供了对齐、混合、缩放和旋转等扩展选项。其中: + - `psurface`:指向要渲染纹理的表面的指针。 + - `resTexture`:指向要渲染的纹理资源的指针。 + - `x`:在表面上绘制纹理的 x 坐标。 + - `y`:在表面上绘制纹理的 y 坐标。 + - `refPoint`:对齐的参考点(例如,中心,左上角等)。 + - `blend`:指向混合选项结构的指针(如果不需要混合,可以为 `NULL`)。 + - `scale`:纹理的缩放因子(1.0 表示不缩放)。 + - `Angle`:纹理的旋转角度,以度为单位。 + +最后,是时候编写创建新对象的函数了,这里我们需要用到 `PX_ObjectCreateEx` 函数,`PX_ObjectCreateEx` 函数用于创建一个扩展对象,并初始化其属性和回调函数。它的参数说明如下: + +- `mp`:指向内存池的指针,用于分配对象所需的内存。 +- `Parent`:指向父对象的指针,如果没有父对象则为 `NULL`。 +- `x`:对象在 x 轴上的初始位置。 +- `y`:对象在 y 轴上的初始位置。 +- `z`:对象在 z 轴上的初始位置,z 坐标会影响其渲染的先后顺序。 +- `Width`:对象的宽度。 +- `Height`:对象的高度。 +- `Lenght`:对象的长度,2D 对象,一般可以是 0。 +- `type`:对象的类型。 +- `Func_ObjectUpdate`:指向对象更新函数的指针。 +- `Func_ObjectRender`:指向对象渲染函数的指针。 +- `Func_ObjectFree`:指向对象释放函数的指针。 +- `desc`:指向对象描述数据的指针。你可以设置为 0,创建时会把这个对象类型的数据填充为 0。 +- `size`:描述数据的大小,就是你定义的对象结构体类型的大小,创建对象函数会在内存池申请一段内存空间,并用于存储你的对象结构体。 + +在创建好一个空对象后,我们使用 `PX_ObjectGetDescIndex` 将对象中的对象结构体指针取出来,这是一个三参数的函数,第一个参数是对象结构体类型,第二个参数则是 `PX_Object *` 指针类型,因为一个 `PX_Object` 可以将多个对象结构体组合在一起,这个组合结构体我们将在之后的教程中会进一步描述,但现在我们只需要知道,调用 `PX_ObjectCreateEx` 函数后,其第一个存储的对象结构体索引是 0 就可以了。 + +取出结构体指针后,我们对其进行一系列初始化,比如加载图片和初始化旋转角度,最后在 `main` 函数中我们创建这个对象: + +```c +#include "PainterEngine.h" +typedef struct +{ + px_texture image; + px_int rotation; +}PX_Object_MyObject; + +PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) +{ +} + +PX_OBJECT_RENDER_FUNCTION(MyObjectRender) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); // 渲染图片 +} + +PX_OBJECT_FREE_FUNCTION(MyObjectFree) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureFree(&pMyObject->image); // 释放图片 +} + +PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) +{ + PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // 取得自定义对象数据 + pMyObject->rotation = 0; + if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // 加载图片 + { + PX_ObjectDelete(pObject); // 加载失败则删除对象 + return PX_NULL; + } + return pObject; +} + +px_int main() +{ + PainterEngine_Initialize(800, 480); + PX_Object_MyObjectCreate(mp, root, 400, 240); // 创建一个自定义对象 + return PX_TRUE; +} +``` + +那么它的运行效果是这样的: + +![](assets/img/12.1.png) + +但现在还没有结束,我们怎么让我们的组件,响应鼠标中键实现旋转呢?还记得我们之前在 [PushButton](#8painterengine-对象传递机制) 中的对象传递机制么?现在,我们也要让我们的组件响应鼠标中键的信息,因此我们给它注册一个 `PX_OBJECT_EVENT_CURSORWHEEL` 事件的回调函数,代码如下: + +```c +#include "PainterEngine.h" +typedef struct +{ + px_texture image; + px_float rotation; +}PX_Object_MyObject; + +PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) +{ +} + +PX_OBJECT_RENDER_FUNCTION(MyObjectRender) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); // 渲染图片 +} + +PX_OBJECT_FREE_FUNCTION(MyObjectFree) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureFree(&pMyObject->image); // 释放图片 +} + +PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + if (PX_ObjectIsCursorInRegion(pObject, e)) // Object 是鼠标位置是否选中当前组件,e 是事件 + pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; +} + +PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) +{ + PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // 取得自定义对象数据 + pMyObject->rotation = 0; + if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // 加载图片 + { + PX_ObjectDelete(pObject); // 加载失败则删除对象 + return PX_NULL; + } + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORWHEEL, MyObjectOnCursorWheel, 0); // 注册鼠标滚轮事件 + return pObject; +} + +px_int main() +{ + PainterEngine_Initialize(800, 480); + PX_Object_MyObjectCreate(mp, root, 400, 240); // 创建一个自定义对象 + return PX_TRUE; +} +``` + +运行结果如下: + +![](assets/img/12.2.gif) + +如果你觉得旋转图的质量不好,有很多锯齿,这是因为 `PX_TextureRenderEx` 旋转时是对原图直接采样的。如果你想要高质量的旋转图,你可以用 `PX_TextureRenderRotation` 函数来替换原函数: + +```c +PX_OBJECT_RENDER_FUNCTION(MyObjectRender) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, pMyObject->rotation); // 渲染图片 +} + +``` + +![](assets/img/12.3.gif) + +那么,我们如何实现拖动效果呢?想要做到拖动效果,我们需要在对象结构体中,新增 `float` 类型的变量 `x`、`y`,用来记录当鼠标选中图片时的位置,同时我们加入了 `bool` 类型的变量 `bselect`,表示当前的图标是否被选中。当鼠标点击我们的图标以后,我们就可以监听 `PX_OBJECT_EVENT_CURSORDRAG` 事件,这是鼠标在屏幕上拖动时会产生的事件,我们通过坐标的偏移,移动我们的组件。最后,不论鼠标非拖动时的移动或鼠标左键抬起,都会取消我们组件的选中状态,在对应处理函数中取消选中状态即可。 + +```c +#include "PainterEngine.h" +typedef struct +{ + px_float last_cursorx, last_cursory; + px_bool bselect; + px_texture image; + px_float rotation; +}PX_Object_MyObject; + +PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) +{ +} + +PX_OBJECT_RENDER_FUNCTION(MyObjectRender) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, (px_int)pMyObject->rotation); // 渲染图片 +} + +PX_OBJECT_FREE_FUNCTION(MyObjectFree) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); + PX_TextureFree(&pMyObject->image); // 释放图片 +} + +PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) +{ + PX_Object_MyObject *pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Object 是鼠标位置是否选中当前组件,e 是事件 + pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; +} + +PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDown) +{ + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Object 是鼠标位置是否选中当前组件,e 是事件 + { + pMyObject->bselect = PX_TRUE; + pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); + pMyObject->last_cursory = PX_Object_Event_GetCursorY(e); + } +} + +PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorRelease) +{ + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + pMyObject->bselect = PX_FALSE; +} + +PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDrag) +{ + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); + if (pMyObject->bselect) + { + pObject->x += PX_Object_Event_GetCursorX(e) - pMyObject->last_cursorx; + pObject->y += PX_Object_Event_GetCursorY(e) - pMyObject->last_cursory; + } + pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); + pMyObject->last_cursory = PX_Object_Event_GetCursorY(e); +} + +PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) +{ + PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // 取得自定义对象数据 + pMyObject->rotation = 0; + if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // 加载图片 + { + PX_ObjectDelete(pObject); // 加载失败则删除对象 + return PX_NULL; + } + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORWHEEL, MyObjectOnCursorWheel, 0); // 注册鼠标滚轮事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, MyObjectOnCursorDrag, 0); // 注册鼠标拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, MyObjectOnCursorDown, 0); // 注册鼠标按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, MyObjectOnCursorRelease, 0); // 注册鼠标释放事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0); // 注册鼠标释放事件 + return pObject; +} + +px_int main() +{ + PainterEngine_Initialize(800, 480); + PX_Object_MyObjectCreate(mp, root, 400, 240); // 创建一个自定义对象 + return PX_TRUE; +} +``` +![](assets/img/12.4.gif) + +当然,你可以调用 `PX_Object_MyObjectCreate` 多次,创建多个组件对象,它们的功能都是一样的: + +![](assets/img/12.5.gif) + +## 13. 组合式组件设计 + +PainterEngine 的组件允许同时拥有多种组件类型,例如,当我们将一个图片框组件和一个按钮进行组合,我们就可以得到一个组合式组件图片按钮。 + +参考如下代码: + +```c +#include "PainterEngine.h" +px_texture tex1, tex2; +PX_Object* image; + +PX_OBJECT_EVENT_FUNCTION(ButtonEvent) +{ + PX_Object_Image *pImage = PX_Object_GetImage(pObject); // 取得 Image 对象数据 + PX_Object_Button *pButton = PX_Object_GetButton(pObject); // 取得 Button 对象数据 + if (pImage->pTexture == &tex1) + { + PX_Object_ImageSetTexture(pObject, &tex2); + } + else + { + PX_Object_ImageSetTexture(pObject, &tex1); + } +} + +px_int main() +{ + PainterEngine_Initialize(800, 480); + if (!PX_LoadTextureFromFile(mp_static, &tex1, "assets/1.png")) return 0; // 加载纹理 1 + if (!PX_LoadTextureFromFile(mp_static, &tex2, "assets/2.png")) return 0; // 加载纹理 2 + image = PX_Object_ImageCreate(mp, root, 300, 140, 200, 200, &tex1); // 创建 Image 对象 + PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255)); // 将一个 Button 对象类型组合到 Image 对象上 + PX_ObjectRegisterEvent(image, PX_OBJECT_EVENT_EXECUTE, ButtonEvent, 0); // 这里实际上是注册 Button 对象的事件 + return 1; +} +``` + +我们创建了一个 Image 图像框类型,然后将一个 Button 对象类型组合上去,这样我们就获得了一个图片按钮: + +![](assets/img/13.1.gif) + +那么,我们如何设计我们自己的可组合对象呢?回到我们的第十二章节,现在,我们就将 "可拖拽" 这个功能设计成一个组合式组件。 + +首先,仍然是定义一个组件对象结构体,为实现拖拽功能,我们需要鼠标按下时的 x, y 坐标,同时需要一个 bool 类型记录是否是选中状态,然后我们需要注册 `CURSOR` 事件,这些事件在上一章节我们已经写过了,最后,我们用 `PX_ObjectCreateDesc` 函数创建一个对象结构体,并将它 Attach 到我们的对象上。 + +`PX_ObjectCreateDesc` 是一个对象结构体创建函数,它的定义原型如下: + +```c +px_void* PX_ObjectCreateDesc(PX_Object* pObject, px_int idesc, px_int type, Function_ObjectUpdate Func_ObjectUpdate, Function_ObjectRender Func_ObjectRender, Function_ObjectFree Func_ObjectFree, px_void* pDesc, px_int descSize) +``` + +第一个参数是需要 Attach 的对象,第二个参数是 Attach 到的对象索引。还记得我们之前提到的对象数据索引么,使用 `PX_ObjectCreateEx` 默认使用的是索引 0,因此,如果我们要附加到一个对象上,我们应该选 1,当然如果 1 也被占用了,它就是 2,以此类推。第三个参数是对象类型,我们使用 `PX_ObjectGetDescByType` 时,可以通过对象类型取出对应的指针,然后就是我们熟悉的 `Update`、`Render`、`Free` 三件套了,最后一个参数给出其结构体描述和结构体大小。请参阅下面的代码: + +```c +#include "PainterEngine.h" +typedef struct +{ + px_float last_cursorx, last_cursory; + px_bool bselect; +}PX_Object_Drag; + + +PX_OBJECT_EVENT_FUNCTION(PX_Object_DragOnCursorDown) +{ + PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_LEFTTOP)) + { + pPX_Object_Drag->bselect = PX_TRUE; + pPX_Object_Drag->last_cursorx = PX_Object_Event_GetCursorX(e); + pPX_Object_Drag->last_cursory = PX_Object_Event_GetCursorY(e); + } +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_DragOnCursorRelease) +{ + PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); + pPX_Object_Drag->bselect = PX_FALSE; +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_DragOnCursorDrag) +{ + PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); + if (pPX_Object_Drag->bselect) + { + pObject->x += PX_Object_Event_GetCursorX(e) - pPX_Object_Drag->last_cursorx; + pObject->y += PX_Object_Event_GetCursorY(e) - pPX_Object_Drag->last_cursory; + } + pPX_Object_Drag->last_cursorx = PX_Object_Event_GetCursorX(e); + pPX_Object_Drag->last_cursory = PX_Object_Event_GetCursorY(e); +} + + +PX_Object* PX_Object_DragAttachObject(PX_Object* pObject, px_int attachIndex) +{ + PX_Object_Drag* pDesc; + + + PX_ASSERTIF(pObject == PX_NULL); + PX_ASSERTIF(attachIndex < 0 || attachIndex >= PX_COUNTOF(pObject->pObjectDesc)); + PX_ASSERTIF(pObject->pObjectDesc[attachIndex] != PX_NULL); + pDesc = (PX_Object_Drag*)PX_ObjectCreateDesc(pObject, attachIndex, PX_OBJECT_TYPE_DRAG, 0, 0, 0, 0, sizeof(PX_Object_Drag)); + PX_ASSERTIF(pDesc == PX_NULL); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_DragOnCursorDrag, 0); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_DragOnCursorDown, 0); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_DragOnCursorRelease, 0); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_DragOnCursorRelease, 0); + return pObject; +} + +px_texture tex1; +PX_Object* image; + +px_int main() +{ + PainterEngine_Initialize(800, 480); + if (!PX_LoadTextureFromFile(mp_static, &tex1, "assets/1.png")) return 0; // 加载纹理 1 + image = PX_Object_ImageCreate(mp, root, 300, 140, 200, 200, &tex1); // 创建 Image 对象 + PX_Object_DragAttachObject(image, 1); // 将一个 Drag 对象类型组合到 Image 对象上 + return 1; +} +``` + +运行结果如下: + +![](assets/img/13.2.gif) + + +## 14. 粒子系统 + +PainterEngine 提供了一个粒子系统实现,下面是一个粒子系统的示范程序: + +```c +#include "PainterEngine.h" + +PX_OBJECT_EVENT_FUNCTION(MyClick) +{ + px_float x = PX_Object_Event_GetCursorX(e); + px_float y = PX_Object_Event_GetCursorY(e); + + PX_Object_Explosion05Create(mp, root, x, y, 10, 20); +} + +px_int main() +{ + PainterEngine_Initialize(800, 600); + PX_ObjectRegisterEvent(root, PX_OBJECT_EVENT_CURSORDOWN, MyClick, PX_NULL); + return 0; +} +``` + +![](assets/img/14.1.gif) + +这是一个用组件包装起来的粒子系统实现,另外一种是提供了更加详细的粒子系统参数配置: + +```c +#include "PainterEngine.h" +px_texture texture; + +int main() +{ + PX_Object* pObject; + PX_ParticalLauncher_InitializeInfo ParticalInfo; + PainterEngine_Initialize(600, 400); + PX_LoadTextureFromFile(mp_static, &texture, "assets/star.traw"); + + PX_ParticalLauncherInitializeDefaultInfo(&ParticalInfo); + ParticalInfo.deviation_rangAngle = 360; + ParticalInfo.deviation_velocity_max = 50; + ParticalInfo.deviation_velocity_min = -50; + ParticalInfo.direction = PX_POINT(0, -1, 0); + ParticalInfo.generateDuration = 100; + ParticalInfo.launchCount = -1; + ParticalInfo.maxCount = 100; + ParticalInfo.position = PX_POINT(0, 0, 0); + ParticalInfo.tex = &texture; + ParticalInfo.velocity = 100; + ParticalInfo.alive = 5000; + ParticalInfo.rotation = 180; + ParticalInfo.deviation_rotation = 180; + ParticalInfo.atomsize = 0.7f; + ParticalInfo.deviation_atomsize_max = 0.7f; + ParticalInfo.deviation_atomsize_min = -0.5f; + ParticalInfo.alpha = 0.8f; + ParticalInfo.deviation_alpha = 0.3f; + ParticalInfo.deviation_hdrR = 0.5f; + ParticalInfo.deviation_hdrG = 0.5f; + ParticalInfo.deviation_hdrB = 0.5f; + ParticalInfo.alphaincrease = -0.2f; + + pObject = PX_Object_ParticalCreate(mp, root, 300, 200, ParticalInfo); + return 0; +} +``` + +以下是这段代码的主要功能和流程解释: + +1. `#include "PainterEngine.h"`:引入 PainterEngine 的头文件,以便使用引擎的功能。 + +2. `px_texture texture;`:声明一个名为 `texture` 的变量,用于存储纹理信息。 + +3. `int main()`:主函数的入口点。 + +4. `PX_Object* pObject;`:声明一个名为 `pObject` 的指向 `PX_Object` 类型的指针,将用于创建粒子系统对象。 + +5. `PX_ParticalLauncher_InitializeInfo ParticalInfo;`:声明一个名为 `ParticalInfo` 的结构体变量,用于配置粒子发射器的初始化信息。 + +6. `PainterEngine_Initialize(600, 400);`:初始化 PainterEngine,设置窗口的宽度为 600 像素,高度为 400 像素。 + +7. `PX_LoadTextureFromFile(mp_static, &texture, "assets/star.traw");`:从文件加载纹理,将纹理数据存储在 `texture` 变量中。纹理文件路径为 "assets/star.traw"。 + +8. `PX_ParticalLauncherInitializeDefaultInfo(&ParticalInfo);`:初始化 `ParticalInfo` 结构体,设置了一些默认的粒子发射器属性。 + +9. 针对 `ParticalInfo` 的各个属性进行了具体的配置,包括粒子的位置、速度、寿命、大小、旋转等。这些属性决定了粒子的外观和行为。 + +10. `pObject = PX_Object_ParticalCreate(mp, root, 300, 200, ParticalInfo);`:使用配置好的 `ParticalInfo` 创建一个粒子系统对象,并将其存储在 `pObject` 中。这个粒子系统对象将会在窗口中的位置 (300, 200) 处发射粒子。 + +其中 `PX_ParticalLauncher_InitializeInfo` 用于配置粒子发射器的初始化信息,即在创建粒子系统时,可以通过填充这个结构体来指定粒子系统的各种属性和行为。以下是该结构体的各个成员的说明: + +1. `px_void *userptr;`:一个指向任意类型数据的指针,可用于存储用户自定义的数据。 + +2. `px_texture *tex;`:指向纹理数据的指针,用于指定粒子的纹理图像。 + +3. `px_point position;`:一个包含 x、y、z 坐标的点,表示粒子系统的初始位置。 + +4. `px_float deviation_position_distanceRange;`:一个浮点数,用于指定粒子的位置偏移范围。 + +5. `px_point direction;`:一个包含 x、y、z 坐标的点,表示粒子的初始运动方向。 + +6. `px_float deviation_rangAngle;`:一个浮点数,用于指定粒子的初始运动方向偏移范围(角度)。 + +7. `px_float velocity;`:一个浮点数,表示粒子的初始速度。 + +8. `px_float deviation_velocity_max;`:一个浮点数,表示粒子速度的最大偏移值。 + +9. `px_float deviation_velocity_min;`:一个浮点数,表示粒子速度的最小偏移值。 + +10. `px_float atomsize;`:一个浮点数,表示粒子的初始大小。 + +11. `px_float deviation_atomsize_max;`:一个浮点数,表示粒子大小的最大偏移值。 + +12. `px_float deviation_atomsize_min;`:一个浮点数,表示粒子大小的最小偏移值。 + +13. `px_float rotation;`:一个浮点数,表示粒子的初始旋转角度。 + +14. `px_float deviation_rotation;`:一个浮点数,表示粒子旋转角度的偏移范围。 + +15. `px_float alpha;`:一个浮点数,表示粒子的初始透明度。 + +16. `px_float deviation_alpha;`:一个浮点数,表示粒子透明度的偏移范围。 + +17. `px_float hdrR;`:一个浮点数,表示粒子的初始红色通道值。 + +18. `px_float deviation_hdrR;`:一个浮点数,表示粒子红色通道值的偏移范围。 + +19. `px_float hdrG;`:一个浮点数,表示粒子的初始绿色通道值。 + +20. `px_float deviation_hdrG;`:一个浮点数,表示粒子绿色通道值的偏移范围。 + +21. `px_float hdrB;`:一个浮点数,表示粒子的初始蓝色通道值。 + +22. `px_float deviation_hdrB;`:一个浮点数,表示粒子蓝色通道值的偏移范围。 + +23. `px_float sizeincrease;`:一个浮点数,表示粒子大小的增加率。 + +24. `px_float alphaincrease;`:一个浮点数,表示粒子透明度的增加率。 + +25. `px_point a;`:一个包含 x、y、z 坐标的点,用于自定义属性。 + +26. `px_float ak;`:一个浮点数,用于自定义属性。 + +27. `px_int alive;`:一个整数,表示粒子的生存时间(毫秒)。 + +28. `px_int generateDuration;`:一个整数,表示粒子发射器的生成周期(毫秒)。 + +29. `px_int maxCount;`:一个整数,表示粒子系统中最大的粒子数量。 + +30. `px_int launchCount;`:一个整数,表示粒子系统的发射次数。 + +31. `PX_ParticalLauncher_CreateAtom Create_func;`:一个函数指针,用于指定自定义的粒子创建函数。 + +32. `PX_ParticalLauncher_UpdateAtom Update_func;`:一个函数指针,用于指定自定义的粒子更新函数。 + +这个结构体允许你灵活地配置粒子系统的各种属性,以满足不同场景和效果的需求。通过调整这些属性,你可以控制粒子的外观、运动轨迹、生命周期等方面的行为。 + +![](assets/img/14.2.gif) + +## 15. 使用 PainterEngine 播放音乐 + +PainterEngine 内置了对 wav 及 mp3 格式音乐的原生支持,使用 PainterEngine 播放音乐的代码十分简单: + +```c +#include "PainterEngine.h" + +PX_SoundData sounddata; // 定义音乐格式 +int main() +{ + PX_Object* pObject; + PainterEngine_Initialize(600, 400); + PainterEngine_InitializeAudio(); // 初始化混音器及音乐设备 + if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav")) return PX_FALSE; // 加载音乐,支持 wav 及 mp3 格式 + PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE)); // 播放音乐 + pObject = PX_Object_SoundViewCreate(mp, root, 0, 0, 600, 400, soundplay); // 音乐频谱可视化组件,可选 + return 0; +} +``` + +![](assets/img/15.1.gif) + +其中,`PX_LoadSoundFromFile` 函数从文件中加载音乐,并解码成 `sounddata` 类型。`PX_SoundCreate` 可以用 `sounddata` 创建一个播放实例,第二个参数表示这个实例是否循环播放,最后使用 `PX_SoundPlayAdd` 将播放实例送入混音器中,即可完成音乐播放。 + +## 16. PainterEngine Live2D 动画系统 + +PainterEngine 内置了一个类 live2D 动画系统,可以加载 live2d 动画,参考代码如下: + +```c +#include "PainterEngine.h" + +PX_LiveFramework liveframework; +PX_Object* pObject; + +PX_OBJECT_EVENT_FUNCTION(onClick) +{ + PX_Object_Live2DPlayAnimationRandom(pObject); +} + +int main() +{ + + PX_IO_Data iodata; + PainterEngine_Initialize(600, 600); + // 加载模型数据 + iodata = PX_LoadFileToIOData("assets/release.live"); + if (iodata.size == 0) return PX_FALSE; + PX_LiveFrameworkImport(mp_static, &liveframework, iodata.buffer, iodata.size); + PX_FreeIOData(&iodata); + // 创建 Live2D 对象 + pObject = PX_Object_Live2DCreate(mp, root, 300, 300, &liveframework); + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, onClick, PX_NULL); + + return 0; +} + +``` + +以下是与 Live2D 模型预览器相关的函数的说明: + +`PX_Object_Live2DCreate` + +```c +PX_Object* PX_Object_Live2DCreate(px_memorypool* mp, PX_Object* Parent, px_int x, px_int y, PX_LiveFramework *pLiveFramework); +``` + +- **描述**:创建一个 Live2D 模型预览器对象,用于在图形界面中显示和交互 Live2D 模型。 +- **参数**: + - `mp`:内存池指针,用于分配内存。 + - `Parent`:父对象,Live2D 模型预览器对象将作为其子对象。 + - `x`, `y`:Live2D 模型预览器对象的位置坐标。 + - `pLiveFramework`:Live2D 模型框架的指针,包括模型数据、纹理等信息。 +- **返回值**:创建的 Live2D 模型预览器对象的指针。 + +`PX_Object_Live2DPlayAnimation` + +```c +px_void PX_Object_Live2DPlayAnimation(PX_Object *pObject, px_char *name); +``` + +- **描述**:播放指定名称的 Live2D 模型动画。 +- **参数**: + - `pObject`:Live2D 模型预览器对象的指针。 + - `name`:动画名称。 +- **返回值**:无。 + +`PX_Object_Live2DPlayAnimationRandom` + +```c +px_void PX_Object_Live2DPlayAnimationRandom(PX_Object* pObject); +``` + +- **描述**:随机播放 Live2D 模型的动画。 +- **参数**: + - `pObject`:Live2D 模型预览器对象的指针。 +- **返回值**:无。 + +`PX_Object_Live2DPlayAnimationIndex` + +```c +px_void PX_Object_Live2DPlayAnimationIndex(PX_Object* pObject, px_int index); +``` + +- **描述**:播放 Live2D 模型的指定索引处的动画。 +- **参数**: + - `pObject`:Live2D 模型预览器对象的指针。 + - `index`:动画的索引。 +- **返回值**:无。 + +这些函数用于创建、配置和管理 Live2D 模型预览器对象,以在图形用户界面中显示和交互 Live2D 模型。可以使用这些函数播放 Live2D 模型的动画,包括指定名称、随机选择和指定索引处的动画。 + +![](assets/img/16.1.gif) + +## 17. PainterEngine 脚本引擎 + +PainterEngine 内置了一个平台无关的脚本引擎系统,集成了编译,运行,调试等功能,你可以很轻松地在脚本之上,实现并行调度功能。PainterEngine Script 的设计,最大程度和 C 语言保持一致性,并对一些类型进行的拓展和简化。 + +例如在脚本中,支持 `int`、`float`、`string`、`memory` 四种类型,`int` 类型是一个 32 位的有符号整数,`float` 是一个浮点数类型,这个和 C 语言的类型保持了一致。`string` 类型类似于 C++ 的 `string`,它允许直接用 `+` 法运算符进行字符串拼接,使用 `strlen` 来获取其字符串长度,而 `memory` 是一个二进制数据存储类型,同样支持 `+` 运算进行拼接。 + +在脚本中如果需要调用 C 语言函数,应该使用 `PX_VM_HOST_FUNCTION` 宏进行定义声明。和组件回调函数一样,`PX_VM_HOST_FUNCTION` 的定义如下: + +```c +#define PX_VM_HOST_FUNCTION(name) px_bool name(PX_VM *Ins, px_void *userptr) +``` +在下面的内容中,我将以一个简单的脚本实例作为范例,为你演示如何使用 PainterEngine 的脚本引擎: + +```c +const px_char shellcode[] = "\ +#name \"main\"\n\ +host void print(string s);\n\ +host void sleep(int ms);\n\ +int main()\n\ +{\n\ + int i, j;\n\ + for(i = 1; i <= 9; i++)\n\ + {\n\ + for(j = 1; j <= i; j++)\n\ + {\n\ + print(string(i) + \" * \" + string(j) + \" = \" + string(i * j));\n\ + sleep(1000);\n\ + }\n\ + }\n\ +}"; + +PX_VM_HOST_FUNCTION(host_print) +{ + if (PX_VM_HOSTPARAM(Ins, 0).type == PX_VARIABLE_TYPE_STRING) + { + PainterEngine_Print(PX_VM_HOSTPARAM(Ins, 0)._string.buffer); + } + return PX_TRUE; +} + +PX_VM_HOST_FUNCTION(host_sleep) +{ + if (PX_VM_HOSTPARAM(Ins, 0).type == PX_VARIABLE_TYPE_INT) + { + PX_VM_Sleep(Ins, PX_VM_HOSTPARAM(Ins, 0)._int); + } + return PX_TRUE; +} + +``` + +首先,`shellcode` 数组中存储着一个输出九九乘法表的程序,其中需要调用两个 `host` 函数(脚本调用 C 语言函数称为 host call,因此 host 函数实际就是专门提供给脚本调用的 C 语言函数),一个是 `print` 函数,一个是 `sleep` 函数。因此在下面,我们定义了两个 `host` 函数,`PX_VM_HOSTPARAM` 用于取得脚本传递过来的参数。在这里,我们需要判断传递过来的参数类型是否符合我们的调用规则,像 `host_print` 函数,作用是在 PainterEngine 中输出字符串,而 `sleep` 函数,则是用来延迟一段时间。 + +现在,PainterEngine Script 是一个编译型脚本,我们需要将上面的代码编译成二进制形式,然后将它送入虚拟机中运行,观察以下代码: + +```c +PX_VM vm; +PX_OBJECT_UPDATE_FUNCTION(VMUpdate) +{ + PX_VMRun(&vm, 0xffff, elapsed); // 运行虚拟机 +} + +px_int main() +{ + PX_Compiler compiler; + px_memory bin; + PainterEngine_Initialize(800, 600); + PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); + PX_CompilerInitialize(mp, &compiler); // 初始化编译器 + PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 + PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 + if (!PX_CompilerCompile(&compiler, &bin, 0, "main")) + { + // 编译失败 + return 0; + } + PX_CompilerFree(&compiler); // 释放编译器 + PX_VMInitialize(&vm, mp, bin.buffer, bin.usedsize); // 初始化虚拟机 + PX_VMRegisterHostFunction(&vm, "print", host_print, 0); // 注册主机函数 print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep, 0); // 注册主机函数 sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 + PX_ObjectSetUpdateFunction(root, VMUpdate, 0); // 设置更新函数 + + return 0; +} +``` + +首先我们用 `PX_Compiler` 编译我们的脚本,然后我们注册我们的 host call,`PX_VMBeginThreadFunction` 的功能是 C 语言调用脚本语言中函数,在这里我们调用脚本中的 `main` 开始运行我们的脚本函数,最后我们将一个 `Update` 函数绑定到 root 节点,以循环更新虚拟机,来执行脚本。 + +最后,看看运行的结果。 + +![](assets/img/17.1.gif) + +如果我们想要对脚本进行调试,我们还可以在编译期间,创建一个符号映射表,这样我们就可以直接使用 `PX_Object_DebuggerMap` 对脚本进行调试。 + +```c +px_int main() +{ + PX_Compiler compiler; + px_memory bin; + PainterEngine_Initialize(800, 480); + PX_VMDebuggerMapInitialize(mp, &debugmap); + PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); + PX_CompilerInitialize(mp, &compiler); // 初始化编译器 + PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 + PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 + if (!PX_CompilerCompile(&compiler, &bin, &debugmap, "main")) + { + // 编译失败 + return 0; + } + PX_CompilerFree(&compiler); // 释放编译器 + PX_VMInitialize(&vm, mp, bin.buffer, bin.usedsize); // 初始化虚拟机 + PX_VMRegisterHostFunction(&vm, "print", host_print, 0); // 注册主机函数 print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep, 0); // 注册主机函数 sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 + PX_Object *pDbgObject = PX_Object_AsmDebuggerCreate(mp, root, 0, 0, 800, 480, 0); + pDbgObject->Visible = PX_TRUE; + PX_Object_AsmDebuggerAttach(pDbgObject, &debugmap, &vm); + return 0; +} +``` + +![](assets/img/17.2.png) + +## 18. 使用 PainterEngine 快速创作一个小游戏 + +为了更好地演示 PainterEngine 的使用,我将用 PainterEngine 创作一个简单的小游戏,你可以在 documents/demo/game 下找到有关这个游戏的所有源码及原始素材。得益于 PainterEngine 的全平台可移植性,你也可以在 [PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) 中,直接玩到这个在线小游戏。 + +在这个小游戏中,我将充分为你展示,如何使用 PainterEngine 的组件化开发模式,快速创建一个 App Game。 + +让我们先开始游戏创作的第一步,我们先准备好所需的美术资源及素材: + +![](assets/img/18.1.png) + +这是一个简单的游戏背景素材,然后我们就可以开始创建我们的 `main.c` 源代码文件,在 PainterEngine 中我们输入下面的代码: + +```c +px_int main() +{ + px_int i; + PainterEngine_Initialize(800, 480); + PX_FontModuleInitialize(mp_static, &score_fm); + PX_FontModuleSetCodepage(&score_fm, PX_FONTMODULE_CODEPAGE_GBK); + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/rasing.png", "fox_rasing")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/taunt.png", "fox_taunt")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/escape.png", "fox_escape")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/beat.png", "fox_beat")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/hurt.png", "fox_hurt")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/mask.png", "fox_mask")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/background.png", "background")) return 0; + if (!PX_LoadAnimationToResource(PainterEngine_GetResourceLibrary(), "assets/song.2dx", "song")) return 0; + PainterEngine_SetBackgroundTexture(PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "background")); + for (i = 0; i <= 9; i++) + { + px_texture tex; + px_char path[64]; + PX_sprintf1(path, 64, "assets/%1.png", PX_STRINGFORMAT_INT(i)); + if (PX_LoadTextureFromFile(mp, &tex, path)) + { + PX_FontModuleAddNewTextureCharacter(&score_fm, '0' + i, &tex); + } + PX_TextureFree(&tex); + } +} +``` + +在代码的开始阶段,我们初始化了一个 800x480 的窗口,然后我们初始化了字模,并用 `PX_FontModuleSetCodepage` 函数设置了其为 GBK 字符集,再后面,我们就是把资源加载进 PainterEngine 的资源管理器中了。 + +### 加载资源及设置背景 + +PainterEngine 内置了一个资源管理器,它在 `PainterEngine_Initialize` 中就被初始化了,使用的是 `mp_static` 内存池。资源管理器的作用是像数据库一样,将图片、音频、脚本等等素材加载到内存中,并将它映射为一个 `key`,之后对资源的访问都是通过 `key` 进行的。资源管理器的映射做了专门的优化,因此你不必太担心映射查询带来的性能损耗问题。 + +`PX_LoadTextureToResource` 函数用于将一个文件系统的资源加载到资源管理器中,第一个参数是这个资源管理器的实例指针,PainterEngine 在初始化阶段会默认创建一个这样的管理器实例,因此你可以直接用 `PainterEngine_GetResourceLibrary` 获得它。第二个参数是需要加载文件的所在路径,第三个参数则是我们想映射的 `key` 了。 + +在代码的下一步,我们使用 `PX_LoadTextureToResource` 加载了若干图片,`PX_LoadAnimationToResource` 加载了一个 2dx 动画(请到应用市场查看 2DX 动画详细说明)。最后,在游戏里我们并没有使用 TTF 字模文件,我们循环加载了 `0.png` 到 `9.png`,并将这些纹理作为图片插入到字模中,这样这个字模绘制数字时,实际显示的就是我们的图片。 + +同时我们还调用了 `PainterEngine_SetBackgroundTexture` 设置 PainterEngine 界面的背景,请注意 `PX_ResourceLibraryGetTexture` 函数,它的作用是使用一个查询 `key`,从资源管理器中取得这个图片的数据结构指针。以上完成后你将可以看到这样的界面: + +![](assets/img/18.2.png) + +### 设计游戏对象 + +我们先来设计第一个游戏对象,就是 `开始游戏按钮`。这一部分我们并不要写太多的代码,因为 PainterEngine 内置就有这种按钮的功能: + +```c +startgame = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 90, "Start Game", 0); +startgame->Visible = PX_TRUE; +PX_Object_PushButtonSetBackgroundColor(startgame, PX_COLOR(96, 255, 255, 255)); +PX_Object_PushButtonSetPushColor(startgame, PX_COLOR(224, 255, 255, 255)); +PX_Object_PushButtonSetCursorColor(startgame, PX_COLOR(168, 255, 255, 255)); +``` + +我们使用了一系列函数,改变了按钮的背景颜色、鼠标悬停颜色和鼠标按下的颜色,因此你可以看到这样的情况: + +![](assets/img/18.3.png) + +然后我们需要创建我们的游戏里的地鼠对象,这是游戏里最复杂的对象,我贴上详细代码,以逐步解释它们: + +```c +typedef enum +{ + PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 + PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 + PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 + PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 + PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 + PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 +}PX_OBJECT_FOX_STATE; + +typedef struct +{ + PX_OBJECT_FOX_STATE state; // 狐狸状态 + px_dword elapsed; // 状态持续时间 + px_float texture_render_offset; // 纹理渲染偏移 + px_dword gen_rand_time; // 生成随机时间 + px_float rasing_down_speed; // 升起速度 + px_texture render_target; // 渲染目标 + px_texture* pcurrent_display_texture; // 当前显示的纹理 + px_texture* ptexture_mask; // 遮罩 +}PX_Object_Fox; + +PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + switch (pfox->state) + { + case PX_OBJECT_FOX_STATE_IDLE: + { + if (pfox->gen_rand_time == 0) + { + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 + } + else + { + if (pfox->gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; + pfox->elapsed = 0; + pfox->gen_rand_time = 0; + pfox->texture_render_offset = pObject->Height; + // 改变纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); + } + else + { + pfox->gen_rand_time -= elapsed; + } + } + } + break; + case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 + { + pfox->elapsed += elapsed; + // 升起纹理偏移量 + pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; + if (pfox->texture_render_offset <= 0) + { + pfox->texture_render_offset = 0; + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 + pfox->elapsed = 0; + } + } + break; + case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 + { + pfox->elapsed += elapsed; + if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 + } + else if (pfox->elapsed>1500) // 嘲讽结束 + { + pfox->texture_render_offset = 0; + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 + pfox->elapsed = 0; + } + } + break; + case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 + { + pfox->elapsed += elapsed; + if (pfox->elapsed>800) + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + } + } + break; + case PX_OBJECT_FOX_STATE_ESCAPE: + { + pfox->elapsed += elapsed; + pfox->texture_render_offset += pfox->rasing_down_speed * elapsed / 1000; + if (pfox->texture_render_offset >= pObject->Height) + { + pfox->texture_render_offset = pObject->Height; + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 + pfox->elapsed = 0; // 重置时间 + pfox->pcurrent_display_texture = PX_NULL; + } + } + break; + default: + break; + } +} + +PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + px_float x, y, width, height; + PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 + if (pfox->pcurrent_display_texture) + { + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 + } + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 +} + + +PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + PX_TextureFree(&pfox->render_target); +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 + { + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 + { + px_int x = (px_int)PX_Object_Event_GetCursorX(e); + px_int y = (px_int)PX_Object_Event_GetCursorY(e); + x = (px_int)(x - (pObject->x - pObject->Width/2)); + y = (px_int)(y - (pObject->y - pObject->Height)); + if (x>32&&y<128) + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); + pfox->state = PX_OBJECT_FOX_STATE_BEAT; + pfox->elapsed = 0; + PX_Object_ScorePanelAddScore(scorePanel, 100); + } + } + } +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + pfox->state = PX_OBJECT_FOX_STATE_IDLE; + pfox->elapsed = 0; + pfox->texture_render_offset = pObject->Height; + pfox->gen_rand_time = 0; + pfox->pcurrent_display_texture = PX_NULL; + +} + +PX_Object *PX_Object_FoxCreate(px_memorypool *mp, PX_Object *parent, px_float x, px_float y) +{ + PX_Object_Fox* pfox; + px_texture *ptexture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); // 从资源管理器中获取纹理 + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); + pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 + pfox->rasing_down_speed = 512; // 升起速度 + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 + if (!PX_TextureCreate(mp, &pfox->render_target, ptexture->width, ptexture->height)) + { + PX_ObjectDelete(pObject); + return 0; + } + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_FoxOnClick, 0); // 注册点击事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_RESET, PX_Object_FoxOnReset, 0); // 注册重置事件 + return pObject; +} + +``` + +- 首先是 `PX_Object_FoxOnUpdate`,这是对象三件套中的 `update` 函数,在这个函数中,我们判断当前这个 `地鼠` 的状态,到底是升起、嘲讽,还是缩回去。 +- 然后是 `PX_Object_FoxOnRender`,这是执行 `render` 的函数,我们通过偏移量把纹理绘制出来,当然在这里我们调用了 `PX_TextureRenderMask` 函数,这是一个带纹理遮罩的绘制函数。 +- `PX_Object_FoxFree` 函数中,主要是对临时渲染表面的释放处理,虽然在本项目中并没有用到。 +- `PX_Object_FoxOnClick` 函数,表示当前的地鼠被击打了,其中是一些命中范围的判断,如果被击中了,应该把状态设置为受伤。 +- `PX_Object_FoxOnReset` 用于执行复位,即游戏结束后,所有地鼠都应该是重置状态,这是一个 `PX_OBJECT_EVENT_RESET` 的回调,你可以在 `PX_Object_FoxCreate` 中找到它。 +- 最后是 `PX_Object_FoxCreate` 函数,在这个函数中我们做了一些初始化工作,为 `地鼠` 注册了事件回调,最终完成这个组件的开发设计。 + + +![](assets/img/18.4.gif) + + +然后,我们需要创建一个 `锤子` 对象来改变我们鼠标的样式。锤子对象的设计很简单,它只有 2 个纹理,一个是鼠标没有按下时的状态,一个是按下时的状态。不同的状态对应不同的纹理: + +```c +typedef struct +{ + px_texture ham01; // 锤子纹理 1,没有按下 + px_texture ham02; // 锤子纹理 2,按下 + px_bool bHit; // 是否按下 +}PX_Object_Hammer; + +PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 +{ + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + px_float x, y, width, height; + PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); + if (phammer->bHit) + { + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 + } + else + { + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 + } + +} + +PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) +{ + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + PX_TextureFree(&phammer->ham01); + PX_TextureFree(&phammer->ham02); +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) +{ + pObject->x = PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 + pObject->y = PX_Object_Event_GetCursorY(e); +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) +{ + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_TRUE; // 按下 +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) +{ + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_FALSE; // 抬起 +} + +PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) +{ + PX_Object_Hammer* phammer; + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, 0, 0, 0, 0, 0, 0, PX_OBJECT_TYPE_HAMMER, 0, PX_Object_HammerRender, PX_Object_HammerFree, 0, sizeof(PX_Object_Hammer)); + phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_FALSE; + if (!PX_LoadTextureFromFile(mp_static, &phammer->ham01, "assets/ham1.png")) return PX_NULL; + if (!PX_LoadTextureFromFile(mp_static, &phammer->ham02, "assets/ham2.png")) return PX_NULL; + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 + + return pObject; +} +``` + +最后则是一个倒计时框,它中间其实是一个 2dx 的动画对象(PainterEngine 直接支持 gif 动画,其实 gif 也可以),外围是一个环,环形的弧度不断减少,以实现一个 `倒计时` 的显示效果: + +```c +typedef struct +{ + PX_Animation animation; // 动画 + px_dword time; // 倒计时时间 + px_dword elapsed; // 倒计时开始后已经过去的时间 +}PX_Object_Clock; + + +PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) +{ + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + clock->elapsed += elapsed; + if (clock->elapsed >= clock->time) + { + clock->elapsed = 0; + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给 game 对象发送重置事件 + game->Visible = PX_FALSE; + game->Enabled = PX_FALSE; + startgame->Visible = PX_TRUE; + pObject->Visible = PX_FALSE; + pObject->Enabled = PX_FALSE; + } + +} + +PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) +{ + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 + // draw ring + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128, 192, 255, 32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 +} + +PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) +{ + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + PX_AnimationFree(&clock->animation); +} + +px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 +{ + PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); + clock->time = time; + pClock->Visible = PX_TRUE; + pClock->Enabled = PX_TRUE; +} + +PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) +{ + PX_Object_Clock* clock; + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 0, 0, 0, PX_OBJECT_TYPE_CLOCK, PX_Object_ClockUpdate, PX_Object_ClockRender, PX_Object_ClockFree, 0, sizeof(PX_Object_Clock)); + clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + clock->time = 0; + clock->elapsed = 0; + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 + { + PX_ObjectDelete(pObject); + return PX_NULL; + } + pObject->Enabled = PX_FALSE; + pObject->Visible = PX_FALSE; + return pObject; +} +``` + +### 放置对象,完成游戏 + +在 `main` 函数中,我们将上述对象一一创建,并放置在游戏场景中,最终完成这个游戏: + +```c +// 创建地鼠 +game = PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); +PX_Object_FoxCreate(mp, game, 173, 326); +PX_Object_FoxCreate(mp, game, 401, 326); +PX_Object_FoxCreate(mp, game, 636, 326); +PX_Object_FoxCreate(mp, game, 173, 476); +PX_Object_FoxCreate(mp, game, 401, 476); +PX_Object_FoxCreate(mp, game, 636, 476); +game->Visible = PX_FALSE; +game->Enabled = PX_FALSE; + +// 创建锤子 +PX_Object_HammerCreate(mp, root); +scorePanel = PX_Object_ScorePanelCreate(mp, root, 400, 60, &score_fm, 100); +// 创建倒计时框 +gameclock = PX_Object_ClockCreate(mp, root, 680, 60); +``` + +在这里,我放上整个游戏的完整代码: + +```c +#include "PainterEngine.h" + +#define PX_OBJECT_TYPE_FOX 24103001 +#define PX_OBJECT_TYPE_HAMMER 24103002 +#define PX_OBJECT_TYPE_CLOCK 24103003 + +PX_FontModule score_fm; +PX_Object* scorePanel; +PX_Object* game, *startgame, *gameclock; + +typedef enum +{ + PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 + PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 + PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 + PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 + PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 + PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 +}PX_OBJECT_FOX_STATE; + +typedef struct +{ + PX_OBJECT_FOX_STATE state; // 狐狸状态 + px_dword elapsed; // 状态持续时间 + px_float texture_render_offset; // 纹理渲染偏移 + px_dword gen_rand_time; // 生成随机时间 + px_float rasing_down_speed; // 升起速度 + px_texture render_target; // 渲染目标 + px_texture* pcurrent_display_texture; // 当前显示的纹理 + px_texture* ptexture_mask; // 遮罩 +}PX_Object_Fox; + +typedef struct +{ + px_texture ham01; // 锤子纹理 1,没有按下 + px_texture ham02; // 锤子纹理 2,按下 + px_bool bHit; // 是否按下 +}PX_Object_Hammer; + +typedef struct +{ + PX_Animation animation; // 动画 + px_dword time; // 倒计时时间 + px_dword elapsed; // 倒计时开始后已经过去的时间 +}PX_Object_Clock; + + +PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) +{ + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + clock->elapsed += elapsed; + if (clock->elapsed >= clock->time) + { + clock->elapsed = 0; + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给 game 对象发送重置事件 + game->Visible = PX_FALSE; + game->Enabled = PX_FALSE; + startgame->Visible = PX_TRUE; + pObject->Visible = PX_FALSE; + pObject->Enabled = PX_FALSE; + } + +} + +PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) +{ + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 + // draw ring + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128, 192, 255, 32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 +} + +PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) +{ + PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + PX_AnimationFree(&clock->animation); +} + +px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 +{ + PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); + clock->time = time; + pClock->Visible = PX_TRUE; + pClock->Enabled = PX_TRUE; +} + +PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) +{ + PX_Object_Clock* clock; + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 0, 0, 0, PX_OBJECT_TYPE_CLOCK, PX_Object_ClockUpdate, PX_Object_ClockRender, PX_Object_ClockFree, 0, sizeof(PX_Object_Clock)); + clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); + clock->time = 0; + clock->elapsed = 0; + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 + { + PX_ObjectDelete(pObject); + return PX_NULL; + } + pObject->Enabled = PX_FALSE; + pObject->Visible = PX_FALSE; + return pObject; +} + +PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + switch (pfox->state) + { + case PX_OBJECT_FOX_STATE_IDLE: + { + if (pfox->gen_rand_time == 0) + { + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 + } + else + { + if (pfox->gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; + pfox->elapsed = 0; + pfox->gen_rand_time = 0; + pfox->texture_render_offset = pObject->Height; + // 改变纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); + } + else + { + pfox->gen_rand_time -= elapsed; + } + } + } + break; + case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 + { + pfox->elapsed += elapsed; + // 升起纹理偏移量 + pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; + if (pfox->texture_render_offset <= 0) + { + pfox->texture_render_offset = 0; + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 + pfox->elapsed = 0; + } + } + break; + case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 + { + pfox->elapsed += elapsed; + if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 + } + else if (pfox->elapsed>1500) // 嘲讽结束 + { + pfox->texture_render_offset = 0; + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 + pfox->elapsed = 0; + } + } + break; + case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 + { + pfox->elapsed += elapsed; + if (pfox->elapsed>800) + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + } + } + break; + case PX_OBJECT_FOX_STATE_ESCAPE: + { + pfox->elapsed += elapsed; + pfox->texture_render_offset += pfox->rasing_down_speed * elapsed / 1000; + if (pfox->texture_render_offset >= pObject->Height) + { + pfox->texture_render_offset = pObject->Height; + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 + pfox->elapsed = 0; // 重置时间 + pfox->pcurrent_display_texture = PX_NULL; + } + } + break; + default: + break; + } +} + +PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + px_float x, y, width, height; + PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 + if (pfox->pcurrent_display_texture) + { + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 + } + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 +} + +PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + PX_TextureFree(&pfox->render_target); +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 + { + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 + { + px_int x = (px_int)PX_Object_Event_GetCursorX(e); + px_int y = (px_int)PX_Object_Event_GetCursorY(e); + x = (px_int)(x - (pObject->x - pObject->Width/2)); + y = (px_int)(y - (pObject->y - pObject->Height)); + if (x>32&&y<128) + { + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); + pfox->state = PX_OBJECT_FOX_STATE_BEAT; + pfox->elapsed = 0; + PX_Object_ScorePanelAddScore(scorePanel, 100); + } + } + } +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) +{ + PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + pfox->state = PX_OBJECT_FOX_STATE_IDLE; + pfox->elapsed = 0; + pfox->texture_render_offset = pObject->Height; + pfox->gen_rand_time = 0; + pfox->pcurrent_display_texture = PX_NULL; + +} + +PX_Object *PX_Object_FoxCreate(px_memorypool *mp, PX_Object *parent, px_float x, px_float y) +{ + PX_Object_Fox* pfox; + px_texture *ptexture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); // 从资源管理器中获取纹理 + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); + pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 + pfox->rasing_down_speed = 512; // 升起速度 + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 + if (!PX_TextureCreate(mp, &pfox->render_target, ptexture->width, ptexture->height)) + { + PX_ObjectDelete(pObject); + return 0; + } + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_FoxOnClick, 0); // 注册点击事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_RESET, PX_Object_FoxOnReset, 0); // 注册重置事件 + return pObject; +} + +PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 +{ + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + px_float x, y, width, height; + PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); + if (phammer->bHit) + { + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 + } + else + { + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 + } + +} + +PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) +{ + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + PX_TextureFree(&phammer->ham01); + PX_TextureFree(&phammer->ham02); +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) +{ + pObject->x = PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 + pObject->y = PX_Object_Event_GetCursorY(e); +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) +{ + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_TRUE; // 按下 +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) +{ + PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_FALSE; // 抬起 +} + +PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) +{ + PX_Object_Hammer* phammer; + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, 0, 0, 0, 0, 0, 0, PX_OBJECT_TYPE_HAMMER, 0, PX_Object_HammerRender, PX_Object_HammerFree, 0, sizeof(PX_Object_Hammer)); + phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); + phammer->bHit = PX_FALSE; + if (!PX_LoadTextureFromFile(mp_static, &phammer->ham01, "assets/ham1.png")) return PX_NULL; + if (!PX_LoadTextureFromFile(mp_static, &phammer->ham02, "assets/ham2.png")) return PX_NULL; + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 + + return pObject; +} + +PX_OBJECT_EVENT_FUNCTION(PX_Object_StartGameOnClick) +{ + game->Visible = PX_TRUE; + startgame->Visible = PX_FALSE; + game->Enabled = PX_TRUE; + PX_Object_ScorePanelSetScore(scorePanel, 0); + PX_Object_ClockBegin(gameclock, 30000); // 开始游戏,游戏时间 30 秒 +} + + + + +px_int main() +{ + px_int i; + PainterEngine_Initialize(800, 480); + PX_FontModuleInitialize(mp_static, &score_fm); + PX_FontModuleSetCodepage(&score_fm, PX_FONTMODULE_CODEPAGE_GBK); + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/rasing.png", "fox_rasing")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/taunt.png", "fox_taunt")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/escape.png", "fox_escape")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/beat.png", "fox_beat")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/hurt.png", "fox_hurt")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/mask.png", "fox_mask")) return 0; + if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/background.png", "background")) return 0; + if (!PX_LoadAnimationToResource(PainterEngine_GetResourceLibrary(), "assets/song.2dx", "song")) return 0; + PainterEngine_SetBackgroundTexture(PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "background")); + for (i = 0; i <= 9; i++) + { + px_texture tex; + px_char path[64]; + PX_sprintf1(path, 64, "assets/%1.png", PX_STRINGFORMAT_INT(i)); + if (PX_LoadTextureFromFile(mp, &tex, path)) + { + PX_FontModuleAddNewTextureCharacter(&score_fm, '0' + i, &tex); + } + PX_TextureFree(&tex); + } + + startgame = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 90, "Start Game", 0); + startgame->Visible = PX_TRUE; + PX_Object_PushButtonSetBackgroundColor(startgame, PX_COLOR(96, 255, 255, 255)); + PX_Object_PushButtonSetPushColor(startgame, PX_COLOR(224, 255, 255, 255)); + PX_Object_PushButtonSetCursorColor(startgame, PX_COLOR(168, 255, 255, 255)); + PX_ObjectRegisterEvent(startgame, PX_OBJECT_EVENT_EXECUTE, PX_Object_StartGameOnClick, 0); + + + + game = PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); + PX_Object_FoxCreate(mp, game, 173, 326); + PX_Object_FoxCreate(mp, game, 401, 326); + PX_Object_FoxCreate(mp, game, 636, 326); + PX_Object_FoxCreate(mp, game, 173, 476); + PX_Object_FoxCreate(mp, game, 401, 476); + PX_Object_FoxCreate(mp, game, 636, 476); + game->Visible = PX_FALSE; + game->Enabled = PX_FALSE; + + + PX_Object_HammerCreate(mp, root); + scorePanel = PX_Object_ScorePanelCreate(mp, root, 400, 60, &score_fm, 100); + + gameclock = PX_Object_ClockCreate(mp, root, 680, 60); + + return PX_TRUE; +} +``` + +你可以在 documents/demo/game 中找到这个游戏的完整资源,并用 PainterEngine 直接编译。 + +![](assets/img/18.5.gif) + +在线试玩:[PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) + From cd7c26521c0134c45852b3297666b36072525c44 Mon Sep 17 00:00:00 2001 From: Recogerous <2737936634@qq.com> Date: Sat, 23 Nov 2024 10:56:47 +0800 Subject: [PATCH 6/9] traslate the doc to english 7-12 and also fix something that left in former commit in the Chinese version --- documents/PainterEngine_the_book_en.md | 401 +++++++++++----------- documents/PainterEngine_the_book_zh-CN.md | 16 +- 2 files changed, 208 insertions(+), 209 deletions(-) diff --git a/documents/PainterEngine_the_book_en.md b/documents/PainterEngine_the_book_en.md index 0d494c38..7372254a 100644 --- a/documents/PainterEngine_the_book_en.md +++ b/documents/PainterEngine_the_book_en.md @@ -505,13 +505,13 @@ In PainterEngine, there are 2 system default memory pools, they are `mp` and `mp These are the configuration macros directly related to these two memory pools, where `PX_APPLICATION_MEMORYPOOL_STATIC_SIZE` represents the memory allocation size of the `mp_static` memory pool, and `PX_APPLICATION_MEMORYPOOL_DYNAMIC_SIZE` is the memory allocation size of the `mp` memory pool, while `PX_APPLICATION_MEMORYPOOL_SPACE_SIZE` refers to other system resources. When the PainterEngine program starts running, it will at least occupy the memory accumulated by these three macros, and subsequent memory allocations will revolve around these few memory pools. If you find that the memory available for PainterEngine's operation is not enough, you can manually expand the size of the memory pool. Of course, if you hope to save some memory, you can also manually reduce their sizes. -## 7. 使用 PainterEngine 创建 GUI 按钮 +## 7. Creating a GUI Button Using PainterEngine -在本章节中,我们将第一次接触 PainterEngine 的组件。现在,我们将使用 PainterEngine 创建一个经典 GUI 组件——按钮。 +In this chapter, we will first encounter PainterEngine components. Now, we will use PainterEngine to create a classic GUI component—a button. -在 PainterEngine 中,所有的组件都是由 `PX_Object` 结构体进行描述的,创建组件返回的都是一个 `PX_Object *` 类型的指针。 +In PainterEngine, all components are described using the `PX_Object` structure, and creating a component returns a pointer of type `PX_Object *`. -但在本章节中,我们并不需要考虑的那么复杂,我们只需要创建一个按钮出来即可。在 PainterEngine 中,最常用的按钮是 `PX_Object_PushButton` 类型。 +However, in this chapter, we don't need to consider things that complex; we just need to create a button. In PainterEngine, the most commonly used button type is `PX_Object_PushButton`. ```c #include "PainterEngine.h" @@ -526,41 +526,41 @@ int main() ``` ![](assets/img/7.1.gif) -现在,我们来详细看看 `PX_Object_PushButtonCreate` 函数。其中,第一个参数是一个内存池,在之前我们说过 PainterEngine 有 2 个系统默认的内存池,其实这里填 `mp` 或者 `mp_static` 都是没有问题的,但考虑到界面可能会变动设计对象分配与销毁,所以我们还是选择 `mp` 内存池。 +Now, let's take a detailed look at the `PX_Object_PushButtonCreate` function. The first parameter is a memory pool. As we mentioned earlier, PainterEngine has two system default memory pools, and it doesn't matter whether you fill in `mp` or `mp_static` here. However, considering that the change of the interface may involve object allocation and destruction, we choose the `mp` memory pool. -第二个参数 `root` 是 PainterEngine 的根对象,PainterEngine 对象管理机制我们将在之后讨论。在这里,你只需要理解为,这里填 `root` 的意思是 **_创建一个按钮对象作为根对象的子对象_**。这样按钮就能链接到系统对象树中,进行事件响应和渲染。 +The second parameter `root` is the root object of PainterEngine. We will discuss the PainterEngine object management mechanism later. Here, you just need to understand that filling in `root` means **_create a button object as a child object of the root object_**. This way, the button can be linked to the system object tree for event response and rendering. -然后是按钮的 x,y,width,height,也就是位置和宽度高度等信息。 +Then come the x, y, width, and height of the button, which specify its position and dimensions. -最后一个是字模指针,也就是之前我们加载的 ttf 字模文件,如果没有它,我们的按钮就不能显示中文汉字了。当然你可以选择其他的字体,以实现不同的风格。 +The last parameter is the font module pointer, which is the TTF font file we loaded earlier. Without it, our button won't be able to display Chinese characters. Of course, you can choose other fonts to achieve different styles. -## 8. PainterEngine 对象传递机制 +## 8. PainterEngine Object Passing Mechanism -在上一个章节中,我们初略了解了根对象 `root`,那么在本章节,我们将学习一下 PainterEngine 的对象管理机制。 +In the previous chapter, we briefly introduced the root object `root`. In this chapter, we will learn about the object management mechanism of PainterEngine. -正如我们之前所说的,在 PainterEngine 中,所有的组件都是由 `PX_Object` 结构体进行描述的,PainterEngine 的对象是以树的形式存在的: +As we mentioned earlier, in PainterEngine, all components are described using the `PX_Object` structure, and PainterEngine objects exist in the form of a tree: ![](assets/img/8.1.png) -每一个 `PX_Object` 都是这个树中的一个节点,都可以有自己的子节点(可能多个)和自己的父节点(只能有一个)。同时,每一个 `PX_Object` 都有以下四个基本功能函数: +Each `PX_Object` is a node in this tree and can have its own child nodes (possibly multiple) and parent node (only one). Additionally, each `PX_Object` has the following four basic functional functions: -`Create`:对象创建函数,或者说是对象初始化函数,在 PainterEngine 中它一般是 `PX_Object_xxxxxCreate` 这种形式的,其中 `xxxxx` 就是这个对象的名称,比如上一章节的 `PushButton`,`Create` 函数一般是对象的一些初始化处理,并会将自己连接到对象树中。 +- `Create`: The object creation function, or rather, the object initialization function. In PainterEngine, it is generally in the form of `PX_Object_xxxxxCreate`, where `xxxxx` is the name of the object, such as `PushButton` in the previous chapter. The `Create` function generally handles some initialization processes of the object and connects it to the object tree. -`Update`:对象的物理信息更新工作基本在这个函数中完成,一般会处理对象的一些物理信息,比如位置大小速度等,常见于游戏设计中的物体,在 GUI 对象中则比较少见,其设计是与之后的 `Render` 也就是绘制函数进行区分,因为在例如游戏服务端中,对象并不需要进行绘制,且绘制是非常消耗性能的。 +- `Update`: The physical information update work of the object is basically completed in this function. It usually handles some physical information of the object, such as position, size, speed, etc., which is common in game design objects and less common in GUI objects. Its design is to distinguish from the subsequent `Render` function, because in game servers for example, objects do not need to be rendered, and rendering is very performance-intensive. -`Render`:对象的绘制工作基本在这个函数中完成,用于 `PX_Object` 的绘制功能,将图像数据渲染到屏幕上,当然有些情况下物理信息也会在这个函数中做,是因为这个对象的物理信息并不会影响游戏的实际运行结果,例如一些特效和粒子效果,多数的 GUI 组件也几乎只用得到 `Render` 函数。 +- `Render`: The rendering work of the object is basically completed in this function, used for the rendering functionality of `PX_Object`, rendering image data to the screen. In some cases, physical information is also handled in this function because the physical information of this object does not affect the actual operation of the game, such as special effects and particle effects. Most GUI components also almost only use the `Render` function. -`Free`:对象的释放工作基本在这个函数中完成,例如在 `Create` 中加载了纹理,或者申请了内存,在这个函数中应该被释放。 +- `Free`: The release work of the object is basically completed in this function, such as releasing textures loaded in `Create` or freeing allocated memory. -以上 `Update`、`Render`、`Free` 函数具有传递的特性,也就是说: +The above `Update`, `Render`, and `Free` functions have a passing characteristic, meaning: -- 如果某个对象节点执行了 `Update`,那么它的所有子对象也会执行 `Update` -- 如果某个对象节点执行了 `Render`,那么它的所有子对象也会执行 `Render` -- 如果某个对象节点执行了 `Free`,那么它的所有子对象也会执行 `Free`,父对象被删除了,它的子节点也会被删除,并且将会一直迭代到以这个节点为根节点的所有子节点都被删除。 +- If an object node executes `Update`, then all its child objects will also execute `Update`. +- If an object node executes `Render`, then all its child objects will also execute `Render`. +- If an object node executes `Free`, then all its child objects will also execute `Free`. When the parent object is deleted, its child nodes will also be deleted, and this process will continue until all child nodes under this node are deleted. -因此,在上一章节我们创建了按钮,并将它连接到了 `root` 节点,那么我们是不需要自己再手动执行 `Update`、`Render`、`Free` 函数的(在 `PX_Object_PushButton.c` 中它们已经被写好了),因为根节点 `root` 是被自动更新渲染和释放的,我们只需要负责 `Create` 就可以了。 +Therefore, in the previous chapter, when we created a button and connected it to the `root` node, we do not need to manually execute the `Update`, `Render`, and `Free` functions (they are already implemented in `PX_Object_PushButton.c`). The root node `root` is automatically updated, rendered, and released, so we only need to handle the `Create` function. -当然,如果你希望删除这个对象的话,你只需要调用 `PX_ObjectDelayDelete` 或者 `PX_ObjectDelete` 就可以了: +Of course, if you want to delete this object, you can simply call `PX_ObjectDelayDelete` or `PX_ObjectDelete`: ```c #include "PainterEngine.h" @@ -570,16 +570,16 @@ int main() PainterEngine_Initialize(800, 480); PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); myButtonObject = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 80, "我是一个按钮", PainterEngine_GetFontModule()); - PX_ObjectDelayDelete(myButtonObject); // 删除对象 + PX_ObjectDelayDelete(myButtonObject); // Delete the object return 1; } ``` -这两个函数的功能和参数都是一样的,但是 `PX_ObjectDelayDelete` 会在更新和渲染完成后才执行删除,`PX_ObjectDelete` 则是立即删除,我建议使用 `PX_ObjectDelayDelete`,这样你就可以避免在某些情况下因为对象被立即删除了,而其它对象仍然引用了这个对象的数据,这会导致其访问失效内存。 +Both functions have the same functionality and parameters, but `PX_ObjectDelayDelete` will execute the deletion after updating and rendering are completed, while `PX_ObjectDelete` performs immediate deletion. I recommend using `PX_ObjectDelayDelete`, as this can prevent issues where other objects still reference the data of an object that has been immediately deleted, leading to accessing invalid memory. -## 9. PainterEngine 消息机制 +## 9. PainterEngine Message Mechanism -现在,虽然我们创建了一个按钮,但我们却没办法响应它,为了响应按钮事件,我们需要将按钮控件和消息进行绑定,请查看以下代码: +Now, although we have created a button, we cannot respond to it. To respond to button events, we need to bind the button control to messages. Please see the following code: ```c #include "PainterEngine.h" @@ -603,78 +603,76 @@ int main() ![](assets/img/9.1.gif) -其中,`PX_OBJECT_EVENT_FUNCTION` 是一个宏,因为事件响应函数是一个固定的格式,因此非常建议你使用宏的方式来申明它,它的定义原型如下: +Here, `PX_OBJECT_EVENT_FUNCTION` is a macro. Since event handler functions have a fixed format, it is highly recommended to declare them using a macro. Its definition prototype is as follows: ```c #define PX_OBJECT_EVENT_FUNCTION(name) px_void name(PX_Object *pObject, PX_Object_Event e, px_void * ptr) ``` -可以看到,这个回调函数有 3 个参数,第一个是响应时间的对象的指针,因为是按钮点击被触发了,所以这个指针指向的就是这个按钮对象;第二个参数是事件类型 `e`,它是触发的事件类型;最后一个参数则是用户传递来的指针,它在注册时间响应函数 `PX_ObjectRegisterEvent` 被调用时就被传递进来了。 +As you can see, this callback function has 3 parameters. The first is a pointer to the object that triggered the event, which in this case is the button object. The second parameter is the event type `e`, which is the type of the triggered event. The last parameter is a user-passed pointer, which is passed in when the event handler function `PX_ObjectRegisterEvent` is called. -事件类型有以下几种: +There are several event types: ```c -#define PX_OBJECT_EVENT_ANY 0 // 任意事件 -#define PX_OBJECT_EVENT_CURSORMOVE 1 // 鼠标移动 -#define PX_OBJECT_EVENT_CURSORUP 2 // 鼠标左键弹起或触摸屏弹起 -#define PX_OBJECT_EVENT_CURSORRDOWN 3 // 鼠标右键按下 -#define PX_OBJECT_EVENT_CURSORDOWN 4 // 鼠标左键按下或触摸屏按下 -#define PX_OBJECT_EVENT_CURSORRUP 5 // 鼠标右键弹起 -#define PX_OBJECT_EVENT_CURSOROVER 6 // 鼠标进入范围 -#define PX_OBJECT_EVENT_CURSOROUT 7 // 鼠标离开范围 -#define PX_OBJECT_EVENT_CURSORWHEEL 8 // 鼠标滚轮 -#define PX_OBJECT_EVENT_CURSORCLICK 9 // 鼠标左键点击 -#define PX_OBJECT_EVENT_CURSORDRAG 10 // 鼠标拖拽 -#define PX_OBJECT_EVENT_STRING 11 // 字符串事件(输入法输入) -#define PX_OBJECT_EVENT_EXECUTE 12 // 执行事件,不同组件有不同的执行方式 -#define PX_OBJECT_EVENT_VALUECHANGED 13 // 值改变事件,例如滑动条的值改变,或者文本框的值改变,或者列表框的选中项改变 -#define PX_OBJECT_EVENT_DRAGFILE 14 // 拖拽文件 -#define PX_OBJECT_EVENT_KEYDOWN 15 // 键盘按下 -#define PX_OBJECT_EVENT_KEYUP 16 // 键盘弹起 -#define PX_OBJECT_EVENT_IMPACT 17 // 碰撞事件 -#define PX_OBJECT_EVENT_SCALE 18 // 缩放事件 -#define PX_OBJECT_EVENT_WINDOWRESIZE 19 // 窗口大小改变 -#define PX_OBJECT_EVENT_ONFOCUS 20 // 获得焦点 -#define PX_OBJECT_EVENT_LOSTFOCUS 21 // 失去焦点 -#define PX_OBJECT_EVENT_CANCEL 22 // 取消事件 -#define PX_OBJECT_EVENT_CLOSE 23 // 关闭事件 -#define PX_OBJECT_EVENT_CURSORMUP 24 // 鼠标中键弹起 -#define PX_OBJECT_EVENT_CURSORMDOWN 25 // 鼠标中键按下 -#define PX_OBJECT_EVENT_REQUESTDATA 26 // 请求数据 -#define PX_OBJECT_EVENT_OPEN 27 // 打开事件 -#define PX_OBJECT_EVENT_SAVE 28 // 保存事件 -#define PX_OBJECT_EVENT_TIMEOUT 29 // 超时事件 -#define PX_OBJECT_EVENT_DAMAGE 30 // 伤害事件 +#define PX_OBJECT_EVENT_ANY 0 // Any event +#define PX_OBJECT_EVENT_CURSORMOVE 1 // Mouse move +#define PX_OBJECT_EVENT_CURSORUP 2 // Left mouse button up or touch screen up +#define PX_OBJECT_EVENT_CURSORRDOWN 3 // Right mouse button down +#define PX_OBJECT_EVENT_CURSORDOWN 4 // Left mouse button down or touch screen down +#define PX_OBJECT_EVENT_CURSORRUP 5 // Right mouse button up +#define PX_OBJECT_EVENT_CURSOROVER 6 // Mouse enters range +#define PX_OBJECT_EVENT_CURSOROUT 7 // Mouse leaves range +#define PX_OBJECT_EVENT_CURSORWHEEL 8 // Mouse wheel +#define PX_OBJECT_EVENT_CURSORCLICK 9 // Left mouse button click +#define PX_OBJECT_EVENT_CURSORDRAG 10 // Mouse drag +#define PX_OBJECT_EVENT_STRING 11 // String event (input by the input method) +#define PX_OBJECT_EVENT_EXECUTE 12 // Execute event, different components have different execution methods +#define PX_OBJECT_EVENT_VALUECHANGED 13 // Value changed event, such as slider value change, or text box value change, or list box selection change +#define PX_OBJECT_EVENT_DRAGFILE 14 // Drag file +#define PX_OBJECT_EVENT_KEYDOWN 15 // Key down +#define PX_OBJECT_EVENT_KEYUP 16 // Key up +#define PX_OBJECT_EVENT_IMPACT 17 // Impact event +#define PX_OBJECT_EVENT_SCALE 18 // Scale event +#define PX_OBJECT_EVENT_WINDOWRESIZE 19 // Window resize +#define PX_OBJECT_EVENT_ONFOCUS 20 // Gain focus +#define PX_OBJECT_EVENT_LOSTFOCUS 21 // Lose focus +#define PX_OBJECT_EVENT_CANCEL 22 // Cancel event +#define PX_OBJECT_EVENT_CLOSE 23 // Close event +#define PX_OBJECT_EVENT_CURSORMUP 24 // Middle mouse button up +#define PX_OBJECT_EVENT_CURSORMDOWN 25 // Middle mouse button down +#define PX_OBJECT_EVENT_REQUESTDATA 26 // Request data +#define PX_OBJECT_EVENT_OPEN 27 // Open event +#define PX_OBJECT_EVENT_SAVE 28 // Save event +#define PX_OBJECT_EVENT_TIMEOUT 29 // Timeout event +#define PX_OBJECT_EVENT_DAMAGE 30 // Damage event ``` -以上事件并非全部都是任何组件都会响应的,例如在上面例子中的 `PX_OBJECT_EVENT_EXECUTE`,它是按钮被单击时会被触发的事件,或者是文本框中按下回车会触发的事件,但有些例如滚动条和进度条,并不会触发这个事件。也就是说有些事件是专属的。 +Not all of these events are responded to by every component. For example, in the above example, `PX_OBJECT_EVENT_EXECUTE` is an event that is triggered when a button is clicked or when the Enter key is pressed in a text box, but components like scroll bars and progress bars do not trigger this event. That means some events are specific to certain components. -但是类似于带有 `CURSOR` 或 `KEY` 的事件,是所有连接在 `root` 节点的组件都会收到的事件(但不一定响应)。需要注意的是,类似于鼠标或触摸屏的 `CURSOR` 事件,并非只有鼠标或触摸屏移动到组件所在位置与范围时才会触发,只要有这类事件投递到 `root` 节点,他就会逐层传递给它的所有子节点。如果你希望实现类似于按钮中的 "仅在鼠标点击到按钮时" 才触发,你必须自行实现范围判断。 +However, events like those prefixed with `CURSOR` or `KEY` are received by all components connected to the `root` node (though they may not all respond). It is important to note that `CURSOR` events, such as mouse or touch screen events, are not only triggered when the mouse or touch screen moves over the component's position and range. Whenever such an event is dispatched to the `root` node, it will be passed down layer by layer to all its child nodes. If you want to implement something like "only trigger when the mouse clicks on the button," you need to implement your own range check. -你可以使用 +You can use the following functions to get the functionality of "where the mouse is now" in a `cursor` event: ```c -px_float PX_Object_Event_GetCursorX(PX_Object_Event e); // 获取cursor事件的 x 坐标 -px_float PX_Object_Event_GetCursorY(PX_Object_Event e); // 获取cursor事件的 y 坐标 -px_float PX_Object_Event_GetCursorZ(PX_Object_Event e); // 获取cursor事件的 z 坐标,一般用于鼠标中键滚轮 +px_float PX_Object_Event_GetCursorX(PX_Object_Event e); // Get the x coordinate of the cursor event +px_float PX_Object_Event_GetCursorY(PX_Object_Event e); // Get the y coordinate of the cursor event +px_float PX_Object_Event_GetCursorZ(PX_Object_Event e); // Get the z coordinate of the cursor event, generally used for middle mouse button scroll wheel ``` -来获取 `cursor` 事件中类似于 "鼠标现在在哪里" 的功能。 +Let's go back to the `OnButtonClick` function in the source code, which simply changes the button text using `PX_Object_PushButtonSetText`. -让我们回到源代码 `OnButtonClick` 中做的很简单,就是用 `PX_Object_PushButtonSetText` 改变了按钮文本的内容。 +Finally, let's look at the `PX_ObjectRegisterEvent` function. This function binds events to C language functions. The first parameter is a pointer to the button component we created earlier. The second parameter is the event type we want to bind, in this case, `PX_OBJECT_EVENT_EXECUTE`, which is triggered when the button is clicked. The third parameter is a user pointer, which is passed to the callback function. If you don't need it, you can directly pass `PX_NULL`. -最后让我们来到 `PX_ObjectRegisterEvent` 函数,这个函数用于将事件与 C 语言函数绑定在一起,第一个参数是我们之前创建好的按钮组件的指针,第二个参数是我们想要绑定的事件类型,这里的 `PX_OBJECT_EVENT_EXECUTE` 就是按钮被点击时触发的,第三个则是用户指针,它会被传递到回调函数中,如果你用不到,你可以直接填 `PX_NULL`。 +## 10. A Small Example: Implementing a Digital Photo Album with PainterEngine -## 10. 小例子,用 PainterEngine 实现一个电子相册 - -现在,让我们用一个小例子来开启 PainterEngine 组件化开发的第一步。在本例程中,我将使用按钮和图片框组件,开发一个电子相册功能。本文中的美术资源,你可以在 `documents/logo` 中找到。 +Now, let's start the first step in component-based development with PainterEngine using a small example. In this example, I will use button and image frame components to develop a digital photo album feature. The art resources used in this article can be found in `documents/logo`. ```c #include "PainterEngine.h" -PX_Object* Previous, * Next, * Image; -px_texture my_texture[10]; // 存放图片的数组 -px_int index = 0; // 当前图片的索引 +PX_Object* Previous, *Next, *Image; +px_texture my_texture[10]; // Array to store images +px_int index = 0; // Index of the current image PX_OBJECT_EVENT_FUNCTION(OnButtonPreClick) { @@ -683,7 +681,7 @@ PX_OBJECT_EVENT_FUNCTION(OnButtonPreClick) { index = 9; } - PX_Object_ImageSetTexture(Image, &my_texture[index]); // 设置图片 + PX_Object_ImageSetTexture(Image, &my_texture[index]); // Set the image } PX_OBJECT_EVENT_FUNCTION(OnButtonNextClick) @@ -699,54 +697,54 @@ PX_OBJECT_EVENT_FUNCTION(OnButtonNextClick) int main() { px_int i; - PainterEngine_Initialize(512, 560); // 初始化 - for(i = 0; i < 10; i++) + PainterEngine_Initialize(512, 560); // Initialization + for (i = 0; i < 10; i++) { px_char path[256]; - PX_sprintf1(path, 256, "assets/%1.png", PX_STRINGFORMAT_INT(i+1)); - if (!PX_LoadTextureFromFile(mp_static, &my_texture[i], path)) // 加载图片 + PX_sprintf1(path, 256, "assets/%1.png", PX_STRINGFORMAT_INT(i + 1)); + if (!PX_LoadTextureFromFile(mp_static, &my_texture[i], path)) // Load the image { - // 加载失败 + // Load failed printf("加载失败"); return 0; } } - PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); // 加载字体 - Image = PX_Object_ImageCreate(mp, root, 0, 0, 512, 512, 0); // 创建图片对象 - Previous = PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "上一张", PainterEngine_GetFontModule()); // 创建按钮对象 - Next = PX_Object_PushButtonCreate(mp, root, 256, 512, 256, 48, "下一张", PainterEngine_GetFontModule()); // 创建按钮对象 - PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL); // 注册按钮事件 - PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL); // 注册按钮事件 + PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20); // Load the font + Image = PX_Object_ImageCreate(mp, root, 0, 0, 512, 512, 0); // Create an image object + Previous = PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "Previous", PainterEngine_GetFontModule()); // Create a button object + Next = PX_Object_PushButtonCreate(mp, root, 256, 512, 256, 48, "Next", PainterEngine_GetFontModule()); // Create a button object + PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL); // Register the button event + PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL); // Register the button event return 1; } ``` -在上述代码中 `OnButtonPreClick` 和 `OnButtonNextClick` 分别是上一张和下一张按钮的回调函数,我们使用 `PX_Object_ImageSetTexture` 函数,对图片框进行切换。 +In the above code, `OnButtonPreClick` and `OnButtonNextClick` are the callback functions for the "Previous" and "Next" buttons, respectively. We use the `PX_Object_ImageSetTexture` function to switch the images in the image frame. -而在 `main` 函数中,我们先加载了 ttf 字体,然后用 `PX_Object_ImageCreate` 创建了一个图片组件,之后我们创建了 2 个按钮,并用 `PX_ObjectRegisterEvent` 绑定了事件回调函数。最后,看看运行结果: +In the `main` function, we first load the TTF font, then create an image component using `PX_Object_ImageCreate`. After that, we create two buttons and bind their event callback functions using `PX_ObjectRegisterEvent`. Finally, here is the result: ![](assets/img/10.1.gif) -## 11. 更多常用的 PainterEngine 组件 +## 11. More Commonly Used PainterEngine Components -你可以在 `PainterEngine/kernel` 的文件中,找到 PainterEngine 的内置组件,所有的组件名称都是以 `PX_Object_XXXXX` 开头的,在这里,我为你列举一些常用的组件及示范代码: +You can find PainterEngine's built-in components in the `PainterEngine/kernel` files. All component names start with `PX_Object_XXXXX`. Here, I will list some commonly used components along with sample code: -- 文本框: +- Text Box: ```c #include "PainterEngine.h" PX_OBJECT_EVENT_FUNCTION(PX_Object_EditOnTextChanged) { - // 文本改变后后这里会被执行 + // This will be executed when the text changes } int main() { PX_Object* pObject; PainterEngine_Initialize(600, 400); - // 创建文本框 + // Create a text box pObject = PX_Object_EditCreate(mp, root, 200, 180, 200, 40, 0); - // 注册编辑框文本改变事件 + // Register the text change event for the edit box PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, PX_Object_EditOnTextChanged, PX_NULL); return 0; } @@ -754,7 +752,7 @@ int main() ![](assets/img/11.1.gif) -- 列表框: +- List Box: ```c #include "PainterEngine.h" @@ -764,21 +762,20 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_OnMyListItemRender) px_float objx, objy, objWidth, objHeight; PX_Object_ListItem *pItem = PX_Object_GetListItem(pObject); PX_OBJECT_INHERIT_CODE(pObject, objx, objy, objWidth, objHeight); - // 绘制出其文本 + // Draw its text PX_FontModuleDrawText(psurface, 0, (px_int)objx + 3, (px_int)objy + 3, PX_ALIGN_LEFTTOP, (const px_char *)pItem->pdata, PX_COLOR_WHITE); } - PX_OBJECT_LIST_ITEM_CREATE_FUNCTION(PX_Object_OnMyListItemCreate) { - // 绑定 ListItem 的渲染函数 + // Bind the render function to the ListItem ItemObject->Func_ObjectRender[0] = PX_Object_OnMyListItemRender; return PX_TRUE; } PX_OBJECT_EVENT_FUNCTION(PX_Object_ListOnSelectChanged) { - // 当选中项改变时 + // When the selected item changes return; } @@ -787,7 +784,7 @@ int main() PX_Object* pObject; PainterEngine_Initialize(600, 400); - // 创建 list + // Create a list pObject = PX_Object_ListCreate(mp, root, 100, 100, 400, 200, 24, PX_Object_OnMyListItemCreate, 0); PX_Object_ListAdd(pObject, "Item1"); PX_Object_ListAdd(pObject, "Item2"); @@ -801,14 +798,14 @@ int main() ![](assets/img/11.2.gif) -- 滑动条: +- Slider: ```c #include "PainterEngine.h" PX_OBJECT_EVENT_FUNCTION(SliderChanged) { - // 垂直滑动条值改变后执行这里的代码 + // Code to be executed when the vertical slider value changes return; } @@ -816,17 +813,18 @@ int main() { PX_Object* pObject; PainterEngine_Initialize(600, 400); - // 水平滑动条 + // Horizontal slider PX_Object_SliderBarCreate(mp, root, 200, 50, 200, 24, PX_OBJECT_SLIDERBAR_TYPE_HORIZONTAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); - // 垂直滑动条 + // Vertical slider pObject = PX_Object_SliderBarCreate(mp, root, 200, 100, 24, 200, PX_OBJECT_SLIDERBAR_TYPE_VERTICAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, SliderChanged, 0); return 0; } ``` + ![](assets/img/11.3.gif) -- 下拉框: +- Drop-down Box: ```c #include "PainterEngine.h" @@ -844,14 +842,15 @@ int main() return 0; } ``` + ![](assets/img/11.4.gif) -- 示波器: +- Oscilloscope: ```c #include "PainterEngine.h" -// 必须是生存域内有效可访问的数据,这里定义为全局变量 +// Must be valid and accessible data within the lifetime domain, defined here as a global variable px_double data_x[100]; px_double data_y[100]; @@ -863,35 +862,35 @@ int main() px_int i; PainterEngine_Initialize(600, 600); - // 初始化一个测试数据 + // Initialize test data for (i = 0; i < 100; i++) { data_x[i] = i; - data_y[i] = i+PX_randRange(-10, 10); + data_y[i] = i + PX_randRange(-10, 10); } pObject = PX_Object_OscilloscopeCreate(mp, root, 0, 0, 600, 600, 0); - // 设置水平坐标最小值最大值 + // Set the minimum and maximum values for the horizontal axis PX_Object_OscilloscopeSetHorizontalMin(pObject, 0); PX_Object_OscilloscopeSetHorizontalMax(pObject, 100); - // 设置垂直坐标(左边)最小值最大值 0-100 + // Set the minimum and maximum values for the vertical axis (left side) 0-100 PX_Object_OscilloscopeSetLeftVerticalMin(pObject, 0); PX_Object_OscilloscopeSetLeftVerticalMax(pObject, 100); - // 数据类型 - data.Color = PX_COLOR(255, 192, 255, 128); // 数据颜色 + // Data type + data.Color = PX_COLOR(255, 192, 255, 128); // Data color data.ID = 0; - data.linewidth = 3; // 数据线宽 - data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT; // 数据映射到左边垂直坐标 - data.MapHorizontalArray = data_x; // 数据水平坐标 - data.MapVerticalArray = data_y; // 数据垂直坐标 - data.Size = 100; // 数据大小 - data.Visibled = PX_TRUE; // 数据可见 - data.Normalization = 1; // 数据归一化系数为 1 + data.linewidth = 3; // Line width of the data + data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT; // Map data to the left vertical axis + data.MapHorizontalArray = data_x; // Horizontal coordinates of the data + data.MapVerticalArray = data_y; // Vertical coordinates of the data + data.Size = 100; // Size of the data + data.Visibled = PX_TRUE; // Data visibility + data.Normalization = 1; // Data normalization factor is 1 - // 添加数据 + // Add data PX_Object_OscilloscopeAddData(pObject, data); return 0; } @@ -899,19 +898,19 @@ int main() ![](assets/img/11.5.gif) -因为实在太多了,我无法为你列举所有的组件,如果你希望知道某个组件的具体用法和某个组件到底是做什么的,你可以访问 PainterEngine 的 [组件市场](https://market.painterengine.com/),在那里你可以找到 PainterEngine 内置组件和三方组件的说明和示例代码。 +Since there are too many components, I cannot list all of them. If you want to know the specific usage and purpose of a particular component, you can visit the [Component Market](https://market.painterengine.com/) of PainterEngine. There, you can find descriptions and sample code for both built-in and third-party components of PainterEngine. ![](assets/img/11.6.png) -## 12. 实现自己的 PainterEngine 组件 +## 12. Implementing Your Own PainterEngine Component -PainterEngine 鼓励组件式的开发架构。也就是说,不论是游戏还是 GUI 交互程序,甚至是程序功能,我们都可以用组件的形式去开发它。 +PainterEngine encourages a component-based development architecture. That is, whether it's a game, a GUI interactive program, or even program functionality, we can develop it in the form of a component. -组件式开发有点类似于 C++ 中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数,你可以参考 [前面的对象传递机制](#8painterengine-对象传递机制) 这一章节。 +Component-based development is somewhat similar to Classes in C++. Each component needs to implement its own `Create`, `Update`, `Render`, and `Free` functions. For the above four functions, you can refer to the [previous section on object passing mechanism](#8-painterengine-object-passing-mechanism). -为了演示这一点,让我们来实现一个“可控拖动旋转图片组件”,即我们可以用鼠标拖动图片在界面的位置,并用鼠标中键来旋转它。 +To demonstrate this, let's implement a "draggable and rotatable image component," which allows us to drag the image around the interface with the mouse and rotate it using the middle mouse button. -为了实现这一个功能,让我们一步一步完成这个步骤。首先,为了创建一个组件,我们需要一个结构体来描述我们的组件。我们需要绘制图片,所以我们需要一个 `px_texture` 类型。同时,我们还需要旋转图片,因此它还有一个 `rotation` 用于描述旋转的角度: +To achieve this function, let's complete this step-by-step. First, to create a component, we need a structure to describe our component. Since we need to draw an image, we require a `px_texture` type. Additionally, since we need to rotate the image, it also has a `rotation` to describe the angle of rotation: ```c #include "PainterEngine.h" @@ -928,7 +927,7 @@ px_int main() } ``` -之后,我们需要定义我们的 `Create`、`Update`、`Render` 和 `Free` 函数,其中 `Update`、`Render`、`Free` 有对应的格式,它们都有一个宏来简化我们的定义过程: +Afterwards, we need to define our `Create`, `Update`, `Render`, and `Free` functions. Among these, `Update`, `Render`, and `Free` have corresponding formats, each with a macro to simplify our definition process: ```c #define PX_OBJECT_RENDER_FUNCTION(name) px_void name(px_surface *psurface, PX_Object *pObject, px_int idesc, px_dword elapsed) @@ -936,7 +935,7 @@ px_int main() #define PX_OBJECT_FREE_FUNCTION(name) px_void name(PX_Object *pObject, px_int idesc) ``` -那么,在主函数中,我们就可以这样定义我们的这几个函数: +So, in the main function, we can define these functions as follows: ```c #include "PainterEngine.h" @@ -953,13 +952,13 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); // Render the image } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); - PX_TextureFree(&pMyObject->image); + PX_TextureFree(&pMyObject->image); // Free the image } px_int main() @@ -969,38 +968,38 @@ px_int main() } ``` -其中,因为我们不需要更新一些物理信息,所以 `MyObjectUpdate` 函数中我们可以什么都不写,在 `MyObjectRender` 中我们只需要把图片绘制出来就可以了,这里我们先使用 `PX_ObjectGetDesc` 函数获得我们定义好的结构体指针,它的第一个参数是结构体类型,第二个参数则是函数传递进来的 `pObject` 指针,然后我们只需要用 `PX_TextureRenderEx` 函数把图片绘制出来就可以了。 - -多提一句,`PX_TextureRenderEx` 函数用于在指定的表面上渲染纹理,并提供了对齐、混合、缩放和旋转等扩展选项。其中: - - `psurface`:指向要渲染纹理的表面的指针。 - - `resTexture`:指向要渲染的纹理资源的指针。 - - `x`:在表面上绘制纹理的 x 坐标。 - - `y`:在表面上绘制纹理的 y 坐标。 - - `refPoint`:对齐的参考点(例如,中心,左上角等)。 - - `blend`:指向混合选项结构的指针(如果不需要混合,可以为 `NULL`)。 - - `scale`:纹理的缩放因子(1.0 表示不缩放)。 - - `Angle`:纹理的旋转角度,以度为单位。 - -最后,是时候编写创建新对象的函数了,这里我们需要用到 `PX_ObjectCreateEx` 函数,`PX_ObjectCreateEx` 函数用于创建一个扩展对象,并初始化其属性和回调函数。它的参数说明如下: - -- `mp`:指向内存池的指针,用于分配对象所需的内存。 -- `Parent`:指向父对象的指针,如果没有父对象则为 `NULL`。 -- `x`:对象在 x 轴上的初始位置。 -- `y`:对象在 y 轴上的初始位置。 -- `z`:对象在 z 轴上的初始位置,z 坐标会影响其渲染的先后顺序。 -- `Width`:对象的宽度。 -- `Height`:对象的高度。 -- `Lenght`:对象的长度,2D 对象,一般可以是 0。 -- `type`:对象的类型。 -- `Func_ObjectUpdate`:指向对象更新函数的指针。 -- `Func_ObjectRender`:指向对象渲染函数的指针。 -- `Func_ObjectFree`:指向对象释放函数的指针。 -- `desc`:指向对象描述数据的指针。你可以设置为 0,创建时会把这个对象类型的数据填充为 0。 -- `size`:描述数据的大小,就是你定义的对象结构体类型的大小,创建对象函数会在内存池申请一段内存空间,并用于存储你的对象结构体。 - -在创建好一个空对象后,我们使用 `PX_ObjectGetDescIndex` 将对象中的对象结构体指针取出来,这是一个三参数的函数,第一个参数是对象结构体类型,第二个参数则是 `PX_Object *` 指针类型,因为一个 `PX_Object` 可以将多个对象结构体组合在一起,这个组合结构体我们将在之后的教程中会进一步描述,但现在我们只需要知道,调用 `PX_ObjectCreateEx` 函数后,其第一个存储的对象结构体索引是 0 就可以了。 - -取出结构体指针后,我们对其进行一系列初始化,比如加载图片和初始化旋转角度,最后在 `main` 函数中我们创建这个对象: +Here, since we don't need to update any physical information, we can leave the `MyObjectUpdate` function empty. In the `MyObjectRender` function, we only need to draw the image. Here, we first use the `PX_ObjectGetDesc` function to obtain the pointer to our defined structure. Its first parameter is the structure type, and the second parameter is the `pObject` pointer passed into the function. Then, we just need to use the `PX_TextureRenderEx` function to draw the image. + +As a side note, the `PX_TextureRenderEx` function is used to render a texture on a specified surface and provides extended options such as alignment, blending, scaling, and rotation. Specifically: + - `psurface`: Pointer to the surface where the texture will be rendered. + - `resTexture`: Pointer to the texture resource to be rendered. + - `x`: X-coordinate where the texture will be drawn on the surface. + - `y`: Y-coordinate where the texture will be drawn on the surface. + - `refPoint`: Alignment reference point (e.g., center, top-left, etc.). + - `blend`: Pointer to a blend option structure (can be `NULL` if no blending is required). + - `scale`: Scaling factor of the texture (1.0 indicates no scaling). + - `Angle`: Rotation angle of the texture, in degrees. + +Finally, it's time to write the function for creating a new object. Here, we need to use the `PX_ObjectCreateEx` function, which is used to create an extended object and initialize its properties and callback functions. The parameters are described as follows: + +- `mp`: Pointer to the memory pool used for allocating the memory required by the object. +- `Parent`: Pointer to the parent object, or `NULL` if there is no parent. +- `x`: Initial position of the object on the x-axis. +- `y`: Initial position of the object on the y-axis. +- `z`: Initial position of the object on the z-axis, affecting the rendering order. +- `Width`: Width of the object. +- `Height`: Height of the object. +- `Lenght`: Length of the object, generally 0 for 2D objects. +- `type`: Type of the object. +- `Func_ObjectUpdate`: Pointer to the object's update function. +- `Func_ObjectRender`: Pointer to the object's render function. +- `Func_ObjectFree`: Pointer to the object's free function. +- `desc`: Pointer to the object's description data. You can set this to 0, and the function will zero-initialize the object type data during creation. +- `size`: Size of the description data, which is the size of the object structure type you defined. The object creation function will allocate a block of memory from the memory pool to store your object structure. + +After creating an empty object, we use `PX_ObjectGetDescIndex` to extract the pointer to the object structure within the object. This is a three-parameter function; the first parameter is the object structure type, and the second parameter is the `PX_Object *` pointer type. Because a single `PX_Object` can combine multiple object structures, we will further describe this combined structure in later tutorials. For now, we only need to know that after calling the `PX_ObjectCreateEx` function, the index of the first stored object structure is 0. + +After extracting the structure pointer, we perform a series of initializations, such as loading the image and initializing the rotation angle. Finally, in the `main` function, we create this object: ```c #include "PainterEngine.h" @@ -1017,23 +1016,23 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); // 渲染图片 + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); // Render the image } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); - PX_TextureFree(&pMyObject->image); // 释放图片 + PX_TextureFree(&pMyObject->image); // Free the image } PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // 取得自定义对象数据 + PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // Create an empty custom object + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // Get the custom object data pMyObject->rotation = 0; - if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // 加载图片 + if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // Load the image { - PX_ObjectDelete(pObject); // 加载失败则删除对象 + PX_ObjectDelete(pObject); // Delete the object if loading fails return PX_NULL; } return pObject; @@ -1042,16 +1041,16 @@ PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_flo px_int main() { PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp, root, 400, 240); // 创建一个自定义对象 + PX_Object_MyObjectCreate(mp, root, 400, 240); // Create a custom object return PX_TRUE; } ``` -那么它的运行效果是这样的: +The running effect is as follows: ![](assets/img/12.1.png) -但现在还没有结束,我们怎么让我们的组件,响应鼠标中键实现旋转呢?还记得我们之前在 [PushButton](#8painterengine-对象传递机制) 中的对象传递机制么?现在,我们也要让我们的组件响应鼠标中键的信息,因此我们给它注册一个 `PX_OBJECT_EVENT_CURSORWHEEL` 事件的回调函数,代码如下: +But it's not over yet. How do we make our component respond to the middle mouse button to enable rotation? Do you remember the object passing mechanism in [PushButton](#8-painterengine-object-passing-mechanism)? Now, we also need to make our component respond to the middle mouse button information, so we register a callback function for the `PX_OBJECT_EVENT_CURSORWHEEL` event. The code is as follows: ```c #include "PainterEngine.h" @@ -1068,62 +1067,62 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); // 渲染图片 + PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, 1, pMyObject->rotation); // Render the image } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); - PX_TextureFree(&pMyObject->image); // 释放图片 + PX_TextureFree(&pMyObject->image); // Free the image } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) { PX_Object_MyObject *pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - if (PX_ObjectIsCursorInRegion(pObject, e)) // Object 是鼠标位置是否选中当前组件,e 是事件 + if (PX_ObjectIsCursorInRegion(pObject, e)) // Check if the mouse cursor is over the component, e is the event pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; } PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // 取得自定义对象数据 + PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // Create an empty custom object + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // Get the custom object data pMyObject->rotation = 0; - if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // 加载图片 + if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // Load the image { - PX_ObjectDelete(pObject); // 加载失败则删除对象 + PX_ObjectDelete(pObject); // Delete the object if loading fails return PX_NULL; } - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORWHEEL, MyObjectOnCursorWheel, 0); // 注册鼠标滚轮事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORWHEEL, MyObjectOnCursorWheel, 0); // Register the mouse wheel event return pObject; } px_int main() { PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp, root, 400, 240); // 创建一个自定义对象 + PX_Object_MyObjectCreate(mp, root, 400, 240); // Create a custom object return PX_TRUE; } ``` -运行结果如下: +The running result is as follows: ![](assets/img/12.2.gif) -如果你觉得旋转图的质量不好,有很多锯齿,这是因为 `PX_TextureRenderEx` 旋转时是对原图直接采样的。如果你想要高质量的旋转图,你可以用 `PX_TextureRenderRotation` 函数来替换原函数: +If you think the quality of the rotated image is poor and there are many jagged edges, this is because `PX_TextureRenderEx` directly samples the original image when rotating. If you want a high-quality rotated image, you can replace the original function with the `PX_TextureRenderRotation` function: ```c PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, pMyObject->rotation); // 渲染图片 + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, pMyObject->rotation); // Render the image } ``` ![](assets/img/12.3.gif) -那么,我们如何实现拖动效果呢?想要做到拖动效果,我们需要在对象结构体中,新增 `float` 类型的变量 `x`、`y`,用来记录当鼠标选中图片时的位置,同时我们加入了 `bool` 类型的变量 `bselect`,表示当前的图标是否被选中。当鼠标点击我们的图标以后,我们就可以监听 `PX_OBJECT_EVENT_CURSORDRAG` 事件,这是鼠标在屏幕上拖动时会产生的事件,我们通过坐标的偏移,移动我们的组件。最后,不论鼠标非拖动时的移动或鼠标左键抬起,都会取消我们组件的选中状态,在对应处理函数中取消选中状态即可。 +So, how do we implement the dragging effect? To achieve the dragging effect, we need to add `float` type variables `x` and `y` to the object structure to record the position of the mouse when the image is selected. We also add a `bool` type variable `bselect` to indicate whether the current icon is selected. When the mouse clicks on our icon, we can listen for the `PX_OBJECT_EVENT_CURSORDRAG` event, which is generated when the mouse is dragged on the screen. By offsetting the coordinates, we move our component. Finally, whether the mouse is moved without dragging or the left mouse button is released, it will cancel the selection state of our component. Simply unselect the component in the corresponding handler functions. ```c #include "PainterEngine.h" @@ -1142,26 +1141,26 @@ PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) PX_OBJECT_RENDER_FUNCTION(MyObjectRender) { PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, (px_int)pMyObject->rotation); // 渲染图片 + PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, 0, (px_int)pMyObject->rotation); // Render the image } PX_OBJECT_FREE_FUNCTION(MyObjectFree) { PX_Object_MyObject *pMyObject = PX_ObjectGetDesc(PX_Object_MyObject, pObject); - PX_TextureFree(&pMyObject->image); // 释放图片 + PX_TextureFree(&pMyObject->image); // Free the image } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) { PX_Object_MyObject *pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Object 是鼠标位置是否选中当前组件,e 是事件 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Check if the mouse cursor is over the component, e is the event pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; } PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDown) { PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Object 是鼠标位置是否选中当前组件,e 是事件 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER)) // Check if the mouse cursor is over the component, e is the event { pMyObject->bselect = PX_TRUE; pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); @@ -1189,32 +1188,32 @@ PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDrag) PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) { - PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // 创建一个空的自定义对象 - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // 取得自定义对象数据 + PX_Object *pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 128, 128, 0, 0, MyObjectUpdate, MyObjectRender, MyObjectFree, 0, sizeof(PX_Object_MyObject)); // Create an empty custom object + PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); // Get the custom object data pMyObject->rotation = 0; - if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // 加载图片 + if (!PX_LoadTextureFromFile(mp, &pMyObject->image, "assets/test.png")) // Load the image { - PX_ObjectDelete(pObject); // 加载失败则删除对象 + PX_ObjectDelete(pObject); // Delete the object if loading fails return PX_NULL; } - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORWHEEL, MyObjectOnCursorWheel, 0); // 注册鼠标滚轮事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, MyObjectOnCursorDrag, 0); // 注册鼠标拖拽事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, MyObjectOnCursorDown, 0); // 注册鼠标按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, MyObjectOnCursorRelease, 0); // 注册鼠标释放事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0); // 注册鼠标释放事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORWHEEL, MyObjectOnCursorWheel, 0); // Register the mouse wheel event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, MyObjectOnCursorDrag, 0); // Register the mouse drag event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, MyObjectOnCursorDown, 0); // Register the mouse down event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, MyObjectOnCursorRelease, 0); // Register the mouse up event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0); // Register the mouse up event return pObject; } px_int main() { PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp, root, 400, 240); // 创建一个自定义对象 + PX_Object_MyObjectCreate(mp, root, 400, 240); // Create a custom object return PX_TRUE; } ``` ![](assets/img/12.4.gif) -当然,你可以调用 `PX_Object_MyObjectCreate` 多次,创建多个组件对象,它们的功能都是一样的: +Of course, you can call `PX_Object_MyObjectCreate` multiple times to create multiple component objects, and they will all have the same functionality: ![](assets/img/12.5.gif) diff --git a/documents/PainterEngine_the_book_zh-CN.md b/documents/PainterEngine_the_book_zh-CN.md index e3d12b5e..c63752aa 100644 --- a/documents/PainterEngine_the_book_zh-CN.md +++ b/documents/PainterEngine_the_book_zh-CN.md @@ -529,13 +529,13 @@ int main() ``` ![](assets/img/7.1.gif) -现在,我们来详细看看 `PX_Object_PushButtonCreate` 函数。其中,第一个参数是一个内存池,在之前我们说过 PainterEngine 有 2 个系统默认的内存池,其实这里填 `mp` 或者 `mp_static` 都是没有问题的,但考虑到界面可能会变动设计对象分配与销毁,所以我们还是选择 `mp` 内存池。 +现在,我们来详细看看 `PX_Object_PushButtonCreate` 函数。其中,第一个参数是一个内存池,在之前我们说过 PainterEngine 有 2 个系统默认的内存池,其实这里填 `mp` 或者 `mp_static` 都是没有问题的,但考虑到界面可能会变动,涉及对象分配与销毁,所以我们还是选择 `mp` 内存池。 第二个参数 `root` 是 PainterEngine 的根对象,PainterEngine 对象管理机制我们将在之后讨论。在这里,你只需要理解为,这里填 `root` 的意思是 **_创建一个按钮对象作为根对象的子对象_**。这样按钮就能链接到系统对象树中,进行事件响应和渲染。 然后是按钮的 x,y,width,height,也就是位置和宽度高度等信息。 -最后一个是字模指针,也就是之前我们加载的 ttf 字模文件,如果没有它,我们的按钮就不能显示中文汉字了。当然你可以选择其他的字体,以实现不同的风格。 +最后一个是字模指针,也就是之前我们加载的 TTF 字模文件,如果没有它,我们的按钮就不能显示中文汉字了。当然你可以选择其他的字体,以实现不同的风格。 ## 8. PainterEngine 对象传递机制 @@ -549,7 +549,7 @@ int main() `Create`:对象创建函数,或者说是对象初始化函数,在 PainterEngine 中它一般是 `PX_Object_xxxxxCreate` 这种形式的,其中 `xxxxx` 就是这个对象的名称,比如上一章节的 `PushButton`,`Create` 函数一般是对象的一些初始化处理,并会将自己连接到对象树中。 -`Update`:对象的物理信息更新工作基本在这个函数中完成,一般会处理对象的一些物理信息,比如位置大小速度等,常见于游戏设计中的物体,在 GUI 对象中则比较少见,其设计是与之后的 `Render` 也就是绘制函数进行区分,因为在例如游戏服务端中,对象并不需要进行绘制,且绘制是非常消耗性能的。 +`Update`:对象的物理信息更新工作基本在这个函数中完成,一般会处理对象的一些物理信息,比如位置、大小、速度等,常见于游戏设计中的物体,在 GUI 对象中则比较少见,其设计是与之后的 `Render` 也就是绘制函数进行区分,因为在例如游戏服务端中,对象并不需要进行绘制,且绘制是非常消耗性能的。 `Render`:对象的绘制工作基本在这个函数中完成,用于 `PX_Object` 的绘制功能,将图像数据渲染到屏幕上,当然有些情况下物理信息也会在这个函数中做,是因为这个对象的物理信息并不会影响游戏的实际运行结果,例如一些特效和粒子效果,多数的 GUI 组件也几乎只用得到 `Render` 函数。 @@ -726,7 +726,7 @@ int main() 在上述代码中 `OnButtonPreClick` 和 `OnButtonNextClick` 分别是上一张和下一张按钮的回调函数,我们使用 `PX_Object_ImageSetTexture` 函数,对图片框进行切换。 -而在 `main` 函数中,我们先加载了 ttf 字体,然后用 `PX_Object_ImageCreate` 创建了一个图片组件,之后我们创建了 2 个按钮,并用 `PX_ObjectRegisterEvent` 绑定了事件回调函数。最后,看看运行结果: +而在 `main` 函数中,我们先加载了 TTF 字体,然后用 `PX_Object_ImageCreate` 创建了一个图片组件,之后我们创建了 2 个按钮,并用 `PX_ObjectRegisterEvent` 绑定了事件回调函数。最后,看看运行结果: ![](assets/img/10.1.gif) @@ -771,7 +771,6 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_OnMyListItemRender) PX_FontModuleDrawText(psurface, 0, (px_int)objx + 3, (px_int)objy + 3, PX_ALIGN_LEFTTOP, (const px_char *)pItem->pdata, PX_COLOR_WHITE); } - PX_OBJECT_LIST_ITEM_CREATE_FUNCTION(PX_Object_OnMyListItemCreate) { // 绑定 ListItem 的渲染函数 @@ -827,6 +826,7 @@ int main() return 0; } ``` + ![](assets/img/11.3.gif) - 下拉框: @@ -870,7 +870,7 @@ int main() for (i = 0; i < 100; i++) { data_x[i] = i; - data_y[i] = i+PX_randRange(-10, 10); + data_y[i] = i + PX_randRange(-10, 10); } pObject = PX_Object_OscilloscopeCreate(mp, root, 0, 0, 600, 600, 0); @@ -910,7 +910,7 @@ int main() PainterEngine 鼓励组件式的开发架构。也就是说,不论是游戏还是 GUI 交互程序,甚至是程序功能,我们都可以用组件的形式去开发它。 -组件式开发有点类似于 C++ 中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数,你可以参考 [前面的对象传递机制](#8painterengine-对象传递机制) 这一章节。 +组件式开发有点类似于 C++ 中的 Class,每一个组件,都要实现自己的 `Create`、`Update`、`Render`、`Free` 函数。关于上面四个函数,你可以参考 [前面的对象传递机制](#8-painterengine-对象传递机制) 这一章节。 为了演示这一点,让我们来实现一个“可控拖动旋转图片组件”,即我们可以用鼠标拖动图片在界面的位置,并用鼠标中键来旋转它。 @@ -1054,7 +1054,7 @@ px_int main() ![](assets/img/12.1.png) -但现在还没有结束,我们怎么让我们的组件,响应鼠标中键实现旋转呢?还记得我们之前在 [PushButton](#8painterengine-对象传递机制) 中的对象传递机制么?现在,我们也要让我们的组件响应鼠标中键的信息,因此我们给它注册一个 `PX_OBJECT_EVENT_CURSORWHEEL` 事件的回调函数,代码如下: +但现在还没有结束,我们怎么让我们的组件,响应鼠标中键实现旋转呢?还记得我们之前在 [PushButton](#8-painterengine-对象传递机制) 中的对象传递机制么?现在,我们也要让我们的组件响应鼠标中键的信息,因此我们给它注册一个 `PX_OBJECT_EVENT_CURSORWHEEL` 事件的回调函数,代码如下: ```c #include "PainterEngine.h" From 80b391f284b6ce9b4359d0a4178e6afe7892541a Mon Sep 17 00:00:00 2001 From: Recogerous <2737936634@qq.com> Date: Sat, 23 Nov 2024 11:35:36 +0800 Subject: [PATCH 7/9] traslate the doc to english 13-17 --- documents/PainterEngine_the_book_en.md | 317 ++++++++++------------ documents/PainterEngine_the_book_zh-CN.md | 8 +- 2 files changed, 141 insertions(+), 184 deletions(-) diff --git a/documents/PainterEngine_the_book_en.md b/documents/PainterEngine_the_book_en.md index 7372254a..7270d5f8 100644 --- a/documents/PainterEngine_the_book_en.md +++ b/documents/PainterEngine_the_book_en.md @@ -1217,11 +1217,11 @@ Of course, you can call `PX_Object_MyObjectCreate` multiple times to create mult ![](assets/img/12.5.gif) -## 13. 组合式组件设计 +## 13. Composite Component Design -PainterEngine 的组件允许同时拥有多种组件类型,例如,当我们将一个图片框组件和一个按钮进行组合,我们就可以得到一个组合式组件图片按钮。 +PainterEngine's components allow for the simultaneous possession of multiple component types, for example, when we combine an image box component with a button, we can get a composite component image button. -参考如下代码: +Refer to the following code: ```c #include "PainterEngine.h" @@ -1230,8 +1230,8 @@ PX_Object* image; PX_OBJECT_EVENT_FUNCTION(ButtonEvent) { - PX_Object_Image *pImage = PX_Object_GetImage(pObject); // 取得 Image 对象数据 - PX_Object_Button *pButton = PX_Object_GetButton(pObject); // 取得 Button 对象数据 + PX_Object_Image *pImage = PX_Object_GetImage(pObject); // Get the Image object data + PX_Object_Button *pButton = PX_Object_GetButton(pObject); // Get the Button object data if (pImage->pTexture == &tex1) { PX_Object_ImageSetTexture(pObject, &tex2); @@ -1245,30 +1245,30 @@ PX_OBJECT_EVENT_FUNCTION(ButtonEvent) px_int main() { PainterEngine_Initialize(800, 480); - if (!PX_LoadTextureFromFile(mp_static, &tex1, "assets/1.png")) return 0; // 加载纹理 1 - if (!PX_LoadTextureFromFile(mp_static, &tex2, "assets/2.png")) return 0; // 加载纹理 2 - image = PX_Object_ImageCreate(mp, root, 300, 140, 200, 200, &tex1); // 创建 Image 对象 - PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255)); // 将一个 Button 对象类型组合到 Image 对象上 - PX_ObjectRegisterEvent(image, PX_OBJECT_EVENT_EXECUTE, ButtonEvent, 0); // 这里实际上是注册 Button 对象的事件 + if (!PX_LoadTextureFromFile(mp_static, &tex1, "assets/1.png")) return 0; // Load texture 1 + if (!PX_LoadTextureFromFile(mp_static, &tex2, "assets/2.png")) return 0; // Load texture 2 + image = PX_Object_ImageCreate(mp, root, 300, 140, 200, 200, &tex1); // Create an Image object + PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255)); // Attach a Button object type to the Image object + PX_ObjectRegisterEvent(image, PX_OBJECT_EVENT_EXECUTE, ButtonEvent, 0); // Here we actually register the Button object's event return 1; } ``` -我们创建了一个 Image 图像框类型,然后将一个 Button 对象类型组合上去,这样我们就获得了一个图片按钮: +We created an Image image box type, then attached a Button object type to it, thus obtaining an image button: ![](assets/img/13.1.gif) -那么,我们如何设计我们自己的可组合对象呢?回到我们的第十二章节,现在,我们就将 "可拖拽" 这个功能设计成一个组合式组件。 +So, how do we design our own composable objects? Let's go back to our twelfth chapter, and turn the "draggable" feature into a composite component. -首先,仍然是定义一个组件对象结构体,为实现拖拽功能,我们需要鼠标按下时的 x, y 坐标,同时需要一个 bool 类型记录是否是选中状态,然后我们需要注册 `CURSOR` 事件,这些事件在上一章节我们已经写过了,最后,我们用 `PX_ObjectCreateDesc` 函数创建一个对象结构体,并将它 Attach 到我们的对象上。 +Firstly, still define a component object structure. To implement the drag function, we need the x, y coordinates when the mouse is pressed, as well as a bool type var to record whether it is selected, then we need to register `CURSOR` events, these events were already written in the previous chapter. Finally, we use the `PX_ObjectCreateDesc` function to create an object structure and attach it to our object. -`PX_ObjectCreateDesc` 是一个对象结构体创建函数,它的定义原型如下: +`PX_ObjectCreateDesc` is an object structure creation function, its prototype definition is as follows: ```c px_void* PX_ObjectCreateDesc(PX_Object* pObject, px_int idesc, px_int type, Function_ObjectUpdate Func_ObjectUpdate, Function_ObjectRender Func_ObjectRender, Function_ObjectFree Func_ObjectFree, px_void* pDesc, px_int descSize) ``` -第一个参数是需要 Attach 的对象,第二个参数是 Attach 到的对象索引。还记得我们之前提到的对象数据索引么,使用 `PX_ObjectCreateEx` 默认使用的是索引 0,因此,如果我们要附加到一个对象上,我们应该选 1,当然如果 1 也被占用了,它就是 2,以此类推。第三个参数是对象类型,我们使用 `PX_ObjectGetDescByType` 时,可以通过对象类型取出对应的指针,然后就是我们熟悉的 `Update`、`Render`、`Free` 三件套了,最后一个参数给出其结构体描述和结构体大小。请参阅下面的代码: +The first parameter is the object that needs to be Attached, the second parameter is the index of the object being Attached to. Do you remember the object data index we mentioned before? Using `PX_ObjectCreateEx` defaults to using index 0, so if we want to attach to an object, we should choose 1. Of course if 1 is also occupied, it would be 2, and so on. The third parameter is the object type, we can retrieve the corresponding pointer through the object type when using `PX_ObjectGetDescByType`, followed by the familiar `Update`, `Render`, `Free` trio. The last parameter provides its structure description and structure size. Please refer to the following code: ```c #include "PainterEngine.h" @@ -1313,7 +1313,6 @@ PX_Object* PX_Object_DragAttachObject(PX_Object* pObject, px_int attachIndex) { PX_Object_Drag* pDesc; - PX_ASSERTIF(pObject == PX_NULL); PX_ASSERTIF(attachIndex < 0 || attachIndex >= PX_COUNTOF(pObject->pObjectDesc)); PX_ASSERTIF(pObject->pObjectDesc[attachIndex] != PX_NULL); @@ -1332,21 +1331,20 @@ PX_Object* image; px_int main() { PainterEngine_Initialize(800, 480); - if (!PX_LoadTextureFromFile(mp_static, &tex1, "assets/1.png")) return 0; // 加载纹理 1 - image = PX_Object_ImageCreate(mp, root, 300, 140, 200, 200, &tex1); // 创建 Image 对象 - PX_Object_DragAttachObject(image, 1); // 将一个 Drag 对象类型组合到 Image 对象上 + if (!PX_LoadTextureFromFile(mp_static, &tex1, "assets/1.png")) return 0; // Load texture 1 + image = PX_Object_ImageCreate(mp, root, 300, 140, 200, 200, &tex1); // Create an Image object + PX_Object_DragAttachObject(image, 1); // Attach a Drag object type to the Image object return 1; } ``` -运行结果如下: +The running result is as follows: ![](assets/img/13.2.gif) +## 14. Particle System -## 14. 粒子系统 - -PainterEngine 提供了一个粒子系统实现,下面是一个粒子系统的示范程序: +PainterEngine provides an implementation of a particle system. Below is a demonstration program for a particle system: ```c #include "PainterEngine.h" @@ -1369,7 +1367,7 @@ px_int main() ![](assets/img/14.1.gif) -这是一个用组件包装起来的粒子系统实现,另外一种是提供了更加详细的粒子系统参数配置: +This is a particle system implementation wrapped in a component. Another approach offers more detailed configuration parameters for the particle system: ```c #include "PainterEngine.h" @@ -1411,125 +1409,85 @@ int main() } ``` -以下是这段代码的主要功能和流程解释: - -1. `#include "PainterEngine.h"`:引入 PainterEngine 的头文件,以便使用引擎的功能。 - -2. `px_texture texture;`:声明一个名为 `texture` 的变量,用于存储纹理信息。 - -3. `int main()`:主函数的入口点。 - -4. `PX_Object* pObject;`:声明一个名为 `pObject` 的指向 `PX_Object` 类型的指针,将用于创建粒子系统对象。 - -5. `PX_ParticalLauncher_InitializeInfo ParticalInfo;`:声明一个名为 `ParticalInfo` 的结构体变量,用于配置粒子发射器的初始化信息。 - -6. `PainterEngine_Initialize(600, 400);`:初始化 PainterEngine,设置窗口的宽度为 600 像素,高度为 400 像素。 - -7. `PX_LoadTextureFromFile(mp_static, &texture, "assets/star.traw");`:从文件加载纹理,将纹理数据存储在 `texture` 变量中。纹理文件路径为 "assets/star.traw"。 - -8. `PX_ParticalLauncherInitializeDefaultInfo(&ParticalInfo);`:初始化 `ParticalInfo` 结构体,设置了一些默认的粒子发射器属性。 - -9. 针对 `ParticalInfo` 的各个属性进行了具体的配置,包括粒子的位置、速度、寿命、大小、旋转等。这些属性决定了粒子的外观和行为。 - -10. `pObject = PX_Object_ParticalCreate(mp, root, 300, 200, ParticalInfo);`:使用配置好的 `ParticalInfo` 创建一个粒子系统对象,并将其存储在 `pObject` 中。这个粒子系统对象将会在窗口中的位置 (300, 200) 处发射粒子。 - -其中 `PX_ParticalLauncher_InitializeInfo` 用于配置粒子发射器的初始化信息,即在创建粒子系统时,可以通过填充这个结构体来指定粒子系统的各种属性和行为。以下是该结构体的各个成员的说明: - -1. `px_void *userptr;`:一个指向任意类型数据的指针,可用于存储用户自定义的数据。 - -2. `px_texture *tex;`:指向纹理数据的指针,用于指定粒子的纹理图像。 - -3. `px_point position;`:一个包含 x、y、z 坐标的点,表示粒子系统的初始位置。 - -4. `px_float deviation_position_distanceRange;`:一个浮点数,用于指定粒子的位置偏移范围。 - -5. `px_point direction;`:一个包含 x、y、z 坐标的点,表示粒子的初始运动方向。 - -6. `px_float deviation_rangAngle;`:一个浮点数,用于指定粒子的初始运动方向偏移范围(角度)。 - -7. `px_float velocity;`:一个浮点数,表示粒子的初始速度。 - -8. `px_float deviation_velocity_max;`:一个浮点数,表示粒子速度的最大偏移值。 - -9. `px_float deviation_velocity_min;`:一个浮点数,表示粒子速度的最小偏移值。 - -10. `px_float atomsize;`:一个浮点数,表示粒子的初始大小。 - -11. `px_float deviation_atomsize_max;`:一个浮点数,表示粒子大小的最大偏移值。 - -12. `px_float deviation_atomsize_min;`:一个浮点数,表示粒子大小的最小偏移值。 - -13. `px_float rotation;`:一个浮点数,表示粒子的初始旋转角度。 - -14. `px_float deviation_rotation;`:一个浮点数,表示粒子旋转角度的偏移范围。 - -15. `px_float alpha;`:一个浮点数,表示粒子的初始透明度。 - -16. `px_float deviation_alpha;`:一个浮点数,表示粒子透明度的偏移范围。 - -17. `px_float hdrR;`:一个浮点数,表示粒子的初始红色通道值。 - -18. `px_float deviation_hdrR;`:一个浮点数,表示粒子红色通道值的偏移范围。 - -19. `px_float hdrG;`:一个浮点数,表示粒子的初始绿色通道值。 - -20. `px_float deviation_hdrG;`:一个浮点数,表示粒子绿色通道值的偏移范围。 - -21. `px_float hdrB;`:一个浮点数,表示粒子的初始蓝色通道值。 - -22. `px_float deviation_hdrB;`:一个浮点数,表示粒子蓝色通道值的偏移范围。 - -23. `px_float sizeincrease;`:一个浮点数,表示粒子大小的增加率。 - -24. `px_float alphaincrease;`:一个浮点数,表示粒子透明度的增加率。 - -25. `px_point a;`:一个包含 x、y、z 坐标的点,用于自定义属性。 - -26. `px_float ak;`:一个浮点数,用于自定义属性。 - -27. `px_int alive;`:一个整数,表示粒子的生存时间(毫秒)。 - -28. `px_int generateDuration;`:一个整数,表示粒子发射器的生成周期(毫秒)。 - -29. `px_int maxCount;`:一个整数,表示粒子系统中最大的粒子数量。 - -30. `px_int launchCount;`:一个整数,表示粒子系统的发射次数。 - -31. `PX_ParticalLauncher_CreateAtom Create_func;`:一个函数指针,用于指定自定义的粒子创建函数。 - -32. `PX_ParticalLauncher_UpdateAtom Update_func;`:一个函数指针,用于指定自定义的粒子更新函数。 - -这个结构体允许你灵活地配置粒子系统的各种属性,以满足不同场景和效果的需求。通过调整这些属性,你可以控制粒子的外观、运动轨迹、生命周期等方面的行为。 +Below is the main functionality and flow explanation of this code: + +1. `#include "PainterEngine.h"`: Includes the PainterEngine header file to enable the use of engine features. +2. `px_texture texture;`: Declares a variable named `texture` to store texture information. +3. `int main()`: Entry point of the main function. +4. `PX_Object* pObject;`: Declares a pointer to `PX_Object` type named `pObject`, which will be used to create a particle system object. +5. `PX_ParticalLauncher_InitializeInfo ParticalInfo;`: Declares a structure variable named `ParticalInfo` to configure the initialization information of the particle launcher. +6. `PainterEngine_Initialize(600, 400);`: Initializes PainterEngine, setting the window width to 600 pixels and height to 400 pixels. +7. `PX_LoadTextureFromFile(mp_static, &texture, "assets/star.traw");`: Loads a texture from a file, storing the texture data in the `texture` variable. The texture file path is "assets/star.traw". +8. `PX_ParticalLauncherInitializeDefaultInfo(&ParticalInfo);`: Initializes the `ParticalInfo` structure, setting some default properties of the particle launcher. +9. Specific configurations are made for various attributes of `ParticalInfo`, including the position, velocity, lifetime, size, and rotation of particles. These attributes determine the appearance and behavior of particles. +10. `pObject = PX_Object_ParticalCreate(mp, root, 300, 200, ParticalInfo);`: Creates a particle system object using the configured `ParticalInfo` and stores it in `pObject`. This particle system object will emit particles at the position (300, 200) in the window. + +The `PX_ParticalLauncher_InitializeInfo` is used to configure the initialization information of the particle launcher. When creating a particle system, you can specify various attributes and behaviors of the particle system by filling out this structure. Here is an explanation of the members of this structure: + +1. `px_void *userptr;`: A pointer to any type of data, which can be used to store user-defined data. +2. `px_texture *tex;`: A pointer to texture data, used to specify the texture image of the particles. +3. `px_point position;`: A point containing x, y, z coordinates, representing the initial position of the particle system. +4. `px_float deviation_position_distanceRange;`: A floating-point number specifying the range of position offset for particles. +5. `px_point direction;`: A point containing x, y, z coordinates, indicating the initial motion direction of the particles. +6. `px_float deviation_rangAngle;`: A floating-point number specifying the range of initial motion direction offset (in degrees) for particles. +7. `px_float velocity;`: A floating-point number representing the initial velocity of the particles. +8. `px_float deviation_velocity_max;`: A floating-point number representing the maximum velocity offset value for particles. +9. `px_float deviation_velocity_min;`: A floating-point number representing the minimum velocity offset value for particles. +10. `px_float atomsize;`: A floating-point number representing the initial size of the particles. +11. `px_float deviation_atomsize_max;`: A floating-point number representing the maximum size offset value for particles. +12. `px_float deviation_atomsize_min;`: A floating-point number representing the minimum size offset value for particles. +13. `px_float rotation;`: A floating-point number representing the initial rotation angle of the particles. +14. `px_float deviation_rotation;`: A floating-point number representing the range of rotation angle offset for particles. +15. `px_float alpha;`: A floating-point number representing the initial transparency of the particles. +16. `px_float deviation_alpha;`: A floating-point number representing the range of transparency offset for particles. +17. `px_float hdrR;`: A floating-point number representing the initial red channel value of the particles. +18. `px_float deviation_hdrR;`: A floating-point number representing the range of red channel value offset for particles. +19. `px_float hdrG;`: A floating-point number representing the initial green channel value of the particles. +20. `px_float deviation_hdrG;`: A floating-point number representing the range of green channel value offset for particles. +21. `px_float hdrB;`: A floating-point number representing the initial blue channel value of the particles. +22. `px_float deviation_hdrB;`: A floating-point number representing the range of blue channel value offset for particles. +23. `px_float sizeincrease;`: A floating-point number representing the rate of increase in particle size. +24. `px_float alphaincrease;`: A floating-point number representing the rate of increase in particle transparency. +25. `px_point a;`: A point containing x, y, z coordinates, used for custom attributes. +26. `px_float ak;`: A floating-point number used for custom attributes. +27. `px_int alive;`: An integer representing the lifetime of particles (in milliseconds). +28. `px_int generateDuration;`: An integer representing the generation period of the particle launcher (in milliseconds). +29. `px_int maxCount;`: An integer representing the maximum number of particles in the particle system. +30. `px_int launchCount;`: An integer representing the number of launches of the particle system. +31. `PX_ParticalLauncher_CreateAtom Create_func;`: A function pointer specifying a custom particle creation function. +32. `PX_ParticalLauncher_UpdateAtom Update_func;`: A function pointer specifying a custom particle update function. + +This structure allows you to flexibly configure various attributes of the particle system to meet the needs of different scenarios and effects. By adjusting these attributes, you can control aspects of particle appearance, trajectory, lifecycle, and other behaviors. ![](assets/img/14.2.gif) -## 15. 使用 PainterEngine 播放音乐 +## 15. Playing Music with PainterEngine -PainterEngine 内置了对 wav 及 mp3 格式音乐的原生支持,使用 PainterEngine 播放音乐的代码十分简单: +PainterEngine natively supports playing music in wav and mp3 formats. The code to play music using PainterEngine is very simple: ```c #include "PainterEngine.h" -PX_SoundData sounddata; // 定义音乐格式 +PX_SoundData sounddata; // Define the music format int main() { PX_Object* pObject; PainterEngine_Initialize(600, 400); - PainterEngine_InitializeAudio(); // 初始化混音器及音乐设备 - if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav")) return PX_FALSE; // 加载音乐,支持 wav 及 mp3 格式 - PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE)); // 播放音乐 - pObject = PX_Object_SoundViewCreate(mp, root, 0, 0, 600, 400, soundplay); // 音乐频谱可视化组件,可选 + PainterEngine_InitializeAudio(); // Initialize the mixer and audio device + if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav")) return PX_FALSE; // Load the music, supporting wav and mp3 formats + PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE)); // Play the music + pObject = PX_Object_SoundViewCreate(mp, root, 0, 0, 600, 400, soundplay); // Optional music spectrum visualization component return 0; } ``` ![](assets/img/15.1.gif) -其中,`PX_LoadSoundFromFile` 函数从文件中加载音乐,并解码成 `sounddata` 类型。`PX_SoundCreate` 可以用 `sounddata` 创建一个播放实例,第二个参数表示这个实例是否循环播放,最后使用 `PX_SoundPlayAdd` 将播放实例送入混音器中,即可完成音乐播放。 +Here, the `PX_LoadSoundFromFile` function loads music from a file and decodes it into the `sounddata` type. `PX_SoundCreate` can create a playback instance using `sounddata`. The second parameter indicates whether this instance should loop. Finally, `PX_SoundPlayAdd` sends the playback instance to the mixer to complete the music playback. -## 16. PainterEngine Live2D 动画系统 +## 16. PainterEngine Live2D Animation System -PainterEngine 内置了一个类 live2D 动画系统,可以加载 live2d 动画,参考代码如下: +PainterEngine includes a Live2D-like animation system that can load Live2D animations. The reference code is as follows: ```c #include "PainterEngine.h" @@ -1547,21 +1505,20 @@ int main() PX_IO_Data iodata; PainterEngine_Initialize(600, 600); - // 加载模型数据 + // Load model data iodata = PX_LoadFileToIOData("assets/release.live"); if (iodata.size == 0) return PX_FALSE; PX_LiveFrameworkImport(mp_static, &liveframework, iodata.buffer, iodata.size); PX_FreeIOData(&iodata); - // 创建 Live2D 对象 + // Create a Live2D object pObject = PX_Object_Live2DCreate(mp, root, 300, 300, &liveframework); PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, onClick, PX_NULL); return 0; } - ``` -以下是与 Live2D 模型预览器相关的函数的说明: +Below are explanations of functions related to the Live2D model previewer: `PX_Object_Live2DCreate` @@ -1569,13 +1526,13 @@ int main() PX_Object* PX_Object_Live2DCreate(px_memorypool* mp, PX_Object* Parent, px_int x, px_int y, PX_LiveFramework *pLiveFramework); ``` -- **描述**:创建一个 Live2D 模型预览器对象,用于在图形界面中显示和交互 Live2D 模型。 -- **参数**: - - `mp`:内存池指针,用于分配内存。 - - `Parent`:父对象,Live2D 模型预览器对象将作为其子对象。 - - `x`, `y`:Live2D 模型预览器对象的位置坐标。 - - `pLiveFramework`:Live2D 模型框架的指针,包括模型数据、纹理等信息。 -- **返回值**:创建的 Live2D 模型预览器对象的指针。 +- **Description**: Creates a Live2D model previewer object for displaying and interacting with Live2D models in the graphical interface. +- **Parameters**: + - `mp`: Pointer to the memory pool used for memory allocation. + - `Parent`: Parent object; the Live2D model previewer object will be a child of this object. + - `x`, `y`: Position coordinates of the Live2D model previewer object. + - `pLiveFramework`: Pointer to the Live2D model framework, which includes model data, textures, and other information. +- **Return Value**: Pointer to the created Live2D model previewer object. `PX_Object_Live2DPlayAnimation` @@ -1583,11 +1540,11 @@ PX_Object* PX_Object_Live2DCreate(px_memorypool* mp, PX_Object* Parent, px_int x px_void PX_Object_Live2DPlayAnimation(PX_Object *pObject, px_char *name); ``` -- **描述**:播放指定名称的 Live2D 模型动画。 -- **参数**: - - `pObject`:Live2D 模型预览器对象的指针。 - - `name`:动画名称。 -- **返回值**:无。 +- **Description**: Plays a specified animation of the Live2D model. +- **Parameters**: + - `pObject`: Pointer to the Live2D model previewer object. + - `name`: Name of the animation. +- **Return Value**: None. `PX_Object_Live2DPlayAnimationRandom` @@ -1595,10 +1552,10 @@ px_void PX_Object_Live2DPlayAnimation(PX_Object *pObject, px_char *name); px_void PX_Object_Live2DPlayAnimationRandom(PX_Object* pObject); ``` -- **描述**:随机播放 Live2D 模型的动画。 -- **参数**: - - `pObject`:Live2D 模型预览器对象的指针。 -- **返回值**:无。 +- **Description**: Plays a random animation of the Live2D model. +- **Parameters**: + - `pObject`: Pointer to the Live2D model previewer object. +- **Return Value**: None. `PX_Object_Live2DPlayAnimationIndex` @@ -1606,28 +1563,29 @@ px_void PX_Object_Live2DPlayAnimationRandom(PX_Object* pObject); px_void PX_Object_Live2DPlayAnimationIndex(PX_Object* pObject, px_int index); ``` -- **描述**:播放 Live2D 模型的指定索引处的动画。 -- **参数**: - - `pObject`:Live2D 模型预览器对象的指针。 - - `index`:动画的索引。 -- **返回值**:无。 +- **Description**: Plays the animation at the specified index of the Live2D model. +- **Parameters**: + - `pObject`: Pointer to the Live2D model previewer object. + - `index`: Index of the animation. +- **Return Value**: None. -这些函数用于创建、配置和管理 Live2D 模型预览器对象,以在图形用户界面中显示和交互 Live2D 模型。可以使用这些函数播放 Live2D 模型的动画,包括指定名称、随机选择和指定索引处的动画。 +These functions are used to create, configure, and manage Live2D model previewer objects to display and interact with Live2D models in the graphical user interface. You can use these functions to play animations of the Live2D model, including those specified by name, randomly selected, and at a specific index. ![](assets/img/16.1.gif) -## 17. PainterEngine 脚本引擎 +## 17. PainterEngine Script Engine -PainterEngine 内置了一个平台无关的脚本引擎系统,集成了编译,运行,调试等功能,你可以很轻松地在脚本之上,实现并行调度功能。PainterEngine Script 的设计,最大程度和 C 语言保持一致性,并对一些类型进行的拓展和简化。 +PainterEngine includes a platform-independent script engine system that integrates compilation, execution, debugging, and other functionalities. You can easily implement parallel scheduling on top of scripts. The design of PainterEngine Script maintains maximum consistency with the C language and extends and simplifies some types. -例如在脚本中,支持 `int`、`float`、`string`、`memory` 四种类型,`int` 类型是一个 32 位的有符号整数,`float` 是一个浮点数类型,这个和 C 语言的类型保持了一致。`string` 类型类似于 C++ 的 `string`,它允许直接用 `+` 法运算符进行字符串拼接,使用 `strlen` 来获取其字符串长度,而 `memory` 是一个二进制数据存储类型,同样支持 `+` 运算进行拼接。 +For example, in the script, four types are supported: `int`, `float`, `string`, and `memory`. The `int` type is a 32-bit signed integer, and the `float` type is a floating-point number, both of which are consistent with C language types. The `string` type is similar to C++'s `string`, allowing direct concatenation of strings using the `+` operator and obtaining the string length using `strlen`. The `memory` type is a binary data storage type that also supports concatenation using the `+` operator. -在脚本中如果需要调用 C 语言函数,应该使用 `PX_VM_HOST_FUNCTION` 宏进行定义声明。和组件回调函数一样,`PX_VM_HOST_FUNCTION` 的定义如下: +If you need to call C language functions in the script, you should define them using the `PX_VM_HOST_FUNCTION` macro. Similar to component callback functions, the definition of `PX_VM_HOST_FUNCTION` is as follows: ```c #define PX_VM_HOST_FUNCTION(name) px_bool name(PX_VM *Ins, px_void *userptr) ``` -在下面的内容中,我将以一个简单的脚本实例作为范例,为你演示如何使用 PainterEngine 的脚本引擎: + +In the following content, I will use a simple script example to demonstrate how to use PainterEngine's script engine: ```c const px_char shellcode[] = "\ @@ -1664,18 +1622,17 @@ PX_VM_HOST_FUNCTION(host_sleep) } return PX_TRUE; } - ``` -首先,`shellcode` 数组中存储着一个输出九九乘法表的程序,其中需要调用两个 `host` 函数(脚本调用 C 语言函数称为 host call,因此 host 函数实际就是专门提供给脚本调用的 C 语言函数),一个是 `print` 函数,一个是 `sleep` 函数。因此在下面,我们定义了两个 `host` 函数,`PX_VM_HOSTPARAM` 用于取得脚本传递过来的参数。在这里,我们需要判断传递过来的参数类型是否符合我们的调用规则,像 `host_print` 函数,作用是在 PainterEngine 中输出字符串,而 `sleep` 函数,则是用来延迟一段时间。 +First, the `shellcode` array stores a program that outputs a multiplication table, which requires calling two `host` functions (calling C language functions from scripts is called a host call, so host functions are actually C language functions specifically provided for script calls). One is the `print` function, and the other is the `sleep` function. Therefore, we define two `host` functions below. `PX_VM_HOSTPARAM` is used to retrieve parameters passed from the script. Here, we need to check if the passed parameters match our calling rules. For example, the `host_print` function is used to output strings in PainterEngine, while the `sleep` function is used to delay for a certain period. -现在,PainterEngine Script 是一个编译型脚本,我们需要将上面的代码编译成二进制形式,然后将它送入虚拟机中运行,观察以下代码: +Now, PainterEngine Script is a compiled script. We need to compile the above code into binary form and then send it to the virtual machine for execution. Observe the following code: ```c PX_VM vm; PX_OBJECT_UPDATE_FUNCTION(VMUpdate) { - PX_VMRun(&vm, 0xffff, elapsed); // 运行虚拟机 + PX_VMRun(&vm, 0xffff, elapsed); // Run the virtual machine } px_int main() @@ -1684,32 +1641,32 @@ px_int main() px_memory bin; PainterEngine_Initialize(800, 600); PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler); // 初始化编译器 - PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 - PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 + PX_CompilerInitialize(mp, &compiler); // Initialize the compiler + PX_CompilerAddSource(&compiler, shellcode); // Add code to the compiler + PX_MemoryInitialize(mp, &bin); // Initialize memory for storing the compiled result if (!PX_CompilerCompile(&compiler, &bin, 0, "main")) { - // 编译失败 + // Compilation failed return 0; } - PX_CompilerFree(&compiler); // 释放编译器 - PX_VMInitialize(&vm, mp, bin.buffer, bin.usedsize); // 初始化虚拟机 - PX_VMRegisterHostFunction(&vm, "print", host_print, 0); // 注册主机函数 print - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep, 0); // 注册主机函数 sleep - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 - PX_ObjectSetUpdateFunction(root, VMUpdate, 0); // 设置更新函数 + PX_CompilerFree(&compiler); // Free the compiler + PX_VMInitialize(&vm, mp, bin.buffer, bin.usedsize); // Initialize the virtual machine + PX_VMRegisterHostFunction(&vm, "print", host_print, 0); // Register the host function print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep, 0); // Register the host function sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // Start running the virtual machine function + PX_ObjectSetUpdateFunction(root, VMUpdate, 0); // Set the update function return 0; } ``` -首先我们用 `PX_Compiler` 编译我们的脚本,然后我们注册我们的 host call,`PX_VMBeginThreadFunction` 的功能是 C 语言调用脚本语言中函数,在这里我们调用脚本中的 `main` 开始运行我们的脚本函数,最后我们将一个 `Update` 函数绑定到 root 节点,以循环更新虚拟机,来执行脚本。 +First, we compile our script using `PX_Compiler`, then register our host calls. The function `PX_VMBeginThreadFunction` is used to call a function in the script language from C. Here, we call the `main` function in the script to start running our script function. Finally, we bind an `Update` function to the root node to cyclically update the virtual machine and execute the script. -最后,看看运行的结果。 +Finally, let's look at the result of the run. ![](assets/img/17.1.gif) -如果我们想要对脚本进行调试,我们还可以在编译期间,创建一个符号映射表,这样我们就可以直接使用 `PX_Object_DebuggerMap` 对脚本进行调试。 +If we want to debug the script, we can create a symbol mapping table during compilation. This way, we can directly use `PX_Object_DebuggerMap` to debug the script. ```c px_int main() @@ -1719,19 +1676,19 @@ px_int main() PainterEngine_Initialize(800, 480); PX_VMDebuggerMapInitialize(mp, &debugmap); PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler); // 初始化编译器 - PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 - PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 + PX_CompilerInitialize(mp, &compiler); // Initialize the compiler + PX_CompilerAddSource(&compiler, shellcode); // Add code to the compiler + PX_MemoryInitialize(mp, &bin); // Initialize memory for storing the compiled result if (!PX_CompilerCompile(&compiler, &bin, &debugmap, "main")) { - // 编译失败 + // Compilation failed return 0; } - PX_CompilerFree(&compiler); // 释放编译器 - PX_VMInitialize(&vm, mp, bin.buffer, bin.usedsize); // 初始化虚拟机 - PX_VMRegisterHostFunction(&vm, "print", host_print, 0); // 注册主机函数 print - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep, 0); // 注册主机函数 sleep - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // 开始运行虚拟机函数 + PX_CompilerFree(&compiler); // Free the compiler + PX_VMInitialize(&vm, mp, bin.buffer, bin.usedsize); // Initialize the virtual machine + PX_VMRegisterHostFunction(&vm, "print", host_print, 0); // Register the host function print + PX_VMRegisterHostFunction(&vm, "sleep", host_sleep, 0); // Register the host function sleep + PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // Start running the virtual machine function PX_Object *pDbgObject = PX_Object_AsmDebuggerCreate(mp, root, 0, 0, 800, 480, 0); pDbgObject->Visible = PX_TRUE; PX_Object_AsmDebuggerAttach(pDbgObject, &debugmap, &vm); diff --git a/documents/PainterEngine_the_book_zh-CN.md b/documents/PainterEngine_the_book_zh-CN.md index c63752aa..5cd95f9e 100644 --- a/documents/PainterEngine_the_book_zh-CN.md +++ b/documents/PainterEngine_the_book_zh-CN.md @@ -1264,7 +1264,7 @@ px_int main() 那么,我们如何设计我们自己的可组合对象呢?回到我们的第十二章节,现在,我们就将 "可拖拽" 这个功能设计成一个组合式组件。 -首先,仍然是定义一个组件对象结构体,为实现拖拽功能,我们需要鼠标按下时的 x, y 坐标,同时需要一个 bool 类型记录是否是选中状态,然后我们需要注册 `CURSOR` 事件,这些事件在上一章节我们已经写过了,最后,我们用 `PX_ObjectCreateDesc` 函数创建一个对象结构体,并将它 Attach 到我们的对象上。 +首先,仍然是定义一个组件对象结构体。为实现拖拽功能,我们需要鼠标按下时的 x, y 坐标,同时需要一个 bool 类型记录是否是选中状态,然后我们需要注册 `CURSOR` 事件,这些事件在上一章节我们已经写过了。最后,我们用 `PX_ObjectCreateDesc` 函数创建一个对象结构体,并将它 Attach 到我们的对象上。 `PX_ObjectCreateDesc` 是一个对象结构体创建函数,它的定义原型如下: @@ -1690,7 +1690,7 @@ px_int main() PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); PX_CompilerInitialize(mp, &compiler); // 初始化编译器 PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 - PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 + PX_MemoryInitialize(mp, &bin); // 初始化内存,用于存储编译后的结果 if (!PX_CompilerCompile(&compiler, &bin, 0, "main")) { // 编译失败 @@ -1725,7 +1725,7 @@ px_int main() PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); PX_CompilerInitialize(mp, &compiler); // 初始化编译器 PX_CompilerAddSource(&compiler, shellcode); // 编译器中添加代码 - PX_MemoryInitialize(mp, &bin); // 初始化内存/用于存储编译后的结果 + PX_MemoryInitialize(mp, &bin); // 初始化内存,用于存储编译后的结果 if (!PX_CompilerCompile(&compiler, &bin, &debugmap, "main")) { // 编译失败 @@ -1747,7 +1747,7 @@ px_int main() ## 18. 使用 PainterEngine 快速创作一个小游戏 -为了更好地演示 PainterEngine 的使用,我将用 PainterEngine 创作一个简单的小游戏,你可以在 documents/demo/game 下找到有关这个游戏的所有源码及原始素材。得益于 PainterEngine 的全平台可移植性,你也可以在 [PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) 中,直接玩到这个在线小游戏。 +为了更好地演示 PainterEngine 的使用,我将用 PainterEngine 创作一个简单的小游戏,你可以在 `documents/demo/game` 下找到有关这个游戏的所有源码及原始素材。得益于 PainterEngine 的全平台可移植性,你也可以在 [PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) 中,直接玩到这个在线小游戏。 在这个小游戏中,我将充分为你展示,如何使用 PainterEngine 的组件化开发模式,快速创建一个 App Game。 From f7598388faa8b1401e69c12954813eaec3966412 Mon Sep 17 00:00:00 2001 From: Recogerous <2737936634@qq.com> Date: Sat, 23 Nov 2024 12:13:53 +0800 Subject: [PATCH 8/9] finish translation --- documents/PainterEngine_the_book_en.md | 351 +++++++++++----------- documents/PainterEngine_the_book_zh-CN.md | 16 +- 2 files changed, 182 insertions(+), 185 deletions(-) diff --git a/documents/PainterEngine_the_book_en.md b/documents/PainterEngine_the_book_en.md index 7270d5f8..1dda2661 100644 --- a/documents/PainterEngine_the_book_en.md +++ b/documents/PainterEngine_the_book_en.md @@ -1698,17 +1698,17 @@ px_int main() ![](assets/img/17.2.png) -## 18. 使用 PainterEngine 快速创作一个小游戏 +## 18. Creating a Simple Game with PainterEngine -为了更好地演示 PainterEngine 的使用,我将用 PainterEngine 创作一个简单的小游戏,你可以在 documents/demo/game 下找到有关这个游戏的所有源码及原始素材。得益于 PainterEngine 的全平台可移植性,你也可以在 [PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) 中,直接玩到这个在线小游戏。 +To better demonstrate the use of PainterEngine, I will create a simple game using PainterEngine. You can find all the source code and original assets for this game under `documents/demo/game`. Thanks to PainterEngine's full-platform portability, you can also play this online mini-game directly in the [PainterEngine Online Application APP — Whack-a-Mole](https://www.painterengine.com/main/app/documentgame/). -在这个小游戏中,我将充分为你展示,如何使用 PainterEngine 的组件化开发模式,快速创建一个 App Game。 +In this little game, I will fully demonstrate how to quickly create an App Game using PainterEngine's component-based development mode. -让我们先开始游戏创作的第一步,我们先准备好所需的美术资源及素材: +Let's start with the first step of game creation by preparing the required art resources and materials: ![](assets/img/18.1.png) -这是一个简单的游戏背景素材,然后我们就可以开始创建我们的 `main.c` 源代码文件,在 PainterEngine 中我们输入下面的代码: +This is a simple game background asset. Then we can begin creating our `main.c` source code file. In PainterEngine, we input the following code: ```c px_int main() @@ -1740,23 +1740,23 @@ px_int main() } ``` -在代码的开始阶段,我们初始化了一个 800x480 的窗口,然后我们初始化了字模,并用 `PX_FontModuleSetCodepage` 函数设置了其为 GBK 字符集,再后面,我们就是把资源加载进 PainterEngine 的资源管理器中了。 +At the beginning of the code, we initialize an 800x480 window, then we initialize the font and set its character set to GBK using the `PX_FontModuleSetCodepage` function. After that, we load the resources into PainterEngine's resource manager. -### 加载资源及设置背景 +### Loading Resources and Setting the Background -PainterEngine 内置了一个资源管理器,它在 `PainterEngine_Initialize` 中就被初始化了,使用的是 `mp_static` 内存池。资源管理器的作用是像数据库一样,将图片、音频、脚本等等素材加载到内存中,并将它映射为一个 `key`,之后对资源的访问都是通过 `key` 进行的。资源管理器的映射做了专门的优化,因此你不必太担心映射查询带来的性能损耗问题。 +PainterEngine has a built-in resource manager that is initialized in `PainterEngine_Initialize` using the `mp_static` memory pool. The role of the resource manager is like a database, loading assets such as images, audio, and scripts into memory and mapping them to a `key`. Subsequent access to resources is done through this `key`. The resource manager's mapping is specially optimized, so you don't have to worry too much about performance loss due to mapping queries. -`PX_LoadTextureToResource` 函数用于将一个文件系统的资源加载到资源管理器中,第一个参数是这个资源管理器的实例指针,PainterEngine 在初始化阶段会默认创建一个这样的管理器实例,因此你可以直接用 `PainterEngine_GetResourceLibrary` 获得它。第二个参数是需要加载文件的所在路径,第三个参数则是我们想映射的 `key` 了。 +The `PX_LoadTextureToResource` function is used to load a resource from the file system into the resource manager. The first parameter is the pointer to the instance of this resource manager. PainterEngine creates such a manager instance by default during initialization, so you can directly get it using `PainterEngine_GetResourceLibrary`. The second parameter is the path to the file to be loaded, and the third parameter is the `key` we want to map to. -在代码的下一步,我们使用 `PX_LoadTextureToResource` 加载了若干图片,`PX_LoadAnimationToResource` 加载了一个 2dx 动画(请到应用市场查看 2DX 动画详细说明)。最后,在游戏里我们并没有使用 TTF 字模文件,我们循环加载了 `0.png` 到 `9.png`,并将这些纹理作为图片插入到字模中,这样这个字模绘制数字时,实际显示的就是我们的图片。 +In the next step of the code, we use `PX_LoadTextureToResource` to load several images, and `PX_LoadAnimationToResource` to load a 2dx animation (please refer to the application market for detailed information on 2DX animations). Finally, in the game, we did not use TTF font files; instead, we looped through the loading of `0.png` to `9.png` and inserted these textures into the font as images. This way, when the font draws numbers, it actually displays our images. -同时我们还调用了 `PainterEngine_SetBackgroundTexture` 设置 PainterEngine 界面的背景,请注意 `PX_ResourceLibraryGetTexture` 函数,它的作用是使用一个查询 `key`,从资源管理器中取得这个图片的数据结构指针。以上完成后你将可以看到这样的界面: +We also called `PainterEngine_SetBackgroundTexture` to set the background of the PainterEngine interface. Note the `PX_ResourceLibraryGetTexture` function, which uses a query `key` to obtain the data structure pointer of the image from the resource manager. After completing the above steps, you will see an interface like this: ![](assets/img/18.2.png) -### 设计游戏对象 +### Designing Game Objects -我们先来设计第一个游戏对象,就是 `开始游戏按钮`。这一部分我们并不要写太多的代码,因为 PainterEngine 内置就有这种按钮的功能: +Let's start by designing the first game object, which is the `Start Game Button`. This part doesn't require writing too much code because PainterEngine already has built-in button functionality: ```c startgame = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 90, "Start Game", 0); @@ -1766,33 +1766,33 @@ PX_Object_PushButtonSetPushColor(startgame, PX_COLOR(224, 255, 255, 255)); PX_Object_PushButtonSetCursorColor(startgame, PX_COLOR(168, 255, 255, 255)); ``` -我们使用了一系列函数,改变了按钮的背景颜色、鼠标悬停颜色和鼠标按下的颜色,因此你可以看到这样的情况: +We used a series of functions to change the button's background color, hover color, and pressed color, so you can see something like this: ![](assets/img/18.3.png) -然后我们需要创建我们的游戏里的地鼠对象,这是游戏里最复杂的对象,我贴上详细代码,以逐步解释它们: +Next, we need to create the mole object in our game, which is the most complex object. I'll provide the detailed code and explain it step by step: ```c typedef enum { - PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 - PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 - PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 - PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 - PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 - PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 + PX_OBJECT_FOX_STATE_IDLE, // Fox is still in the hole + PX_OBJECT_FOX_STATE_RASING, // Fox is rising + PX_OBJECT_FOX_STATE_TAUNT, // Fox is taunting + PX_OBJECT_FOX_STATE_ESCAPE, // Fox is escaping + PX_OBJECT_FOX_STATE_BEAT, // Fox is beaten + PX_OBJECT_FOX_STATE_HURT, // Fox is hurt and escaping }PX_OBJECT_FOX_STATE; typedef struct { - PX_OBJECT_FOX_STATE state; // 狐狸状态 - px_dword elapsed; // 状态持续时间 - px_float texture_render_offset; // 纹理渲染偏移 - px_dword gen_rand_time; // 生成随机时间 - px_float rasing_down_speed; // 升起速度 - px_texture render_target; // 渲染目标 - px_texture* pcurrent_display_texture; // 当前显示的纹理 - px_texture* ptexture_mask; // 遮罩 + PX_OBJECT_FOX_STATE state; // Fox state + px_dword elapsed; // Duration of the state + px_float texture_render_offset; // Texture rendering offset + px_dword gen_rand_time; // Generated random time + px_float rasing_down_speed; // Rising speed + px_texture render_target; // Render target + px_texture* pcurrent_display_texture; // Current displayed texture + px_texture* ptexture_mask; // Mask }PX_Object_Fox; PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) @@ -1804,18 +1804,18 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) { if (pfox->gen_rand_time == 0) { - pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // Time the fox stays in the hole; rises when the time is up } else { - if (pfox->gen_rand_time gen_rand_time < elapsed) // Time is up { - // 升起 + // Rise pfox->state = PX_OBJECT_FOX_STATE_RASING; pfox->elapsed = 0; pfox->gen_rand_time = 0; pfox->texture_render_offset = pObject->Height; - // 改变纹理 + // Change texture pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); } else @@ -1825,42 +1825,42 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) } } break; - case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 + case PX_OBJECT_FOX_STATE_RASING: // Fox rising { pfox->elapsed += elapsed; - // 升起纹理偏移量 + // Rising texture offset pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; if (pfox->texture_render_offset <= 0) { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // Taunt after rising pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 + case PX_OBJECT_FOX_STATE_TAUNT: // Fox taunting { pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 + if (pfox->elapsed > 600 && pfox->elapsed < 1500) // Taunting time { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // Taunting texture } - else if (pfox->elapsed>1500) // 嘲讽结束 + else if (pfox->elapsed > 1500) // End of taunting { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // Escape + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // Escaping texture pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 + case PX_OBJECT_FOX_STATE_BEAT: // Fox beaten { pfox->elapsed += elapsed; - if (pfox->elapsed>800) + if (pfox->elapsed > 800) { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // Hurt texture + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // Escape } } break; @@ -1871,8 +1871,8 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) if (pfox->texture_render_offset >= pObject->Height) { pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 - pfox->elapsed = 0; // 重置时间 + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // End of escape + pfox->elapsed = 0; // Reset time pfox->pcurrent_display_texture = PX_NULL; } } @@ -1887,33 +1887,32 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); px_float x, y, width, height; PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // Clear render target if (pfox->pcurrent_display_texture) { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width / 2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // Render fox } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // Draw texture with mask } - PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) { PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); PX_TextureFree(&pfox->render_target); } -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // Fox clicked { PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT || pfox->state == PX_OBJECT_FOX_STATE_RASING) // Click is valid when fox is taunting or rising { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // Valid click area { px_int x = (px_int)PX_Object_Event_GetCursorX(e); px_int y = (px_int)PX_Object_Event_GetCursorY(e); - x = (px_int)(x - (pObject->x - pObject->Width/2)); + x = (px_int)(x - (pObject->x - pObject->Width / 2)); y = (px_int)(y - (pObject->y - pObject->Height)); - if (x>32&&y<128) + if (x > 32 && y < 128) { pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); pfox->state = PX_OBJECT_FOX_STATE_BEAT; @@ -1932,63 +1931,60 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) pfox->texture_render_offset = pObject->Height; pfox->gen_rand_time = 0; pfox->pcurrent_display_texture = PX_NULL; - } PX_Object *PX_Object_FoxCreate(px_memorypool *mp, PX_Object *parent, px_float x, px_float y) { PX_Object_Fox* pfox; - px_texture *ptexture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); // 从资源管理器中获取纹理 - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); + px_texture *ptexture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); // Get texture from resource manager + PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width * 1.f, ptexture->height * 1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 - pfox->rasing_down_speed = 512; // 升起速度 - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // Fox state + pfox->rasing_down_speed = 512; // Rising speed + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // Mask if (!PX_TextureCreate(mp, &pfox->render_target, ptexture->width, ptexture->height)) { PX_ObjectDelete(pObject); return 0; } - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_FoxOnClick, 0); // 注册点击事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_RESET, PX_Object_FoxOnReset, 0); // 注册重置事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_FoxOnClick, 0); // Register click event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_RESET, PX_Object_FoxOnReset, 0); // Register reset event return pObject; } ``` -- 首先是 `PX_Object_FoxOnUpdate`,这是对象三件套中的 `update` 函数,在这个函数中,我们判断当前这个 `地鼠` 的状态,到底是升起、嘲讽,还是缩回去。 -- 然后是 `PX_Object_FoxOnRender`,这是执行 `render` 的函数,我们通过偏移量把纹理绘制出来,当然在这里我们调用了 `PX_TextureRenderMask` 函数,这是一个带纹理遮罩的绘制函数。 -- `PX_Object_FoxFree` 函数中,主要是对临时渲染表面的释放处理,虽然在本项目中并没有用到。 -- `PX_Object_FoxOnClick` 函数,表示当前的地鼠被击打了,其中是一些命中范围的判断,如果被击中了,应该把状态设置为受伤。 -- `PX_Object_FoxOnReset` 用于执行复位,即游戏结束后,所有地鼠都应该是重置状态,这是一个 `PX_OBJECT_EVENT_RESET` 的回调,你可以在 `PX_Object_FoxCreate` 中找到它。 -- 最后是 `PX_Object_FoxCreate` 函数,在这个函数中我们做了一些初始化工作,为 `地鼠` 注册了事件回调,最终完成这个组件的开发设计。 - +- First is `PX_Object_FoxOnUpdate`, which is the `update` function in the object's trio. In this function, we determine the current state of the `mole`, whether it is rising, taunting, or retreating. +- Next is `PX_Object_FoxOnRender`, which is the function that performs the `render`. We draw the texture using an offset, and here we call the `PX_TextureRenderMask` function, which is a drawing function with a texture mask. +- In the `PX_Object_FoxFree` function, the main task is to handle the release of temporary render surfaces, although it is not used in this project. +- The `PX_Object_FoxOnClick` function represents the current mole being hit, where there are some hit range checks. If hit, the state should be set to hurt. +- `PX_Object_FoxOnReset` is used to perform a reset, meaning that after the game ends, all moles should be in a reset state. This is a callback for `PX_OBJECT_EVENT_RESET`, which you can find in `PX_Object_FoxCreate`. +- Finally, the `PX_Object_FoxCreate` function, where we do some initialization work, register event callbacks for the mole, and ultimately complete the development and design of this component. ![](assets/img/18.4.gif) - -然后,我们需要创建一个 `锤子` 对象来改变我们鼠标的样式。锤子对象的设计很简单,它只有 2 个纹理,一个是鼠标没有按下时的状态,一个是按下时的状态。不同的状态对应不同的纹理: +Then, we need to create a `Hammer` object to change the style of our mouse. The design of the hammer object is very simple; it only has two textures, one for when the mouse is not pressed and another for when it is pressed. Different states correspond to different textures: ```c typedef struct { - px_texture ham01; // 锤子纹理 1,没有按下 - px_texture ham02; // 锤子纹理 2,按下 - px_bool bHit; // 是否按下 + px_texture ham01; // Hammer texture 1, not pressed + px_texture ham02; // Hammer texture 2, pressed + px_bool bHit; // Whether pressed }PX_Object_Hammer; -PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 +PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // Hammer rendering { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); px_float x, y, width, height; PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); if (phammer->bHit) { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // Pressed } else { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // Not pressed } } @@ -2002,20 +1998,20 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) { - pObject->x = PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 + pObject->x = PX_Object_Event_GetCursorX(e); // Hammer follows mouse movement pObject->y = PX_Object_Event_GetCursorY(e); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_TRUE; // 按下 + phammer->bHit = PX_TRUE; // Pressed } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE; // 抬起 + phammer->bHit = PX_FALSE; // Released } PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) @@ -2026,24 +2022,24 @@ PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) phammer->bHit = PX_FALSE; if (!PX_LoadTextureFromFile(mp_static, &phammer->ham01, "assets/ham1.png")) return PX_NULL; if (!PX_LoadTextureFromFile(mp_static, &phammer->ham02, "assets/ham2.png")) return PX_NULL; - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // Register move event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // Register drag event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // Register press event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // Register press event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // Register release event return pObject; } ``` -最后则是一个倒计时框,它中间其实是一个 2dx 的动画对象(PainterEngine 直接支持 gif 动画,其实 gif 也可以),外围是一个环,环形的弧度不断减少,以实现一个 `倒计时` 的显示效果: +Finally, there is a countdown box. At its core, it is a 2dx animation object (PainterEngine directly supports gif animations, which are essentially gifs), surrounded by a ring whose arc continuously decreases to achieve a `countdown` display effect: ```c typedef struct { - PX_Animation animation; // 动画 - px_dword time; // 倒计时时间 - px_dword elapsed; // 倒计时开始后已经过去的时间 + PX_Animation animation; // Animation + px_dword time; // Countdown time + px_dword elapsed; // Time elapsed since the countdown started }PX_Object_Clock; @@ -2054,7 +2050,7 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) if (clock->elapsed >= clock->time) { clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给 game 对象发送重置事件 + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // Reset fox states, send a reset event to the game object game->Visible = PX_FALSE; game->Enabled = PX_FALSE; startgame->Visible = PX_TRUE; @@ -2067,11 +2063,11 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) { PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 - // draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128, 192, 255, 32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 + PX_AnimationUpdate(&clock->animation, elapsed); // Update animation + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // Render animation + // Draw ring + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // Draw the border of the countdown ring + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128, 192, 255, 32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // Draw the countdown ring } PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) @@ -2080,7 +2076,7 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) PX_AnimationFree(&clock->animation); } -px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 +px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // Start countdown { PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); clock->time = time; @@ -2095,7 +2091,7 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); clock->time = 0; clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // Get animation from resource manager { PX_ObjectDelete(pObject); return PX_NULL; @@ -2106,12 +2102,12 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float } ``` -### 放置对象,完成游戏 +### Placing Objects and Completing the Game -在 `main` 函数中,我们将上述对象一一创建,并放置在游戏场景中,最终完成这个游戏: +In the `main` function, we create the aforementioned objects one by one and place them in the game scene, ultimately completing the game: ```c -// 创建地鼠 +// Create moles game = PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); PX_Object_FoxCreate(mp, game, 173, 326); PX_Object_FoxCreate(mp, game, 401, 326); @@ -2122,14 +2118,14 @@ PX_Object_FoxCreate(mp, game, 636, 476); game->Visible = PX_FALSE; game->Enabled = PX_FALSE; -// 创建锤子 +// Create hammer PX_Object_HammerCreate(mp, root); scorePanel = PX_Object_ScorePanelCreate(mp, root, 400, 60, &score_fm, 100); -// 创建倒计时框 +// Create countdown box gameclock = PX_Object_ClockCreate(mp, root, 680, 60); ``` -在这里,我放上整个游戏的完整代码: +Here provides the complete code for the entire game: ```c #include "PainterEngine.h" @@ -2144,38 +2140,38 @@ PX_Object* game, *startgame, *gameclock; typedef enum { - PX_OBJECT_FOX_STATE_IDLE, // 狐狸还在洞里 - PX_OBJECT_FOX_STATE_RASING, // 狐狸正在升起 - PX_OBJECT_FOX_STATE_TAUNT, // 狐狸在嘲讽 - PX_OBJECT_FOX_STATE_ESCAPE, // 狐狸逃跑 - PX_OBJECT_FOX_STATE_BEAT, // 狐狸被打 - PX_OBJECT_FOX_STATE_HURT, // 狐狸受伤后逃跑 + PX_OBJECT_FOX_STATE_IDLE, // Fox is still in the hole + PX_OBJECT_FOX_STATE_RASING, // Fox is rising + PX_OBJECT_FOX_STATE_TAUNT, // Fox is taunting + PX_OBJECT_FOX_STATE_ESCAPE, // Fox is escaping + PX_OBJECT_FOX_STATE_BEAT, // Fox is beaten + PX_OBJECT_FOX_STATE_HURT, // Fox is hurt and escaping }PX_OBJECT_FOX_STATE; typedef struct { - PX_OBJECT_FOX_STATE state; // 狐狸状态 - px_dword elapsed; // 状态持续时间 - px_float texture_render_offset; // 纹理渲染偏移 - px_dword gen_rand_time; // 生成随机时间 - px_float rasing_down_speed; // 升起速度 - px_texture render_target; // 渲染目标 - px_texture* pcurrent_display_texture; // 当前显示的纹理 - px_texture* ptexture_mask; // 遮罩 + PX_OBJECT_FOX_STATE state; // Fox state + px_dword elapsed; // Duration of the state + px_float texture_render_offset; // Texture rendering offset + px_dword gen_rand_time; // Generated random time + px_float rasing_down_speed; // Rising speed + px_texture render_target; // Render target + px_texture* pcurrent_display_texture; // Current displayed texture + px_texture* ptexture_mask; // Mask }PX_Object_Fox; typedef struct { - px_texture ham01; // 锤子纹理 1,没有按下 - px_texture ham02; // 锤子纹理 2,按下 - px_bool bHit; // 是否按下 + px_texture ham01; // Hammer texture 1, not pressed + px_texture ham02; // Hammer texture 2, pressed + px_bool bHit; // Whether pressed }PX_Object_Hammer; typedef struct { - PX_Animation animation; // 动画 - px_dword time; // 倒计时时间 - px_dword elapsed; // 倒计时开始后已经过去的时间 + PX_Animation animation; // Animation + px_dword time; // Countdown time + px_dword elapsed; // Time elapsed since the countdown started }PX_Object_Clock; @@ -2186,7 +2182,7 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) if (clock->elapsed >= clock->time) { clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // 重置狐狸状态,给 game 对象发送重置事件 + PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET)); // Reset fox states, send a reset event to the game object game->Visible = PX_FALSE; game->Enabled = PX_FALSE; startgame->Visible = PX_TRUE; @@ -2199,11 +2195,11 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) { PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed); // 更新动画 - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // 绘制动画 + PX_AnimationUpdate(&clock->animation, elapsed); // Update animation + PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL); // Render animation // draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // 绘制倒计时环边框 - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128, 192, 255, 32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // 绘制倒计时环 + PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK); // Draw the border of the countdown ring + PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128, 192, 255, 32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time))); // Draw the countdown ring } PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) @@ -2212,7 +2208,7 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) PX_AnimationFree(&clock->animation); } -px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // 开始倒计时 +px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time) // Start countdown { PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); clock->time = time; @@ -2227,7 +2223,8 @@ PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); clock->time = 0; clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) // 从资源管理器中获取动画 + // Get animation from resource manager + if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))) { PX_ObjectDelete(pObject); return PX_NULL; @@ -2246,18 +2243,18 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) { if (pfox->gen_rand_time == 0) { - pfox->gen_rand_time = PX_rand() % 3000 + 1000; // 狐狸在洞里的时间,时间到了就升起来 + pfox->gen_rand_time = PX_rand() % 3000 + 1000; // Time the fox stays in the hole; rises when the time is up } else { - if (pfox->gen_rand_time gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; pfox->elapsed = 0; pfox->gen_rand_time = 0; pfox->texture_render_offset = pObject->Height; - // 改变纹理 + // Change texture pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); } else @@ -2267,42 +2264,42 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) } } break; - case PX_OBJECT_FOX_STATE_RASING: // 狐狸升起 + case PX_OBJECT_FOX_STATE_RASING: // Fox rising { pfox->elapsed += elapsed; - // 升起纹理偏移量 + // Rising texture offset pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; if (pfox->texture_render_offset <= 0) { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // 升起后嘲讽 + pfox->state = PX_OBJECT_FOX_STATE_TAUNT; // Taunt after rising pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 + case PX_OBJECT_FOX_STATE_TAUNT: // Fox taunting { pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 + if (pfox->elapsed>600&& pfox->elapsed <1500) // Taunting time { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // Taunting texture } - else if (pfox->elapsed>1500) // 嘲讽结束 + else if (pfox->elapsed>1500) // End of taunting { pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // 逃跑纹理 + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // Escape + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape"); // Escaping texture pfox->elapsed = 0; } } break; - case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 + case PX_OBJECT_FOX_STATE_BEAT: // Fox beaten { pfox->elapsed += elapsed; if (pfox->elapsed>800) { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 + pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // Hurt texture + pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // Escape } } break; @@ -2313,8 +2310,8 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) if (pfox->texture_render_offset >= pObject->Height) { pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 逃跑结束 - pfox->elapsed = 0; // 重置时间 + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // End of escape + pfox->elapsed = 0; // Reset time pfox->pcurrent_display_texture = PX_NULL; } } @@ -2329,12 +2326,12 @@ PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); px_float x, y, width, height; PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // 清空渲染目标 + PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE); // Clear render target if (pfox->pcurrent_display_texture) { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // 渲染狐狸 + PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL); // Render fox } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // 以遮罩形式绘制纹理 + PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL); // Draw texture with mask } PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) @@ -2343,12 +2340,12 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) PX_TextureFree(&pfox->render_target); } -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 +PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // Fox clicked { PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // 狐狸嘲讽或者升起时点击有效 + if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING) // Click is valid when fox is taunting or rising { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // 点击有效区域 + if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM)) // Valid click area { px_int x = (px_int)PX_Object_Event_GetCursorX(e); px_int y = (px_int)PX_Object_Event_GetCursorY(e); @@ -2379,34 +2376,34 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) PX_Object *PX_Object_FoxCreate(px_memorypool *mp, PX_Object *parent, px_float x, px_float y) { PX_Object_Fox* pfox; - px_texture *ptexture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); // 从资源管理器中获取纹理 + px_texture *ptexture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); // Get texture from resource manager PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - pfox->state = PX_OBJECT_FOX_STATE_IDLE; // 狐狸状态 - pfox->rasing_down_speed = 512; // 升起速度 - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // 遮罩 + pfox->state = PX_OBJECT_FOX_STATE_IDLE; // Fox state + pfox->rasing_down_speed = 512; // Rising speed + pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask"); // Mask if (!PX_TextureCreate(mp, &pfox->render_target, ptexture->width, ptexture->height)) { PX_ObjectDelete(pObject); return 0; } - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_FoxOnClick, 0); // 注册点击事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_RESET, PX_Object_FoxOnReset, 0); // 注册重置事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_FoxOnClick, 0); // Register click event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_RESET, PX_Object_FoxOnReset, 0); // Register reset event return pObject; } -PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // 锤子渲染 +PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender) // Hammer rendering { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); px_float x, y, width, height; PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); if (phammer->bHit) { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 按下 + PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // Pressed } else { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // 未按下 + PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL); // Not pressed } } @@ -2420,20 +2417,20 @@ PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) { - pObject->x = PX_Object_Event_GetCursorX(e); // 锤子跟随鼠标移动 + pObject->x = PX_Object_Event_GetCursorX(e); // Hammer follows mouse movement pObject->y = PX_Object_Event_GetCursorY(e); } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_TRUE; // 按下 + phammer->bHit = PX_TRUE; // Pressed } PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) { PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE; // 抬起 + phammer->bHit = PX_FALSE; // Released } PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) @@ -2444,11 +2441,11 @@ PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) phammer->bHit = PX_FALSE; if (!PX_LoadTextureFromFile(mp_static, &phammer->ham01, "assets/ham1.png")) return PX_NULL; if (!PX_LoadTextureFromFile(mp_static, &phammer->ham02, "assets/ham2.png")) return PX_NULL; - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // 注册移动事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // 注册拖拽事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // 注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // 注册按下事件 - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // 注册抬起事件 + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL); // Register move event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL); // Register drag event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL); // Register press event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL); // Register press event + PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL); // Register release event return pObject; } @@ -2459,7 +2456,7 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_StartGameOnClick) startgame->Visible = PX_FALSE; game->Enabled = PX_TRUE; PX_Object_ScorePanelSetScore(scorePanel, 0); - PX_Object_ClockBegin(gameclock, 30000); // 开始游戏,游戏时间 30 秒 + PX_Object_ClockBegin(gameclock, 30000); // Start countdown, 30 seconds } @@ -2521,9 +2518,9 @@ px_int main() } ``` -你可以在 documents/demo/game 中找到这个游戏的完整资源,并用 PainterEngine 直接编译。 +You can find the complete resources for this game in `documents/demo/game`, and compile it directly with PainterEngine. ![](assets/img/18.5.gif) -在线试玩:[PainterEngine 在线应用 APP——打地鼠](https://www.painterengine.com/main/app/documentgame/) +Online demo: [PainterEngine Online APP - Whack-a-Mole](https://www.painterengine.com/main/app/documentgame/) diff --git a/documents/PainterEngine_the_book_zh-CN.md b/documents/PainterEngine_the_book_zh-CN.md index 5cd95f9e..9e7007bd 100644 --- a/documents/PainterEngine_the_book_zh-CN.md +++ b/documents/PainterEngine_the_book_zh-CN.md @@ -1888,11 +1888,11 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 { pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 + if (pfox->elapsed > 600 && pfox->elapsed < 1500) // 嘲讽时间 { pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 } - else if (pfox->elapsed>1500) // 嘲讽结束 + else if (pfox->elapsed > 1500) // 嘲讽结束 { pfox->texture_render_offset = 0; pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 @@ -1904,7 +1904,7 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 { pfox->elapsed += elapsed; - if (pfox->elapsed>800) + if (pfox->elapsed > 800) { pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 @@ -1960,7 +1960,7 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 px_int y = (px_int)PX_Object_Event_GetCursorY(e); x = (px_int)(x - (pObject->x - pObject->Width/2)); y = (px_int)(y - (pObject->y - pObject->Height)); - if (x>32&&y<128) + if (x > 32 && y < 128) { pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); pfox->state = PX_OBJECT_FOX_STATE_BEAT; @@ -2330,11 +2330,11 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) case PX_OBJECT_FOX_STATE_TAUNT: // 狐狸嘲讽 { pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500) // 嘲讽时间 + if (pfox->elapsed > 600 && pfox->elapsed < 1500) // 嘲讽时间 { pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt"); // 嘲讽纹理 } - else if (pfox->elapsed>1500) // 嘲讽结束 + else if (pfox->elapsed > 1500) // 嘲讽结束 { pfox->texture_render_offset = 0; pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 @@ -2346,7 +2346,7 @@ PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) case PX_OBJECT_FOX_STATE_BEAT: // 狐狸被打 { pfox->elapsed += elapsed; - if (pfox->elapsed>800) + if (pfox->elapsed > 800) { pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt"); // 受伤纹理 pfox->state = PX_OBJECT_FOX_STATE_ESCAPE; // 逃跑 @@ -2401,7 +2401,7 @@ PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick) // 狐狸被点击 px_int y = (px_int)PX_Object_Event_GetCursorY(e); x = (px_int)(x - (pObject->x - pObject->Width/2)); y = (px_int)(y - (pObject->y - pObject->Height)); - if (x>32&&y<128) + if (x > 32 && y < 128) { pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); pfox->state = PX_OBJECT_FOX_STATE_BEAT; From 159934acd85943aa19ab10e64a5c1bd646d833b0 Mon Sep 17 00:00:00 2001 From: Recogerous <2737936634@qq.com> Date: Sat, 23 Nov 2024 12:16:45 +0800 Subject: [PATCH 9/9] Merge --- documents/PainterEngine the book.en.md | 2483 ------------------------ 1 file changed, 2483 deletions(-) delete mode 100644 documents/PainterEngine the book.en.md diff --git a/documents/PainterEngine the book.en.md b/documents/PainterEngine the book.en.md deleted file mode 100644 index c1847f3a..00000000 --- a/documents/PainterEngine the book.en.md +++ /dev/null @@ -1,2483 +0,0 @@ -# The Book Of PainterEngine - -## Introduction - -Welcome to the first lesson of PainterEngine. Before we begin, you might not fully understand what PainterEngine is about. Perhaps you have seen it mentioned online and know that it could be a graphics library or a game engine. You may have come across topics like acoustics, cryptography, neural networks, digital signal processing, compilers, virtual machines, or even an FPGA-based GPU IP core that are built upon it. This might lead you to think that PainterEngine is a mix of various libraries. - -All of the above are correct, but fundamentally, I prefer to describe PainterEngine as an application framework. Its ultimate purpose at inception was to address the hassle of third-party (even standard) library dependencies in software development, simplifying platform portability and reducing compilation difficulties as much as possible. - -As you will see, compiling PainterEngine does not trap you in dependency issues. It can run on almost all platforms that provide a C language compilation environment. It does not depend on an operating system or file system, making it suitable for running in bare-metal MCU environments. Even the official PainterEngine website is built using PainterEngine itself. - -PainterEngine adheres to a minimalist design principle and uses C language as its primary development language. Its built-in scripting engine is also highly compatible with C syntax, with minor abstraction and generalization of C's types, further lowering the barrier to entry. C, being a historically significant language, is now a fundamental course in most engineering-related majors. It has maintained strong competitiveness and widespread recognition in computer programming and development, becoming a de facto standard supported by nearly all hardware platforms. C strikes a delicate balance between learning and development costs, allowing you to grasp the language in a short time. With PainterEngine, you can deploy your programs across platforms and experience the true artistry of programming. - -PainterEngine has undergone nearly a decade of development. For a long time, it was not formally promoted as a public library, primarily because many of its interfaces and functions were unstable during iterations. We needed extensive practical experience to ensure the interfaces were reasonable and user-friendly, distinguishing between those that are "truly useful and convenient" and those that merely appear impressive but lack real utility. Consequently, for many years, PainterEngine lacked detailed and stable documentation. However, after years of iteration, we have refined its stable, user-friendly, and easy-to-learn designs, allowing us to finally present this document to you. - -Lastly, I do not wish to make the introduction overly lengthy. It is time to dive into the subject. In the first lesson of PainterEngine, we will start with setting up its environment. If you have any questions or discover bugs, feel free to ask on the PainterEngine forum or directly send an email to matrixcascade@gmail.com, and I will respond to you as soon as possible. - -![](assets/mini/1.png) - -## 1. A Simplest PainterEngine Program - -Before setting up the development environment, let us write the simplest PainterEngine program. Create a new file named `main.c` (the filename can be arbitrary), and input the following code: - -```c -#include "PainterEngine.h" -int main() -{ - PainterEngine_Initialize(800,480); - return 1; -} -``` - -This is a very basic PainterEngine program. To explain briefly, in the first line, we use `#include` to include the header file of PainterEngine. In the `main` function, we use `PainterEngine_Initialize` to initialize PainterEngine. The function `PainterEngine_Initialize` takes two parameters, which represent the width and height of the window (or screen). After running the program, you should see a result similar to this: - -![](assets/img/1.1.png) - -Of course, we have not yet used PainterEngine to draw anything on the window, so what you see is a blank screen. It is important to note that in PainterEngine's framework, the program does not immediately terminate after the `main` function returns. In fact, in `PainterEngine.h`, the `main` function is replaced by `px_main`. The actual `main` function is implemented in `px_main.c`. However, as a user, you do not need to worry about this for now—just remember that the program continues running normally even after the `main` function returns. - -If you wish to exit the program, you can call the `exit` function from C manually. However, in many scenarios with PainterEngine, this is not necessary. For example, in embedded microcontroller systems, web applications, or driver programs, the concept of exiting is rarely needed. Even on Android and iOS platforms, most of the time, you do not need to design an exit feature in your program. - -## 2. Compiling a PainterEngine Program - -### Compiling with PainterEngine Make - -If you want to compile a PainterEngine project file, the simplest method is to use PainterEngine Make. This is a compilation tool that you can download from **PainterEngine.com**. You will find the download button at the bottom of the homepage: - -![](assets/img/2.1.png) - -After extracting the files, run `PainterEngine make.exe`, and you will see the following interface: - -![](assets/img/2.2.png) - -Next, select the platform you need to compile for and then choose the C source file we created earlier: - -![](assets/img/2.3.png) - -Finally, just wait for the compilation process to complete: - -![](assets/img/2.4.png) - -### Compiling with Visual Studio Code - -To compile using Visual Studio Code, you need to ensure that the C language development environment for Visual Studio Code is properly installed. We will skip this step, as there are already plenty of tutorials available online for this. - -Next, go to PainterEngine and download the source code for PainterEngine: - -![](assets/img/2.5.png) - -Extract the downloaded source code to a directory on your computer. Then, you need to note the location of this directory and create a new environment variable in Windows called `PainterEnginePath`, assigning it the path of the PainterEngine library directory: - -![](assets/img/2.6.png) - -![](assets/img/2.7.png) - -Copy the `PainterEngine/Platform/.vscode` directory to a location next to the source code files: - -![](assets/img/2.8.png) - -![](assets/img/2.9.png) - -Next, open the `main.c` file using Visual Studio Code, and you can proceed to compile and run it: - -![](assets/img/2.10.png) - -![](assets/img/2.11.png) - -### Compiling with Visual Studio - -Of course, if you need the full IDE development experience, it is still recommended to use Visual Studio for development and compilation. To develop PainterEngine using Visual Studio, you need to open Visual Studio and create an empty project: - -![](assets/img/2.12.png) - -![](assets/img/2.13.png) - -After creating the project, I strongly recommend that you create a new filter: - -![](assets/img/2.14.png) - -Then add all the files from the `core`, `kernel`, `runtime`, and `platform/windows` directories under the PainterEngine directory to this filter: - -![](assets/img/2.15.png) - -![](assets/img/2.16.png) - -Then add the `main.c` file we created earlier to the project as well: - -![](assets/img/2.17.png) - -Open `Project` → `Properties` → `VC++ Directories`, and include the directory where PainterEngine is located: - -![](assets/img/2.18.png) - -![](assets/img/2.19.png) - -![](assets/img/2.20.png) - -Be sure to check whether the configuration matches the current configuration of Visual Studio, as this is a common point of error: - -![](assets/img/2.21.png) - -Finally, you can compile, run, and debug: - -![](assets/img/2.22.png) - -## 3. PainterEngine Lesson 1: Displaying the Text "Hello PainterEngine" - -As you can see, PainterEngine is a graphical application framework. However, following tradition, our first lesson is still about how to display text using PainterEngine. In most cases, it is more accurate to say "draw text" rather than "output text." Drawing text with PainterEngine is very straightforward. Refer to the code below: - -```c -#include "PainterEngine.h" -int main() -{ - PainterEngine_Initialize(800,480); - //PainterEngine_DrawText - //Parameter 1: x coordinate - //Parameter 2: y coordinate - //Parameter 3: text content - //Parameter 4: alignment - //Parameter 5: color - PainterEngine_DrawText(400, 240, "Hello PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); - return 1; -} -``` - -We will focus on the `PainterEngine_DrawText` function, which is a text-drawing function. It takes five parameters. When you run the program, you will see the following result: - -![](assets/img/3.1.png) - -The function is quite easy to understand, but let us explain the meaning of the `alignment` and `color` parameters in detail, as these two concepts will be frequently mentioned in later tutorials: - -The `alignment` parameter specifies how the content is aligned when drawn on the screen. PainterEngine supports the following alignment formats: - -```c -typedef enum -{ - PX_ALIGN_LEFTTOP = 7,//Align to the top-left corner - PX_ALIGN_MIDTOP = 8,//Align to the middle-top - PX_ALIGN_RIGHTTOP = 9,//Align to the top-right corner - PX_ALIGN_LEFTMID = 4,//Align to the middle-left - PX_ALIGN_CENTER = 5,//Align to the center - PX_ALIGN_RIGHTMID = 6,//Align to the middle-right - PX_ALIGN_LEFTBOTTOM = 1,//Align to the bottom-left corner - PX_ALIGN_MIDBOTTOM = 2,//Align to the middle-bottom - PX_ALIGN_RIGHTBOTTOM = 3//Align to the bottom-right corner -}PX_ALIGN; -``` - -The alignment enumeration is designed in a way that corresponds to the numeric keypad. You can directly reference your numeric keypad, as the alignment styles align with the values of the keys. - -The `color format`, on the other hand, is defined as a structure called `px_color`. This structure consists of 4 bytes with four member variables: `a`, `r`, `g`, and `b`, representing the alpha (transparency), red, green, and blue channels of the color, respectively. Each component has a value range of 0-255. For example, for the red channel, the higher the value, the redder the color. - -In the example code above, we drew a red text `Hello PainterEngine`. Now, let us try using Chinese characters by modifying the code to the following format: - -```c -#include "PainterEngine.h" - -int main() -{ - PainterEngine_Initialize(800, 480); - // PainterEngine_DrawText - // Parameter 1: x coordinate - // Parameter 2: y coordinate - // Parameter 3: Text content - // Parameter 4: Alignment mode - // Parameter 5: Color - PainterEngine_DrawText(400, 240, "Hello PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); - - return 1; -} -``` - -![](assets/img/3.2.png) - -However, Chinese characters cannot be displayed correctly because PainterEngine only includes English fonts by default. So, what should we do if we want to support Chinese? - -This is still not difficult. To achieve this, we first need to prepare a TTF font file. For example, here I have prepared a "YouYuan" font. All we need to do is load this font into the program: - -```c -#include "PainterEngine.h" -int main() -{ - PainterEngine_Initialize(800,480); - PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 24); - PainterEngine_DrawText(400, 240, "Hello PainterEngine", PX_ALIGN_CENTER, PX_COLOR(255, 255, 0, 0)); - return 1; -} -``` - -![](assets/img/3.3.png) - -The first parameter of the `PainterEngine_LoadFontModule` function is the path to the TTF font file, with the relative path being relative to the location of the executable file. The second parameter specifies the character set. By default, Visual Studio uses the GBK character set. If you are using Visual Studio Code, which defaults to UTF-8 encoding, the second parameter should be set to `PX_FONTMODULE_CODEPAGE_GBK`. The last parameter specifies the font size. - -## 4. Drawing Geometric Shapes with PainterEngine - -In addition to drawing text, PainterEngine can directly draw the following types of geometric shapes: - -`px_void PainterEngine_DrawLine(px_int x1, px_int y1, px_int x2, px_int y2, px_int linewidth, px_color color);` -This function is used to draw a line segment. - -- **x1, y1**: The starting coordinates of the line segment. -- **x2, y2**: The ending coordinates of the line segment. -- **linewidth**: The width of the line segment. -- **color**: The color of the line segment. - -```c -#include "PainterEngine.h" - -int main() -{ - PainterEngine_Initialize(800, 480); - - // Set the starting and ending coordinates - px_int x1 = 50; - px_int y1 = 50; - px_int x2 = 200; - px_int y2 = 200; - - // Set the line width and color - px_int linewidth = 5; - px_color color = PX_COLOR(255, 0, 0, 255); // Red - - // Draw the line segment - PainterEngine_DrawLine(x1, y1, x2, y2, linewidth, color); - - return 1; -} -``` - -![](assets/img/3.4.png) - -`px_void PainterEngine_DrawRect(px_int x, px_int y, px_int width, px_int height, px_color color);` -This function is used to draw a rectangle. - -- **x, y**: The coordinates of the top-left corner of the rectangle. -- **width**: The width of the rectangle. -- **height**: The height of the rectangle. -- **color**: The color of the rectangle. - -![](assets/img/3.5.png) - -```c -#include "PainterEngine.h" - -int main() -{ - PainterEngine_Initialize(800, 480); - - // Set the top-left coordinates of the rectangle - px_int x = 100; - px_int y = 100; - - // Set the width and height of the rectangle - px_int width = 150; - px_int height = 100; - - // Set the color of the rectangle - px_color color = PX_COLOR(255, 0, 255, 0); // Green - - // Draw the rectangle - PainterEngine_DrawRect(x, y, width, height, color); - - return 1; -} - -``` - -The `px_void PainterEngine_DrawCircle(px_int x, px_int y, px_int radius, px_int linewidth, px_color color);` function is used to draw a circular ring. - -* `x, y`: The coordinates of the circle's center. -* `radius`: The radius of the circle. -* `linewidth`: The width of the circle's line. -* `color`: The color of the circle. - -```c -#include "PainterEngine.h" - -int main() -{ - PainterEngine_Initialize(800, 480); - - // Set the coordinates of the circle's center - px_int x = 200; - px_int y = 200; - - // Set the radius of the circle - px_int radius = 50; - - // Set the line width of the circle - px_int linewidth = 5; - - // Set the color of the circle - px_color color = PX_COLOR(255, 0, 0, 255); // Blue - - // Draw the circle - PainterEngine_DrawCircle(x, y, radius, linewidth, color); - - return 1; -} -``` - -![](assets/img/3.6.png) - -The `px_void PainterEngine_DrawSolidCircle(px_int x, px_int y, px_int radius, px_color color);` function is used to draw a solid circle. - -* `x, y`: The coordinates of the circle's center. -* `radius`: The radius of the circle. -* `color`: The color of the circle. - -```c -#include "PainterEngine.h" - -int main() -{ - PainterEngine_Initialize(800, 480); - - // Set the coordinates of the circle's center - px_int x = 100; - px_int y = 100; - - // Set the radius of the circle - px_int radius = 50; - - // Set the color of the circle - px_color color = PX_COLOR(255, 255, 0, 255); - - // Draw a solid circle - PainterEngine_DrawSolidCircle(x, y, radius, color); - - return 1; -} -``` - -![](assets/img/3.7.png) - -The `px_void PainterEngine_DrawSector(px_int x, px_int y, px_int inside_radius, px_int outside_radius, px_int start_angle, px_int end_angle, px_color color);` function is used to draw a sector. - -Parameter description: - -* `x, y`: The coordinates of the center of the sector. -* `inside_radius`: The inner radius of the sector. -* `outside_radius`: The outer radius of the sector. -* `start_angle`: The starting angle of the sector (in degrees, supports negative angles). -* `end_angle`: The ending angle of the sector (in degrees, supports negative angles). -* `color`: The color of the sector. - -```c -#include "PainterEngine.h" - -int main() -{ - PainterEngine_Initialize(800, 480); - - // Set the center coordinates of the sector - px_int x = 100; - px_int y = 100; - - // Set the radii of the sector - px_int inside_radius = 50; - px_int outside_radius = 100; - - // Set the start and end angles of the sector - px_int start_angle = 0; - px_int end_angle = 135; - - // Set the color of the sector - px_color color = PX_COLOR(255, 255, 0, 0); // Red - - // Draw the sector - PainterEngine_DrawSector(x, y, inside_radius, - -``` - -![](assets/img/3.8.png) - -The `px_void PainterEngine_DrawPixel(px_int x, px_int y, px_color color);` function is used to draw a single pixel. - -* `x, y`: The coordinates of the pixel. -* `color`: The color of the pixel. - -This function simply draws one pixel, so no example image is provided. - -## 5. Using PainterEngine to Draw Images - -Using PainterEngine to draw images is still straightforward, but before drawing an image, we need to load the image first. - -PainterEngine can directly load images from files. It natively supports four static image formats: PNG, JPG, BMP, and TRAW. To store the loaded image, we need to use a structure called a texture. - -In PainterEngine, textures are described using the `px_texture` structure. Therefore, to load a texture, we need the `PX_LoadTextureFromFile` function, which is a three-parameter function for loading image files. - -1. **First parameter:** The memory pool. This will be explained in more detail in later sections. By default, PainterEngine provides two default memory pools: `mp` and `mp_static`. The first is typically used for elements that require frequent allocation and deallocation, while the latter is used for storing static resources. Since images are generally static resources, you can simply specify `mp_static` here. - -2. **Second parameter:** A pointer to the texture structure. After the image is successfully loaded, this structure will be initialized and used to store image data. - -3. **Third parameter:** The file path of the image. - -After successfully loading the file, we use the `PainterEngine_DrawTexture` function to draw it. This is a four-parameter function: - -* **First parameter:** The pointer to the texture structure we loaded earlier. -* **Second and third parameters:** The x and y coordinates where the image will be drawn. -* **Fourth parameter:** The alignment mode, as previously mentioned. - -Refer to the following code example: - -```c -#include "PainterEngine.h" - -px_texture mytexture; // Texture - -int main() -{ - PainterEngine_Initialize(512, 512); - - // Load the texture from a file - if (!PX_LoadTextureFromFile(mp_static, &mytexture, "assets/demo.png")) - { - // Failed to load texture - return 0; - } - - // Draw the texture - PainterEngine_DrawTexture(&mytexture, 0, 0, PX_ALIGN_LEFTTOP); - - return 1; -} -``` - -![](assets/img/4.1.png) - -## 6. PainterEngine Memory Pool Management Mechanism - -Since PainterEngine is independent of both the operating system and standard libraries, it requires its own memory management system separate from the system and standard libraries. To achieve this, PainterEngine implements an internal memory management system using memory pools as a dynamic memory management system. - -The implementation of the PainterEngine memory pool is equally straightforward. To use memory, you must first prepare a block of available memory space to serve as the memory pool's managed space. For instance, in the code below, we can define a large global array in C language and use the space of this array as the memory pool's allocation space: - -```c -#include "PainterEngine.h" - -unsigned char my_memory_cache[1024 * 1024]; - -int main() -{ - px_memorypool mp; - px_void* myalloc; - - // Create a memory pool - mp = PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache)); - - // Allocate 1024 bytes from the memory pool - myalloc = MP_Malloc(&mp, 1024); - - return 1; -} -``` - -It is important to note that **_the space available for allocation within the memory pool will be slightly smaller than the total space assigned to the memory pool. If the allocated space exceeds the memory pool's capacity, it will result in a crash._** - -```c -#include "PainterEngine.h" -unsigned char my_memory_cache[1024 * 1024]; -int main() -{ - px_memorypool mp; - px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//Create memory pool - myalloc=MP_Malloc(&mp, 1024*1024);//Allocate 1024*1024 bytes in the memory pool, but the actual capacity of the memory pool is less than the capacity allocated to the memory pool, so the memory here is insufficient, and an interrupt will occur here - return 1; -} -``` - -If you want to avoid a crash caused by insufficient memory pool capacity, you can use the following two approaches: - -1. You can set an error callback to handle memory pool errors yourself: - -```c -#include "PainterEngine.h" -unsigned char my_memory_cache[1024 * 1024]; -PX_MEMORYPOOL_ERROR_FUNCTION(my_memory_cache_error) -{ - switch (error) - { - case PX_MEMORYPOOL_ERROR_OUTOFMEMORY: - printf("Memory access error\n");//Memory access error - break; - case PX_MEMORYPOOL_ERROR_INVALID_ACCESS: - printf("Unable to access memory\n");//Unable to access memory - break; - case PX_MEMORYPOOL_ERROR_INVALID_ADDRESS: - printf("Invalid memory address (UAF or double free)\n");//Invalid memory address (UAF or double free) - break; - default: - break; - } -} -int main() -{ - px_memorypool mp; - px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//Create memory pool - MP_ErrorCatch(&mp, my_memory_cache_error,0);//Set error callback - myalloc=MP_Malloc(&mp, 1024*1024);//Allocate 1024*1024 bytes in the memory pool - return 1; -} -``` - -2. Alternatively, you can disable the memory pool's error exception handling. In this case, if the memory pool cannot allocate enough memory, it will simply return `NULL`: - -```c -#include "PainterEngine.h" -unsigned char my_memory_cache[1024 * 1024]; -int main() -{ - px_memorypool mp; - px_void* myalloc; - mp=PX_MemorypoolCreate(my_memory_cache, sizeof(my_memory_cache));//Create memory pool - MP_NoCatchError(&mp, PX_TRUE);//Set the memory pool not to catch errors - myalloc=MP_Malloc(&mp, 1024*1024);//Allocate 1024*1024 bytes in the memory pool, but the memory pool does not catch errors, so it will directly return NULL - return 1; -} -``` - -In PainterEngine, there are two system default memory pools: `mp` and `mp_static`. You can open the `PainterEngine_Application.h` file to find the definitions of these two memory pools. However, the most important part to focus on is the following code: - -```c -#define PX_APPLICATION_NAME "PainterEngine" -#define PX_APPLICATION_MEMORYPOOL_STATIC_SIZE (1024*1024*64) -#define PX_APPLICATION_MEMORYPOOL_DYNAMIC_SIZE (1024*1024*32) -#define PX_APPLICATION_MEMORYPOOL_SPACE_SIZE (1024*1024*16) -``` - -These are the directly related configuration macros for the two memory pools. `PX_APPLICATION_MEMORYPOOL_STATIC_SIZE` specifies the memory allocation size for the `mp_static` memory pool, while `PX_APPLICATION_MEMORYPOOL_DYNAMIC_SIZE` specifies the memory allocation size for the `mp` memory pool. `PX_APPLICATION_MEMORYPOOL_SPACE_SIZE` represents other system resources. At the start of the PainterEngine program, at least the cumulative memory defined by these three macros is occupied. All subsequent memory allocations revolve around these memory pools. If you find that the memory is insufficient when running PainterEngine, you can manually expand the size of the memory pools. Of course, if you want to save memory, you can manually reduce their sizes. - -## 7. Using PainterEngine to Create GUI Buttons - -In this chapter, we will encounter PainterEngine components for the first time. Now, we will use PainterEngine to create a classic GUI component—a button. - -In PainterEngine, all components are described by the `PX_Object` structure, and the creation of a component always returns a pointer of type `PX_Object *`. - -However, in this chapter, we do not need to consider complexities. We only need to create a button. In PainterEngine, the most commonly used button type is `PX_Object_PushButton`. - -```c -#include "PainterEngine.h" -int main() -{ - PX_Object* myButtonObject; - PainterEngine_Initialize(800, 480);//Initialize the Painter Engine with a resolution of 800x480 - PainterEngine_LoadFontModule("assets/font.ttf",PX_FONTMODULE_CODEPAGE_GBK,20);//Load the font module from the specified file with GBK code page and a font size of 20 - myButtonObject=PX_Object_PushButtonCreate(mp,root,300,200,200,80,"I am a button", PainterEngine_GetFontModule());//Create a push button object with the specified parameters - return 1; -} -``` - -![](assets/img/7.1.gif) - -Now, let us take a detailed look at the `PX_Object_PushButtonCreate` function. - -The first parameter is a memory pool. As mentioned earlier, PainterEngine has two default system memory pools. In this case, using either `mp` or `mp_static` works fine. However, considering that the interface design might involve allocating and destroying objects, it is better to use the `mp` memory pool. - -The second parameter, `root`, is the root object in PainterEngine. We will discuss the object management mechanism in PainterEngine later. For now, you just need to understand that providing `root` here means **_creating a button object as a child object of the root object_**. This allows the button to be linked to the system object tree, enabling event response and rendering. - -Next are the `x`, `y`, `width`, and `height` parameters, which specify the position, width, and height of the button. - -The last parameter is a font pointer, which is the TTF font file we loaded earlier. Without it, the button cannot display Chinese characters. Of course, you can choose other fonts to achieve different styles. - -## 8. PainterEngine Object Transmission Mechanism - -In the previous chapter, we briefly introduced the root object, `root`. In this chapter, we will learn about the object management mechanism in PainterEngine. - -As mentioned earlier, all components in PainterEngine are described by the `PX_Object` structure. PainterEngine's objects exist in the form of a tree structure: - -![](assets/img/8.1.png) - -Each `PX_Object` is a node in the tree, and can have its own children (possibly more than one) and its own parent (only one). At the same time, each `PX_Object` has the following four basic functions: - -`Create`: object creation function, or object initialisation function, in PainterEngine it is usually `PX_Object_xxxxxCreate`, where `xxxxx` is the name of the object, such as the `PushButton` in the previous section, `Create` function is usually the object and connects itself to the object tree. - -`Update`: the physical information of the object is basically updated in this function, which generally deals with the physical information of the object, such as position, size, speed, etc. It is commonly used for objects in game design, and is less common in GUI objects, which is designed to be differentiated from the `Render` function, which is the drawing function, because in the game server side, for example, the object doesn't need to be drawn, and drawing is a very important part of game design. because in game servers, for example, objects don't need to be drawn, and drawing is very performance-intensive. - -`Render`: the drawing of the object is basically done in this function, which is used in the drawing function of `PX_Object` to render the image data to the screen, of course, in some cases, the physics information is also done in this function, because the physics information of the object doesn't affect the actual running result of the game, for example, some special effects and particle effects, and most of the GUI components are almost only used by the `Render` function. Most GUI components also use the `Render` function almost exclusively. - -`Free`: the release of the object is basically done in this function, e.g. if a texture is loaded in `Create`, or memory is requested, it should be released in this function. - -The above `Update`, `Render`, and `Free` functions have a pass-through property, which means: - -* If an object node executes `Update`, then all its children will also execute `Update` * If an object node executes `Update`, then all its children will also execute `Update`. -* If an object node executes `Render`, all its children execute `Render`. * If an object node executes `Update`, all its children execute `Update`. -* If an object node does `Free`, then all its children do `Free`, and if the parent is deleted, its children are deleted, and iterates until all children rooted at that node are deleted. - -So, if we created the button in the previous section and attached it to the `root` node, we don't need to manually execute the `Update`, `Render`, and `Free` functions ourselves (they're already written in `PX_Object_PushButton.c`), because the root node, `root`, is automatically updated, rendered, and freed. and released automatically, we just need to take care of `Create`. - -Of course, if you wish to delete the object, you can just call `PX_ObjectDelayDelete` or `PX_ObjectDelete`: - -```c -#include "PainterEngine.h" -int main() -{ - PX_Object* myButtonObject; - PainterEngine_Initialize(800, 480);//Initialize the Painter Engine with a resolution of 800x480 - PainterEngine_LoadFontModule("assets/font.ttf",PX_FONTMODULE_CODEPAGE_GBK,20);//Load the font module from the specified file with GBK code page and a font size of 20 - myButtonObject=PX_Object_PushButtonCreate(mp,root,300,200,200,80,"I am a button", PainterEngine_GetFontModule());//Create a push button object with the specified parameters - PX_ObjectDelayDelete(myButtonObject);//Delete the object - return 1; -} -``` - -The functionality and parameters of these two functions are the same. However, `PX_ObjectDelayDelete` executes the deletion only after the update and rendering are complete, whereas `PX_ObjectDelete` performs the deletion immediately. I recommend using `PX_ObjectDelayDelete` to avoid situations where an object is deleted immediately while other objects still reference its data, which could lead to accessing invalid memory. - -## 9. PainterEngine Messaging Mechanism - -Now, although we have created a button, we cannot yet respond to it. To respond to button events, we need to bind the button control to a message. Please refer to the following code: - -```c -#include "PainterEngine.h" -PX_OBJECT_EVENT_FUNCTION(OnButtonClick)//Define the button click event handler function -{ - PX_Object_PushButtonSetText(pObject,"I was clicked");//Set the button text to "I was clicked" -} -int main() -{ - PX_Object* myButtonObject; - PainterEngine_Initialize(800, 480);//Initialize the Painter Engine with a resolution of 800x480 - PainterEngine_LoadFontModule("assets/font.ttf",PX_FONTMODULE_CODEPAGE_GBK,20);//Load the font module from the specified file with GBK code page and a font size of 20 - myButtonObject=PX_Object_PushButtonCreate(mp,root,300,200,200,80,"I am a button", PainterEngine_GetFontModule());//Create a push button object with the specified parameters - PX_ObjectRegisterEvent(myButtonObject,PX_OBJECT_EVENT_EXECUTE,OnButtonClick,0);//Register the button click event handler function - - return 1; -} -``` - -![](assets/img/9.1.gif) - -Among them, ``PX_OBJECT_EVENT_FUNCTION`` is a macro, because the event response function is a fixed format, so it is highly recommended that you use the macro to declare it, its definition prototype is as follows. - -``c -#define PX_OBJECT_EVENT_FUNCTION(name) px_void name(PX_Object *pObject,PX_Object_Event e,px_void * ptr) -`` - -As you can see, this callback function has three parameters, the first is a pointer to the object of the response time, because the button click was triggered, so this pointer points to the button object; the second parameter is the type of event `e`, which is the type of the triggered event; the last parameter is the pointer passed by the user, which is in the registration of the time response function `PX_ObjectRegisterEvent` is triggered. ObjectRegisterEvent` is called. - -The event types are the following. - -```c -#define PX_OBJECT_EVENT_ANY 0 //Any event -#define PX_OBJECT_EVENT_CURSORMOVE 1 //Mouse move -#define PX_OBJECT_EVENT_CURSORUP 2 //Left mouse button or touch screen release -#define PX_OBJECT_EVENT_CURSORRDOWN 3 //Right mouse button press -#define PX_OBJECT_EVENT_CURSORDOWN 4 //Left mouse button press or touch screen press -#define PX_OBJECT_EVENT_CURSORRUP 5 //Right mouse button release -#define PX_OBJECT_EVENT_CURSOROVER 6 //Mouse enter the range -#define PX_OBJECT_EVENT_CURSOROUT 7 //Mouse leave the range -#define PX_OBJECT_EVENT_CURSORWHEEL 8 //Mouse wheel -#define PX_OBJECT_EVENT_CURSORCLICK 9 //Left mouse button click -#define PX_OBJECT_EVENT_CURSORDRAG 10 //Mouse drag -#define PX_OBJECT_EVENT_STRING 11 //String event (input method input) -#define PX_OBJECT_EVENT_EXECUTE 12 //Execute event, different components have different execution methods -#define PX_OBJECT_EVENT_VALUECHANGED 13 //Value changed event, such as the value of the slider changes, or the value of the text box changes, or the selected item of the list box changes -#define PX_OBJECT_EVENT_DRAGFILE 14 //Drag and drop file -#define PX_OBJECT_EVENT_KEYDOWN 15 //Keyboard press -#define PX_OBJECT_EVENT_KEYUP 16 //Keyboard release -#define PX_OBJECT_EVENT_IMPACT 17 //Collision event -#define PX_OBJECT_EVENT_SCALE 18 //Scaling event -#define PX_OBJECT_EVENT_WINDOWRESIZE 19 //Window size change -#define PX_OBJECT_EVENT_ONFOCUS 20 //Gain focus -#define PX_OBJECT_EVENT_LOSTFOCUS 21 //Lost focus -#define PX_OBJECT_EVENT_CANCEL 22 //Cancel event -#define PX_OBJECT_EVENT_CLOSE 23 //Close event -#define PX_OBJECT_EVENT_CURSORMUP 24 //Middle mouse button release -#define PX_OBJECT_EVENT_CURSORMDOWN 25 //Middle mouse button press -#define PX_OBJECT_EVENT_REQUESTDATA 26 //Request data -#define PX_OBJECT_EVENT_OPEN 27 //Open event -#define PX_OBJECT_EVENT_SAVE 28 //Save event -#define PX_OBJECT_EVENT_TIMEOUT 29 //Timeout event -#define PX_OBJECT_EVENT_DAMAGE 30 //Damage event -``` - -Not all of the above events will be responded to by any component, such as `PX_OBJECT_EVENT_EXECUTE` in the above example, it is the event that will be triggered when a button is clicked, or the event that will be triggered when the enter is pressed in a text box, but some of them, such as scrollbars and progressbars, will not be triggered by this event. This means that some events are exclusive. - -But events like those with `CURSOR` or `KEY` are events that all components attached to the `root` node will receive (but not necessarily respond to). Note that `CURSOR` events, like mouse or touchscreen events, are not triggered only when the mouse or touchscreen is moved into the component's location and range; whenever such an event is delivered to the `root` node, it is passed on to all of its children, layer by layer. If you want to implement something similar to the `only on mouse click' in buttons, you will have to implement the scope judgement yourself. - -You can use the - -```c -px_float PX_Object_Event_GetCursorX(PX_Object_Event e);//Get the x coordinate of the cursor event -px_float PX_Object_Event_GetCursorY(PX_Object_Event e);//Get the y coordinate of the cursor event -px_float PX_Object_Event_GetCursorZ(PX_Object_Event e);//Get the z coordinate of the cursor event, usually used for the mouse middle button scroll -``` - -to get something like ‘where is the mouse now’ in the `cursor` event. - -Let's go back to the source code `OnButtonClick` and do something very simple, change the content of the button text with `PX_Object_PushButtonSetText`. - -Finally we come to the `PX_ObjectRegisterEvent` function, which is used to bind an event to a C function. The first parameter is a pointer to the button component we created earlier, the second parameter is the type of event we want to bind, in this case `PX_OBJECT_EVENT_EXECUTE` is the event triggered when the button is clicked, and the third parameter is the event triggered when the button is clicked, and the third parameter is the event triggered when the button is clicked. Here, `PX_OBJECT_EVENT_EXECUTE` is the event triggered when the button is clicked, and the third argument is the user pointer, which will be passed to the callback function, or if you don't want to use it, you can just fill in `PX_NULL`. - -## 10. Small example of a digital photo album with PainterEngine - -Now, let's kick off the first step in the componentised development of PainterEngine with a small example. In this routine, I'll develop a digital photo album using buttons and picture frames components. You can find the art resources for this article in `documents/logo`. - -```c -#include "PainterEngine.h" -PX_Object* Previous, * Next, * Image; -px_texture my_texture[10];//Array to store the images -px_int index = 0;//Index of the current image -PX_OBJECT_EVENT_FUNCTION(OnButtonPreClick)//Previous button click event handler -{ - index--;//Decrement the index - if(index < 0)//If the index is less than 0 - { - index = 9;//Set the index to 9 (the last index) - } - PX_Object_ImageSetTexture(Image, &my_texture[index]);//Set the image texture -} -PX_OBJECT_EVENT_FUNCTION(OnButtonNextClick)//Next button click event handler -{ - index++;//Increment the index - if(index > 9)//If the index is greater than 9 - { - index = 0;//Set the index to 0 (the first index) - } - PX_Object_ImageSetTexture(Image, &my_texture[index]);//Set the image texture -} -int main() -{ - px_int i; - PainterEngine_Initialize(512, 560);//Initialize the Painter Engine - for(i=0;i<10;i++)//Load the 10 images - { - px_char path[256]; - PX_sprintf1(path,256, "assets/%1.png", PX_STRINGFORMAT_INT(i+1));//Construct the file path - if(!PX_LoadTextureFromFile(mp_static, &my_texture[i],path))//Load the image - { - //Failed to load - printf("Failed to load"); - return 0; - } - } - PainterEngine_LoadFontModule("assets/font.ttf", PX_FONTMODULE_CODEPAGE_GBK, 20);//Load the font module - Image = PX_Object_ImageCreate(mp, root, 0, 0, 512, 512, 0);//Create the image object - Previous= PX_Object_PushButtonCreate(mp, root, 0, 512, 256, 48, "Previous",PainterEngine_GetFontModule());//Create the previous button - Next = PX_Object_PushButtonCreate(mp, root, 256, 512, 256, 48, "Next", PainterEngine_GetFontModule());//Create the next button - PX_ObjectRegisterEvent(Previous, PX_OBJECT_EVENT_EXECUTE, OnButtonPreClick, PX_NULL);//Register the previous button event handler - PX_ObjectRegisterEvent(Next, PX_OBJECT_EVENT_EXECUTE, OnButtonNextClick, PX_NULL);//Register the next button event handler - return 1; -} -``` - -In the above code, `OnButtonPreClick` and `OnButtonNextClick` are the callback functions for the previous and next buttons respectively, and we use the `PX_Object_ImageSetTexture` function to switch the image box. - -In the `main` function, we load the ttf font first, then we create an image component with `PX_Object_ImageCreate`, then we create 2 buttons and bind the event callback function with `PX_ObjectRegisterEvent`. Finally, let's take a look at the result: - -![](assets/img/10.1.gif) - -## 11. More common PainterEngine components - -You can find the built-in components of PainterEngine in the `PainterEngine/kernel` file, all the component names start with `PX_Object_XXXXX`, here, I list some of the commonly used components and the sample code for you: - -* Textbox. - -```c -#include "PainterEngine.h" -PX_OBJECT_EVENT_FUNCTION(PX_Object_EditOnTextChanged)//Text change event handler function -{ - //This will be executed after the text is changed -} -int main() -{ - PX_Object* pObject; - PainterEngine_Initialize(600, 400);//Initialize the Painter Engine - //Create the text box - pObject=PX_Object_EditCreate(mp,root,200,180,200,40,0); - //Register the text box text change event - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_VALUECHANGED, PX_Object_EditOnTextChanged,PX_NULL); - return 0; -} -``` - -![](assets/img/11.1.gif) - -* List Box: - -```c -#include "PainterEngine.h" -PX_OBJECT_RENDER_FUNCTION(PX_Object_OnMyListItemRender)//List item render function -{ - px_float objx,objy,objWidth,objHeight; - PX_Object_ListItem *pItem=PX_Object_GetListItem(pObject);//Get the list item - PX_OBJECT_INHERIT_CODE(pObject,objx, objy, objWidth, objHeight);//Get the object's position and size - //Draw the text - PX_FontModuleDrawText(psurface, 0, (px_int)objx + 3, (px_int)objy + 3, PX_ALIGN_LEFTTOP, (const px_char *)pItem->pdata, PX_COLOR_WHITE); -} -PX_OBJECT_LIST_ITEM_CREATE_FUNCTION(PX_Object_OnMyListItemCreate)//List item create function -{ - //Bind the list item render function - ItemObject->Func_ObjectRender[0]=PX_Object_OnMyListItemRender; - return PX_TRUE; -} -PX_OBJECT_EVENT_FUNCTION(PX_Object_ListOnSelectChanged)//List selection change event handler -{ - //When the selected item changes - return; -} -int main() -{ - PX_Object* pObject; - PainterEngine_Initialize(600, 400);//Initialize the Painter Engine - //Create the list - pObject = PX_Object_ListCreate(mp,root,100,100,400,200,24,PX_Object_OnMyListItemCreate,0); - PX_Object_ListAdd(pObject, "Item1");//Add items to the list - PX_Object_ListAdd(pObject, "Item2"); - PX_Object_ListAdd(pObject, "Item3"); - PX_Object_ListAdd(pObject, "Item4"); - PX_Object_ListAdd(pObject, "Item5"); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, PX_Object_ListOnSelectChanged, 0);//Register the list selection change event handler - return 0; -} -``` - -![](assets/img/11.2.gif) - -* Slider: - -```c -#include "PainterEngine.h" -PX_OBJECT_EVENT_FUNCTION(SliderChanged)//Vertical slider value change event handler -{ - //The code here will be executed when the vertical slider value changes - return; -} -int main() -{ - PX_Object* pObject; - PainterEngine_Initialize(600, 400);//Initialize the Painter Engine - //Create a horizontal slider - PX_Object_SliderBarCreate(mp, root, 200, 50, 200,24,PX_OBJECT_SLIDERBAR_TYPE_HORIZONTAL,PX_OBJECT_SLIDERBAR_STYLE_BOX); - //Create a vertical slider - pObject=PX_Object_SliderBarCreate(mp, root, 200, 100, 24, 200, PX_OBJECT_SLIDERBAR_TYPE_VERTICAL, PX_OBJECT_SLIDERBAR_STYLE_BOX); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_VALUECHANGED, SliderChanged, 0);//Register the vertical slider value change event handler - return 0; -} -``` - -![](assets/img/11.3.gif) - -* Dropdown box: - -```c -#include "PainterEngine.h" - -int main() -{ - PX_Object* pObject; - PainterEngine_Initialize(600, 400); - pObject = PX_Object_SelectBarCreate(mp, root, 200, 150, 200,24,0); - PX_Object_SelectBarAddItem(pObject, "Item1"); - PX_Object_SelectBarAddItem(pObject, "Item2"); - PX_Object_SelectBarAddItem(pObject, "Item3"); - PX_Object_SelectBarAddItem(pObject, "Item4"); - PX_Object_SelectBarAddItem(pObject, "Item5"); - return 0; -} -``` - -![](assets/img/11.4.gif) - -* Oscilloscope: - -```c -#include "PainterEngine.h" -//The data must be in the valid and accessible scope, defined here as global variables -px_double data_x[100]; -px_double data_y[100]; -int main() -{ - PX_Object_OscilloscopeData data; - PX_Object* pObject; - px_int i; - PainterEngine_Initialize(600, 600); - - //Initialize a test data - for (i = 0; i < 100; i++) - { - data_x[i] = i; - data_y[i] = i+PX_randRange(-10,10); - } - - pObject = PX_Object_OscilloscopeCreate(mp, root, 0, 0, 600, 600, 0); - //Set the minimum and maximum values of the horizontal coordinate - PX_Object_OscilloscopeSetHorizontalMin(pObject, 0); - PX_Object_OscilloscopeSetHorizontalMax(pObject, 100); - //Set the minimum and maximum values of the vertical coordinate (left) - PX_Object_OscilloscopeSetLeftVerticalMin(pObject, 0); - PX_Object_OscilloscopeSetLeftVerticalMax(pObject, 100); - //Data type - data.Color=PX_COLOR(255,192,255,128);//Data color - data.ID = 0; - data.linewidth = 3;//Data line width - data.Map = PX_OBJECT_OSCILLOSCOPE_OSCILLOSCOPEDATA_MAP_LEFT;//Data mapped to the left vertical coordinate - data.MapHorizontalArray = data_x;//Data horizontal coordinate - data.MapVerticalArray = data_y;//Data vertical coordinate - data.Size = 100;//Data size - data.Visibled = PX_TRUE;//Data visible - data.Normalization = 1;//Data normalization coefficient is 1 - - //Add data - PX_Object_OscilloscopeAddData(pObject, data); - return 0; -} -``` - -![](assets/img/11.5.gif) - -Because there are too many, I can't list all of them for you, if you want to know exactly how to use a component and what a component does, you can visit PainterEngine's [Component Marketplace](https://market.painterengine.com/), there you can find PainterEngine's Component Marketplace (), where you can find descriptions and sample code for PainterEngine's built-in components and third-party components. - -![](assets/img/11.6.png) - -## 12. Implementing your own PainterEngine components - -PainterEngine encourages a component-based development architecture. That is, whether it is a game, a GUI interaction, or even a program function, we can develop it as a component. - -Component development is kind of like Class in C++, each component has to implement its own `Create`, `Update`, `Render`, `Free` functions. For the above four functions, you can refer to the section [Object Passing Mechanism in Front](#8painterengine - Object Passing Mechanism). - -To demonstrate this, let's implement a ‘controlled drag and rotate image component’, i.e. we can drag the image with the mouse to the position in the interface and rotate it with the middle mouse button. - -In order to implement this feature, let's go through the steps step by step. Firstly, in order to create a component, we need a structure to describe our component. We need to draw a picture, so we need a `px_texture` type. At the same time, we need to rotate the image, so it also has a `rotation` to describe the angle of rotation: - -```c -#include "PainterEngine.h" -typedef struct -{ - px_texture image; - px_int rotation; -}PX_Object_MyObject; - -px_int main() -{ - PainterEngine_Initialize(800, 480); - return PX_TRUE; -} -``` - -After that, we need to define our `Create`, `Update`, `Render`, and `Free` functions, where `Update`, `Render`, and `Free` have corresponding formats, and all of them have a macro to simplify our definition process: - -```c -#define PX_OBJECT_RENDER_FUNCTION(name) px_void name(px_surface *psurface,PX_Object *pObject,px_int idesc,px_dword elapsed) -#define PX_OBJECT_UPDATE_FUNCTION(name) px_void name(PX_Object *pObject,px_int idesc,px_dword elapsed) -#define PX_OBJECT_FREE_FUNCTION(name) px_void name(PX_Object *pObject,px_int idesc) -``` - -So, in the main function, we can define our functions like this: - -```c -#include "PainterEngine.h" -typedef struct -{ - px_texture image; - px_int rotation; -}PX_Object_MyObject; - -PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) -{ -} - -PX_OBJECT_RENDER_FUNCTION(MyObjectRender) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation); -} - -PX_OBJECT_FREE_FUNCTION(MyObjectFree) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image); -} - -px_int main() -{ - PainterEngine_Initialize(800, 480); - return PX_TRUE; -} -``` - -Among them, because we do not need to update some physical information, so `MyObjectUpdate` function we can not write anything, in the `MyObjectRender` we just need to draw the picture out can be, here we first use the `PX_ObjectGetDesc` function to get our defined structure pointer, its first parameter is the structure type, the second parameter is the `pObject` pointer, then we just use the `PX_TextureRenderEx` function to draw the picture out, then we just need to use the `PX_TextureRenderEx` function to draw the picture out. The first parameter is the type of the structure, the second parameter is the `pObject` pointer passed in by the function, and then we just need to use the `PX_TextureRenderEx` function to render the picture. - -As an aside, the `PX_TextureRenderEx` function is used to render a texture on a specified surface and provides extended options for alignment, blending, scaling and rotation. Among them: - - * `psurface`: a pointer to the surface on which to render the texture. - * `resTexture`: pointer to the texture resource to render. - * `x`: the x coordinate at which to draw the texture on the surface. - * `y`: y coordinate of the texture to draw on the surface. - * `refPoint`: reference point for alignment (e.g. centre, top left corner, etc.). - * `blend`: pointer to the blend options structure (can be `NULL` if no blending is needed). - * `scale`: the scale factor of the texture (1.0 means no scale). - * `Angle`: the rotation angle of the texture, in degrees. - -Finally, it's time to write the function that creates the new object, here we need to use the `PX_ObjectCreateEx` function, the `PX_ObjectCreateEx` function is used to create an extended object and initialise its properties and callback functions. Its parameters are described as follows. - -* `mp`: pointer to a memory pool to allocate the memory required by the object. -* `Parent`: pointer to the parent object, or `NULL` if there is no parent. -* `x`: the initial position of the object on the x-axis. -* `y`: the initial position of the object on the y-axis. -* `z`: the initial position of the object on the z-axis, the z-coordinate affects its rendering order. -* `Width`: the width of the object. -* `Height`: the height of the object. -* `Lenght`: the length of the object, for 2D objects, it can be 0. * `type`: the length of the object. -* `type`: the type of the object. -* `Func_ObjectUpdate`: pointer to the object update function. -* `Func_ObjectRender`: pointer to the object render function. -* `Func_ObjectFree`: pointer to the object free function. -* `desc`: pointer to the object description data. You can set it to 0, which will fill the data of this object type with 0 when it is created. -* `size`: the size of the description data, that is, the size of the type of object structure you defined. The object creation function will request a section of memory space in the memory pool and use it to store your object structure. - -After creating an empty object, we use `PX_ObjectGetDescIndex` to get the object structure pointer out of the object, this is a three-parameter function, the first parameter is the type of the object structure, and the second parameter is the type of the `PX_Object *` pointer, because a `PX_Object` can be combined with multiple object structures. We'll describe this combination of structures later in the tutorial, but for now we just need to know that after calling the `PX_ObjectCreateEx` function, the first object structure stored is at index zero. - -After taking the pointer out of the structure, we do a series of initialisations on it, such as loading the image and initialising the rotation angle, and finally we create the object in the `main` function: - -```c -#include "PainterEngine.h" -typedef struct -{ - px_texture image; - px_int rotation; -}PX_Object_MyObject; -PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) -{ -} -PX_OBJECT_RENDER_FUNCTION(MyObjectRender) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation);//Render the image -} -PX_OBJECT_FREE_FUNCTION(MyObjectFree) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image);//Free the image -} -PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) -{ - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject));//Create an empty custom object - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0);//Get the custom object data - pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png"))//Load the image - { - PX_ObjectDelete(pObject);//Delete the object if loading fails - return PX_NULL; - } - return pObject; -} -px_int main() -{ - PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240);//Create a custom object - return PX_TRUE; -} -``` - -Then it runs like this. - -![](assets/img/12.1.png) - -But it doesn't end there, how do we get our component to rotate in response to the middle mouse button? Remember our object passing mechanism from [PushButton](#8painterengine-object passing mechanism)? Now, we also want our component to respond to the middle mouse button, so we register it with a callback function for the `PX_OBJECT_EVENT_CURSORWHEEL` event, with the following code. - -```c -#include "PainterEngine.h" -typedef struct -{ - px_texture image; - px_float rotation; -}PX_Object_MyObject; -PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) -{ -} -PX_OBJECT_RENDER_FUNCTION(MyObjectRender) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderEx(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0,1, pMyObject->rotation);//Render the image -} -PX_OBJECT_FREE_FUNCTION(MyObjectFree) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image);//Free the image -} -PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDescIndex(PX_Object_MyObject,pObject,0); - if(PX_ObjectIsCursorInRegion(pObject,e))//Check if the object's region contains the cursor position, e is the event - pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; -} -PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) -{ - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject));//Create a custom object - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0);//Get the custom object data - pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png"))//Load the image - { - PX_ObjectDelete(pObject);//Delete the object if the load fails - return PX_NULL; - } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0);//Register the cursor wheel event - return pObject; -} -px_int main() -{ - PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240);//Create a custom object - return PX_TRUE; -} -``` - -The results are as follows. - -![](assets/img/12.2.gif) - -If you think the quality of the rotated image is not good, there are a lot of jaggies, it's because `PX_TextureRenderEx` rotates the image by sampling the original image directly. If you want a high quality rotation, you can replace the original function with the `PX_TextureRenderRotation` function. - -```c -PX_OBJECT_RENDER_FUNCTION(MyObjectRender) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, pMyObject->rotation);//Render the image -} -``` - -![](assets/img/12.3.gif) - -So, how do we achieve the drag effect? To achieve the drag effect, we need to add `float` type variables `x`, `y`, used to record the position of the image when the mouse selects it, at the same time, we add a `bool` type variable `bselect`, which indicates whether the current icon is selected or not. When the mouse clicks on our icon, we can listen to the `PX_OBJECT_EVENT_CURSORDRAG` event, which is generated when the mouse is dragged on the screen, and we move our component by the offset of the coordinates. Finally, no matter the mouse non-dragging movement or the left mouse button lift, will be cancelled our component selection state, in the corresponding handler function to cancel the selection state can be. - -```c -#include "PainterEngine.h" -typedef struct -{ - px_float last_cursorx, last_cursory; - px_bool bselect; - px_texture image; - px_float rotation; -}PX_Object_MyObject; -PX_OBJECT_UPDATE_FUNCTION(MyObjectUpdate) -{ -} -PX_OBJECT_RENDER_FUNCTION(MyObjectRender) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureRenderRotation(psurface, &pMyObject->image, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER,0, (px_int)pMyObject->rotation);//Render the image -} -PX_OBJECT_FREE_FUNCTION(MyObjectFree) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDesc(PX_Object_MyObject,pObject); - PX_TextureFree(&pMyObject->image);//Free the image -} -PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorWheel) -{ - PX_Object_MyObject *pMyObject=PX_ObjectGetDescIndex(PX_Object_MyObject,pObject,0); - if(PX_ObjectIsCursorInRegionAlign(pObject,e,PX_ALIGN_CENTER))//Check if the cursor is over the object, e is the event - pMyObject->rotation += (px_float)PX_Object_Event_GetCursorZ(e)/10; -} -PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDown) -{ - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_CENTER))//Check if the cursor is over the object, e is the event - { - pMyObject->bselect = PX_TRUE; - pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); - pMyObject->last_cursory = PX_Object_Event_GetCursorY(e); - } -} -PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorRelease) -{ - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - pMyObject->bselect = PX_FALSE; -} -PX_OBJECT_EVENT_FUNCTION(MyObjectOnCursorDrag) -{ - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject, 0); - if (pMyObject->bselect) - { - pObject->x += PX_Object_Event_GetCursorX(e) - pMyObject->last_cursorx; - pObject->y += PX_Object_Event_GetCursorY(e) - pMyObject->last_cursory; - } - pMyObject->last_cursorx = PX_Object_Event_GetCursorX(e); - pMyObject->last_cursory = PX_Object_Event_GetCursorY(e); -} -PX_Object* PX_Object_MyObjectCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) -{ - PX_Object *pObject=PX_ObjectCreateEx(mp,parent,x,y,0,128,128,0,0, MyObjectUpdate, MyObjectRender, MyObjectFree,0,sizeof(PX_Object_MyObject));//Create a custom object - PX_Object_MyObject* pMyObject = PX_ObjectGetDescIndex(PX_Object_MyObject, pObject,0);//Get the custom object data - pMyObject->rotation = 0; - if(!PX_LoadTextureFromFile(mp,&pMyObject->image, "assets/test.png"))//Load the image - { - PX_ObjectDelete(pObject);//If failed, delete the object - return PX_NULL; - } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORWHEEL,MyObjectOnCursorWheel,0);//Register the mouse wheel event - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDRAG,MyObjectOnCursorDrag,0);//Register the mouse drag event - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,MyObjectOnCursorDown,0);//Register the mouse down event - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORUP,MyObjectOnCursorRelease,0);//Register the mouse release event - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, MyObjectOnCursorRelease, 0);//Register the mouse move event - return pObject; -} -px_int main() -{ - PainterEngine_Initialize(800, 480); - PX_Object_MyObjectCreate(mp,root,400,240);//Create a custom object - return PX_TRUE; -} -``` - -![](assets/img/12.4.gif) - -Of course, you can call `PX_Object_MyObjectCreate` multiple times to create multiple component objects, which all function the same way: - -![](assets/img/12.5.gif) - -## 13. Combined Component Design - -PainterEngine's components allow to have multiple component types at the same time, for example, when we combine a PictureBox component with a Button, we get a Combo Component PictureButton. - -Refer to the following code: - -```c -#include "PainterEngine.h" -px_texture tex1, tex2; -PX_Object* image; -PX_OBJECT_EVENT_FUNCTION(ButtonEvent) -{ - PX_Object_Image *pImage=PX_Object_GetImage(pObject);//Get the Image object data - PX_Object_Button *pButton=PX_Object_GetButton(pObject);//Get the Button object data - if (pImage->pTexture==&tex1) - { - PX_Object_ImageSetTexture(pObject,&tex2); - } - else - { - PX_Object_ImageSetTexture(pObject,&tex1); - } -} -px_int main() -{ - PainterEngine_Initialize(800, 480); - if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0;//Load texture 1 - if(!PX_LoadTextureFromFile(mp_static,&tex2,"assets/2.png")) return 0;//Load texture 2 - image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1);//Create an Image object - PX_Object_ButtonAttachObject(image, 1, PX_COLOR(64, 255, 255, 255), PX_COLOR(96, 255, 255, 255));//Attach a Button object type to the Image object - PX_ObjectRegisterEvent(image,PX_OBJECT_EVENT_EXECUTE,ButtonEvent,0);//This actually registers the Button object's event - return 1; -} -``` - -We create an Image image box type, and then put a Button object type on it, so we get a picture button: - -![](assets/img/13.1.gif) - -So, how do we design our own composable objects? Going back to our chapter 12, we will now design the ‘Drag and Drop’ functionality as a composable component. - -First of all, we still define a component object structure, for drag and drop functionality, we need the x, y coordinates of the mouse press, and a bool type to record whether it is selected or not, then we need to register the `CURSOR` events, which we have already written about in the previous section, and lastly, we create an object structure with the `PX_ObjectCreateDesc` function. to create an object structure and attach it to our object. - -``PX_ObjectCreateDesc`` is an object structure creation function, which is defined with the following prototype: - -```c -px_void* PX_ObjectCreateDesc(PX_Object* pObject, px_int idesc, px_int type, Function_ObjectUpdate Func_ObjectUpdate, Function_ObjectRender Func_ObjectRender, Function_ObjectFree Func_ObjectFree, px_void* pDesc, px_int descSize) -``` - -The first parameter is the object to be Attached, the second parameter is the index of the object to be Attached to. Remember the object data index we mentioned before, using `PX_ObjectCreateEx` defaults to index 0, so if we want to attach to an object, we should choose 1, of course if 1 is also occupied, it is 2, and so on. The third parameter is the object type, when we use `PX_ObjectGetDescByType`, we can get the corresponding pointer from the object type, and then the familiar `Update`, `Render`, `Free` triple, and the last parameter gives the structure description and structure size. See the following code. - -```c -#include "PainterEngine.h" -typedef struct -{ - px_float last_cursorx, last_cursory; - px_bool bselect; -}PX_Object_Drag; -PX_OBJECT_EVENT_FUNCTION(PX_Object_DragOnCursorDown) -{ - PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_LEFTTOP)) - { - pPX_Object_Drag->bselect = PX_TRUE; - pPX_Object_Drag->last_cursorx = PX_Object_Event_GetCursorX(e); - pPX_Object_Drag->last_cursory = PX_Object_Event_GetCursorY(e); - } -} -PX_OBJECT_EVENT_FUNCTION(PX_Object_DragOnCursorRelease) -{ - PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); - pPX_Object_Drag->bselect = PX_FALSE; -} -PX_OBJECT_EVENT_FUNCTION(PX_Object_DragOnCursorDrag) -{ - PX_Object_Drag* pPX_Object_Drag = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_DRAG); - if (pPX_Object_Drag->bselect) - { - pObject->x += PX_Object_Event_GetCursorX(e) - pPX_Object_Drag->last_cursorx; - pObject->y += PX_Object_Event_GetCursorY(e) - pPX_Object_Drag->last_cursory; - } - pPX_Object_Drag->last_cursorx = PX_Object_Event_GetCursorX(e); - pPX_Object_Drag->last_cursory = PX_Object_Event_GetCursorY(e); -} -PX_Object* PX_Object_DragAttachObject(PX_Object* pObject, px_int attachIndex) -{ - PX_Object_Drag* pDesc; - PX_ASSERTIF(pObject == PX_NULL); - PX_ASSERTIF(attachIndex < 0 || attachIndex >= PX_COUNTOF(pObject->pObjectDesc)); - PX_ASSERTIF(pObject->pObjectDesc[attachIndex] != PX_NULL); - pDesc = (PX_Object_Drag*)PX_ObjectCreateDesc(pObject, attachIndex, PX_OBJECT_TYPE_DRAG, 0, 0, 0, 0, sizeof(PX_Object_Drag)); - PX_ASSERTIF(pDesc == PX_NULL); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_DragOnCursorDrag, 0); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_DragOnCursorDown, 0); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_DragOnCursorRelease, 0); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_DragOnCursorRelease, 0); - return pObject; -} -px_texture tex1; -PX_Object* image; -px_int main() -{ - PainterEngine_Initialize(800, 480); - if(!PX_LoadTextureFromFile(mp_static,&tex1,"assets/1.png")) return 0;//Load texture 1 - image=PX_Object_ImageCreate(mp,root,300,140,200,200,&tex1);//Create an Image object - PX_Object_DragAttachObject(image, 1);//Attach a Drag object type to the Image object - return 1; -} -``` - -The running result is as follows: - -![](assets/img/13.2.gif) - -## 14. Particle Systems - -PainterEngine provides an implementation of a particle system, and the following is a sample particle system application: - -```c -#include "PainterEngine.h" - -PX_OBJECT_EVENT_FUNCTION(MyClick) -{ - px_float x = PX_Object_Event_GetCursorX(e); - px_float y = PX_Object_Event_GetCursorY(e); - - PX_Object_Explosion05Create(mp, root, x, y, 10, 20); -} - -px_int main() -{ - PainterEngine_Initialize(800, 600); - PX_ObjectRegisterEvent(root, PX_OBJECT_EVENT_CURSORDOWN, MyClick, PX_NULL); - return 0; -} -``` - -![](assets/img/14.1.gif) - -This is an implementation of a particle system wrapped in components, while the other provides more detailed configuration of the particle system parameters. - -```c -#include "PainterEngine.h" -px_texture texture; - -int main() -{ - PX_Object* pObject; - PX_ParticalLauncher_InitializeInfo ParticalInfo; - PainterEngine_Initialize(600, 400); - PX_LoadTextureFromFile(mp_static, &texture, "assets/star.traw"); - - PX_ParticalLauncherInitializeDefaultInfo(&ParticalInfo); - ParticalInfo.deviation_rangAngle = 360; - ParticalInfo.deviation_velocity_max = 50; - ParticalInfo.deviation_velocity_min = -50; - ParticalInfo.direction = PX_POINT(0, -1, 0); - ParticalInfo.generateDuration = 100; - ParticalInfo.launchCount = -1; - ParticalInfo.maxCount = 100; - ParticalInfo.position = PX_POINT(0, 0, 0); - ParticalInfo.tex = &texture; - ParticalInfo.velocity = 100; - ParticalInfo.alive = 5000; - ParticalInfo.rotation = 180; - ParticalInfo.deviation_rotation = 180; - ParticalInfo.atomsize = 0.7f; - ParticalInfo.deviation_atomsize_max = 0.7f; - ParticalInfo.deviation_atomsize_min = -0.5f; - ParticalInfo.alpha = 0.8f; - ParticalInfo.deviation_alpha = 0.3f; - ParticalInfo.deviation_hdrR = 0.5f; - ParticalInfo.deviation_hdrG = 0.5f; - ParticalInfo.deviation_hdrB = 0.5f; - ParticalInfo.alphaincrease = -0.2f; - - pObject=PX_Object_ParticalCreate(mp,root,300,200,ParticalInfo); - return 0; -} -``` - -Below is an explanation of the main functions and processes of this code: - -1. `#include ‘PainterEngine.h’`: Introduces the PainterEngine header file in order to use the engine's functionality. - -2. `px_texture texture;`: declares a variable named `texture` to store texture information. - -3. `int main()`: entry point for the main function. - -4. `PX_Object* pObject;`: Declare a pointer to a `PX_Object` type named `pObject`, which will be used to create the particle system object. - -5. `PX_ParticalLauncher_InitializeInfo ParticalInfo;`: declares a struct variable named `ParticalInfo` that will be used to configure the initialisation information for the particle launcher. - -6. `PainterEngine_Initialize(600, 400);`: Initialise the PainterEngine and set the window width to 600 pixels and height to 400 pixels. - -7. `PX_LoadTextureFromFile(mp_static, &texture, ‘assets/star.draw’);`: load texture from file, store texture data in `texture` variable. Texture file path is `assets/star.traw`. - -8. `PX_ParticalLauncherInitializeDefaultInfo(&ParticalInfo);`: Initialise the `ParticalInfo` structure, set some default particle emitter properties. - -9. Specific configurations are made for each property of `ParticalInfo`, including particle position, speed, lifetime, size, rotation, etc. These properties determine the appearance and rotation of the particles. These properties determine the appearance and behaviour of the particles. - -10. `pObject=PX_Object_ParticalCreate(mp,root,300,200,ParticalInfo);`: Creates a particle system object with the configured `ParticalInfo` and stores it in `pObject`. This particle system object will emit particles at position (300, 200) in the window. - -The `PX_ParticalLauncher_InitializeInfo` is used to configure the initialisation information of the particle launcher, i.e. when creating a particle system, this structure can be populated to specify the properties and behaviour of the particle system. The following is a description of each member of this structure: - -1. `px_void *userptr;`: a pointer to any type of data that can be used to store user-defined data. - -2. `px_texture *tex;`: a pointer to texture data that can be used to specify a texture image for the particle. - -3. `px_point position;`: a point containing x, y, and z coordinates representing the initial position of the particle system. - -4. `px_float deviation_position_distanceRange;`: a floating point number that specifies the range of the particle's position offset. - -5. `px_point direction;`: a point containing x, y, and z coordinates indicating the initial direction of motion of the particle. - -6. `px_float deviation_rangAngle;`: a floating point number that specifies the range (angle) of the particle's initial motion direction offset. - -7. `px_float velocity;`: a floating point number specifying the initial velocity of the particle. - -8. `px_float deviation_velocity_max;`: a floating point number indicating the maximum deviation value of the particle velocity. - -9. `px_float deviation_velocity_min;`: a floating point number representing the minimum offset value of the particle velocity. - -10. `px_float atomsize;`: a floating point number representing the initial size of the particle. - -11. `px_float deviation_atomsize_max;`: a floating point number representing the maximum offset value for the particle size. - -12. `px_float deviation_atomsize_min;`: a floating point number representing the minimum offset value for the particle size. - -13. `px_float rotation;`: a floating point number representing the initial rotation angle of the particle. - -14. `px_float deviation_rotation;`: a floating point number representing the offset range of the particle rotation angle. - -15. `px_float alpha;`: a floating point number indicating the initial transparency of the particle. - -16. `px_float deviation_alpha;`: a float number representing the offset range of the transparency of the particle. - -17. `px_float hdrR;`: a floating point number representing the initial red channel value of the particle. - -18. `px_float deviation_hdrR;`: a float representing the offset range of the particle's red channel value. - -19. `px_float hdrG;`: a float representing the initial green channel value of the particle. - -20. `px_float deviation_hdrG;`: a float representing the offset range of the particle's green channel value. - -21. `px_float hdrB;`: a float representing the initial blue channel value of the particle. - -22. `px_float deviation_hdrB;`: a floating point number representing the offset range of the particle's blue channel value. - -23. `px_float sizeincrease;`: a floating point number indicating the rate of increase of the particle size. - -24. `px_float alphaincrease;`: a floating point number indicating the rate of increase of the transparency of the particle. - -25. `px_point a;`: a point containing x, y, and z coordinates for custom attributes. - -26. `px_float ak;`: a floating point number, used for custom attributes. - -27. `px_int alive;`: an integer indicating how long the particle will be alive (in milliseconds). - -28. `px_int generateDuration;`: an integer representing the generation period (in milliseconds) of the particle emitter. - -29. `px_int maxCount;`: an integer indicating the maximum number of particles in the particle system. - -30. `px_int launchCount;`: an integer indicating the number of launches of the particle system. - -31. `PX_ParticalLauncher_CreateAtom Create_func;`: a function pointer specifying a custom particle creation function. - -32. `PX_ParticalLauncher_UpdateAtom Update_func;`: a function pointer to specify a custom particle update function. - -This structure allows you to flexibly configure various properties of the particle system to meet the needs of different scenes and effects. By adjusting these properties, you can control the behaviour of the particles in terms of their appearance, trajectory, lifecycle, and so on. - -![](assets/img/14.2.gif) - -## 15.Playing Music with PainterEngine - -PainterEngine has built-in native support for music in wav and mp3 formats, and the code to play music with PainterEngine is very simple: - -```c -#include "PainterEngine.h" -PX_SoundData sounddata;//Define the audio format -int main() -{ - PX_Object* pObject; - PainterEngine_Initialize(600, 400); - PainterEngine_InitializeAudio();//Initialize the mixer and audio device - if (!PX_LoadSoundFromFile(mp_static, &sounddata, "assets/bliss.wav"))return PX_FALSE;//Load the music, supports wav and mp3 formats - PX_SoundPlayAdd(soundplay, PX_SoundCreate(&sounddata, PX_TRUE));//Play the music - pObject = PX_Object_SoundViewCreate(mp,root,0,0,600,400,soundplay);//Audio spectrum visualization component, optional - return 0; -} -``` - -![](assets/img/15.1.gif) - -The `PX_LoadSoundFromFile` function loads music from a file and decodes it into `sounddata` type. The `PX_SoundCreate` function creates a playback instance with `sounddata`, the second parameter indicates whether the instance is looped or not, and the `PX_SoundPlayAdd` function feeds the playback instance into the mixer to complete the music playback. - -## 16. PainterEngine live2D animation system - -PainterEngine has a built-in class live2D animation system which can load live2d animation, the reference code is as below: - -```c -#include "PainterEngine.h" -PX_LiveFramework liveframework; -PX_Object* pObject; -PX_OBJECT_EVENT_FUNCTION(onClick) -{ - PX_Object_Live2DPlayAnimationRandom(pObject); -} -int main() -{ - PX_IO_Data iodata; - PainterEngine_Initialize(600, 600); - //Load the model data - iodata = PX_LoadFileToIOData("assets/release.live"); - if (iodata.size == 0)return PX_FALSE; - PX_LiveFrameworkImport(mp_static, &liveframework, iodata.buffer, iodata.size); - PX_FreeIOData(&iodata); - //Create a Live2D object - pObject = PX_Object_Live2DCreate(mp,root,300,300,&liveframework); - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, onClick, PX_NULL); - return 0; -} -``` - -The following are descriptions of functions related to the Live2D model viewer: - -`PX_Object_Live2DCreate` - -```c -PX_Object* PX_Object_Live2DCreate(px_memorypool* mp, PX_Object* Parent, px_int x, px_int y, PX_LiveFramework *pLiveFramework); -``` - -- **Description**: Creates a Live2D model viewer object for displaying and interacting with Live2D models in a graphical interface. -- **Parameters**: - - `mp`: Pointer to the memory pool used for memory allocation. - - `Parent`: Parent object, under which the Live2D model viewer object will be created as a child. - - `x`, `y`: Position coordinates of the Live2D model viewer object. - - `pLiveFramework`: Pointer to the Live2D framework, which includes model data, textures, and other related information. -- **Return Value**: A pointer to the created Live2D model viewer object. - -`PX_Object_Live2DPlayAnimation` - -```c -px_void PX_Object_Live2DPlayAnimation(PX_Object *pObject, px_char *name); -``` - -- **Description**: Plays a Live2D model animation by its specified name. -- **Parameters**: - - `pObject`: Pointer to the Live2D model viewer object. - - `name`: The name of the animation to play. -- **Return Value**: None. - -`PX_Object_Live2DPlayAnimationRandom` - -```c -px_void PX_Object_Live2DPlayAnimationRandom(PX_Object* pObject); -``` - -- **Description**: Plays a random animation of the Live2D model. -- **Parameters**: - - `pObject`: Pointer to the Live2D model viewer object. -- **Return Value**: None. - -`PX_Object_Live2DPlayAnimationIndex` - -```c -px_void PX_Object_Live2DPlayAnimationIndex(PX_Object* pObject, px_int index); -``` - -- **Description**: Plays a Live2D model animation at the specified index. -- **Parameters**: - - `pObject`: Pointer to the Live2D model viewer object. - - `index`: The index of the animation to play. -- **Return Value**: None. - -These functions are used to create, configure, and manage Live2D model viewer objects for displaying and interacting with Live2D models in a graphical user interface. You can use these functions to play animations of the Live2D model, including by name, randomly, or by a specified index. - -![](assets/img/16.1.gif) - -## 17. PainterEngine Script Engine - -PainterEngine includes a platform-independent script engine system with integrated features for compiling, running, and debugging. It allows you to easily implement parallel scheduling functionality on top of the scripting system. The design of PainterEngine Script closely aligns with C language while extending and simplifying certain types. - -For example, the scripting language supports four types: `int`, `float`, `string`, and `memory`. The `int` type is a 32-bit signed integer, and `float` is a floating-point type, both consistent with C language types. The `string` type is similar to C++'s `string`, allowing string concatenation using the `+` operator and using `strlen` to get the string length. The `memory` type is a binary data storage type that also supports concatenation using the `+` operator. - -To call C language functions in the script, you should use the `PX_VM_HOST_FUNCTION` macro to define and declare them. Like component callback functions, the `PX_VM_HOST_FUNCTION` macro is defined as follows: - -```c -#define PX_VM_HOST_FUNCTION(name) px_bool name(PX_VM *Ins, px_void *userptr) -``` - -Below is a simple script example to demonstrate how to use the PainterEngine script engine: - -```c -const px_char shellcode[] = "\ -#name \"main\"\n\ -host void print(string s);\n\ -host void sleep(int ms);\n\ -int main()\n\ -{\n\ - int i,j;\n\ - for(i = 1; i <= 9; i++)\n\ - {\n\ - for(j = 1; j <= i; j++)\n\ - {\n\ - print(string(i) + \" * \" + string(j) + \" = \" + string(i * j));\n\ - sleep(1000);\n\ - }\n\ - }\n\ -}"; - -PX_VM_HOST_FUNCTION(host_print) -{ - if (PX_VM_HOSTPARAM(Ins, 0).type == PX_VARIABLE_TYPE_STRING) - { - PainterEngine_Print(PX_VM_HOSTPARAM(Ins, 0)._string.buffer); - } - return PX_TRUE; -} - -PX_VM_HOST_FUNCTION(host_sleep) -{ - if (PX_VM_HOSTPARAM(Ins, 0).type == PX_VARIABLE_TYPE_INT) - { - PX_VM_Sleep(Ins,PX_VM_HOSTPARAM(Ins, 0)._int); - } - return PX_TRUE; -} -``` - -In this example, the `shellcode` array contains a program to output the multiplication table, which calls two `host` functions (functions provided to the script by C are called "host calls"). The `host` functions are `print` and `sleep`. Below, two `host` functions are defined. The `PX_VM_HOSTPARAM` macro is used to retrieve parameters passed from the script. Here, you must check whether the parameter types match the expected rules. The `host_print` function outputs a string in PainterEngine, and the `host_sleep` function delays execution for a specified time. - -PainterEngine Script is a compiled script. You need to compile the above code into binary form and then pass it to the virtual machine for execution. The following code demonstrates this process: - -```c -PX_VM vm; -PX_OBJECT_UPDATE_FUNCTION(VMUpdate) -{ - PX_VMRun(&vm, 0xffff, elapsed); // Run the virtual machine -} - -px_int main() -{ - PX_Compiler compiler; - px_memory bin; - PainterEngine_Initialize(800, 600); - PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler); // Initialize the compiler - PX_CompilerAddSource(&compiler, shellcode); // Add the script code to the compiler - PX_MemoryInitialize(mp, &bin); // Initialize memory to store compiled binary - if (!PX_CompilerCompile(&compiler, &bin, 0, "main")) - { - // Compilation failed - return 0; - } - PX_CompilerFree(&compiler); // Release the compiler resources - PX_VMInitialize(&vm, mp, bin.buffer, bin.usedsize); // Initialize the virtual machine - PX_VMRegisterHostFunction(&vm, "print", host_print, 0); // Register the `print` host function - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep, 0); // Register the `sleep` host function - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0); // Begin executing the `main` script function - PX_ObjectSetUpdateFunction(root, VMUpdate, 0); // Set the update function for the root node - - return 0; -} -``` - -First, we compile the script using the `PX_Compiler`. Then, we register the host calls. The `PX_VMBeginThreadFunction` function allows C code to call script functions. Here, it invokes the `main` function to start executing the script. Finally, the `Update` function is bound to the root node to continuously update the virtual machine and execute the script. - -Now, observe the result of the execution. - -![](assets/img/17.1.gif) - -If we want to debug the script, we can also create a symbol mapping table during compilation. This allows us to directly use `PX_Object_DebuggerMap` to debug the script. - -```c -px_int main() -{ - PX_Compiler compiler; - px_memory bin; - PainterEngine_Initialize(800, 480); - PX_VMDebuggerMapInitialize(mp,&debugmap); - PainterEngine_SetBackgroundColor(PX_COLOR_BLACK); - PX_CompilerInitialize(mp, &compiler);//Initialize the compiler - PX_CompilerAddSource(&compiler, shellcode);//Add the code to the compiler - PX_MemoryInitialize(mp, &bin);//Initialize the memory to store the compiled result - if (!PX_CompilerCompile(&compiler, &bin, &debugmap, "main")) - { - //Compilation failed - return 0; - } - PX_CompilerFree(&compiler);//Release the compiler - PX_VMInitialize(&vm,mp,bin.buffer,bin.usedsize);//Initialize the virtual machine - PX_VMRegisterHostFunction(&vm, "print", host_print,0);//Register the host function print - PX_VMRegisterHostFunction(&vm, "sleep", host_sleep,0);//Register the host function sleep - PX_VMBeginThreadFunction(&vm, 0, "main", PX_NULL, 0);//Start running the virtual machine function - PX_Object *pDbgObject = PX_Object_AsmDebuggerCreate(mp, root, 0, 0, 800, 480, 0); - pDbgObject->Visible = PX_TRUE; - PX_Object_AsmDebuggerAttach(pDbgObject, &debugmap, &vm); - return 0; -} -``` - -![](assets/img/17.2.png) - -## 18. Using PainterEngine to Quickly Create a Simple Game - -To better demonstrate the usage of PainterEngine, I will create a simple game using it. You can find all the source code and original assets related to this game under `documents/demo/game`. Thanks to PainterEngine's cross-platform portability, you can also directly play this online game on [PainterEngine Online Application APP - Whack-a-Mole](https://www.painterengine.com/main/app/documentgame/). - -In this game, I will show you how to use PainterEngine's component-based development model to quickly create an app game. - -Let us start with the first step of game creation by preparing the required art assets and materials: - -![](assets/img/18.1.png) - -This is a simple game background asset. Now we can begin creating our `main.c` source code file. In PainterEngine, we input the following code: - -```c -px_int main() -{ - px_int i; - PainterEngine_Initialize(800, 480); - PX_FontModuleInitialize(mp_static,&score_fm); - PX_FontModuleSetCodepage(&score_fm, PX_FONTMODULE_CODEPAGE_GBK); - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/rasing.png", "fox_rasing")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/taunt.png", "fox_taunt")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/escape.png", "fox_escape")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/beat.png", "fox_beat")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/hurt.png", "fox_hurt")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/mask.png", "fox_mask")) return 0; - if (!PX_LoadTextureToResource(PainterEngine_GetResourceLibrary(), "assets/background.png", "background")) return 0; - if (!PX_LoadAnimationToResource(PainterEngine_GetResourceLibrary(), "assets/song.2dx", "song"))return 0; - PainterEngine_SetBackgroundTexture(PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "background")); - for (i = 0; i <= 9; i++) - { - px_texture tex; - px_char path[64]; - PX_sprintf1(path,64, "assets/%1.png", PX_STRINGFORMAT_INT(i)); - if (PX_LoadTextureFromFile(mp,&tex,path)) - { - PX_FontModuleAddNewTextureCharacter(&score_fm, '0' + i, &tex); - } - PX_TextureFree(&tex); - } -} -``` - -### Initializing the Window and Font Module - -At the beginning of the code, we initialize an 800x480 window. Then, we initialize the font module and use the `PX_FontModuleSetCodepage` function to set it to the GBK character set. Following that, we load resources into PainterEngine's resource manager. - -### Loading Resources and Setting the Background - -PainterEngine includes a built-in resource manager that is initialized during the `PainterEngine_Initialize` function. It uses the `mp_static` memory pool. The resource manager functions like a database, loading assets such as images, audio, and scripts into memory and mapping them to a `key`. Accessing these resources later is done via their `key`. The resource mapping is optimized, so you do not need to worry about performance loss caused by lookup operations. - -The `PX_LoadTextureToResource` function is used to load a resource from the file system into the resource manager. The first parameter is a pointer to the resource manager instance. PainterEngine automatically creates this instance during initialization, and you can access it directly using `PainterEngine_GetResourceLibrary`. The second parameter is the file path of the resource to be loaded, and the third parameter is the `key` you want to map to the resource. - -In the next step of the code, we use `PX_LoadTextureToResource` to load several images and `PX_LoadAnimationToResource` to load a 2dx animation (refer to the app market for detailed explanations on 2DX animations). Finally, instead of using TTF font files in the game, we loop through `0.png` to `9.png` and insert these textures as images into the font module. This way, when the font module draws numbers, it actually displays the corresponding images. - -We also call `PainterEngine_SetBackgroundTexture` to set the PainterEngine interface background. Note the `PX_ResourceLibraryGetTexture` function, which is used to retrieve the texture's data structure pointer using a `key` from the resource manager. After completing these steps, you will see a screen like this: - -![](assets/img/18.2.png) - -### Designing Game Objects - -#### Start Game Button - -The first game object we design is the "Start Game" button. This part does not require much code, as PainterEngine has built-in functionality for buttons: - -```c -startgame = PX_Object_PushButtonCreate(mp, root, 300, 200, 200, 90, "Start Game", 0); -startgame->Visible = PX_TRUE; -PX_Object_PushButtonSetBackgroundColor(startgame, PX_COLOR(96, 255, 255, 255)); -PX_Object_PushButtonSetPushColor(startgame, PX_COLOR(224, 255, 255, 255)); -PX_Object_PushButtonSetCursorColor(startgame, PX_COLOR(168, 255, 255, 255)); -``` - -We use a series of functions to change the button's background color, mouse hover color, and mouse pressed color. As a result, you will see this on the screen: - -![](assets/img/18.3.png) - -#### Game Mole Object - -Next, we create the mole object for the game, which is the most complex object in the game. Below is the detailed code with step-by-step explanations: - -```c -typedef enum -{ - PX_OBJECT_FOX_STATE_IDLE,//The fox is still in the den - PX_OBJECT_FOX_STATE_RASING,//The fox is rising up - PX_OBJECT_FOX_STATE_TAUNT,//The fox is taunting - PX_OBJECT_FOX_STATE_ESCAPE,//The fox is escaping - PX_OBJECT_FOX_STATE_BEAT,//The fox is being beaten - PX_OBJECT_FOX_STATE_HURT,//The fox is fleeing after being hurt -}PX_OBJECT_FOX_STATE; - -typedef struct -{ - PX_OBJECT_FOX_STATE state;//Fox state - px_dword elapsed;//Duration of the state - px_float texture_render_offset;//Texture rendering offset - px_dword gen_rand_time;//Random generation time - px_float rasing_down_speed;//Rising speed - px_texture render_target;//Render target - px_texture* pcurrent_display_texture;//Current displayed texture - px_texture* ptexture_mask;//Mask -}PX_Object_Fox; - -PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) -{ - PX_Object_Fox* pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - switch (pfox->state) - { - case PX_OBJECT_FOX_STATE_IDLE: - { - if (pfox->gen_rand_time ==0) - { - pfox->gen_rand_time = PX_rand() % 3000 + 1000;//Time the fox spends in the den, when the time is up it will rise up - } - else - { - if (pfox->gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; - pfox->elapsed = 0; - pfox->gen_rand_time = 0; - pfox->texture_render_offset = pObject->Height; - //Change texture - pfox->pcurrent_display_texture= PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); - } - else - { - pfox->gen_rand_time -= elapsed; - } - } - } - break; - case PX_OBJECT_FOX_STATE_RASING://Fox is rising - { - pfox->elapsed += elapsed; - //Rising texture offset - pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; - if (pfox->texture_render_offset <= 0) - { - pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT;//After rising, taunt - pfox->elapsed = 0; - } - } - break; - case PX_OBJECT_FOX_STATE_TAUNT://Fox is taunting - { - pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500)//Taunting time - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt");//Taunting texture - } - else if (pfox->elapsed>1500)//Taunting ends - { - pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//Escaping - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape");//Escaping texture - pfox->elapsed = 0; - } - } - break; - case PX_OBJECT_FOX_STATE_BEAT://Fox is being beaten - { - pfox->elapsed += elapsed; - if (pfox->elapsed>800) - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt");//Hurt texture - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//Escaping - } - } - break; - case PX_OBJECT_FOX_STATE_ESCAPE: - { - pfox->elapsed += elapsed; - pfox->texture_render_offset+=pfox->rasing_down_speed * elapsed / 1000; - if (pfox->texture_render_offset >= pObject->Height) - { - pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE;//Escaping ends - pfox->elapsed = 0;//Reset time - pfox->pcurrent_display_texture = PX_NULL; - } - } - break; - default: - break; - } -} - -PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) -{ - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - px_float x,y,width,height; - PX_OBJECT_INHERIT_CODE(pObject,x,y,width,height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE);//Clear the render target - if (pfox->pcurrent_display_texture) - { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL);//Render the fox - } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL);//Render the texture with a mask -} - - -PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) -{ - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - PX_TextureFree(&pfox->render_target); -} - -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick)//Fox was clicked -{ - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING)//Clicking is valid when the fox is taunting or rising - { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM))//Valid click region - { - px_int x= (px_int)PX_Object_Event_GetCursorX(e); - px_int y= (px_int)PX_Object_Event_GetCursorY(e); - x=(px_int)(x-(pObject->x-pObject->Width/2)); - y= (px_int)(y-(pObject->y - pObject->Height)); - if (x>32&&y<128) - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); - pfox->state = PX_OBJECT_FOX_STATE_BEAT; - pfox->elapsed = 0; - PX_Object_ScorePanelAddScore(scorePanel, 100); - } - - } - } -} - -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) -{ - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - pfox->state = PX_OBJECT_FOX_STATE_IDLE; - pfox->elapsed = 0; - pfox->texture_render_offset = pObject->Height; - pfox->gen_rand_time = 0; - pfox->pcurrent_display_texture = PX_NULL; - -} - -PX_Object *PX_Object_FoxCreate(px_memorypool *mp,PX_Object *parent,px_float x,px_float y) -{ - PX_Object_Fox* pfox; - px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing");//Get the texture from the resource manager - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); - pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - pfox->state= PX_OBJECT_FOX_STATE_IDLE;//Fox state - pfox->rasing_down_speed = 512;//Rising speed - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask");//Mask - if(!PX_TextureCreate(mp,&pfox->render_target,ptexture->width,ptexture->height)) - { - PX_ObjectDelete(pObject); - return 0; - } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0);//Register click event - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0);//Register reset event - return pObject; -} -``` - -* First is `PX_Object_FoxOnUpdate`, this is the `update` function in the object suite, where we determine the current state of the `Gopher`, whether it's up, taunting, or down. * Then is `PX_Object_FoxOnRender`, this is the function that performs `rendering`, we draw the texture by offset, of course here we call the `Gopher` function. -* Then there's the `PX_Object_FoxOnRender`, which is the function that does the `rendering`, where we draw the texture by offset, and of course we call the `PX_TextureRenderMask` function, which is a rendering function with a texture mask. -* `PX_Object_FoxFree` function, mainly for the temporary rendering of the surface of the free processing, although in this project does not use. -* `PX_Object_FoxOnClick` function, means the current gopher is hit, which is some judgement of the hit range, if it is hit, it should set the state to injured. -* `PX_Object_FoxOnReset` is used to perform a reset, that is, after the game is over, all gophers should be reset, this is a `PX_OBJECT_EVENT_RESET` callback, you can find it in `PX_Object_FoxCreate`. -* And finally the `PX_Object_FoxCreate` function, where we do some initialisation, register event callbacks for the `Gopher`, and finally complete the development of the component. - - -![](assets/img/18.4.gif) - - -Then, we need to create a `hammer` object to change the style of our mouse. The hammer object has a simple design, it has only 2 textures, one for when the mouse is not pressed and one for when it is pressed. Different states correspond to different textures: - -```c -typedef struct -{ - px_texture ham01;//Hammer texture 1, not pressed - px_texture ham02;//Hammer texture 2, pressed - px_bool bHit;//Whether pressed -}PX_Object_Hammer; -PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender)//Hammer rendering -{ - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - px_float x, y, width, height; - PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); - if (phammer->bHit) - { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//Pressed - } - else - { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//Not pressed - } - -} -PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) -{ - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - PX_TextureFree(&phammer->ham01); - PX_TextureFree(&phammer->ham02); -} -PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnMove) -{ - pObject->x=PX_Object_Event_GetCursorX(e);//Hammer follows the mouse movement - pObject->y=PX_Object_Event_GetCursorY(e); -} -PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorDown) -{ - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_TRUE;//Pressed -} -PX_OBJECT_EVENT_FUNCTION(PX_Object_HammerOnCursorUp) -{ - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE;//Released -} -PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent) -{ - PX_Object_Hammer* phammer; - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, 0, 0, 0, 0, 0, 0, PX_OBJECT_TYPE_HAMMER, 0, PX_Object_HammerRender, PX_Object_HammerFree, 0, sizeof(PX_Object_Hammer)); - phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - phammer->bHit = PX_FALSE; - if (!PX_LoadTextureFromFile(mp_static,&phammer->ham01, "assets/ham1.png")) return PX_NULL; - if (!PX_LoadTextureFromFile(mp_static,&phammer->ham02, "assets/ham2.png")) return PX_NULL; - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORMOVE, PX_Object_HammerOnMove, PX_NULL);//Register move event - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDRAG, PX_Object_HammerOnMove, PX_NULL);//Register drag event - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnCursorDown, PX_NULL);//Register press event - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORDOWN, PX_Object_HammerOnMove, PX_NULL);//Register press event - PX_ObjectRegisterEvent(pObject, PX_OBJECT_EVENT_CURSORUP, PX_Object_HammerOnCursorUp, PX_NULL);//Register release event - return pObject; -} -``` - -Finally, there is a countdown box, which is actually a 2dx animation object in the middle (PainterEngine supports gif animation directly, in fact, gif can also be used), and a ring on the outside, the arc of the ring decreases continuously, in order to achieve a `countdown` display effect: - -```c -typedef struct -{ - PX_Animation animation;//Animation - px_dword time;//Countdown time - px_dword elapsed;//Time elapsed since the countdown began -}PX_Object_Clock; -PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) -{ - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - clock->elapsed += elapsed; - if (clock->elapsed >= clock->time) - { - clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET));//Reset the fox state, send a reset event to the game object - game->Visible = PX_FALSE; - game->Enabled = PX_FALSE; - startgame->Visible = PX_TRUE; - pObject->Visible = PX_FALSE; - pObject->Enabled = PX_FALSE; - } -} -PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) -{ - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed);//Update the animation - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL);//Render the animation - //draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK);//Draw the countdown ring frame - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time)));//Draw the countdown ring -} -PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) -{ - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationFree(&clock->animation); -} -px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time)//Start the countdown -{ - PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); - clock->time = time; - pClock->Visible = PX_TRUE; - pClock->Enabled = PX_TRUE; -} -PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) -{ - PX_Object_Clock* clock; - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 0, 0, 0, PX_OBJECT_TYPE_CLOCK, PX_Object_ClockUpdate, PX_Object_ClockRender, PX_Object_ClockFree, 0, sizeof(PX_Object_Clock)); - clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - clock->time = 0; - clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))//Get the animation from the resource manager - { - PX_ObjectDelete(pObject); - return PX_NULL; - } - pObject->Enabled = PX_FALSE; - pObject->Visible = PX_FALSE; - return pObject; -} -``` - -### Place objects, complete the game - -In the `main` function, we create each of these objects and place them in the game scene to complete the game: - -```c -//Create gophers -game=PX_ObjectCreate(mp, root, 0, 0, 0, 0, 0, 0); -PX_Object_FoxCreate(mp, game, 173, 326); -PX_Object_FoxCreate(mp, game, 401, 326); -PX_Object_FoxCreate(mp, game, 636, 326); -PX_Object_FoxCreate(mp, game, 173, 476); -PX_Object_FoxCreate(mp, game, 401, 476); -PX_Object_FoxCreate(mp, game, 636, 476); -game->Visible=PX_FALSE; -game->Enabled=PX_FALSE; -//Create hammers -PX_Object_HammerCreate(mp, root); -scorePanel = PX_Object_ScorePanelCreate(mp, root, 400, 60, &score_fm, 100); -//Create countdown frame -gameclock=PX_Object_ClockCreate(mp,root,680,60); -``` - -Here, I put the complete code for the entire game: - -```c -#include "PainterEngine.h" - -#define PX_OBJECT_TYPE_FOX 24103001 -#define PX_OBJECT_TYPE_HAMMER 24103002 -#define PX_OBJECT_TYPE_CLOCK 24103003 - -PX_FontModule score_fm; -PX_Object* scorePanel; -PX_Object* game,*startgame,*gameclock; - -typedef enum -{ - PX_OBJECT_FOX_STATE_IDLE,//Gopher still in the hole - PX_OBJECT_FOX_STATE_RASING,//Gopher is rising up - PX_OBJECT_FOX_STATE_TAUNT,//Gopher is taunting - PX_OBJECT_FOX_STATE_ESCAPE,//Gopher is escaping - PX_OBJECT_FOX_STATE_BEAT,//Gopher is hit - PX_OBJECT_FOX_STATE_HURT,//Gopher is hurt and escaping -}PX_OBJECT_FOX_STATE; - -typedef struct -{ - PX_OBJECT_FOX_STATE state;//Gopher state - px_dword elapsed;//Duration of the current state - px_float texture_render_offset;//Texture rendering offset - px_dword gen_rand_time;//Time to generate random action - px_float rasing_down_speed;//Rising speed - px_texture render_target;//Rendering target - px_texture* pcurrent_display_texture;//Current display texture - px_texture* ptexture_mask;//Mask texture -}PX_Object_Fox; - -typedef struct -{ - px_texture ham01;//Hammer texture 1, not pressed - px_texture ham02;//Hammer texture 2, pressed - px_bool bHit;//Whether the hammer is pressed -}PX_Object_Hammer; - -typedef struct -{ - PX_Animation animation;//Animation - px_dword time;//Countdown time - px_dword elapsed;//Time elapsed since the countdown began -}PX_Object_Clock; - - -PX_OBJECT_UPDATE_FUNCTION(PX_Object_ClockUpdate) -{ - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - clock->elapsed += elapsed; - if (clock->elapsed >= clock->time) - { - clock->elapsed = 0; - PX_ObjectPostEvent(game, PX_OBJECT_BUILD_EVENT(PX_OBJECT_EVENT_RESET));//Reset the gopher state, send a reset event to the game object - game->Visible = PX_FALSE; - game->Enabled = PX_FALSE; - startgame->Visible = PX_TRUE; - pObject->Visible = PX_FALSE; - pObject->Enabled = PX_FALSE; - } - -} - -PX_OBJECT_RENDER_FUNCTION(PX_Object_ClockRender) -{ - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationUpdate(&clock->animation, elapsed);//Update the animation - PX_AnimationRender(psurface, &clock->animation, (px_int)pObject->x, (px_int)pObject->y, PX_ALIGN_CENTER, PX_NULL);//Render the animation - //draw ring - PX_GeoDrawCircle(psurface, (px_int)pObject->x, (px_int)pObject->y, 38, 8, PX_COLOR_BLACK);//Draw the countdown ring frame - PX_GeoDrawRing(psurface, (px_int)pObject->x, (px_int)pObject->y, 36, 6, PX_COLOR(128,192,255,32), -90, -90 + (px_int)(360 * (1 - clock->elapsed * 1.0f / clock->time)));//Draw the countdown ring -} - -PX_OBJECT_FREE_FUNCTION(PX_Object_ClockFree) -{ - PX_Object_Clock* clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - PX_AnimationFree(&clock->animation); -} - -px_void PX_Object_ClockBegin(PX_Object* pClock, px_dword time)//Start the countdown -{ - PX_Object_Clock* clock = PX_ObjectGetDescByType(pClock, PX_OBJECT_TYPE_CLOCK); - clock->time = time; - pClock->Visible = PX_TRUE; - pClock->Enabled = PX_TRUE; -} - -PX_Object* PX_Object_ClockCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y) -{ - PX_Object_Clock* clock; - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 0, 0, 0, PX_OBJECT_TYPE_CLOCK, PX_Object_ClockUpdate, PX_Object_ClockRender, PX_Object_ClockFree, 0, sizeof(PX_Object_Clock)); - clock = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_CLOCK); - clock->time = 0; - clock->elapsed = 0; - if (!PX_AnimationCreate(&clock->animation, PX_ResourceLibraryGetAnimationLibrary(PainterEngine_GetResourceLibrary(), "song"))//Get the animation from the resource manager - { - PX_ObjectDelete(pObject); - return PX_NULL; - } - pObject->Enabled = PX_FALSE; - pObject->Visible = PX_FALSE; - return pObject; -} - -PX_OBJECT_UPDATE_FUNCTION(PX_Object_FoxOnUpdate) -{ - PX_Object_Fox* pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - switch (pfox->state) - { - case PX_OBJECT_FOX_STATE_IDLE: - { - if (pfox->gen_rand_time ==0) - { - pfox->gen_rand_time = PX_rand() % 3000 + 1000;//Time the gopher stays in the hole, after which it rises up - } - else - { - if (pfox->gen_rand_time state = PX_OBJECT_FOX_STATE_RASING; - pfox->elapsed = 0; - pfox->gen_rand_time = 0; - pfox->texture_render_offset = pObject->Height; - //Change texture - pfox->pcurrent_display_texture= PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_rasing"); - } - else - { - pfox->gen_rand_time -= elapsed; - } - } - } - break; - case PX_OBJECT_FOX_STATE_RASING://Gopher is rising up - { - pfox->elapsed += elapsed; - //Rising texture offset - pfox->texture_render_offset -= pfox->rasing_down_speed * elapsed / 1000; - if (pfox->texture_render_offset <= 0) - { - pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_TAUNT;//After rising up, start taunting - pfox->elapsed = 0; - } - } - break; - case PX_OBJECT_FOX_STATE_TAUNT://Gopher is taunting - { - pfox->elapsed += elapsed; - if (pfox->elapsed>600&& pfox->elapsed <1500)//Taunting duration - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_taunt");//Taunting texture - } - else if (pfox->elapsed>1500)//Taunting ends - { - pfox->texture_render_offset = 0; - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//Escape - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_escape");//Escape texture - pfox->elapsed = 0; - } - } - break; - case PX_OBJECT_FOX_STATE_BEAT://Gopher is hit - { - pfox->elapsed += elapsed; - if (pfox->elapsed>800) - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_hurt");//Hurt texture - pfox->state = PX_OBJECT_FOX_STATE_ESCAPE;//Escape - } - } - break; - case PX_OBJECT_FOX_STATE_ESCAPE: - { - pfox->elapsed += elapsed; - pfox->texture_render_offset+=pfox->rasing_down_speed * elapsed / 1000; - if (pfox->texture_render_offset >= pObject->Height) - { - pfox->texture_render_offset = pObject->Height; - pfox->state = PX_OBJECT_FOX_STATE_IDLE;//Escape ends - pfox->elapsed = 0;//Reset time - pfox->pcurrent_display_texture = PX_NULL; - } - } - break; - default: - break; - } -} - -PX_OBJECT_RENDER_FUNCTION(PX_Object_FoxOnRender) -{ - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - px_float x,y,width,height; - PX_OBJECT_INHERIT_CODE(pObject,x,y,width,height); - PX_TextureClearAll(&pfox->render_target, PX_COLOR_NONE);//Clear the rendering target - if (pfox->pcurrent_display_texture) - { - PX_TextureRender(&pfox->render_target, pfox->pcurrent_display_texture, (px_int)pfox->render_target.width/2, (px_int)pfox->texture_render_offset, PX_ALIGN_MIDTOP, PX_NULL);//Render the gopher - } - PX_TextureRenderMask(psurface, pfox->ptexture_mask, &pfox->render_target, (px_int)x, (px_int)y, PX_ALIGN_MIDBOTTOM, PX_NULL);//Render the texture using the mask -} - -PX_OBJECT_FREE_FUNCTION(PX_Object_FoxFree) -{ - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - PX_TextureFree(&pfox->render_target); -} - -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnClick)//Gopher is clicked -{ - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - if (pfox->state == PX_OBJECT_FOX_STATE_TAUNT|| pfox->state == PX_OBJECT_FOX_STATE_RASING)//Gopher is taunting or rising, click is valid - { - if (PX_ObjectIsCursorInRegionAlign(pObject, e, PX_ALIGN_MIDBOTTOM))//Click is within the valid region - { - px_int x= (px_int)PX_Object_Event_GetCursorX(e); - px_int y= (px_int)PX_Object_Event_GetCursorY(e); - x=(px_int)(x-(pObject->x-pObject->Width/2)); - y= (px_int)(y-(pObject->y - pObject->Height)); - if (x>32&&y<128) - { - pfox->pcurrent_display_texture = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_beat"); - pfox->state = PX_OBJECT_FOX_STATE_BEAT; - pfox->elapsed = 0; - PX_Object_ScorePanelAddScore(scorePanel, 100); - } - - } - } -} - -PX_OBJECT_EVENT_FUNCTION(PX_Object_FoxOnReset) -{ - PX_Object_Fox* pfox = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_FOX); - pfox->state = PX_OBJECT_FOX_STATE_IDLE; - pfox->elapsed = 0; - pfox->texture_render_offset = pObject->Height; - pfox->gen_rand_time = 0; - pfox->pcurrent_display_texture = PX_NULL; - -} - -PX_Object *PX_Object_FoxCreate(px_memorypool *mp,PX_Object *parent,px_float x,px_float y) -{ - PX_Object_Fox* pfox; - px_texture *ptexture=PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(),"fox_rasing");//Get the texture from the resource manager - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, ptexture->width*1.f, ptexture->height*1.f, 0, PX_OBJECT_TYPE_FOX, PX_Object_FoxOnUpdate, PX_Object_FoxOnRender, PX_Object_FoxFree, 0, sizeof(PX_Object_Fox)); - pfox=PX_ObjectGetDescByType(pObject,PX_OBJECT_TYPE_FOX); - pfox->state= PX_OBJECT_FOX_STATE_IDLE;//Gopher state - pfox->rasing_down_speed = 512;//Rising speed - pfox->ptexture_mask = PX_ResourceLibraryGetTexture(PainterEngine_GetResourceLibrary(), "fox_mask");//Mask texture - if(!PX_TextureCreate(mp,&pfox->render_target,ptexture->width,ptexture->height)) - { - PX_ObjectDelete(pObject); - return 0; - } - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_CURSORDOWN,PX_Object_FoxOnClick,0);//Register click event - PX_ObjectRegisterEvent(pObject,PX_OBJECT_EVENT_RESET,PX_Object_FoxOnReset,0);//Register reset event - return pObject; -} - -PX_OBJECT_RENDER_FUNCTION(PX_Object_HammerRender)//Hammer rendering -{ - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - px_float x, y, width, height; - PX_OBJECT_INHERIT_CODE(pObject, x, y, width, height); - if (phammer->bHit) - { - PX_TextureRender(psurface, &phammer->ham02, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//Pressed - } - else - { - PX_TextureRender(psurface, &phammer->ham01, (px_int)x, (px_int)y, PX_ALIGN_CENTER, PX_NULL);//Not pressed - } - -} - -PX_OBJECT_FREE_FUNCTION(PX_Object_HammerFree) -{ - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - PX_TextureFree(&phammer->ham01); - PX_TextureFree(&phammer->ham02); -} - -px_void PX_Object_HammerReset(PX_Object* hammer)//Reset hammer state -{ - PX_Object_Hammer* phammer = PX_ObjectGetDescByType(hammer, PX_OBJECT_TYPE_HAMMER); - hammer->Visible = PX_FALSE; - phammer->bHit = PX_FALSE; -} - -PX_Object* PX_Object_HammerCreate(px_memorypool* mp, PX_Object* parent, px_float x, px_float y)//Create hammer object -{ - PX_Object_Hammer* phammer; - PX_Object* pObject = PX_ObjectCreateEx(mp, parent, x, y, 0, 0, 0, 0, PX_OBJECT_TYPE_HAMMER, 0, PX_Object_HammerRender, PX_Object_HammerFree, 0, sizeof(PX_Object_Hammer)); - phammer = PX_ObjectGetDescByType(pObject, PX_OBJECT_TYPE_HAMMER); - - if (!PX_TextureCreateFromMemory(mp, PX_ResourceLibraryGetBuffer(PainterEngine_GetResourceLibrary(), "ham01"), &phammer->ham01))//Load hammer texture 1 - { - PX_ObjectDelete(pObject); - return PX_NULL; - } - - if (!PX_TextureCreateFromMemory(mp, PX_ResourceLibraryGetBuffer(PainterEngine_GetResourceLibrary(), "ham02"), &phammer->ham02))//Load hammer texture 2 - { - PX_TextureFree(&phammer->ham01); - PX_ObjectDelete(pObject); - return PX_NULL; - } - - phammer->bHit = PX_FALSE; - pObject->Visible = PX_FALSE; - - return pObject; -} -``` - -You can find the complete resources for this game in `documents/demo/game` and compile it directly with PainterEngine. - -![](assets/img/18.5.gif) - -Try it online: [PainterEngine Online App – Whack-a-Mole](https://www.painterengine.com/main/app/documentgame/) -