Search code examples
javaandroidcocos2d-android

Is this typically how Java interfaces are used to set up event handlers, and are there hidden drawbacks to this approach?


Hey all, I'm still relatively new to Java, and looking for a sanity check.

I've been studying this Java port of Cocos2D and noticed that the CCLayer class has built-in hooks to the Android native touch events. That's great, but what I'd really like is for objects like CCSprite to directly respond to touch events without having to listen for those events in the layer and iterate through all the children to find which ones happen to intersect the event's x/y coordinates. So I figured that this would be the perfect chance to test my understanding of how to set up some event handlers and make a subclass of CCSprite that actually listens for touches without needing to go through CCLayer to know about it. Furthermore, I wanted to be able to assign different behaviors to different CCSprite instances on an ad-hoc basis without explicitly subclassing further, much like Android Buttons don't need to be subclassed just to give them a handler for their touch events.

This is what I came up with on a first pass:

// My touch interface for all touchable CCNode objects.

package com.scriptocalypse.cocos2d;

public interface ITouchable {

    boolean onCCTouchesBegan();
    boolean onCCTouchesEnded();
    boolean onCCTouchesMoved();

}

And now the class that uses the ITouchable interface for its callbacks...

public class CCTouchSprite extends CCSprite implements CCTouchDelegateProtocol {

    protected ITouchable mTouchable;

    public void setTouchable(ITouchable pTouchable){
        mTouchable = pTouchable;
        boolean enable = mTouchable != null;
        this.setIsTouchEnabled(enable);
    }

    public void setIsTouchable(boolean pEnabled){
            // code to enable and disable touches snipped...
    }



    /////
    //  And now implementing the CCTouchDelegateProtocol...
    /////
    public boolean ccTouchesBegan(MotionEvent event) {

        Log.d("hi there", "touch me");
        if(mTouchable != null){
        mTouchable.onCCTouchesBegan();
        }
        return CCTouchDispatcher.kEventHandled;  // TODO Auto-generated method stub
    }

    public boolean ccTouchesMoved(MotionEvent event) {

        if(mTouchable != null){
            mTouchable.onCCTouchesMoved();
        }
        return CCTouchDispatcher.kEventIgnored;  // TODO Auto-generated method stub
    }

    public boolean ccTouchesEnded(MotionEvent event) {
        Log.d("hi there", "not touch me");
        if(mTouchable != null){
            mTouchable.onCCTouchesEnded();
        }
        return CCTouchDispatcher.kEventIgnored;  // TODO Auto-generated method stub
    }

}

And finally, instantiate the class and implement the interface...

final CCTouchSprite sprite = new CCTouchSprite(tex);
sprite.setIsTouchEnabled(true);
sprite.setPosition(CGPoint.ccp(160,240));
sprite.setTouchable(new ITouchable(){
    @Override
    public boolean onCCTouchesBegan() {

        Log.d("SWEET SUCCESS", "I got a touch through my interface!");

        return true;
    }

    @Override
    public boolean onCCTouchesEnded() {
        Log.d("SWEET SUCCESS", "You stopped touching my interface!");
        sprite.runAction(CCRotateBy.action(1, 360));
        return false;
    }

    @Override
    public boolean onCCTouchesMoved(){
        Log.d("SWEET SUCCESS", "You moved the touch");
        return false;
    }
});

So all of this works. The subclass does successfully register with the Cocos2D touch dispatcher, which successfully calls those ccTouches functions and pass them MotionEvents, which in turn call my Interface functions if the interface has been instantiated.

Is this the "proper" way to do it (Define "it" as you see fit, ranging from using Interfaces to create event handlers to working with Cocos2D, to writing Java at all)? Are there drawbacks to this that I'm not aware of? Is this somehow worse for performance than iterating through all the CCNode objects that are children of CCLayer? If so, how can that possibly be?


Solution

  • I think you have got the basics for setting up a listener right. There are some things I would change though.

    First, the setter setIsTouchable. It's weird. You need a listener object to pass touch events to right? So what is this setter going to do when you pass it true (as your example does)? You snipped the code, but setting a boolean field to true does not seem right here as it would put the sprite object in an inconsistent internal state. I would just drop that setter. The getter can just evaluate whether mTouchable is assigned or null.

    Second, why limit yourself to one listener? Change mTouchable to mTouchables, being a list of ITouchables. Then change setTouchable to addTouchable and possibly add removeTouchable and clearTouchables methods. This way you can add multiple listeners for different behaviors having to respond to the same events. This is how most other event systems work as well. You then just change isTouchable to check whether the list is empty or not.