using System; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Utilities; ////TODO: hide in UI namespace UnityEngine.InputSystem.Controls { /// /// A button that is considered pressed if the underlying state has a value in the specific range. /// /// /// This control is most useful for handling HID-style hat switches. Unlike , /// which by default is stored as a bitfield of four bits that each represent a direction on the pad, /// these hat switches enumerate the possible directions that the switch can be moved in. For example, /// the value 1 could indicate that the switch is moved to the left whereas 3 could indicate it is /// moved up. /// /// /// /// [StructLayout(LayoutKind.Explicit, Size = 32)] /// internal struct DualShock4HIDInputReport : IInputStateTypeInfo /// { /// [FieldOffset(0)] public byte reportId; /// /// [InputControl(name = "dpad", format = "BIT", layout = "Dpad", sizeInBits = 4, defaultState = 8)] /// [InputControl(name = "dpad/up", format = "BIT", layout = "DiscreteButton", parameters = "minValue=7,maxValue=1,nullValue=8,wrapAtValue=7", bit = 0, sizeInBits = 4)] /// [InputControl(name = "dpad/right", format = "BIT", layout = "DiscreteButton", parameters = "minValue=1,maxValue=3", bit = 0, sizeInBits = 4)] /// [InputControl(name = "dpad/down", format = "BIT", layout = "DiscreteButton", parameters = "minValue=3,maxValue=5", bit = 0, sizeInBits = 4)] /// [InputControl(name = "dpad/left", format = "BIT", layout = "DiscreteButton", parameters = "minValue=5, maxValue=7", bit = 0, sizeInBits = 4)] /// [FieldOffset(5)] public byte buttons1; /// } /// /// /// public class DiscreteButtonControl : ButtonControl { /// /// Value (inclusive) at which to start considering the button to be pressed. /// /// /// is allowed to be larger than . This indicates /// a setup where the value wraps around beyond , skips , /// and then goes all the way up to . /// /// For example, if the underlying state represents a circular D-pad and enumerates its /// 9 possible positions (including null state) going clock-wise from 0 to 8 and with 1 /// indicating that the D-pad is pressed to the left, then 1, 2, and 8 would indicate /// that the "left" button is held on the D-pad. To set this up, set /// to 8, to 2, and to 0 (the default). /// public int minValue; /// /// Value (inclusive) beyond which to stop considering the button to be pressed. /// public int maxValue; /// /// Value (inclusive) at which the values cut off and wrap back around. Considered to not be set if equal to . /// /// /// This is useful if, for example, an enumeration has more bits than are used for "on" states of controls. For example, /// a bitfield of 4 bits where values 1-7 (i.e. 0001 to 0111) indicate "on" states of controls and value 8 indicating an /// "off" state. In that case, set to 8 and to 7. /// public int wrapAtValue; /// /// Value at which the button is considered to not be pressed. Usually zero. Some devices, however, /// use non-zero default states. /// public int nullValue; /// /// Determines the behavior of . By default, attempting to write a /// will result in a NotSupportedException. /// public WriteMode writeMode; protected override void FinishSetup() { base.FinishSetup(); if (!stateBlock.format.IsIntegerFormat()) throw new NotSupportedException( $"Non-integer format '{stateBlock.format}' is not supported for DiscreteButtonControl '{this}'"); } public override unsafe float ReadUnprocessedValueFromState(void* statePtr) { var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset; // Note that all signed data in state buffers is in excess-K format. var intValue = MemoryHelpers.ReadTwosComplementMultipleBitsAsInt(valuePtr, m_StateBlock.bitOffset, m_StateBlock.sizeInBits); var value = 0.0f; if (minValue > maxValue) { // If no wrapping point is set, default to wrapping around exactly // at the point of minValue. if (wrapAtValue == nullValue) wrapAtValue = minValue; if ((intValue >= minValue && intValue <= wrapAtValue) || (intValue != nullValue && intValue <= maxValue)) value = 1.0f; } else { value = intValue >= minValue && intValue <= maxValue ? 1.0f : 0.0f; } return Preprocess(value); } public override unsafe void WriteValueIntoState(float value, void* statePtr) { if (writeMode == WriteMode.WriteNullAndMaxValue) { var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset; var valueToWrite = value >= pressPointOrDefault ? maxValue : nullValue; MemoryHelpers.WriteIntAsTwosComplementMultipleBits(valuePtr, m_StateBlock.bitOffset, m_StateBlock.sizeInBits, valueToWrite); return; } // The way these controls are usually used, the state is shared between multiple DiscreteButtons. So writing one // may have unpredictable effects on the value of other buttons. throw new NotSupportedException("Writing value states for DiscreteButtonControl is not supported as a single value may correspond to multiple states"); } /// /// How should behave. /// public enum WriteMode { /// /// will throw NotSupportedException. /// WriteDisabled = 0, /// /// Write for when the button is considered not pressed and /// write for when the button is considered pressed. /// WriteNullAndMaxValue = 1, } } }