游戏手柄和操纵杆
就像鼠标输入和键盘输入一样,SDL也有能力读取来自操纵杆/游戏手柄/游戏控制器的输入。在本教程中,我们将根据操纵杆的输入使箭头旋转。
//模拟手柄死区
const int JOYSTICK_DEAD_ZONE = 8000;
SDL处理游戏控制器上的模拟杆的方式是,它将其位置转换为-32768和32767之间的数字。这意味着一个轻敲可以报告一个1000+的位置。我们想忽略轻敲,所以我们想创建一个死区,在这个死区中,来自操纵杆的输入被忽略。这就是我们定义这个常数的原因,我们稍后会看到它是如何工作的。
//游戏控制器1处理机
SDL_Joystick* gGameController = NULL;
游戏控制器的数据类型是SDL_Joystick。在这里,我们声明全局操纵杆手柄,我们将使用它来与操纵杆进行交互。
bool init()
{
//Initialization flag
bool success = true;
//Initialize SDL
if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK ) < 0 )
{
printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError() );
success = false;
}
这一点很重要。
到目前为止,我们一直只初始化视频,以便我们可以渲染到屏幕上。现在我们需要初始化操纵杆子系统,否则从操纵杆读取数据将无法工作。
//将纹理过滤设置为线性
if( !SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" ) )
{
printf( "Warning: Linear texture filtering not enabled!" );
}
//检查操纵杆
if( SDL_NumJoysticks() < 1 )
{
printf( "Warning: No joysticks connected!\n" );
}
else
{
//加载操纵杆
gGameController = SDL_JoystickOpen( 0 );
if( gGameController == NULL )
{
printf( "Warning: Unable to open game controller! SDL Error: %s\n", SDL_GetError() );
}
}
在初始化操纵杆子系统后,我们要打开我们的操纵杆。首先我们调用SDL_NumJoysticks
来检查是否至少有一个操纵杆连接。如果有,我们调用SDL_JoystickOpen
来打开索引0的操纵杆。操纵杆打开后,现在它将向SDL事件队列报告事件。
void close()
{
//Free loaded images
gArrowTexture.free();
//Close game controller
SDL_JoystickClose( gGameController );
gGameController = NULL;
//Destroy window
SDL_DestroyRenderer( gRenderer );
SDL_DestroyWindow( gWindow );
gWindow = NULL;
gRenderer = NULL;
//Quit SDL subsystems
IMG_Quit();
SDL_Quit();
}
完成操纵杆的操作后,请使用SDL_JoystickClose将其关闭。
//Main loop flag
bool quit = false;
//Event handler
SDL_Event e;
//正常化方向
int xDir = 0;
int yDir = 0;
在这个演示中,我们要跟踪x和y的方向。如果x等于-1,则操纵杆的x位置指向左边。如果是+1,则x位置指向右。操纵杆的y位置有正的为上,负的为下,所以y=+1为上,y=-1为下。如果x或y为0,说明它在死区,处于中心位置。
//Handle events on queue
while( SDL_PollEvent( &e ) != 0 )
{
//User requests quit
if( e.type == SDL_QUIT )
{
quit = true;
}
else if( e.type == SDL_JOYAXISMOTION )
{
//Motion on controller 0
if( e.jaxis.which == 0 )
{
//X axis motion
if( e.jaxis.axis == 0 )
{
//死区左侧
if( e.jaxis.value < -JOYSTICK_DEAD_ZONE )
{
xDir = -1;
}
//死区右侧
else if( e.jaxis.value > JOYSTICK_DEAD_ZONE )
{
xDir = 1;
}
else
{
xDir = 0;
}
}
在我们的事件循环中,通过检查SDL_JoyAxisEvent来检查操纵杆是否已经移动。"which"变量表示轴的运动来自哪个控制器,这里我们检查事件来自操纵杆 0。
接下来我们要检查它是x方向的运动还是y方向的运动,"axis"变量表示。通常情况下,0轴是x轴。
"value"变量表示模拟杆在轴上的什么位置。如果x位置小于死区,则方向设置为负。如果位置大于死区,则方向设置为正。如果在死区,则方向设置为0。
//Y轴运动
else if( e.jaxis.axis == 1 )
{
//死区下方
if( e.jaxis.value < -JOYSTICK_DEAD_ZONE )
{
yDir = -1;
}
//死区上方
else if( e.jaxis.value > JOYSTICK_DEAD_ZONE )
{
yDir = 1;
}
else
{
yDir = 0;
}
}
}
}
}
在这里,我们再次对y轴(用轴ID 1标识)执行相同的操作。
//Clear screen
SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
SDL_RenderClear( gRenderer );
//Calculate angle
double joystickAngle = atan2( (double)yDir, (double)xDir ) * ( 180.0 / M_PI );
//Correct angle
if( xDir == 0 && yDir == 0 )
{
joystickAngle = 0;
}
在我们渲染箭头之前,我们需要计算出箭头的角度。 我们使用cmath函数atan2进行此操作,该函数代表反正切2,即AKA反正切2。
对于熟悉三角函数的人来说,这基本上是反正切函数,其中包含一些附加代码,这些附加代码考虑了值来自哪个象限。
对于只熟悉几何的人来说,只要知道您给它的y位置和x位置,它就会为您提供以弧度为单位的角度。 SDL想要以度为单位的旋转角度,所以我们必须将弧度转换成度,将它乘以Pi的180。
当x和y位置均为0时,我们可以得到一个无意义的角度,因此我们将该值校正为等于0。
//Render joystick 8 way angle
gArrowTexture.render( ( SCREEN_WIDTH - gArrowTexture.getWidth() ) / 2, ( SCREEN_HEIGHT - gArrowTexture.getHeight() ) / 2, NULL, joystickAngle );
//Update screen
SDL_RenderPresent( gRenderer );
最后我们将箭头呈现在屏幕上旋转。
还有其他的操纵杆事件,比如按钮按下、pov hats和插入或移除控制器。它们都相当简单,你应该可以通过查看文档和实验来了解它们。
在 这里下载本教程的媒体和源代码。
关注我的公众号:编程之路从0到1