Застрял при создании функции отмены в C#/XNA Game

Поэтому мне нужна функция отмены для MapEditor, над которым я работаю в своей игре. У меня частично получилось. Когда я нажимаю на карту, она сохраняет как старую, так и новую плитку в отдельных списках. Когда я нажимаю Ctrl + Z, это отменяет последнее действие и так далее.

Моя проблема, когда вы отменяете некоторые действия, а затем добавляете новое действие в списки. Что должно произойти тогда? Должен ли я просто добавить новое действие в конец списков или удалить все с текущей позиции в списках до конца, а затем добавить новое действие в список.

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

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

Мой текущий код при добавлении в список отмены:

private void UpdateCorrectedTiles(Dictionary<TileSide, Tile> correctedTiles, bool saveEditedTiles)
{
    List<Tile> tmpUndoTilesList = new List<Tile>();
    List<Tile> tmpRedoTilesList = new List<Tile>();

    foreach (Tile tile in tiles)
    {
        foreach (KeyValuePair<TileSide, Tile> correctedTile in correctedTiles)
        {
            if (tile.GetTilePosition() == correctedTile.Value.GetTilePosition())
            {
                if (correctedTile.Key == TileSide.Clicked && saveEditedTiles
                    && Tile.IsTileChanged(previousClickedTile, correctedTile.Value))
                {
                    Tile undoTile = Tile.CreateCloneTile(previousClickedTile);
                    Tile redoTile = correctedTile.Value;

                    tmpUndoTilesList.Add(undoTile);
                    tmpRedoTilesList.Add(redoTile);
                }

                TileInfo info = correctedTile.Value.GetTileInfo();
                Vector2 frames = correctedTile.Value.GetCurrentFrame();

                tile.SetTileInfo(info);
                tile.SetCurrentFrame(frames);
            }
        }
    }

    if (saveEditedTiles && tmpUndoTilesList.Count > 0 && tmpRedoTilesList.Count > 0)
    {
        undoTilesList.Add(tmpUndoTilesList);
        redoTilesList.Add(tmpRedoTilesList);

        currentUndoRedoIndex = undoTilesList.Count - 1;
    }
}

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

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

Надеюсь, вы понимаете, чего я хочу.
Спасибо!


person DijkeMark    schedule 16.03.2013    source источник
comment
Можете ли вы объяснить, что этот метод действительно делает? Я не могу понять это. Почему он добавляется как к undoList, так и к redoList?   -  person Mohammad Dehghan    schedule 16.03.2013
comment
Мне жаль. Добавлено объяснение под кодом   -  person DijkeMark    schedule 16.03.2013


Ответы (2)


Стандартное ожидаемое поведение для отмены: вы можете отменить действие, а затем повторить, чтобы вернуться к тому, что было до того, как вы использовали отмену ... если вы не выполните новое действие! Новое действие удаляет все возможности повторения.

Чтобы помочь осмыслить (в скобках показано текущее состояние):

Акт1 -> Акт2 -> (Акт3)

Отменить дважды дает...

(Акт1) -> Акт2 -> Акт3

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

Акт1 -> (НовыйАкт2)

...вот и все! Act3 сейчас просто забыт, выброшен как вчерашний мусор. Альтернатива слишком сложна в реализации и ужасно неинтуитивна в использовании! Например, если вы создаете плитку, меняете цвет, затем отменяете действие перед созданием плитки и создаете плитку в другом месте. Если вы повторите, эта новая плитка изменит цвет? Появляется ли старая плитка в новом цвете? Если вы снова отмените действие, вернется ли первый набор правок вместо последнего? Фу!

Так работают даже такие сложные программы, как Photoshop, поэтому было бы неразумно ожидать, что редактор карт будет вести себя как-то иначе. Многие программы даже не поддерживают Redo, так что вам решать, хотите ли вы его поддерживать! Это как раз один из тех случаев, когда человек усложняет себе жизнь.

В принципе, вы, кажется, хорошо идти, как есть!

person BrianH    schedule 16.03.2013
comment
Ну, по крайней мере, у меня есть правильное представление. Теперь мне нужно найти способ заставить его работать. Спасибо - person DijkeMark; 16.03.2013

Вообще говоря, у вас должен быть стек IMapChange, где IMapChange выглядит примерно так:

public interface IMapChange
{
    //Performs the change on a TileMap
    Boolean PerformChange(TileMap map);

    //Reverts the change on a TileMap
    Boolean RevertChange(TileMap map);
}

У вас должен быть метод, который вызывается для внесения изменений:

public void SetTile(Vector2 position, int tileId)
{
    Tile oldTile = Map.GetTile(position);

    Tile newTile = new Tile(position, tileId);

    MapChange change = new MapChange(oldTile, newTile);

    //Only push to cahngestacks if successfull    
    if (change.PerformChange(Map))
    {
        ChangeStack.Push(change);

        //We don't want you to be able to "redo" anymore if you do something new.
        RedoStack.Clear();
    }
}

public void RemoveTile(Vector2 position)
{
    Tile oldTile = Map.GetTile(position);

    Tile newTile = null;

    MapChange change = new MapChange(oldTile, newTile);

    //Only push to changestacks if successfull    
    if (change.PerformChange(Map))
    {
        ChangeStack.Push(change);

        //We don't want you to be able to "redo" anymore if you do something new.
        RedoStack.Clear();
    }
}

Отмена может выглядеть так:

public void Undo()
{
    var lastChange = ChangeStack.Pop();

    //Try to revert. If revert fails, put the change back in the stack
    if (!lastChange.RevertChange(Map))
        ChangeStack.Push(lastChange);
    else
        RedoStack.Push(lastChange);
}

и отменить так:

public void Redo()
{
    var lastChange = RedoStack.Pop();

    //Try to perform. If successfull, put back in ChangeStack
    if (lastChange.PerformChange(Map))
        ChangeStack.Push(lastChange);
    else
        RedoStack.Push(lastChange);
}

PerformChange и RevertChange в основном:

Выполнить:
- если oldTile не нулевой, попытаться удалить его с карты.
- вставить новыйTile в карту.

Возврат:
- если newTile не нулевой, чтобы удалить его с карты
- вставить oldTile в карту.

person Stig-Rune Skansgård    schedule 18.03.2013