Cocos2d-x 多平台按键映射实战:Win32、Android 与遥控器适配

问题背景

Cocos2d-x 要跑在 Win32、Android、TV 盒子等多种平台上。不同平台的输入设备差异很大:Win32 用键盘,Android 用触屏和按键,TV 盒子用遥控器。这篇文章分享我在项目中实现的一套统一按键映射方案,让一套代码适配所有输入设备。

平台按键差异概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────────────────┐
│ 不同平台的按键输入对比 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Win32 键盘 Android 设备 TV 遥控器 │
│ ───────────── ───────────── ───────────── │
│ │
│ ↑ VK_UP(38) ↑ KEYCODE_DPAD_UP ↑ KEYCODE_DPAD_UP│
│ ↓ VK_DOWN(40) ↓ KEYCODE_DPAD_DOWN ↓ KEYCODE_DPAD_DOWN│
│ ← VK_LEFT(37) ← KEYCODE_DPAD_LEFT ← KEYCODE_DPAD_LEFT│
│ → VK_RIGHT(39) → KEYCODE_DPAD_RIGHT → KEYCODE_DPAD_RIGHT│
│ ↵ VK_RETURN(13) ↵ KEYCODE_ENTER ↵ KEYCODE_DPAD_CENTER│
│ Esc VK_ESCAPE(27) ◁ KEYCODE_BACK ◁ KEYCODE_BACK │
│ F1 VK_F1(112) ☰ KEYCODE_MENU ☰ KEYCODE_MENU │
│ 1 VK_1(49) 1 KEYCODE_1 1 KEYCODE_1 │
│ 2 VK_2(50) 2 KEYCODE_2 2 KEYCODE_2 │
│ │
│ 问题:不同平台的键值完全不同! │
│ 解决方案:建立统一的按键映射层 │
│ │
└─────────────────────────────────────────────────────────────────────┘

Cocos2d-x 按键系统架构

原始按键系统的问题

Cocos2d-x 2.x 版本的原始按键分发器(CCKeypadDispatcher)只支持两个按键:

1
2
3
4
5
6
7
8
9
10
11
// 原始代码 (CCKeypadDispatcher.cpp)
switch (nMsgType) {
case kTypeBackClicked:
pDelegate->keyBackClicked();
break;
case kTypeMenuClicked:
pDelegate->keyMenuClicked();
break;
default:
break;
}

这显然无法满足复杂游戏的需求。我们需要扩展按键系统,支持所有按键类型。

扩展后的按键架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
┌─────────────────────────────────────────────────────────────┐
│ 扩展按键系统架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CCKeypadDispatcher │ │
│ │ (按键分发器) │ │
│ └────────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Win32键盘 │ │ Android按键 │ │ 遥控器按键 │ │
│ │ (VK_CODE) │ │ (KeyCode) │ │ (KeyEvent) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └────────────────┴────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 统一按键编码 │ │
│ │ (GameKeyCode) │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ onKeyDown/Up │ │
│ │ (业务处理) │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

核心修改方案

步骤 1:扩展按键委托协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// CCKeypadDelegate.h
// 增加通用按键事件处理方法

class CCKeypadDelegate {
public:
// 原始方法(保持兼容)
virtual void keyBackClicked() {}
virtual void keyMenuClicked() {}

// 新增:通用按键按下事件
virtual void onKeyDown(int keyCode) {}

// 新增:通用按键抬起事件
virtual void onKeyUp(int keyCode) {}

// 新增:通用按键长按事件
virtual void onKeyLongPress(int keyCode) {}
};

步骤 2:修改按键分发器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// CCKeypadDispatcher.cpp

// 修改前:只处理返回和菜单键
/*
switch (nMsgType) {
case kTypeBackClicked:
pDelegate->keyBackClicked();
break;
case kTypeMenuClicked:
pDelegate->keyMenuClicked();
break;
default:
break;
}
*/

// 修改后:直接传递所有按键事件
bool CCKeypadDispatcher::dispatchKeypadMSG(ccKeypadMSGType nMsgType) {
// 先尝试调用旧的回调保持兼容性
switch (nMsgType) {
case kTypeBackClicked:
pDelegate->keyBackClicked();
break;
case kTypeMenuClicked:
pDelegate->keyMenuClicked();
break;
default:
break;
}

// 调用新的通用按键回调
pDelegate->onKeyDown(nMsgType);

return true;
}

