문제

I'm trying to use events to do something on actor but i don't understand how to do it properly.

I have a button on my screen and a text (just for example). they are both actors in a stage my purpose is: if i click on the button, i would like to change text

I add listener on my button, i get the click but i don't know how to send event (or anything else) to my text to set it.

Main class with stage definition and his

public class AGame implements ApplicationListener {
private WorldRendererTouchPad renderer;
private Stage stage;
private static Vector3 cameraVelocity=new Vector3(0,0,0);

private ButtonJump button;

public static final int SCREEN_WIDTH=800;
public static final int SCREEN_HEIGHT=480;

public void create() {
    stage = new Stage();
    stage.setViewport(SCREEN_WIDTH, SCREEN_HEIGHT, true);
    stage.getCamera().translate(-stage.getGutterWidth(), -stage.getGutterHeight(), 0);

    renderer = new MyRenderer(SCREEN_WIDTH, SCREEN_HEIGHT);
    stage.addActor(renderer);       
    renderer.create();

    button=new ButtonJump();
    stage.addActor(button);     
    button.create();

    Gdx.input.setInputProcessor(stage);
}
....
resize and other methods 
}

MyRenderer class (contains text actor):

public class MyRenderer  {
private TextTest text;

public MyRenderer(float screenWidth, float screenHeight) {
    setBounds(0, 0, screenWidth, screenHeight);
}

public void create() {
    this.initActors();      
}

private void initActors() {
    text=new TextTest("Hello world!");
    addActor(text);
}


// is it usefull?
public void setText(String newText) {
    text.setText(newText);

}

}

and the ButtonJump class (extends MyButton just here for define Skin and ButtonStyle)

public class ButtonJump extends MyButton {
public boolean isJump=false;

private static InputListener buttonListener=new InputListener() {
    public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
        Gdx.app.log("event" , "="+event.toString());

        // do something to update text

        return true;
    }
};


public ButtonJump() {
    super();
}

public void create() {
    this.setPosition(getStage().getWidth()-60, 30);
    this.addCaptureListener(buttonListener);
}

public void capture() {
    if (this.isJump)
        Gdx.app.log("jump button", "Jump is set");
    else
        Gdx.app.log("jump button", "No jump");

}
}
도움이 되었습니까?

해결책

If you use the clicklistener you need to let the other actor hold an reference to it to call a method on click. It is not that good to let all Actor know of each other. Use an anonymous way.

There is a "common" system for it in games.

If you really want to use Events, do implement an Event-System. Therefore you have an interface Listen and an Interface Event_Handler. At the start of your game you init one Implementation of the Eventhandler. The interface should at least look like this:

public interface Interface_EventHandler extends Disposable
{
    public void handleEvent(final Event... e);
    public void registerListener(final Interface_Listen listener,
            final Event_Type... type);
    public void unregisterListener(final Interface_Listen... listener);
    public void unregisterAllListener();
    public void unregisterAllListener(final Event_Type... type);
    public void processEvents();
    public void processEvents(final int maxTimeInMS);
}

Okay so now how does it work. The handler has an hashmap with all eventtypes as Key and an list of listeners as Value. So if someone want to notice an event he registers with the registerListerner at the handler for the right Event_Type (Enum). It need to have the interface Listen to get events. Everyone can now push an Event into the handler with the handleEvent(...) method. Or even more than one.. (varargs) ..

Okay that still does not explain how it work. We now have a registered listener (actor for example) and we have events that get into the handler.

Every Rendercycle you call the processEvents() at the hanlder once. That mean that every event that get pushed in at a frame get handled at the next frame. (Asynchronus) While that he iterates over all events and push them to the listeners. Moreover the listener should have a queue too where they put all events and when they are at their .act() they handle the events. (more asynchronus).

Okay here is an Handler i use:

package com.portaaenigma.eventsystem;

import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import com.badlogic.gdx.utils.TimeUtils; import com.portaaenigma.managers.Logger;

