网页资讯视频图片知道文库贴吧地图采购
进入贴吧全吧搜索

 
 
 
日一二三四五六
       
       
       
       
       
       

签到排名:今日本吧第个签到,

本吧因你更精彩,明天继续来努力!

本吧签到人数:0

一键签到
成为超级会员,使用一键签到
一键签到
本月漏签0次!
0
成为超级会员,赠送8张补签卡
如何使用?
点击日历上漏签日期,即可进行补签。
连续签到:天  累计签到:天
0
超级会员单次开通12个月以上,赠送连续签到卡3张
使用连续签到卡
01月25日漏签0天
c4droid吧 关注:42,770贴子:262,833
  • 看贴

  • 图片

  • 吧主推荐

  • 游戏

  • 1 2 下一页 尾页
  • 56回复贴,共2页
  • ,跳到 页  
<<返回c4droid吧
>0< 加载中...

【记录】零基础用c4droid写一个3D魔方软件

  • 只看楼主
  • 收藏

  • 回复
  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
这是一个记录贴,标题只是一个目标,只是一个不了解c4droid,不了解安卓的人所随便定下的目标,不一定能够达成。
但由于是一个什么也不懂的初学者,所以遇到的问题和解决方法可以写出来供其他也遇到类似问题的初学者参考。
不定期更新。


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
强二楼!


2026-01-25 16:57:32
广告
不感兴趣
开通SVIP免广告
  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
1.安装c4droid、gcc、SDL
俗话说得好,巧妇难为无米之炊,c4droid、gcc以及SDL就好比这位巧妇,它们没有我这粒米,又该怎么开始编程呢?所以我是很重要的,你们这些成熟的c4droid、gcc以及SDL,要学会自己安装到我的手机上给我用了。下面就是它们自己安装自己的曲折过程:
首先某应用市场上有成套的软件,它们下载安装完后,即使不安装gcc插件,理应也能使用TCC编译运行,但是结果是编译提示成功,运行后进入终端模拟器却无结果,如下图(当时没截图,图是从吧里找的):

然后安装gcc,用gcc编译试试呢?结果只出来个条形方块,如下图(图也是从吧里找的):

然后经过无数次卸载找其他版本安装再卸载再找其他版本再安装后终于找到了问题所在:如果c4droid的版本与安卓版本不适配,会导致终端模拟器出问题,安卓9版本上装5.x版本的c4droid就会出现这个问题。解决方法就是安装6.x及以上版本的c4droid,推荐去qaiu的博客网站,可以在上面下到汉化版本的c4droid以及另外两个插件,安装方法也有详细说明,并且还在保持更新。


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
2.什么是C4droid、GCC和SDL?
反正我就是按照教程安装的,安装的时候也没想过为什么除了C4droid以外还要安装另外两个插件,现在明白了,C4droid只是一个IDE,提供一个集成的编程环境,GCC才是编译器,编译你写出来的C/C++代码,而SDL算是一个第三方库,提供一些和图形显示、音频播放等有关的api。当然C4droid除了提供编程环境以外还提供了一个运行环境:终端模拟器,没有它你的程序编译出来了也没法像在电脑上一样通过shell去运行并测试输入输出。


  • 浅凝
  • 高手寂寞
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
jiauou


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
3.使用终端模拟器(terminal emulator)编译并执行程序
通过屏幕下方的编译和运行按钮就能对程序进行编译和执行,但如果我就是闲着没事干想用终端模拟器通过命令行的形式来编译和执行程序怎么办?没问题,首先右上角,点开终端模拟器,然后就可以开始你的表演了。但是如果我们按部就班输入下面两条命令(分别进行编译和执行):
$ gcc helloworld.cpp
$ ./a.out
就会出错:

