RPG Mechanics
Author |
Message |
Zren
|
Posted: Tue Dec 06, 2011 10:08 am Post subject: RPG Mechanics |
|
|
My Eventual Goal is something similar to FF Tactics, 3D and all as I can't create decent pixelart to save my life. However, RPGs are complex motherfuckers hard, and complicated.
First off, I attempted to make an isometric view in 3D. This led me down the spiral pit into madness of learning OpenGL in Java. This entire project was respured into motion when I realized how simple Processing did 3D objects. Though I wanted more complex things with it than it provided. So I checked what it ran on. JOGL was a disaster. Luckily I remembered about LWJGL from reading about Minecraft. I briefly considered JME, but it felt like using a very high end tool when I needed to learn the low end first. So after grasping the basics of GL, I came up with this:
After implementing ImprovedNoise (2D)
As you might of noticed, I haven't converted the mass of cubes into a single mesh yet. For now however, I realized I should most definitely start with the game mechanics. The renderer can be either 2D or 3D, but neither matters if the backend doesn't work.
So for now, I'm going to recreate the mechanics of the origional FF first. Figured I'd post the tidbits I finish for peer evaluation as I write them. Basicly for peers to tell me what I'm doing terribly, terribly wrong.
For now a LevelCurve class.
LevelCurve
Java: |
/**
* A class to hold and generate level curves. Uses the factory method pattern in
* order to generate the curve. Level's begin from 1 .. maxLevel. The experience
* required for each level is stored in experienceRequiredPerLevel at the id of
* (level - 1) or shifted to start from zero.
*
* @author Chris H (Shade / Zren)
*/
public class LevelCurve {
private final int[] experienceRequiredPerLevel;
/**
* Default constructor to initalized the data array.
*
* @param maxLevel The number of levels, as well as the size of the data array.
*/
private LevelCurve (int maxLevel ) {
experienceRequiredPerLevel = new int[maxLevel ];
}
/**
* Gets the maximum level, and since levels begin from 1 instead of 0, we can
* use the length of the data array.
*
* @return The maximum level.
*/
public int getMaxLevel () {
return experienceRequiredPerLevel. length;
}
/**
* Returns the experience required for the specified level. Input below 1 will
* return 0 (Level 1), and input above maxLevel will return the experience required
* for the maximum level.
*
* @param level
* @return The experience required for the specified level.
*/
public int getExpirienceRequiredForLevel (int level ) {
int l = Math. max(0, level - 1);
l = Math. min(l, getMaxLevel () - 1);
return experienceRequiredPerLevel [l ];
}
/**
* Get the level which our experience is good for according to
* the current LevelCurve.
*
* @param experience
* @return The level at which the specified experience is good for.
*/
public int getLevelFromExperience (int experience ) {
for (int level = 1; level <= getMaxLevel (); level++ ) {
if (experience < getExpirienceRequiredForLevel (level ))
// This level requires more experience than we have, so return the last level.
return level - 1;
}
// We've already reached the maximum level.
return getMaxLevel ();
}
/**
* Generate and fill experience data for a linear level curve.
* The experience required for each level remains the same.
* Level 1 will always be 0 experience.
*
* @param dy
*/
private void initLinear (int dy ) {
int n = 0;
for (int i = 0; i < getMaxLevel (); i++ ) {
experienceRequiredPerLevel [i ] = n;
n += dy;
}
}
/**
* Generate and fill experience data for a quadratic level curve.
* The experience required for each level will grow for each consecutive level.
* Level 1 will always be 0 experience.
*
* @param baseY The experience required for level 2
* @param scaleY The scale which the quadratic uses for each consecutive level.
*/
private void initQuadratic (double baseY, double scaleY ) {
int n = 0;
for (int i = 0; i < getMaxLevel (); i++ ) {
experienceRequiredPerLevel [i ] = n;
n += Math. ceil(baseY + scaleY * Math. pow(i, 2));
}
}
/**
* Factory method to create and seed a level curve that only has
* one level starting at 0 experience. Useful for a default level
* curve that may or may not be overwritten later on.
*
* @return An empty level curve.
*/
public static LevelCurve none () {
return linear (1, 0);
}
/**
* Factory method to create and seed a linear LevelCurve.
*
* @param maxLevel Generate experience required for each level up to this.
* @param dy
* @return A linear LevelCurve.
*/
public static LevelCurve linear (int maxLevel, int dy ) {
LevelCurve levelCurve = new LevelCurve (maxLevel );
levelCurve. initLinear(dy );
return levelCurve;
}
/**
* Factory method to create and seed a quadratic LevelCurve.
*
* @param maxLevel Generate experience required for each level up to this.
* @param baseY The experience required for level 2
* @param scaleY The scale which the quadratic uses for each consecutive level.
* @return A quadratic LevelCurve.
*/
public static LevelCurve quadratic (int maxLevel, double baseY, double scaleY ) {
LevelCurve levelCurve = new LevelCurve (maxLevel );
levelCurve. initQuadratic(baseY, scaleY );
return levelCurve;
}
/**
* Main method used for testing.
*
* @param args
*/
public static void main (String[] args ) {
LevelCurve lc = LevelCurve. quadratic(25, 10, 5);
System. out. println("[Exp Required For Level]");
System. out. println(String. format("%8s | %8s", "Level", "Exp"));
for (int level = 1; level <= lc. getMaxLevel(); level++ ) {
System. out. println(String. format("%8d | %8d", level, lc. getExpirienceRequiredForLevel(level )));
}
System. out. println("[Exp]");
System. out. println(String. format("%8s | %8s", "Exp", "Level"));
for (int exp = 0; exp < 30; exp++ ) {
System. out. println(String. format("%8d | %8d", exp, lc. getLevelFromExperience(exp )));
}
}
}
|
Output
code: |
[Exp Required For Level]
Level | Exp
1 | 0
2 | 10
3 | 25
4 | 55
5 | 110
6 | 200
7 | 335
8 | 525
9 | 780
10 | 1110
11 | 1525
12 | 2035
13 | 2650
14 | 3380
15 | 4235
16 | 5225
17 | 6360
18 | 7650
19 | 9105
20 | 10735
21 | 12550
22 | 14560
23 | 16775
24 | 19205
25 | 21860
|
Next up will be a Character class to store Exp (and cached Level).
From there:
- Character -> Stats
- Character -> Jobs (aka Classes)
- Character -> Job -> onLevelUp(Character -> Stats)
- Item/Equipment Type
- Character -> Equipment
After that I'll probably start the battle system with just a basic Attack action. |
|
|
|
|
|
Sponsor Sponsor
|
|
|
Zren
|
Posted: Wed Dec 07, 2011 3:32 pm Post subject: Re: RPG Mechanics |
|
|
Alright, the next bit of code will most likely get a fair bit of scrutiny. That because I'm still not sure how I want to do it. The character statistics come in 3 categories.
- Always increasing integer (Level / Experience)
- Integer that will also sometimes have offsets (buffers / slow magic / etc) ( = value + offset)
- A fraction of a maximum amount. Sometimes the maximum will be buffed (HP / MP) [====----] [50/100]
While #3 could be done as #2 (as the following code shows), it seems weird. The only saving grace is in a later term I create a buffStat function, it'll work on the #3 type as well as #2.
#1 was easy enough, Making them part of the Character data structure. Later on I can make it more complicated should I wish.
Here's the Character class so far. I skimped the non-relevant methods that aren't about Level/Experience. If you want to see the full code, check the link at the bottom.
Character
Java: |
public class Character {
private Map<Stat, Integer> stats = new HashMap<Stat, Integer> ();
private Map<Stat, Integer> statOffsets = new HashMap<Stat, Integer> ();
private Job job;
private LevelCurve levelCurve;
private int level = 1;
private int experience = 0;
private final EquipmentInventory equipmentInventory = new EquipmentInventory ();
public Character(Job job ) {
setJob (job );
setLevelCurve (LevelCurve. none());
resetStatMap (stats );
resetStatMap (statOffsets );
}
public void setLevelCurve (LevelCurve levelCurve ) {
this. levelCurve = levelCurve;
}
public LevelCurve getLevelCurve () {
return levelCurve;
}
public int getExperience () {
return experience;
}
public void setExperience (int experience ) {
this. experience = experience;
}
public void addExperience (int value ) {
setExperience (getExperience () + value );
}
public int getExperienceToNextLevel () {
if (getLevel () >= levelCurve. getMaxLevel())
return 0;
return levelCurve. getExpirienceRequiredForLevel(getLevel () + 1) - getExperience ();
}
public void setLevelAndExperience (int level ) {
setLevel (level );
setExperience (getLevelCurve (). getExpirienceRequiredForLevel(level ));
}
public void setLevel (int level ) {
this. level = level;
}
public int getLevel () {
return level;
}
public void nextLevel () {
setLevel (getLevel () + 1);
}
// ...
/**
* Main method used for testing.
*
* @param args
*/
public static void main (String[] args ) {
Character c = Character. random();
while (true) {
// Print status of character
System. out. println(String. format("[%s]", c. toString()));
System. out. println(String. format(" Level: %s", c. getLevel()));
System. out. println(String. format(" Exp: %s", c. getExperience()));
System. out. println(String. format(" Exp To Next Level: %s", c. getExperienceToNextLevel()));
System. out. println(String. format(" Job: %s", c. getJob()));
System. out. println(String. format(" Equipment:"));
Weapon weapon = c. getEquipmentInventory(). getWeapon();
if (weapon != null) {
System. out. println(String. format(" Weapon: %s [Atk: %d, Acc: %d, Crit: %d] ",
weapon,
weapon. getAttack(),
weapon. getAccuracy(),
weapon. getCritical()));
} else {
System. out. println(String. format(" Weapon: -"));
}
System. out. println(String. format(" Armor:"));
for (Armor. Type armorType : Armor. Type. values()) {
Armor armor = c. getEquipmentInventory(). getArmor(armorType );
if (armor != null) {
System. out. println(String. format(" %s: %s [Def: %d, Weight: %d, Eva: %d] ",
armorType,
armor,
armor. getDefence(),
armor. getWeight(),
armor. getEvasion()));
} else {
System. out. println(String. format(" %s: -", armorType ));
}
}
System. out. println(String. format(" Stats:"));
for (Stat stat : Stat. values()) {
System. out. println(String. format(" %s: %d/%d", stat, c. getCalcStatValue(stat ), c. getStat(stat )));
}
// Give experience
c. addExperience(25);
// Level up if need be.
while (c. getLevel() < c. getLevelCurve(). getMaxLevel() && c. getExperienceToNextLevel() <= 0) {
// A proper engine will call a CHARACTER_LEVEL_UP event.
c. nextLevel();
for (Stat stat : Stat. values()) {
c. addToStat(stat, (int)(Math. random() * 10));
}
}
// Create a ticking effect.
try {
Thread. sleep(500);
} catch (InterruptedException e ) {
e. printStackTrace();
}
}
}
/**
* Factory method to generate a random character.
*
* @return A character with a random inventory and stats.
*/
public static Character random () {
Character c = new Character(Job. values()[(int)(Math. random() * Job. values(). length)]);
// Level
c. setLevelCurve(LevelCurve. quadratic(100, 10, 5));
// Stats
for (Stat stat : Stat. values()) {
c. setStat(stat, (int)(Math. random() * 100));
//c.setStatOffset(stat, (int)(Math.random() * 10) - 5);
}
// Note that Inventory is checked to see if it's compatible with Job.
// Weapon
c. getEquipmentInventory(). setWeapon(Weapon. values()[(int)(Math. random() * Weapon. values(). length)]);
// Armor
for (Armor. Type armorType : Armor. Type. values()) {
List<Armor> armorOfType = Armor. getArmorOfType(armorType );
c. getEquipmentInventory(). setArmor(armorType, armorOfType. get((int)(Math. random() * armorOfType. size())));
}
return c;
}
}
|
Output (of a single tick):
code: |
[quickie.Character@1d58aae]
Level: 5
Exp: 125
Exp To Next Level: 75
Job: Monk
Equipment:
Weapon: Mythril Knife [Atk: 10, Acc: 15, Crit: 16]
Armor:
BODY: Leather Armor [Def: 4, Weight: 8, Eva: 0]
HEADGEAR: Leather Cap [Def: 1, Weight: 1, Eva: 0]
GLOVES: Steel Gloves [Def: 4, Weight: 5, Eva: 0]
SHIELD: Buckler [Def: 2, Weight: 0, Eva: 0]
Stats:
Hp: 78/78
Mp: 36/36
Strenth: 33/33
Speed: 60/60
Stamina: 37/37
Magic: 47/47
Attack: 39/39
Defence: 96/96
Evasion: 76/76
Magic Defence: 34/34
Magic Evasion: 56/56
|
If you wish to see the rest of the classes (Stat, Job, Weapon, Armor, EquipmentInventory) then check them out at the following Gist link.
Full Code (https://gist.github.com/1444276)
My next update will be a simple back and forth battle (Traditional_Turn-Based). submitted something of it's kind before here, but oh well. Following that I'll work on making into an ATB. Then I'll walk down that page. I'm purposely skipping magic and other menus as the more current system involve a charging up and a cooldown system. |
|
|
|
|
|
|
|