//按钮类 class Button { private: //按钮的属性 SDL_Rect box; //精灵图中显示出来的子图 SDL_Rect* clip; public: //初始化变量 Button( int x, int y, int w, int h ); //处理事件并设置按钮的精灵图子图 void handle_events(); //将按钮显示在窗口中 void show(); };
这是我们的Button类,我们将与它进行交互。
我们用一个矩形来定义按钮的位置和大小。我们还有一个指针,它指向了按钮所使用的精灵图子图。
然后我们有一个构造函数,它依据传入的参数设置来按钮的属性。然后是我们的
接下来是
我们用一个矩形来定义按钮的位置和大小。我们还有一个指针,它指向了按钮所使用的精灵图子图。
然后我们有一个构造函数,它依据传入的参数设置来按钮的属性。然后是我们的
handle_events()
函数,它用于处理鼠标移动事件和鼠标按钮事件。接下来是
show()
函数,它会将按钮显示到窗口中。void set_clips() { //分割精灵图 clips[ CLIP_MOUSEOVER ].x = 0; clips[ CLIP_MOUSEOVER ].y = 0; clips[ CLIP_MOUSEOVER ].w = 320; clips[ CLIP_MOUSEOVER ].h = 240; clips[ CLIP_MOUSEOUT ].x = 320; clips[ CLIP_MOUSEOUT ].y = 0; clips[ CLIP_MOUSEOUT ].w = 320; clips[ CLIP_MOUSEOUT ].h = 240; clips[ CLIP_MOUSEDOWN ].x = 0; clips[ CLIP_MOUSEDOWN ].y = 240; clips[ CLIP_MOUSEDOWN ].w = 320; clips[ CLIP_MOUSEDOWN ].h = 240; clips[ CLIP_MOUSEUP ].x = 320; clips[ CLIP_MOUSEUP ].y = 240; clips[ CLIP_MOUSEUP ].w = 320; clips[ CLIP_MOUSEUP ].h = 240; }
这是我们从精灵图中分割子图的函数。
你可以看到,我们有一张显示不同类型鼠标事件的精灵图。所以我们有一个包含4个
SDL_Rect
类型数据的数组,它被用来从精灵图中分割出每个按钮子图。每个按钮子图都有一个与之相关的常量。Button::Button( int x, int y, int w, int h ) { //设置按钮的属性 box.x = x; box.y = y; box.w = w; box.h = h; //设置默认的子图 clip = &clips[ CLIP_MOUSEOUT ]; }
Button类的构造函数十分浅显易懂,它设置了按钮的X/Y轴上的偏移量以及其宽高。
并且它将精灵图中的某一个子图设为了默认子图。
译者注:offset直译是“偏移量”,指的从坐标原点(注意是左上角)沿着某一坐标轴平移到某一点时所经过的距离,平移方向决定了+/-号,故含义上同“坐标”。因此本文中的“偏移量”与“坐标”指的是一个概念,并且为了保证语句通顺,“偏移量”与“坐标”两种翻译可能都会用到。
并且它将精灵图中的某一个子图设为了默认子图。
void Button::handle_events() { //鼠标坐标 int x = 0, y = 0; //如果鼠标发生了移动 if( event.type == SDL_MOUSEMOTION ) { //获得鼠标坐标 x = event.motion.x; y = event.motion.y; //如果鼠标在按钮上方 if( ( x > box.x ) && ( x < box.x + box.w ) && ( y > box.y ) && ( y < box.y + box.h ) ) { //设置按钮子图 clip = &clips[ CLIP_MOUSEOVER ]; } //如果不是 else { //设置按钮子图 clip = &clips[ CLIP_MOUSEOUT ]; } }
在事件处理函数中,我们首先要做的是检查鼠标是否发生了移动。鼠标移动后,会产生一个
如果鼠标发生了移动,我们从事件结构体中获取鼠标的偏移量,然后检查鼠标是否在按钮的上方。如果鼠标在按钮的上方,我们将按钮的子图设为“Mouse Over”子图,否则设为“Mouse Out”子图。
SDL_MOUSEMOTION
事件。如果鼠标发生了移动,我们从事件结构体中获取鼠标的偏移量,然后检查鼠标是否在按钮的上方。如果鼠标在按钮的上方,我们将按钮的子图设为“Mouse Over”子图,否则设为“Mouse Out”子图。
//如果一个鼠标按钮被按下了 if( event.type == SDL_MOUSEBUTTONDOWN ) { //如果鼠标左键被按下了 if( event.button.button == SDL_BUTTON_LEFT ) { //获取鼠标坐标 x = event.button.x; y = event.button.y; //如果鼠标在按钮上方 if( ( x > box.x ) && ( x < box.x + box.w ) && ( y > box.y ) && ( y < box.y + box.h ) ) { //设置按钮子图 clip = &clips[ CLIP_MOUSEDOWN ]; } } }
然后我们检查是否有鼠标按钮被按下了。如果一个鼠标按钮被按下,它会产生一个
因为我们想让按钮只与鼠标左键交互,所以我们检查一下鼠标左键是否被按下。
然后我们再检查一下是否是当鼠标位于按钮上方时,发生了左键单击事件。如果是的,将按钮子图设为“Mouse Down”子图。
SDL_MOUSEBUTTONDOWN
事件。因为我们想让按钮只与鼠标左键交互,所以我们检查一下鼠标左键是否被按下。
然后我们再检查一下是否是当鼠标位于按钮上方时,发生了左键单击事件。如果是的,将按钮子图设为“Mouse Down”子图。
//如果一个鼠标按钮被释放了 if( event.type == SDL_MOUSEBUTTONUP ) { //如果鼠标左键被释放了 if( event.button.button == SDL_BUTTON_LEFT ) { //获取鼠标坐标 x = event.button.x; y = event.button.y; //如果鼠标在按钮上方 if( ( x > box.x ) && ( x < box.x + box.w ) && ( y > box.y ) && ( y < box.y + box.h ) ) { //设置按钮子图 clip = &clips[ CLIP_MOUSEUP ]; } } } }
然后我们检查是否有鼠标按钮被释放了。如果一个鼠标按钮被释放,它会产生一个
在这个程序里,我们通过事件结构体来获取鼠标的坐标。实际上,通过
SDL_MOUSEBUTTONUP
事件。
在这个程序里,我们通过事件结构体来获取鼠标的坐标。实际上,通过
SDL_GetMouseState()
这个函数来获取鼠标坐标会更高效,但是...呃...我懒得回去改代码了。void Button::show() { //显示按钮 apply_surface( box.x, box.y, buttonSheet, screen, clip ); }
接下来,我们让按钮的子图显示在窗口中。
//分割精灵图 set_clips(); //新建按钮 Button myButton( 170, 120, 320, 240 );
在main()函数的顶部,我们在做完初始化和加载后,要先分割精灵图并新建我们的按钮。
//当用户还没有退出 while( quit == false ) { //如果有事件需要处理 if( SDL_PollEvent( &event ) ) { //处理鼠标按钮事件 myButton.handle_events(); //如果用户点击了窗口右上角的关闭按钮 if( event.type == SDL_QUIT ) { //退出程序 quit = true; } } //使用白色填充整个窗口 SDL_FillRect( screen, &screen->clip_rect, SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ) ); //显示按钮 myButton.show(); //更新窗口 if( SDL_Flip( screen ) == -1 ) { return 1; } }
这里展示了Button类在主循环中的具体用法。
一开始我们要处理事件。你可以看出来,我们使用了Button类的事件处理函数并且我们也检查用户是否需要退出。
一般情况下,我们使用while循环来处理事件,但在本节课中(以及前面一节课中),我们都用了“if”。这么做会使得每一帧中仅有一个事件被处理,于是可以更容易地观察到单独的事件。但在绝大多数真实的应用中,你会使用“while”循环,因为你需要在每一帧中处理所有新的事件。
事件处理完毕后,我们用填充白色的方法清除窗口中旧的图像。然后我们让按钮显示出来并更新窗口。
下面主循环会继续运行,所以我们可以一帧一帧地渲染,直到用户请求退出。
一开始我们要处理事件。你可以看出来,我们使用了Button类的事件处理函数并且我们也检查用户是否需要退出。
一般情况下,我们使用while循环来处理事件,但在本节课中(以及前面一节课中),我们都用了“if”。这么做会使得每一帧中仅有一个事件被处理,于是可以更容易地观察到单独的事件。但在绝大多数真实的应用中,你会使用“while”循环,因为你需要在每一帧中处理所有新的事件。
事件处理完毕后,我们用填充白色的方法清除窗口中旧的图像。然后我们让按钮显示出来并更新窗口。
下面主循环会继续运行,所以我们可以一帧一帧地渲染,直到用户请求退出。
对于你们当中使用较快电脑的人来说,可能看不到“Mouse Up”子图。这是因为这程序跑得太快了,以至于这个子图仅仅显示了极短的时间。幸运的是,我们下面会有一系列关于计时和调节帧率的教程。如果你将程序的运行速度调节到20帧每秒以下,你至少应该可以注意到这个子图。