-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial
This tutorial is for java-desktop implementation
First you'll need to create java project with any build tool you like. I suggest maven or gradle. When you are done please add this library to the dependencies. Here is a jitpack link for this project: https://jitpack.io/#PanJohnny/PJGameLibrary. Otherwise you'll need to build it yourself. After you're done create your main class and you are set to go.
Now when the project is fully configured we can start writing code. First I'll need to load the library.
// Initialize the library with the java desktop adaptation. Window width and height can be passed to the JDInitializer.
PJGL.init(new JDInitializer("My cool game window title!"));
final PJGL pjgl = PJGL.getInstance();
// Start!
pjgl.start();
After writing code a window should popup and following info should be logged:
[PJGL/INFO] PJGL initialized
[PJGL/INFO] PJGL started
[PJGL/INFO] Closing...
You can turn off loging, set FPS cap and more with EngineOptions
. For example:
EngineOptions.logEngineState = false;
Great we have a game but I would like to add a player.
Meet Arnold! I've not definetelly created it because I am to lazy to animate or draw something more complicated... On that. Let's go to something that I can control better. Coding!
public class Arnold extends GameObject {
// Position of Arnold on screen. (in px)
public Position position = addComponent(new Position(this, 100, 100));
// Size (to hint renderer how large should Arnold be). (in px)
public Size size = addComponent(new Size(this, 100, 100));
// This component will make sure that Arnold gets rendered.
public SpriteRenderer renderer = addComponent(new SpriteRenderer(this, "player"));
}
And look at that Arnold should be now rendered. But one key part is missing. We need to register Arnold to the game object manager or it won't know that it exists.
SpriteRegistry.registerImageSprite("player", "/arnold.png");
pjgl.getManager().addObject(new Arnold());
pjgl.start();
Now when arnold exists I think it should respond to the player input. Let's say it follows the mouse!
@Override
public void update(long deltaTime) {
// Keep in super.update(deltaTime) is not needed in this scenario, we don't have any component that requires updating, but it is good practice to not to remove it.
super.update(deltaTime);
// Get the mouse.
final JDMouse mouse = PJGL.getInstance().getMouse();
// Fix the position.
position.x = mouse.getX();
position.y = mouse.getY();
}
Wow now when you move the mouse Arnold moves with it. Let's say that upon left-clicking it will shoot projectile.
After putting my artistic skills yet to another test I created this thing. It will serve as a projectile. So let's create a Projectile
object.
public class Projectile extends GameObject {
// Spawn out of the screen.
public Position position = addComponent(new Position(this, -100, -100));
public Size size = addComponent(new Size(this, 20, 20));
public SpriteRenderer renderer = addComponent(new SpriteRenderer(this, "projectile"));
// Collider for future use
public Collider collider = addComponent(new Collider(this));
private final Position.Direction direction;
public Projectile(Arnold arnold, Position.Direction direction) {
this.direction = direction;
position.x = arnold.position.x;
position.y = arnold.position.y;
}
@Override
public void update(long deltaTime) {
// In this case collider needs update to check for collisions.
super.update(deltaTime);
// 10 px/frame.
final int velocity = 10;
// Move.
position.add(velocity * direction.x, velocity * direction.y);
// Check if it is still in window.
final JDWindow window = PJGL.getInstance().getWindow();
// Remove the element if out of screen.
if (position.x < 0 || position.x > window.getWidth() || position.y < 0 || position.y > window.getHeight()) {
// Queue removal to prevent ConcurrentModificationException
PJGL.getInstance().getManager().queueRemoval(this);
}
}
}
Now we need to detect left-click. Now let's return to Arnold's update and add this code.
position.y = mouse.getY();
if (mouse.isKeyDown(MouseAdapter.BUTTON_LEFT)) {
// :)
// Spawn new projectile. It will be shot to the right. (Queued to prevent concurrent modification)
PJGL.getInstance().getManager().queueAddition(new Projectile(this, Position.Direction.RIGHT));
}
Register the sprite back in main class:
...
SpriteRegistry.registerImageSprite("projectile", "/projectile.png")
pjgl.getManager().addObject(new Arnold());
pjgl.start();
I want to decide in which direction should the projectile go by looking at which keys are held.
D = RIGHT
D + W = UP_RIGHT
So let's implement that, again let's go to Arthur's update. To the place where I left the :)
comment.
if (mouse.isKeyDown(MouseAdapter.BUTTON_LEFT)) {
final JDKeyboard keyboard = PJGL.getInstance().getKeyboard();
// Get the direction which can be combined.
Position.Direction directionX = Position.Direction.NONE;
Position.Direction directionY = Position.Direction.NONE;
if (keyboard.isKeyDown('a')) {
directionX = Position.Direction.LEFT;
}
if (keyboard.isKeyDown('d')) {
if (directionX != Position.Direction.NONE) {
directionX = Position.Direction.NONE;
} else
directionX = Position.Direction.RIGHT;
}
if (keyboard.isKeyDown('w')) {
directionY = Position.Direction.UP;
}
if (keyboard.isKeyDown('s')) {
if (directionY != Position.Direction.NONE) {
directionY = Position.Direction.NONE;
} else
directionY = Position.Direction.DOWN;
}
Position.Direction direction = directionX.combine(directionY);
if (direction != Position.Direction.NONE) {
// Spawn new projectile. (Queued to prevent concurrent modification)
PJGL.getInstance().getManager().queueAddition(new Projectile(this, direction));
}
}
This is how I calculated the direction and now I can shoot projectiles in diferent directions.
To make our projectiles usefull. Let's add enemies.
This is Zombie.
First I'll add collider to Arnold.
public Collider collider = addComponent(new Collider(this));
Now let's implement zombie.
public class Zombie extends GameObject {
// Spawn out of the screen.
public Position position = addComponent(new Position(this, 300, 300));
public Size size = addComponent(new Size(this, 60, 60));
public SpriteRenderer renderer = addComponent(new SpriteRenderer(this, "zombie"));
// Collider for future use
public Collider collider = addComponent(new Collider(this));
private final Arnold arnold;
public Zombie(Arnold arnold) {
this.arnold = arnold;
}
public Zombie(Arnold arnold, int x, int y) {
this.arnold = arnold;
position.x = x;
position.y = y;
}
@Override
public void update(long deltaTime) {
// In this case collider needs update to check for collisions.
super.update(deltaTime);
// 3 px/frame.
final int velocity = 2;
// Get direction to Arnold.
Position.Direction direction = Position.Direction.fromTo(position, arnold.position);
// Move.
position.add(velocity * direction.x, velocity * direction.y);
// Zombie is colliding.
if (!collider.getCollisions().isEmpty()) {
if (collider.getCollisions().contains(arnold)) {
// The zombie killed Arnold.
System.out.println("You lost!");
// Remove all objects
for (GameObject o : PJGL.getInstance().getManager().getObjects()) {
PJGL.getInstance().getManager().queueRemoval(o);
}
// TODO add game over display
} else {
// The zombie got hit by projectile.
PJGL.getInstance().getManager().queueRemoval(this);
// Remove projectile / projectiles
for (GameObject o : collider.getCollisions()) {
PJGL.getInstance().getManager().queueRemoval(o);
}
final JDWindow window = PJGL.getInstance().getWindow();
// TODO add points
Random random = new Random();
PJGL.getInstance().getManager().queueAddition(new Zombie(arnold, 0, random.nextInt(window.getHeight())));
if (random.nextBoolean())
PJGL.getInstance().getManager().queueAddition(new Zombie(arnold, window.getWidth(), random.nextInt(window.getHeight())));
}
}
}
}
Zombies follow Arnold and when Arnold shoots them they have chance of duplicating - spawning randomly in the window.
The main method should look like this now in order to add first zombie:
public static void main(String[] args) {
// Initialize the library with the java desktop adaptation.
PJGL.init(new JDInitializer("My cool game window title!"));
final PJGL pjgl = PJGL.getInstance();
SpriteRegistry.registerImageSprite("player", "/arnold.png")
SpriteRegistry.registerImageSprite("projectile", "/projectile.png")
SpriteRegistry.registerImageSprite("zombie", "/zombie.png")
final Arnold arnold = new Arnold();
pjgl.getManager().addObject(arnold);
pjgl.getManager().addObject(new Zombie(arnold));
// Start!
pjgl.start();
}
Now let's say that I want to render "Game Over" string and "Points: " string. I can do that with G2DRenderer
.
public class TextDisplay extends GameObject {
public Position position = addComponent(new Position(this));
public G2DRenderer renderer = addComponent(new G2DRenderer(this, this::render));
public String text;
public TextDisplay(String text, int x, int y) {
this.text = text;
position.x = x;
position.y = y;
}
public void render(Graphics2D g) {
// Set font to arial 20px plain.
g.setFont(new Font("Arial", Font.PLAIN, 20));
// x: 0, y: 0 because we don't want to move it away from the set position.
g.drawString(text, 0, 0);
}
}
Now after creating this, I'll add field score to Arnold.
public int score = 0;
Let's now go to Zombie where I left those TODOs.
// First TODO - display
PJGL.getInstance().getManager().queueAddition(new TextDisplay("Game Over!", 100, 100));
PJGL.getInstance().getManager().queueAddition(new TextDisplay("Final score: " + arnold.score, 100, 200));
// Second TODO - add score
arnold.score += 100;
In order to play track I'll add this code at the end of the main class.
try {
Track track = new Track("/music.wav", "Industrial background - PossessedSinner", "industrial_bg");
track.loop();
track.setVolume(0.1f);
track.play();
} catch (Exception e) {
throw new RuntimeException(e);
}
That means the game is "finished" you can check out this version right here: https://github.com/PanJohnny/PJGLGameDemo/tree/wiki
The final polished version: https://github.com/PanJohnny/PJGLGameDemo/tree/final
NOTE: github repo contains deprecated sprite handling, always use SpriteRegistry