出错原因不明,只知道改成使用下面两条命令:
$ gcc -pie helloworld.cpp
$ ./a.out
就能正确执行:


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
4.输入括号时光标会跳到起始位
通常情况下输入左括号时会自动输入右括号,光标会聚焦在两个括号之间。但在C4droid中输入括号光标会莫名其妙跳到起始位置,原因不明。
在设置中关掉“允许键盘建议”就能规避此问题,但同时也失去了自动输入右括号带来的便利。


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
5.C/C++教程
这没什么可说的,正如本吧简介所说:“有问题为什么不先问问C语言吧呢?”
另外,要学会善用搜索引擎。一般初学者的问题基本都问烂了,一搜一大把,关于C/C++的教程更是遍地开花。个人认为学会基本语法,能看懂大部分代码的逻辑结构就可以了,剩下的在编程过程中遇到问题的时候再交给搜索引擎就好,边用边学。
介绍两个关于C/C++非常全面的参考网站:
cppreference.com(语言可选中文)
cplusplus.com
这两个网站不仅有基础教程,还有详尽的API文档,要想查询API或有其他语法问题,直接在这两个网站里按关键字搜索或按分类查找即可。


2026-01-25 16:51:32
广告
不感兴趣
开通SVIP免广告
  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
6.DXKite的《SDL2初级教程》
这是一篇贴吧内写就的教程,写得比较随意,篇幅很短,一两天就能学完,需要搭配对应的例程学习。主要内容包括:
1.教你如何显示一张现成的图片,并对图片进行去背景色(背景透明化)、切割、缩放、旋转处理。
2.利用外部字体文件显示文字。
3.检测触屏事件。
4.检测键盘事件。不知何故,手机输入法键盘的大多数按键都检测不到。
5.播放音频文件。目前只能播放出例程中的.ogg和.wav格式的音频文件,.mp3格式无效果。
6.通过获取时间信息来限制帧率,以使不同设备之间都能获得一致的帧率体验。
7.多线程。我知道意思是想要让程序同时做多件事,但教程描述非常简略,看不出是如何使用的。
首次学习SDL2时跟着这个教程做能快速入门,但还需要注意以下几点:
1.配套的源代码与教程中所展示的代码有出入,似乎是为电脑编写的,尽管和手机上比差别不大,但是有些代码需要修改后才能编译运行成功。代码中有些路径可能与自己的路径不同,建议都改成相对路径,会比较省事。
2.毕竟是初级教程,涵盖的点不够全面,也不够深入。虽然教程里有提到高级教程,但作者后来已经宣布不会继续写高级教程了。
3.教程有时过分简略,有些人可能看不懂。
那么学完这个如何进一步学习?一个是看SDL的官网:
libsdl.org
不仅有API文档可供检索,还有一个教程列表,列出其他提供教程的网站,其中有一套lazy foo的教程被吧友提到过:
lazyfoo.net/tutorials/SDL
还未细看,据说是最好的教程。


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
7.导出apk安装包
如果你已经写好了一个成熟的程序,你肯定不希望每次运行都要通过C4droid打开源代码文件运行。如果你想把你的程序分享给其他人使用,也不能保证对方机子上也装了个C4droid。所以生成一个apk安装包是很有必要的。
用C4droid导出apk安装包很简单:右上角导出,再在弹出的界面上点“导出”,就会在程序文件所在目录下生成一个同名安装包。
当不需要加载图片等外部资源的情况下,上面的做法不会出什么问题。但当需要加载图片等外部资源时,上面的做法可能会出问题。出问题的原因是资源没有放在正确的地方,也没有在导出界面正确地设置资源目录,从而导致资源没被打包进安装包,安装后打开应用程序找不到资源自然会出错。
解决方法是在程序文件所在目录下新建一个文件夹(asserts)专门用来放资源,把资源放进去:

由于资源路径变了,程序代码里涉及到资源路径的地方也要做出相应的改变:

然后开始导出,在导出界面中的“应用资源目录”一栏填新建的资源文件夹(asserts)名:

再导出,生成的安装包就正常了。


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
8.叜駣的“native activity教程”
C4droid在安装GCC的时候可以勾选“安装例子到...”一项:

安装后会在相应目录下出现一个文件夹,里面放着一堆例子。其中有一个NativeActivity的例子(cube.c)非常有意思,运行后会出现一个立方体,用手指触摸屏幕的不同位置立方体的体位就会发生相应的变化:

