nes 游戏引擎 原理分析

来源:互联网 发布:ally mac tyana磁力 编辑:程序博客网 时间:2024/05/20 10:11

apk 下载位置:

http://download.csdn.net/detail/a332324956/7544819

nes 模拟器源码:联系本人QQ:332324956

Android 主入口MainActivity 菜单为R.menu.main ,前两个Rom Gripper 和刷

新在R.menu.file_chooser 里面,文件为FileChooser.java,
这个文件为选择T 卡nes 游戏。
点击菜单设置进入
case R.id.menu_settings:
startActivity(new Intent(this, EmulatorSettings.class));
return true;
这个就是所有设置界面了。
当选择了一个nes 游戏后,进入代码为
protected void onFileSelected(Uri uri) {
// remember the last file
settings.edit().
putString("lastGame", uri.getPath()).commit();
Intent intent = new Intent(Intent.ACTION_VIEW, uri,
this, EmulatorActivity.class);
if (!creatingShortcut)
startActivity(intent);
else {
emulatorIntent = intent;
showDialog(DIALOG_SHORTCUT);
}
}
进入到EmulatorActivity 里面了,这个就是模拟的主要界面了。
创建模拟实例
emulator = Emulator.createInstance(getApplicationContext(),
getEmulatorEngine(prefs));
看下这里的实例实现:
System.loadLibrary("emu");这里加载了动态库
继续查看:
emulator = new Emulator(libDir);
initialize(libDir, Integer.parseInt(Build.VERSION.SDK));
thread = new Thread() {
public void run() {
nativeRun();
}
};
thread.start();
我们来看下private native boolean initialize(String libDir, int sdk);
这个函数了
这个可以看到是native 接口了,所以就是c++实现在so 里面。
下来我们看下c++和java 层如何关联的:
JNI_OnLoad 这个函数为java 加载动态库时触发的一个入口函数,可以看到里面有:
if (registerNatives(env) != JNI_TRUE) {
LOGE("ERROR: registerNatives failed");
goto bail;
}
这里便是加载对接java 接口。
static int registerNatives(JNIEnv* env)
{
REGISTER_NATIVE(env, Emulator);
REGISTER_NATIVE(env, Cheats);
return JNI_TRUE;
}
我们来看下
REGISTER_NATIVE(env, Emulator);
为宏:
#define REGISTER_NATIVE(env, module) { \
extern int register_##module(JNIEnv *); \
if (register_##module(env) < 0) \
return JNI_FALSE; \
}
翻译过来为:
{
extern int register_Emulator(JNIEnv *);
if (register_Emulator(env) < 0)
return JNI_FALSE;
}
所以我们去看下register_Emulator(env)
int register_Emulator(JNIEnv *env)
{
static const JNINativeMethod methods[] = {
{ "setFrameUpdateListener",
"(Lcom/lxm/Emulator$FrameUpdateListener;)V",
(void *) Emulator_setFrameUpdateListener },
{ "setSurface", "(Landroid/view/SurfaceHolder;)V",
(void *) Emulator_setSurface },
{ "setSurfaceRegion", "(IIII)V", (void *)
Emulator_setSurfaceRegion },
{ "setKeyStates", "(I)V", (void *) Emulator_setKeyStates },
{ "processTrackball", "(IIII)V", (void *)
Emulator_processTrackball },
{ "fireLightGun", "(II)V", (void *) Emulator_fireLightGun },
{ "setOption", "(Ljava/lang/String;Ljava/lang/String;)V",
(void *) Emulator_setOption },
{ "getOption", "(Ljava/lang/String;)I", (void *)
Emulator_getOption },
{ "getVideoWidth", "()I", (void *) Emulator_getVideoWidth },
{ "getVideoHeight", "()I", (void *) Emulator_getVideoHeight },
{ "loadEngine", "(Ljava/lang/String;Ljava/lang/String;)Z",
(void *) Emulator_loadEngine },
{ "initialize", "(Ljava/lang/String;I)Z",
(void *) Emulator_initialize },
{ "nativeRun", "()V", (void *) Emulator_run },
{ "nativeLoadROM", "(Ljava/lang/String;)Z", (void *)
Emulator_loadROM },
{ "nativeUnloadROM", "()V", (void *) Emulator_unloadROM },
{ "reset", "()V", (void *) Emulator_reset },
{ "power", "()V", (void *) Emulator_power },
{ "pause", "()V", (void *) Emulator_pause },
{ "resume", "()V", (void *) Emulator_resume },
{ "getScreenshot", "(Ljava/nio/Buffer;)V",
(void *) Emulator_getScreenshot },
{ "saveState", "(Ljava/lang/String;)Z", (void *)
Emulator_saveState },
{ "loadState", "(Ljava/lang/String;)Z", (void *)
Emulator_loadState },
};
return jniRegisterNativeMethods(env, "com/lxm/Emulator",
methods, NELEM(methods));
}
这里比如这个
{ "initialize", "(Ljava/lang/String;I)Z",
(void *) Emulator_initialize },
上面我们记得
private native boolean initialize(String libDir, int sdk); 在java
里面
{ "initialize", "(Ljava/lang/String;I)Z",
(void *) Emulator_initialize },
这里的"initialize" 就是java 上面的initialize 函数。
(Ljava/lang/String;I)Z 为参数和返回值。Emulator_initialize 为c++里面的
对应函数,然后通过
return jniRegisterNativeMethods(env, "com/lxm/Emulator",
methods, NELEM(methods));
将这些c++的函数和java 层com/lxm/Emulator 这个包下面的函数对应,如此则c++
和java 关联住了。
C++调用java 的方法举例为:
jclass clazz = env->FindClass(
"com/lxm/Emulator$FrameUpdateListener");
midOnFrameUpdate = env->GetMethodID(clazz, "onFrameUpdate",
"(I)I");
这里为找到java 的类com/lxm/Emulator$FrameUpdateListener , 去找里面的
onFrameUpdate 方法,接口参数为整数,返回整数,可以找到在:
public int onFrameUpdate(int keys)
throws IOException, InterruptedException {
final int remote = netPlayService.sendFrameUpdate(keys);
if (netPlayService.isServer())
return makeKeyStates(keys, remote);
else
return makeKeyStates(remote, keys);
}
所有配置都在:
final String[] prefKeys = {
"fullScreenMode",
"flipScreen",
"fastForwardSpeed",
"frameSkipMode",
"maxFrameSkips",
"refreshRate",
"soundEnabled",
"soundVolume",
"accurateRendering",
"secondController",
"enableTrackball",
"trackballSensitivity",
"enableSensor",
"sensorSensitivity",
"enableVKeypad",
"scalingMode",
"aspectRatio",
"enableCheats",
"orientation",
"useInputMethod",
"quickLoad",
"quickSave",
"fastForward",
"screenshot",
};
这里我们搜索aspectRatio 来修改下默认屏幕比率
<ListPreference android:key="aspectRatio"
android:title="@string/aspect_ratio"
android:entries="@array/aspect_ratio_entries"
android:entryValues="@array/aspect_ratio_entryvalues"
android:defaultValue="1.7778"/>
这里又一个默认值,是打开设置项操作的,需要修改下。
else if ("aspectRatio".equals(key)) {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
float ratio = Float.parseFloat(prefs.getString(key,
"1.7778"));
if (ratio != 0) {
float dpiRatio = metrics.xdpi / metrics.ydpi;
// some models seem to report wrong dpi
if (dpiRatio < 1.6667f && dpiRatio > 0.6f)
ratio *= dpiRatio;
}
emulatorView.setAspectRatio(ratio);
}
因为第一次进入游戏,没有设置,所以会读取后面的默认值,此处也要修改,这样一个默认
值则修改好了,其余的设置一样处理。
包名要修改,搜索所有的lxm,改为你需要的就行了。
原理基本为:
Activity 使用里面有一个全屏SurfaceView,来实现游戏绘制。加载的方式都在
Emulator.java 里面
这里面private Emulator(String libDir) {
initialize(libDir, Integer.parseInt(Build.VERSION.SDK));
thread = new Thread() {
public void run() {
nativeRun();
}
};
thread.start();
}
这里使用一个线程,而此处的nativeRun 是个死循环,只有退出条件成立才退出,否则一
直进行循环,进行绘制模拟画面。
然后我们使用onGameKeyChanged 来调用c++方法,驱动游戏前进。
模拟按键:VirtualKeypad 里面
按键注册在Keyboard。Java 里面
虚拟按键的刷屏在:
public void onFrameDrawn(Canvas canvas) {
if (vkeypad != null)
vkeypad.draw(canvas);
}
里面调用过程为:
是EmuMedia.java 里面使用:
static void bitBlt(int[] image, boolean flip) {
// Fill background
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.BLACK);
if (flip)
canvas.rotate(180, canvas.getWidth() / 2,
canvas.getHeight() / 2);
canvas.drawBitmap(image, 0, region.width(), region.left,
region.top,
region.width(), region.height(), false, null);
if (onFrameDrawnListener != null)
onFrameDrawnListener.onFrameDrawn(canvas);
holder.unlockCanvasAndPost(canvas);
}
此函数是在:
midBitBlt = env->GetStaticMethodID(jPeerClass,
"bitBlt", "([IZ)V");
继续跟进:
void EmuMediaImpl::unlockSurface(JNIEnv *env)
{
jsize size = env->GetArrayLength(jVideoBuffer);
jint *image = env->GetIntArrayElements(jVideoBuffer, 0);
for (int i = 0; i < size; i++) {
unsigned short pix = screen[i];
image[i] = (pix & 0xf800) << 8 |
(pix & 0x07e0) << 5 |
(pix & 0x1f) << 3;
}
env->ReleaseIntArrayElements(jVideoBuffer, image, 0);
env->CallStaticVoidMethod(jPeerClass, midBitBlt,
jVideoBuffer, flipScreen);
}
继续查找:
inline void videoUpdate(uint8 *xbuf)
{
EmuEngine::Surface surface;
if (callbacks->lockSurface(&surface)) {
engine->renderFrame(surface);
callbacks->unlockSurface(&surface);
}
}
extern "C"
void FCEUD_Update(uint8 *xbuf, int16 *Buffer, int Count)
{
if (xbuf != NULL)
videoUpdate(xbuf);
if (Count > 0)
callbacks->playAudio(Buffer, Count * 2);
}
是由这个函数调用:
FCEUI_Emulate098
继续
runFrame
runEmulator
Emulator_run
到这个函数则完成了使命:
{ "nativeRun", "()V", (void *) Emulator_run },因为这个函数对应java 的
nativeRun 函数,它就是之前说的java 上层跑的那个线程。
private Emulator(String libDir) {
initialize(libDir, Integer.parseInt(Build.VERSION.SDK));
thread = new Thread() {
public void run() {
nativeRun();
}
};
thread.start();
}
如此流程则一目了然了。
主界面的设置菜单为:
Emulator.xml 代码位置为:EmulatorActivity.java 的onCreateOptionsMenu,
响应函数在onOptionsItemSelected 里面。
-------------------------------------
补充一个:
这套代码要编译,是需要配置NDK 环境的。
配置NDK 环境只需要下载官方的NDK 包,放置在D 盘根目录,解压即可。
环境变量配置一个
NDK 为D:\android-ndk-r8d(这里的D:\android-ndk-r8d 是你自己的解压目录,
此目录可以看到ndk-build.cmd 则是正确的)
然后将源码导入eclipse.exe ,进入属性设置,选择builders,点击new,
Location 填入D:\android-ndk-r8d\ndk-build.cmd ,(需要对应自己的这个文件
路径)
Working Directory 写入${workspace_loc:/Nesoid/jni} ,这里的Nesoid 为
eclipse.exe 创建的工程名。
然后选择上面的Environment,选择new,加一个name 写为:NDK_PROJECT_PATH
Value 写为..

即可编译了。

-------

apk下载位置为在我的共享资源里面。



0 0