Unity【SwitchableObject】- 实现一个物体开关控制系统

2022-08-29 16:50:05 浏览数 (2)

本文介绍如何实现一个物体的开关控制系统,例如门的开关控制、灯的开关控制等,一切包含打开、关闭这两种状态的物体,均可以通过继承下面的抽象类进行重写实现。

状态枚举:

代码语言:javascript复制
namespace SK.Framework
{
    /// <summary>
    /// 状态
    /// </summary>
    public enum SwitchState
    {
        /// <summary>
        /// 开着的
        /// </summary>
        Open,
        /// <summary>
        /// 关着的
        /// </summary>
        Close,
    }
}

接口:

代码语言:javascript复制
namespace SK.Framework
{
    /// <summary>
    /// 可开关物体接口
    /// </summary>
    public interface ISwitchableObject
    {
        SwitchState State { get; }

        void Switch();

        void Open();

        void Close();
    }
}

抽象类:

代码语言:javascript复制
using UnityEngine;

namespace SK.Framework
{
    public abstract class SwitchableObject : MonoBehaviour, ISwitchableObject
    {
        //默认设为关闭状态
        [SerializeField] protected SwitchState state = SwitchState.Close;

        /// <summary>
        /// 当前状态
        /// </summary>
        public SwitchState State { get { return state; } }

        /// <summary>
        /// 切换 若为打开状态则关闭 若为关闭状态则打开
        /// </summary>
        public void Switch()
        {
            switch (State)
            {
                case SwitchState.Open: Close(); break;
                case SwitchState.Close: Open(); break;
            }
        }

        /// <summary>
        /// 开门
        /// </summary>
        public abstract void Open();
        /// <summary>
        /// 关门
        /// </summary>
        public abstract void Close();
    }
}

开关处理器,例如我们想要通过一个开关控制多个灯时,或者通过一个开关控制一对门时,均可以通过开关处理器,同时处理多个可开关物体:

代码语言:javascript复制
using UnityEngine;

namespace SK.Framework
{
    /// <summary>
    /// 开关处理器(把手)
    /// </summary>
    public class SwitchableObjectHandler : SwitchableObject
    {
        [SerializeField] private SwitchableObject[] handleArray;

        public override void Open()
        {
            if (state == SwitchState.Open) return;
            state = SwitchState.Open;
            for (int i = 0; i < handleArray.Length; i  )
            {
                handleArray[i].Open();
            }
        }
        public override void Close()
        {
            if (state == SwitchState.Close) return;
            state = SwitchState.Close;
            for (int i = 0; i < handleArray.Length; i  )
            {
                handleArray[i].Close();
            }
        }
    }
}

这里以门的开关控制为例,我们将门的类型分为移动门和旋转门,首先创建一个门的基类:

代码语言:javascript复制
using UnityEngine;

namespace SK.Framework
{
    /// <summary>
    /// 可开关门
    /// </summary>
    public abstract class SwitchableDoor : SwitchableObject
    {
        //开/关所用的时长
        [SerializeField] protected float duration = 0.5f;
        //打开状态的值
        protected Vector3 openValue;
        //关闭状态的值
        protected Vector3 closeValue;
    }
}

1.移动门:

参数说明:

1.State:门的默认状态(在场景中门是开着还是关着的);

2.Duration:开关门动作的时长;

3.Direction:门从默认状态到另一个状态的移动方向;

4.Magnitude:门从默认状态到另一状态移动的长度。

代码语言:javascript复制
using UnityEngine;
using System.Collections;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace SK.Framework
{
    /// <summary>
    /// 移动门
    /// </summary>
    public class MoveDoor : SwitchableDoor
    {
        [SerializeField] private Vector3 direction; //移动方向
        [SerializeField] private float magnitude = 1f; //移动的长度
        private Coroutine switchCoroutine;

        private void Start()
        {
            switch (state)
            {
                case SwitchState.Open:
                    openValue = transform.position;
                    closeValue = transform.position   direction.normalized * magnitude;
                    break;
                case SwitchState.Close:
                    openValue = transform.position   direction.normalized * magnitude;
                    closeValue = transform.position;
                    break;
            }
        }
        public override void Open()
        {
            if (state == SwitchState.Open) return;
            state = SwitchState.Open;
            if (switchCoroutine != null) StopCoroutine(switchCoroutine);
            switchCoroutine = StartCoroutine(OpenCoroutine());
        }
        public override void Close()
        {
            if (state == SwitchState.Close) return;
            state = SwitchState.Close;
            if (switchCoroutine != null) StopCoroutine(switchCoroutine);
            switchCoroutine = StartCoroutine(CloseCoroutine());
        }

        private IEnumerator OpenCoroutine()
        {
            float beginTime = Time.time;
            Vector3 beginPos = transform.position;
            for (;(Time.time - beginTime) < duration;)
            {
                float t = (Time.time - beginTime) / duration;
                transform.position = Vector3.Lerp(beginPos, openValue, t);
                yield return null;
            }
            transform.position = openValue;
            switchCoroutine = null;
        }
        private IEnumerator CloseCoroutine()
        {
            float beginTime = Time.time;
            Vector3 beginPos = transform.position;
            for (; (Time.time - beginTime) < duration;)
            {
                float t = (Time.time - beginTime) / duration;
                transform.position = Vector3.Lerp(beginPos, closeValue, t);
                yield return null;
            }
            transform.position = closeValue;
            switchCoroutine = null;
        }
#if UNITY_EDITOR
        private void OnDrawGizmosSelected()
        {
            if (!Application.isPlaying)
            {
                switch (state)
                {
                    case SwitchState.Open:
                        openValue = transform.position;
                        closeValue = transform.position   direction.normalized * magnitude;
                        break;
                    case SwitchState.Close:
                        openValue = transform.position   direction.normalized * magnitude;
                        closeValue = transform.position;
                        break;
                }
            }
            Handles.color = Color.cyan;
            Handles.DrawWireCube(openValue, Vector3.one * .1f);
            Handles.DrawWireCube(closeValue, Vector3.one * .1f);
            Handles.DrawLine(openValue, closeValue);
            Handles.Label(openValue, "Open");
            Handles.Label(closeValue, "Close");
        }
#endif
    }
}

