Refactor animations

This commit is contained in:
2025-07-21 13:50:17 +02:00
parent 8a2a5511f4
commit 1ce0810cc2
11 changed files with 386 additions and 106 deletions

View File

@@ -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.
*
* <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 {
/**
* 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();
/**
* 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 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();
/**
* 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);
/**
* 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);
}

View File

@@ -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).
*
* <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 {
/**
* 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);
/**
* 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();
/**
* 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);
/**
* 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);
/**
* 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();
}

View File

@@ -293,6 +293,11 @@ public abstract class CharacterDelegate implements Character {
character.setZIndex(zIndex);
}
@Override
public void setDefaultSpriteColumn(int column) {
character.setDefaultSpriteColumn(column);
}
@Override
public <E extends Event> void handleEvent(E event) {
character.handleEvent(event);

View File

@@ -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();