Game Design – Best Practices for Recreating Pokemon

designobject-oriented

I'm currently working on a group project to recreate the original Pokemon Yellow version in Java. We've only just started planning it out but they way we've drawn out the design right now is to have an abstract Pokemon class and have 151 (Count of the original Pokemon) classes that extend it, each one being a different Pokemon. Each class will be able to store data such as an ID, its name, the type of Pokemon it is (ie: rock/water/fire/etc), and the moves it can use and learn. It seems a little much to be creating 151 classes for the Pokemon but it also seems like a good way to limit coupling. As far as design goes is this a good way to go about doing it, if not what are some better methods to go about doing it?

Best Answer

You don't really explain why your group thought it would be a good idea to (hopefully) generate 151 classes, as "to reduce coupling" doesn't really make sense yet as you had no design or code and therefore no coupling.

Each class will be able to store data such as an ID, its name, the type of Pokemon it is (ie: rock/water/fire/etc), and the moves it can use and learn.

Change 'class' to 'instance' in that sentence and you get why you only need to create one Pokemon class: they all share the same properties. A derived class should add or implement properties (and / or methods) unique to that derivation.

If you define the properties of ElementType, Move and PokemonBase as such:

enum ElementType
{
    Fire = 1,
    Water = 2,
    Grass = 4,
    Rock = 8,
}

class Move
{
    int ID;
    string Name;
    ElementType ElementType;
    int BaseDamage;
}

class PokemonBase
{
    int ID;
    string Name;
    ElementType ElementTypes;
    List<Move> Moves;

    decimal HitPointsPerLevel;
    decimal AttackPointsPerLevel;
}

Then your various instances only differ in what is in those properties. Your class and code should not care about that. A Pokemon that has the type Fire can be stored in exactly the same class as a Pokemon of type Water: they only carry a different value of their ElementTypes property.

You can now fill a list of Pokemon instances with their properties and moves, for example by loading a text file or reading from a database, in a format you like.

You can solve all those problems when loading the Pokemon. In the data source you can store additional properties, which in the case of "the moves it can use and learn" can be a relational table (or flat text equivalent) where you join Pokemon.ID, Move.ID and Level to each other (if memory serves me right, they learn a new move on reaching preset levels), storing it in a list or dictionary property in your Pokemon class.

Or perhaps while designing you thought you needed a different class to implement the fighting system differently for each Pokémon. You don't have to, as by combining the various properties you can give each instance its unique properties.


The problem of course starts when you realize that you actually have two types of Pokemon: a 'blueprint' for all Pokemon that can possibly occur in the game, and an actual Pokemon that a player carries, which has a Level, HitPoints, and so on, all properties depending on or calculated through the Level or based on the fact that the isntance is "in the game". You can implement those properties in a derived class:

class Pokemon : PokemonBase
{
    int Level;
    int MaxHitPoints;
    int HitPoints;

    // Stats, for example
    int Attack { get { return Level * base.AttackPointsPerLevel } };
    int Defense;

    // This method directly applies the attack results to the incoming Pokemon, by reference.
    int Attack(Pokemon other, Move attack)
    {       
        int damageDone = (this.Attack * attack.BaseDamage) - defender.Defense;

        // TODO: http://bulbapedia.bulbagarden.net/wiki/Type_chart
        decimal modifier = CalculateEffectiveness(other, attack);
        damageDone *= modifier;

        other.HitPoints -= damageDone;      

        return damageDone;
    }
}

And all Pokemon instances can use it, without having to derive anything. The key is modeling your class and method so, that the actions you want to undertake on each of them can be applied to all combinations of types. You can also for exampla add a List<DamageOverTimeAttack> to a Pokemon to implement attacks that should stay active for a few turns, handling it in the Attack() method.

Now when a player encounters a new Pokemon, you might want create that from a Factory:

class PokemonFactory
{
    List<PokemonBase> _allPokemon;

    Pokemon GetPokemon(id, level)
    {
        blueprint = _allPokemon.Find(id);

        return new Pokemon
        {
            ID = blueprint.ID,
            Name = blueprint.Name,
            ...
            Level = level,
            ...
        };
    }       
}
Related Topic