步骤 3:Win32 平台事件接入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// CCEGLView.cpp (Win32)

// 修改前:只处理 F1, F2, ESC 键
/*
if (wParam == VK_F1 || wParam == VK_F2) {
CCDirector* pDirector = CCDirector::sharedDirector();
if (GetKeyState(VK_LSHIFT) < 0 || GetKeyState(VK_RSHIFT) < 0 || GetKeyState(VK_SHIFT) < 0)
pDirector->getKeypadDispatcher()->dispatchKeypadMSG(
wParam == VK_F1 ? kTypeBackClicked : kTypeMenuClicked);
}
else if (wParam == VK_ESCAPE) {
CCDirector::sharedDirector()->getKeypadDispatcher()->dispatchKeypadMSG(kTypeBackClicked);
}
*/

// 修改后:传递所有按键事件
LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_KEYDOWN:
// 将所有按键事件传递给分发器
CCDirector::sharedDirector()->getKeypadDispatcher()
->dispatchKeypadMSG(wParam);
break;

case WM_KEYUP:
// 可以添加按键抬起事件
break;

// ... 其他消息处理
}

return DefWindowProc(m_hWnd, message, wParam, lParam);
}

步骤 4:Android 平台事件接入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// TouchesJni.cpp (Android JNI 层)

// 修改前:只处理返回键和菜单键
/*
switch (keyCode) {
case KEYCODE_BACK:
if (pDirector->getKeypadDispatcher()->dispatchKeypadMSG(kTypeBackClicked))
return JNI_TRUE;
break;
case KEYCODE_MENU:
if (pDirector->getKeypadDispatcher()->dispatchKeypadMSG(kTypeMenuClicked))
return JNI_TRUE;
break;
default:
return JNI_FALSE;
}
*/

// 修改后:传递所有按键事件
extern "C" {
JNIEXPORT jboolean JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeKeyDown(
JNIEnv* env, jobject thiz, jint keyCode) {

CCDirector* pDirector = CCDirector::sharedDirector();

// 将所有按键码传递给 C++ 层
if (pDirector->getKeypadDispatcher()->dispatchKeypadMSG(keyCode)) {
return JNI_TRUE;
}

return JNI_FALSE;
}
}

步骤 5:Android Java 层修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Cocos2dxGLSurfaceView.java

// 修改前:只处理返回键和菜单键
/*
switch (pKeyCode) {
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_MENU:
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyDown(pKeyCode);
}
});
return true;
default:
return super.onKeyDown(pKeyCode, pKeyEvent);
}
*/

// 修改后:处理所有按键
@Override
public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent) {
// 将所有按键事件传递给 C++ 层处理
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyDown(pKeyCode);
}
});

// 返回 true 消费事件,不冒泡(除了返回键可能需要特殊处理)
if (pKeyCode == KeyEvent.KEYCODE_BACK) {
// 返回键由 C++ 层决定是否消费
return true;
}

return true;
}

@Override
public boolean onKeyUp(final int pKeyCode, final KeyEvent pKeyEvent) {
// 可选:传递按键抬起事件
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyUp(pKeyCode);
}
});

return super.onKeyUp(pKeyCode, pKeyEvent);
}

统一按键编码系统

定义游戏按键枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// GameKeyCode.h
#ifndef __GAME_KEY_CODE_H__
#define __GAME_KEY_CODE_H__

// 统一的游戏按键编码(与平台无关)
enum GameKeyCode {
// 方向键
GAME_KEY_UP = 0,
GAME_KEY_DOWN,
GAME_KEY_LEFT,
GAME_KEY_RIGHT,

// 功能键
GAME_KEY_OK, // 确定/回车
GAME_KEY_BACK, // 返回
GAME_KEY_MENU, // 菜单
GAME_KEY_HOME, // 主页

// 数字键
GAME_KEY_0,
GAME_KEY_1,
GAME_KEY_2,
GAME_KEY_3,
GAME_KEY_4,
GAME_KEY_5,
GAME_KEY_6,
GAME_KEY_7,
GAME_KEY_8,
GAME_KEY_9,

// 媒体键
GAME_KEY_VOLUME_UP,
GAME_KEY_VOLUME_DOWN,
GAME_KEY_MUTE,

// 未知按键
GAME_KEY_UNKNOWN = -1
};

#endif

平台按键映射器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// KeyCodeMapper.h
#ifndef __KEY_CODE_MAPPER_H__
#define __KEY_CODE_MAPPER_H__

