Как настроить систему отмены / повтора во время выполнения с Unity3D - PullRequest
0 голосов
/ 13 февраля 2020

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

Я просто хотел, чтобы конечный пользователь мог отменить / повторить его последнее действие во время выполнения и не в режиме редактора . Я сделал несколько попыток и попробовал много различных сценариев, но не добился никакого прогресса. Какие-либо предложения?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class undoit : MonoBehaviour
{
    public class setting
    {
        public GameObject Obj;
        public Vector3 Pos;
        public Quaternion Rot;
        public bool Deleted;

        public void Restore()
        {
            Obj.transform.position = Pos;
            Obj.transform.rotation = Rot;
            Obj.SetActive(Deleted);
        }
        public setting(GameObject g)
        {
            Obj = g;
            Pos = g.transform.position;
            Rot = g.transform.rotation;
            Deleted = g.activeSelf;
        }
    }
    public List<setting> UndoList;

    public void AddUndo(GameObject g)
    {
        setting s = new setting(g);
        UndoList.Add(s);
    }
    public void Undo()
    {
        if (UndoList.Count > 0)
        {
            UndoList[UndoList.Count - 1].Restore();
            UndoList.RemoveAt(UndoList.Count - 1);
        }
    }
    void Start()
    {
        UndoList = new List<setting>();
    }
}

1 Ответ

1 голос
/ 13 февраля 2020

Сначала вам нужно что-то, что вы можете сохранить соответствующие данные, например,

public struct ObjectState
{
    // The transform this data belongs to
    private Transform transform;

    private Vector3 localPosition;
    private Quaternion localRotation;
    private Vector3 localScale;

    private bool active;

    public ObjectState (GameObject obj)
    {
        transform = obj.transform;
        localPosition = transform.localPosition;
        localRotation = transform.localRotation;
        localScale = transform.localScale;

        active = obj.activeSelf;
    }

    public void Apply()
    {
        transform.localPosition = localPosition;
        transform.localRotation = localRotation;
        transform.localScale = localScale;

        transform.gameObject.SetActive(active);
    }
}

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

public struct UndoableChange
{
    private ObjectState _before;
    private ObjectState _after;

    public UndoableChange (ObjectState before, ObjectState after)
    {
        _before = before;
        _after = after;
    }

    public void Undo()
    {
        _before.Apply();
    }   

    public void Redo()
    {
        _after.Apply();
    }     
}

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

public struct UndoableChange
{
    private List<ObjectState> _before;
    private List<ObjectState> _after;

    public UndoableChange (List<ObjectState> before, List<ObjectState> after)
    {
        _before = before;
        _after = after;
    }

    public void Undo()
    {
        foreach(var state in _before)
        {
            state.Apply();
        }
    }   

    public void Redo()
    {
        foreach(var state in _after)
        {
            state.Apply();
        }
    }     
}

Тогда для контроллера я бы использовал два Stack. A Stack является «первым пришел - первым вышел», именно то, что вам нужно. Вы добавляете новые записи, используя Push, и используя Pop, вы извлекаете последний добавленный элемент и в то же время удаляете его из стека. Так что это выглядело бы как

public static class UndoRedo
{
    private static Stack<UndoableChange> undoStack = new Stack<UndoableChange>();
    private static Stack<UndoableChange> redoStack = new Stack<UndoableChange>();

    public static void Undo()
    {
        if(undoStack.Count == 0) return;

        var lastAction = undoStack.Pop();

        lastAction.Undo();

        redoStack.Push(lastAction);
    }

    public static void Redo()
    {
        if(redoStack.Count == 0) return;

        var lastAction = redoStack.Pop();

        lastAction.Redo();

        undoStack.Push(lastAction);
    }

    public static void AddAction(UndoableChange action)
    {
        redoStack.Clear();

        undoStack.Push(action);
    }
}

Я не знаю, как именно ваш пользователь взаимодействует с объектами, но теперь вы всегда будете делать что-то вроде

// at some point store the initial values
var before = new ObjectState (selectedGameObject);

//TODO Apply all your changes to the selected object 

// get the new values
var after = new ObjectState (selectedGameObject);

// Log to the history
UndoRedo.AddAction(new UndoableChange(before, after));

или использовать списки для всех выбранных объектов соответственно например, просто используя Linq

using System.Linq;

...

var before = selectedGameObjects.Select(obj => new ObjectState(obj)).ToList();

//TODO Apply all your changes to all the selected objects 

var after = selectedGameObjects.Select(obj => new ObjectState(obj)).ToList();

UndoRedo.AddAction(new UndoableChange(before, after));

Примечание: напечатано на смартфоне, но я надеюсь, что идея проясняется

...