Skip to content
PanJohnny edited this page Sep 25, 2023 · 14 revisions

This is a brief introduction into more technical details for this library. If you want to follow a tutorial with java-desktop@pjgl adaptation look here: https://github.com/PanJohnny/PJGameLibrary/wiki/Tutorial

Project structure

This project is structured to three separate parts:

  • Core
  • API
  • Implementation

Core

Core consists of the "Main class" which is PJGLCore. PJGLCore is what everything is built upon. It handles the loop, calls a few events and manages implementations.

API

Api is a large division of this project. These are the most important classes:

  • PJGL - used to initialize the library, full docs
  • PJGLEvents - contains a set of predefined events for PJGL Engine, these events can be listened, full docs

Implementation

Contains implementations of this project, currently lwjgl and java-desktop.

Getting started

First you will need to initialize the library using the static init method. For that you can create your own adapters or choose an implementation.

Examples will have LWGL used.

PJGL.init(new LWJGLInitializer("Hello, World!", 750, 750)); // creates window with the title Hello, World!, size 750x750

final PJGL pjgl = PJGL.getInstance(); // PJGL is singleton class, you can get the instance and keep it as a variable

When using LWJGL adaptation you need to register sprites and objects when PJGLEvents.VISIBLE is called. Add a listener to this event and then call start. This is in-place because of initialization of OpenGL context.

PJGLEvents.VISIBLE.listen(() -> {
    SpriteRegistry.register...
    manager.queueAddition... // addition needs to be queued
});

Sprites

There are two types of sprites.

1. Image Sprites

Image sprites are mainly for com.panjohnny.pjgl.adapt.desktop adaptation, when Image is used to render sprites.

Register them with registerImageSprite(String, String). This method supports loading from classpath and filesystem.

2. Texture Sprites

These sprites contain integer representing OpenGL texture.

Register them with registerTextureSprite(String, String). This method supports loading from filesystem.

You can also register texture sprite as a atlas and then assign regions of it to SpriteRenderer. (Only LWJGL)

TextureAtlas atlas = SpriteRegistry.registerTextureSpriteAsAtlas("juan", "./assets/atlas.png");
AtlasRegion region0 = atlas.defineRegion(0, 0, 16, 16);

Objects

NOTE: Objects are always registered synchronously. To prevent concurrent modifications there are special queue methods.

Game objects on their own are just listeners for the update event. When you provide a components you add other functions.

GameObject apple = new GameObject() {
    public final Position position = addComponent(new Position(this, 10, 10));
    public final Size size = addComponent(new Size(this, 100, 100));
    public final SpriteRenderer renderer = addComponent(new SpriteRenderer(this, "apple"));
    public final Animator animator = addComponent(new Animator(this, appleAnimation));
};

In this sample a GameObject apple is created with a few components - position, size, renderer and animator.

Below is game object update method schema, the order of adding components matter.

GAME OBJECT UPDATE SCHEMA

super.update() // code that GameObject has
| -> Components[0].update()
| -> ...
| -> Components[last].update()

    -> #update() // your new code

Components are updated on GameObject#update() instances of GameObject update later.

Objects need to be registered to the manager.

GameObjectManager manager = pjgl.getManager();
manager.addObject(apple); // this could be done only before calling pjgl.start()
manager.queueAddition(apple); // better method for thread safety, apple will be added next update, can be called when pjgl is running

Examples

Java desktop

PJGL.init(new JDInitializer("Apple!", 750, 750));
PJGL pjgl = PJGL.getInstance();

SpriteRegistry.registerImageSprite("apple", "apple.png");

GameObject apple = new GameObject() {
    public final Position position = addComponent(new Position(this, 10, 10));
    public final Size size = addComponent(new Size(this, 100, 100));
    public final SpriteRenderer renderer = addComponent(new SpriteRenderer(this, "apple"));
};

pjgl.getManager().addObject(apple); // notice that objects can be added safely because pjgl.start() was not called
pjgl.start();

LWJGL

PJGL.init(new LWJGLInitializer("Apple!", 750, 750));
PJGL pjgl = PJGL.getInstance();

PJGLEvents.VISIBLE.listen(() -> {
  SpriteRegistry.registerTextureSprite("apple", "./assets/apple.png");

  // Juan is sprite map
  TextureAtlas atlas = SpriteRegistry.registerTextureSpriteAsAtlas("juan", "./assets/juan-map.png");
  AtlasRegion j0 = atlas.defineRegion(0, 0, 16, 16);

  GameObject apple = new GameObject() {
    public final Position position = addComponent(new Position(this, 10, 10));
    public final Size size = addComponent(new Size(this, 100, 100));
    public final SpriteRenderer renderer = addComponent(new SpriteRenderer(this, "apple"));
    public final Animator animator = addComponent(new Animator(this, a));
  };

  pjgl.getManager().queueAddition(apple);
});

GLFWKeyboard keyboard = pjgl.getKeyboard();
GLFWWindow window = pjgl.getWindow();

// Example of input handling with GLFW
PJGLEvents.TICK.listen(() -> {
  if (keyboard.isKeyDown(GLFW.GLFW_KEY_ESCAPE)) {
    window.close();
  }

  if (keyboard.isKeyDown(GLFW.GLFW_KEY_LEFT)) {
    pjgl.getRenderer().getCamera().move(-1, 0);
  }

  if (keyboard.isKeyDown(GLFW.GLFW_KEY_RIGHT)) {
    pjgl.getRenderer().getCamera().move(1, 0);
  }

  if (keyboard.isKeyDown(GLFW.GLFW_KEY_UP)) {
    pjgl.getRenderer().getCamera().move(0, -1);
  }

  if (keyboard.isKeyDown(GLFW.GLFW_KEY_DOWN)) {
    pjgl.getRenderer().getCamera().move(0, 1);
  }
});
pjgl.start();