#include "GameKeyCode.h"
#include <map>

class KeyCodeMapper {
public:
// 平台类型
enum PlatformType {
PLATFORM_WIN32,
PLATFORM_ANDROID,
PLATFORM_IOS,
PLATFORM_TV_BOX
};

// 获取单例
static KeyCodeMapper* getInstance();

// 初始化映射表
void init(PlatformType platform);

// 将平台按键码映射为游戏按键码
GameKeyCode mapToGameKey(int nativeKeyCode);

// 将游戏按键码映射为平台按键码
int mapToNativeKey(GameKeyCode gameKey);

// 获取按键名称
const char* getKeyName(GameKeyCode gameKey);

private:
KeyCodeMapper();

PlatformType _platform;
std::map<int, GameKeyCode> _nativeToGame;
std::map<GameKeyCode, int> _gameToNative;

// 各平台的初始化方法
void initWin32Mapping();
void initAndroidMapping();
void initTVBoxMapping();
};

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// KeyCodeMapper.cpp
#include "KeyCodeMapper.h"

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
#include <windows.h>
#endif

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
// Android 按键定义
#define AKEYCODE_DPAD_UP 19
#define AKEYCODE_DPAD_DOWN 20
#define AKEYCODE_DPAD_LEFT 21
#define AKEYCODE_DPAD_RIGHT 22
#define AKEYCODE_DPAD_CENTER 23
#define AKEYCODE_BACK 4
#define AKEYCODE_MENU 82
#define AKEYCODE_HOME 3
#define AKEYCODE_0 7
#define AKEYCODE_1 8
#define AKEYCODE_VOLUME_UP 24
#define AKEYCODE_VOLUME_DOWN 25
#endif

static KeyCodeMapper* s_instance = nullptr;

KeyCodeMapper* KeyCodeMapper::getInstance() {
if (!s_instance) {
s_instance = new KeyCodeMapper();
}
return s_instance;
}

void KeyCodeMapper::init(PlatformType platform) {
_platform = platform;
_nativeToGame.clear();
_gameToNative.clear();

switch (platform) {
case PLATFORM_WIN32:
initWin32Mapping();
break;
case PLATFORM_ANDROID:
initAndroidMapping();
break;
case PLATFORM_TV_BOX:
initTVBoxMapping();
break;
default:
initAndroidMapping();
break;
}
}

void KeyCodeMapper::initWin32Mapping() {
// Win32 虚拟键码映射
_nativeToGame[VK_UP] = GAME_KEY_UP;
_nativeToGame[VK_DOWN] = GAME_KEY_DOWN;
_nativeToGame[VK_LEFT] = GAME_KEY_LEFT;
_nativeToGame[VK_RIGHT] = GAME_KEY_RIGHT;
_nativeToGame[VK_RETURN] = GAME_KEY_OK;
_nativeToGame[VK_ESCAPE] = GAME_KEY_BACK;
_nativeToGame[VK_SPACE] = GAME_KEY_OK;
_nativeToGame['0'] = GAME_KEY_0;
_nativeToGame['1'] = GAME_KEY_1;
_nativeToGame['2'] = GAME_KEY_2;
_nativeToGame['3'] = GAME_KEY_3;
_nativeToGame['4'] = GAME_KEY_4;
_nativeToGame['5'] = GAME_KEY_5;
_nativeToGame['6'] = GAME_KEY_6;
_nativeToGame['7'] = GAME_KEY_7;
_nativeToGame['8'] = GAME_KEY_8;
_nativeToGame['9'] = GAME_KEY_9;

// 建立反向映射
for (auto& pair : _nativeToGame) {
_gameToNative[pair.second] = pair.first;
}
}

