2
\$\begingroup\$

Suppose I create a turn based RPG in the veins of Pokémon, with a lot of moves and abilities, that would have elements of hand made code, in cases for special effects after a successful attack or status effect, for example, a status buff every turn, or an automatic attack every turn (think rollout or outrage from Pokémon).

def move_name():
    do_primary_effect()//damage, status effect
    do_secondary_effect()//on hit effects, side-effects,
    //delayed buffs/debuffs, setting next move, i.e. rollout, etc.
    set_flags()//flags like invulnerability from moves like dig, fly, etc.

Now suppose I wanted to make this game open source and allowed people to make their own builds/rebuilds or online versions, where they might use a different language. How can I program my moves/abilities in such a way that it can be interpreted by any compiler, regardless of language, so that there is no re-coding moves for different languages?

tldr; how would I go about a format for moves/abilities such that it is compiler independent(might be the wrong word, but means can be read and translated). Now I'm not asking for a direct answer, though those are greatly appreciated, I'm talking mostly about how such a format would look like, so real life examples for existing videogames are greatly appreciated. I've heard my friend say that Pokémon does this with a data structure, so data structures are also appreciated.

\$\endgroup\$
4
  • \$\begingroup\$ When you create a format which is able to encode any possible functionality, then you have, by definition, created another programming language. \$\endgroup\$
    – Philipp
    Jan 7, 2021 at 1:56
  • 1
    \$\begingroup\$ We have some past Q&A about implementing flexible systems for authoring abilities that modify future actions that might be useful to you here. \$\endgroup\$
    – DMGregory
    Jan 7, 2021 at 2:03
  • \$\begingroup\$ Maybe a scripting language like Lua can help you with this problem. \$\endgroup\$ Jan 7, 2021 at 6:33
  • 2
    \$\begingroup\$ Usually when people want to do this they either create their own scripting language or save move information in the form of a data sheet which the system can read on startup and turn into a generic move. Basically you would be making your own game engine. For example an xml document like <Attack name="Pocket Sand" turnsToCharge="0" turnsToReapply="0"><StatusEffect type="debuff" attribute="accuracy" dur="4">60.0%</StatusEffect><StatusEffect type="damage" attribute="attitude" dur="-1">20</StatusEffect></Attack> makes a move that lowers accuracy 60% for 4 turns and starts to piss off enemies. \$\endgroup\$ Jan 7, 2021 at 7:23

2 Answers 2

3
\$\begingroup\$

It looks like what you would essentially like to do is create a game engine (like RPG Maker) or a game with some modding tools with the ability to create custom semi-defined object (like Skyrim).

Game Engine Approach
Typically when people make their own engine, their own program will include a compiler that reads another scripting language (typically LUA but Unity for example reads Unity Script and C# even though its core was written in C++). Normally this is a really difficult approach but if you want to make a fully open Game Engine then this is the route for you. Just bear in mind it is again, really difficult and probably way out of the scope of what your really want.

Data Driven Approach
When creating the ability to mod in new existing concepts in your game (items, moves, characters, stories, etc.) you can do this by creating a generic classes that represent these concepts (Item.cs, Move.cs, Npc.cs, Dialog.cs, etc.) and setting variables within them using data read from external files. These files can be stored anywhere, but to make things easy you should probably place them in your StreamingAssets folder. Typically this approach can be a bit limited but it allows players to build on your game, or make their own custom game without varying too much from the core gameplay you designed. You can find many examples of systems like (such as Pokemon like your friend suggested), but I will give you a quick untested sample of how you might make something like this:

--- Game Classes ---

public enum EffectType
{
    Damage,
    Debuff,
    // etc.
}

public enum CharacterAttribute
{
    Health,
    Magic,
    Accuracy,
    Mood
}

public enum TargetType
{
    Self,
    Friend,
    Foe,
    PropaneAndPropaneAccessorySalesmen
}

// If you don't want to do one mega super class for status effects you could 
// switch to using a StatusEffect abstract class or IStatusEffect interface
// and create child classes for each effect type. This can greatly improve
// organization for really big projects but for simplicity I took the 
// mega-monster-too-big-to-easily-debug option.
public class StatusEffect
{
    EffectType effectType = EffectType.Damage;
    CharacterAttribute targetAttribute = CharacterAttribute.Health;
    TargetType targetType = TargetType.Foe;
    int duration = -1; // amount of turns this lasts. -1 if permanent
    float value; // amount of whatever this does, hp lost, accuracy down, etc.

    public bool DoEffect(Character sourceCharacter, Character targetCharacter)
    {
        bool successful = true;
        // put effect logic here, probably a switch statement
        return successful;
    }

    public bool StopEffect(Character sourceCharacter, Character targetCharacter)
    {
        bool successful = true;
        // put logic here to stop effect if possible
        return successful;
    }
}

public class PlayerMove
{
    public List<StatusEffect> effects = new List<StatusEffect>();
    public int chargeTurns = 0; // player should check if this many turns have passed before doing move.
    public int reapplyTurns = 0; // player should retrigger this effect for this many turns.

    public bool DoMove(Character player, Character target)
    {
        int successCount = 0;
        foreach (StatusEffect effect in effects)
        {
            successCount += effect.DoEffect(player, target) ? 1 : 0;
        }
        return successCount == effects.Count; // put whatever metric for a successful you want (all effects succeed or maybe just 1 is enough)
    }
}

--- XML Data File ---

<PlayerMoveDatabase>
    <!-- Define Moves Here -->
    <PlayerMove name="Pocket Sand" turnsToCharge="0" turnsToReapply="0">
        <Data>
            <Description>
                An attack worthy of Rusty Shackleford.
            </Description>
            <LearnedBy>
                <Player id="0" requiredLevel="10"/>
                <Player id="1" requiredLevel="10"/>
                <Player id="5" requiredLevel="0"/>
           </LearnedBy>
        </Data>
        <Effects>
            <StatusEffect target="foe" type="debuff" attribute="accuracy" dur="4">60</StatusEffect> <!-- lowers accuracy 60% -->
            <StatusEffect target="foe" type="damage" attribute="mood" dur="-1">20</StatusEffect> <!-- Makes targets 20 points angrier. -->
        </Effects> 
    </PlayerMove>
</PlayerMoveDatabase>
\$\endgroup\$
1
\$\begingroup\$

What I would do is to separate the moves into "building blocks" of sorts, a bit like a scripting language like others proposed but simpler, more akin to opcodes - I think the actual Pokémon games use something similar. Each block will have a type and a set of parameters.

You could have "blocks" like "deal X damage of Y type", "apply status effect X for Y turns", "if X has status effect Y skip next Z actions".

To store these in a file, you would store something like this per move:

  • Name
  • Cost
  • Conditions (if you have those)
  • Blocks: amount of blocks
    • Block 1, type A, params X, Y
    • Block 2, type B, no params
    • Block 3, type C, params X

You would then need a base class (or interface) for such a block so when a move is executed, it just goes thru blocks 1 by 1 and calls their common Execute method.

When reading a move's file, each block will be fed into a factory class which will then reconstitute to the proper block type - I recommend using strings here because they're a lot more human readable and future proof.

So in your factory class you would basically have a huge switch with something like (pseudocode!)

case "damage_direct":
    return new DamageBlock(param 1, param 2)
case "status_effect":
    return new StatusEffectBlock(param 1, param 2)
etc......

You would obviously have to implement each of these in your code or in a new language's code, but the actual moves will be just the data you input.

So in short, cut up moves into blocks, implement each block, have a factory class that creates appropriate type of a block for its type.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .