Skip to content

Commit 67a3c98

Browse files
authored
Merge pull request #117 from fastjengine/images-sprites-animations
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`
2 parents 7ab3f01 + e5402fd commit 67a3c98

39 files changed

+1517
-111
lines changed

src/example/java/tech/fastj/example/bullethell/scenes/GameScene.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import tech.fastj.graphics.game.Polygon2D;
1111
import tech.fastj.graphics.game.Text2D;
1212
import tech.fastj.graphics.util.DrawUtil;
13-
import tech.fastj.graphics.util.ModelUtil;
13+
import tech.fastj.resources.models.ModelUtil;
1414

1515
import tech.fastj.systems.control.Scene;
1616

src/example/java/tech/fastj/example/engineconfig/Main.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ public static void main(String[] args) {
6565

6666
/* Now, we'll move onto configureHardwareAcceleration.
6767
* By making use of java2d, FastJ supports a few hardware-accelerated graphics APIs:
68-
* - OpenGL
69-
* - Direct3D
68+
* - OpenGL, for devices of all OSes
69+
* - Direct3D, for Windows devices
70+
* - X11, for Linux devices
71+
* As well as CPURender, for software rendering.
7072
*
7173
* With that in mind, "FastJEngine#configureHardwareAcceleration" allows you to configure
7274
* the type of hardware acceleration your game uses. This is set using the "HWAccel" enum.

src/example/java/tech/fastj/example/modelreadwrite/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import tech.fastj.graphics.game.Model2D;
66
import tech.fastj.graphics.game.Polygon2D;
77
import tech.fastj.graphics.util.DrawUtil;
8-
import tech.fastj.graphics.util.ModelUtil;
8+
import tech.fastj.resources.models.ModelUtil;
99

1010
import java.io.IOException;
1111
import java.nio.file.Files;

src/main/java/module-info.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
exports tech.fastj.graphics.display;
2727
exports tech.fastj.graphics.game;
2828
exports tech.fastj.graphics.gradients;
29-
exports tech.fastj.graphics.io;
29+
exports tech.fastj.graphics.textures;
3030
exports tech.fastj.graphics.ui;
3131
exports tech.fastj.graphics.ui.elements;
3232
exports tech.fastj.graphics.util;
@@ -35,10 +35,14 @@
3535
exports tech.fastj.input.keyboard;
3636
exports tech.fastj.input.mouse;
3737

38+
exports tech.fastj.resources;
39+
exports tech.fastj.resources.files;
40+
exports tech.fastj.resources.images;
41+
exports tech.fastj.resources.models;
42+
3843
exports tech.fastj.systems.audio;
3944
exports tech.fastj.systems.audio.state;
4045
exports tech.fastj.systems.behaviors;
4146
exports tech.fastj.systems.control;
42-
exports tech.fastj.systems.fio;
4347
exports tech.fastj.systems.tags;
4448
}

src/main/java/tech/fastj/engine/FastJEngine.java

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
import tech.fastj.input.keyboard.Keyboard;
1010
import tech.fastj.input.mouse.Mouse;
11+
import tech.fastj.resources.Resource;
12+
import tech.fastj.resources.ResourceManager;
13+
import tech.fastj.resources.images.ImageResource;
14+
import tech.fastj.resources.images.ImageResourceManager;
1115
import tech.fastj.systems.audio.AudioManager;
1216
import tech.fastj.systems.audio.StreamedAudioPlayer;
1317
import tech.fastj.systems.behaviors.BehaviorManager;
@@ -17,6 +21,8 @@
1721
import java.util.ArrayList;
1822
import java.util.Arrays;
1923
import java.util.List;
24+
import java.util.Map;
25+
import java.util.concurrent.ConcurrentHashMap;
2026
import java.util.concurrent.Executors;
2127
import java.util.concurrent.ScheduledExecutorService;
2228
import java.util.concurrent.TimeUnit;
@@ -73,10 +79,21 @@ public class FastJEngine {
7379
private static final List<Runnable> AfterUpdateList = new ArrayList<>();
7480
private static final List<Runnable> AfterRenderList = new ArrayList<>();
7581

82+
// Resources
83+
private static final Map<Class<Resource<?>>, ResourceManager<Resource<?>, ?>> ResourceManagers = new ConcurrentHashMap<>();
84+
7685
private FastJEngine() {
7786
throw new java.lang.IllegalStateException();
7887
}
7988

89+
static {
90+
/* I never thought I would find a use for one of these, but I would rather the default resource managers be
91+
added as soon as the FastJEngine class is loaded.
92+
Rather than assume the engine will be initialized, it makes more sense for it to activate upon
93+
initialization. */
94+
addDefaultResourceManagers();
95+
}
96+
8097
/**
8198
* Initializes the game engine with the specified title and logic manager.
8299
* <p>
@@ -122,6 +139,10 @@ public static void init(String gameTitle, LogicManager gameManager, int fps, int
122139
configure(fps, ups, windowResolution, internalResolution, hardwareAcceleration);
123140
}
124141

142+
private static void addDefaultResourceManagers() {
143+
addResourceManager(new ImageResourceManager(), ImageResource.class);
144+
}
145+
125146
/**
126147
* Configures the game's FPS (Frames Per Second), UPS (Updates Per Second), window resolution, internal resolution,
127148
* and hardware acceleration.
@@ -204,8 +225,7 @@ public static void configureHardwareAcceleration(HWAccel hardwareAcceleration) {
204225
private static boolean isSystemSupportingHA(HWAccel hardwareAcceleration) {
205226
if (hardwareAcceleration.equals(HWAccel.Direct3D)) {
206227
return System.getProperty("os.name").startsWith("Win");
207-
}
208-
else if (hardwareAcceleration.equals(HWAccel.X11)) {
228+
} else if (hardwareAcceleration.equals(HWAccel.X11)) {
209229
return System.getProperty("os.name").startsWith("Linux");
210230
}
211231
return true;
@@ -307,7 +327,6 @@ public static void setTargetUPS(int ups) {
307327
* In both situations, the game engine will be closed via {@link FastJEngine#forceCloseGame()} beforehand.
308328
*
309329
* @param shouldThrowExceptions The {@code boolean} to set whether exceptions should be thrown.
310-
*
311330
* @since 1.5.0
312331
*/
313332
public static void setShouldThrowExceptions(boolean shouldThrowExceptions) {
@@ -359,6 +378,18 @@ public static double getFPSData(FPSValue dataType) {
359378
}
360379
}
361380

381+
@SuppressWarnings("unchecked")
382+
public static <U, V extends Resource<U>, T extends ResourceManager<V, U>> void addResourceManager(T resourceManager, Class<V> resourceClass) {
383+
ResourceManagers.put((Class<Resource<?>>) resourceClass, (ResourceManager<Resource<?>, ?>) resourceManager);
384+
}
385+
386+
@SuppressWarnings("unchecked")
387+
public static <U, V extends Resource<U>, T extends ResourceManager<V, U>> T getResourceManager(Class<V> resourceClass) {
388+
return (T) ResourceManagers.computeIfAbsent((Class<Resource<?>>) resourceClass, rClass -> {
389+
throw new IllegalStateException("No resource manager was added for the resource type \"" + resourceClass.getTypeName() + "\".");
390+
});
391+
}
392+
362393
/** Runs the game. */
363394
public static void run() {
364395
initEngine();
@@ -440,7 +471,6 @@ public static <T> void error(T errorMessage, Exception exception) {
440471
* otherwise, such as adding a game object to a scene while in an {@link LogicManager#update(Display)} call.
441472
*
442473
* @param action Disposable action to be run after the next {@link LogicManager#update(Display)} call.
443-
*
444474
* @since 1.4.0
445475
*/
446476
public static void runAfterUpdate(Runnable action) {
@@ -454,7 +484,6 @@ public static void runAfterUpdate(Runnable action) {
454484
* otherwise, such as adding a game object to a scene while in an {@link LogicManager#update(Display)} call.
455485
*
456486
* @param action Disposable action to be run after the next {@link LogicManager#render(Display)} call.
457-
*
458487
* @since 1.5.0
459488
*/
460489
public static void runAfterRender(Runnable action) {
@@ -561,6 +590,8 @@ private static void exit() {
561590
TagManager.reset();
562591
AfterUpdateList.clear();
563592
AfterRenderList.clear();
593+
ResourceManagers.forEach(((resourceClass, resourceResourceManager) -> resourceResourceManager.unloadAllResources()));
594+
ResourceManagers.clear();
564595

565596
// engine speed variables
566597
targetFPS = 0;

src/main/java/tech/fastj/graphics/display/Display.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import tech.fastj.graphics.util.DisplayUtil;
1111
import tech.fastj.graphics.util.DrawUtil;
1212

13+
import tech.fastj.input.keyboard.Keyboard;
14+
import tech.fastj.input.mouse.Mouse;
15+
1316
import javax.swing.JFrame;
1417
import java.awt.Canvas;
1518
import java.awt.Color;
@@ -28,9 +31,6 @@
2831
import java.util.LinkedHashMap;
2932
import java.util.Map;
3033

31-
import tech.fastj.input.keyboard.Keyboard;
32-
import tech.fastj.input.mouse.Mouse;
33-
3434
/**
3535
* Class that draws to a screen using a combination of Swing's JFrame, and AWT's Canvas.
3636
*
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package tech.fastj.graphics.game;
2+
3+
public enum AnimationStyle {
4+
ContinuousLoop,
5+
Static,
6+
PlayUntilEnd
7+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package tech.fastj.graphics.game;
2+
3+
import tech.fastj.graphics.util.DrawUtil;
4+
5+
import tech.fastj.resources.images.ImageResource;
6+
import tech.fastj.resources.images.ImageUtil;
7+
import tech.fastj.systems.control.Scene;
8+
import tech.fastj.systems.control.SimpleManager;
9+
10+
import java.awt.Graphics2D;
11+
import java.awt.geom.AffineTransform;
12+
import java.awt.image.BufferedImage;
13+
import java.util.Arrays;
14+
import java.util.Objects;
15+
import java.util.concurrent.Executors;
16+
import java.util.concurrent.ScheduledExecutorService;
17+
import java.util.concurrent.TimeUnit;
18+
19+
public class Sprite2D extends GameObject {
20+
21+
public static final int DefaultStartingFrame = 0;
22+
public static final int DefaultAnimationFPS = 12;
23+
public static final AnimationStyle DefaultAnimationStyle = AnimationStyle.ContinuousLoop;
24+
public static final int DefaultHorizontalImageCount = 1;
25+
public static final int DefaultVerticalImageCount = 1;
26+
27+
private ImageResource spritesResource;
28+
private BufferedImage[] sprites;
29+
private int currentFrame;
30+
private int animationFPS = DefaultAnimationFPS;
31+
private AnimationStyle animationStyle;
32+
33+
private ScheduledExecutorService spriteAnimator;
34+
35+
Sprite2D(ImageResource spritesResource, int horizontalImageCount, int verticalImageCount) {
36+
this.spritesResource = spritesResource;
37+
setCollisionPath(DrawUtil.createPath(DrawUtil.createBoxFromImage(sprites[0])));
38+
resetSpriteSheet(horizontalImageCount, verticalImageCount);
39+
resetSpriteAnimator();
40+
}
41+
42+
public static Sprite2DBuilder create(ImageResource spritesResource) {
43+
return new Sprite2DBuilder(spritesResource);
44+
}
45+
46+
public static Sprite2D fromImageResource(ImageResource spritesResource) {
47+
return new Sprite2DBuilder(spritesResource).build();
48+
}
49+
50+
public void changeSpriteResource(ImageResource spritesResource, int horizontalImageCount, int verticalImageCount) {
51+
this.spritesResource = spritesResource;
52+
resetSpriteSheet(horizontalImageCount, verticalImageCount);
53+
}
54+
55+
public int getCurrentFrame() {
56+
return currentFrame;
57+
}
58+
59+
public int getAnimationFPS() {
60+
return animationFPS;
61+
}
62+
63+
public AnimationStyle getAnimationStyle() {
64+
return animationStyle;
65+
}
66+
67+
public Sprite2D setCurrentFrame(int currentFrame) {
68+
this.currentFrame = currentFrame;
69+
return this;
70+
}
71+
72+
public Sprite2D setAnimationFPS(int animationFPS) {
73+
this.animationFPS = animationFPS;
74+
resetSpriteAnimator();
75+
return this;
76+
}
77+
78+
public Sprite2D setAnimationStyle(AnimationStyle animationStyle) {
79+
this.animationStyle = animationStyle;
80+
return this;
81+
}
82+
83+
@Override
84+
public void render(Graphics2D g) {
85+
if (!shouldRender()) {
86+
return;
87+
}
88+
89+
AffineTransform oldTransform = (AffineTransform) g.getTransform().clone();
90+
g.transform(getTransformation());
91+
92+
g.drawImage(sprites[currentFrame], null, null);
93+
94+
g.setTransform(oldTransform);
95+
}
96+
97+
@Override
98+
public void destroy(Scene origin) {
99+
spriteAnimator.shutdownNow();
100+
spriteAnimator = null;
101+
sprites = null;
102+
currentFrame = -1;
103+
animationFPS = -1;
104+
animationStyle = null;
105+
106+
super.destroyTheRest(origin);
107+
}
108+
109+
@Override
110+
public void destroy(SimpleManager origin) {
111+
spriteAnimator.shutdownNow();
112+
sprites = null;
113+
currentFrame = -1;
114+
animationFPS = -1;
115+
animationStyle = null;
116+
117+
super.destroyTheRest(origin);
118+
}
119+
120+
private void resetSpriteSheet(int horizontalImageCount, int verticalImageCount) {
121+
sprites = ImageUtil.createSpriteSheet(this.spritesResource.get(), horizontalImageCount, verticalImageCount);
122+
}
123+
124+
private void resetSpriteAnimator() {
125+
if (spriteAnimator != null) {
126+
spriteAnimator.shutdownNow();
127+
spriteAnimator = null;
128+
}
129+
130+
spriteAnimator = Executors.newSingleThreadScheduledExecutor();
131+
spriteAnimator.scheduleAtFixedRate(
132+
() -> {
133+
switch (animationStyle) {
134+
case Static: {
135+
break;
136+
}
137+
case ContinuousLoop: {
138+
currentFrame++;
139+
if (currentFrame == sprites.length) {
140+
currentFrame = 0;
141+
}
142+
break;
143+
}
144+
case PlayUntilEnd: {
145+
if (currentFrame < sprites.length - 1) {
146+
currentFrame++;
147+
}
148+
break;
149+
}
150+
}
151+
},
152+
1000 / animationFPS,
153+
1000 / animationFPS,
154+
TimeUnit.MILLISECONDS
155+
);
156+
}
157+
158+
@Override
159+
public boolean equals(Object other) {
160+
if (this == other) {
161+
return true;
162+
}
163+
if (other == null || getClass() != other.getClass()) {
164+
return false;
165+
}
166+
if (!super.equals(other)) {
167+
return false;
168+
}
169+
Sprite2D sprite2D = (Sprite2D) other;
170+
return currentFrame == sprite2D.currentFrame
171+
&& animationFPS == sprite2D.animationFPS
172+
&& animationStyle == sprite2D.animationStyle
173+
&& Arrays.equals(sprites, sprite2D.sprites);
174+
}
175+
176+
@Override
177+
public int hashCode() {
178+
int result = Objects.hash(currentFrame, animationFPS, animationStyle);
179+
result = 31 * result + Arrays.hashCode(sprites);
180+
return result;
181+
}
182+
183+
@Override
184+
public String toString() {
185+
return "Sprite2D{" +
186+
"sprites=" + Arrays.toString(sprites) +
187+
", currentFrame=" + currentFrame +
188+
", animationFPS=" + animationFPS +
189+
", animationStyle=" + animationStyle +
190+
'}';
191+
}
192+
}

0 commit comments

Comments
 (0)