我意识到这个例子和我的目标是高度契合的。于是我抛弃了SDL2的学习,转而去寻找native activity的教程。结果发现,至少在中文世界里,关于native activity的教程真是少之又少,能看懂并使用的就更少了,唯一对其框架进行详细解释的,我就只找到了由叜駣在吧内写就的“native activity教程”。
该教程的作者首先针对之前提到的那个例子(cube.c),在结构上对其批判一番,称这个例子写得“相当凌乱,毫无章法”,让人难以理解,然后想用一个极简的讲解方式让大家更容易理解并使用native activity。
native activity到底是什么我不是很清楚,只知道它是一个比较底层,比较靠近设备输入输出的一个地方,能够用于监听设备的系统事件(比如窗口初始化和关闭窗口)和输入事件(比如键盘输入和触屏输入),在这个基础上,如果用EGL来打开窗口,就可以使用OpenGL ES来画出3D图像。该教程不仅包含native avtivity最基础的框架部分,还涵盖了多点触控和陀螺仪(重力感应器)输入的获取,以及EGL的开窗过程。所以,按照这个教程做下来,可以得到一个比较万能的框架,剩下的只需根据自己的实际需求填入程序逻辑以及OpenGL ES代码即可。
这个教程比较遗憾的地方在于消失的15楼,从后面的内容可以推测出15楼也是教程的一部分,它的缺失对理解该楼之后的内容有一定影响,但问题不大。


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
9.从前述教程中整理出来的native activity框架
/*请忽视注释中提到的楼层,或结合叜駣的原教程阅读*/
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <android/sensor.h>
#include <android_native_app_glue.h>
const ASensor *sensor;/* 感应器 */
ASensorEventQueue *sensor_event_queue; /* 感应器事件队列 */
struct android_app *android_app;/* java扔给我们的系统状态 */
EGLSurface surface;/* egl 显示平面 */
EGLDisplay display;/* egl 显示设备 */
EGLContext context;/* egl 显示上下文 */
int x0, y0, x1, y1, x2, y2;
void refresh()
{
int x, y, z;/* 获得陀螺仪矢量的x/y/z值 */
ASensorEvent event;/* 创立一个感应器事件用来储存 */
int looper_id;/* 函数ALooper_pollAll()返回的事件代码 */
struct android_poll_source *source;
eglSwapBuffers(display, surface);/* 交换显示缓冲,display与surface于7楼创建,假定它们是全局变量 */
while ((looper_id = ALooper_pollAll(0, NULL, NULL, (void **)&source)) >= 0) /* 有事件发生,保存事件源到source,并保存事件代码 */
{
if (source)/* 如果事件源有效 */
source->process(android_app, source);
/* 将java扔给我们的系统状态与事件源扔进事件源中名为process的成员函数,对事件进行处理 */
if (looper_id == LOOPER_ID_USER)/* 如果返回的事件标识与我们初始化陀螺仪时设置的标识相同 */
while (ASensorEventQueue_getEvents(sensor_event_queue, &event, 1) > 0) /* 循环从感应器事件队列sensor_event_queue中获得事件,并保存到感应器事件event中,每次保存一个 */
{
x = event.acceleration.x;/* 从感应器事件event的成员acceleration中得到陀螺仪矢量的x值 */
y = event.acceleration.y;/* 从感应器事件event的成员acceleration中得到陀螺仪矢量的y值 */
z = event.acceleration.z;/* 从感应器事件event的成员acceleration中得到陀螺仪矢量的z值 */
}
}
}
int on_input_event(struct android_app *app, AInputEvent * input_event) /* app是java扔给我们的系统状态,input_event是java扔给我们的输入事件 */
{
if (AInputEvent_getType(input_event) == AINPUT_EVENT_TYPE_MOTION) /* 我们用AInputEvent_getType()得知输入事件是屏幕触碰 */
{
x0 = AMotionEvent_getX(input_event, 0);/* 将输入事件和触碰序号0,代入AMotionEvent_getX,得到0号手指的触碰横坐标 */
y0 = AMotionEvent_getY(input_event, 0);/* 将输入事件和触碰序号0,代入AMotionEvent_getX,得到0号手指的触碰纵坐标 */
x1 = AMotionEvent_getX(input_event, 1);/* 将输入事件和触碰序号1,代入AMotionEvent_getX,得到1号手指的触碰横坐标 */
y1 = AMotionEvent_getY(input_event, 1);/* 将输入事件和触碰序号1,代入AMotionEvent_getX,得到1号手指的触碰纵坐标 */
x2 = AMotionEvent_getX(input_event, 2);/* 将输入事件和触碰序号2,代入AMotionEvent_getX,得到2号手指的触碰横坐标 */
y2 = AMotionEvent_getY(input_event, 2);/* 将输入事件和触碰序号2,代入AMotionEvent_getX,得到2号手指的触碰纵坐标 */
}
return 0;/* 应当返回0,但如果认为input_event事件已经完全处理完毕的话,可以返回1 */
}
void on_app_cmd(struct android_app *app, int event_type) /* app是java扔给我们的系统状态,event_type是java扔给我们的事件类型 */
{
switch (event_type)/* 我们根据java扔给我们的事件类型做出不同的反应 */
{
case APP_CMD_INIT_WINDOW:/* java扔给我们的事件类型是:初始化窗口 */
{
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE, 8,
EGL_NONE
};
EGLint w, h, dummy, format;
EGLint numConfigs;
EGLConfig config;
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(app->window, 0, 0, format);
surface = eglCreateWindowSurface(display, config, app->window, NULL);
context = eglCreateContext(display, config, NULL, NULL);
eglMakeCurrent(display, surface, surface, context);
break;
}
case APP_CMD_TERM_WINDOW:/* java扔给我们的事件类型是:关闭窗口 */
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(display, context);
eglDestroySurface(display, surface);
eglTerminate(display);
break;
case APP_CMD_GAINED_FOCUS:/* java扔给我们的事件类型是:获得焦点 */
ASensorEventQueue_enableSensor(sensor_event_queue, sensor); /* 为感应器事件队列(sensor_event_queue)启用陀螺仪感应器(sensor) */
ASensorEventQueue_setEventRate(sensor_event_queue, sensor, 1000000 / 60); /* 设定感应器的事件频率,换算后是1/60秒,与一般的屏幕刷新率一致) */
break;
case APP_CMD_LOST_FOCUS:/* java扔给我们的事件类型是:失去焦点 */
ASensorEventQueue_disableSensor(sensor_event_queue, sensor); /* 为感应器事件队列(sensor_event_queue)关闭感应器(sensor) */
break;
}
}
void android_main(struct android_app *app)
{
ASensorManager *sensor_manager;/* 感应器管理器 */
app_dummy();/* 确保glue的功能没有被忽略 */
app->onAppCmd = on_app_cmd;/* 将9楼完成的on_app_cmd回传函数赋值到app的onAppCmd成员 */
app->onInputEvent = on_input_event;/* 将13楼完成的on_input_event回传函数赋值到app的onInputEvent成员 */
android_app = app;/* 将参数app赋值到全局变量android_app */
sensor_manager = ASensorManager_getInstance(); /* 获得感应器管理器 */
sensor = ASensorManager_getDefaultSensor(sensor_manager, ASENSOR_TYPE_ACCELEROMETER); /* 获得陀螺仪感应器 */
sensor_event_queue = ASensorManager_createEventQueue(sensor_manager, app->looper, LOOPER_ID_USER, NULL, NULL); /* 创建系统循环更新时的感应器事件队列 */
/* 自定义部分,主循环也包含在这个部分中 */
while (1)
{
refresh();
}
}


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
10.基于上述框架重写native activity例子(cube.c)
其实上面的框架和cube.c大同小异,主要的区别是上面的框架抛弃了engine、state结构体,改为使用全局变量。
代码太长,就不贴上来了,楼中楼分享。


  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
