Wednesday, March 27, 2013

Input processing with AndEngine

I'd like to share the method I am using to process input events when working with AndEngine. In many situations, touch events need to affect the entities on the scene - however, AndEngine renders on the UI thread. Updating any entities on the UI thread (where input events are received) are largely disliked by AndEngine, and even more so by box2d. The problem may seem trivial to some, but I have seen multiple questions on this topic in different forums.

Here's the method I am using, and it works quite well for me. I'd assume that our entities (sprites) usually override onAreaTouched, and the events will be processed where the game logic resides (the Activity or the Scene).

Let's define a class for our input events (borrowing from the widely used Command design pattern):

public class InputEvent {
   
    public Sprite mSource;
    public TouchEvent mTouchEvent;
   
    public InputEvent(Sprite pSource, TouchEvent pTouchEvent) {
        mSource = pSource;
        mTouchEvent = pTouchEvent;
    }
   
    public Source getSource() {
        return mSource;
    }
   
    public TouchEvent getTouchEvent() {
        return mTouchEvent;
    }
}


These objects will be communicated from the source entities (sprites) to the processing instance. I tend to use the Scene for game logic, but many prefer to use the Activity. Also, keep in mind that we may want to process several input events every time.

Let us define the input events queue in the processing class:

private Queue<InputEvent> mInputQueue = new LinkedBlockingQueue<InputEvent>();


And a publicly accessible method for posting input events to that queue:

public void queueInputEvent(InputEvent pInputEvent) {
    mInputQueue.add(pInputEvent);
}


Please note that I opted to use a blocking queue - this implementation should be sufficient in most cases.

This method will be invoked from the sprite's touch event implementation:

     @Override
    public boolean onAreaTouched(final TouchEvent pSceneTouchEvent, final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
        mScene.queueInputEvent(new InputEvent(this, pSceneTouchEvent));
        return true;
    }

Where mScene is the parent scene which i save for each sprite. Other methods can be used - e.g. calling getParent() etc.

Now, lets implement the actual event processor:

private IUpdateHandler mInputProcessor = new IUpdateHandler() {
       
    private List<Marble> mSelectedSequence = new ArrayList<Marble>();

    @Override
    public void onUpdate(float pSecondsElapsed) {
        while(!mInputQueue.isEmpty()) {
            InputEvent evt = mInputQueue.remove();
            switch(evt.getTouchEvent().getAction()) {
            case TouchEvent.ACTION_DOWN:

                //
                // Process your ACTION_DOWN events here
                //                 
                break;
            case TouchEvent.ACTION_UP:

                //
                // Process your ACTION_UP events here
                //
                break;

            //
            // and process all other event type here
            //
            }
        }
    }
}


And lastly, lets register this update handler with the scene (usually in any of the initialization methods):

registerUpdateHandler(mInputProcessor);

And that's all - now all our input events will be processed on the update thread without crashing the application.

I hope this post helped someone!

No comments:

Post a Comment