Skip to content

Commit

Permalink
Merge pull request #117 from fastjengine/images-sprites-animations
Browse files Browse the repository at this point in the history
Images, Sprites, Animations, and a lot more

Additions
-  (#104) added Linux's X11 to list of supported hardware accelerations
- (#108) Added Sprite2D and simple animation system
    - Manages an array of sprites, as well as an image resource instance
    - has default animations for "continuous", "play to end", and "static" (default is "continuous")
- (#113) Added centralized, extensible resource manager
- (#108, #113) Added image resource manager implementation
- (#108) Added `ImageUtil` to load and manage buffered images (makes use of resource manager where needed)
- (#113) Added default resource management, loaded on `FastJEngine` static initialization (_before_ `FastJEngine.init`!)
- (#10, #110) added `TexturePaint` builder, and support for `TexturePaint` in `.psdf` files
- (#10) Added support for writing/parsing `Model2D` to the `.obj`/`.mtl` file format


Bug Fixes
- (Fixes #106) Fixed translation doubling by removing translation from `setMetrics` method calls
- (Fixes #114) Added missing call to `Scene.reset` for each scene in `SceneManager` during a call to `SceneManager.reset`.
-  (Fixes #115) Added calls to destroy `Drawable`s in `Scene`/`SimpleManager`
- (Fixes #86) Fixed issue where occasional `ConcurrentModificationException`s woulc crop up thanks to slight issues with consistent multithreaded/event-based inputs (keyboard, mouse, window, etc).


Breaking Changes
- Moved `tech.fastj.systems.fio.FileUtil` to `tech.fastj.resources.files.FileUtil`
- Moved `tech.fastj.graphics.util.ModelUtil` to `tech.fastj.resources.models.ModelUtil`
- Moved `tech.fastj.graphics.io.PsdfUtil` to `tech.fastj.resources.models.PsdfUtil`
- Moved `tech.fastj.graphics.io.SupportedModelFormats` to `tech.fastj.resources.models.SupportedModelFormats`
  • Loading branch information
lucasstarsz authored Sep 12, 2021
2 parents 7ab3f01 + e5402fd commit 67a3c98
Show file tree
Hide file tree
Showing 39 changed files with 1,517 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import tech.fastj.graphics.game.Polygon2D;
import tech.fastj.graphics.game.Text2D;
import tech.fastj.graphics.util.DrawUtil;
import tech.fastj.graphics.util.ModelUtil;
import tech.fastj.resources.models.ModelUtil;

import tech.fastj.systems.control.Scene;

Expand Down
6 changes: 4 additions & 2 deletions src/example/java/tech/fastj/example/engineconfig/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ public static void main(String[] args) {

/* Now, we'll move onto configureHardwareAcceleration.
* By making use of java2d, FastJ supports a few hardware-accelerated graphics APIs:
* - OpenGL
* - Direct3D
* - OpenGL, for devices of all OSes
* - Direct3D, for Windows devices
* - X11, for Linux devices
* As well as CPURender, for software rendering.
*
* With that in mind, "FastJEngine#configureHardwareAcceleration" allows you to configure
* the type of hardware acceleration your game uses. This is set using the "HWAccel" enum.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import tech.fastj.graphics.game.Model2D;
import tech.fastj.graphics.game.Polygon2D;
import tech.fastj.graphics.util.DrawUtil;
import tech.fastj.graphics.util.ModelUtil;
import tech.fastj.resources.models.ModelUtil;

import java.io.IOException;
import java.nio.file.Files;
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
exports tech.fastj.graphics.display;
exports tech.fastj.graphics.game;
exports tech.fastj.graphics.gradients;
exports tech.fastj.graphics.io;
exports tech.fastj.graphics.textures;
exports tech.fastj.graphics.ui;
exports tech.fastj.graphics.ui.elements;
exports tech.fastj.graphics.util;
Expand All @@ -35,10 +35,14 @@
exports tech.fastj.input.keyboard;
exports tech.fastj.input.mouse;

exports tech.fastj.resources;
exports tech.fastj.resources.files;
exports tech.fastj.resources.images;
exports tech.fastj.resources.models;

exports tech.fastj.systems.audio;
exports tech.fastj.systems.audio.state;
exports tech.fastj.systems.behaviors;
exports tech.fastj.systems.control;
exports tech.fastj.systems.fio;
exports tech.fastj.systems.tags;
}
41 changes: 36 additions & 5 deletions src/main/java/tech/fastj/engine/FastJEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

import tech.fastj.input.keyboard.Keyboard;
import tech.fastj.input.mouse.Mouse;
import tech.fastj.resources.Resource;
import tech.fastj.resources.ResourceManager;
import tech.fastj.resources.images.ImageResource;
import tech.fastj.resources.images.ImageResourceManager;
import tech.fastj.systems.audio.AudioManager;
import tech.fastj.systems.audio.StreamedAudioPlayer;
import tech.fastj.systems.behaviors.BehaviorManager;
Expand All @@ -17,6 +21,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -73,10 +79,21 @@ public class FastJEngine {
private static final List<Runnable> AfterUpdateList = new ArrayList<>();
private static final List<Runnable> AfterRenderList = new ArrayList<>();

// Resources
private static final Map<Class<Resource<?>>, ResourceManager<Resource<?>, ?>> ResourceManagers = new ConcurrentHashMap<>();

private FastJEngine() {
throw new java.lang.IllegalStateException();
}

static {
/* I never thought I would find a use for one of these, but I would rather the default resource managers be
added as soon as the FastJEngine class is loaded.
Rather than assume the engine will be initialized, it makes more sense for it to activate upon
initialization. */
addDefaultResourceManagers();
}

/**
* Initializes the game engine with the specified title and logic manager.
* <p>
Expand Down Expand Up @@ -122,6 +139,10 @@ public static void init(String gameTitle, LogicManager gameManager, int fps, int
configure(fps, ups, windowResolution, internalResolution, hardwareAcceleration);
}

private static void addDefaultResourceManagers() {
addResourceManager(new ImageResourceManager(), ImageResource.class);
}

/**
* Configures the game's FPS (Frames Per Second), UPS (Updates Per Second), window resolution, internal resolution,
* and hardware acceleration.
Expand Down Expand Up @@ -204,8 +225,7 @@ public static void configureHardwareAcceleration(HWAccel hardwareAcceleration) {
private static boolean isSystemSupportingHA(HWAccel hardwareAcceleration) {
if (hardwareAcceleration.equals(HWAccel.Direct3D)) {
return System.getProperty("os.name").startsWith("Win");
}
else if (hardwareAcceleration.equals(HWAccel.X11)) {
} else if (hardwareAcceleration.equals(HWAccel.X11)) {
return System.getProperty("os.name").startsWith("Linux");
}
return true;
Expand Down Expand Up @@ -307,7 +327,6 @@ public static void setTargetUPS(int ups) {
* In both situations, the game engine will be closed via {@link FastJEngine#forceCloseGame()} beforehand.
*
* @param shouldThrowExceptions The {@code boolean} to set whether exceptions should be thrown.
*
* @since 1.5.0
*/
public static void setShouldThrowExceptions(boolean shouldThrowExceptions) {
Expand Down Expand Up @@ -359,6 +378,18 @@ public static double getFPSData(FPSValue dataType) {
}
}

@SuppressWarnings("unchecked")
public static <U, V extends Resource<U>, T extends ResourceManager<V, U>> void addResourceManager(T resourceManager, Class<V> resourceClass) {
ResourceManagers.put((Class<Resource<?>>) resourceClass, (ResourceManager<Resource<?>, ?>) resourceManager);
}

@SuppressWarnings("unchecked")
public static <U, V extends Resource<U>, T extends ResourceManager<V, U>> T getResourceManager(Class<V> resourceClass) {
return (T) ResourceManagers.computeIfAbsent((Class<Resource<?>>) resourceClass, rClass -> {
throw new IllegalStateException("No resource manager was added for the resource type \"" + resourceClass.getTypeName() + "\".");
});
}

/** Runs the game. */
public static void run() {
initEngine();
Expand Down Expand Up @@ -440,7 +471,6 @@ public static <T> void error(T errorMessage, Exception exception) {
* otherwise, such as adding a game object to a scene while in an {@link LogicManager#update(Display)} call.
*
* @param action Disposable action to be run after the next {@link LogicManager#update(Display)} call.
*
* @since 1.4.0
*/
public static void runAfterUpdate(Runnable action) {
Expand All @@ -454,7 +484,6 @@ public static void runAfterUpdate(Runnable action) {
* otherwise, such as adding a game object to a scene while in an {@link LogicManager#update(Display)} call.
*
* @param action Disposable action to be run after the next {@link LogicManager#render(Display)} call.
*
* @since 1.5.0
*/
public static void runAfterRender(Runnable action) {
Expand Down Expand Up @@ -561,6 +590,8 @@ private static void exit() {
TagManager.reset();
AfterUpdateList.clear();
AfterRenderList.clear();
ResourceManagers.forEach(((resourceClass, resourceResourceManager) -> resourceResourceManager.unloadAllResources()));
ResourceManagers.clear();

// engine speed variables
targetFPS = 0;
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/tech/fastj/graphics/display/Display.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import tech.fastj.graphics.util.DisplayUtil;
import tech.fastj.graphics.util.DrawUtil;

import tech.fastj.input.keyboard.Keyboard;
import tech.fastj.input.mouse.Mouse;

import javax.swing.JFrame;
import java.awt.Canvas;
import java.awt.Color;
Expand All @@ -28,9 +31,6 @@
import java.util.LinkedHashMap;
import java.util.Map;

import tech.fastj.input.keyboard.Keyboard;
import tech.fastj.input.mouse.Mouse;

/**
* Class that draws to a screen using a combination of Swing's JFrame, and AWT's Canvas.
*
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/tech/fastj/graphics/game/AnimationStyle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package tech.fastj.graphics.game;

public enum AnimationStyle {
ContinuousLoop,
Static,
PlayUntilEnd
}
192 changes: 192 additions & 0 deletions src/main/java/tech/fastj/graphics/game/Sprite2D.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package tech.fastj.graphics.game;

import tech.fastj.graphics.util.DrawUtil;

import tech.fastj.resources.images.ImageResource;
import tech.fastj.resources.images.ImageUtil;
import tech.fastj.systems.control.Scene;
import tech.fastj.systems.control.SimpleManager;

import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Sprite2D extends GameObject {

public static final int DefaultStartingFrame = 0;
public static final int DefaultAnimationFPS = 12;
public static final AnimationStyle DefaultAnimationStyle = AnimationStyle.ContinuousLoop;
public static final int DefaultHorizontalImageCount = 1;
public static final int DefaultVerticalImageCount = 1;

private ImageResource spritesResource;
private BufferedImage[] sprites;
private int currentFrame;
private int animationFPS = DefaultAnimationFPS;
private AnimationStyle animationStyle;

private ScheduledExecutorService spriteAnimator;

Sprite2D(ImageResource spritesResource, int horizontalImageCount, int verticalImageCount) {
this.spritesResource = spritesResource;
setCollisionPath(DrawUtil.createPath(DrawUtil.createBoxFromImage(sprites[0])));
resetSpriteSheet(horizontalImageCount, verticalImageCount);
resetSpriteAnimator();
}

public static Sprite2DBuilder create(ImageResource spritesResource) {
return new Sprite2DBuilder(spritesResource);
}

public static Sprite2D fromImageResource(ImageResource spritesResource) {
return new Sprite2DBuilder(spritesResource).build();
}

public void changeSpriteResource(ImageResource spritesResource, int horizontalImageCount, int verticalImageCount) {
this.spritesResource = spritesResource;
resetSpriteSheet(horizontalImageCount, verticalImageCount);
}

public int getCurrentFrame() {
return currentFrame;
}

public int getAnimationFPS() {
return animationFPS;
}

public AnimationStyle getAnimationStyle() {
return animationStyle;
}

public Sprite2D setCurrentFrame(int currentFrame) {
this.currentFrame = currentFrame;
return this;
}

public Sprite2D setAnimationFPS(int animationFPS) {
this.animationFPS = animationFPS;
resetSpriteAnimator();
return this;
}

public Sprite2D setAnimationStyle(AnimationStyle animationStyle) {
this.animationStyle = animationStyle;
return this;
}

@Override
public void render(Graphics2D g) {
if (!shouldRender()) {
return;
}

AffineTransform oldTransform = (AffineTransform) g.getTransform().clone();
g.transform(getTransformation());

g.drawImage(sprites[currentFrame], null, null);

g.setTransform(oldTransform);
}

@Override
public void destroy(Scene origin) {
spriteAnimator.shutdownNow();
spriteAnimator = null;
sprites = null;
currentFrame = -1;
animationFPS = -1;
animationStyle = null;

super.destroyTheRest(origin);
}

@Override
public void destroy(SimpleManager origin) {
spriteAnimator.shutdownNow();
sprites = null;
currentFrame = -1;
animationFPS = -1;
animationStyle = null;

super.destroyTheRest(origin);
}

private void resetSpriteSheet(int horizontalImageCount, int verticalImageCount) {
sprites = ImageUtil.createSpriteSheet(this.spritesResource.get(), horizontalImageCount, verticalImageCount);
}

private void resetSpriteAnimator() {
if (spriteAnimator != null) {
spriteAnimator.shutdownNow();
spriteAnimator = null;
}

spriteAnimator = Executors.newSingleThreadScheduledExecutor();
spriteAnimator.scheduleAtFixedRate(
() -> {
switch (animationStyle) {
case Static: {
break;
}
case ContinuousLoop: {
currentFrame++;
if (currentFrame == sprites.length) {
currentFrame = 0;
}
break;
}
case PlayUntilEnd: {
if (currentFrame < sprites.length - 1) {
currentFrame++;
}
break;
}
}
},
1000 / animationFPS,
1000 / animationFPS,
TimeUnit.MILLISECONDS
);
}

@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
if (!super.equals(other)) {
return false;
}
Sprite2D sprite2D = (Sprite2D) other;
return currentFrame == sprite2D.currentFrame
&& animationFPS == sprite2D.animationFPS
&& animationStyle == sprite2D.animationStyle
&& Arrays.equals(sprites, sprite2D.sprites);
}

@Override
public int hashCode() {
int result = Objects.hash(currentFrame, animationFPS, animationStyle);
result = 31 * result + Arrays.hashCode(sprites);
return result;
}

@Override
public String toString() {
return "Sprite2D{" +
"sprites=" + Arrays.toString(sprites) +
", currentFrame=" + currentFrame +
", animationFPS=" + animationFPS +
", animationStyle=" + animationStyle +
'}';
}
}
Loading

0 comments on commit 67a3c98

Please sign in to comment.