The app works fine if I wait a bit after clicking play, but if I want click on anything clickable right away, the app crashes.
Here is the error message:
E/InputEventReceiver: Exception dispatching input event.
D/AndroidRuntime: Shutting down VM E/AndroidRuntime: FATAL EXCEPTION:
main
Process: com.example.combatcats, PID: 13769
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Boolean.booleanValue()' on a null object reference
at com.example.combatcats.Joystick.getIsPressed(Joystick.java:86)
at com.example.combatcats.GameView.onTouchEvent(GameView.java:187)
at android.view.View.dispatchTouchEvent(View.java:14540)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:502)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1890)
at android.app.Activity.dispatchTouchEvent(Activity.java:4196)
at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:460)
at android.view.View.dispatchPointerEvent(View.java:14799)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6347)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6148)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5626)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5683)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5649)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5814)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5657)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5871)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5630)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5683)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5649)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5657)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5630)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:8562)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:8513)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:8482)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:8685)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:259)
at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native
Method)
at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:239)
at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:8642)
at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:8771)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1037)
at android.view.Choreographer.doCallbacks(Choreographer.java:845)
at android.view.Choreographer.doFrame(Choreographer.java:772)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method) E/AndroidRuntime: at
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Here is my GameView
class:
package com.example.combatcats;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceView;
public class GameView extends SurfaceView implements Runnable {
private final Joystick joystick;
private Thread thread;
private boolean isPlaying;
private int screenX, screenY;
public static float screenRatioX, screenRatioY;
private Paint paint;
private Cat cat;
private Background background1, background2;
private int InAirCounter;
private final MainMenuButton mainMenuButton;
private GameScreen activity;
public GameView(GameScreen activity, int screenX, int screenY) {
super(activity);
this.activity = activity;
this.screenX = screenX;
this.screenY = screenY;
screenRatioX = 1920f / screenX;
screenRatioY = 1080f / screenY;
background1 = new Background(screenX, screenY, getResources());
background2 = new Background(screenX, screenY, getResources());
cat = new Cat(screenY, getResources());
background2.x = screenX;
paint = new Paint();
joystick = new Joystick(190,900,90,55);
mainMenuButton = new MainMenuButton(60,60,250,158);
}
@Override
public void run() {
while (isPlaying) {
update ();
draw ();
sleep ();
}
}
private void update () {
background1.x -= 10 * screenRatioX;
background2.x -= 10 * screenRatioX;
InAirCounter -= 1;
if (background1.x background1.background.getWidth() < 0) {
background1.x = screenX;
}
if (background2.x background2.background.getWidth() < 0) {
background2.x = screenX;
}
if (cat.y == (screenY - cat.height) && cat.isJumping) {
InAirCounter = 7;
}
if (InAirCounter > 0)
cat.y -= 30 * screenRatioY;
else
cat.y = 30 * screenRatioY;
if (cat.y < 0)
cat.y = 0;
if (cat.y > screenY - cat.height)
cat.y = screenY - cat.height;
if (cat.x < 0)
cat.x = 0;
if (cat.x > screenX - cat.width)
cat.x = screenX - cat.width;
//Log.d("","var_name = " var);
cat.update(joystick);
joystick.update();
}
private void draw () {
if (getHolder().getSurface().isValid()) {
Canvas canvas = getHolder().lockCanvas();
canvas.drawBitmap(background1.background, background1.x, background1.y, paint);
canvas.drawBitmap(background2.background, background2.x, background2.y, paint);
canvas.drawBitmap(cat.getBlink(), cat.x, cat.y, paint);
joystick.draw(canvas);
mainMenuButton.draw(canvas);
getHolder().unlockCanvasAndPost(canvas);
}
}
private void sleep () {
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void resume () {
isPlaying = true;
thread = new Thread(this);
thread.start();
}
public void pause () {
try {
isPlaying = false;
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (event.getX() > screenX / 2 && event.getY() > screenY / 2) {
cat.isJumping = true;
}
if(joystick.isPressed((double) event.getX(), (double) event.getY())){
joystick.setIsPressed(true);
}
if(event.getX() >= mainMenuButton.getValue(1) &&
event.getX() <= mainMenuButton.getValue(3) &&
event.getY() >= mainMenuButton.getValue(2) &&
event.getY() <= mainMenuButton.getValue(4)) {
isPlaying = false;
activity.startActivity(new Intent(activity, MainActivity.class));
activity.finish();
}
break;
case MotionEvent.ACTION_MOVE:
if(joystick.getIsPressed()) {
joystick.setActuator((double) event.getX(), (double) event.getY());
}
break;
case MotionEvent.ACTION_UP:
joystick.setIsPressed(false);
joystick.resetActuator();
cat.isJumping = false;
break;
}
return true;
}
}
CodePudding user response:
The call stack
virtual method 'boolean java.lang.Boolean.booleanValue()' on a null object reference at com.example.combatcats.Joystick.getIsPressed(Joystick.java:86) at com.example.combatcats.GameView.onTouchEvent(GameView.java:187) at
tells you that in the method Joystick.getIsPressed
you are trying to convert a Boolean
object to a boolean
by invoking booleanValue()
on it, but that object is null
.
You didn't show the code for Joystick
but I suspect it looks something like this:
class Joystick {
Boolean pressed;
void setIsPressed(boolean pressed){
this.pressed = pressed;
}
boolean isPressed() {
return pressed.getBooleanValue();
}
The important part is that pressed
doesn't get initialised on construction.
If we inspect your GameView.onTouchEvent
method you'll note that setIsPressed
is invoked on ACTION_DOWN
and ACTION_UP
, while isPressed
is invoked on ACTION_MOVE
.
When you wait a little until the UI is fully initialized ACTION_MOVE
will probably always happen after ACTION_DOWN
and therefore everything is fine.
But when spam clicking it might happen that the ACTION_DOWN event doesn't get forwarded to your view, because it is not ready yet. But
ACTION_MOVEis. Now this is the first event you process,
pressed` is not yet initialized and you get the NPE you are seeing.
The solution is to either initialize pressed
on construction of Joystick
or even better: make it a primitive boolean
in the first place.