-2

ティックベースのシステムを実装する方法について考えてみたいと思います。

プレイヤーまたは非プレイヤーが取得したすべてのアクションには、最初に実行する時間とクールダウン時間があります。クリーチャーのクールダウン時間が経過すると、新しいアクションを選択できるようになります。プレーヤーがアクションを選択する必要がある場合、ゲームは「一時停止」されます。

例:

1: プレーヤーのヘビースイング (実行に 50 ティック、クールダウンに 50 ティック)

2: ゲームは 50 ティック続きます。

3: NPC はアクションを設定できます。

4: プレイヤーがスイングし、50 ティックの間クールダウンします。

5:NPCはアクションを設定できます。

6: プレーヤーのゲームが一時停止しました。

私が現在持っているものは機能しますが、効率的ではありません。各アクションを静的メソッドとして持つクラスがあります。これらのメソッドは、すべてのデータを含む構造体を出力します。これは、個々のクリーチャーのアクション キューに渡されます。

更新ループごとにキューが呼び出され、プレーヤーがアクションを実行した場合は攻撃時間のカウントダウンが開始されます。攻撃が解決されたら、アクション クラスの静的メソッドを再度呼び出します。そして、クールダウン タイマーのカウント ダウンを開始します。

したがって、私が持っているべきものは、おそらくすべてのアクションを保持し、そのリストをソートして不要な時間/ティックをスキップし、次のアクションに直接進むリストです。しかし、移動、攻撃、能力などのさまざまなタイプのアクションがあり、これの適切な実装について頭を悩ませることはできません.

クリーチャーが基本的な攻撃を実行すると、これが呼び出されます (attack はクリーチャー自身のインスタンス化された攻撃構造体です)

attack = Actions.BasicAttack(this, player, rand);

Actions クラスは次のようになります。

public struct Attack
    {
        public int Damage;
        public string Type;
        public int Time;
        public int Cooldown;
        public Creature target;
        public bool solved;
    }




    public static Attack BasicAttack(Creature attacker, Creature defender, Random rand)
    {
        Attack attack = new Attack();

        attack.Damage = rand.Next(attacker.MinBaseDmg, attacker.MaxBaseDmg + 1);
        attack.Type = "Melee";
        attack.Time = 50;
        attack.Cooldown = 30;
        attack.target = defender;
        attack.solved = false;

        return attack;
    }

これは、プレイヤーがアクション キューを取得したときに、各クリーチャーの update メソッドで呼び出されます。プレーヤーにキューされたアクションがない場合は Tick = 0、プレーヤーにキューされたアクションがある場合は Tick = 1 です。

protected void ActionCue(int tick)
    {
        if (attack.target != null)
        {
            if (attack.Time > 1)
            {
                Console.WriteLine(attack.Time);
                attack.Time -= tick;
                this.free = false;
            }
            else if (!attack.solved)
            {
                Actions.SolveAttack(attack.Damage, attack.Type, attack.target);
                attack.solved = true;
            }
            else if (attack.solved && attack.Cooldown > 1)
            {
                //Console.WriteLine(attack.Cooldown);
                attack.Cooldown -= tick;
            }
            else
                free = true;
        }
    }
4

2 に答える 2

1

このようなものを検討してください(私は疑似コードを使用します-最適化などにはほど遠いですが、それは十分に速いか、あなたがやろうとしていることを最適化するようにあなたを設定するかもしれません)

class CombatEventList
{
   public static AddEvent(CombatEvent event, int ticksTillHappens)   
}

virtual class CombatEvent
{
    public virtual void CombatAction()
}

class PlayerActionChoice : ComabtEvent
{
   public void CombatAction
   {
       var playerAction = GetUserDecision();//returns i.e CombatEvent PlayerMeeleAttack
       CombatEventList.AddEvent(playerAction, 0);
   }
}

class PlayerMeeleAttack : CombatEvent
{
   int cooldownInTicks = 50;

   public void CombatAction
   {
       MakeAttack()//damages the moster etc - all the stuff the attack is supposed to do
       var nextEvent = new PlayerActionChoice();
       CombatEventList.AddEvent(nextEvent, cooldownInTicks);
   }
}

それで、これはどのように機能しますか?

イベント一覧ができました。

リストは、現在発生するはずのすべてのイベントをチェックし、それらの CombatAction を実行します。

