6.3 处理游戏手柄事件

wxJoystick类让你可以在windows平台或者linux平台上使用一到两个游戏手柄.典型的使用方法是,你创建wxJoystick的一个实例,并且传递给它wxJOYSTICK1或者wxJOYSTICK2的参数,并且以全局指针保持这个实例.当你需要处理手柄事件的时候,使用 SetCapture函数和一个窗口指针作为参数,来使的这个窗口收到游戏手柄事件,当你不需要使用手柄的时候,调用ReleaseCapture函数移出手柄事件.当然,你完全可以在应用程序初始化的时候调用SetCapture函数而在应用程序退出的时候调用ReleaseCapture函数,以便在整个应用程序生命周期内处理游戏手柄事件.

在开始描述详细的函数和事件之前,让我们先看一下wxWidgets的发行版中samples/joystick目录中的例子.在这个例子中,用户可以使用游戏手柄上的某个按钮画线,并且在按下按钮的时候播放一个声音片断.

下面大概列出了应用程序的起始代码,首先,应用程序通过创建一个临时的游戏手柄对象来检查系统是否有安装手柄,如果没有则退出应用程序.然后装载声音文件,并且获取手柄的最大活动范围以便在窗口上画画的时候实现合理的缩放使得画画的区域充满整个绘画窗口.

#include "wx/wx.h"
#include "wx/sound.h"
#include "wx/joystick.h"
bool MyApp::OnInit()
{
    wxJoystick stick(wxJOYSTICK1);
    if (!stick.IsOk())
    {
        wxMessageBox(wxT("No joystick detected!"));
        return false;
    }
    m_fire.Create(wxT("buttonpress.wav"));
    m_minX = stick.GetXMin();
    m_minY = stick.GetYMin();
    m_maxX = stick.GetXMax();
    m_maxY = stick.GetYMax();
    // Create the main frame window
    ...
    return true;
}

MyCanvas是那个用来接收和处理手柄事件的窗口,下面的MyCanvas类的实现部分:

BEGIN_EVENT_TABLE(MyCanvas, wxScrolledWindow)
    EVT_JOYSTICK_EVENTS(MyCanvas::OnJoystickEvent)
END_EVENT_TABLE()
MyCanvas::MyCanvas(wxWindow *parent, const wxPoint& pos,
    const wxSize& size):
    wxScrolledWindow(parent, wxID_ANY, pos, size, wxSUNKEN_BORDER)
{
    m_stick = new wxJoystick(wxJOYSTICK1);
    m_stick->SetCapture(this, 10);
}
MyCanvas::~MyCanvas()
{
    m_stick->ReleaseCapture();
    delete m_stick;
}
void MyCanvas::OnJoystickEvent(wxJoystickEvent& event)
{
    static long xpos = -1;
    static long ypos = -1;
    wxClientDC dc(this);
    wxPoint pt(event.GetPosition());
    // if negative positions are possible then shift everything up
    int xmin = wxGetApp().m_minX;
    int xmax = wxGetApp().m_maxX;
    int ymin = wxGetApp().m_minY;
    int ymax = wxGetApp().m_maxY;
    if (xmin < 0) {
        xmax += abs(xmin);
        pt.x += abs(xmin);
    }
    if (ymin < 0) {
        ymax += abs(ymin);
        pt.y += abs(ymin);
    }
    // Scale to canvas size
    int cw, ch;
    GetSize(&cw, &ch);
    pt.x = (long) (((double)pt.x/(double)xmax) * cw);
    pt.y = (long) (((double)pt.y/(double)ymax) * ch);
    if (xpos > -1 && ypos > -1 && event.IsMove() && event.ButtonIsDown())
    {
        dc.SetPen(*wxBLACK_PEN);
        dc.DrawLine(xpos, ypos, pt.x, pt.y);
    }
    xpos = pt.x;
    ypos = pt.y;
    wxString buf;
    if (event.ButtonDown())
        buf.Printf(wxT("Joystick (%d, %d) Fire!"), pt.x, pt.y);
    else
        buf.Printf(wxT("Joystick (%d, %d)"), pt.x, pt.y);
    frame->SetStatusText(buf);
    if (event.ButtonDown() && wxGetApp().m_fire.IsOk())
    {
        wxGetApp().m_fire.Play();
    }
}

wxJoystick的事件

wxJoystick类产生wxJoystickEvent类型的事件,下表列出了所有相关的事件映射宏:

EVT_JOY_BUTTON(func) 用来处理wxEVT_JOY_BUTTON_DOWN事件,手柄上的某个按钮被按下的时候产生.
EVT_JOY_BUTTON(func) 用来处理wxEVT_JOY_BUTTON_UP事件,某个按钮被释放的时候产生.
EVT_JOY_MOVE(func) 用来处理wxEVT_JOY_MOVE事件,手柄在X-Y平面上产生移动的时候产生.
EVT_JOY_ZMOVE(func) 用来处理wxEVT_JOY_ZMOVE事件,手柄在Z方向上产生移动的时候产生.
EVT_JOYSTICK_EVENTS(func) 处理所有的手柄事件.

wxJoystickEvent的成员函数

下面列出了所有wxJoystickEvent的成员函数,你可以使用它们获取关于手柄事件更详细的信息,当然,首先还是GetEventType函数,这个函数在你使用EVT_JOYSTICK_EVENTS宏的时候告诉你你正在处理哪种类型的手柄事件.

调用ButtonDown函数来检测是否是按钮被按下事件,你可以传递可选的wxJOY_BUTTONn(n代表1,2,3或4)来测试是否某个特定的按钮正被按下,或者使用wxJOY_BUTTON_ANY如果你不关心具体是哪个按钮被按下的话.ButtonUp则用相似的方法来检测是按钮被释放事件.而IsButton则相当于调用ButtonDown() || ButtonUp().

要判断是否某个按钮被按下的状态而不是事件本身,你可以使用ButtonIsDown函数,它和ButtonDown的参数相似.或者你可以直接使用GetButtonState函数和类似的参数来返回指定按钮的按下状态的比特位.

使用IsMove函数来判断是否是一个XY平面的移动事件,使用IsZMove判断是否是一个Z平面的移动事件.

GetPosition返回游戏手柄当前XY平面的座标,而GetZPosition返回Z方向的深度值(当然,如果手柄支持的话).

最后,你可以使用GetJoystick来判断这个事件是哪个手柄(wxJOYSTICK1还是wxJOYSTICK2)产生的.

wxJoystick成员函数

我们没有列出游戏手柄类所有的成员函数,你可以参考wxWidgets的手册,不过下面是其中最有趣的几个:

正象我们在前面的例子中看到的那样,SetCapture函数需要被调用如果你想让某个窗口处理手柄事件,而一个匹配的 ReleaseCapture函数调用用来释放这次捕获,以便允许别的应用程序使用手柄.为避免别的程序正在使用手柄,或者手柄不能正常工作,你应该在调用SetCapture之前先调用IsOK函数来判断手柄的状态.你还可以通过这些函数来获取手柄的能力集:GetNumberButtons, GetNumberJoysticks,GetNumberAxes,HasRudder等等.

GetPosition函数和GetButtonState函数可以让你在手柄事件处理函数以外的地方获取手柄的状态.

你的应用程序还可能需要调用GetXMin,GetXMax等这些类似的函数来判断一个手柄支持的范围.