1
\$\begingroup\$

I don't really know how to explain what I am trying to do that well, but yeah I'll try my best.

I'm currently creating a game with LWJGL 3, and I hope to make it event based and rather abstract as to allow the game to be hopefully easily moddable by community members through their own .jar files. To give them the ability to modify the current functions of the game, I want to make the game event based so they can listen to events and modify them or cancel them completely before my function executes, and provide a way for them to create their own structures, entities, events, artifacts(items), et cetera. I just cannot wrap my head around how to do this exactly, at least with creating the API.

I spent a lot of time researching and I just came up with allowing modules through Unity or with Lua, but that's not what I am looking for. So, what is the best way to set up an API like this? Do I create interfaces for all of my own events, entities, items, and things or do I just create base interfaces which I implement in my own classes? Then however I do this, what do I actually provide for them to create their mod/module? My whole game or just these interfaces. I just cannot figure out a good way to set up the API especially since I've never created one myself, let alone for a game.

\$\endgroup\$
  • 1
    \$\begingroup\$ You can look at my code : github.com/gioragutt/xna-ecs and look at the EMS library. It's in C#, but C# is practically Java and vice versa. You can approach me if you need help in understanding what's going on there. I've also Implemented an Entity Component System which is also important for what you want \$\endgroup\$ – Giora Guttsait Aug 27 '16 at 9:39
  • \$\begingroup\$ The repository itself is a game that uses the libraries that I write for the game. I believe it's easily convertible to java. \$\endgroup\$ – Giora Guttsait Aug 27 '16 at 9:39
1
\$\begingroup\$

You may use Java 8's builtin JavaScript engine Nashorn for solving this type of problem. The main game loop update game objects with a certain frequency (x times per second)

JavaScript files may be loaded from a ZipInputStream, Jar using getResourceAsStream(), external file or even a socket. Using Zip-files make modding quite easy, especially when combining them with JSON as data files for definitions.

Just call the JavaScript implementations as regular Java using the getInterface method to get an instance of the interface.

public static ScriptEngine createScriptEngine() {
    return new NashornScriptEngineFactory().getScriptEngine("-strict", "--no-java");
}

public static void loadScript(final ScriptEngine scriptEngine, final InputStream inputStream) throws IOException {
        try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
            scriptEngine.eval(reader);
        } catch (ScriptException e) {
            throw new IOException(e);
        }
}

public static <C> C getInterface(final ScriptEngine scriptEngine, final String objectName, final Class<C> clazz)
        throws ScriptException {
    final Invocable invocable = (Invocable) scriptEngine;

    final Object def = scriptEngine.get(objectName);
    if (def == null) {
        throw new ScriptException("Missing object " + objectName);
    }

    final C object = invocable.getInterface(def, clazz);

    if (object == null) {
        throw new ScriptException("Failed to create instance for " + objectName);
    }

    return object;
}

Using an interface like this:

public interface ScenarioCallback {
    public boolean isLevelUnlocked(int level);
}

JavaScript code in scenario.js

var Scenario1 = new Object();

Scenario1.isLevelUnlocked = function(level) {
    return true;
};

Use callback from Java

ScriptEngine scriptEngine = createScriptEngine();

loadScript(scriptEngine, getClass().getResourceAsStream("/scenario.js"));

ScenarioCallback scenarioCallback = getInterface(scriptEngine, "Scenario1", ScenarioCallback.class);

if(scenarioCallback.isLevelUnlocked(7)) {
    // You win
}

Use small simple methods that for callbacks. But you need to identify what type of game objects that you use, and that is unique for each game.

\$\endgroup\$
0
\$\begingroup\$

I haven't done this before, but the approach I would take would be to have a mods folder that I put in the classpath. There would be a high level EventListener interface that all listeners must implement, and you can use reflection to find all implemented concrete classes. Then, create an instance of each implemented concrete class and load it into your event system. You can create sub-interfaces for specific interface types (ItemEventListener, BuildingEventListener, CharacterEventListener, etc.) that can be shuffled into the appropriate systems. If you want to get really complex, you can have listeners for different stages (BeforeAttackListener, AttackListener, AfterAttackListener). You'll also need interfaces for the concrete objects (Item, Character, Building) and for events (ItemEvent, CharacterEvent, BuildingEvent).

The interfaces will have two methods, fireEvent and getPriority. fireEvent will take one argument, a subtype of Event. The method will modify the event as appropriate (reduce HP, remove from inventory, etc.) and return the event. The event will traverse the stack of relevant listeners until it comes out the other side, resulting in the final state of the event. Then, the event will act upon its relevant concrete objects. You don't want the listeners acting directly on the objects while the event traverses the listener stack, otherwise you wouldn't be able to override previous listener behaviors. getPriority is used to determine stack order.

Obviously, the final implementation will only bear a passing resemblance to this, as additional complexities are discovered, but this is where I would start. Then, once you finish the system, the really hard part will begin: documenting your API! In addition to explaining this system, you will also need to document every single one of your Listeners, Events, and ConcreteObjects along with their various attributes and priorities. Which, honestly, sounds like a job for a custom Javadoc generator.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.