创建主游戏线程并让游戏运行

时间:2012-03-05 06:31:53

标签: android multithreading android-activity surfaceview

我的简单游戏遇到了一些麻烦,特别是了解线程部分的工作原理。我知道有一个主要的游戏循环,负责更新和绘制一切。我认为我在创建不同的状态,更新和绘图以及维护体面的FPS方面做得不错。

但是,我不太明白,为什么我的线程不起作用。在添加所有状态和暂停/启动/等之前,我没有遇到让它运行和完美工作的问题。添加它们之后,我在线程创建时得到NullPointerException。下面是我的活动类和surfaceview类。

概述游戏:在屏幕(右侧)生成白色圆圈,它们以恒定速度向左移动。点按它们时,会使用粒子创建爆炸。如果它们击中左侧墙壁,它们就会消失,您的健康状况会减去1.对于您点击的每个圆圈,您的总数会上升1。

非常感谢任何帮助。我只学习了几天的机器人,之前我的java很有能力。

所有进口都在那里(只是没有C / P)。

我也为缩进道歉,不知道如何在这个网站上修复它。

错误来自以下活动类中的此行: mPummelThread = mPummelView.getThread();

更新:尝试了以下两条建议,但仍然收到完全相同的错误。

提供NullPointerException

public class PummelActivity extends Activity {
/** Called when the activity is first created. */

private static final String TAG = PummelActivity.class.getSimpleName();

private static final int MENU_START = 0;
private static final int MENU_PAUSE = 1;
private static final int MENU_RESUME = 2;
private static final int MENU_STOP = 3;

private PummelView mPummelView;
private PummelThread mPummelThread;

/**@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);


    menu.add(0, MENU_START, 0, R.string.menu_start);
    menu.add(0, MENU_PAUSE, 0, R.string.menu_pause);
    menu.add(0, MENU_RESUME, 0, R.string.menu_resume);
    menu.add(0, MENU_STOP, 0, R.string.menu_stop);

}*/

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);

    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    mPummelThread = mPummelView.getThread();
    mPummelThread.setState(PummelThread.STATE_READY);

    setContentView(new PummelView(this));
    Log.d(TAG, "View added");
}

@Override
protected void onPause() {
    super.onPause();
    mPummelView.getThread().pause();
}

@Override
protected void onStop() {
    Log.d(TAG, "Stopping...");
    super.onStop();
}
}

^活动类。

public class PummelView extends SurfaceView implements SurfaceHolder.Callback {

private static final String TAG = PummelView.class.getSimpleName();

private static final int EXPLOSION_SIZE = 100;

private PummelThread thread;
private ArrayList<Pummel> pummels = new ArrayList<Pummel>();

private ArrayList<Explosion> explosion = new ArrayList<Explosion>();
private int totalScore;
private String mScore = "Total Score = 0";
private int totalHp = 100;
private String mHP = "HP: 100";

public PummelView(Context context) {
    super(context);
    SurfaceHolder holder = getHolder();
    holder.addCallback(this);
    for (int i = 0; i < 3; i++) {
        pummels.add(i, new Pummel(BitmapFactory.decodeResource(getResources(), R.drawable.bullet), 850, (int) (Math.random() * 200) + 80));
    }
    thread = new PummelThread(getHolder(), this);
    setFocusable(true);
}

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
    if (!hasWindowFocus) {
        thread.pause();
    }
}

public PummelThread getThread() {
    return thread;
}

public void setScore(String score) {
    mScore = score;
}

public void setHP(String hp) {
    mHP = hp;
}

public void displayHp(Canvas canvas, String hp) {
    if (canvas != null) {
        Paint paint = new Paint();
        paint.setARGB(255, 255, 255, 255);
        canvas.drawText(hp, 10, 20, paint);
    }
}

public void displayScore(Canvas canvas, String score) {
    if (canvas != null) {
        Paint paint = new Paint();
        paint.setARGB(255, 255, 255, 255);
        canvas.drawText(score, this.getWidth() / 2 - 50, 20, paint);
    }
}

private void render(Canvas canvas) {
    canvas.drawColor(Color.BLACK);
    for (int i = 0; i < pummels.size(); i++) {
        pummels.get(i).draw(canvas);
    }

    if (explosion != null) {
        for (int j = 0; j < explosion.size(); j++) {
        explosion.get(j).draw(canvas);
    }
    }
    displayHp(canvas, mHP);
    displayScore(canvas, mScore);
}

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {       
}

public void surfaceCreated(SurfaceHolder holder) {
    thread.setRunning(true);
    thread.start();
}

