3-8 用draw.io来绘制界面*
⏱ 本节预计用时:60分钟
本节意义:3-6 里我们画井字棋界面时,每个按钮、每行字的位置都是一串串手写死的数字(250, 250, 300, 80…),想挪一下就得改数字、重新编译、跑起来再看,太麻烦。
于是你很自然地想,有没有画图软件,能让我直接在屏幕上把界面画出来?然后它生成的文件可以转成文本文件,自带每个图形的大小和位置信息。(这样我把文件喂给ai,它就能帮我直接根据我选的图形库,生成绘制的代码!)
有的。有很多,业界常用的figma,上位机工程师几乎必会的qtdesigner,做的都是这个事情。但考虑到教学方便,我将隆重为你介绍一款使用起来简单粗暴,免费无广告的软件:draw.io。
你拿ppt也能做,但是比较麻烦。为啥ai能生成ppt,本质上就是先生成一个含有图像位置和大小信息的文本文件,然后把它通过脚本转成ppt的格式;反过来,你在ppt里通过拖拽的方式布局好文字和图片,一样可以通过脚本转成文本文件。
本节目标:搞清楚为什么用画图工具做界面;下载draw.io;在draw.io里会画图,能改文字、颜色;让 AI 从 .drawio 生成 raylib 绘制代码,替换进井字棋项目跑起来。
1 下载 draw.io
打开官网:
https://www.drawio.com/
下载:
网络不好的话可以去资源文件夹里下,安装好之后解压,直接双击draw.io.exe就可以使用了。
第一次打开会让你选哪里保存文件——右上角可以切语言,选中文。随便选一个(Local Device 本机保存就行),它就进到画布页面了:中间一大片带网格的白色画布,左边一列图形库,这就是我们的"工作台"。
画布上的小格子网格不是乱画的,它像坐标纸一样,让我们能对齐、能估算距离——坐标纸,正是这一节的主题。
2 画出井字棋主菜单
2.1 改窗口大小
你可以修改成任意你喜欢的大小,我在这里还是用800*600像素。
你可能觉得它和井字棋的800 600窗口大小不一样,不用管,这是为了显示效果好看,windows系统对drawio软件整体做的缩放,不影响我们之后使用。
可以把这个以图片格式导出,发现导出的东西确实是800 600大小的。
2.2 补全文字
咱们这次换个布局,文字在左,按钮在右。
在左侧找到文字拖到窗口左边,你可以改成你喜欢的大小和颜色。
这个时候你保存退出,你会得到一个.drawio文件,用draw.io软件打开是那个图像。现在让我们用trae打开,你会看到一堆文本信息,里边就包括了你刚才设置的内容。
这种"尖括号一层层包起来"的文本格式叫 XML。不用会写,你只要知道它是文本,AI 最会读文本就行了。直接把他喂给AI,能一字不差翻成 raylib 绘制代码,坐标、文字、颜色,这些全一毛一样。
拿snipaste回去测量,字体所在矩形的大小和位置信息和文本编辑器里写的一模一样。
更牛逼的是,你在这里可以直接改它的属性,退出去之后拿drawio打开,你改成啥样打开就是啥样。
当然,我们也可以通过在drawio中直接输入数值的方式,来调整位置和大小。
ok,演示完毕,把x坐标(即位置-左)改回70。
2.3 画按键
选中矩形拖动并在右边改样式。
提前预警:这里画的很精美,后边可能是没法正常显示的。
因为raylib库的功能有限,ai没办法很好的将文本文件里的意思转换成raylib的代码。
比如你在drawio里画成这样:
最后显示出来可能是这个效果:
这不是你的问题,也不是这套工作流的问题,是工具本身的能力不够强。之后我们会学更强的设计软件和功能更强的图形库,到时候,我们能把这件事做的更好,先知道这个思路就行。
3 让AI读.drawio文件,生成raylib代码
先给ai介绍你这个项目的基本情况,让他结合.drawio文件对原本的界面做修改。
提示词:
(8-1井字棋图形版修改主菜单)这是我的c语言井字棋项目,分为3个界面,主界面、游戏界面和结算界面,这是我主界面的(主界面.drawio).drawio文件,你结合它对现在项目里主界面的代码做修改,重新编译
修改之后的效果展示:
代码只改了view.c:
#include "raylib.h"
#include "view.h"
#define CELL_SIZE 120 /* 棋盘每个格子的边长 */
#define BOARD_X 220 /* 棋盘左上角的横坐标 */
#define BOARD_Y 140 /* 棋盘左上角的纵坐标 */
#define LINE_SIZE 6 /* 格线的粗细(格子之间的缝) */
/* 三个按钮的位置(x, y, 宽, 高)。
* 主菜单两个按钮的坐标是照着 drawio 设计稿量的:标题在左边,
* Start / Exit 两个按钮在右边排成一列 */
Rectangle startBtn = { 450, 130, 220, 90 }; /* 主菜单的"Start"按钮 */
Rectangle exitBtn = { 450, 350, 220, 90 }; /* 主菜单的"Exit"按钮 */
Rectangle backBtn = { 300, 515, 200, 60 }; /* 游戏/结算里的"返回主菜单" */
/* 主菜单配色,从 drawio 设计稿里抄来的。
* drawio 里的 #fff2cc 这种写法叫"十六进制颜色",
* 每两位对应一个 0~255 的数,依次是红、绿、蓝;
* 最后一个 255 表示完全不透明。 */
Color startFill = { 255, 242, 204, 255 }; /* #fff2cc 浅黄(Start 的底色) */
Color startLine = { 214, 182, 86, 255 }; /* #d6b656 深黄(Start 的边框) */
Color exitFill = { 218, 232, 252, 255 }; /* #dae8fc 浅蓝(Exit 的底色) */
Color exitLine = { 108, 142, 191, 255 }; /* #6c8ebf 深蓝(Exit 的边框) */
/* 把棋盘第 row 行第 col 列,转成屏幕上的一个矩形(row、col 和 board 下标一样是 0~2) */
Rectangle cellRect(int row, int col)
{
Rectangle rec;
rec.x = BOARD_X + col * CELL_SIZE;
rec.y = BOARD_Y + row * CELL_SIZE;
rec.width = CELL_SIZE;
rec.height = CELL_SIZE;
return rec;
}
/* ---------- 绘图:一个函数画一个界面 ---------- */
/* 画主菜单(照 drawio 设计稿:左边大标题,右边上下两个按钮) */
void showMainMenu(void)
{
/* 1. 标题:设计稿里标题占的区域是 (70, 210),宽 290 高 130,字号 50。
* 用 MeasureText 算出文字实际有多宽,再让它在这块区域里居中 */
int titleW = MeasureText("Tic-Tac-Toe", 50);
DrawText("Tic-Tac-Toe",
70 + (290 - titleW) / 2, /* 横向居中:左边空 (区域宽-字宽)/2 */
210 + (130 - 50) / 2, /* 纵向居中:上边空 (区域高-字高)/2 */
50, BLACK);
/* 2. Start 按钮:浅黄底 + 深黄边框,文字 30 号居中 */
DrawRectangleRec(startBtn, startFill);
DrawRectangleLinesEx(startBtn, 2, startLine);
DrawText("Start",
(int)(startBtn.x + (startBtn.width - MeasureText("Start", 30)) / 2),
(int)(startBtn.y + (startBtn.height - 30) / 2),
30, BLACK);
/* 3. Exit 按钮:浅蓝底 + 深蓝边框,文字 30 号居中 */
DrawRectangleRec(exitBtn, exitFill);
DrawRectangleLinesEx(exitBtn, 2, exitLine);
DrawText("Exit",
(int)(exitBtn.x + (exitBtn.width - MeasureText("Exit", 30)) / 2),
(int)(exitBtn.y + (exitBtn.height - 30) / 2),
30, BLACK);
}
/* 画游戏界面里的"返回主菜单"按钮 */
void showGameMenu(void)
{
DrawRectangleRec(backBtn, LIGHTGRAY);
DrawText("MENU", 370, 530, 25, DARKGRAY);
}
/* 画棋盘:深灰色大矩形当底 + 9 个浅灰色格子矩形,再画棋子 */
void showBoard(void)
{
int row, col;
/* 1. 深灰色大矩形:覆盖整个棋盘区域 */
DrawRectangle(BOARD_X, BOARD_Y, 3 * CELL_SIZE, 3 * CELL_SIZE, DARKGRAY);
/* 2. 9 个浅灰色格子:每个比一格小一圈(四周各让出 LINE_SIZE / 2),盖在深灰底上 */
for (row = 0; row < 3; row++) {
for (col = 0; col < 3; col++) {
Rectangle r = cellRect(row, col);
DrawRectangle((int)r.x + LINE_SIZE / 2,
(int)r.y + LINE_SIZE / 2,
CELL_SIZE - LINE_SIZE,
CELL_SIZE - LINE_SIZE,
LIGHTGRAY);
}
}
/* 3. 棋子 */
for (row = 0; row < 3; row++) {
for (col = 0; col < 3; col++) {
Rectangle r = cellRect(row, col);
int cx = (int)(r.x + CELL_SIZE / 2) - 15; /* 让字大概落在格子中央 */
int cy = (int)(r.y + CELL_SIZE / 2) - 15;
if (board[row][col] == CELL_X) {
DrawText("X", cx, cy, 40, BLUE);
} else if (board[row][col] == CELL_O) {
DrawText("O", cx, cy, 40, RED);
}
}
}
}
/* 画"当前轮到谁"这行提示 */
void showPlayerInfo(void)
{
DrawText("Turn:", 220, 60, 40, DARKGRAY);
DrawText(TextFormat("%c", players[currentPlayer - 1].mark), 350, 60, 40, RED);
}
/* 画结算界面(谁赢了 / 平局,加一个返回按钮) */
void showResult(CellType winner)
{
if (winner == CELL_EMPTY) {
DrawText("DRAW!", 290, 230, 80, DARKGRAY);
} else {
DrawText(TextFormat("%c wins!", players[winner - 1].mark), 280, 230, 80, DARKGRAY);
}
DrawRectangleRec(backBtn, LIGHTGRAY);
DrawText("MENU", 370, 530, 25, DARKGRAY);
}
/* ---------- 判断鼠标点了哪里(把鼠标坐标翻译成"点中了什么") ---------- */
int startBtnClicked(Vector2 mouse) { return CheckCollisionPointRec(mouse, startBtn); }
int exitBtnClicked(Vector2 mouse) { return CheckCollisionPointRec(mouse, exitBtn); }
int backBtnClicked(Vector2 mouse) { return CheckCollisionPointRec(mouse, backBtn); }
int cellClicked(Vector2 mouse, int row, int col)
{
return CheckCollisionPointRec(mouse, cellRect(row, col));
}
4 流程总结
这一节你知道怎么快速搞界面了,恭喜!
画界面 → 存成文本文件 → AI 读坐标/文字/颜色等信息 → 结合你用的图形库生成代码