void KeyCodeMapper::initAndroidMapping() {
// Android 按键码映射
_nativeToGame[AKEYCODE_DPAD_UP] = GAME_KEY_UP;
_nativeToGame[AKEYCODE_DPAD_DOWN] = GAME_KEY_DOWN;
_nativeToGame[AKEYCODE_DPAD_LEFT] = GAME_KEY_LEFT;
_nativeToGame[AKEYCODE_DPAD_RIGHT] = GAME_KEY_RIGHT;
_nativeToGame[AKEYCODE_DPAD_CENTER] = GAME_KEY_OK;
_nativeToGame[AKEYCODE_BACK] = GAME_KEY_BACK;
_nativeToGame[AKEYCODE_MENU] = GAME_KEY_MENU;
_nativeToGame[AKEYCODE_HOME] = GAME_KEY_HOME;
_nativeToGame[AKEYCODE_0] = GAME_KEY_0;
_nativeToGame[AKEYCODE_1] = GAME_KEY_1;
_nativeToGame[AKEYCODE_2] = GAME_KEY_2;
_nativeToGame[AKEYCODE_VOLUME_UP] = GAME_KEY_VOLUME_UP;
_nativeToGame[AKEYCODE_VOLUME_DOWN] = GAME_KEY_VOLUME_DOWN;

// 建立反向映射
for (auto& pair : _nativeToGame) {
_gameToNative[pair.second] = pair.first;
}
}

void KeyCodeMapper::initTVBoxMapping() {
// TV 盒子遥控器映射(与 Android 基本相同)
initAndroidMapping();

// 某些盒子可能有特殊按键,在这里添加
// _nativeToGame[KEYCODE_CHANNEL_UP] = GAME_KEY_UP;
}

GameKeyCode KeyCodeMapper::mapToGameKey(int nativeKeyCode) {
auto it = _nativeToGame.find(nativeKeyCode);
if (it != _nativeToGame.end()) {
return it->second;
}
return GAME_KEY_UNKNOWN;
}

int KeyCodeMapper::mapToNativeKey(GameKeyCode gameKey) {
auto it = _gameToNative.find(gameKey);
if (it != _gameToNative.end()) {
return it->second;
}
return -1;
}

const char* KeyCodeMapper::getKeyName(GameKeyCode gameKey) {
switch (gameKey) {
case GAME_KEY_UP: return "UP";
case GAME_KEY_DOWN: return "DOWN";
case GAME_KEY_LEFT: return "LEFT";
case GAME_KEY_RIGHT: return "RIGHT";
case GAME_KEY_OK: return "OK";
case GAME_KEY_BACK: return "BACK";
case GAME_KEY_MENU: return "MENU";
case GAME_KEY_HOME: return "HOME";
default: return "UNKNOWN";
}
}

业务层使用示例

按键监听层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// BaseLayer.h
#ifndef __BASE_LAYER_H__
#define __BASE_LAYER_H__

#include "cocos2d.h"
#include "GameKeyCode.h"

USING_NS_CC;

class BaseLayer : public CCLayer {
public:
virtual bool init();

// 实现按键委托方法
virtual void onKeyDown(int nativeKeyCode);
virtual void onKeyUp(int nativeKeyCode);

// 业务层按键处理方法(子类重写)
virtual void onGameKeyDown(GameKeyCode keyCode);
virtual void onGameKeyUp(GameKeyCode keyCode);

CREATE_FUNC(BaseLayer);
};

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// BaseLayer.cpp
#include "BaseLayer.h"
#include "KeyCodeMapper.h"

bool BaseLayer::init() {
if (!CCLayer::init()) {
return false;
}

// 启用按键监听
this->setKeypadEnabled(true);

return true;
}

void BaseLayer::onKeyDown(int nativeKeyCode) {
// 将平台按键码映射为游戏按键码
GameKeyCode gameKey = KeyCodeMapper::getInstance()->mapToGameKey(nativeKeyCode);

if (gameKey != GAME_KEY_UNKNOWN) {
CCLOG("Key Down: %s (native: %d)",
KeyCodeMapper::getInstance()->getKeyName(gameKey),
nativeKeyCode);

// 调用业务层处理方法
onGameKeyDown(gameKey);
}
}

void BaseLayer::onKeyUp(int nativeKeyCode) {
GameKeyCode gameKey = KeyCodeMapper::getInstance()->mapToGameKey(nativeKeyCode);

if (gameKey != GAME_KEY_UNKNOWN) {
onGameKeyUp(gameKey);
}
}

void BaseLayer::onGameKeyDown(GameKeyCode keyCode) {
// 基类默认实现:子类重写此方法处理按键
switch (keyCode) {
case GAME_KEY_BACK:
// 默认返回键行为:返回上一场景
CCDirector::sharedDirector()->popScene();
break;
default:
break;
}
}

void BaseLayer::onGameKeyUp(GameKeyCode keyCode) {
// 子类重写
}

具体业务层实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ShopLayer.h
#ifndef __SHOP_LAYER_H__
#define __SHOP_LAYER_H__

