3-5 画矩形和按钮
⏱ 本节预计用时:20分钟
本节意义:不管用啥图形库,画矩形、做按钮都是类似的流程。做项目必用。
本节目标:核心不在记住函数,在于理解。会画矩形,理解按钮是怎么回事;做出一个点 EXIT 就关窗口的小程序;养成"先更新、后渲染"的绘图习惯。
1 画矩形
直接问AI画矩形的函数,以及它声明在哪个头文件里。
DrawRectangle(100, 100, 200, 150, RED);
//参数为 X Y 宽 高 颜色
//声明在raylib.h里
易知,这行代码在 (100, 100) 处画一个宽200、高150的红色实心矩形。画出来的矩形位置是唯一确定的,它占据了从 (100,100) 到 (300,250) 的区域。
把上边画文字的代码换成画矩形的代码,编译运行:
矩形的位置及大小示意图:
和颜色一样,矩形也有结构体Rectangle,它用来描述一个矩形,有四个成员:
typedef struct Rectangle {
float x; /* 矩形左上角的横坐标 */
float y; /* 矩形左上角的纵坐标 */
float width; /* 宽 */
float height; /* 高 */
} Rectangle;
它的几个成员和DrawRectangle传入的参数一样,那你很自然地就会想,那我参数直接传入Rectangle不就完了嘛,我能想到做库的大佬也能想到,那肯定有对应的函数。
一问Ai果然有,于是我们也可以这么画矩形:
Rectangle rect = {100, 100, 200, 150};
DrawRectangleRec(rect, RED);
完整示例:
#include "raylib.h"
int main()
{
int screenWidth = 800;
int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "Hello raylib");
SetTargetFPS(60);
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(RAYWHITE);
Rectangle rect = {100, 100, 200, 150};
DrawRectangleRec(rect, RED);
EndDrawing();
}
CloseWindow();
return 0;
}
运行效果:
2 鼠标交互
在命令行版井字棋里,你想"开始游戏",得输入数字 1 再按回车;在图形版里,你希望的是:鼠标点一下"开始游戏"那个矩形,程序就启动。
这也是图形软件和命令行软件最大的区别之一。用户不再靠敲键盘和程序交互了,而是靠鼠标点点点。
这背后其实藏着两个问题:
- 程序怎么知道鼠标现在在哪?
- 程序怎么知道鼠标点了没有,怎么点的?
去问AI:"raylib 里,怎么获取鼠标位置?怎么判断左键被按下?请给我一个能跑通的最小例子。"
得到了这么一坨:
#include "raylib.h"
int main(void)
{
InitWindow(800, 600, "鼠标测试");
SetTargetFPS(60);
int clickCount = 0; /* 记录一共点了多少下左键 */
while (!WindowShouldClose())
{
/* 1. GetMousePosition:返回鼠标当前的坐标,是一个 Vector2 */
Vector2 mouse = GetMousePosition();
/* 2. IsMouseButtonPressed:这一帧"刚刚按下"左键才返回 true */
/* 注意:是"按下这一下",不是"一直按着不放" */
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
clickCount = clickCount + 1;
}
BeginDrawing();
ClearBackground(RAYWHITE);
/* 把坐标和点击次数画到屏幕上(TextFormat 是格式化字符串,就像 printf) */
DrawText(TextFormat("鼠标位置: %d, %d", (int)mouse.x, (int)mouse.y),
20, 20, 20, DARKGRAY);
DrawText(TextFormat("左键点击次数: %d", clickCount),
20, 50, 20, DARKGRAY);
/* 在鼠标位置画一个红色小圆点,移动鼠标就能看到圆点跟着走 */
DrawCircleV(mouse, 8, RED);
EndDrawing();
}
CloseWindow();
return 0;
}
先编译:
gcc 4-1鼠标测试.c -o 4-1鼠标测试.exe -I raylib/include -L raylib/lib -lraylib -lopengl32 -lgdi32 -lwinmm
运行:
你移动鼠标会看到红点跟着走,屏幕上实时显示坐标;每点一下左键,计数就加 1。
这里面出现了几个新东西,Vector2、GetMousePosition、MOUSE_BUTTON_LEFT、IsMouseButtonPressed、TextFormat等等。
这里我们不看头文件了,直接结合ai给的这些注释去看代码,去主动地想这些词应该是什么东西,做了什么事情。
- Vector2,应该是有x和y两个成员的结构体,表示坐标位置,和我们第二章井字棋里写的Position结构体一样。
- MOUSE_BUTTON_LEFT是一个鼠标左键的宏,我猜它应该还有鼠标中键和右键的宏。
- GetMousePosition,翻译一下就是获取鼠标位置,返回值的类型当然是Vector2(因为要获取x,y坐标),不用传入参数,因为系统里的鼠标光标只有一个,直接在函数体里对它操作就行。
- IsMouseButtonPressed,翻译一下就是鼠标是否点击,IsMouseButtonPressed()就像score>100一样,会返回一个布尔值,如果点了则返回真,没点则返回假。
- TextFormat,很明显,它是个像printf一样的函数,区别是printf是把字符串组装好之后输出到终端,它是直接返回。
为什么鼠标在桌子上动一下,电脑就知道鼠标具体怎么懂的,并且能返回它在屏幕上的位置?暂时不用管,后边的书会讲嵌入式和linux系统,到时候你就会明白整个流程是什么样的。
认真去想这个过程,觉得一切都是这么地顺其自然,这时再去看源码,发现确实就是这么回事(有些地方有偏差,就再去想人家为啥这样)。这时,对于整个过程的理解也便更深了一层,再次出现类似的需求时也就有思路了。
3 咋点按钮
知道咋点了,那怎么"点按钮"呢?这里我们假设按钮是一个矩形,判断点没点按钮,就是判断鼠标点的那个位置,在不在矩形里面。
"点 P 在矩形 R 里",翻译成大白话就是:
P 的横坐标要在 R 的左、右两条边之间,P 的纵坐标要在 R 的上、下两条边之间。
那么,判断点在不在矩形里的逻辑就应该这样写:
Rectangle btn = { 250, 250, 300, 80 }; /* 一个按钮矩形 */
if (mouse.x >= btn.x && mouse.x <= btn.x + btn.width && /* 横坐标在左右边之间 */
mouse.y >= btn.y && mouse.y <= btn.y + btn.height) { /* 纵坐标在上下边之间 */
/* 鼠标在这个矩形里 */
}
你很自然地就会想,这个操作用的还挺多,之前raylib官方肯定写过这个逻辑了。
一查果然有:
bool CheckCollisionPointRec(Vector2 point, Rectangle rec);
作用:point 落在矩形 rec 里就返回 true,否则 false。
所以"判断鼠标点中了按钮"就是这么一句:
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && CheckCollisionPointRec(mouse, btn)) {
/* 点中了按钮 btn */
}
这里我们画一个退出的按钮,点它就关闭窗口。下一节做井字棋的主菜单时可以直接复用。
注意代码中的逻辑是先更新,再渲染,这是行业通用的绘图标准。
不是先渲染、再更新,也不是边渲染、边更新。这个我们会放到第七章讲清楚。
#include "raylib.h"
int main(void)
{
InitWindow(800, 600, "退出按钮示例");
SetTargetFPS(60);
Rectangle exitBtn = { 250, 360, 300, 80 };
while (!WindowShouldClose())
{
// 1. 更新阶段:读取输入
/* 每一帧,先把"输入"读进来 */
Vector2 mouse = GetMousePosition();
int clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
if (clicked && CheckCollisionPointRec(mouse, exitBtn)) {
break; /* 点退出:关程序 */
}
// 2. 渲染阶段:只负责画东西
/* 画这一帧 */
BeginDrawing();
ClearBackground(RAYWHITE);
DrawRectangleRec(exitBtn, GRAY); /* 画"退出"按钮 */
DrawText("EXIT", 360, 382, 30, WHITE);
EndDrawing();
}
CloseWindow();
return 0;
}
运行效果:
ok,该学的都学完了,下一讲,让我们开始改井字棋!