У меня есть система цепочек атак для игры, но она очень быстро усложняется.

Я прохожу курс разработки игр для бакалавров компьютерной науки, и наш проект состоит в том, чтобы сделать игру XNA. Мы делаем игру в стиле jrpg и придумали классную идею для боя: каждый ход вы ставите в очередь 3 навыка, в зависимости от навыков и выбранного порядка могут применяться различные бонусные эффекты. Мы сделали базовый навык с двумя навыками: атака и огонь.

Вы начинаете с первого навыка, затем стрелки показывают выбранные второй и третий навыки. Ячейки на втором и третьем уровнях показывают эффект, добавленный в этот момент.

Основы: Атака+Атака = Более высокий шанс критического удара для второй атаки. Fire+Attack = Добавляет немного урона от огня к первоначальному урону от атаки. Fire+Fire = применяет статус горения

Сложнее всего, когда вы наносите 3 удара подряд, так как они совершают своего рода специальную атаку. Атака наносит 1/4 шанса критического удара, Огонь наносит двойной урон Огненной атакой (называется Огненный шар).

Реализация этого в операторах if может быть болезненной с большим количеством навыков. Количество операторов if равно сумме n^n от 1 до n, поэтому, если бы мы хотели 6 навыков, нам нужно было бы написать 6+36+216=258 операторов if! Многие из которых также были бы лишними! Это было бы подвержено ошибкам, поскольку нам пришлось бы кропотливо создавать каждый оператор if, чтобы они находились в правильном положении, когда мы кодируем наши блок-схемы.

Вот почему мы подумали, что у нас должны быть некоторые обобщенные комбинации со статическим эффектом, возможно, увеличение счетчика, если он может быть кумулятивным, а затем, когда у нас есть 3 подряд, вызовите функцию, которая имеет специальную атаку этого навыка.

Первое, что пришло на ум, это конечный автомат. Я думаю, он сможет обрабатывать все случаи, кроме особых. Может быть, выталкивающие автоматы? Основная проблема заключается в том, что я понятия не имею, как реализовать их в коде. Класс, в котором я их изучал, был теоретическим.

Существуют ли какие-либо другие более эффективные или простые в написании/кодировании методы?


person Portaljacker    schedule 15.11.2012    source источник
comment
Если у вас нет лучшей модели, вы должны придерживаться FSM. Однако, как вы уже сказали, он может стать довольно большим.   -  person John Dvorak    schedule 15.11.2012
comment
но похоже, что навыки просто изменяют некоторые модификаторы, которые затем изменяют следующий навык, поэтому ваша модель может быть чем-то вроде {norm-bonus, fire-bonus}; {n,f} + fire => {0,f*1.1} + {burn:f}; {n,f} + normal => {n+0.01,f*0.5} + {atk:n*f} (читай: если применяется огонь, увеличьте модификатор огня на 10% и сделайте немного ожога. Если обычная атака применяется, увеличивает шанс критического удара на 1%, уменьшает эффект огня на 50% и наносит некоторый урон, равный шансу критического удара, умноженному на эффект огня)   -  person John Dvorak    schedule 15.11.2012


Ответы (2)


Вместо этого я бы придумал какую-то рекурсивную модель:

enum Outcomes { Crit, DoubleCrit, FireDMG, Burn, NoEffect }

abstract class Attack 
{ 
    public Attack() { Child = null; }

    List<Outcomes> GetOutcomes(); 
    protected virtual Attack Child; 
}
class Melee : Attack 
{ 
    public Melee() : base() { }
    public Melee(Attack child) : base() { Child = child; }

    List<Outcomes> GetOutcomes()
    {
        List<Outcomes> ret = new List<Outcomes>();
        if(Child != null) ret.Add(Child.GetOutcomes());

        if(ret.Contains(Outcomes.Crit))
            ret.Add(Outcomes.DoubleCrit);
        else
            ret.Add(Outcomes.Crit);

        return ret;
    }
}
class Fire : Attack 
{ 
    public Fire() : base() { }
    public Fire(Attack child) : base() { Child = child; }

