Programming C, C++, Java, PHP, Ruby, Turing, VB
Computer Science Canada 
Programming C, C++, Java, PHP, Ruby, Turing, VB  

Username:   Password: 
 RegisterRegister   
 RPG Mechanics
Index -> Programming, Java -> Java Submissions
View previous topic Printable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic
Author Message
Zren




PostPosted: 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:

Posted Image, might have been reduced in size. Click Image to view fullscreen.
Posted Image, might have been reduced in size. Click Image to view fullscreen.
Posted Image, might have been reduced in size. Click Image to view fullscreen.

After implementing ImprovedNoise (2D)
Posted Image, might have been reduced in size. Click Image to view fullscreen.
Posted Image, might have been reduced in size. Click Image to view fullscreen.

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
Sponsor
sponsor
Zren




PostPosted: 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.
Display posts from previous:   
   Index -> Programming, Java -> Java Submissions
View previous topic Tell A FriendPrintable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic

Page 1 of 1  [ 2 Posts ]
Jump to:   


Style:  
Search: