UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc69a9a42102dea4dafad4147e3ed98e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,839 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "DefaultInputActions",
|
||||
"maps": [
|
||||
{
|
||||
"name": "Player",
|
||||
"id": "df70fa95-8a34-4494-b137-73ab6b9c7d37",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Move",
|
||||
"type": "Value",
|
||||
"id": "351f2ccd-1f9f-44bf-9bec-d62ac5c5f408",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
},
|
||||
{
|
||||
"name": "Look",
|
||||
"type": "Value",
|
||||
"id": "6b444451-8a00-4d00-a97e-f47457f736a8",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
},
|
||||
{
|
||||
"name": "Fire",
|
||||
"type": "Button",
|
||||
"id": "6c2ab1b8-8984-453a-af3d-a3c78ae1679a",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{
|
||||
"name": "",
|
||||
"id": "978bfe49-cc26-4a3d-ab7b-7d7a29327403",
|
||||
"path": "<Gamepad>/leftStick",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "WASD",
|
||||
"id": "00ca640b-d935-4593-8157-c05846ea39b3",
|
||||
"path": "Dpad",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "Move",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "e2062cb9-1b15-46a2-838c-2f8d72a0bdd9",
|
||||
"path": "<Keyboard>/w",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "8180e8bd-4097-4f4e-ab88-4523101a6ce9",
|
||||
"path": "<Keyboard>/upArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "320bffee-a40b-4347-ac70-c210eb8bc73a",
|
||||
"path": "<Keyboard>/s",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "1c5327b5-f71c-4f60-99c7-4e737386f1d1",
|
||||
"path": "<Keyboard>/downArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "d2581a9b-1d11-4566-b27d-b92aff5fabbc",
|
||||
"path": "<Keyboard>/a",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "2e46982e-44cc-431b-9f0b-c11910bf467a",
|
||||
"path": "<Keyboard>/leftArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "fcfe95b8-67b9-4526-84b5-5d0bc98d6400",
|
||||
"path": "<Keyboard>/d",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "77bff152-3580-4b21-b6de-dcd0c7e41164",
|
||||
"path": "<Keyboard>/rightArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "1635d3fe-58b6-4ba9-a4e2-f4b964f6b5c8",
|
||||
"path": "<XRController>/{Primary2DAxis}",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "XR",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "3ea4d645-4504-4529-b061-ab81934c3752",
|
||||
"path": "<Joystick>/stick",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Joystick",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "c1f7a91b-d0fd-4a62-997e-7fb9b69bf235",
|
||||
"path": "<Gamepad>/rightStick",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Look",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "8c8e490b-c610-4785-884f-f04217b23ca4",
|
||||
"path": "<Pointer>/delta",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse;Touch",
|
||||
"action": "Look",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "3e5f5442-8668-4b27-a940-df99bad7e831",
|
||||
"path": "<Joystick>/{Hatswitch}",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Joystick",
|
||||
"action": "Look",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "143bb1cd-cc10-4eca-a2f0-a3664166fe91",
|
||||
"path": "<Gamepad>/rightTrigger",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Fire",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "05f6913d-c316-48b2-a6bb-e225f14c7960",
|
||||
"path": "<Mouse>/leftButton",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Fire",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "886e731e-7071-4ae4-95c0-e61739dad6fd",
|
||||
"path": "<Touchscreen>/primaryTouch/tap",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Touch",
|
||||
"action": "Fire",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "ee3d0cd2-254e-47a7-a8cb-bc94d9658c54",
|
||||
"path": "<Joystick>/trigger",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Joystick",
|
||||
"action": "Fire",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "8255d333-5683-4943-a58a-ccb207ff1dce",
|
||||
"path": "<XRController>/{PrimaryAction}",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "XR",
|
||||
"action": "Fire",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UI",
|
||||
"id": "272f6d14-89ba-496f-b7ff-215263d3219f",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Navigate",
|
||||
"type": "PassThrough",
|
||||
"id": "c95b2375-e6d9-4b88-9c4c-c5e76515df4b",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "Submit",
|
||||
"type": "Button",
|
||||
"id": "7607c7b6-cd76-4816-beef-bd0341cfe950",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "Cancel",
|
||||
"type": "Button",
|
||||
"id": "15cef263-9014-4fd5-94d9-4e4a6234a6ef",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "Point",
|
||||
"type": "PassThrough",
|
||||
"id": "32b35790-4ed0-4e9a-aa41-69ac6d629449",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
},
|
||||
{
|
||||
"name": "Click",
|
||||
"type": "PassThrough",
|
||||
"id": "3c7022bf-7922-4f7c-a998-c437916075ad",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
},
|
||||
{
|
||||
"name": "ScrollWheel",
|
||||
"type": "PassThrough",
|
||||
"id": "0489e84a-4833-4c40-bfae-cea84b696689",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "MiddleClick",
|
||||
"type": "PassThrough",
|
||||
"id": "dad70c86-b58c-4b17-88ad-f5e53adf419e",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "RightClick",
|
||||
"type": "PassThrough",
|
||||
"id": "44b200b1-1557-4083-816c-b22cbdf77ddf",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "TrackedDevicePosition",
|
||||
"type": "PassThrough",
|
||||
"id": "24908448-c609-4bc3-a128-ea258674378a",
|
||||
"expectedControlType": "Vector3",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "TrackedDeviceOrientation",
|
||||
"type": "PassThrough",
|
||||
"id": "9caa3d8a-6b2f-4e8e-8bad-6ede561bd9be",
|
||||
"expectedControlType": "Quaternion",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{
|
||||
"name": "Gamepad",
|
||||
"id": "809f371f-c5e2-4e7a-83a1-d867598f40dd",
|
||||
"path": "2DVector",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "Navigate",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "14a5d6e8-4aaf-4119-a9ef-34b8c2c548bf",
|
||||
"path": "<Gamepad>/leftStick/up",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "9144cbe6-05e1-4687-a6d7-24f99d23dd81",
|
||||
"path": "<Gamepad>/rightStick/up",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "2db08d65-c5fb-421b-983f-c71163608d67",
|
||||
"path": "<Gamepad>/leftStick/down",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "58748904-2ea9-4a80-8579-b500e6a76df8",
|
||||
"path": "<Gamepad>/rightStick/down",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "8ba04515-75aa-45de-966d-393d9bbd1c14",
|
||||
"path": "<Gamepad>/leftStick/left",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "712e721c-bdfb-4b23-a86c-a0d9fcfea921",
|
||||
"path": "<Gamepad>/rightStick/left",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "fcd248ae-a788-4676-a12e-f4d81205600b",
|
||||
"path": "<Gamepad>/leftStick/right",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "1f04d9bc-c50b-41a1-bfcc-afb75475ec20",
|
||||
"path": "<Gamepad>/rightStick/right",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "fb8277d4-c5cd-4663-9dc7-ee3f0b506d90",
|
||||
"path": "<Gamepad>/dpad",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "Joystick",
|
||||
"id": "e25d9774-381c-4a61-b47c-7b6b299ad9f9",
|
||||
"path": "2DVector",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "Navigate",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "3db53b26-6601-41be-9887-63ac74e79d19",
|
||||
"path": "<Joystick>/stick/up",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Joystick",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "0cb3e13e-3d90-4178-8ae6-d9c5501d653f",
|
||||
"path": "<Joystick>/stick/down",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Joystick",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "0392d399-f6dd-4c82-8062-c1e9c0d34835",
|
||||
"path": "<Joystick>/stick/left",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Joystick",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "942a66d9-d42f-43d6-8d70-ecb4ba5363bc",
|
||||
"path": "<Joystick>/stick/right",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Joystick",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "Keyboard",
|
||||
"id": "ff527021-f211-4c02-933e-5976594c46ed",
|
||||
"path": "2DVector",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "Navigate",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "563fbfdd-0f09-408d-aa75-8642c4f08ef0",
|
||||
"path": "<Keyboard>/w",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "eb480147-c587-4a33-85ed-eb0ab9942c43",
|
||||
"path": "<Keyboard>/upArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "2bf42165-60bc-42ca-8072-8c13ab40239b",
|
||||
"path": "<Keyboard>/s",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "85d264ad-e0a0-4565-b7ff-1a37edde51ac",
|
||||
"path": "<Keyboard>/downArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "74214943-c580-44e4-98eb-ad7eebe17902",
|
||||
"path": "<Keyboard>/a",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "cea9b045-a000-445b-95b8-0c171af70a3b",
|
||||
"path": "<Keyboard>/leftArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "8607c725-d935-4808-84b1-8354e29bab63",
|
||||
"path": "<Keyboard>/d",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "4cda81dc-9edd-4e03-9d7c-a71a14345d0b",
|
||||
"path": "<Keyboard>/rightArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Navigate",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "9e92bb26-7e3b-4ec4-b06b-3c8f8e498ddc",
|
||||
"path": "*/{Submit}",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR",
|
||||
"action": "Submit",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "82627dcc-3b13-4ba9-841d-e4b746d6553e",
|
||||
"path": "*/{Cancel}",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR",
|
||||
"action": "Cancel",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "c52c8e0b-8179-41d3-b8a1-d149033bbe86",
|
||||
"path": "<Mouse>/position",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Point",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "e1394cbc-336e-44ce-9ea8-6007ed6193f7",
|
||||
"path": "<Pen>/position",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Keyboard&Mouse",
|
||||
"action": "Point",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "5693e57a-238a-46ed-b5ae-e64e6e574302",
|
||||
"path": "<Touchscreen>/touch*/position",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Touch",
|
||||
"action": "Point",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "4faf7dc9-b979-4210-aa8c-e808e1ef89f5",
|
||||
"path": "<Mouse>/leftButton",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Click",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "8d66d5ba-88d7-48e6-b1cd-198bbfef7ace",
|
||||
"path": "<Pen>/tip",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Click",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "47c2a644-3ebc-4dae-a106-589b7ca75b59",
|
||||
"path": "<Touchscreen>/touch*/press",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Touch",
|
||||
"action": "Click",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "bb9e6b34-44bf-4381-ac63-5aa15d19f677",
|
||||
"path": "<XRController>/trigger",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "XR",
|
||||
"action": "Click",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "38c99815-14ea-4617-8627-164d27641299",
|
||||
"path": "<Mouse>/scroll",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "ScrollWheel",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "24066f69-da47-44f3-a07e-0015fb02eb2e",
|
||||
"path": "<Mouse>/middleButton",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "MiddleClick",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "4c191405-5738-4d4b-a523-c6a301dbf754",
|
||||
"path": "<Mouse>/rightButton",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "RightClick",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "7236c0d9-6ca3-47cf-a6ee-a97f5b59ea77",
|
||||
"path": "<XRController>/devicePosition",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "XR",
|
||||
"action": "TrackedDevicePosition",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "23e01e3a-f935-4948-8d8b-9bcac77714fb",
|
||||
"path": "<XRController>/deviceRotation",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "XR",
|
||||
"action": "TrackedDeviceOrientation",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"controlSchemes": [
|
||||
{
|
||||
"name": "Keyboard&Mouse",
|
||||
"bindingGroup": "Keyboard&Mouse",
|
||||
"devices": [
|
||||
{
|
||||
"devicePath": "<Keyboard>",
|
||||
"isOptional": false,
|
||||
"isOR": false
|
||||
},
|
||||
{
|
||||
"devicePath": "<Mouse>",
|
||||
"isOptional": false,
|
||||
"isOR": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Gamepad",
|
||||
"bindingGroup": "Gamepad",
|
||||
"devices": [
|
||||
{
|
||||
"devicePath": "<Gamepad>",
|
||||
"isOptional": false,
|
||||
"isOR": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Touch",
|
||||
"bindingGroup": "Touch",
|
||||
"devices": [
|
||||
{
|
||||
"devicePath": "<Touchscreen>",
|
||||
"isOptional": false,
|
||||
"isOR": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Joystick",
|
||||
"bindingGroup": "Joystick",
|
||||
"devices": [
|
||||
{
|
||||
"devicePath": "<Joystick>",
|
||||
"isOptional": false,
|
||||
"isOR": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "XR",
|
||||
"bindingGroup": "XR",
|
||||
"devices": [
|
||||
{
|
||||
"devicePath": "<XRController>",
|
||||
"isOptional": false,
|
||||
"isOR": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca9f5fa95ffab41fb9a615ab714db018
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
|
||||
generateWrapperCode: 0
|
||||
wrapperCodePath:
|
||||
wrapperClassName:
|
||||
wrapperCodeNamespace: UnityEngine.InputSystem
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
|
||||
////TODO: API to get the control and device from the internal context
|
||||
|
||||
////TODO: ToString()
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps around values provided by input actions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a wrapper around <see cref="InputAction.CallbackContext"/> chiefly for use
|
||||
/// with GameObject messages (i.e. <see cref="GameObject.SendMessage(string,object)"/>). It exists
|
||||
/// so that action callback data can be represented as an object, can be reused, and shields
|
||||
/// the receiver from having to know about action callback specifics.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputAction"/>
|
||||
[DebuggerDisplay("Value = {Get()}")]
|
||||
public class InputValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Read the current value as an object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method allocates GC memory and will thus create garbage. If used during gameplay,
|
||||
/// it will lead to GC spikes.
|
||||
/// </remarks>
|
||||
/// <returns>The current value in the form of a boxed object.</returns>
|
||||
public object Get()
|
||||
{
|
||||
return m_Context.Value.ReadValueAsObject();
|
||||
}
|
||||
|
||||
////TODO: add automatic conversions
|
||||
/// <summary>
|
||||
/// Read the current value of the action.
|
||||
/// </summary>
|
||||
/// <returns>The current value from the action cast to the specified type.</returns>
|
||||
/// <typeparam name="TValue">Type of value to read. This must correspond to the
|
||||
/// <see cref="InputControl.valueType"/> of the action or, if it is a composite, by the
|
||||
/// <see cref="InputBindingComposite.valueType"/>.
|
||||
/// The type depends on what type of controls the action is bound to.
|
||||
/// Common types are <c>float</c> and <see cref="UnityEngine.Vector2"/></typeparam>
|
||||
/// <exception cref="InvalidOperationException">The given type <typeparamref name="TValue"/>
|
||||
/// does not match the value type expected by the control or binding composite.</exception>
|
||||
/// <remarks>
|
||||
/// The following example shows how to read a value from a <see cref="PlayerInput"/> message.
|
||||
/// The given <c>InputValue</c> is only valid for the duration of the callback. Storing the <c>InputValue</c> references somewhere and calling Get<T>() later does not work correctly.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// using UnityEngine;
|
||||
/// using UnityEngine.InputSystem;
|
||||
/// [RequireComponent(typeof(PlayerInput))]
|
||||
/// public class MyPlayerLogic : MonoBehaviour
|
||||
/// {
|
||||
/// private Vector2 m_Move;
|
||||
///
|
||||
/// // 'Move' input action has been triggered.
|
||||
/// public void OnMove(InputValue value)
|
||||
/// {
|
||||
/// // Read value from control. The type depends on what type of controls the action is bound to.
|
||||
/// m_Move = value.Get<Vector2>();
|
||||
/// }
|
||||
///
|
||||
/// public void Update()
|
||||
/// {
|
||||
/// // Update transform from m_Move
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="InputAction.CallbackContext.ReadValue{TValue}"/>
|
||||
public TValue Get<TValue>()
|
||||
where TValue : struct
|
||||
{
|
||||
if (!m_Context.HasValue)
|
||||
throw new InvalidOperationException($"Values can only be retrieved while in message callbacks");
|
||||
|
||||
return m_Context.Value.ReadValue<TValue>();
|
||||
}
|
||||
|
||||
////TODO: proper message if value type isn't right
|
||||
/// <summary>
|
||||
/// Check if the action button is pressed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// True if the button is activated over the button threshold. False otherwise
|
||||
/// The following example check if a button is pressed when receiving a <see cref="PlayerInput"/> message.
|
||||
/// The given <c>InputValue</c> is only valid for the duration of the callback. Storing the <c>InputValue</c> references somewhere and calling Get<T>() later does not work correctly.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// [RequireComponent(typeof(PlayerInput))]
|
||||
/// public class MyPlayerLogic : MonoBehaviour
|
||||
/// {
|
||||
/// // 'Fire' input action has been triggered.
|
||||
/// public void OnFire(InputValue value)
|
||||
/// {
|
||||
/// if (value.isPressed)
|
||||
/// FireWeapon();
|
||||
/// }
|
||||
///
|
||||
/// public void FireWeapon()
|
||||
/// {
|
||||
/// // Weapon firing code
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="ButtonControl.pressPointOrDefault"/>
|
||||
public bool isPressed => Get<float>() >= ButtonControl.s_GlobalDefaultButtonPressPoint;
|
||||
|
||||
internal InputAction.CallbackContext? m_Context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b24da9e286b594ace8ec4dfd4a374780
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62899f850307741f2a39c98a8b639597
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -100
|
||||
icon: {fileID: 2800000, guid: 40265a896a0f341bb956e90c59025a29, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,653 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Users;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_ENABLE_UI
|
||||
using UnityEngine.InputSystem.UI;
|
||||
using UnityEngine.InputSystem.UI.Editor;
|
||||
#endif
|
||||
|
||||
////TODO: detect if new input system isn't enabled and provide UI to enable it
|
||||
#pragma warning disable 0414
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom inspector for the <see cref="PlayerInput"/> component.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PlayerInput))]
|
||||
internal class PlayerInputEditor : UnityEditor.Editor
|
||||
{
|
||||
public const string kDefaultInputActionsAssetPath =
|
||||
"Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/DefaultInputActions.inputactions";
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
InputActionImporter.onImport += Refresh;
|
||||
InputUser.onChange += OnUserChange;
|
||||
|
||||
// Look up properties.
|
||||
m_ActionsProperty = serializedObject.FindProperty(nameof(PlayerInput.m_Actions));
|
||||
m_DefaultControlSchemeProperty = serializedObject.FindProperty(nameof(PlayerInput.m_DefaultControlScheme));
|
||||
m_NeverAutoSwitchControlSchemesProperty = serializedObject.FindProperty(nameof(PlayerInput.m_NeverAutoSwitchControlSchemes));
|
||||
m_DefaultActionMapProperty = serializedObject.FindProperty(nameof(PlayerInput.m_DefaultActionMap));
|
||||
m_NotificationBehaviorProperty = serializedObject.FindProperty(nameof(PlayerInput.m_NotificationBehavior));
|
||||
m_CameraProperty = serializedObject.FindProperty(nameof(PlayerInput.m_Camera));
|
||||
m_ActionEventsProperty = serializedObject.FindProperty(nameof(PlayerInput.m_ActionEvents));
|
||||
m_DeviceLostEventProperty = serializedObject.FindProperty(nameof(PlayerInput.m_DeviceLostEvent));
|
||||
m_DeviceRegainedEventProperty = serializedObject.FindProperty(nameof(PlayerInput.m_DeviceRegainedEvent));
|
||||
m_ControlsChangedEventProperty = serializedObject.FindProperty(nameof(PlayerInput.m_ControlsChangedEvent));
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_ENABLE_UI
|
||||
m_UIInputModuleProperty = serializedObject.FindProperty(nameof(PlayerInput.m_UIInputModule));
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
new InputComponentEditorAnalytic(InputSystemComponent.PlayerInput).Send();
|
||||
new PlayerInputEditorAnalytic(this).Send();
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
InputActionImporter.onImport -= Refresh;
|
||||
InputUser.onChange -= OnUserChange;
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
////FIXME: doesn't seem like we're picking up the results of the latest import
|
||||
m_ActionAssetInitialized = false;
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void OnUserChange(InputUser user, InputUserChange change, InputDevice device)
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
// Action config section.
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_ActionsProperty);
|
||||
var actionsWereChanged = false;
|
||||
|
||||
// Check for if we're using project-wide actions to raise a warning message.
|
||||
if (m_ActionsProperty.objectReferenceValue != null)
|
||||
{
|
||||
InputActionAsset actions = m_ActionsProperty.objectReferenceValue as InputActionAsset;
|
||||
if (actions == InputSystem.actions)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Project-wide actions asset is not recommended to be used with Player " +
|
||||
"Input because it is a singleton reference and all actions maps are enabled by default.\r\n" +
|
||||
"You should manually disable all action maps on Start() and " +
|
||||
"manually enable the default action map.",
|
||||
MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
var assetChanged = CheckIfActionAssetChanged();
|
||||
// initialize the editor component if the asset has changed or if it has not been initialized yet
|
||||
if (EditorGUI.EndChangeCheck() || !m_ActionAssetInitialized || assetChanged || m_ActionAssetInstanceID == 0)
|
||||
{
|
||||
InitializeEditorComponent(assetChanged);
|
||||
actionsWereChanged = true;
|
||||
}
|
||||
|
||||
++EditorGUI.indentLevel;
|
||||
if (m_ControlSchemeOptions != null && m_ControlSchemeOptions.Length > 1) // Don't show if <Any> is the only option.
|
||||
{
|
||||
// Default control scheme picker.
|
||||
Color currentBg = GUI.backgroundColor;
|
||||
// if the invalid DefaultControlSchemeName is selected set the popup draw the BG color in red
|
||||
if (m_InvalidDefaultControlSchemeName != null && m_SelectedDefaultControlScheme == 1)
|
||||
GUI.backgroundColor = Color.red;
|
||||
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
var label = EditorGUI.BeginProperty(rect, m_DefaultControlSchemeText, m_DefaultControlSchemeProperty);
|
||||
var selected = EditorGUI.Popup(rect, label, m_SelectedDefaultControlScheme, m_ControlSchemeOptions);
|
||||
EditorGUI.EndProperty();
|
||||
if (selected != m_SelectedDefaultControlScheme)
|
||||
{
|
||||
if (selected == 0)
|
||||
{
|
||||
m_DefaultControlSchemeProperty.stringValue = null;
|
||||
}
|
||||
// if there is an invalid default scheme name it will be at rank 1.
|
||||
// we use m_InvalidDefaultControlSchemeName to prevent usage of the string with "name<Not Found>"
|
||||
else if (m_InvalidDefaultControlSchemeName != null && selected == 1)
|
||||
{
|
||||
m_DefaultControlSchemeProperty.stringValue = m_InvalidDefaultControlSchemeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_DefaultControlSchemeProperty.stringValue = m_ControlSchemeOptions[selected].text;
|
||||
}
|
||||
m_SelectedDefaultControlScheme = selected;
|
||||
}
|
||||
// Restore the initial color
|
||||
GUI.backgroundColor = currentBg;
|
||||
|
||||
|
||||
rect = EditorGUILayout.GetControlRect();
|
||||
label = EditorGUI.BeginProperty(rect, m_AutoSwitchText, m_NeverAutoSwitchControlSchemesProperty);
|
||||
var neverAutoSwitchValueOld = m_NeverAutoSwitchControlSchemesProperty.boolValue;
|
||||
var neverAutoSwitchValueNew = !EditorGUI.Toggle(rect, label, !neverAutoSwitchValueOld);
|
||||
EditorGUI.EndProperty();
|
||||
if (neverAutoSwitchValueOld != neverAutoSwitchValueNew)
|
||||
{
|
||||
m_NeverAutoSwitchControlSchemesProperty.boolValue = neverAutoSwitchValueNew;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
if (m_ActionMapOptions != null && m_ActionMapOptions.Length > 0)
|
||||
{
|
||||
// Default action map picker.
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
var label = EditorGUI.BeginProperty(rect, m_DefaultActionMapText, m_DefaultActionMapProperty);
|
||||
var selected = EditorGUI.Popup(rect, label, m_SelectedDefaultActionMap,
|
||||
m_ActionMapOptions);
|
||||
EditorGUI.EndProperty();
|
||||
if (selected != m_SelectedDefaultActionMap)
|
||||
{
|
||||
if (selected == 0)
|
||||
{
|
||||
m_DefaultActionMapProperty.stringValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use ID rather than name.
|
||||
var asset = (InputActionAsset)m_ActionsProperty.objectReferenceValue;
|
||||
var actionMap = asset.FindActionMap(m_ActionMapOptions[selected].text);
|
||||
if (actionMap != null)
|
||||
m_DefaultActionMapProperty.stringValue = actionMap.id.ToString();
|
||||
}
|
||||
m_SelectedDefaultActionMap = selected;
|
||||
}
|
||||
}
|
||||
--EditorGUI.indentLevel;
|
||||
DoHelpCreateAssetUI();
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_ENABLE_UI
|
||||
// UI config section.
|
||||
if (m_UIPropertyText == null)
|
||||
m_UIPropertyText = EditorGUIUtility.TrTextContent("UI Input Module", m_UIInputModuleProperty.GetTooltip());
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_UIInputModuleProperty, m_UIPropertyText);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (m_UIInputModuleProperty.objectReferenceValue != null)
|
||||
{
|
||||
var uiModule = m_UIInputModuleProperty.objectReferenceValue as InputSystemUIInputModule;
|
||||
if (m_ActionsProperty.objectReferenceValue != null && uiModule.actionsAsset != m_ActionsProperty.objectReferenceValue)
|
||||
{
|
||||
EditorGUILayout.HelpBox("The referenced InputSystemUIInputModule is configured using different input actions than this PlayerInput. They should match if you want to synchronize PlayerInput actions to the UI input.", MessageType.Warning);
|
||||
if (GUILayout.Button(m_FixInputModuleText))
|
||||
InputSystemUIInputModuleEditor.ReassignActions(uiModule, m_ActionsProperty.objectReferenceValue as InputActionAsset);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Camera section.
|
||||
if (m_CameraPropertyText == null)
|
||||
m_CameraPropertyText = EditorGUIUtility.TrTextContent("Camera", m_CameraProperty.GetTooltip());
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_CameraProperty, m_CameraPropertyText);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
// Notifications/event section.
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_NotificationBehaviorProperty, m_NotificationBehaviorText);
|
||||
if (EditorGUI.EndChangeCheck() || actionsWereChanged || !m_NotificationBehaviorInitialized)
|
||||
OnNotificationBehaviorChange();
|
||||
switch ((PlayerNotifications)m_NotificationBehaviorProperty.intValue)
|
||||
{
|
||||
case PlayerNotifications.SendMessages:
|
||||
case PlayerNotifications.BroadcastMessages:
|
||||
Debug.Assert(m_SendMessagesHelpText != null);
|
||||
EditorGUILayout.HelpBox(m_SendMessagesHelpText);
|
||||
break;
|
||||
|
||||
case PlayerNotifications.InvokeUnityEvents:
|
||||
m_EventsGroupUnfolded = EditorGUILayout.Foldout(m_EventsGroupUnfolded, m_EventsGroupText, toggleOnLabelClick: true);
|
||||
if (m_EventsGroupUnfolded)
|
||||
{
|
||||
// Action events. Group by action map.
|
||||
if (m_ActionNames != null)
|
||||
{
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
{
|
||||
for (var n = 0; n < m_NumActionMaps; ++n)
|
||||
{
|
||||
// Skip action maps that have no names (case 1317735).
|
||||
if (m_ActionMapNames[n] == null)
|
||||
continue;
|
||||
|
||||
m_ActionMapEventsUnfolded[n] = EditorGUILayout.Foldout(m_ActionMapEventsUnfolded[n],
|
||||
m_ActionMapNames[n], toggleOnLabelClick: true);
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
{
|
||||
if (m_ActionMapEventsUnfolded[n])
|
||||
{
|
||||
for (var i = 0; i < m_ActionNames.Length; ++i)
|
||||
{
|
||||
if (m_ActionMapIndices[i] != n)
|
||||
continue;
|
||||
|
||||
EditorGUILayout.PropertyField(m_ActionEventsProperty.GetArrayElementAtIndex(i), m_ActionNames[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Misc events.
|
||||
EditorGUILayout.PropertyField(m_DeviceLostEventProperty);
|
||||
EditorGUILayout.PropertyField(m_DeviceRegainedEventProperty);
|
||||
EditorGUILayout.PropertyField(m_ControlsChangedEventProperty);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Miscellaneous buttons.
|
||||
DoUtilityButtonsUI();
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
// Debug UI.
|
||||
if (EditorApplication.isPlaying)
|
||||
DoDebugUI();
|
||||
}
|
||||
|
||||
// This checks changes that are not captured by BeginChangeCheck/EndChangeCheck.
|
||||
// One such case is when the user triggers a "Reset" on the component.
|
||||
bool CheckIfActionAssetChanged()
|
||||
{
|
||||
if (m_ActionsProperty.objectReferenceValue != null)
|
||||
{
|
||||
var assetInstanceID = m_ActionsProperty.objectReferenceValue.GetInstanceID();
|
||||
// if the m_ActionAssetInstanceID is 0 the PlayerInputEditor has not been initialized yet, but the asset did not change
|
||||
bool result = assetInstanceID != m_ActionAssetInstanceID && m_ActionAssetInstanceID != 0;
|
||||
m_ActionAssetInstanceID = (int)assetInstanceID;
|
||||
return result;
|
||||
}
|
||||
|
||||
m_ActionAssetInstanceID = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DoHelpCreateAssetUI()
|
||||
{
|
||||
if (m_ActionsProperty.objectReferenceValue != null)
|
||||
{
|
||||
// All good. We already have an asset.
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox("There are no input actions associated with this input component yet. Click the button below to create "
|
||||
+ "a new set of input actions or drag an existing input actions asset into the field above.", MessageType.Info);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
if (GUILayout.Button(m_CreateActionsText, EditorStyles.miniButton, GUILayout.MaxWidth(120)))
|
||||
{
|
||||
// Request save file location.
|
||||
var defaultFileName = Application.productName;
|
||||
var fileName = EditorUtility.SaveFilePanel("Create Input Actions Asset", "Assets", defaultFileName,
|
||||
InputActionAsset.Extension);
|
||||
|
||||
////TODO: take current Supported Devices into account when creating this
|
||||
|
||||
// Create and import asset and open editor.
|
||||
if (!string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
if (!fileName.StartsWith(Application.dataPath))
|
||||
{
|
||||
Debug.LogError($"Path must be located in Assets/ folder (got: '{fileName}')");
|
||||
EditorGUILayout.EndHorizontal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileName.EndsWith("." + InputActionAsset.Extension))
|
||||
fileName += "." + InputActionAsset.Extension;
|
||||
|
||||
// Load default actions and update all GUIDs.
|
||||
var defaultActionsText = File.ReadAllText(kDefaultInputActionsAssetPath);
|
||||
var newActions = InputActionAsset.FromJson(defaultActionsText);
|
||||
foreach (var map in newActions.actionMaps)
|
||||
{
|
||||
map.m_Id = Guid.NewGuid().ToString();
|
||||
foreach (var action in map.actions)
|
||||
action.m_Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
newActions.name = Path.GetFileNameWithoutExtension(fileName);
|
||||
var newActionsText = newActions.ToJson();
|
||||
|
||||
// Write it out and tell the asset DB to pick it up.
|
||||
File.WriteAllText(fileName, newActionsText);
|
||||
|
||||
// Import the new asset
|
||||
var relativePath = "Assets/" + fileName.Substring(Application.dataPath.Length + 1);
|
||||
AssetDatabase.ImportAsset(relativePath, ImportAssetOptions.ForceSynchronousImport);
|
||||
|
||||
// Load imported object.
|
||||
var importedObject = AssetDatabase.LoadAssetAtPath<InputActionAsset>(relativePath);
|
||||
|
||||
// Set it on the PlayerInput component.
|
||||
m_ActionsProperty.objectReferenceValue = importedObject;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
// Open the asset.
|
||||
AssetDatabase.OpenAsset(importedObject);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Separator();
|
||||
}
|
||||
|
||||
private void DoUtilityButtonsUI()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button(m_OpenSettingsText, EditorStyles.miniButton))
|
||||
InputSettingsProvider.Open();
|
||||
|
||||
if (GUILayout.Button(m_OpenDebuggerText, EditorStyles.miniButton))
|
||||
InputDebuggerWindow.CreateOrShow();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DoDebugUI()
|
||||
{
|
||||
var playerInput = (PlayerInput)target;
|
||||
|
||||
if (!playerInput.user.valid)
|
||||
return;
|
||||
|
||||
////TODO: show actions when they happen
|
||||
|
||||
var user = playerInput.user.index.ToString();
|
||||
var controlScheme = playerInput.user.controlScheme?.name;
|
||||
var devices = string.Join(", ", playerInput.user.pairedDevices);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(m_DebugText, EditorStyles.boldLabel);
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.LabelField("User", user);
|
||||
EditorGUILayout.LabelField("Control Scheme", controlScheme);
|
||||
EditorGUILayout.LabelField("Devices", devices);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
private void OnNotificationBehaviorChange()
|
||||
{
|
||||
Debug.Assert(m_ActionAssetInitialized);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
var notificationBehavior = (PlayerNotifications)m_NotificationBehaviorProperty.intValue;
|
||||
switch (notificationBehavior)
|
||||
{
|
||||
// Create text that lists all the messages sent by the component.
|
||||
case PlayerNotifications.BroadcastMessages:
|
||||
case PlayerNotifications.SendMessages:
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("Will ");
|
||||
if (notificationBehavior == PlayerNotifications.BroadcastMessages)
|
||||
builder.Append("BroadcastMessage()");
|
||||
else
|
||||
builder.Append("SendMessage()");
|
||||
builder.Append(" to GameObject: ");
|
||||
builder.Append(PlayerInput.DeviceLostMessage);
|
||||
builder.Append(", ");
|
||||
builder.Append(PlayerInput.DeviceRegainedMessage);
|
||||
builder.Append(", ");
|
||||
builder.Append(PlayerInput.ControlsChangedMessage);
|
||||
|
||||
var playerInput = (PlayerInput)target;
|
||||
var asset = playerInput.m_Actions;
|
||||
if (asset != null)
|
||||
{
|
||||
foreach (var action in asset)
|
||||
{
|
||||
builder.Append(", On");
|
||||
builder.Append(CSharpCodeHelpers.MakeTypeName(action.name));
|
||||
}
|
||||
}
|
||||
|
||||
m_SendMessagesHelpText = new GUIContent(builder.ToString());
|
||||
break;
|
||||
}
|
||||
|
||||
case PlayerNotifications.InvokeUnityEvents:
|
||||
{
|
||||
var playerInput = (PlayerInput)target;
|
||||
|
||||
bool areEventsDirty = (playerInput.m_DeviceLostEvent == null) || (playerInput.m_DeviceRegainedEvent == null) || (playerInput.m_ControlsChangedEvent == null);
|
||||
|
||||
playerInput.m_DeviceLostEvent ??= new PlayerInput.DeviceLostEvent();
|
||||
playerInput.m_DeviceRegainedEvent ??= new PlayerInput.DeviceRegainedEvent();
|
||||
playerInput.m_ControlsChangedEvent ??= new PlayerInput.ControlsChangedEvent();
|
||||
|
||||
if (areEventsDirty)
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// Force action refresh.
|
||||
m_ActionAssetInitialized = false;
|
||||
Refresh();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_NotificationBehaviorInitialized = true;
|
||||
}
|
||||
|
||||
private void InitializeEditorComponent(bool assetChanged)
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
m_ActionAssetInitialized = true;
|
||||
|
||||
var playerInput = (PlayerInput)target;
|
||||
var asset = (InputActionAsset)m_ActionsProperty.objectReferenceValue;
|
||||
|
||||
if (assetChanged)
|
||||
m_SelectedDefaultActionMap = -1;
|
||||
if (asset == null)
|
||||
{
|
||||
m_ControlSchemeOptions = null;
|
||||
m_ActionMapOptions = null;
|
||||
m_ActionNames = null;
|
||||
m_SelectedDefaultControlScheme = -1;
|
||||
m_InvalidDefaultControlSchemeName = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're sending Unity events, read out the event list.
|
||||
if ((PlayerNotifications)m_NotificationBehaviorProperty.intValue ==
|
||||
PlayerNotifications.InvokeUnityEvents)
|
||||
{
|
||||
////FIXME: this should preserve the same order that we have in the asset
|
||||
var newActionNames = new List<GUIContent>();
|
||||
var newActionEvents = new List<PlayerInput.ActionEvent>();
|
||||
var newActionMapIndices = new List<int>();
|
||||
|
||||
m_NumActionMaps = 0;
|
||||
m_ActionMapNames = null;
|
||||
|
||||
void AddEntry(InputAction action, PlayerInput.ActionEvent actionEvent)
|
||||
{
|
||||
newActionNames.Add(new GUIContent(action.name));
|
||||
newActionEvents.Add(actionEvent);
|
||||
|
||||
var actionMapIndex = asset.actionMaps.IndexOfReference(action.actionMap);
|
||||
newActionMapIndices.Add(actionMapIndex);
|
||||
|
||||
if (actionMapIndex >= m_NumActionMaps)
|
||||
m_NumActionMaps = actionMapIndex + 1;
|
||||
|
||||
ArrayHelpers.PutAtIfNotSet(ref m_ActionMapNames, actionMapIndex,
|
||||
() => new GUIContent(action.actionMap.name));
|
||||
}
|
||||
|
||||
// Bring over any action events that we already have and that are still in the asset.
|
||||
var oldActionEvents = playerInput.m_ActionEvents;
|
||||
if (oldActionEvents != null)
|
||||
{
|
||||
foreach (var entry in oldActionEvents)
|
||||
{
|
||||
var guid = entry.actionId;
|
||||
var action = asset.FindAction(guid);
|
||||
if (action != null)
|
||||
AddEntry(action, entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any new actions.
|
||||
foreach (var action in asset)
|
||||
{
|
||||
// Skip if it was already in there.
|
||||
if (oldActionEvents != null && oldActionEvents.Any(x => x.actionId == action.id.ToString()))
|
||||
continue;
|
||||
|
||||
////FIXME: adds bindings to the name
|
||||
AddEntry(action, new PlayerInput.ActionEvent(action.id, action.ToString()));
|
||||
}
|
||||
|
||||
m_ActionNames = newActionNames.ToArray();
|
||||
m_ActionMapIndices = newActionMapIndices.ToArray();
|
||||
Array.Resize(ref m_ActionMapEventsUnfolded, m_NumActionMaps);
|
||||
playerInput.m_ActionEvents = newActionEvents.ToArray();
|
||||
}
|
||||
|
||||
// Read out control schemes.
|
||||
var selectedDefaultControlScheme = playerInput.defaultControlScheme;
|
||||
m_InvalidDefaultControlSchemeName = null;
|
||||
m_SelectedDefaultControlScheme = 0;
|
||||
////TODO: sort alphabetically and ensure that the order is the same in the schemes editor
|
||||
var controlSchemesNames = asset.controlSchemes.Select(cs => cs.name).ToList();
|
||||
|
||||
// try to find the selected Default Control Scheme
|
||||
if (!string.IsNullOrEmpty(selectedDefaultControlScheme))
|
||||
{
|
||||
// +1 since <Any> will be the first in the list
|
||||
m_SelectedDefaultControlScheme = 1 + controlSchemesNames.FindIndex(name => string.Compare(name, selectedDefaultControlScheme,
|
||||
StringComparison.InvariantCultureIgnoreCase) == 0);
|
||||
// if not found, will insert the invalid name next to <Any>
|
||||
if (m_SelectedDefaultControlScheme == 0)
|
||||
{
|
||||
m_InvalidDefaultControlSchemeName = selectedDefaultControlScheme;
|
||||
m_SelectedDefaultControlScheme = 1;
|
||||
controlSchemesNames.Insert(0, $"{selectedDefaultControlScheme}{L10n.Tr("<Not Found>")}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
playerInput.defaultControlScheme = null;
|
||||
}
|
||||
|
||||
m_ControlSchemeOptions = new GUIContent[controlSchemesNames.Count + 1];
|
||||
m_ControlSchemeOptions[0] = new GUIContent(EditorGUIUtility.TrTextContent("<Any>"));
|
||||
for (var i = 0; i < controlSchemesNames.Count; ++i)
|
||||
{
|
||||
m_ControlSchemeOptions[i + 1] = new GUIContent(controlSchemesNames[i]);
|
||||
}
|
||||
|
||||
// Read out action maps.
|
||||
var selectedDefaultActionMap = !string.IsNullOrEmpty(playerInput.defaultActionMap)
|
||||
? asset.FindActionMap(playerInput.defaultActionMap)
|
||||
: null;
|
||||
m_SelectedDefaultActionMap = (asset.actionMaps.Count > 0 && m_SelectedDefaultActionMap == -1) ? 1 : 0;
|
||||
var actionMaps = asset.actionMaps;
|
||||
m_ActionMapOptions = new GUIContent[actionMaps.Count + 1];
|
||||
m_ActionMapOptions[0] = new GUIContent(EditorGUIUtility.TrTextContent("<None>"));
|
||||
////TODO: sort alphabetically
|
||||
for (var i = 0; i < actionMaps.Count; ++i)
|
||||
{
|
||||
var actionMap = actionMaps[i];
|
||||
m_ActionMapOptions[i + 1] = new GUIContent(actionMap.name);
|
||||
|
||||
if (selectedDefaultActionMap != null && actionMap == selectedDefaultActionMap)
|
||||
m_SelectedDefaultActionMap = i + 1;
|
||||
}
|
||||
if (m_SelectedDefaultActionMap <= 0)
|
||||
playerInput.defaultActionMap = null;
|
||||
else
|
||||
playerInput.defaultActionMap = m_ActionMapOptions[m_SelectedDefaultActionMap].text;
|
||||
|
||||
serializedObject.Update();
|
||||
}
|
||||
|
||||
[SerializeField] private bool m_EventsGroupUnfolded;
|
||||
[SerializeField] private bool[] m_ActionMapEventsUnfolded;
|
||||
|
||||
[NonSerialized] private readonly GUIContent m_CreateActionsText = EditorGUIUtility.TrTextContent("Create Actions...");
|
||||
[NonSerialized] private readonly GUIContent m_FixInputModuleText = EditorGUIUtility.TrTextContent("Fix UI Input Module");
|
||||
[NonSerialized] private readonly GUIContent m_OpenSettingsText = EditorGUIUtility.TrTextContent("Open Input Settings");
|
||||
[NonSerialized] private readonly GUIContent m_OpenDebuggerText = EditorGUIUtility.TrTextContent("Open Input Debugger");
|
||||
[NonSerialized] private readonly GUIContent m_EventsGroupText =
|
||||
EditorGUIUtility.TrTextContent("Events", "UnityEvents triggered by the PlayerInput component");
|
||||
[NonSerialized] private readonly GUIContent m_NotificationBehaviorText =
|
||||
EditorGUIUtility.TrTextContent("Behavior",
|
||||
"Determine how notifications should be sent when an input-related event associated with the player happens.");
|
||||
[NonSerialized] private readonly GUIContent m_DefaultControlSchemeText =
|
||||
EditorGUIUtility.TrTextContent("Default Scheme", "Which control scheme to try by default. If not set, PlayerInput "
|
||||
+ "will simply go through all control schemes in the action asset and try one after the other. If set, PlayerInput will try "
|
||||
+ "the given scheme first but if using that fails (e.g. when not required devices are missing) will fall back to trying the other "
|
||||
+ "control schemes in order.");
|
||||
[NonSerialized] private readonly GUIContent m_DefaultActionMapText =
|
||||
EditorGUIUtility.TrTextContent("Default Map", "Action map to enable by default. If not set, no actions will be enabled by default.");
|
||||
[NonSerialized] private readonly GUIContent m_AutoSwitchText =
|
||||
EditorGUIUtility.TrTextContent("Auto-Switch",
|
||||
"By default, when there is only a single PlayerInput, the player "
|
||||
+ "is allowed to freely switch between control schemes simply by starting to use a different device. By toggling this property off, this "
|
||||
+ "behavior is disabled and even with a single player, the player will stay locked onto the explicitly selected control scheme. Note "
|
||||
+ "that you can still change control schemes explicitly through the PlayerInput API.\n\nWhen there are multiple PlayerInputs in the game, auto-switching is disabled automatically regardless of the value of this property.");
|
||||
[NonSerialized] private readonly GUIContent m_DebugText = EditorGUIUtility.TrTextContent("Debug");
|
||||
[NonSerialized] private GUIContent m_UIPropertyText;
|
||||
[NonSerialized] private GUIContent m_CameraPropertyText;
|
||||
[NonSerialized] private GUIContent m_SendMessagesHelpText;
|
||||
[NonSerialized] private GUIContent[] m_ActionNames;
|
||||
[NonSerialized] private GUIContent[] m_ActionMapNames;
|
||||
[NonSerialized] private int[] m_ActionMapIndices;
|
||||
[NonSerialized] private int m_NumActionMaps;
|
||||
[NonSerialized] private int m_SelectedDefaultControlScheme;
|
||||
[NonSerialized] private string m_InvalidDefaultControlSchemeName;
|
||||
[NonSerialized] private GUIContent[] m_ControlSchemeOptions;
|
||||
[NonSerialized] private int m_SelectedDefaultActionMap;
|
||||
[NonSerialized] private GUIContent[] m_ActionMapOptions;
|
||||
|
||||
[NonSerialized] private SerializedProperty m_ActionsProperty;
|
||||
[NonSerialized] private SerializedProperty m_DefaultControlSchemeProperty;
|
||||
[NonSerialized] private SerializedProperty m_DefaultActionMapProperty;
|
||||
[NonSerialized] private SerializedProperty m_NeverAutoSwitchControlSchemesProperty;
|
||||
[NonSerialized] private SerializedProperty m_NotificationBehaviorProperty;
|
||||
#if UNITY_INPUT_SYSTEM_ENABLE_UI
|
||||
[NonSerialized] private SerializedProperty m_UIInputModuleProperty;
|
||||
#endif
|
||||
[NonSerialized] private SerializedProperty m_ActionEventsProperty;
|
||||
[NonSerialized] private SerializedProperty m_CameraProperty;
|
||||
[NonSerialized] private SerializedProperty m_DeviceLostEventProperty;
|
||||
[NonSerialized] private SerializedProperty m_DeviceRegainedEventProperty;
|
||||
[NonSerialized] private SerializedProperty m_ControlsChangedEventProperty;
|
||||
|
||||
[NonSerialized] private bool m_NotificationBehaviorInitialized;
|
||||
[NonSerialized] private bool m_ActionAssetInitialized;
|
||||
[NonSerialized] private int m_ActionAssetInstanceID;
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78dcfd26eada049b0840e276bd029712
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,813 @@
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Users;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
////REVIEW: should we automatically pool/retain up to maxPlayerCount player instances?
|
||||
|
||||
////REVIEW: the join/leave messages should probably give a *GameObject* rather than the PlayerInput component (which can be gotten to via a simple GetComponent(InChildren) call)
|
||||
|
||||
////TODO: add support for reacting to players missing devices
|
||||
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages joining and leaving of players.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a singleton component. Only one instance should be active in a game at any one time. To retrieve the
|
||||
/// current instance, use the <see cref="instance"/> property.
|
||||
///
|
||||
/// PlayerInputManager provides the implementation of specific player joining mechanisms (<see cref="joinBehavior"/>).
|
||||
/// It also automatically assigns <see cref="splitScreen">split-screen areas</see>. The input system does not require
|
||||
/// the PlayerInputManager to have multiple <see cref="PlayerInput"/> components. However, you can always implement
|
||||
/// your own custom logic instead and simply instantiate multiple [GameObjects](xref:UnityEngine.GameObject) with
|
||||
/// <see cref="PlayerInput"/> yourself.
|
||||
///
|
||||
/// When you use PlayerInputManager, the join behavior you define controls pairing devices to players. This means
|
||||
/// that <see cref="PlayerInput"/> automatically pairs the device from which the player joined. If control schemes
|
||||
/// are present in the PlayerInput's set of <see cref="PlayerInput.actions"/>, the input system selects the first compatible
|
||||
/// device for pairing. If additional devices are required, the input system selects them from the pool of currently
|
||||
/// unpaired devices.
|
||||
/// </remarks>
|
||||
[AddComponentMenu("Input/Player Input Manager")]
|
||||
[HelpURL(InputSystem.kDocUrl + "/manual/PlayerInputManager.html")]
|
||||
public class PlayerInputManager : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the message that is sent when a player joins the game.
|
||||
/// </summary>
|
||||
public const string PlayerJoinedMessage = "OnPlayerJoined";
|
||||
|
||||
public const string PlayerLeftMessage = "OnPlayerLeft";
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, each player will automatically be assigned a portion of the available screen area.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For this to work, each <see cref="PlayerInput"/> component must have an associated <see cref="Camera"/>
|
||||
/// object through <see cref="PlayerInput.camera"/>.
|
||||
///
|
||||
/// Note that as player join, the screen may be increasingly subdivided and players may see their
|
||||
/// previous screen area getting resized.
|
||||
/// </remarks>
|
||||
public bool splitScreen
|
||||
{
|
||||
get => m_SplitScreen;
|
||||
set
|
||||
{
|
||||
if (m_SplitScreen == value)
|
||||
return;
|
||||
|
||||
m_SplitScreen = value;
|
||||
|
||||
if (!m_SplitScreen)
|
||||
{
|
||||
// Reset rects on all player cameras.
|
||||
foreach (var player in PlayerInput.all)
|
||||
{
|
||||
var camera = player.camera;
|
||||
if (camera != null)
|
||||
camera.rect = new Rect(0, 0, 1, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateSplitScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////REVIEW: we probably need support for filling unused screen areas automatically
|
||||
/// <summary>
|
||||
/// If <see cref="splitScreen"/> is enabled, this property determines whether subdividing the screen is allowed to
|
||||
/// produce screen areas that have an aspect ratio different from the screen resolution.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, when <see cref="splitScreen"/> is enabled, the manager will add or remove screen subdivisions in
|
||||
/// steps of two. This means that when, for example, the second player is added, the screen will be subdivided into
|
||||
/// a left and a right screen area; the left one allocated to the first player and the right one allocated to the
|
||||
/// second player.
|
||||
///
|
||||
/// This behavior makes optimal use of screen real estate but will result in screen areas that have aspect ratios
|
||||
/// different from the screen resolution. If this is not acceptable, this property can be set to true to enforce
|
||||
/// split-screen to only create screen areas that have the same aspect ratio of the screen.
|
||||
///
|
||||
/// This results in the screen being subdivided more aggressively. When, for example, a second player is added,
|
||||
/// the screen will immediately be divided into a four-way split-screen setup with the lower two screen areas
|
||||
/// not being used.
|
||||
///
|
||||
/// This property is irrelevant if <see cref="fixedNumberOfSplitScreens"/> is used.
|
||||
/// </remarks>
|
||||
public bool maintainAspectRatioInSplitScreen => m_MaintainAspectRatioInSplitScreen;
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="splitScreen"/> is enabled, this property determines how many screen divisions there will be.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used if <see cref="splitScreen"/> is true.
|
||||
///
|
||||
/// By default this is set to -1 which means the screen will automatically be divided to best fit the
|
||||
/// current number of players i.e. the highest player index in <see cref="PlayerInput"/>
|
||||
/// </remarks>
|
||||
public int fixedNumberOfSplitScreens => m_FixedNumberOfSplitScreens;
|
||||
|
||||
/// <summary>
|
||||
/// The normalized screen rectangle available for allocating player split-screens into.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used if <see cref="splitScreen"/> is true.
|
||||
///
|
||||
/// By default it is set to <c>(0,0,1,1)</c>, i.e. the entire screen area will be used for player screens.
|
||||
/// If, for example, part of the screen should display a UI/information shared by all players, this
|
||||
/// property can be used to cut off the area and not have it used by PlayerInputManager.
|
||||
/// </remarks>
|
||||
public Rect splitScreenArea => m_SplitScreenRect;
|
||||
|
||||
/// <summary>
|
||||
/// The current number of active players.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This count corresponds to all <see cref="PlayerInput"/> instances that are currently enabled.
|
||||
/// </remarks>
|
||||
public int playerCount => PlayerInput.s_AllActivePlayersCount;
|
||||
|
||||
////FIXME: this needs to be settable
|
||||
/// <summary>
|
||||
/// Maximum number of players allowed concurrently in the game.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this limit is reached, joining is turned off automatically.
|
||||
///
|
||||
/// By default this is set to -1. Any negative value deactivates the player limit and allows
|
||||
/// arbitrary many players to join.
|
||||
/// </remarks>
|
||||
public int maxPlayerCount => m_MaxPlayerCount;
|
||||
|
||||
/// <summary>
|
||||
/// Whether new players can currently join.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// While this is true, new players can join via the mechanism determined by <see cref="joinBehavior"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="EnableJoining"/>
|
||||
/// <seealso cref="DisableJoining"/>
|
||||
public bool joiningEnabled => m_AllowJoining;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the mechanism by which players can join when joining is enabled (<see cref="joiningEnabled"/>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// </remarks>
|
||||
public PlayerJoinBehavior joinBehavior
|
||||
{
|
||||
get => m_JoinBehavior;
|
||||
set
|
||||
{
|
||||
if (m_JoinBehavior == value)
|
||||
return;
|
||||
|
||||
var joiningEnabled = m_AllowJoining;
|
||||
if (joiningEnabled)
|
||||
DisableJoining();
|
||||
m_JoinBehavior = value;
|
||||
if (joiningEnabled)
|
||||
EnableJoining();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The input action that a player must trigger to join the game.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the join action is a reference to an existing input action, it will be cloned when the PlayerInputManager
|
||||
/// is enabled. This avoids the situation where the join action can become disabled after the first user joins which
|
||||
/// can happen when the join action is the same as a player in-game action. When a player joins, input bindings from
|
||||
/// devices other than the device they joined with are disabled. If the join action had a binding for keyboard and one
|
||||
/// for gamepad for example, and the first player joined using the keyboard, the expectation is that the next player
|
||||
/// could still join by pressing the gamepad join button. Without the cloning behavior, the gamepad input would have
|
||||
/// been disabled.
|
||||
///
|
||||
/// For more details about joining behavior, see <see cref="PlayerInput"/>.
|
||||
/// </remarks>
|
||||
public InputActionProperty joinAction
|
||||
{
|
||||
get => m_JoinAction;
|
||||
set
|
||||
{
|
||||
if (m_JoinAction == value)
|
||||
return;
|
||||
|
||||
////REVIEW: should we suppress notifications for temporary disables?
|
||||
|
||||
var joinEnabled = m_AllowJoining && m_JoinBehavior == PlayerJoinBehavior.JoinPlayersWhenJoinActionIsTriggered;
|
||||
if (joinEnabled)
|
||||
DisableJoining();
|
||||
|
||||
m_JoinAction = value;
|
||||
|
||||
if (joinEnabled)
|
||||
EnableJoining();
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerNotifications notificationBehavior
|
||||
{
|
||||
get => m_NotificationBehavior;
|
||||
set => m_NotificationBehavior = value;
|
||||
}
|
||||
|
||||
public PlayerJoinedEvent playerJoinedEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_PlayerJoinedEvent == null)
|
||||
m_PlayerJoinedEvent = new PlayerJoinedEvent();
|
||||
return m_PlayerJoinedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerLeftEvent playerLeftEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_PlayerLeftEvent == null)
|
||||
m_PlayerLeftEvent = new PlayerLeftEvent();
|
||||
return m_PlayerLeftEvent;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<PlayerInput> onPlayerJoined
|
||||
{
|
||||
add
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
m_PlayerJoinedCallbacks.AddCallback(value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
m_PlayerJoinedCallbacks.RemoveCallback(value);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<PlayerInput> onPlayerLeft
|
||||
{
|
||||
add
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
m_PlayerLeftCallbacks.AddCallback(value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
m_PlayerLeftCallbacks.RemoveCallback(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the prefab that the manager will instantiate when players join.
|
||||
/// </summary>
|
||||
/// <value>Prefab to instantiate for new players.</value>
|
||||
public GameObject playerPrefab
|
||||
{
|
||||
get => m_PlayerPrefab;
|
||||
set => m_PlayerPrefab = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singleton instance of the manager.
|
||||
/// </summary>
|
||||
/// <value>Singleton instance or null.</value>
|
||||
public static PlayerInputManager instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allow players to join the game based on <see cref="joinBehavior"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="DisableJoining"/>
|
||||
/// <seealso cref="joiningEnabled"/>
|
||||
public void EnableJoining()
|
||||
{
|
||||
switch (m_JoinBehavior)
|
||||
{
|
||||
case PlayerJoinBehavior.JoinPlayersWhenButtonIsPressed:
|
||||
ValidateInputActionAsset();
|
||||
|
||||
if (!m_UnpairedDeviceUsedDelegateHooked)
|
||||
{
|
||||
if (m_UnpairedDeviceUsedDelegate == null)
|
||||
m_UnpairedDeviceUsedDelegate = OnUnpairedDeviceUsed;
|
||||
InputUser.onUnpairedDeviceUsed += m_UnpairedDeviceUsedDelegate;
|
||||
m_UnpairedDeviceUsedDelegateHooked = true;
|
||||
++InputUser.listenForUnpairedDeviceActivity;
|
||||
}
|
||||
break;
|
||||
|
||||
case PlayerJoinBehavior.JoinPlayersWhenJoinActionIsTriggered:
|
||||
// Hook into join action if we have one.
|
||||
if (m_JoinAction.action != null)
|
||||
{
|
||||
if (!m_JoinActionDelegateHooked)
|
||||
{
|
||||
if (m_JoinActionDelegate == null)
|
||||
m_JoinActionDelegate = JoinPlayerFromActionIfNotAlreadyJoined;
|
||||
m_JoinAction.action.performed += m_JoinActionDelegate;
|
||||
m_JoinActionDelegateHooked = true;
|
||||
}
|
||||
m_JoinAction.action.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(
|
||||
$"No join action configured on PlayerInputManager but join behavior is set to {nameof(PlayerJoinBehavior.JoinPlayersWhenJoinActionIsTriggered)}",
|
||||
this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
m_AllowJoining = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inhibit players from joining the game.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this method might disable the action, depending on how the player
|
||||
/// joined initially. Specifically, if the initial joining was triggered using
|
||||
/// the <see cref="PlayerJoinBehavior.JoinPlayersWhenJoinActionIsTriggered"/> behavior,
|
||||
/// this method also disables the join action.
|
||||
/// </remarks>
|
||||
/// <seealso cref="EnableJoining"/>
|
||||
/// <seealso cref="joiningEnabled"/>
|
||||
public void DisableJoining()
|
||||
{
|
||||
switch (m_JoinBehavior)
|
||||
{
|
||||
case PlayerJoinBehavior.JoinPlayersWhenButtonIsPressed:
|
||||
if (m_UnpairedDeviceUsedDelegateHooked)
|
||||
{
|
||||
InputUser.onUnpairedDeviceUsed -= m_UnpairedDeviceUsedDelegate;
|
||||
m_UnpairedDeviceUsedDelegateHooked = false;
|
||||
--InputUser.listenForUnpairedDeviceActivity;
|
||||
}
|
||||
break;
|
||||
|
||||
case PlayerJoinBehavior.JoinPlayersWhenJoinActionIsTriggered:
|
||||
if (m_JoinActionDelegateHooked)
|
||||
{
|
||||
var joinAction = m_JoinAction.action;
|
||||
if (joinAction != null)
|
||||
m_JoinAction.action.performed -= m_JoinActionDelegate;
|
||||
m_JoinActionDelegateHooked = false;
|
||||
}
|
||||
m_JoinAction.action?.Disable();
|
||||
break;
|
||||
}
|
||||
|
||||
m_AllowJoining = false;
|
||||
}
|
||||
|
||||
////TODO
|
||||
/// <summary>
|
||||
/// Join a new player based on input on a UI element.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be called directly from a UI callback such as <see cref="Button.onClick"/>. The device
|
||||
/// that the player joins with is taken from the device that was used to interact with the UI element.
|
||||
/// </remarks>
|
||||
internal void JoinPlayerFromUI()
|
||||
{
|
||||
if (!CheckIfPlayerCanJoin())
|
||||
return;
|
||||
|
||||
//find used device; InputSystemUIInputModule should probably make that available
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Join a new player based on input received through an <see cref="InputAction"/>.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <remarks>
|
||||
/// </remarks>
|
||||
public void JoinPlayerFromAction(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!CheckIfPlayerCanJoin())
|
||||
return;
|
||||
|
||||
var device = context.control.device;
|
||||
JoinPlayer(pairWithDevice: device);
|
||||
}
|
||||
|
||||
public void JoinPlayerFromActionIfNotAlreadyJoined(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!CheckIfPlayerCanJoin())
|
||||
return;
|
||||
|
||||
var device = context.control.device;
|
||||
if (PlayerInput.FindFirstPairedToDevice(device) != null)
|
||||
return;
|
||||
|
||||
JoinPlayer(pairWithDevice: device);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn a new player from <see cref="playerPrefab"/>.
|
||||
/// </summary>
|
||||
/// <param name="playerIndex">Optional explicit <see cref="PlayerInput.playerIndex"/> to assign to the player. Must be unique within
|
||||
/// <see cref="PlayerInput.all"/>. If not supplied, a player index will be assigned automatically (smallest unused index will be used).</param>
|
||||
/// <param name="splitScreenIndex">Optional <see cref="PlayerInput.splitScreenIndex"/>. If supplied, this assigns a split-screen area to the player. For example,
|
||||
/// a split-screen index of </param>
|
||||
/// <param name="controlScheme">Control scheme to activate on the player (optional). If not supplied, a control scheme will
|
||||
/// be selected based on <paramref name="pairWithDevice"/>. If no device is given either, the first control scheme that matches
|
||||
/// the currently available unpaired devices (see <see cref="InputUser.GetUnpairedInputDevices()"/>) is used.</param>
|
||||
/// <param name="pairWithDevice">Device to pair to the player. Also determines which control scheme to use if <paramref name="controlScheme"/>
|
||||
/// is not given.</param>
|
||||
/// <returns>The newly instantiated player or <c>null</c> if joining failed.</returns>
|
||||
/// <remarks>
|
||||
/// Joining must be enabled (see <see cref="joiningEnabled"/>) or the method will fail.
|
||||
///
|
||||
/// To pair multiple devices, use <see cref="JoinPlayer(int,int,string,InputDevice[])"/>.
|
||||
/// </remarks>
|
||||
public PlayerInput JoinPlayer(int playerIndex = -1, int splitScreenIndex = -1, string controlScheme = null, InputDevice pairWithDevice = null)
|
||||
{
|
||||
if (!CheckIfPlayerCanJoin(playerIndex))
|
||||
return null;
|
||||
|
||||
PlayerInput.s_DestroyIfDeviceSetupUnsuccessful = true;
|
||||
return PlayerInput.Instantiate(m_PlayerPrefab, playerIndex: playerIndex, splitScreenIndex: splitScreenIndex,
|
||||
controlScheme: controlScheme, pairWithDevice: pairWithDevice);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn a new player from <see cref="playerPrefab"/>.
|
||||
/// </summary>
|
||||
/// <param name="playerIndex">Optional explicit <see cref="PlayerInput.playerIndex"/> to assign to the player. Must be unique within
|
||||
/// <see cref="PlayerInput.all"/>. If not supplied, a player index will be assigned automatically (smallest unused index will be used).</param>
|
||||
/// <param name="splitScreenIndex">Optional <see cref="PlayerInput.splitScreenIndex"/>. If supplied, this assigns a split-screen area to the player. For example,
|
||||
/// a split-screen index of </param>
|
||||
/// <param name="controlScheme">Control scheme to activate on the player (optional). If not supplied, a control scheme will
|
||||
/// be selected based on <paramref name="pairWithDevices"/>. If no device is given either, the first control scheme that matches
|
||||
/// the currently available unpaired devices (see <see cref="InputUser.GetUnpairedInputDevices()"/>) is used.</param>
|
||||
/// <param name="pairWithDevices">Devices to pair to the player. Also determines which control scheme to use if <paramref name="controlScheme"/>
|
||||
/// is not given.</param>
|
||||
/// <returns>The newly instantiated player or <c>null</c> if joining failed.</returns>
|
||||
/// <remarks>
|
||||
/// Joining must be enabled (see <see cref="joiningEnabled"/>) or the method will fail.
|
||||
/// </remarks>
|
||||
public PlayerInput JoinPlayer(int playerIndex = -1, int splitScreenIndex = -1, string controlScheme = null, params InputDevice[] pairWithDevices)
|
||||
{
|
||||
if (!CheckIfPlayerCanJoin(playerIndex))
|
||||
return null;
|
||||
|
||||
PlayerInput.s_DestroyIfDeviceSetupUnsuccessful = true;
|
||||
return PlayerInput.Instantiate(m_PlayerPrefab, playerIndex: playerIndex, splitScreenIndex: splitScreenIndex,
|
||||
controlScheme: controlScheme, pairWithDevices: pairWithDevices);
|
||||
}
|
||||
|
||||
[SerializeField] internal PlayerNotifications m_NotificationBehavior;
|
||||
[Tooltip("Set a limit for the maximum number of players who are able to join.")]
|
||||
[SerializeField] internal int m_MaxPlayerCount = -1;
|
||||
[SerializeField] internal bool m_AllowJoining = true;
|
||||
[SerializeField] internal PlayerJoinBehavior m_JoinBehavior;
|
||||
[SerializeField] internal PlayerJoinedEvent m_PlayerJoinedEvent;
|
||||
[SerializeField] internal PlayerLeftEvent m_PlayerLeftEvent;
|
||||
[SerializeField] internal InputActionProperty m_JoinAction;
|
||||
[SerializeField] internal GameObject m_PlayerPrefab;
|
||||
[SerializeField] internal bool m_SplitScreen;
|
||||
[SerializeField] internal bool m_MaintainAspectRatioInSplitScreen;
|
||||
[Tooltip("Explicitly set a fixed number of screens or otherwise allow the screen to be divided automatically to best fit the number of players.")]
|
||||
[SerializeField] internal int m_FixedNumberOfSplitScreens = -1;
|
||||
[SerializeField] internal Rect m_SplitScreenRect = new Rect(0, 0, 1, 1);
|
||||
|
||||
[NonSerialized] private bool m_JoinActionDelegateHooked;
|
||||
[NonSerialized] private bool m_UnpairedDeviceUsedDelegateHooked;
|
||||
[NonSerialized] private Action<InputAction.CallbackContext> m_JoinActionDelegate;
|
||||
[NonSerialized] private Action<InputControl, InputEventPtr> m_UnpairedDeviceUsedDelegate;
|
||||
[NonSerialized] private CallbackArray<Action<PlayerInput>> m_PlayerJoinedCallbacks;
|
||||
[NonSerialized] private CallbackArray<Action<PlayerInput>> m_PlayerLeftCallbacks;
|
||||
|
||||
internal static string[] messages => new[]
|
||||
{
|
||||
PlayerJoinedMessage,
|
||||
PlayerLeftMessage,
|
||||
};
|
||||
|
||||
private bool CheckIfPlayerCanJoin(int playerIndex = -1)
|
||||
{
|
||||
if (m_PlayerPrefab == null)
|
||||
{
|
||||
Debug.LogError("playerPrefab must be set in order to be able to join new players", this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_MaxPlayerCount >= 0 && playerCount >= m_MaxPlayerCount)
|
||||
{
|
||||
Debug.LogWarning("Maximum number of supported players reached: " + maxPlayerCount, this);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have a player index, make sure it's unique.
|
||||
if (playerIndex != -1)
|
||||
{
|
||||
for (var i = 0; i < PlayerInput.s_AllActivePlayersCount; ++i)
|
||||
if (PlayerInput.s_AllActivePlayers[i].playerIndex == playerIndex)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Player index #{playerIndex} is already taken by player {PlayerInput.s_AllActivePlayers[i]}",
|
||||
PlayerInput.s_AllActivePlayers[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnUnpairedDeviceUsed(InputControl control, InputEventPtr eventPtr)
|
||||
{
|
||||
if (!m_AllowJoining)
|
||||
return;
|
||||
|
||||
if (m_JoinBehavior == PlayerJoinBehavior.JoinPlayersWhenButtonIsPressed)
|
||||
{
|
||||
// Make sure it's a button that was actuated.
|
||||
if (!(control is ButtonControl))
|
||||
return;
|
||||
|
||||
// Make sure it's a device that is usable by the player's actions. We don't want
|
||||
// to join a player who's then stranded and has no way to actually interact with the game.
|
||||
if (!IsDeviceUsableWithPlayerActions(control.device))
|
||||
return;
|
||||
|
||||
////REVIEW: should we log a warning or error when the actions for the player do not have control schemes?
|
||||
|
||||
JoinPlayer(pairWithDevice: control.device);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Multiple PlayerInputManagers in the game. There should only be one PlayerInputManager", this);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the join action is a reference, clone it so we don't run into problems with the action being disabled by
|
||||
// PlayerInput when devices are assigned to individual players
|
||||
if (joinAction.reference != null && joinAction.action?.actionMap?.asset != null)
|
||||
{
|
||||
var inputActionAsset = Instantiate(joinAction.action.actionMap.asset);
|
||||
var inputActionReference = InputActionReference.Create(inputActionAsset.FindAction(joinAction.action.name));
|
||||
joinAction = new InputActionProperty(inputActionReference);
|
||||
}
|
||||
|
||||
// Join all players already in the game.
|
||||
for (var i = 0; i < PlayerInput.s_AllActivePlayersCount; ++i)
|
||||
NotifyPlayerJoined(PlayerInput.s_AllActivePlayers[i]);
|
||||
|
||||
if (m_AllowJoining)
|
||||
EnableJoining();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (instance == this)
|
||||
instance = null;
|
||||
|
||||
if (m_AllowJoining)
|
||||
DisableJoining();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If split-screen is enabled, then for each player in the game, adjust the player's <see cref="Camera.rect"/>
|
||||
/// to fit the player's split screen area according to the number of players currently in the game and the
|
||||
/// current split-screen configuration.
|
||||
/// </summary>
|
||||
private void UpdateSplitScreen()
|
||||
{
|
||||
// Nothing to do if split-screen is not enabled.
|
||||
if (!m_SplitScreen)
|
||||
return;
|
||||
|
||||
// Determine number of split-screens to create based on highest player index we have.
|
||||
var minSplitScreenCount = 0;
|
||||
foreach (var player in PlayerInput.all)
|
||||
{
|
||||
if (player.playerIndex >= minSplitScreenCount)
|
||||
minSplitScreenCount = player.playerIndex + 1;
|
||||
}
|
||||
|
||||
// Adjust to fixed number if we have it.
|
||||
if (m_FixedNumberOfSplitScreens > 0)
|
||||
{
|
||||
if (m_FixedNumberOfSplitScreens < minSplitScreenCount)
|
||||
Debug.LogWarning(
|
||||
$"Highest playerIndex of {minSplitScreenCount} exceeds fixed number of split-screens of {m_FixedNumberOfSplitScreens}",
|
||||
this);
|
||||
|
||||
minSplitScreenCount = m_FixedNumberOfSplitScreens;
|
||||
}
|
||||
|
||||
// Determine divisions along X and Y. Usually, we have a square grid of split-screens so all we need to
|
||||
// do is make it large enough to fit all players.
|
||||
var numDivisionsX = Mathf.CeilToInt(Mathf.Sqrt(minSplitScreenCount));
|
||||
var numDivisionsY = numDivisionsX;
|
||||
if (!m_MaintainAspectRatioInSplitScreen && numDivisionsX * (numDivisionsX - 1) >= minSplitScreenCount)
|
||||
{
|
||||
// We're allowed to produce split-screens with aspect ratios different from the screen meaning
|
||||
// that we always add one more column before finally adding an entirely new row.
|
||||
numDivisionsY -= 1;
|
||||
}
|
||||
|
||||
// Assign split-screen area to each player.
|
||||
foreach (var player in PlayerInput.all)
|
||||
{
|
||||
// Make sure the player's splitScreenIndex isn't out of range.
|
||||
var splitScreenIndex = player.splitScreenIndex;
|
||||
if (splitScreenIndex >= numDivisionsX * numDivisionsY)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Split-screen index of {splitScreenIndex} on player is out of range (have {numDivisionsX * numDivisionsY} screens); resetting to playerIndex",
|
||||
player);
|
||||
player.m_SplitScreenIndex = player.playerIndex;
|
||||
}
|
||||
|
||||
// Make sure we have a camera.
|
||||
var camera = player.camera;
|
||||
if (camera == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
"Player has no camera associated with it. Cannot set up split-screen. Point PlayerInput.camera to camera for player.",
|
||||
player);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Assign split-screen area based on m_SplitScreenRect.
|
||||
var column = splitScreenIndex % numDivisionsX;
|
||||
var row = splitScreenIndex / numDivisionsX;
|
||||
var rect = new Rect
|
||||
{
|
||||
width = m_SplitScreenRect.width / numDivisionsX,
|
||||
height = m_SplitScreenRect.height / numDivisionsY
|
||||
};
|
||||
rect.x = m_SplitScreenRect.x + column * rect.width;
|
||||
// Y is bottom-to-top but we fill from top down.
|
||||
rect.y = m_SplitScreenRect.y + m_SplitScreenRect.height - (row + 1) * rect.height;
|
||||
camera.rect = rect;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDeviceUsableWithPlayerActions(InputDevice device)
|
||||
{
|
||||
Debug.Assert(device != null);
|
||||
|
||||
if (m_PlayerPrefab == null)
|
||||
return true;
|
||||
|
||||
var playerInput = m_PlayerPrefab.GetComponentInChildren<PlayerInput>();
|
||||
if (playerInput == null)
|
||||
return true;
|
||||
|
||||
var actions = playerInput.actions;
|
||||
if (actions == null)
|
||||
return true;
|
||||
|
||||
// If the asset has control schemes, see if there's one that works with the device plus
|
||||
// whatever unpaired devices we have left.
|
||||
if (actions.controlSchemes.Count > 0)
|
||||
{
|
||||
using (var unpairedDevices = InputUser.GetUnpairedInputDevices())
|
||||
{
|
||||
if (InputControlScheme.FindControlSchemeForDevices(unpairedDevices, actions.controlSchemes,
|
||||
mustIncludeDevice: device) == null)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise just check whether any of the maps has bindings usable with the device.
|
||||
foreach (var actionMap in actions.actionMaps)
|
||||
if (actionMap.IsUsableWithDevice(device))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ValidateInputActionAsset()
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (m_PlayerPrefab == null || m_PlayerPrefab.GetComponentInChildren<PlayerInput>() == null)
|
||||
return;
|
||||
|
||||
var actions = m_PlayerPrefab.GetComponentInChildren<PlayerInput>().actions;
|
||||
if (actions == null)
|
||||
return;
|
||||
|
||||
var isValid = true;
|
||||
foreach (var controlScheme in actions.controlSchemes)
|
||||
{
|
||||
if (controlScheme.deviceRequirements.Count > 0)
|
||||
break;
|
||||
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (isValid) return;
|
||||
|
||||
var assetInfo = actions.name;
|
||||
#if UNITY_EDITOR
|
||||
assetInfo = AssetDatabase.GetAssetPath(actions);
|
||||
#endif
|
||||
Debug.LogWarning($"The input action asset '{assetInfo}' in the player prefab assigned to PlayerInputManager has " +
|
||||
"no control schemes with required devices. The JoinPlayersWhenButtonIsPressed join behavior " +
|
||||
"will not work unless the expected input devices are listed as requirements in the input " +
|
||||
"action asset.", m_PlayerPrefab);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by <see cref="PlayerInput"/> when it is enabled.
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
internal void NotifyPlayerJoined(PlayerInput player)
|
||||
{
|
||||
Debug.Assert(player != null);
|
||||
|
||||
UpdateSplitScreen();
|
||||
|
||||
switch (m_NotificationBehavior)
|
||||
{
|
||||
case PlayerNotifications.SendMessages:
|
||||
SendMessage(PlayerJoinedMessage, player, SendMessageOptions.DontRequireReceiver);
|
||||
break;
|
||||
|
||||
case PlayerNotifications.BroadcastMessages:
|
||||
BroadcastMessage(PlayerJoinedMessage, player, SendMessageOptions.DontRequireReceiver);
|
||||
break;
|
||||
|
||||
case PlayerNotifications.InvokeUnityEvents:
|
||||
m_PlayerJoinedEvent?.Invoke(player);
|
||||
break;
|
||||
|
||||
case PlayerNotifications.InvokeCSharpEvents:
|
||||
DelegateHelpers.InvokeCallbacksSafe(ref m_PlayerJoinedCallbacks, player, "onPlayerJoined");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by <see cref="PlayerInput"/> when it is disabled.
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
internal void NotifyPlayerLeft(PlayerInput player)
|
||||
{
|
||||
Debug.Assert(player != null);
|
||||
|
||||
UpdateSplitScreen();
|
||||
|
||||
switch (m_NotificationBehavior)
|
||||
{
|
||||
case PlayerNotifications.SendMessages:
|
||||
SendMessage(PlayerLeftMessage, player, SendMessageOptions.DontRequireReceiver);
|
||||
break;
|
||||
|
||||
case PlayerNotifications.BroadcastMessages:
|
||||
BroadcastMessage(PlayerLeftMessage, player, SendMessageOptions.DontRequireReceiver);
|
||||
break;
|
||||
|
||||
case PlayerNotifications.InvokeUnityEvents:
|
||||
m_PlayerLeftEvent?.Invoke(player);
|
||||
break;
|
||||
|
||||
case PlayerNotifications.InvokeCSharpEvents:
|
||||
DelegateHelpers.InvokeCallbacksSafe(ref m_PlayerLeftCallbacks, player, "onPlayerLeft");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class PlayerJoinedEvent : UnityEvent<PlayerInput>
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class PlayerLeftEvent : UnityEvent<PlayerInput>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 621567455fd1c4ceb811cc8a00b6a1a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 40265a896a0f341bb956e90c59025a29, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,269 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Users;
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom inspector for <see cref="PlayerInputManager"/>.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PlayerInputManager))]
|
||||
internal class PlayerInputManagerEditor : UnityEditor.Editor
|
||||
{
|
||||
public void OnEnable()
|
||||
{
|
||||
InputUser.onChange += OnUserChange;
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
new InputComponentEditorAnalytic(InputSystemComponent.PlayerInputManager).Send();
|
||||
new PlayerInputManagerEditorAnalytic(this).Send();
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
InputUser.onChange -= OnUserChange;
|
||||
}
|
||||
|
||||
private void OnUserChange(InputUser user, InputUserChange change, InputDevice device)
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
////TODO: cache properties
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
DoNotificationSectionUI();
|
||||
EditorGUILayout.Space();
|
||||
DoJoinSectionUI();
|
||||
EditorGUILayout.Space();
|
||||
DoSplitScreenSectionUI();
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (EditorApplication.isPlaying)
|
||||
DoDebugUI();
|
||||
}
|
||||
|
||||
private void DoNotificationSectionUI()
|
||||
{
|
||||
var notificationBehaviorProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_NotificationBehavior));
|
||||
EditorGUILayout.PropertyField(notificationBehaviorProperty);
|
||||
switch ((PlayerNotifications)notificationBehaviorProperty.intValue)
|
||||
{
|
||||
case PlayerNotifications.SendMessages:
|
||||
if (m_SendMessagesHelpText == null)
|
||||
m_SendMessagesHelpText = EditorGUIUtility.TrTextContent(
|
||||
$"Will SendMessage() to GameObject: " + string.Join(",", PlayerInputManager.messages));
|
||||
EditorGUILayout.HelpBox(m_SendMessagesHelpText);
|
||||
break;
|
||||
|
||||
case PlayerNotifications.BroadcastMessages:
|
||||
if (m_BroadcastMessagesHelpText == null)
|
||||
m_BroadcastMessagesHelpText = EditorGUIUtility.TrTextContent(
|
||||
$"Will BroadcastMessage() to GameObject: " + string.Join(",", PlayerInputManager.messages));
|
||||
EditorGUILayout.HelpBox(m_BroadcastMessagesHelpText);
|
||||
break;
|
||||
|
||||
case PlayerNotifications.InvokeUnityEvents:
|
||||
m_EventsExpanded = EditorGUILayout.Foldout(m_EventsExpanded, m_EventsLabel, toggleOnLabelClick: true);
|
||||
if (m_EventsExpanded)
|
||||
{
|
||||
var playerJoinedEventProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_PlayerJoinedEvent));
|
||||
var playerLeftEventProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_PlayerLeftEvent));
|
||||
|
||||
EditorGUILayout.PropertyField(playerJoinedEventProperty);
|
||||
EditorGUILayout.PropertyField(playerLeftEventProperty);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoJoinSectionUI()
|
||||
{
|
||||
EditorGUILayout.LabelField(m_JoiningGroupLabel, EditorStyles.boldLabel);
|
||||
|
||||
// Join behavior
|
||||
var joinBehaviorProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_JoinBehavior));
|
||||
EditorGUILayout.PropertyField(joinBehaviorProperty);
|
||||
if ((PlayerJoinBehavior)joinBehaviorProperty.intValue != PlayerJoinBehavior.JoinPlayersManually)
|
||||
{
|
||||
++EditorGUI.indentLevel;
|
||||
|
||||
// Join action.
|
||||
if ((PlayerJoinBehavior)joinBehaviorProperty.intValue ==
|
||||
PlayerJoinBehavior.JoinPlayersWhenJoinActionIsTriggered)
|
||||
{
|
||||
var joinActionProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_JoinAction));
|
||||
EditorGUILayout.PropertyField(joinActionProperty);
|
||||
}
|
||||
|
||||
// Player prefab.
|
||||
var playerPrefabProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_PlayerPrefab));
|
||||
EditorGUILayout.PropertyField(playerPrefabProperty);
|
||||
|
||||
ValidatePlayerPrefab(joinBehaviorProperty, playerPrefabProperty);
|
||||
|
||||
--EditorGUI.indentLevel;
|
||||
}
|
||||
|
||||
// Enabled-by-default.
|
||||
var allowJoiningProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_AllowJoining));
|
||||
if (m_AllowingJoiningLabel == null)
|
||||
m_AllowingJoiningLabel = new GUIContent("Joining Enabled By Default", allowJoiningProperty.GetTooltip());
|
||||
EditorGUILayout.PropertyField(allowJoiningProperty, m_AllowingJoiningLabel);
|
||||
|
||||
// Max player count.
|
||||
var maxPlayerCountProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_MaxPlayerCount));
|
||||
if (m_EnableMaxPlayerCountLabel == null)
|
||||
m_EnableMaxPlayerCountLabel = EditorGUIUtility.TrTextContent("Limit Number of Players", maxPlayerCountProperty.GetTooltip());
|
||||
if (maxPlayerCountProperty.intValue > 0)
|
||||
m_MaxPlayerCountEnabled = true;
|
||||
m_MaxPlayerCountEnabled = EditorGUILayout.Toggle(m_EnableMaxPlayerCountLabel, m_MaxPlayerCountEnabled);
|
||||
if (m_MaxPlayerCountEnabled)
|
||||
{
|
||||
++EditorGUI.indentLevel;
|
||||
if (maxPlayerCountProperty.intValue < 0)
|
||||
maxPlayerCountProperty.intValue = 1;
|
||||
EditorGUILayout.PropertyField(maxPlayerCountProperty);
|
||||
--EditorGUI.indentLevel;
|
||||
}
|
||||
else
|
||||
maxPlayerCountProperty.intValue = -1;
|
||||
}
|
||||
|
||||
private static void ValidatePlayerPrefab(SerializedProperty joinBehaviorProperty,
|
||||
SerializedProperty playerPrefabProperty)
|
||||
{
|
||||
if ((PlayerJoinBehavior)joinBehaviorProperty.intValue != PlayerJoinBehavior.JoinPlayersWhenButtonIsPressed)
|
||||
return;
|
||||
|
||||
if (playerPrefabProperty.objectReferenceValue == null)
|
||||
return;
|
||||
|
||||
var playerInput = ((GameObject)playerPrefabProperty.objectReferenceValue)
|
||||
.GetComponentInChildren<PlayerInput>();
|
||||
|
||||
if (playerInput == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("No PlayerInput component found in player prefab.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerInput.actions == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("PlayerInput component has no input action asset assigned.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerInput.actions.controlSchemes.Any(c => c.deviceRequirements.Count > 0) == false)
|
||||
EditorGUILayout.HelpBox("Join Players When Button Is Pressed behavior will not work when the Input Action Asset " +
|
||||
"assigned to the PlayerInput component has no required devices in any control scheme.",
|
||||
MessageType.Info);
|
||||
}
|
||||
|
||||
private void DoSplitScreenSectionUI()
|
||||
{
|
||||
EditorGUILayout.LabelField(m_SplitScreenGroupLabel, EditorStyles.boldLabel);
|
||||
|
||||
// Split-screen toggle.
|
||||
var splitScreenProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_SplitScreen));
|
||||
if (m_SplitScreenLabel == null)
|
||||
m_SplitScreenLabel = new GUIContent("Enable Split-Screen", splitScreenProperty.GetTooltip());
|
||||
EditorGUILayout.PropertyField(splitScreenProperty, m_SplitScreenLabel);
|
||||
if (!splitScreenProperty.boolValue)
|
||||
return;
|
||||
|
||||
++EditorGUI.indentLevel;
|
||||
|
||||
// Maintain-aspect-ratio toggle.
|
||||
var maintainAspectRatioProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_MaintainAspectRatioInSplitScreen));
|
||||
if (m_MaintainAspectRatioLabel == null)
|
||||
m_MaintainAspectRatioLabel =
|
||||
new GUIContent("Maintain Aspect Ratio", maintainAspectRatioProperty.GetTooltip());
|
||||
EditorGUILayout.PropertyField(maintainAspectRatioProperty, m_MaintainAspectRatioLabel);
|
||||
|
||||
// Fixed-number toggle.
|
||||
var fixedNumberProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_FixedNumberOfSplitScreens));
|
||||
if (m_EnableFixedNumberOfSplitScreensLabel == null)
|
||||
m_EnableFixedNumberOfSplitScreensLabel = EditorGUIUtility.TrTextContent("Set Fixed Number", fixedNumberProperty.GetTooltip());
|
||||
if (fixedNumberProperty.intValue > 0)
|
||||
m_FixedNumberOfSplitScreensEnabled = true;
|
||||
m_FixedNumberOfSplitScreensEnabled = EditorGUILayout.Toggle(m_EnableFixedNumberOfSplitScreensLabel,
|
||||
m_FixedNumberOfSplitScreensEnabled);
|
||||
if (m_FixedNumberOfSplitScreensEnabled)
|
||||
{
|
||||
++EditorGUI.indentLevel;
|
||||
if (fixedNumberProperty.intValue < 0)
|
||||
fixedNumberProperty.intValue = 4;
|
||||
if (m_FixedNumberOfSplitScreensLabel == null)
|
||||
m_FixedNumberOfSplitScreensLabel = EditorGUIUtility.TrTextContent("Number of Screens",
|
||||
fixedNumberProperty.tooltip);
|
||||
EditorGUILayout.PropertyField(fixedNumberProperty, m_FixedNumberOfSplitScreensLabel);
|
||||
--EditorGUI.indentLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
fixedNumberProperty.intValue = -1;
|
||||
}
|
||||
|
||||
// Split-screen area.
|
||||
var splitScreenAreaProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_SplitScreenRect));
|
||||
if (m_SplitScreenAreaLabel == null)
|
||||
m_SplitScreenAreaLabel = new GUIContent("Screen Rectangle", splitScreenAreaProperty.GetTooltip());
|
||||
EditorGUILayout.PropertyField(splitScreenAreaProperty, m_SplitScreenAreaLabel);
|
||||
|
||||
--EditorGUI.indentLevel;
|
||||
}
|
||||
|
||||
private void DoDebugUI()
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(m_DebugLabel, EditorStyles.boldLabel);
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
|
||||
var players = PlayerInput.all;
|
||||
if (players.Count == 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("No Players");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var player in players)
|
||||
{
|
||||
var str = player.gameObject.name;
|
||||
if (player.splitScreenIndex != -1)
|
||||
str += $" (Screen #{player.splitScreenIndex})";
|
||||
EditorGUILayout.LabelField("Player #" + player.playerIndex, str);
|
||||
}
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
[SerializeField] private bool m_EventsExpanded;
|
||||
[SerializeField] private bool m_MaxPlayerCountEnabled;
|
||||
[SerializeField] private bool m_FixedNumberOfSplitScreensEnabled;
|
||||
|
||||
[NonSerialized] private readonly GUIContent m_JoiningGroupLabel = EditorGUIUtility.TrTextContent("Joining");
|
||||
[NonSerialized] private readonly GUIContent m_SplitScreenGroupLabel = EditorGUIUtility.TrTextContent("Split-Screen");
|
||||
[NonSerialized] private readonly GUIContent m_EventsLabel = EditorGUIUtility.TrTextContent("Events");
|
||||
[NonSerialized] private readonly GUIContent m_DebugLabel = EditorGUIUtility.TrTextContent("Debug");
|
||||
[NonSerialized] private GUIContent m_SendMessagesHelpText;
|
||||
[NonSerialized] private GUIContent m_BroadcastMessagesHelpText;
|
||||
[NonSerialized] private GUIContent m_AllowingJoiningLabel;
|
||||
[NonSerialized] private GUIContent m_SplitScreenLabel;
|
||||
[NonSerialized] private GUIContent m_MaintainAspectRatioLabel;
|
||||
[NonSerialized] private GUIContent m_SplitScreenAreaLabel;
|
||||
[NonSerialized] private GUIContent m_FixedNumberOfSplitScreensLabel;
|
||||
[NonSerialized] private GUIContent m_EnableMaxPlayerCountLabel;
|
||||
[NonSerialized] private GUIContent m_EnableFixedNumberOfSplitScreensLabel;
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6247927155f224aa5a61c15611fcd34a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines how <see cref="PlayerInputManager"/> joins new players.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// </remarks>
|
||||
/// <seealso cref="PlayerInputManager"/>
|
||||
/// <seealso cref="PlayerInputManager.joinBehavior"/>
|
||||
public enum PlayerJoinBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Listen for button presses on devices that are not paired to any player. If they occur
|
||||
/// and joining is allowed, join a new player using the device the button was pressed on.
|
||||
/// </summary>
|
||||
JoinPlayersWhenButtonIsPressed,
|
||||
|
||||
/// <summary>
|
||||
/// Listen for button presses on devices that are not paired to any player. If the control
|
||||
/// they triggered matches a specific action and joining is allowed, join a new player using
|
||||
/// the device the button was pressed on.
|
||||
/// </summary>
|
||||
JoinPlayersWhenJoinActionIsTriggered,
|
||||
|
||||
/// <summary>
|
||||
/// Don't join players automatically. Call <see cref="PlayerInputManager.JoinPlayerFromUI"/>
|
||||
/// or <see cref="PlayerInputManager.JoinPlayerFromAction"/> explicitly in order to join new
|
||||
/// players. Alternatively, just create GameObjects with <see cref="PlayerInput"/>
|
||||
/// components directly and they will be joined automatically.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This behavior also allows implementing more sophisticated device pairing mechanisms when multiple devices
|
||||
/// are involved. While initial engagement required by <see cref="JoinPlayersWhenButtonIsPressed"/> or
|
||||
/// <see cref="JoinPlayersWhenJoinActionIsTriggered"/> allows pairing a single device reliably to a player,
|
||||
/// additional devices that may be required by a control scheme will still get paired automatically out of the
|
||||
/// pool of available devices.
|
||||
/// </remarks>
|
||||
JoinPlayersManually,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86b230bdbb8ce487882861a17e6d2bde
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace UnityEngine.InputSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines how the triggering of an action or other input-related events are relayed to other GameObjects.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1717:Only FlagsAttribute enums should have plural names")]
|
||||
public enum PlayerNotifications
|
||||
{
|
||||
////TODO: add a "None" behavior; for actions, users may want to poll (or use the generated interfaces)
|
||||
|
||||
/// <summary>
|
||||
/// Use <see cref="GameObject.SendMessage(string,object)"/> to send a message to the <see cref="GameObject"/>
|
||||
/// that <see cref="PlayerInput"/> belongs to.
|
||||
///
|
||||
/// The message name will be the name of the action (e.g. "Jump"; it will not include the action map name),
|
||||
/// and the object will be the <see cref="PlayerInput"/> on which the action was triggered.
|
||||
///
|
||||
/// If the notification is for an action that was triggered, <see cref="SendMessageOptions"/> will be
|
||||
/// <see cref="SendMessageOptions.RequireReceiver"/> (i.e. an error will be logged if there is no corresponding
|
||||
/// method). Otherwise it will be <see cref="SendMessageOptions.DontRequireReceiver"/>.
|
||||
/// </summary>
|
||||
SendMessages,
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="SendMessages"/> but instead of using <see cref="GameObject.SendMessage(string,object)"/>,
|
||||
/// use <see cref="GameObject.BroadcastMessage(string,object)"/>.
|
||||
/// </summary>
|
||||
BroadcastMessages,
|
||||
|
||||
/// <summary>
|
||||
/// Have a separate <a href="https://docs.unity3d.com/ScriptReference/Events.UnityEvent.html">UnityEvent</a> for each notification.
|
||||
/// Allows wiring up target methods to invoke such that the connection is persisted in Unity serialized data.
|
||||
///
|
||||
/// See <see cref="PlayerInput.actionEvents"/> and related callbacks such as <see cref="PlayerInput.controlsChangedEvent"/>.
|
||||
/// </summary>
|
||||
InvokeUnityEvents,
|
||||
|
||||
////TODO: Kill
|
||||
/// <summary>
|
||||
/// Use plain C# callbacks.
|
||||
/// </summary>
|
||||
InvokeCSharpEvents
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 326532c3044e949b696215102693a1a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user