diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/animation/Animated.java b/api/src/main/java/com/bartlomiejpluta/base/api/animation/Animated.java
index 57f07dd9..c46bc091 100644
--- a/api/src/main/java/com/bartlomiejpluta/base/api/animation/Animated.java
+++ b/api/src/main/java/com/bartlomiejpluta/base/api/animation/Animated.java
@@ -2,20 +2,141 @@ package com.bartlomiejpluta.base.api.animation;
import com.bartlomiejpluta.base.internal.program.Updatable;
+/**
+ * Represents an object that can be animated by cycling through multiple frames of a texture.
+ *
+ *
This interface provides a complete animation system for 2D sprites and other visual objects
+ * in the game engine. Animated objects automatically cycle through their available frames at a
+ * specified speed when animation is enabled.
+ *
+ *
The animation system works by:
+ *
+ * - Maintaining an internal timer that tracks elapsed time
+ * - Automatically advancing to the next frame when enough time has passed
+ * - Cycling through available frames in sequence
+ * - Supporting both full texture animation and subset-based animation
+ *
+ *
+ * Animation can be controlled in real-time through enable/disable methods and speed adjustments.
+ * The animation automatically resets and pauses when disabled.
+ *
+ *
This interface extends {@link Updatable}, meaning animated objects are automatically
+ * updated each frame by the game engine's update loop.
+ */
public interface Animated extends Updatable {
+
+ /**
+ * Returns whether animation is currently enabled for this object.
+ *
+ *
When animation is disabled, the object remains on its current frame
+ * and the internal animation timer is reset to zero.
+ *
+ * @return {@code true} if animation is enabled, {@code false} otherwise
+ */
boolean isAnimationEnabled();
+ /**
+ * Enables or disables animation for this object.
+ *
+ *
When animation is disabled:
+ *
+ * - The object stops cycling through frames
+ * - The current frame remains displayed
+ * - The internal animation timer is reset to zero
+ *
+ *
+ * When animation is enabled, the object resumes cycling through frames
+ * at the configured animation speed.
+ *
+ * @param enabled {@code true} to enable animation, {@code false} to disable
+ */
void setAnimationEnabled(boolean enabled);
- void enableAnimation();
+ /**
+ * Enables animation for this object.
+ *
+ *
This is a convenience method equivalent to calling
+ * {@code setAnimationEnabled(true)}.
+ *
+ * @see #setAnimationEnabled(boolean)
+ */
+ default void enableAnimation() {
+ setAnimationEnabled(true);
+ }
- void disableAnimation();
+ /**
+ * Disables animation for this object.
+ *
+ *
This is a convenience method equivalent to calling
+ * {@code setAnimationEnabled(false)}. The object will remain on its
+ * current frame and the animation timer will be reset.
+ *
+ * @see #setAnimationEnabled(boolean)
+ */
+ default void disableAnimation() {
+ setAnimationEnabled(false);
+ }
- void toggleAnimationEnabled();
+ /**
+ * Toggles the animation enabled state.
+ *
+ *
If animation is currently enabled, it will be disabled.
+ * If animation is currently disabled, it will be enabled.
+ *
+ * @see #isAnimationEnabled()
+ * @see #setAnimationEnabled(boolean)
+ */
+ default void toggleAnimationEnabled() {
+ setAnimationEnabled(!isAnimationEnabled());
+ }
+ /**
+ * Returns the current animation speed.
+ *
+ *
Animation speed determines how quickly the object cycles through its frames.
+ * Higher values result in faster animation, while lower values create slower animation.
+ *
+ *
The speed is typically measured in relation to the game engine's target
+ * update rate, where a speed of 1.0 represents normal timing.
+ * If 60PFS (target update rate) is maintained, it should be measured in frames per second.
+ *
+ * @return the current animation speed as a positive float value
+ */
float getAnimationSpeed();
+ /**
+ * Sets the animation speed for this object.
+ *
+ *
Animation speed controls how quickly frames are cycled during animation.
+ * The speed value is typically:
+ *
+ * - 1.0 for normal speed animation
+ * - Greater than 1.0 for faster animation
+ * - Between 0.0 and 1.0 for slower animation
+ * - Values are automatically clamped to prevent invalid speeds
+ *
+ *
+ * The actual frame timing is calculated based on the game engine's target
+ * update rate and the specified speed multiplier.
+ * If 60PFS (target update rate) is maintained, it should be measured in frames per second.
+ *
+ * @param speed the new animation speed as a positive float value
+ * @throws IllegalArgumentException if speed is negative or zero
+ */
void setAnimationSpeed(float speed);
+ /**
+ * Manually sets the current animation frame.
+ *
+ *
This method allows direct control over which frame is currently displayed,
+ * bypassing the automatic animation timing. The frame index is automatically
+ * wrapped if it exceeds the available frame count.
+ *
+ *
+ *
Frame indices are zero-based, and values that exceed the available frame
+ * count are automatically wrapped using modulo operation.
+ *
+ * @param frame the zero-based index of the frame to display
+ */
void setAnimationFrame(int frame);
}
diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/character/Character.java b/api/src/main/java/com/bartlomiejpluta/base/api/character/Character.java
index f88770e6..dd82628c 100644
--- a/api/src/main/java/com/bartlomiejpluta/base/api/character/Character.java
+++ b/api/src/main/java/com/bartlomiejpluta/base/api/character/Character.java
@@ -8,15 +8,103 @@ import com.bartlomiejpluta.base.api.move.Movement;
import java.util.concurrent.CompletableFuture;
+/**
+ * Represents a game world object that can face four cardinal directions and uses
+ * a special texture format called a character set (charset).
+ *
+ *
A character set is a sprite sheet organized in a 4-row, multi-column format where:
+ *
+ * - Row 1: Graphics for facing DOWN
+ * - Row 2: Graphics for facing LEFT
+ * - Row 3: Graphics for facing RIGHT
+ * - Row 4: Graphics for facing UP
+ *
+ *
+ * Each column represents a different animation frame or object variant. For animated
+ * characters, columns contain sequential animation frames. For static objects (like boxes),
+ * each column may represent different object types or states (e.g., normal box, colored box,
+ * damaged box).
+ *
+ *
This interface combines movement capabilities ({@link Movable}), animation features
+ * ({@link Animated}), and entity properties ({@link Entity}) to create a complete
+ * character system for 2D games.
+ */
public interface Character extends Movable, Animated, Entity {
+ /**
+ * Initiates movement in the specified direction.
+ *
+ *
This method handles the character's movement mechanics and automatically
+ * updates the facing direction to match the movement direction.
+ *
+ * @param direction the direction to move towards
+ * @return a {@link Movement} object representing the movement operation
+ * @throws IllegalArgumentException if direction is null
+ */
Movement move(Direction direction);
+ /**
+ * Returns the current facing direction of the character.
+ *
+ *
The facing direction determines which row of the character set
+ * is used for rendering and animation.
+ *
+ * @return the current facing direction
+ */
Direction getFaceDirection();
+ /**
+ * Sets the default sprite column to use from the character set.
+ *
+ *
This method is particularly useful for character sets that contain
+ * multiple object variants in different columns. For example, if a character
+ * set contains different box types in each column, this method selects
+ * which box type to display.
+ *
+ *
The column index is zero-based and must be within the bounds of the
+ * current character set.
+ *
+ * @param column the zero-based column index to use as default
+ * @throws IndexOutOfBoundsException if the column index is invalid
+ */
+ void setDefaultSpriteColumn(int column);
+
+ /**
+ * Changes the character's facing direction without initiating movement.
+ *
+ *
This method updates which row of the character set is used for rendering.
+ * It can be used to create custom animations by cycling through different
+ * facing directions, or to orient the character without moving.
+ *
+ * @param direction the new facing direction
+ * @throws IllegalArgumentException if direction is null
+ */
void setFaceDirection(Direction direction);
+ /**
+ * Changes the character set (sprite sheet) used by this character.
+ *
+ *
This allows dynamic switching between different visual representations
+ * of the character, such as different costumes, character states, or
+ * completely different character types.
+ *
+ * @param characterSetUid the unique identifier of the new character set
+ * @throws IllegalArgumentException if characterSetUid is null or empty
+ */
void changeCharacterSet(String characterSetUid);
+ /**
+ * Performs an instant animation using all frames from the current facing direction.
+ *
+ *
This method executes an animation sequence by cycling through all columns
+ * of the row corresponding to the character's current facing direction. For example,
+ * if the character is facing UP, the animation will use all frames from the fourth
+ * row of the character set.
+ *
+ *
The animation runs asynchronously and the returned {@link CompletableFuture}
+ * completes when the animation finishes.
+ *
+ * @return a {@link CompletableFuture} that completes when the animation finishes
+ */
CompletableFuture performInstantAnimation();
}
diff --git a/api/src/main/java/com/bartlomiejpluta/base/lib/character/CharacterDelegate.java b/api/src/main/java/com/bartlomiejpluta/base/lib/character/CharacterDelegate.java
index 0def00c3..5479ce34 100644
--- a/api/src/main/java/com/bartlomiejpluta/base/lib/character/CharacterDelegate.java
+++ b/api/src/main/java/com/bartlomiejpluta/base/lib/character/CharacterDelegate.java
@@ -293,6 +293,11 @@ public abstract class CharacterDelegate implements Character {
character.setZIndex(zIndex);
}
+ @Override
+ public void setDefaultSpriteColumn(int column) {
+ character.setDefaultSpriteColumn(column);
+ }
+
@Override
public void handleEvent(E event) {
character.handleEvent(event);
diff --git a/api/src/main/java/com/bartlomiejpluta/base/util/world/MapObject.java b/api/src/main/java/com/bartlomiejpluta/base/util/world/MapObject.java
index e5611ac6..91db20d9 100644
--- a/api/src/main/java/com/bartlomiejpluta/base/util/world/MapObject.java
+++ b/api/src/main/java/com/bartlomiejpluta/base/util/world/MapObject.java
@@ -42,7 +42,8 @@ public abstract class MapObject extends CharacterDelegate {
this.frame = frame;
setBlocking(true);
disableAnimation();
- setAnimationFrame(frame);
+ setDefaultSpriteColumn(frame);
+ setFaceDirection(DOWN);
pathExecutor.setRepeat(1);
initPath();
diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/animation/model/AnimatedSprite.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/animation/model/AnimatedSprite.java
index e28eb289..4f2dea93 100644
--- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/animation/model/AnimatedSprite.java
+++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/animation/model/AnimatedSprite.java
@@ -6,7 +6,6 @@ import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
import com.bartlomiejpluta.base.engine.world.object.Sprite;
import com.bartlomiejpluta.base.util.math.MathUtil;
import lombok.EqualsAndHashCode;
-import org.joml.Vector2fc;
@EqualsAndHashCode(callSuper = true)
public abstract class AnimatedSprite extends Sprite implements Animated {
@@ -22,9 +21,51 @@ public abstract class AnimatedSprite extends Sprite implements Animated {
protected abstract boolean shouldAnimate();
- protected abstract Vector2fc[] getSpriteAnimationFramesPositions();
+ /**
+ * Allows subclasses to restrict animation to a specific subset of frames.
+ *
+ * By default, this method returns {@code null}, which means all frames in the texture
+ * are available for animation. In this case, the frame IDs used by {@link #setAnimationFrame(int)}
+ * correspond directly to the global frame indices in the texture, as shown below:
+ *
+ *
+ * +----+----+----+----+
+ * | 00 | 01 | 02 | 03 |
+ * +----+----+----+----+
+ * | 04 | 05 | 06 | 07 |
+ * +----+----+----+----+
+ * | 08 | 09 | 10 | 11 |
+ * +----+----+----+----+
+ * | 12 | 13 | 14 | 15 |
+ * +----+----+----+----+
+ *
+ *
+ * However, if this method returns a specific array of frame indices (e.g., {@code {8, 9, 10, 11, 13, 15}}),
+ * only those frames will be used for animation. The frame IDs are then remapped, where index 0
+ * corresponds to the first frame in the subset, index 1 to the second, and so on:
+ *
+ *
+ * +----+----+----+----+
+ * | | | | |
+ * +----+----+----+----+
+ * | | | | |
+ * +----+----+----+----+
+ * | 00 | 01 | 02 | 03 |
+ * +----+----+----+----+
+ * | | 04 | | 05 |
+ * +----+----+----+----+
+ *
+ *
+ * Important: When this method returns {@code null}, calling
+ * {@link #setAnimationFrame(int)} has the same effect as calling {@code setFrame(int)}
+ * directly, since the frame IDs map directly to the global texture frame indices.
+ * When a subset is defined, {@link #setAnimationFrame(int)} uses local indices
+ * within that subset.
+ *
+ * @return an array of global frame IDs to use for animation, or {@code null} to use all frames
+ */
- protected int[] getAvailableFrames() {
+ protected int[] getAvailableFramesSubset() {
return null;
}
@@ -40,7 +81,7 @@ public abstract class AnimatedSprite extends Sprite implements Animated {
@Override
public void setAnimationFrame(int frame) {
- var availableFrames = getAvailableFrames();
+ var availableFrames = getAvailableFramesSubset();
if (availableFrames == null) {
setFrame(frame % getTextureCoordinates().length);
@@ -55,11 +96,9 @@ public abstract class AnimatedSprite extends Sprite implements Animated {
if (shouldAnimate()) {
time += dt * 1000;
setAnimationFrame(time / intervalInMilliseconds * intervalInMilliseconds);
-// var maxFrames = getTextureCoordinates().length;
-// currentAnimationFrame = ((time % (maxFrames * intervalInMilliseconds)) / intervalInMilliseconds);
-// setSprite(currentAnimationFrame);
- } else {
- time = 0;
+ return;
}
+
+ time = 0;
}
}
diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/animation/model/DefaultAnimation.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/animation/model/DefaultAnimation.java
index 3f434866..6fcf7ffc 100644
--- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/animation/model/DefaultAnimation.java
+++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/animation/model/DefaultAnimation.java
@@ -6,7 +6,6 @@ import com.bartlomiejpluta.base.api.map.layer.object.ObjectLayer;
import com.bartlomiejpluta.base.api.move.AnimationMovement;
import com.bartlomiejpluta.base.api.move.Direction;
import com.bartlomiejpluta.base.api.move.Movement;
-import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
import com.bartlomiejpluta.base.engine.world.movement.MovableSprite;
import com.bartlomiejpluta.base.util.path.Path;
@@ -90,11 +89,6 @@ public class DefaultAnimation extends MovableSprite implements Animation {
return enabled;
}
- @Override
- protected Vector2fc[] getSpriteAnimationFramesPositions() {
- return frames;
- }
-
@Override
protected void setDefaultAnimationFrame() {
// do nothing
diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/CharacterSetManager.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/CharacterSetManager.java
index 5f9f75f1..9f273271 100644
--- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/CharacterSetManager.java
+++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/CharacterSetManager.java
@@ -1,9 +1,8 @@
package com.bartlomiejpluta.base.engine.world.character.manager;
import com.bartlomiejpluta.base.engine.common.manager.AssetManager;
-import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
-import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
import com.bartlomiejpluta.base.engine.world.character.asset.CharacterSetAsset;
+import com.bartlomiejpluta.base.engine.world.character.model.CharacterSet;
-public interface CharacterSetManager extends AssetManager {
+public interface CharacterSetManager extends AssetManager {
}
diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/DefaultCharacterManager.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/DefaultCharacterManager.java
index 79f88d61..313aed2e 100644
--- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/DefaultCharacterManager.java
+++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/DefaultCharacterManager.java
@@ -1,44 +1,21 @@
package com.bartlomiejpluta.base.engine.world.character.manager;
import com.bartlomiejpluta.base.api.character.Character;
-import com.bartlomiejpluta.base.api.move.Direction;
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
import com.bartlomiejpluta.base.engine.world.character.config.CharacterSpriteConfiguration;
import com.bartlomiejpluta.base.engine.world.character.model.DefaultCharacter;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.joml.Vector2f;
-import org.joml.Vector2fc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import static java.util.stream.Collectors.toUnmodifiableMap;
-
@Slf4j
@Component
+@RequiredArgsConstructor(onConstructor_ = @__(@Autowired))
public class DefaultCharacterManager implements CharacterManager {
private final MeshManager meshManager;
private final CharacterSetManager characterSetManager;
-
- private final int defaultSpriteColumn;
- private final Map spriteDirectionRows;
- private final Map spriteDefaultRows;
-
- @Autowired
- public DefaultCharacterManager(MeshManager meshManager, CharacterSetManager characterSetManager, CharacterSpriteConfiguration configuration) {
- this.meshManager = meshManager;
- this.characterSetManager = characterSetManager;
-
- this.spriteDirectionRows = configuration.getSpriteDirectionRows();
-
- defaultSpriteColumn = configuration.getDefaultSpriteColumn();
- this.spriteDefaultRows = spriteDirectionRows
- .entrySet()
- .stream()
- .collect(toUnmodifiableMap(Entry::getKey, entry -> new Vector2f(defaultSpriteColumn, entry.getValue())));
- }
+ private final CharacterSpriteConfiguration configuration;
@Override
public void init() {
@@ -47,7 +24,7 @@ public class DefaultCharacterManager implements CharacterManager {
@Override
public Character createCharacter(String characterSetUid) {
- return new DefaultCharacter(characterSetManager, defaultSpriteColumn, spriteDirectionRows, spriteDefaultRows, characterSetUid);
+ return new DefaultCharacter(characterSetManager, characterSetUid, configuration.getDefaultSpriteColumn());
}
@Override
diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/DefaultCharacterSetManager.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/DefaultCharacterSetManager.java
index 29400619..42b2331e 100644
--- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/DefaultCharacterSetManager.java
+++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/manager/DefaultCharacterSetManager.java
@@ -1,11 +1,11 @@
package com.bartlomiejpluta.base.engine.world.character.manager;
-import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
-import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
import com.bartlomiejpluta.base.engine.core.gl.object.texture.TextureManager;
import com.bartlomiejpluta.base.engine.error.AppException;
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
import com.bartlomiejpluta.base.engine.world.character.asset.CharacterSetAsset;
+import com.bartlomiejpluta.base.engine.world.character.config.CharacterSpriteConfiguration;
+import com.bartlomiejpluta.base.engine.world.character.model.CharacterSet;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -20,6 +20,8 @@ import java.util.Map;
public class DefaultCharacterSetManager implements CharacterSetManager {
private final TextureManager textureManager;
private final Map assets = new HashMap<>();
+ private final Map charSets = new HashMap<>();
+ private final CharacterSpriteConfiguration charsetConfiguration;
private final ProjectConfiguration configuration;
@Override
@@ -34,15 +36,24 @@ public class DefaultCharacterSetManager implements CharacterSetManager {
}
@Override
- public Texture loadObject(String uid) {
- var asset = assets.get(uid);
+ public CharacterSet loadObject(String uid) {
+ var charSet = charSets.get(uid);
- if (asset == null) {
- throw new AppException("The character set asset with UID: [%s] does not exist", uid);
+ if (charSet == null) {
+ var asset = assets.get(uid);
+
+ if (asset == null) {
+ throw new AppException("The character set asset with UID: [%s] does not exist", uid);
+ }
+
+ var source = configuration.projectFile("charsets", asset.getSource());
+
+ var texture = textureManager.loadTexture(source, asset.getRows(), asset.getColumns());
+ charSet = CharacterSet.from(texture, charsetConfiguration.getSpriteDirectionRows());
+ log.info("Loading character set from assets to cache under the key: [{}]", uid);
+ charSets.put(uid, charSet);
}
- var source = configuration.projectFile("charsets", asset.getSource());
-
- return textureManager.loadTexture(source, asset.getRows(), asset.getColumns());
+ return charSet;
}
}
diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/model/CharacterSet.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/model/CharacterSet.java
new file mode 100644
index 00000000..ca4013a6
--- /dev/null
+++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/model/CharacterSet.java
@@ -0,0 +1,74 @@
+package com.bartlomiejpluta.base.engine.world.character.model;
+
+import com.bartlomiejpluta.base.api.move.Direction;
+import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
+import lombok.Getter;
+import lombok.NonNull;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import static com.bartlomiejpluta.base.api.move.Direction.values;
+import static java.util.Arrays.stream;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toUnmodifiableMap;
+import static java.util.stream.IntStream.range;
+
+@Getter
+public class CharacterSet {
+ private final Texture texture;
+ private final Map frames;
+
+ private CharacterSet(@NonNull Texture texture, @NonNull Map charsetRowsByDirections) {
+ this.texture = texture;
+ this.frames = stream(values())
+ .collect(toUnmodifiableMap(identity(), calculateFrames(charsetRowsByDirections)));
+ }
+
+ private Function calculateFrames(Map charsetRowsByDirections) {
+ return d -> framesForRow(charsetRowsByDirections.get(d));
+ }
+
+ private int[] framesForRow(int row) {
+ var cols = texture.getColumns();
+ return range(row * cols, (row + 1) * cols).toArray();
+ }
+
+ /**
+ * Creates a new CharacterSet instance from the specified texture and direction-to-row mapping.
+ *
+ * This factory method constructs a CharacterSet by associating each facing direction
+ * with its corresponding row in the character set texture. The method automatically
+ * calculates frame indices for each direction based on the texture dimensions and
+ * row mappings.
+ *
+ *
The character set texture should be organized as a grid where:
+ *
+ * - Each row represents graphics for a specific facing direction
+ * - Each column represents either an animation frame or object variant
+ * - Row indices are zero-based (0 = first row, 1 = second row, etc.)
+ *
+ *
+ * Example mapping:
+ *
{@code
+ * Map mapping = Map.of(
+ * Direction.DOWN, 0, // First row (index 0) for DOWN direction
+ * Direction.LEFT, 1, // Second row (index 1) for LEFT direction
+ * Direction.RIGHT, 2, // Third row (index 2) for RIGHT direction
+ * Direction.UP, 3 // Fourth row (index 3) for UP direction
+ * );
+ * CharacterSet charset = CharacterSet.from(texture, mapping);
+ * }
+ *
+ * @param texture the character set texture containing sprite graphics organized in rows and columns
+ * @param charsetRowsByDirections a mapping that defines which texture row corresponds to each facing direction,
+ * where the key is the direction and the value is the zero-based row index
+ * @return a new CharacterSet instance configured with the specified texture and direction mappings
+ * @throws NullPointerException if either texture or charsetRowsByDirections is null
+ * @throws IllegalArgumentException if the charsetRowsByDirections map is empty or contains invalid row indices
+ * @throws IndexOutOfBoundsException if any row index in the mapping exceeds the texture's row count
+ */
+ public static CharacterSet from(Texture texture, Map charsetRowsByDirections) {
+ return new CharacterSet(texture, charsetRowsByDirections);
+ }
+}
diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/model/DefaultCharacter.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/model/DefaultCharacter.java
index 23169b34..d43a21a3 100644
--- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/model/DefaultCharacter.java
+++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/character/model/DefaultCharacter.java
@@ -7,8 +7,6 @@ import com.bartlomiejpluta.base.api.map.layer.object.ObjectLayer;
import com.bartlomiejpluta.base.api.move.CharacterMovement;
import com.bartlomiejpluta.base.api.move.Direction;
import com.bartlomiejpluta.base.api.move.Movement;
-import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
-import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
import com.bartlomiejpluta.base.engine.error.AppException;
import com.bartlomiejpluta.base.engine.world.character.manager.CharacterSetManager;
import com.bartlomiejpluta.base.engine.world.movement.MovableSprite;
@@ -21,7 +19,6 @@ import org.joml.Vector2f;
import org.joml.Vector2fc;
import java.util.LinkedList;
-import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -30,24 +27,15 @@ import static java.util.Objects.requireNonNull;
@EqualsAndHashCode(callSuper = true)
public class DefaultCharacter extends MovableSprite implements Character {
- private static final Map CHARSET_FRAMES = Map.of(
- Direction.DOWN, new int[]{0, 1, 2, 3},
- Direction.LEFT, new int[]{4, 5, 6, 7},
- Direction.RIGHT, new int[]{8, 9, 10, 11},
- Direction.UP, new int[]{12, 13, 14, 15}
- );
-
- private static final int DEFAULT_CHARSET_FRAME_COLUMN = 0;
-
- private final int defaultSpriteColumn;
private final CharacterSetManager characterSetManager;
- private final Map spriteDirectionRows;
- private final Map spriteDefaultRows;
private final Vector2f characterScale = new Vector2f(1, 1);
- private Texture texture;
- private Vector2fc characterSetSize;
-
private final EventHandler eventHandler = new EventHandler();
+ private final Queue instantAnimations = new LinkedList<>();
+
+ @Setter
+ private int defaultSpriteColumn;
+ private CharacterSet characterSet;
+ private Vector2fc characterSetSize;
@Getter
@Setter
@@ -65,22 +53,19 @@ public class DefaultCharacter extends MovableSprite implements Character {
private boolean animationEnabled = true;
- private final Queue instantAnimations = new LinkedList<>();
-
- public DefaultCharacter(CharacterSetManager characterSetManager, int defaultSpriteColumn, @NonNull Map spriteDirectionRows, Map spriteDefaultRows, @NonNull String characterSetUid) {
- this(characterSetManager.loadObject(characterSetUid), characterSetManager, defaultSpriteColumn, spriteDirectionRows, spriteDefaultRows);
+ public DefaultCharacter(CharacterSetManager characterSetManager, @NonNull String characterSetUid, int defaultSpriteColumn) {
+ this(characterSetManager.loadObject(characterSetUid), characterSetManager, defaultSpriteColumn);
}
- private DefaultCharacter(@NonNull Texture texture, @NonNull CharacterSetManager characterSetManager, int defaultSpriteColumn, @NonNull Map spriteDirectionRows, @NonNull Map spriteDefaultRows) {
- super(texture);
+ private DefaultCharacter(@NonNull CharacterSet characterSet, @NonNull CharacterSetManager characterSetManager, int defaultSpriteColumn) {
+ super(characterSet.getTexture());
+
this.defaultSpriteColumn = defaultSpriteColumn;
this.characterSetManager = characterSetManager;
- this.spriteDirectionRows = spriteDirectionRows;
this.faceDirection = Direction.DOWN;
- this.spriteDefaultRows = spriteDefaultRows;
+ this.characterSet = characterSet;
+ this.characterSetSize = characterSet.getTexture().getSpriteSize();
- this.texture = texture;
- this.characterSetSize = texture.getSpriteSize();
super.setScale(characterSetSize.x() * characterScale.x, characterSetSize.y() * characterScale.y);
setDefaultAnimationFrame();
@@ -117,35 +102,21 @@ public class DefaultCharacter extends MovableSprite implements Character {
}
@Override
- protected Vector2fc[] getSpriteAnimationFramesPositions() {
- var row = spriteDirectionRows.get(faceDirection);
- var frames = getMaterial().getColumns();
- var array = new Vector2f[frames];
-
- for (int column = 0; column < frames; ++column) {
- array[column] = new Vector2f(column, row);
- }
-
- return array;
- }
-
- @Override
- protected int[] getAvailableFrames() {
- return CHARSET_FRAMES.get(faceDirection);
+ protected int[] getAvailableFramesSubset() {
+ return characterSet.getFrames().get(faceDirection);
}
@Override
protected void setDefaultAnimationFrame() {
-// getMaterial().setSpritePosition(spriteDefaultRows.get(faceDirection));
- setAnimationFrame(CHARSET_FRAMES.get(faceDirection)[DEFAULT_CHARSET_FRAME_COLUMN]);
+ setAnimationFrame(characterSet.getFrames().get(faceDirection)[defaultSpriteColumn]);
}
@Override
public void changeCharacterSet(String characterSetUid) {
- this.texture = characterSetManager.loadObject(characterSetUid);
- this.characterSetSize = texture.getSpriteSize();
+ this.characterSet = characterSetManager.loadObject(characterSetUid);
+ this.characterSetSize = characterSet.getTexture().getSpriteSize();
super.setScale(characterSetSize.x() * characterScale.x, characterSetSize.y() * characterScale.y);
- setMaterial(texture);
+ setMaterial(characterSet.getTexture());
}
@Override