public void surfaceDestroyed(SurfaceHolder holder) {
    boolean retry = true;
    thread.setRunning(false);
    while(retry) {
        try {
            thread.join();
            retry = false;
        } catch (InterruptedException e) {

        }
    }
}

class PummelThread extends Thread {     
    public static final int STATE_LOSE = 1;
    public static final int STATE_PAUSE = 2;
    public static final int STATE_READY = 3;
    public static final int STATE_RUNNING = 4;

    private final static int MAX_FPS = 50;
    private final static int MAX_FRAME_SKIPS = 5;
    private final static int FRAME_PERIOD = 1000/ MAX_FPS;

    private boolean mRunning;
    private SurfaceHolder mSurfaceHolder;
    private PummelView mGamePanel;
    private Handler mHandler;

    private int mMode;
    private long beginTime;
    private long timeDiff;
    private int sleepTime = 0;
    private int framesSkipped;

    public PummelThread(SurfaceHolder surfaceHolder, PummelView gamePanel) {
        super();
        mSurfaceHolder = surfaceHolder;
        mGamePanel = gamePanel;
    }

    public void setRunning(boolean running) {
        mRunning = running;
    }

    public void doStart() {
        synchronized (mSurfaceHolder) {
            setState(STATE_RUNNING);
        }
    }

    public void pause() {
        synchronized (mSurfaceHolder) {
            if (mMode == STATE_RUNNING) {
                setState(STATE_PAUSE);
            }
        }
    }

    public void unpause() {
        // Move the real time clock up to now
        synchronized (mSurfaceHolder) {
            timeDiff = System.currentTimeMillis() + 100;
        }
        setState(STATE_RUNNING);
    }

    public void setState(int mode) {
        synchronized (mSurfaceHolder) {
            setState(mode, null);
        }
    }

    public void setState(int mode, CharSequence message) {
        /*
         * This method optionally can cause a text message to be displayed
         * to the user when the mode changes. Since the View that actually
         * renders that text is part of the main View hierarchy and not
         * owned by this thread, we can't touch the state of that View.
         * Instead we use a Message + Handler to relay commands to the main
         * thread, which updates the user-text View.
         */
        synchronized (mSurfaceHolder) {
            mMode = mode;

            if (mMode == STATE_RUNNING) {
                Message msg = mHandler.obtainMessage();
                Bundle b = new Bundle();
                b.putString("text", "");
                b.putInt("viz", View.INVISIBLE);
                msg.setData(b);
                mHandler.sendMessage(msg);
            } else {
                CharSequence str = "";
                if (mMode == STATE_READY) {
                    str = (CharSequence) getTag(R.string.mode_ready);
                }
                else if (mMode == STATE_PAUSE) {
                    str = (CharSequence) getTag(R.string.mode_pause);
                }
                else if (mMode == STATE_LOSE) {
                    str = (CharSequence) getTag(R.string.mode_lose);
                }

                if (message != null) {
                    str = message + "\n" + str;
                }

                Message msg = mHandler.obtainMessage();
                Bundle b = new Bundle();
                b.putString("text", str.toString());
                b.putInt("viz", View.VISIBLE);
                msg.setData(b);
                mHandler.sendMessage(msg);
            }
        }
    }

    public boolean screenTouch(MotionEvent event) {
        boolean okStart = false;
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            okStart = true;
        }

