| name | fxgl-fighting |
| description | Build a fighting game in FXGL — implement a character state machine (idle/walk/jump/attack/ hurt/block/knockdown), separate hitbox and hurtbox entities for accurate hit detection, an input buffer for combo and special move recognition, frame- window management (startup/ active/recovery frames), health bar HUD with chip-damage animation, knockback on hit, block mechanics with chip damage, round timer and round structure, and combo counter display. Use this skill when building a 1v1 or 2v2 fighting game, a boss fight with frame-data mechanics, or any game requiring precise hitbox/hurtbox distinction.
|
| triggers | ["fighting game","hitbox hurtbox","combo","frame data","input buffer","knockback","block","health bar","round system","startup active recovery","FSM fighter","character state machine","special move"] |
| compatibility | Java 17+, FXGL 21.x
|
| category | fxgl/game-types |
| tags | ["fxgl","java","javafx","game-types","fighting"] |
| metadata | {"author":"fxgl-skills","version":"1.0","fxgl-version":"21.1"} |
| allowed-tools | ["Read","Write","Edit","Bash"] |
FXGL Fighting Game
Fighter State Machine
public enum FighterState {
IDLE, WALKING_FORWARD, WALKING_BACK,
JUMPING, FALLING,
ATTACK_LIGHT, ATTACK_HEAVY, ATTACK_SPECIAL,
BLOCKING,
HURT, KNOCKBACK, KNOCKED_DOWN, RISING
}
public class FighterComponent extends Component {
private PhysicsComponent physics;
private FighterState state = FighterState.IDLE;
private double stateTimer = 0;
private int hitPoints;
private boolean isGrounded;
private boolean facingRight;
private final Deque<InputEvent> inputBuffer = new ArrayDeque<>();
private static final double BUFFER_WINDOW = 0.4;
public record InputEvent(String key, double timestamp) {}
@Override
public void onUpdate( tpf) {
stateTimer += tpf;
pruneInputBuffer();
processState(tpf);
}
{
(state) {
IDLE -> handleIdle(tpf);
WALKING_FORWARD, WALKING_BACK -> handleWalking(tpf);
JUMPING -> handleJumping(tpf);
HURT -> handleHurt(tpf);
KNOCKED_DOWN -> handleKnockedDown(tpf);
RISING -> handleRising(tpf);
ATTACK_LIGHT -> handleAttack(, , , , tpf);
ATTACK_HEAVY -> handleAttack(, , , , tpf);
BLOCKING -> handleBlocking();
}
}
{
(stateTimer < startup) {
} (stateTimer < startup + active) {
activateHitbox(type, startup);
} (stateTimer < startup + active + recovery) {
deactivateHitbox();
} {
deactivateHitbox();
transitionTo(FighterState.IDLE);
}
}
{
state = newState;
stateTimer = ;
}
{
System.currentTimeMillis() / ;
inputBuffer.removeIf(e -> now - e.timestamp() > BUFFER_WINDOW);
}
{
System.currentTimeMillis() / ;
inputBuffer.addLast( (key, now));
checkSpecialMoves();
}
{
List<String> keys = inputBuffer.stream().map(InputEvent::key).toList();
(containsSequence(keys, , , )) {
inputBuffer.clear();
fireProjectile();
}
}
{
;
(String key : buffer) {
(key.equals(sequence[si])) si++;
(si == sequence.length) ;
}
;
}
;
Entity hitboxEntity;
{
(hitboxActive) ;
hitboxActive = ;
type.equals() ? : ;
type.equals() ? : ;
facingRight ? : -( + w);
hitboxEntity = entityBuilder()
.type(EntityType.HITBOX)
.at(entity.getX() + offsetX, entity.getCenter().getY() - h / )
.bbox(BoundingShape.box(w, h))
.with( ())
.set(, entity)
.set(, type.equals() ? : )
.buildAndAttach();
}
{
(!hitboxActive) ;
hitboxActive = ;
(hitboxEntity != && hitboxEntity.isActive()) {
hitboxEntity.removeFromWorld();
}
}
{
(isBlocking) {
hitPoints -= ()(damage * );
transitionTo(FighterState.IDLE);
} {
hitPoints -= damage;
(hitPoints <= ) {
hitPoints = ;
transitionTo(FighterState.KNOCKED_DOWN);
} {
transitionTo(FighterState.HURT);
}
}
}
{ }
{ }
{ }
{
(stateTimer >= ) transitionTo(FighterState.IDLE);
}
{
(stateTimer >= ) transitionTo(FighterState.RISING);
}
{
(stateTimer >= ) transitionTo(FighterState.IDLE);
}
{ }
{}
{}
}