CombatAction で、イベントは新しいイベントをリストに追加します。たとえば、PlayerMeeleAttack イベントは、適切なクールダウンの後に PlayerActionChoice イベントを設定し、後で別のアクションを実行できるようにします。

現在のすべての CombatEvents が解決され、独自の CombatEvents がリストに追加された後、リストは次のイベントをチェックします (遅延が最も少ない)。

リストは、指定されたティック数の間スリープします (次のイベントの遅延)。スリープが完了すると、すべてのイベントのクールダウンが適切な量だけ低下し、現在のすべてのイベント (遅延が 0 になったもの) を処理します。

これはループに入る

リストは CombatStartEvent から始まります。これはすぐに発生します (遅延 0)。CombatAction メソッドで PlayerActionChoice および MonsterActionChoice イベントを設定します。

もちろん、これは最適とはほど遠いものであり、単なるスケッチ、または熟考するためのアイデアに過ぎません。より良いアイデアがあるかもしれませんが、私は問題をあまり考えていませんでした-しかし、これは明らかにあなたの現在の解決策よりも効率的です:)

于 2012-10-11T13:20:32.283 に答える
0

数時間後、これが機能したようです。これが必要な人のためのコードです。私もフィードバックをお待ちしています。

これは能力クラスです。必要なものはすべてここに追加できます。ティックベースのシステムでは、時間変数だけが重要です。

public string AbilityName { get; private set; }
    public int minDamage { get; private set; }
    public int maxDamage { get; private set; }

    public int ActivationTime { get; private set; }
    public int CooldownTime { get; private set; }
    public int Timer;

    public Ability(string AbilityName)
    {
        if (AbilityName == "attack")
        {
            this.AbilityName = AbilityName;
            minDamage = 10;
            maxDamage = 20;
            ActivationTime = 20;
            CooldownTime = 30;
            Timer = ActivationTime;

            iconPath = "ability/icon/attack";
        }
    }

これはタスク クラスであり、アビリティ、アタッカー、およびターゲットがパラメーターとして渡されます。アビリティ名またはタイプを使用して、移動と攻撃などのさまざまな種類/タイプのアビリティを実行できます。

public Ability ability { get; private set; }
    public bool onCooldown;

    public Creature attacker { get; private set; }
    List<Creature> targets = new List<Creature>();

    /// <summary>
    /// Initiates a attack task
    /// </summary>
    /// <param name="attacker"></param>
    /// <param name="defender"></param>
    public Task(Creature attacker, List<Creature> targets, Ability ability)
    {
        this.ability = ability;
        this.attacker = attacker;
        this.targets = targets;

        onCooldown = false;
    }

    public void Perform()
    {
        //performce abilty
        Console.WriteLine(attacker.Name + " performce ability");
    }

プレイヤーまたは AI は、所有するアビリティから次のようにタスクを作成できるようになりました。

targets.Add(player); //This is just a basic attack so only one "creature" gets in the list
                task = new Task(this, targets, abilityList[0]); //Task is created
                taskList.Add(task); //Task is added to a list i manage in a main class
                free = false; //creature is put on hold and cant do anything till task is completed

これは、ほとんどの魔法が起こる場所です。メイン クラスでは、プレーヤーが「フリー」でない場合、このメソッドは更新ごとに呼び出されます。ステータスを更新した後にその統計を編集したくないため、次のタスクで何かを行う前にすべてのタスクを更新します。

private void TaskHandler()
    {
        int ticksToAdvance = 0;
        // get the next task requiring a action
        taskList.Sort((x, y) => x.ability.Timer.CompareTo(y.ability.Timer));

        //get the amount of cooldown left
        ticksToAdvance = taskList[0].ability.Timer;

        //Update all tasks
        foreach (Task t in taskList)
        {
            t.ability.Timer -= ticksToAdvance;
        }

        //check if this task is on cooldown
        if (taskList[0].onCooldown)
        {
            //Reset ability timer, free creature and remove task from the list.
            taskList[0].ability.Timer = taskList[0].ability.ActivationTime;
            taskList[0].attacker.free = true;
            taskList.RemoveAt(0);
        }
        else
        {
            //perform ability
            taskList[0].Perform();
            //set timer to cooldown
            taskList[0].onCooldown = true;
            taskList[0].ability.Timer = taskList[0].ability.CooldownTime;
        }
    }
于 2012-10-11T17:13:58.620 に答える