3-5 画矩形和按钮

0 阅读6分钟

3-5 画矩形和按钮

⏱ 本节预计用时:20分钟

本节意义:不管用啥图形库,画矩形、做按钮都是类似的流程。做项目必用。

本节目标:核心不在记住函数,在于理解。会画矩形,理解按钮是怎么回事;做出一个点 EXIT 就关窗口的小程序;养成"先更新、后渲染"的绘图习惯。

1 画矩形

直接问AI画矩形的函数,以及它声明在哪个头文件里。

DrawRectangle(100, 100, 200, 150, RED);
//参数为        X    Y    宽   高   颜色
//声明在raylib.h里

易知,这行代码在 (100, 100) 处画一个宽200、高150的红色实心矩形。画出来的矩形位置是唯一确定的,它占据了从 (100,100) 到 (300,250) 的区域。

把上边画文字的代码换成画矩形的代码,编译运行:

5-1 矩形示例1

矩形的位置及大小示意图:

5-2 矩形示例2

和颜色一样,矩形也有结构体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;
}

运行效果:

5-3 矩形示例3

2 鼠标交互

在命令行版井字棋里,你想"开始游戏",得输入数字 1 再按回车;在图形版里,你希望的是:鼠标点一下"开始游戏"那个矩形,程序就启动。

这也是图形软件和命令行软件最大的区别之一。用户不再靠敲键盘和程序交互了,而是靠鼠标点点点。

这背后其实藏着两个问题:

  1. 程序怎么知道鼠标现在在哪?
  2. 程序怎么知道鼠标点了没有,怎么点的?

去问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

运行:

5-4 鼠标点击示例

你移动鼠标会看到红点跟着走,屏幕上实时显示坐标;每点一下左键,计数就加 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;
}

运行效果:

5-5 退出按钮示例

ok,该学的都学完了,下一讲,让我们开始改井字棋!