2.旋转门:

参数说明:

1.State:门的默认状态(在场景中门是开着还是关着的);

2.Duration:开关门动作的时长;

3.Angle:门从默认状态到另一个状态的旋转角度。

代码语言:javascript复制
using UnityEngine;
using System.Collections;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace SK.Framework
{
    /// <summary>
    /// 旋转门
    /// </summary>
    public class RotateDoor : SwitchableDoor
    {
        [SerializeField] private float angle = 90f; //旋转角度
        private Coroutine switchCoroutine;

        private void Start()
        {
            switch (state)
            {
                case SwitchState.Open:
                    openValue = transform.forward   transform.position;
                    closeValue = Quaternion.AngleAxis(angle, transform.up) * transform.forward   transform.position;
                    break;
                case SwitchState.Close:
                    openValue = Quaternion.AngleAxis(angle, transform.up) * transform.forward   transform.position;
                    closeValue = transform.forward   transform.position;
                    break;
            }
        }
        public override void Open()
        {
            if (state == SwitchState.Open) return;
            state = SwitchState.Open;
            if (switchCoroutine != null) StopCoroutine(switchCoroutine);
            switchCoroutine = StartCoroutine(OpenCoroutine());
        }
        public override void Close()
        {
            if (state == SwitchState.Close) return;
            state = SwitchState.Close;
            if (switchCoroutine != null) StopCoroutine(switchCoroutine);
            switchCoroutine = StartCoroutine(CloseCoroutine());
        }

        private IEnumerator OpenCoroutine()
        {
            float beginTime = Time.time;
            Quaternion beginRot = transform.rotation;
            Quaternion targetRot = Quaternion.LookRotation(openValue - transform.position, transform.up);
            for (; (Time.time - beginTime) < duration;)
            {
                float t = (Time.time - beginTime) / duration;
                transform.rotation = Quaternion.Lerp(beginRot, targetRot, t);
                yield return null;
            }
            transform.rotation = targetRot;
            switchCoroutine = null;
        }
        private IEnumerator CloseCoroutine()
        {
            float beginTime = Time.time;
            Quaternion beginRot = transform.rotation;
            Quaternion targetRot = Quaternion.LookRotation(closeValue - transform.position, transform.up);
            for (; (Time.time - beginTime) < duration;)
            {
                float t = (Time.time - beginTime) / duration;
                transform.rotation = Quaternion.Lerp(beginRot, targetRot, t);
                yield return null;
            }
            transform.rotation = targetRot;
            switchCoroutine = null;
        }
#if UNITY_EDITOR
        private void OnDrawGizmosSelected()
        {
            if (!Application.isPlaying)
            {
                switch (state)
                {
                    case SwitchState.Open:
                        openValue = transform.forward   transform.position;
                        closeValue = Quaternion.AngleAxis(angle, transform.up) * transform.forward   transform.position;
                        break;
                    case SwitchState.Close:
                        openValue = Quaternion.AngleAxis(angle, transform.up) * transform.forward   transform.position;
                        closeValue = transform.forward   transform.position;
                        break;
                }
            }
            Handles.color = Color.cyan;
            Handles.DrawWireCube(openValue, Vector3.one * .1f);
            Handles.DrawWireCube(closeValue, Vector3.one * .1f);
            Handles.DrawLine(transform.position, openValue);
            Handles.DrawLine(transform.position, closeValue);
            Handles.Label(openValue, "Open");
            Handles.Label(closeValue, "Close");
        }
#endif
    }
}

测试代码:

代码语言:javascript复制
using UnityEngine;
using SK.Framework;

public class Example : MonoBehaviour
{
    [SerializeField] private SwitchableObject door;

    private void OnGUI()
    {
        if (GUILayout.Button("Open", GUILayout.Width(200f), GUILayout.Height(50f))) door.Open();
        if (GUILayout.Button("Close", GUILayout.Width(200f), GUILayout.Height(50f))) door.Close();
        if (GUILayout.Button("Switch", GUILayout.Width(200f), GUILayout.Height(50f))) door.Switch();
    }
}

0 人点赞