test
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"displayName": "UI vs. Game Input",
|
||||
"description": "An example that shows how to deal with ambiguities that may arrise when overlaying interactive UI elements on top of a game scene."
|
||||
}
|
@@ -0,0 +1,114 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1001 &100100000
|
||||
Prefab:
|
||||
m_ObjectHideFlags: 1
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
m_TransformParent: {fileID: 0}
|
||||
m_Modifications: []
|
||||
m_RemovedComponents: []
|
||||
m_SourcePrefab: {fileID: 0}
|
||||
m_RootGameObject: {fileID: 1050929111787496}
|
||||
m_IsPrefabAsset: 1
|
||||
--- !u!1 &1050929111787496
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 100100000}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4939001955002736}
|
||||
- component: {fileID: 33329194892259988}
|
||||
- component: {fileID: 65754914235384544}
|
||||
- component: {fileID: 23498649278503262}
|
||||
- component: {fileID: 54430940299048930}
|
||||
m_Layer: 0
|
||||
m_Name: SimpleProjectile
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &4939001955002736
|
||||
Transform:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 100100000}
|
||||
m_GameObject: {fileID: 1050929111787496}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0.2921129, y: 0.2921129, z: 0.2921129}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!23 &23498649278503262
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 100100000}
|
||||
m_GameObject: {fileID: 1050929111787496}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RenderingLayerMask: 4294967295
|
||||
m_Materials:
|
||||
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_PreserveUVs: 1
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 0
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
--- !u!33 &33329194892259988
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 100100000}
|
||||
m_GameObject: {fileID: 1050929111787496}
|
||||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!54 &54430940299048930
|
||||
Rigidbody:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 100100000}
|
||||
m_GameObject: {fileID: 1050929111787496}
|
||||
serializedVersion: 2
|
||||
m_Mass: 1
|
||||
m_Drag: 0
|
||||
m_AngularDrag: 0.05
|
||||
m_UseGravity: 1
|
||||
m_IsKinematic: 0
|
||||
m_Interpolate: 0
|
||||
m_Constraints: 0
|
||||
m_CollisionDetection: 0
|
||||
--- !u!65 &65754914235384544
|
||||
BoxCollider:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 100100000}
|
||||
m_GameObject: {fileID: 1050929111787496}
|
||||
m_Material: {fileID: 0}
|
||||
m_IsTrigger: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_Size: {x: 1, y: 1, z: 1}
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5da6e8a2833c9042afb6a52de81b66c
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
addedSourceAssetObjects: []
|
||||
isPrefabVariant: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,47 @@
|
||||
# UI vs Game Input
|
||||
|
||||
>NOTE: More information related to ambiguities between UI and game input may be found [here in the documentation](http://docs.unity3d.com/Packages/com.unity.inputsystem@1.1/manual/UISupport.html#ui-and-game-input).
|
||||
|
||||
When interactive UI elements are overlaid over a game view, ambiguity may arise for inputs.
|
||||
|
||||
If, for example, there is a `UI.Button` on screen that can be clicked/tapped, while clicking/tapping on the scene itself also has associated functionality, clicking on the UI button should not also trigger the corresponding action on the scene.
|
||||
|
||||
This sample demonstrates how to handle input in such a situation.
|
||||
|
||||
## The Sample Scene
|
||||
|
||||

|
||||
|
||||
The sample scene has a UI button in each of the corners of the screen. The camera in the scene can be rotated and projectiles can be fired while at the same time the buttons in the UI can be clicked.
|
||||
|
||||
There are two ways to control the game:
|
||||
|
||||
1. "Pointer", i.e. mouse (optionally combined with keyboard) or touch input, and
|
||||
2. "Navigation", i.e. gamepad input.
|
||||
|
||||
### Mouse/Touch/Keyboard Input
|
||||
|
||||
- When clicking any of the buttons, the "status bar" text along the bottom edge of the screen changes.
|
||||
- Left-click-dragging with the mouse or finger-dragging with touch rotates the camera (note: only when starting the drag on the background). Alternatively, when using mouse&keyboard, holding the the left control key will engage camera control.
|
||||
- Right-clicking with the mouse or tapping the second finger while rotating the camera shoots a projectile.
|
||||
- Double-clicking/tapping on the scene resets the camera orientation.
|
||||
- Pressing `Escape` will bring up the game menu. With touch input, an extra button is shown in the game UI to do that.
|
||||
|
||||
### Gamepad Input
|
||||
|
||||
- The right stick rotates the camera and the right trigger fires a projectile.
|
||||
- Double-pressing the A button will reset the camera to its initial orientation.
|
||||
- Holding the left trigger switch to UI focus. UI selection is now active and can be changed with the d-pad or the sticks. The A button performs a button press.
|
||||
- Pressing B while in game brings up the main menu.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Pointer Input
|
||||
|
||||
For the most part, input processing is done in `Update()` such that actions are processed on a per-frame basis. Responses to actions that may conflict with UI input use [`IsPointerOverGameObject`](https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.EventSystem.html?q=ispointerovergameobj#UnityEngine_EventSystems_EventSystem_IsPointerOverGameObject) to determine whether the pointer is currently over UI. Since this is called from `Update()` and thus *outside* of input processing (i.e. not from within an `InputAction` callback), the method can be safely called and will return an accurate result.
|
||||
|
||||
There are two implementations of handling the `Fire` action. One uses the same approach just mentioned where the action's response is dealt with once per frame. The second one, however, immediately creates a projectile within the callback and thus operates at sub-frame accuracy. For a low-frequency input such as the `Fire` action here, this is not generally a useful thing to do but it is done here for the sake of demonstration. We cannot call [`IsPointerOverGameObject`](https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.EventSystem.html?q=ispointerovergameobj#UnityEngine_EventSystems_EventSystem_IsPointerOverGameObject) from the action callback and thus need to use the UI's public raycasting interface to determine "over UI?" state manually for the current pointer position.
|
||||
|
||||
### Navigation Input
|
||||
|
||||
Navigation input employs an explicit mode switch to go from gameplay to UI input. This is handled by `OnUIEngage`.
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3dac9f9dc33cd84e92ae1d43f926694
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 100b460020a15704692cc0ec34331d8a
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
|
||||
generateWrapperCode: 0
|
||||
wrapperCodePath:
|
||||
wrapperClassName:
|
||||
wrapperCodeNamespace:
|
Binary file not shown.
After Width: | Height: | Size: 146 KiB |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 638b0bf39f44c074ba954f8eecf4bc19
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -100
|
||||
wrapU: -1
|
||||
wrapV: -1
|
||||
wrapW: -1
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f402cc73a7e60cb42a78150f04a0a905
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,475 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
#endif
|
||||
|
||||
public class UIvsGameInputHandler : MonoBehaviour
|
||||
{
|
||||
public Text statusBarText;
|
||||
public GameObject inGameUI;
|
||||
public GameObject mainMenuUI;
|
||||
public GameObject menuButton;
|
||||
public GameObject firstButtonInMainMenu;
|
||||
public GameObject firstNavigationSelection;
|
||||
[Space]
|
||||
public PlayerInput playerInput;
|
||||
public GameObject projectile;
|
||||
|
||||
[Space]
|
||||
[Tooltip("Multiplier for Pointer.delta values when adding to rotation.")]
|
||||
public float m_MouseLookSensitivity = 0.1f;
|
||||
[Tooltip("Rotation per second with fully actuated Gamepad/joystick stick.")]
|
||||
public float m_GamepadLookSpeed = 10f;
|
||||
|
||||
private bool m_OpenMenuActionTriggered;
|
||||
private bool m_ResetCameraActionTriggered;
|
||||
private bool m_FireActionTriggered;
|
||||
internal bool m_UIEngaged;
|
||||
|
||||
private Vector2 m_Rotation;
|
||||
private InputAction m_LookEngageAction;
|
||||
private InputAction m_LookAction;
|
||||
private InputAction m_CancelAction;
|
||||
private InputAction m_UIEngageAction;
|
||||
private GameObject m_LastNavigationSelection;
|
||||
|
||||
private Mouse m_Mouse;
|
||||
private Vector2? m_MousePositionToWarpToAfterCursorUnlock;
|
||||
|
||||
internal enum State
|
||||
{
|
||||
InGame,
|
||||
InGameControllingCamera,
|
||||
InMenu,
|
||||
}
|
||||
|
||||
internal State m_State;
|
||||
|
||||
internal enum ControlStyle
|
||||
{
|
||||
None,
|
||||
KeyboardMouse,
|
||||
Touch,
|
||||
GamepadJoystick,
|
||||
}
|
||||
|
||||
internal ControlStyle m_ControlStyle;
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
// By default, hide menu and show game UI.
|
||||
inGameUI.SetActive(true);
|
||||
mainMenuUI.SetActive(false);
|
||||
menuButton.SetActive(false);
|
||||
|
||||
// Look up InputActions on the player so we don't have to do this over and over.
|
||||
m_LookEngageAction = playerInput.actions["LookEngage"];
|
||||
m_LookAction = playerInput.actions["Look"];
|
||||
m_CancelAction = playerInput.actions["UI/Cancel"];
|
||||
m_UIEngageAction = playerInput.actions["UIEngage"];
|
||||
|
||||
m_State = State.InGame;
|
||||
}
|
||||
|
||||
// This is called when PlayerInput updates the controls bound to its InputActions.
|
||||
public void OnControlsChanged()
|
||||
{
|
||||
// We could determine the types of controls we have from the names of the control schemes or their
|
||||
// contents. However, a way that is both easier and more robust is to simply look at the kind of
|
||||
// devices we have assigned to us. We do not support mixed models this way but this does correspond
|
||||
// to the limitations of the current control code.
|
||||
|
||||
if (playerInput.GetDevice<Touchscreen>() != null) // Note that Touchscreen is also a Pointer so check this first.
|
||||
m_ControlStyle = ControlStyle.Touch;
|
||||
else if (playerInput.GetDevice<Pointer>() != null)
|
||||
m_ControlStyle = ControlStyle.KeyboardMouse;
|
||||
else if (playerInput.GetDevice<Gamepad>() != null || playerInput.GetDevice<Joystick>() != null)
|
||||
m_ControlStyle = ControlStyle.GamepadJoystick;
|
||||
else
|
||||
Debug.LogError("Control scheme not recognized: " + playerInput.currentControlScheme);
|
||||
|
||||
m_Mouse = default;
|
||||
m_MousePositionToWarpToAfterCursorUnlock = default;
|
||||
|
||||
// Enable button for main menu depending on whether we use touch or not.
|
||||
// With kb&mouse and gamepad, not necessary but with touch, we have no "Cancel" control.
|
||||
menuButton.SetActive(m_ControlStyle == ControlStyle.Touch);
|
||||
|
||||
// If we're using navigation-style input, start with UI control disengaged.
|
||||
if (m_ControlStyle == ControlStyle.GamepadJoystick)
|
||||
SetUIEngaged(false);
|
||||
|
||||
RepaintInspector();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
switch (m_State)
|
||||
{
|
||||
case State.InGame:
|
||||
{
|
||||
if (m_OpenMenuActionTriggered)
|
||||
{
|
||||
m_State = State.InMenu;
|
||||
|
||||
// Bring up main menu.
|
||||
inGameUI.SetActive(false);
|
||||
mainMenuUI.SetActive(true);
|
||||
|
||||
// Disable gameplay inputs.
|
||||
playerInput.DeactivateInput();
|
||||
|
||||
// Select topmost button.
|
||||
EventSystem.current.SetSelectedGameObject(firstButtonInMainMenu);
|
||||
}
|
||||
|
||||
var pointerIsOverUI = IsPointerOverUI();
|
||||
if (pointerIsOverUI)
|
||||
break;
|
||||
|
||||
if (m_ResetCameraActionTriggered)
|
||||
transform.rotation = default;
|
||||
|
||||
// When using a pointer-based control scheme, we engage camera look explicitly.
|
||||
if (m_ControlStyle != ControlStyle.GamepadJoystick && m_LookEngageAction.WasPressedThisFrame() && IsPointerInsideScreen())
|
||||
EngageCameraControl();
|
||||
|
||||
// With gamepad/joystick, we can freely rotate the camera at any time.
|
||||
if (m_ControlStyle == ControlStyle.GamepadJoystick)
|
||||
ProcessCameraLook();
|
||||
|
||||
if (m_FireActionTriggered)
|
||||
Fire();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case State.InGameControllingCamera:
|
||||
|
||||
if (m_ResetCameraActionTriggered && !IsPointerOverUI())
|
||||
transform.rotation = default;
|
||||
|
||||
if (m_FireActionTriggered && !IsPointerOverUI())
|
||||
Fire();
|
||||
|
||||
// Rotate camera.
|
||||
ProcessCameraLook();
|
||||
|
||||
// Keep track of distance we travel with the mouse while in mouse lock so
|
||||
// that when we unlock, we can jump to a position that feels "right".
|
||||
if (m_Mouse != null)
|
||||
m_MousePositionToWarpToAfterCursorUnlock = m_MousePositionToWarpToAfterCursorUnlock.Value + m_Mouse.delta.ReadValue();
|
||||
|
||||
if (m_CancelAction.WasPressedThisFrame() || !m_LookEngageAction.IsPressed())
|
||||
DisengageCameraControl();
|
||||
|
||||
break;
|
||||
|
||||
case State.InMenu:
|
||||
|
||||
if (m_CancelAction.WasPressedThisFrame())
|
||||
OnContinueClicked();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
m_ResetCameraActionTriggered = default;
|
||||
m_OpenMenuActionTriggered = default;
|
||||
m_FireActionTriggered = default;
|
||||
}
|
||||
|
||||
private void ProcessCameraLook()
|
||||
{
|
||||
var rotate = m_LookAction.ReadValue<Vector2>();
|
||||
if (!(rotate.sqrMagnitude > 0.01))
|
||||
return;
|
||||
|
||||
// For gamepad and joystick, we rotate continuously based on stick actuation.
|
||||
float rotateScaleFactor;
|
||||
if (m_ControlStyle == ControlStyle.GamepadJoystick)
|
||||
rotateScaleFactor = m_GamepadLookSpeed * Time.deltaTime;
|
||||
else
|
||||
rotateScaleFactor = m_MouseLookSensitivity;
|
||||
|
||||
m_Rotation.y += rotate.x * rotateScaleFactor;
|
||||
m_Rotation.x = Mathf.Clamp(m_Rotation.x - rotate.y * rotateScaleFactor, -89, 89);
|
||||
transform.localEulerAngles = m_Rotation;
|
||||
}
|
||||
|
||||
private void EngageCameraControl()
|
||||
{
|
||||
// With a mouse, it's annoying to always end up with the pointer centered in the middle of
|
||||
// the screen after we come out of a cursor lock. So, what we do is we simply remember where
|
||||
// the cursor was when we locked and then warp the mouse back to that position after the cursor
|
||||
// lock is released.
|
||||
m_Mouse = playerInput.GetDevice<Mouse>();
|
||||
m_MousePositionToWarpToAfterCursorUnlock = m_Mouse?.position.ReadValue();
|
||||
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
|
||||
m_State = State.InGameControllingCamera;
|
||||
|
||||
RepaintInspector();
|
||||
}
|
||||
|
||||
private void DisengageCameraControl()
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
|
||||
if (m_MousePositionToWarpToAfterCursorUnlock != null)
|
||||
m_Mouse?.WarpCursorPosition(m_MousePositionToWarpToAfterCursorUnlock.Value);
|
||||
|
||||
m_State = State.InGame;
|
||||
|
||||
RepaintInspector();
|
||||
}
|
||||
|
||||
public void OnTopLeftClicked()
|
||||
{
|
||||
statusBarText.text = "'Top Left' button clicked";
|
||||
}
|
||||
|
||||
public void OnBottomLeftClicked()
|
||||
{
|
||||
statusBarText.text = "'Bottom Left' button clicked";
|
||||
}
|
||||
|
||||
public void OnTopRightClicked()
|
||||
{
|
||||
statusBarText.text = "'Top Right' button clicked";
|
||||
}
|
||||
|
||||
public void OnBottomRightClicked()
|
||||
{
|
||||
statusBarText.text = "'Bottom Right' button clicked";
|
||||
}
|
||||
|
||||
public void OnMenuClicked()
|
||||
{
|
||||
m_OpenMenuActionTriggered = true;
|
||||
}
|
||||
|
||||
public void OnContinueClicked()
|
||||
{
|
||||
mainMenuUI.SetActive(false);
|
||||
inGameUI.SetActive(true);
|
||||
|
||||
// Reenable gameplay inputs.
|
||||
playerInput.ActivateInput();
|
||||
|
||||
m_State = State.InGame;
|
||||
|
||||
RepaintInspector();
|
||||
}
|
||||
|
||||
public void OnExitClicked()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.ExitPlaymode();
|
||||
#else
|
||||
Application.Quit();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnMenu(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed)
|
||||
m_OpenMenuActionTriggered = true;
|
||||
}
|
||||
|
||||
public void OnResetCamera(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed)
|
||||
m_ResetCameraActionTriggered = true;
|
||||
}
|
||||
|
||||
public void OnUIEngage(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!context.performed)
|
||||
return;
|
||||
|
||||
// From here, we could also do things such as showing UI that we only
|
||||
// have up while the UI is engaged. For example, the same approach as
|
||||
// here could be used to display a radial selection dials for items.
|
||||
|
||||
SetUIEngaged(!m_UIEngaged);
|
||||
}
|
||||
|
||||
private void SetUIEngaged(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
playerInput.actions.FindActionMap("UI").Enable();
|
||||
SetPlayerActionsEnabled(false);
|
||||
|
||||
// Select the GO that was selected last time.
|
||||
if (m_LastNavigationSelection == null)
|
||||
m_LastNavigationSelection = firstNavigationSelection;
|
||||
EventSystem.current.SetSelectedGameObject(m_LastNavigationSelection);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_LastNavigationSelection = EventSystem.current.currentSelectedGameObject; // If this happens to be null, we will automatically pick up firstNavigationSelection again.
|
||||
EventSystem.current.SetSelectedGameObject(null);
|
||||
|
||||
playerInput.actions.FindActionMap("UI").Disable();
|
||||
SetPlayerActionsEnabled(true);
|
||||
}
|
||||
|
||||
m_UIEngaged = value;
|
||||
|
||||
RepaintInspector();
|
||||
}
|
||||
|
||||
// Enable/disable every in-game action other than the UI toggle.
|
||||
private void SetPlayerActionsEnabled(bool value)
|
||||
{
|
||||
var actions = playerInput.actions.FindActionMap("Player");
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (action == m_UIEngageAction)
|
||||
continue;
|
||||
|
||||
if (value)
|
||||
action.Enable();
|
||||
else
|
||||
action.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
// There's two different approaches taken here. The first OnFire() just does the same as the action
|
||||
// callbacks above and just sets some state to leave action responses to Update().
|
||||
// The second OnFire() puts the response logic directly inside the callback.
|
||||
|
||||
#if false
|
||||
|
||||
public void OnFire(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed)
|
||||
m_FireActionTriggered = true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
public void OnFire(InputAction.CallbackContext context)
|
||||
{
|
||||
// For this action, let's try something different. Let's say we want to trigger a response
|
||||
// right away every time the "fire" action triggers. Theoretically, this would allow us
|
||||
// to correctly respond even if there is multiple activations in a single frame. In practice,
|
||||
// this will realistically only happen with low framerates (and even then it can be questionable
|
||||
// whether we want to respond this way).
|
||||
|
||||
if (!context.performed)
|
||||
return;
|
||||
|
||||
var device = playerInput.GetDevice<Pointer>();
|
||||
if (device != null && IsRaycastHittingUIObject(device.position.ReadValue()))
|
||||
return;
|
||||
|
||||
Fire();
|
||||
}
|
||||
|
||||
// Can't use IsPointerOverGameObject() from within InputAction callbacks as the UI won't update
|
||||
// until after input processing is complete. So, need to explicitly raycast here.
|
||||
// NOTE: This is not something we'd want to do from a high-frequency action. If, for example, this
|
||||
// is called from an action bound to `<Mouse>/position`, there will be an immense amount of
|
||||
// raycasts performed per frame.
|
||||
private bool IsRaycastHittingUIObject(Vector2 position)
|
||||
{
|
||||
if (m_PointerData == null)
|
||||
m_PointerData = new PointerEventData(EventSystem.current);
|
||||
m_PointerData.position = position;
|
||||
EventSystem.current.RaycastAll(m_PointerData, m_RaycastResults);
|
||||
return m_RaycastResults.Count > 0;
|
||||
}
|
||||
|
||||
private PointerEventData m_PointerData;
|
||||
private List<RaycastResult> m_RaycastResults = new List<RaycastResult>();
|
||||
|
||||
#endif
|
||||
|
||||
private bool IsPointerOverUI()
|
||||
{
|
||||
// If we're not controlling the UI with a pointer, we can early out of this.
|
||||
if (m_ControlStyle == ControlStyle.GamepadJoystick)
|
||||
return false;
|
||||
|
||||
// Otherwise, check if the primary pointer is currently over a UI object.
|
||||
return EventSystem.current.IsPointerOverGameObject();
|
||||
}
|
||||
|
||||
////REVIEW: check this together with the focus PR; ideally, the code here should not be necessary
|
||||
private bool IsPointerInsideScreen()
|
||||
{
|
||||
var pointer = playerInput.GetDevice<Pointer>();
|
||||
if (pointer == null)
|
||||
return true;
|
||||
|
||||
return Screen.safeArea.Contains(pointer.position.ReadValue());
|
||||
}
|
||||
|
||||
private void Fire()
|
||||
{
|
||||
var transform = this.transform;
|
||||
var newProjectile = Instantiate(projectile);
|
||||
newProjectile.transform.position = transform.position + transform.forward * 0.6f;
|
||||
newProjectile.transform.rotation = transform.rotation;
|
||||
const int kSize = 1;
|
||||
newProjectile.transform.localScale *= kSize;
|
||||
newProjectile.GetComponent<Rigidbody>().mass = Mathf.Pow(kSize, 3);
|
||||
newProjectile.GetComponent<Rigidbody>().AddForce(transform.forward * 20f, ForceMode.Impulse);
|
||||
newProjectile.GetComponent<MeshRenderer>().material.color =
|
||||
new Color(Random.value, Random.value, Random.value, 1.0f);
|
||||
}
|
||||
|
||||
private void RepaintInspector()
|
||||
{
|
||||
// We have a custom inspector below that prints some debugging information for internal state.
|
||||
// When we change state, this will not result in an automatic repaint of the inspector as Unity
|
||||
// doesn't know about the change.
|
||||
//
|
||||
// We thus manually force a refresh. There's more elegant ways to do this but the easiest by
|
||||
// far is to just globally force a repaint of the entire editor window.
|
||||
|
||||
#if UNITY_EDITOR
|
||||
InternalEditorUtility.RepaintAllViews();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(UIvsGameInputHandler))]
|
||||
internal class UIvsGameInputHandlerEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Debug");
|
||||
EditorGUILayout.Space();
|
||||
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
{
|
||||
var state = ((UIvsGameInputHandler)target).m_State;
|
||||
EditorGUILayout.LabelField("State", state.ToString());
|
||||
var style = ((UIvsGameInputHandler)target).m_ControlStyle;
|
||||
EditorGUILayout.LabelField("Controls", style.ToString());
|
||||
if (style == UIvsGameInputHandler.ControlStyle.GamepadJoystick)
|
||||
{
|
||||
var uiEngaged = ((UIvsGameInputHandler)target).m_UIEngaged;
|
||||
EditorGUILayout.LabelField("UI Engaged?", uiEngaged ? "Yes" : "No");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5506eddfb38f55949a804b8deafc582f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "UnityEngine.InputSystem.Samples.UIvsGameInput",
|
||||
"references": [
|
||||
"GUID:75469ad4d38634e559750d17036d5f7c",
|
||||
"GUID:2bafac87e7f4b9b418d9448d219b01ab"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83518c5567fea0a469db341a17245243
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user