A game about forced loneliness, made by TACStudios
at master 535 lines 20 kB view raw
1using System; 2using UnityEngine; 3using UnityEditor.ShortcutManagement; 4using UnityEngine.UIElements; 5 6namespace UnityEditor.Rendering.LookDev 7{ 8 class CameraController : Manipulator 9 { 10 [Flags] 11 enum Direction 12 { 13 None = 0, 14 Up = 1 << 0, 15 Down = 1 << 1, 16 Left = 1 << 2, 17 Right = 1 << 3, 18 Forward = 1 << 4, 19 Backward = 1 << 5, 20 All = Up | Down | Left | Right | Forward | Backward 21 } 22 Direction m_DirectionKeyPressed = Direction.None; 23 24 float m_StartZoom = 0.0f; 25 float m_ZoomSpeed = 0.0f; 26 27 float m_TotalMotion = 0.0f; 28 Vector3 m_MotionDirection = new Vector3(); 29 30 float m_FlySpeedNormalized = .5f; 31 float m_FlySpeed = 1f; 32 float m_FlySpeedAccelerated = 0f; 33 const float m_FlySpeedMin = .01f; 34 const float m_FlySpeedMax = 2f; 35 //[TODO: check if necessary to add hability to deactivate acceleration] 36 const float k_FlyAcceleration = 1.1f; 37 bool m_ShiftBoostedFly = false; 38 bool m_InFlyMotion; 39 40 bool m_IsDragging; 41 static TimeHelper s_Timer = new TimeHelper(); 42 43 ViewTool m_BehaviorState; 44 ViewTool behaviorState 45 { 46 get { return m_BehaviorState; } 47 set 48 { 49 if (value != m_BehaviorState && m_BehaviorState == ViewTool.FPS) 50 { 51 isDragging = false; 52 inFlyMotion = false; 53 m_DirectionKeyPressed = Direction.None; 54 } 55 m_BehaviorState = value; 56 } 57 } 58 59 protected CameraState m_CameraState; 60 DisplayWindow m_Window; 61 protected Action m_Focused; 62 63 Rect screen => target.contentRect; 64 65 bool inFlyMotion 66 { 67 get => m_InFlyMotion; 68 set 69 { 70 if (value ^ m_InFlyMotion) 71 { 72 if (value) 73 { 74 s_Timer.Begin(); 75 EditorApplication.update += UpdateFPSMotion; 76 } 77 else 78 { 79 m_FlySpeedAccelerated = 0f; 80 m_MotionDirection = Vector3.zero; 81 m_ShiftBoostedFly = false; 82 EditorApplication.update -= UpdateFPSMotion; 83 } 84 m_InFlyMotion = value; 85 } 86 } 87 } 88 89 float flySpeedNormalized 90 { 91 get => m_FlySpeedNormalized; 92 set 93 { 94 m_FlySpeedNormalized = Mathf.Clamp01(value); 95 float speed = Mathf.Lerp(m_FlySpeedMin, m_FlySpeedMax, m_FlySpeedNormalized); 96 // Round to nearest decimal: 2 decimal points when between [0.01, 0.1]; 1 decimal point when between [0.1, 10]; integral between [10, 99] 97 speed = (float)(System.Math.Round((double)speed, speed < 0.1f ? 2 : speed < 10f ? 1 : 0)); 98 m_FlySpeed = Mathf.Clamp(speed, m_FlySpeedMin, m_FlySpeedMax); 99 } 100 } 101 float flySpeed 102 { 103 get => m_FlySpeed; 104 set => flySpeedNormalized = Mathf.InverseLerp(m_FlySpeedMin, m_FlySpeedMax, value); 105 } 106 107 virtual protected bool isDragging 108 { 109 get => m_IsDragging; 110 set 111 { 112 //As in scene view, stop dragging as first button is release in case of multiple button down 113 if (value ^ m_IsDragging) 114 { 115 if (value) 116 { 117 target.RegisterCallback<MouseMoveEvent>(OnMouseDrag); 118 target.CaptureMouse(); 119 EditorGUIUtility.SetWantsMouseJumping(1); //through screen edges 120 } 121 else 122 { 123 EditorGUIUtility.SetWantsMouseJumping(0); 124 target.ReleaseMouse(); 125 target.UnregisterCallback<MouseMoveEvent>(OnMouseDrag); 126 } 127 m_IsDragging = value; 128 } 129 } 130 } 131 132 public CameraController(DisplayWindow window, Action focused) 133 { 134 m_Window = window; 135 m_Focused = focused; 136 } 137 138 public void UpdateCameraState(Context context, ViewIndex index) 139 { 140 m_CameraState = context.GetViewContent(index).camera; 141 } 142 143 private void ResetCameraControl() 144 { 145 isDragging = false; 146 inFlyMotion = false; 147 behaviorState = ViewTool.None; 148 } 149 150 protected virtual void OnScrollWheel(WheelEvent evt) 151 { 152 // See UnityEditor.SceneViewMotion.HandleScrollWheel 153 switch (behaviorState) 154 { 155 case ViewTool.FPS: OnChangeFPSCameraSpeed(evt); break; 156 default: OnZoom(evt); break; 157 } 158 } 159 160 void OnMouseDrag(MouseMoveEvent evt) 161 { 162 switch (behaviorState) 163 { 164 case ViewTool.Orbit: OnMouseDragOrbit(evt); break; 165 case ViewTool.FPS: OnMouseDragFPS(evt); break; 166 case ViewTool.Pan: OnMouseDragPan(evt); break; 167 case ViewTool.Zoom: OnMouseDragZoom(evt); break; 168 default: break; 169 } 170 } 171 172 void OnKeyDown(KeyDownEvent evt) 173 { 174 OnKeyUpOrDownFPS(evt); 175 OnKeyDownReset(evt); 176 } 177 178 void OnChangeFPSCameraSpeed(WheelEvent evt) 179 { 180 float scrollWheelDelta = evt.delta.y; 181 flySpeedNormalized -= scrollWheelDelta * .01f; 182 string cameraSpeedDisplayValue = flySpeed.ToString(flySpeed < 0.1f ? "F2" : flySpeed < 10f ? "F1" : "F0"); 183 if (flySpeed < 0.1f) 184 cameraSpeedDisplayValue = cameraSpeedDisplayValue.TrimStart(new Char[] { '0' }); 185 GUIContent cameraSpeedContent = EditorGUIUtility.TrTempContent( 186 $"{cameraSpeedDisplayValue}x"); 187 m_Window.ShowNotification(cameraSpeedContent, .5f); 188 evt.StopPropagation(); 189 } 190 191 void OnZoom(WheelEvent evt) 192 { 193 const float deltaCutoff = .3f; 194 const float minZoom = .003f; 195 float scrollWheelDelta = evt.delta.y; 196 float relativeDelta = m_CameraState.viewSize * scrollWheelDelta * .015f; 197 if (relativeDelta > 0 && relativeDelta < deltaCutoff) 198 relativeDelta = deltaCutoff; 199 else if (relativeDelta <= 0 && relativeDelta > -deltaCutoff) 200 relativeDelta = -deltaCutoff; 201 m_CameraState.viewSize += relativeDelta; 202 if (m_CameraState.viewSize < minZoom) 203 m_CameraState.viewSize = minZoom; 204 evt.StopPropagation(); 205 } 206 207 void OnMouseDragOrbit(MouseMoveEvent evt) 208 { 209 Quaternion rotation = m_CameraState.rotation; 210 rotation = Quaternion.AngleAxis(evt.mouseDelta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation; 211 rotation = Quaternion.AngleAxis(evt.mouseDelta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation; 212 m_CameraState.rotation = rotation; 213 evt.StopPropagation(); 214 } 215 216 void OnMouseDragFPS(MouseMoveEvent evt) 217 { 218 Vector3 camPos = m_CameraState.pivot - m_CameraState.rotation * Vector3.forward * m_CameraState.distanceFromPivot; 219 Quaternion rotation = m_CameraState.rotation; 220 rotation = Quaternion.AngleAxis(evt.mouseDelta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation; 221 rotation = Quaternion.AngleAxis(evt.mouseDelta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation; 222 m_CameraState.rotation = rotation; 223 m_CameraState.pivot = camPos + rotation * Vector3.forward * m_CameraState.distanceFromPivot; 224 evt.StopPropagation(); 225 } 226 227 void OnMouseDragPan(MouseMoveEvent evt) 228 { 229 //[TODO: fix WorldToScreenPoint and ScreenToWorldPoint 230 var screenPos = m_CameraState.QuickProjectPivotInScreen(screen); 231 screenPos += new Vector3(evt.mouseDelta.x, -evt.mouseDelta.y, 0); 232 //Vector3 newWorldPos = m_CameraState.ScreenToWorldPoint(screen, screenPos); 233 Vector3 newWorldPos = m_CameraState.QuickReprojectionWithFixedFOVOnPivotPlane(screen, screenPos); 234 Vector3 worldDelta = newWorldPos - m_CameraState.pivot; 235 worldDelta *= EditorGUIUtility.pixelsPerPoint; 236 if (evt.shiftKey) 237 worldDelta *= 4; 238 m_CameraState.pivot += worldDelta; 239 evt.StopPropagation(); 240 } 241 242 void OnMouseDragZoom(MouseMoveEvent evt) 243 { 244 float zoomDelta = HandleUtility.niceMouseDeltaZoom * (evt.shiftKey ? 9 : 3); 245 m_TotalMotion += zoomDelta; 246 if (m_TotalMotion < 0) 247 m_CameraState.viewSize = m_StartZoom * (1 + m_TotalMotion * .001f); 248 else 249 m_CameraState.viewSize = m_CameraState.viewSize + zoomDelta * m_ZoomSpeed * .003f; 250 evt.StopPropagation(); 251 } 252 253 void OnKeyDownReset(KeyDownEvent evt) 254 { 255 if (evt.keyCode == KeyCode.Escape) 256 ResetCameraControl(); 257 evt.StopPropagation(); 258 } 259 260 void OnKeyUpOrDownFPS<T>(KeyboardEventBase<T> evt) 261 where T : KeyboardEventBase<T>, new() 262 { 263 if (behaviorState != ViewTool.FPS) 264 return; 265 266 //Note: Keydown is called in loop but between first occurence of the 267 // loop and laters, there is a small pause. To deal with this, we 268 // need to register the UpdateMovement function to the Editor update 269 KeyCombination combination; 270 if (GetKeyCombinationByID("3D Viewport/Fly Mode Forward", out combination) && combination.Match(evt)) 271 RegisterMotionChange(Direction.Forward, evt); 272 if (GetKeyCombinationByID("3D Viewport/Fly Mode Backward", out combination) && combination.Match(evt)) 273 RegisterMotionChange(Direction.Backward, evt); 274 if (GetKeyCombinationByID("3D Viewport/Fly Mode Left", out combination) && combination.Match(evt)) 275 RegisterMotionChange(Direction.Left, evt); 276 if (GetKeyCombinationByID("3D Viewport/Fly Mode Right", out combination) && combination.Match(evt)) 277 RegisterMotionChange(Direction.Right, evt); 278 if (GetKeyCombinationByID("3D Viewport/Fly Mode Up", out combination) && combination.Match(evt)) 279 RegisterMotionChange(Direction.Up, evt); 280 if (GetKeyCombinationByID("3D Viewport/Fly Mode Down", out combination) && combination.Match(evt)) 281 RegisterMotionChange(Direction.Down, evt); 282 } 283 284 void RegisterMotionChange<T>(Direction direction, KeyboardEventBase<T> evt) 285 where T : KeyboardEventBase<T>, new() 286 { 287 m_ShiftBoostedFly = evt.shiftKey; 288 Direction formerDirection = m_DirectionKeyPressed; 289 bool keyUp = evt is KeyUpEvent; 290 bool keyDown = evt is KeyDownEvent; 291 if (keyDown) 292 m_DirectionKeyPressed |= direction; 293 else if (keyUp) 294 m_DirectionKeyPressed &= (Direction.All & ~direction); 295 if (formerDirection != m_DirectionKeyPressed) 296 { 297 m_MotionDirection = new Vector3( 298 ((m_DirectionKeyPressed & Direction.Right) > 0 ? 1 : 0) - ((m_DirectionKeyPressed & Direction.Left) > 0 ? 1 : 0), 299 ((m_DirectionKeyPressed & Direction.Up) > 0 ? 1 : 0) - ((m_DirectionKeyPressed & Direction.Down) > 0 ? 1 : 0), 300 ((m_DirectionKeyPressed & Direction.Forward) > 0 ? 1 : 0) - ((m_DirectionKeyPressed & Direction.Backward) > 0 ? 1 : 0)); 301 302 inFlyMotion = m_DirectionKeyPressed != Direction.None; 303 } 304 evt.StopPropagation(); 305 } 306 307 Vector3 GetMotionDirection() 308 { 309 var deltaTime = s_Timer.Update(); 310 Vector3 result; 311 float speed = (m_ShiftBoostedFly ? 5 * flySpeed : flySpeed); 312 if (m_FlySpeedAccelerated == 0) 313 m_FlySpeedAccelerated = 9; 314 else 315 m_FlySpeedAccelerated *= Mathf.Pow(k_FlyAcceleration, deltaTime); 316 result = m_MotionDirection.normalized * m_FlySpeedAccelerated * speed * deltaTime; 317 return result; 318 } 319 320 void UpdateFPSMotion() 321 { 322 m_CameraState.pivot += m_CameraState.rotation * GetMotionDirection(); 323 m_Window.Repaint(); //this prevent hich on key down as in CameraFlyModeContext.cs 324 } 325 326 bool GetKeyCombinationByID(string ID, out KeyCombination combination) 327 { 328 var sequence = ShortcutManager.instance.GetShortcutBinding(ID).keyCombinationSequence.GetEnumerator(); 329 if (sequence.MoveNext()) //have a first entry 330 { 331 combination = new KeyCombination(sequence.Current); 332 return true; 333 } 334 else 335 { 336 combination = default; 337 return false; 338 } 339 } 340 341 ViewTool GetBehaviorTool<T>(MouseEventBase<T> evt, bool onMac) where T : MouseEventBase<T>, new() 342 { 343 if (evt.button == 2) 344 return ViewTool.Pan; 345 else if (evt.button == 0 && evt.ctrlKey && onMac || evt.button == 1 && evt.altKey) 346 return ViewTool.Zoom; 347 else if (evt.button == 0) 348 return ViewTool.Orbit; 349 else if (evt.button == 1 && !evt.altKey) 350 return ViewTool.FPS; 351 return ViewTool.None; 352 } 353 354 void OnMouseUp(MouseUpEvent evt) 355 { 356 bool onMac = Application.platform == RuntimePlatform.OSXEditor; 357 var state = GetBehaviorTool(evt, onMac); 358 359 if (state == behaviorState) 360 ResetCameraControl(); 361 evt.StopPropagation(); 362 } 363 364 void OnMouseDown(MouseDownEvent evt) 365 { 366 bool onMac = Application.platform == RuntimePlatform.OSXEditor; 367 behaviorState = GetBehaviorTool(evt, onMac); 368 369 if (behaviorState == ViewTool.Zoom) 370 { 371 m_StartZoom = m_CameraState.viewSize; 372 m_ZoomSpeed = Mathf.Max(Mathf.Abs(m_StartZoom), .3f); 373 m_TotalMotion = 0; 374 } 375 376 // see also SceneView.HandleClickAndDragToFocus() 377 if (evt.button == 1 && onMac) 378 m_Window.Focus(); 379 380 target.Focus(); //required for keyboard event 381 isDragging = true; 382 evt.StopPropagation(); 383 384 m_Focused?.Invoke(); 385 } 386 387 protected override void RegisterCallbacksOnTarget() 388 { 389 target.focusable = true; //prerequisite for being focusable and recerive keydown events 390 target.RegisterCallback<MouseUpEvent>(OnMouseUp); 391 target.RegisterCallback<MouseDownEvent>(OnMouseDown); 392 target.RegisterCallback<WheelEvent>(OnScrollWheel); 393 target.RegisterCallback<KeyDownEvent>(OnKeyDown); 394 target.RegisterCallback<KeyUpEvent>(OnKeyUpOrDownFPS); 395 } 396 397 protected override void UnregisterCallbacksFromTarget() 398 { 399 target.UnregisterCallback<MouseUpEvent>(OnMouseUp); 400 target.UnregisterCallback<MouseDownEvent>(OnMouseDown); 401 target.UnregisterCallback<WheelEvent>(OnScrollWheel); 402 target.UnregisterCallback<KeyDownEvent>(OnKeyDown); 403 target.UnregisterCallback<KeyUpEvent>(OnKeyUpOrDownFPS); 404 } 405 406 struct KeyCombination 407 { 408 KeyCode key; 409 EventModifiers modifier; 410 public bool shiftOnLastMatch; 411 412 public KeyCombination(UnityEditor.ShortcutManagement.KeyCombination shortcutCombination) 413 { 414 key = shortcutCombination.keyCode; 415 modifier = EventModifiers.None; 416 if ((shortcutCombination.modifiers & ShortcutModifiers.Shift) != 0) 417 modifier |= EventModifiers.Shift; 418 if ((shortcutCombination.modifiers & ShortcutModifiers.Alt) != 0) 419 modifier |= EventModifiers.Alt; 420 if ((shortcutCombination.modifiers & ShortcutModifiers.Action) != 0) 421 { 422 if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer) 423 modifier |= EventModifiers.Command; 424 else 425 modifier |= EventModifiers.Control; 426 } 427 shiftOnLastMatch = false; 428 } 429 430 //atLeastModifier allow case were A is required but event provide shift+A 431 public bool Match(IKeyboardEvent evt, bool atLeastForModifier = true) 432 { 433 shiftOnLastMatch = evt.shiftKey; 434 if (atLeastForModifier) 435 return key == evt.keyCode && modifier == (evt.modifiers & modifier); 436 else 437 return key == evt.keyCode && modifier == evt.modifiers; 438 } 439 } 440 441 struct TimeHelper 442 { 443 long lastTime; 444 445 public void Begin() => lastTime = System.DateTime.Now.Ticks; 446 447 public float Update() 448 { 449 float deltaTime = (System.DateTime.Now.Ticks - lastTime) / 10000000.0f; 450 lastTime = System.DateTime.Now.Ticks; 451 return deltaTime; 452 } 453 } 454 } 455 456 class SwitchableCameraController : CameraController 457 { 458 CameraState m_FirstView; 459 CameraState m_SecondView; 460 ViewIndex m_CurrentViewIndex; 461 462 bool switchedDrag = false; 463 bool switchedWheel = false; 464 465 public SwitchableCameraController(DisplayWindow window, Action<ViewIndex> focused) 466 : base(window, null) 467 { 468 m_CurrentViewIndex = ViewIndex.First; 469 470 m_Focused = () => focused?.Invoke(m_CurrentViewIndex); 471 } 472 473 public void UpdateCameraState(Context context) 474 { 475 m_FirstView = context.GetViewContent(ViewIndex.First).camera; 476 m_SecondView = context.GetViewContent(ViewIndex.Second).camera; 477 478 m_CameraState = m_CurrentViewIndex == ViewIndex.First ? m_FirstView : m_SecondView; 479 } 480 481 void SwitchTo(ViewIndex index) 482 { 483 CameraState stateToSwitch; 484 switch (index) 485 { 486 case ViewIndex.First: 487 stateToSwitch = m_FirstView; 488 break; 489 case ViewIndex.Second: 490 stateToSwitch = m_SecondView; 491 break; 492 default: 493 throw new ArgumentException("Unknown ViewIndex"); 494 } 495 496 if (stateToSwitch != m_CameraState) 497 m_CameraState = stateToSwitch; 498 499 m_CurrentViewIndex = index; 500 } 501 502 public void SwitchUntilNextEndOfDrag() 503 { 504 switchedDrag = true; 505 SwitchTo(ViewIndex.Second); 506 } 507 508 override protected bool isDragging 509 { 510 get => base.isDragging; 511 set 512 { 513 bool switchBack = false; 514 if (switchedDrag && base.isDragging && !value) 515 switchBack = true; 516 base.isDragging = value; 517 if (switchBack) 518 SwitchTo(ViewIndex.First); 519 } 520 } 521 522 public void SwitchUntilNextWheelEvent() 523 { 524 switchedWheel = true; 525 SwitchTo(ViewIndex.Second); 526 } 527 528 protected override void OnScrollWheel(WheelEvent evt) 529 { 530 base.OnScrollWheel(evt); 531 if (switchedWheel) 532 SwitchTo(ViewIndex.First); 533 } 534 } 535}