    List<Outcomes> GetOutcomes()
    {
        List<Outcomes> ret = new List<Outcomes>();
        if(Child != null) ret.Add(Child.GetOutcomes());

        List<Outcomes> PossibleOutcomes = new List<Outcomes>();        

        PossibleOutcomes.Add(Outcomes.FireDMG);
        PossibleOutcomes.Add(Outcomes.Burn);

        if(ret.Contains(Outcomes.Burn)) PossibleOutcomes.Add(Outcomes.Fireball)
        if(ret.Contains(Outcomes.FireDMG)) PossibleOutcomes.Add(Outcomes.NoEffect);

        // Use some randomization method to select an item from PossibleOutcomes
        int rnd = 2; // Totally random number.
        ret.Add(PossibleOutcomes[rnd]);

        return ret;
    }
}

Затем для цепных атак просто используйте:

Attack ChosenAttack = new Melee(new Fire(new Melee()));

Или, предположив, что пользователь выбирает каждую ветку, вам просто нужно сохранить последнюю и постоянно добавлять ее в качестве дочерней к следующей выбранной им атаке.

Attack ChosenAttack = new Melee();

// Some events occur...

ChosenAttack = new Fire(ChosenAttack);

// Some more...

ChosenAttack = new Melee(ChosenAttack);

Прошу прощения, если я не совсем понимаю проблему, и мое предложение не сработает.

person Spencer Ruport    schedule 15.11.2012

Похоже, вы действительно хотите иметь возможность настраивать, какие эффекты будут иметь различные варианты выбора для игрока, избегая при этом написания всего кода.

Один из способов сделать это — создать структуру данных, которая будет обрабатывать результат любой комбинации и упростить задачу создания новых комбинаций для обработки потенциально большого количества возможностей в чем-то, что легче изменить, чем код. вам нужно перекомпилировать его в XML и запросить результат на основе выбора пользователей.

У вас может быть класс, подобный следующему, для хранения результата.

public class AttackResult
{
    public int ChanceToCrit { get;set; }
    public int ChanceToBurn { get;set; }
    public int FireDmg { get;set; }
}

Что касается XML, у вас, вероятно, будет что-то вроде

<Options>
    <Attack>
        <AttackResult>
            <ChanceToCrit>5</ChanceToCrit>
        </AttackResult>
        <Attack>
            <AttackResult>
                <ChanceTCrit>10</ChanceToCrit>
            </AttackResult>
        </Attack>
    </Attack>
    <Fire>
    </Fire>
</Options>

Вы поняли идею. При обработке вашего выбора пользователей (я просто предполагаю здесь) вы можете сохранить то, что они делают, например, в Enum. Таким образом, чтобы использовать XML с этим, вы можете сделать что-то вроде

public enum ActionType
{
    Attack,
    Fire,
    None
}

Я думаю, что все действия должны быть выполнены для того, чтобы этот пример работал с вашей игровой идеей.

public ActionResult ProcessActions(List<ActionType> allActions)
{
    string xpathquery = "Options";

    foreach(var playerAction in allActions)
    {
        if(playerAction == ActionType.None)
           break;
        xpathquery += "/" + playerAction.ToString();
    }

    //Query xmldocument of all options
    var result = Singleton.ActionsDoc.SelectSingleNode(xpathquery);
    string attackRes = result.InnerXml;
    XmlSerializer serializer = new XmlSerializer(typeof(ActionResult))
    //Might need to prepend something to attackRes before deserializing, none of this is tested
    byte[] allBytes = Encoding.UTF8.GetBytes(attackRes);
    MemoryStream ms = new MemoryStream(allBytes);

    ActionResult result = (ActionResult)serializer.Deserialize(ms);

    return result;
}

У этого варианта есть свои недостатки, такие как XmlSerializer, который довольно медленный, но это означает, что вы можете создать простой инструмент, который поможет сгенерировать XML, необходимый для всех ваших параметров, и просто настроить значения для балансировки игры.

Один вариант на столе. Надеюсь, это поможет.

person Darren Reid    schedule 15.11.2012