11.改进cube.c中方块旋转的交互方式
在原例子中,虽然在屏幕上滑动手指可以让方块看起来像是在连续转动,但实际上它是通过读取手指在屏幕上的绝对位置来决定方块的体位的,手指戳在哪个位置,方块就立马摆出哪个位置所对应的体位,这也造成了方块在连续变化时转动规律表现得十分反直觉,人机交互友好度基本为零。原例子的效果是这样的:

为了实现更加人性化的拖曳转动交互方式,也为以后实现魔方的转动打下基础,我开始对这个例子进行改造。改造的前提是读懂原来的例子是如何利用OpenGL ES实现的。开始之前,我以为随便查几个API就能顺利完成,还意识不到其中暗藏的陷阱,让我走了很多弯路。首先要说的是,画图的API不会让人在理解上产生歧义,真正坑人的地方在于坐标变换,也就是通常所说的平移、旋转以及缩放。OpenGL ES有相应的函数用来执行这些操作,但要小心这些函数写的顺序和实际操作的执行顺序是相反的(我至今也不知道开发者为什么要这么设计),也就是说,如果你先写平移,再写旋转,最终效果则是先旋转再平移,并且OpenGL并不会记住变换后的模型状态,当下一次进行变换时并不会接着上一次的状态进行变换,而是继续遵循着后写先变换的原则对你后来写的先进行变换再对以前写的进行变换,这就不是你想要的结果了!当时我就是栽在了这里,花费了好长时间才爬出来,即使找到了问题所在我还要面临另一个问题:如何解决?要知道坐标变换实质是变换矩阵相乘,只要把之前的变换矩阵保存起来,再重新写现在的变换,然后再把以前的变换拿出来,这样顺序就调换过来了。即使我知道了这个原理,我也在找获取变换矩阵的函数上花了很多时间。
现在我们来看看改进成功后的版本是什么样的。在改进后的版本中,方块以手指滑动方向和滑动距离(而不是绝对位置)为基准进行旋转。来直观看看它的效果:

