Skip to main content Início Criadores johannesrabauer fxgl-skills fxgl-rpg
fxgl-rpg Build an RPG or JRPG in FXGL — define character stats (ATK, DEF, HP, MP, SPD, XP/level), implement an experience and leveling system with stat growth, create a turn-based battle scene that transitions from overworld exploration, build an equipment system that modifies effective stats, implement status effects (poison, stun, burn) that persist across turns, define abilities with MP cost and target types, trigger encounters on NPC contact or random steps, display a battle menu (Attack/Skill/Item/Run), show loot and XP rewards on victory, and save the full party state with SaveLoadService. Use this skill when building a classic RPG, JRPG, turn-based adventure, or any game with stats, leveling, and structured battle sequences.
Ir para a instalação Skills Marketplace Descubra e explore skills de IA criadas pela comunidade.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Copiar promptMostrar detalhes do prompt Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
npx skills add https://github.com/JohannesRabauer/fxgl-skills --skill fxgl-rpgO comando permanece em uma só linha. Role horizontalmente para revisá-lo antes de copiar.
Prefere uma cópia local? Baixe os arquivos disponíveis atualmente no SkillsMP.
Baixar Zip Baixando... Mais deste repositório Add particle systems and visual effects to an FXGL game — configure ParticleEmitter with function-based setters (setVelocityFunction, setAccelerationFunction, setExpireFunction, setScaleFunction, setSpawnPointFunction); use built-in factory emitters (fire, explosion, smoke, rain); apply image-textured particles with multiplyColor / toColor; write custom per-particle physics via setControl; attach TrailParticleComponent for motion trails; apply SlowTimeEffect for bullet-time; apply WobbleEffect for screen-shake; stack multiple effects with EffectComponent; build fireworks displays. Use this skill when adding explosions, fire, smoke, rain, motion trails, impact bursts, bullet-time slow-motion, screen shake, or any particle-based visual polish.
Start a new FXGL project through a bounded Socratic discovery flow, then turn the answers into a docs-first starter specification with ADRs, an arc42-lite architecture summary, and Mermaid use-case diagrams before scaffolding the project itself. Continue using the same questioning approach for initialization choices, and require documentation updates plus automated tests for every implemented step.
Build a Breakout or Arkanoid style game in FXGL — control a paddle horizontally, keep a ball at constant speed, bounce it off walls, paddle, and bricks, load brick layouts from level data, angle the reflection based on paddle hit position, handle lives and bottom-drain loss, spawn falling power-ups, and finish the level when all breakable bricks are destroyed.
Ocupações relacionadas SOC
Baseado na classificação ocupacional SOC
name fxgl-rpg description Build an RPG or JRPG in FXGL — define character stats (ATK, DEF, HP, MP, SPD, XP/level), implement an experience and leveling system with stat growth, create a turn-based battle scene that transitions from overworld exploration, build an equipment system that modifies effective stats, implement status effects (poison, stun, burn) that persist across turns, define abilities with MP cost and target types, trigger encounters on NPC contact or random steps, display a battle menu (Attack/Skill/Item/Run), show loot and XP rewards on victory, and save the full party state with SaveLoadService. Use this skill when building a classic RPG, JRPG, turn-based adventure, or any game with stats, leveling, and structured battle sequences.
triggers ["RPG","JRPG","turn-based battle","stats","ATK DEF HP MP","leveling","XP experience","equipment","status effect","ability","random encounter","battle scene","party system","overworld"] compatibility Java 17+, FXGL 21.x
category fxgl/game-types tags ["fxgl","java","javafx","game-types","rpg"] metadata {"author":"fxgl-skills","version":"1.0","fxgl-version":"21.1"} allowed-tools ["Read","Write","Edit","Bash"]
FXGL RPG / JRPG
Character Stats Data Class
public class CharacterStats {
public String name;
public int level = 1 ;
public int xp = 0 ;
public int xpToNext = 100 ;
public int baseHP, maxHP, currentHP;
public int baseMP, maxMP, currentMP;
public int baseATK, baseATKBonus;
public int baseDEF, baseDEFBonus;
public int baseSPD;
public int getATK () { return baseATK + baseATKBonus; }
public int getDEF () { return baseDEF + baseDEFBonus; }
public Item weapon;
public Item armor;
public Item accessory;
public final List<StatusEffect> statusEffects = new ArrayList <>();
{
xp += amount;
(xp >= xpToNext) {
xp -= xpToNext;
levelUp();
}
}
{
level++;
xpToNext = ( )(xpToNext * );
maxHP += ;
maxMP += ;
baseATK += ;
baseDEF += ;
baseSPD += ;
currentHP = maxHP;
currentMP = maxMP;
}
{
(item == ) ;
(item.slot) {
WEAPON -> {
(weapon != ) baseATKBonus -= weapon.atkBonus;
weapon = item;
baseATKBonus += item.atkBonus;
}
ARMOR -> {
(armor != ) baseDEFBonus -= armor.defBonus;
armor = item;
baseDEFBonus += item.defBonus;
}
}
}
}
public
void
gainXP
(int amount)
while
private
void
levelUp
()
int
1.5
15
8
2
1
1
public
void
equip
(Item item)
if
null
return
switch
case
if
null
case
if
null
Status Effect System public class StatusEffect {
public final StatusType type;
public int remainingTurns;
public final int magnitude;
public enum StatusType { POISON, BURN, STUN, BLIND, REGEN }
public StatusEffect (StatusType type, int turns, int magnitude) {
this .type = type;
this .remainingTurns = turns;
this .magnitude = magnitude;
}
public void onTurnStart (CharacterStats target) {
switch (type) {
case POISON -> target.currentHP = Math.max(1 , target.currentHP - magnitude);
case REGEN -> target.currentHP = Math.min(target.maxHP, target.currentHP + magnitude);
case BURN -> target.currentHP = Math.max(1 , target.currentHP - (int )(target.maxHP * 0.08 ));
}
remainingTurns--;
}
public boolean isStunned () { return type == StatusType.STUN; }
public boolean isExpired () { return remainingTurns <= 0 ; }
}
Turn-Based Battle Scene (GameSubScene) public class BattleSubScene extends GameSubScene {
private final CharacterStats player;
private final List<EnemyStats> enemies;
private final List<CharacterStats> turnOrder;
private int turnIndex = 0 ;
private Label statusLabel;
private VBox battleMenu;
@Override
public void onOpen () {
buildBattleUI();
buildTurnOrder();
processTurn();
}
private void buildTurnOrder () {
turnOrder.clear();
turnOrder.add(player);
turnOrder.addAll(enemies);
turnOrder.sort(Comparator.comparingInt(c -> -c.baseSPD));
}
private void processTurn () {
CharacterStats current = turnOrder.get(turnIndex % turnOrder.size());
current.statusEffects.forEach(se -> se.onTurnStart(current));
current.statusEffects.removeIf(StatusEffect::isExpired);
boolean stunned = current.statusEffects.stream().anyMatch(StatusEffect::isStunned);
if (current == player && !stunned) {
showPlayerMenu();
} else if (stunned) {
statusLabel.setText(current.name + " is stunned and cannot act!" );
advanceTurn();
} else {
executeEnemyAI((EnemyStats) current);
}
checkBattleEnd();
}
private void showPlayerMenu () {
battleMenu.setVisible(true );
}
public void onPlayerAttack () {
battleMenu.setVisible(false );
EnemyStats target = enemies.get(0 );
int damage = Math.max(1 ,
player.getATK() - target.baseDEF
+ FXGLMath.random(-player.getATK() / 5 , player.getATK() / 5 ));
target.currentHP -= damage;
statusLabel.setText("You dealt " + damage + " damage to " + target.name + "!" );
animateHit(target);
runOnce(this ::advanceTurn, Duration.seconds(1.5 ));
}
public void onPlayerSkill (Skill skill) {
if (player.currentMP < skill.mpCost) {
statusLabel.setText("Not enough MP!" );
return ;
}
player.currentMP -= skill.mpCost;
skill.execute(player, enemies, this ::advanceTurn);
}
private void executeEnemyAI (EnemyStats enemy) {
int damage = Math.max(1 , enemy.baseATK - player.getDEF());
player.currentHP -= damage;
statusLabel.setText(enemy.name + " attacks for " + damage + " damage!" );
animateHit(player);
runOnce(this ::advanceTurn, Duration.seconds(1.5 ));
}
private void advanceTurn () {
turnIndex++;
processTurn();
}
private void checkBattleEnd () {
if (enemies.stream().allMatch(e -> e.currentHP <= 0 )) {
onVictory();
} else if (player.currentHP <= 0 ) {
onDefeat();
}
}
private void onVictory () {
int xpGain = enemies.stream().mapToInt(e -> e.xpReward).sum();
int goldGain = enemies.stream().mapToInt(e -> e.goldReward).sum();
player.gainXP(xpGain);
inc("gold" , goldGain);
showVictoryScreen(xpGain, goldGain);
runOnce(() -> getSceneService().popSubScene(), Duration.seconds(3 ));
}
private void onDefeat () {
showDefeatScreen();
}
private void animateHit (CharacterStats target) { }
private void buildBattleUI () { }
}
Encounter Trigger
@Override
protected void initPhysics () {
onCollisionBegin(EntityType.PLAYER, EntityType.ENEMY_OVERWORLD, (player, enemy) -> {
String enemyType = enemy.getString("enemyType" );
List<EnemyStats> enemies = createEnemyGroup(enemyType);
getSceneService().pushSubScene(new BattleSubScene (playerStats, enemies));
});
}
private int stepCount = 0 ;
@Override
protected void onUpdate (double tpf) {
if (isPlayerMoving()) {
stepCount++;
if (stepCount >= 50 && FXGLMath.random() < 0.05 ) {
stepCount = 0 ;
triggerRandomEncounter();
}
}
}
Ability Definition public abstract class Skill {
public final String name;
public final int mpCost;
public final String description;
public abstract void execute (CharacterStats caster, List<EnemyStats> enemies, Runnable onComplete) ;
}
public class FireballSkill extends Skill {
public FireballSkill () {
super ();
name = "Fireball" ;
mpCost = 15 ;
description = "Deals fire damage to all enemies." ;
}
@Override
public void execute (CharacterStats caster, List<EnemyStats> enemies, Runnable onComplete) {
int damage = caster.getATK() + 20 ;
enemies.forEach(e -> {
e.currentHP -= Math.max(1 , damage - e.baseDEF / 2 );
e.statusEffects.add(new StatusEffect (StatusType.BURN, 3 , 5 ));
});
play("sounds/fireball.wav" );
runOnce(onComplete::run, Duration.seconds(1.0 ));
}
}
Equipment Shop Integration
Shop<Item> armorShop = new Shop <>();
armorShop.addItem(new TradeItem <>(new Item ("Iron Sword" , "icon_sword.png" , "+10 ATK" ) {{
slot = ItemSlot.WEAPON; atkBonus = 10 ;
}}, 200 , 100 ));
armorShop.addItem(new TradeItem <>(new Item ("Chain Mail" , "icon_armor.png" , "+15 DEF" ) {{
slot = ItemSlot.ARMOR; defBonus = 15 ;
}}, 300 , 150 ));
public void buyAndEquip (Item item) {
playerStats.equip(item);
updateStatsHUD();
}
Save/Load Full Party State @Override
public void writeSaveState (DataFile data) {
Bundle party = data.getBundle("party" );
party.put("playerName" , playerStats.name);
party.put("level" , playerStats.level);
party.put("xp" , playerStats.xp);
party.put("currentHP" , playerStats.currentHP);
party.put("currentMP" , playerStats.currentMP);
party.put("gold" , geti("gold" ));
party.put("mapFile" , currentMapFile);
party.put("playerX" , player.getX());
party.put("playerY" , player.getY());
party.put("weaponId" , playerStats.weapon != null ? playerStats.weapon.id : "none" );
}
@Override
public void readSaveState (DataFile data) {
Bundle party = data.getBundle("party" );
playerStats.level = party.get("level" );
playerStats.xp = party.get("xp" );
playerStats.currentHP = party.get("currentHP" );
playerStats.currentMP = party.get("currentMP" );
set("gold" , party.get("gold" ));
setLevelFromMap(party.get("mapFile" ));
player.setPosition(party.get("playerX" ), party.get("playerY" ));
String weaponId = party.get("weaponId" );
if (!weaponId.equals("none" )) playerStats.equip(getItemById(weaponId));
}
Gotchas
BattleSubScene over the overworld — use getSceneService().pushSubScene() not
setLevelFromMap(). The overworld should resume exactly where it was after the battle ends.
turnOrder must be rebuilt after enemy deaths — removing a dead enemy from turnOrder
while iterating throws ConcurrentModificationException. Use removeIf between turns.
Minimum damage of 1 — always Math.max(1, damage) after subtracting defense. Zero
or negative damage from heavy armor frustrates players and stalls fights.
Status effect icons in HUD — display small icons for active status effects per character
in the battle HUD. Players need to see that poison is ticking.
gainXP may level up multiple times in one call — the while loop handles this. Don't
use if instead of while or the player could lose XP from defeating high-XP enemies.
Encounter rate on steps — check isPlayerMoving() not onUpdate(tpf) for step counting.
Ticking encounter rate every frame at 60fps = 60× faster encounter rate than expected.
Battle scene must be pausable — on ALT+TAB or window minimize, the battle should pause.
Wrap all runOnce timers in a paused check or use getGameController().pauseEngine().