Refactor animations
This commit is contained in:
@@ -2,20 +2,141 @@ package com.bartlomiejpluta.base.api.animation;
|
|||||||
|
|
||||||
import com.bartlomiejpluta.base.internal.program.Updatable;
|
import com.bartlomiejpluta.base.internal.program.Updatable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an object that can be animated by cycling through multiple frames of a texture.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>The animation system works by:
|
||||||
|
* <ul>
|
||||||
|
* <li>Maintaining an internal timer that tracks elapsed time</li>
|
||||||
|
* <li>Automatically advancing to the next frame when enough time has passed</li>
|
||||||
|
* <li>Cycling through available frames in sequence</li>
|
||||||
|
* <li>Supporting both full texture animation and subset-based animation</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Animation can be controlled in real-time through enable/disable methods and speed adjustments.
|
||||||
|
* The animation automatically resets and pauses when disabled.
|
||||||
|
*
|
||||||
|
* <p>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 {
|
public interface Animated extends Updatable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether animation is currently enabled for this object.
|
||||||
|
*
|
||||||
|
* <p>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();
|
boolean isAnimationEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables animation for this object.
|
||||||
|
*
|
||||||
|
* <p>When animation is disabled:
|
||||||
|
* <ul>
|
||||||
|
* <li>The object stops cycling through frames</li>
|
||||||
|
* <li>The current frame remains displayed</li>
|
||||||
|
* <li>The internal animation timer is reset to zero</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>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 setAnimationEnabled(boolean enabled);
|
||||||
|
|
||||||
void enableAnimation();
|
/**
|
||||||
|
* Enables animation for this object.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>Animation speed determines how quickly the object cycles through its frames.
|
||||||
|
* Higher values result in faster animation, while lower values create slower animation.
|
||||||
|
*
|
||||||
|
* <p>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();
|
float getAnimationSpeed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the animation speed for this object.
|
||||||
|
*
|
||||||
|
* <p>Animation speed controls how quickly frames are cycled during animation.
|
||||||
|
* The speed value is typically:
|
||||||
|
* <ul>
|
||||||
|
* <li>1.0 for normal speed animation</li>
|
||||||
|
* <li>Greater than 1.0 for faster animation</li>
|
||||||
|
* <li>Between 0.0 and 1.0 for slower animation</li>
|
||||||
|
* <li>Values are automatically clamped to prevent invalid speeds</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>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);
|
void setAnimationSpeed(float speed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually sets the current animation frame.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <p>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);
|
void setAnimationFrame(int frame);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,103 @@ import com.bartlomiejpluta.base.api.move.Movement;
|
|||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
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).
|
||||||
|
*
|
||||||
|
* <p>A character set is a sprite sheet organized in a 4-row, multi-column format where:
|
||||||
|
* <ul>
|
||||||
|
* <li>Row 1: Graphics for facing DOWN</li>
|
||||||
|
* <li>Row 2: Graphics for facing LEFT</li>
|
||||||
|
* <li>Row 3: Graphics for facing RIGHT</li>
|
||||||
|
* <li>Row 4: Graphics for facing UP</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>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).
|
||||||
|
*
|
||||||
|
* <p>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 {
|
public interface Character extends Movable, Animated, Entity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates movement in the specified direction.
|
||||||
|
*
|
||||||
|
* <p>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);
|
Movement move(Direction direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current facing direction of the character.
|
||||||
|
*
|
||||||
|
* <p>The facing direction determines which row of the character set
|
||||||
|
* is used for rendering and animation.
|
||||||
|
*
|
||||||
|
* @return the current facing direction
|
||||||
|
*/
|
||||||
Direction getFaceDirection();
|
Direction getFaceDirection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default sprite column to use from the character set.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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);
|
void setFaceDirection(Direction direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the character set (sprite sheet) used by this character.
|
||||||
|
*
|
||||||
|
* <p>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);
|
void changeCharacterSet(String characterSetUid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an instant animation using all frames from the current facing direction.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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<Void> performInstantAnimation();
|
CompletableFuture<Void> performInstantAnimation();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,6 +293,11 @@ public abstract class CharacterDelegate implements Character {
|
|||||||
character.setZIndex(zIndex);
|
character.setZIndex(zIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDefaultSpriteColumn(int column) {
|
||||||
|
character.setDefaultSpriteColumn(column);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <E extends Event> void handleEvent(E event) {
|
public <E extends Event> void handleEvent(E event) {
|
||||||
character.handleEvent(event);
|
character.handleEvent(event);
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ public abstract class MapObject extends CharacterDelegate {
|
|||||||
this.frame = frame;
|
this.frame = frame;
|
||||||
setBlocking(true);
|
setBlocking(true);
|
||||||
disableAnimation();
|
disableAnimation();
|
||||||
setAnimationFrame(frame);
|
setDefaultSpriteColumn(frame);
|
||||||
|
setFaceDirection(DOWN);
|
||||||
pathExecutor.setRepeat(1);
|
pathExecutor.setRepeat(1);
|
||||||
|
|
||||||
initPath();
|
initPath();
|
||||||
|
|||||||
@@ -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.engine.world.object.Sprite;
|
||||||
import com.bartlomiejpluta.base.util.math.MathUtil;
|
import com.bartlomiejpluta.base.util.math.MathUtil;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.joml.Vector2fc;
|
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public abstract class AnimatedSprite extends Sprite implements Animated {
|
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 boolean shouldAnimate();
|
||||||
|
|
||||||
protected abstract Vector2fc[] getSpriteAnimationFramesPositions();
|
/**
|
||||||
|
* Allows subclasses to restrict animation to a specific subset of frames.
|
||||||
|
*
|
||||||
|
* <p>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:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* +----+----+----+----+
|
||||||
|
* | 00 | 01 | 02 | 03 |
|
||||||
|
* +----+----+----+----+
|
||||||
|
* | 04 | 05 | 06 | 07 |
|
||||||
|
* +----+----+----+----+
|
||||||
|
* | 08 | 09 | 10 | 11 |
|
||||||
|
* +----+----+----+----+
|
||||||
|
* | 12 | 13 | 14 | 15 |
|
||||||
|
* +----+----+----+----+
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>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:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* +----+----+----+----+
|
||||||
|
* | | | | |
|
||||||
|
* +----+----+----+----+
|
||||||
|
* | | | | |
|
||||||
|
* +----+----+----+----+
|
||||||
|
* | 00 | 01 | 02 | 03 |
|
||||||
|
* +----+----+----+----+
|
||||||
|
* | | 04 | | 05 |
|
||||||
|
* +----+----+----+----+
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p><strong>Important:</strong> 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +81,7 @@ public abstract class AnimatedSprite extends Sprite implements Animated {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAnimationFrame(int frame) {
|
public void setAnimationFrame(int frame) {
|
||||||
var availableFrames = getAvailableFrames();
|
var availableFrames = getAvailableFramesSubset();
|
||||||
|
|
||||||
if (availableFrames == null) {
|
if (availableFrames == null) {
|
||||||
setFrame(frame % getTextureCoordinates().length);
|
setFrame(frame % getTextureCoordinates().length);
|
||||||
@@ -55,11 +96,9 @@ public abstract class AnimatedSprite extends Sprite implements Animated {
|
|||||||
if (shouldAnimate()) {
|
if (shouldAnimate()) {
|
||||||
time += dt * 1000;
|
time += dt * 1000;
|
||||||
setAnimationFrame(time / intervalInMilliseconds * intervalInMilliseconds);
|
setAnimationFrame(time / intervalInMilliseconds * intervalInMilliseconds);
|
||||||
// var maxFrames = getTextureCoordinates().length;
|
return;
|
||||||
// currentAnimationFrame = ((time % (maxFrames * intervalInMilliseconds)) / intervalInMilliseconds);
|
|
||||||
// setSprite(currentAnimationFrame);
|
|
||||||
} else {
|
|
||||||
time = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.AnimationMovement;
|
||||||
import com.bartlomiejpluta.base.api.move.Direction;
|
import com.bartlomiejpluta.base.api.move.Direction;
|
||||||
import com.bartlomiejpluta.base.api.move.Movement;
|
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.core.gl.object.texture.Texture;
|
||||||
import com.bartlomiejpluta.base.engine.world.movement.MovableSprite;
|
import com.bartlomiejpluta.base.engine.world.movement.MovableSprite;
|
||||||
import com.bartlomiejpluta.base.util.path.Path;
|
import com.bartlomiejpluta.base.util.path.Path;
|
||||||
@@ -90,11 +89,6 @@ public class DefaultAnimation extends MovableSprite implements Animation {
|
|||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Vector2fc[] getSpriteAnimationFramesPositions() {
|
|
||||||
return frames;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setDefaultAnimationFrame() {
|
protected void setDefaultAnimationFrame() {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package com.bartlomiejpluta.base.engine.world.character.manager;
|
package com.bartlomiejpluta.base.engine.world.character.manager;
|
||||||
|
|
||||||
import com.bartlomiejpluta.base.engine.common.manager.AssetManager;
|
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.asset.CharacterSetAsset;
|
||||||
|
import com.bartlomiejpluta.base.engine.world.character.model.CharacterSet;
|
||||||
|
|
||||||
public interface CharacterSetManager extends AssetManager<CharacterSetAsset, Texture> {
|
public interface CharacterSetManager extends AssetManager<CharacterSetAsset, CharacterSet> {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,21 @@
|
|||||||
package com.bartlomiejpluta.base.engine.world.character.manager;
|
package com.bartlomiejpluta.base.engine.world.character.manager;
|
||||||
|
|
||||||
import com.bartlomiejpluta.base.api.character.Character;
|
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.util.mesh.MeshManager;
|
||||||
import com.bartlomiejpluta.base.engine.world.character.config.CharacterSpriteConfiguration;
|
import com.bartlomiejpluta.base.engine.world.character.config.CharacterSpriteConfiguration;
|
||||||
import com.bartlomiejpluta.base.engine.world.character.model.DefaultCharacter;
|
import com.bartlomiejpluta.base.engine.world.character.model.DefaultCharacter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.joml.Vector2f;
|
|
||||||
import org.joml.Vector2fc;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toUnmodifiableMap;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
|
@RequiredArgsConstructor(onConstructor_ = @__(@Autowired))
|
||||||
public class DefaultCharacterManager implements CharacterManager {
|
public class DefaultCharacterManager implements CharacterManager {
|
||||||
private final MeshManager meshManager;
|
private final MeshManager meshManager;
|
||||||
private final CharacterSetManager characterSetManager;
|
private final CharacterSetManager characterSetManager;
|
||||||
|
private final CharacterSpriteConfiguration configuration;
|
||||||
private final int defaultSpriteColumn;
|
|
||||||
private final Map<Direction, Integer> spriteDirectionRows;
|
|
||||||
private final Map<Direction, Vector2fc> 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())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() {
|
public void init() {
|
||||||
@@ -47,7 +24,7 @@ public class DefaultCharacterManager implements CharacterManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Character createCharacter(String characterSetUid) {
|
public Character createCharacter(String characterSetUid) {
|
||||||
return new DefaultCharacter(characterSetManager, defaultSpriteColumn, spriteDirectionRows, spriteDefaultRows, characterSetUid);
|
return new DefaultCharacter(characterSetManager, characterSetUid, configuration.getDefaultSpriteColumn());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.bartlomiejpluta.base.engine.world.character.manager;
|
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.core.gl.object.texture.TextureManager;
|
||||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||||
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
|
import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration;
|
||||||
import com.bartlomiejpluta.base.engine.world.character.asset.CharacterSetAsset;
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -20,6 +20,8 @@ import java.util.Map;
|
|||||||
public class DefaultCharacterSetManager implements CharacterSetManager {
|
public class DefaultCharacterSetManager implements CharacterSetManager {
|
||||||
private final TextureManager textureManager;
|
private final TextureManager textureManager;
|
||||||
private final Map<String, CharacterSetAsset> assets = new HashMap<>();
|
private final Map<String, CharacterSetAsset> assets = new HashMap<>();
|
||||||
|
private final Map<String, CharacterSet> charSets = new HashMap<>();
|
||||||
|
private final CharacterSpriteConfiguration charsetConfiguration;
|
||||||
private final ProjectConfiguration configuration;
|
private final ProjectConfiguration configuration;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -34,15 +36,24 @@ public class DefaultCharacterSetManager implements CharacterSetManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Texture loadObject(String uid) {
|
public CharacterSet loadObject(String uid) {
|
||||||
var asset = assets.get(uid);
|
var charSet = charSets.get(uid);
|
||||||
|
|
||||||
if (asset == null) {
|
if (charSet == null) {
|
||||||
throw new AppException("The character set asset with UID: [%s] does not exist", uid);
|
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 charSet;
|
||||||
|
|
||||||
return textureManager.loadTexture(source, asset.getRows(), asset.getColumns());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<Direction, int[]> frames;
|
||||||
|
|
||||||
|
private CharacterSet(@NonNull Texture texture, @NonNull Map<Direction, Integer> charsetRowsByDirections) {
|
||||||
|
this.texture = texture;
|
||||||
|
this.frames = stream(values())
|
||||||
|
.collect(toUnmodifiableMap(identity(), calculateFrames(charsetRowsByDirections)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function<Direction, int[]> calculateFrames(Map<Direction, Integer> 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.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>The character set texture should be organized as a grid where:
|
||||||
|
* <ul>
|
||||||
|
* <li>Each row represents graphics for a specific facing direction</li>
|
||||||
|
* <li>Each column represents either an animation frame or object variant</li>
|
||||||
|
* <li>Row indices are zero-based (0 = first row, 1 = second row, etc.)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Example mapping:
|
||||||
|
* <pre>{@code
|
||||||
|
* Map<Direction, Integer> 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);
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @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<Direction, Integer> charsetRowsByDirections) {
|
||||||
|
return new CharacterSet(texture, charsetRowsByDirections);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.CharacterMovement;
|
||||||
import com.bartlomiejpluta.base.api.move.Direction;
|
import com.bartlomiejpluta.base.api.move.Direction;
|
||||||
import com.bartlomiejpluta.base.api.move.Movement;
|
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.error.AppException;
|
||||||
import com.bartlomiejpluta.base.engine.world.character.manager.CharacterSetManager;
|
import com.bartlomiejpluta.base.engine.world.character.manager.CharacterSetManager;
|
||||||
import com.bartlomiejpluta.base.engine.world.movement.MovableSprite;
|
import com.bartlomiejpluta.base.engine.world.movement.MovableSprite;
|
||||||
@@ -21,7 +19,6 @@ import org.joml.Vector2f;
|
|||||||
import org.joml.Vector2fc;
|
import org.joml.Vector2fc;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -30,24 +27,15 @@ import static java.util.Objects.requireNonNull;
|
|||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class DefaultCharacter extends MovableSprite implements Character {
|
public class DefaultCharacter extends MovableSprite implements Character {
|
||||||
private static final Map<Direction, int[]> 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 CharacterSetManager characterSetManager;
|
||||||
private final Map<Direction, Integer> spriteDirectionRows;
|
|
||||||
private final Map<Direction, Vector2fc> spriteDefaultRows;
|
|
||||||
private final Vector2f characterScale = new Vector2f(1, 1);
|
private final Vector2f characterScale = new Vector2f(1, 1);
|
||||||
private Texture texture;
|
|
||||||
private Vector2fc characterSetSize;
|
|
||||||
|
|
||||||
private final EventHandler eventHandler = new EventHandler();
|
private final EventHandler eventHandler = new EventHandler();
|
||||||
|
private final Queue<CharacterInstantAnimation> instantAnimations = new LinkedList<>();
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private int defaultSpriteColumn;
|
||||||
|
private CharacterSet characterSet;
|
||||||
|
private Vector2fc characterSetSize;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@@ -65,22 +53,19 @@ public class DefaultCharacter extends MovableSprite implements Character {
|
|||||||
|
|
||||||
private boolean animationEnabled = true;
|
private boolean animationEnabled = true;
|
||||||
|
|
||||||
private final Queue<CharacterInstantAnimation> instantAnimations = new LinkedList<>();
|
public DefaultCharacter(CharacterSetManager characterSetManager, @NonNull String characterSetUid, int defaultSpriteColumn) {
|
||||||
|
this(characterSetManager.loadObject(characterSetUid), characterSetManager, defaultSpriteColumn);
|
||||||
public DefaultCharacter(CharacterSetManager characterSetManager, int defaultSpriteColumn, @NonNull Map<Direction, Integer> spriteDirectionRows, Map<Direction, Vector2fc> spriteDefaultRows, @NonNull String characterSetUid) {
|
|
||||||
this(characterSetManager.loadObject(characterSetUid), characterSetManager, defaultSpriteColumn, spriteDirectionRows, spriteDefaultRows);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultCharacter(@NonNull Texture texture, @NonNull CharacterSetManager characterSetManager, int defaultSpriteColumn, @NonNull Map<Direction, Integer> spriteDirectionRows, @NonNull Map<Direction, Vector2fc> spriteDefaultRows) {
|
private DefaultCharacter(@NonNull CharacterSet characterSet, @NonNull CharacterSetManager characterSetManager, int defaultSpriteColumn) {
|
||||||
super(texture);
|
super(characterSet.getTexture());
|
||||||
|
|
||||||
this.defaultSpriteColumn = defaultSpriteColumn;
|
this.defaultSpriteColumn = defaultSpriteColumn;
|
||||||
this.characterSetManager = characterSetManager;
|
this.characterSetManager = characterSetManager;
|
||||||
this.spriteDirectionRows = spriteDirectionRows;
|
|
||||||
this.faceDirection = Direction.DOWN;
|
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);
|
super.setScale(characterSetSize.x() * characterScale.x, characterSetSize.y() * characterScale.y);
|
||||||
|
|
||||||
setDefaultAnimationFrame();
|
setDefaultAnimationFrame();
|
||||||
@@ -117,35 +102,21 @@ public class DefaultCharacter extends MovableSprite implements Character {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Vector2fc[] getSpriteAnimationFramesPositions() {
|
protected int[] getAvailableFramesSubset() {
|
||||||
var row = spriteDirectionRows.get(faceDirection);
|
return characterSet.getFrames().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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setDefaultAnimationFrame() {
|
protected void setDefaultAnimationFrame() {
|
||||||
// getMaterial().setSpritePosition(spriteDefaultRows.get(faceDirection));
|
setAnimationFrame(characterSet.getFrames().get(faceDirection)[defaultSpriteColumn]);
|
||||||
setAnimationFrame(CHARSET_FRAMES.get(faceDirection)[DEFAULT_CHARSET_FRAME_COLUMN]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changeCharacterSet(String characterSetUid) {
|
public void changeCharacterSet(String characterSetUid) {
|
||||||
this.texture = characterSetManager.loadObject(characterSetUid);
|
this.characterSet = characterSetManager.loadObject(characterSetUid);
|
||||||
this.characterSetSize = texture.getSpriteSize();
|
this.characterSetSize = characterSet.getTexture().getSpriteSize();
|
||||||
super.setScale(characterSetSize.x() * characterScale.x, characterSetSize.y() * characterScale.y);
|
super.setScale(characterSetSize.x() * characterScale.x, characterSetSize.y() * characterScale.y);
|
||||||
setMaterial(texture);
|
setMaterial(characterSet.getTexture());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user