是不是人性化了许多?实际上这种操作方式叫做轨迹球(trackball)。攻克这一关很重要,因为以后我也会采用类似的方式来转魔方。
源代码见楼中楼。


2026-01-25 16:45:32
广告
不感兴趣
开通SVIP免广告
  • 贴吧用户_Q5P6URC
  • 哈啰沃德
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
12.拾取面元
点击屏幕可以产生一个屏幕坐标,如何通过这个坐标判断空间中的哪个对象被选中非常重要,至少在我要实现的功能里,必须要能判断出手指点中了哪一个小方块,然后根据滑动方向决定转动哪一层。拾取面元看起来好像挺简单的,也应该是一个被经常用到的功能,所以理应包含在OpenGL的库里。经过我的多方查找,OpenGL确实把这个功能封装实现了,但是OpenGL ES却把这部分给精简掉了,所以不存在一个可以直接拿来就用的工具,需要重新实现。思路是:1.将每一个面元从当前位置通过投影矩阵投影到屏幕上。2.判断手指点击的位置落在哪些投影面内。3.通过比较这些面元的深度就能判断出哪个面元被选中。思路看起来很简单,但真正深入到细节就很繁琐,需要注意:1.OpenGL的矩阵是纵向排列的,可以看成是矩阵的转置。2.模型视图矩阵和投影矩阵都是可以直接获得的,但是视口变换矩阵只能获得关键参数,矩阵还得自己算。包括矩阵乘法也得自己写。也就是说从原始坐标一路变换到屏幕上的整个过程都要自己写。3.判断点是否在一个多边形内是一个经典问题,网上可以搜到很多算法。我直接搬了一个判断射线与多边形交点个数的奇偶性的算法。为了验证我写的拾取算法的正确性,我接着前面改写的cube.c例子实现了这样的效果:手指点击方块的哪一面,哪一面的颜色就会发生切换。具体如图:
说是点击,其实手指松开时的位置落在面上就会发生切换,不影响验证。在实现过程中,我还发现了面上的顶点输入到绘图函数的顺序必须是8字形排列的,否则画不全。之所以发现这个是因为它的排列顺序和输入判断点是否在面内的函数的顶点排列顺序是冲突的。而且我偷懒严重,直接通过判断面元重心的深度来判断哪个面元最近,这仅适用于方块这种简单情况,其他稍复杂情况可能就不行了。还有很多地方写得不够通用,以后可能还会改进。源代码见楼中楼。


登录百度账号

扫二维码下载贴吧客户端

下载贴吧APP
看高清直播、视频!
  • 贴吧页面意见反馈
  • 违规贴吧举报反馈通道
  • 贴吧违规信息处理公示
  • 1 2 下一页 尾页
  • 56回复贴,共2页
  • ,跳到 页  
<<返回c4droid吧
分享到:
©2026 Baidu贴吧协议|隐私政策|吧主制度|意见反馈|网络谣言警示