#include "BaseLayer.h"

class ShopLayer : public BaseLayer {
public:
virtual bool init();

// 重写按键处理方法
virtual void onGameKeyDown(GameKeyCode keyCode);

// 菜单导航方法
void selectNextItem();
void selectPrevItem();
void confirmSelection();
void backToMainMenu();

CREATE_FUNC(ShopLayer);
};

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// ShopLayer.cpp
#include "ShopLayer.h"

bool ShopLayer::init() {
if (!BaseLayer::init()) {
return false;
}

// 初始化商店界面
initShopUI();

return true;
}

void ShopLayer::onGameKeyDown(GameKeyCode keyCode) {
// 处理方向键导航
switch (keyCode) {
case GAME_KEY_UP:
selectPrevItem();
break;

case GAME_KEY_DOWN:
selectNextItem();
break;

case GAME_KEY_LEFT:
// 切换商品分类
switchCategory(-1);
break;

case GAME_KEY_RIGHT:
// 切换商品分类
switchCategory(1);
break;

case GAME_KEY_OK:
case GAME_KEY_ENTER:
confirmSelection();
break;

case GAME_KEY_BACK:
backToMainMenu();
break;

case GAME_KEY_1:
case GAME_KEY_2:
case GAME_KEY_3:
// 快捷键直接购买
quickBuy(keyCode - GAME_KEY_1);
break;

default:
// 未处理的按键传递给基类
BaseLayer::onGameKeyDown(keyCode);
break;
}
}

void ShopLayer::selectNextItem() {
_selectedIndex++;
if (_selectedIndex >= _itemCount) {
_selectedIndex = 0;
}
updateSelection();
}

void ShopLayer::selectPrevItem() {
_selectedIndex--;
if (_selectedIndex < 0) {
_selectedIndex = _itemCount - 1;
}
updateSelection();
}

void ShopLayer::confirmSelection() {
// 播放确认音效
AudioManager::playEffect("confirm.mp3");

// 执行购买逻辑
buyItem(_selectedIndex);
}

void ShopLayer::backToMainMenu() {
// 播放返回音效
AudioManager::playEffect("back.mp3");

// 返回主菜单
CCDirector::sharedDirector()->replaceScene(
CCTransitionFade::create(0.5f, MainMenuScene::scene())
);
}

各平台键值参考表

Win32 虚拟键码

按键 键值 按键 键值
VK_UP 38 VK_DOWN 40
VK_LEFT 37 VK_RIGHT 39
VK_RETURN 13 VK_ESCAPE 27
VK_SPACE 32 VK_TAB 9
VK_F1 112 VK_F2 113
‘0’-‘9’ 48-57 ‘A’-‘Z’ 65-90

Android 按键码

按键 键值 按键 键值
KEYCODE_DPAD_UP 19 KEYCODE_DPAD_DOWN 20
KEYCODE_DPAD_LEFT 21 KEYCODE_DPAD_RIGHT 22
KEYCODE_DPAD_CENTER 23 KEYCODE_ENTER 66
KEYCODE_BACK 4 KEYCODE_MENU 82
KEYCODE_HOME 3 KEYCODE_VOLUME_UP 24
KEYCODE_0 7 KEYCODE_1 8

遥控器按键码(基于 Android)

按键 键值 说明
KEYCODE_DPAD_UP 19 方向上
KEYCODE_DPAD_DOWN 20 方向下
KEYCODE_DPAD_LEFT 21 方向左
KEYCODE_DPAD_RIGHT 22 方向右
KEYCODE_DPAD_CENTER 23 确定/OK
KEYCODE_BACK 4 返回
KEYCODE_MENU 82 菜单
KEYCODE_HOME 3 主页
KEYCODE_VOLUME_UP 24 音量+
KEYCODE_VOLUME_DOWN 25 音量-

小结

Cocos2d-x 多平台按键映射的核心思路:

  1. 统一抽象:定义平台无关的游戏按键枚举(GameKeyCode)
  2. 映射层:建立平台按键码到游戏按键码的双向映射
  3. 分发器改造:扩展 CCKeypadDispatcher 支持所有按键类型
  4. 平台适配:在 Win32/Android 等平台层接入所有按键事件
  5. 业务隔离:业务层只处理统一的游戏按键码,不用关心平台差异

这套方案我在 TV 游戏项目中用过,一套代码同时支持 Win32 调试、Android 手机和遥控器操作。