public class EventHandler implements Interface_EventHandler { private HashMap> listeners; private LinkedList events;

public EventHandler()
{
    listeners = new HashMap<Event_Type, ArrayList<Interface_Listen>>();
    // add the arraylist for every Eventtype
    for (Event_Type e : Event_Type.values())
    {
        listeners.put(e, new ArrayList<Interface_Listen>());
    }
    events = new LinkedList<Event>();
}

@Override
public void handleEvent(final Event... e)
{
    for (Event event : e)
    {
        events.push(event);
    }
}

@Override
public void unregisterListener(final Interface_Listen... listener)
{
    for (Event_Type e : Event_Type.values())
    {
        for (Interface_Listen interface_Listen : listener)
        {
            listeners.get(e).remove(interface_Listen);
        }
    }
}

@Override
public void processEvents()
{
    while (events.size() != 0)
    {
        // get the first element and delete it
        Event e = events.pop();
        for (Interface_Listen l : listeners.get(e.getType()))
        {
            l.handleEvent(e);
        }
    }
}

@Override
public void processEvents(final int maxTimeInMS)
{
    int startSize = 0;
    if (events.size() != 0)
    {
        startSize = events.size();
        Logger.log("Processing Events: " + events.size());
    }
    long startTime = TimeUtils.millis();

    while (events.size() != 0)
    {
        // get the first element and delete it
        Event e = events.pop();
        for (Interface_Listen l : listeners.get(e.getType()))
        {
            l.handleEvent(e);
        }
        // stop handling if time is up
        if (startTime - TimeUtils.millis() > maxTimeInMS)
        {
            Logger.log("Handled " + (events.size() - startSize) + " Events");
            break;
        }
    }
}

@Override
public void registerListener(final Interface_Listen listener,
        Event_Type... type)
{
    for (Event_Type event_Type : type)
    {
        listeners.get(event_Type).add(listener);
    }
}

@Override
public void unregisterAllListener()
{
    Logger.log("UnregisterAll");
    for (Event_Type e : Event_Type.values())
    {
        listeners.get(e).clear();
    }
}

@Override
public void unregisterAllListener(final Event_Type... type)
{
    for (Event_Type event_Type : type)
    {
        listeners.get(event_Type).clear();
    }
}

@Override
public void dispose()
{
    unregisterAllListener();
    events.clear();
    listeners.clear();
}
}

The interface for all listeners is simple it's just this: public interface Interface_Listen

{
    public void handleEvent(final Event e);
}

Last but not least the event. How can you now send different data? Quiet simple. Have an hashmap out of Strings and Strings and for sure the EventType.

public class Event
{
private Event_Type type;
private HashMap<String, String> m_messages;

public Event(final Event_Type e, final Event_Message... m)
{
    m_messages = new HashMap<String, String>();
    for (Event_Message message : m)
    {
        m_messages.put(message.m_key, message.m_value);
    }
    type = e;
}


public Event_Type getType()
{
    return type;
}

public void addMessages(final Event_Message... m)
{
    for (Event_Message event_Message : m)
    {
        m_messages.put(event_Message.m_key, event_Message.m_value);
    }
}

public String getMessage(final String name)
{
    if (m_messages.get(name) == null)
    {
        Logger.error("Message not found: " + name);
    }

    // if null return an empty string
    return m_messages.get(name) != null ? m_messages.get(name) : "";
}

public void clearMessages()
{
    m_messages.clear();
}
}

Okay i hope this does explain how to implement an EventSystem at a Game. This meight not be the regular way at other Software but in games you queue up the events and handle them once in a Gameloop cycle. Also the listeners do the same.

So in your case. Implement such an handler and register the actors as listener. Sure they need to implement the listener interface and do something with the event. Let the one actor push an event into the handler which directs to the other actor and your are done. And they event dont need to know of each other and it does work for as much actors as you whish. You can even create 1 event for different classes different actors and so on. Usefull for example at mapchange. You push one event with the notice.. "changemap".. and every actor knows he need to stop moving and every subsystem knows that it does need to stop because of an mapchange and so on ...

It seems to be a bit overkill but it has alot of advantages and worth to use even at early stages. I made the misstake and started using it laterly and now i regret it.

Sorry for the bracing. It's not the regular java standart but more clear i think... Sorry for alot of varargs just like it at that point. Meight be confusing.


Literatur:

Game Coding Complete, Fourth Edition Chapter 11

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top