        if (okStart && (mMode == STATE_READY || mMode == STATE_LOSE)) {
            doStart();
            return true;
        } else if (okStart && mMode == STATE_PAUSE) {
            unpause();
            return true;
        } else if (mMode == STATE_RUNNING) {
            onTouchEvent(event);
        }
        return false;
    }

    public boolean onTouchEvent(MotionEvent event) {
        if (mMode == STATE_RUNNING) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                for (int i = 0; i < pummels.size(); i++) {
                    pummels.get(i).handleActionDown((int) event.getX(), (int) event.getY());
                    if (pummels.get(i).isTouched()) {
                        totalScore++;
                        pummels.remove(i);
                        explosion.add(new Explosion(EXPLOSION_SIZE, (int) event.getX(), (int) event.getY()));
                        pummels.add(i, new Pummel(BitmapFactory.decodeResource(getResources(), R.drawable.bullet), getWidth() * 2, (int) (Math.random() * 200) + 80));
                        pummels.add(i, new Pummel(BitmapFactory.decodeResource(getResources(), R.drawable.bullet), getWidth() * 2, (int) (Math.random() * 200) + 80));
                    }
                }
            }
        }
        setScore("Total Score: " + String.valueOf(totalScore));
        return true;
    }

    @Override
    public void run() {
        Log.d(TAG, "Starting game loop");

        while(mRunning) {
            Canvas c = null;
            try {
                c = mSurfaceHolder.lockCanvas();
                synchronized (mSurfaceHolder) {
                    beginTime = System.currentTimeMillis();
                    framesSkipped = 0;
                    updatePhysics();
                    mGamePanel.render(c);
                    timeDiff = System.currentTimeMillis() - beginTime;
                    sleepTime = (int)(FRAME_PERIOD - timeDiff);

                    if (sleepTime > 0) {
                        try {
                            Thread.sleep(sleepTime);
                        } catch (InterruptedException e) {
                        }
                    }
                    while (sleepTime <= 0 && framesSkipped < MAX_FRAME_SKIPS) {
                        updatePhysics();
                        sleepTime += FRAME_PERIOD;
                        framesSkipped++;
                    }

                    if (framesSkipped > 0) {
                        Log.d(TAG, "Skipped:" + framesSkipped);
                    }
                }
            } finally {
                if (c != null) {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }

    public void updatePhysics() {
        int pWidth = BitmapFactory.decodeResource(getResources(), R.drawable.bullet).getWidth();
        for (int i = 0; i < pummels.size(); i++) {
            //checks collision with left side wall
            //changes direction if it collides
            pummels.get(i).update();
            if (pummels.get(i).getSpeed().getxDirection() == Speed.DIRECTION_LEFT && pummels.get(i).getX() - pWidth / 2 <= 0) {
                totalHp--;
                setHP("HP: " + String.valueOf(totalHp));
                if (totalHp == 0) {
                    setState(STATE_LOSE);
                    break;
                }
                pummels.remove(i);
            }
            if (pummels.size() == 0) {
                for (int j = 0; j < 10; j++) {
                    pummels.add(j, new Pummel(BitmapFactory.decodeResource(getResources(), R.drawable.bullet), 850, (int) (Math.random() * 200) + 80));
                }
            }
            if (explosion != null) {
                for (int j = 0; j < explosion.size(); j++) {
                    explosion.get(j).update(getHolder().getSurfaceFrame());
                }
            }
        }
    }
}
}

^ SurfaceView类

03-05 11:49:10.283: E/AndroidRuntime(281): FATAL EXCEPTION: main
03-05 11:49:10.283: E/AndroidRuntime(281): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.joel.pummel/com.joel.pummel.PummelActivity}: java.lang.NullPointerException
03-05 11:49:10.283: E/AndroidRuntime(281):  at  android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2663)
03-05 11:49:10.283: E/AndroidRuntime(281):  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
03-05 11:49:10.283: E/AndroidRuntime(281):  at android.app.ActivityThread.access$2300(ActivityThread.java:125)
03-05 11:49:10.283: E/AndroidRuntime(281):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
03-05 11:49:10.283: E/AndroidRuntime(281):  at android.os.Handler.dispatchMessage(Handler.java:99)
03-05 11:49:10.283: E/AndroidRuntime(281):  at android.os.Looper.loop(Looper.java:123)
03-05 11:49:10.283: E/AndroidRuntime(281):  at android.app.ActivityThread.main(ActivityThread.java:4627)
03-05 11:49:10.283: E/AndroidRuntime(281):  at java.lang.reflect.Method.invokeNative(Native Method)
03-05 11:49:10.283: E/AndroidRuntime(281):  at java.lang.reflect.Method.invoke(Method.java:521)
03-05 11:49:10.283: E/AndroidRuntime(281):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
03-05 11:49:10.283: E/AndroidRuntime(281):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
03-05 11:49:10.283: E/AndroidRuntime(281):  at dalvik.system.NativeStart.main(Native Method)
03-05 11:49:10.283: E/AndroidRuntime(281): Caused by: java.lang.NullPointerException
03-05 11:49:10.283: E/AndroidRuntime(281):  at com.joel.pummel.PummelActivity.onCreate(PummelActivity.java:46)
03-05 11:49:10.283: E/AndroidRuntime(281):  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
03-05 11:49:10.283: E/AndroidRuntime(281):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)

3 个答案:

答案 0 :(得分:0)

mPummelView在您调用mPummelView.getThread()时为空。它直到您的(稍后)setContentView()。

才存在

答案 1 :(得分:0)

您忘记初始化mPummelView:

mPummelView = new PummelView(this);

mPummelThread = mPummelView.getThread();
mPummelThread.setState(PummelThread.STATE_READY);

setContentView(mPummelView);

答案 2 :(得分:0)

在此行之后调用setContentView(new PummelView(this));并查看其是否有效

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                     WindowManager.LayoutParams.FLAG_FULLSCREEN);