示例#1
0
            // TODO Track updates of individual config values to improve rendering performance

            public ConfigDescription(ITypedDataCursor <ParachuteConfig> config, Vector3 pilotTorsoScale)
            {
                Radius = new InputRange(
                    CreateRange(-8f, 8f, config.To(c => c.RadiusVertical)),
                    CreateRange(1f, 16f, config.To(c => c.RadiusHorizontal))
                    );
                Volume = new InputRange(
                    CreateRange(1f, 16f, config.To(c => c.Span)),
                    zAxisDescription: CreateRange(0.5f, 5, config.To(c => c.Chord))
                    );
                RiggingAngle = new InputRange(
                    CreateRange(0, 30, config.To(c => c.RiggingAngle))
                    );
                HeightOffset = new InputRange(
                    yAxisDescription: CreateRange(0f, 16f, config.To(c => c.HeightOffset))
                    );
                var rigAttachCursor = config.To(c => c.RigAttachPos);

                RigAttachPosition = new InputRange(
                    RigAttachComponent(0f, 1f, rigAttachCursor.To(DefaultPath.Vector3X), pilotTorsoScale.x),
                    RigAttachComponent(0f, 1f, rigAttachCursor.To(DefaultPath.Vector3Y), pilotTorsoScale.y),
                    RigAttachComponent(-1f, 1f, rigAttachCursor.To(DefaultPath.Vector3Z), pilotTorsoScale.z)
                    );
                PilotWeight = new InputRange(
                    CreateRange(40f, 200f, config.To(c => c.PilotWeight))
                    );
                WeightShiftMagnitude = new InputRange(
                    CreateRange(0.1f, 0.5f, config.To(c => c.WeightshiftMagnitude))
                    );
            }
示例#2
0
            /// <summary>
            /// </summary>
            /// <param name="cursor">
            /// <code>
            /// {"items": GameObject list,
            ///  "isInteractable": bool}
            /// </code>
            /// </param>
            /// <param name="indexCursor"><code>int?</code></param>
            public static IDisposable Initialize(ITypedDataCursor <UIListState> cursor)
            {
                var isInteractable   = cursor.To(s => s.IsInteractable);
                var highlightedIndex = cursor.To(s => s.HighlightedIndex);

                return(AddMouseHighlight(
                           updateHighlightedIndex: index => {
                    if (isInteractable.Get() && index.HasValue)
                    {
                        highlightedIndex.Set(index.Value);
                    }
                },
                           items: cursor.To(s => s.Items).Get()));
            }
示例#3
0
            private static void Update(ITypedDataCursor <UIListState> cursor)
            {
                var items = cursor.To(s => s.Items).Get();

                for (int i = 0; i < items.Count; i++)
                {
                    var item          = items[i];
                    var highlightable = item.GetComponentOfInterface <IHighlightable>();
                    highlightable.UnHighlight();
                }

                var highlightedIndex = cursor.To(s => s.HighlightedIndex).Get();

                items[highlightedIndex].GetComponentOfInterface <IHighlightable>().Highlight();
            }
示例#4
0
        IEnumerator <WaitCommand> OnEnter()
        {
            _data.CameraManager.SwitchMount(_data.CameraMount.GetComponent <ICameraMount>());

            var spawnpointBillboards = _uiState.To(s => s.SpawnpointUIList).To(s => s.Items).Get();

            for (int i = 0; i < _spawnpoints.Count; i++)
            {
                var spawnpoint = _spawnpoints[i];
                spawnpointBillboards[i].SetActive(spawnpoint.IsDiscovered);
            }

            OnResume();

            yield return(WaitCommand.WaitRoutine(CameraTransitions.FadeIn(_data.CameraManager.Rig.ScreenFader, _data.MenuClock, _data.FaderSettings)));
        }
示例#5
0
            public NavigableUIList(ITypedDataCursor <UIListState> state, Camera camera, Action <int> selectItem)
            {
                _isInteractable = state.To(s => s.IsInteractable);
                _selectItem     = selectItem;

                _highlightedIndex = state.To(s => s.HighlightedIndex);
                MouseHighlight.Initialize(state);
                MouseInteraction.LeftMouseClick(state.To(s => s.Items).Get(), selectItem);

                _controllerHighlight = ControllerHighlight.CreateControllerIndexUpdater(
                    cursor: state,
                    itemSelector: (items, currentIndex, inputDirection) => {
                    return(ControllerHighlight.MoveControllerIndex(camera.WorldToScreenPoint, items, currentIndex, inputDirection));
                });

                Highlighter.Initialize(state);
            }
示例#6
0
            /// <summary>
            /// </summary>
            /// <param name="cursor">
            /// <code>
            /// {"items": GameObject list,
            ///  "isInteractable": bool}
            /// </code>
            /// </param>
            /// <param name="indexCursor"><code>int?</code></param>
            public static Action <Vector2> CreateControllerIndexUpdater(ITypedDataCursor <UIListState> cursor,
                                                                        Func <IList <GameObject>, int, Vector2, int> itemSelector)
            {
                // Everytime there is new keyboard input, feed it
                // the currently highlighted controller index
                var items            = cursor.To(s => s.Items);
                var isInteractable   = cursor.To(s => s.IsInteractable);
                var highlightedIndex = cursor.To(s => s.HighlightedIndex);

                return(keyboardInput => {
                    // Magnitude check for performance reasons
                    if (keyboardInput.magnitude > 0 && isInteractable.Get())
                    {
                        var currentIndex = highlightedIndex.Get();
                        highlightedIndex.Set(itemSelector(items.Get(), currentIndex, keyboardInput));
                    }
                });
            }
示例#7
0
        public SpawnScreen(IStateMachine machine, Data data) : base(machine)
        {
            _data = data;

            var billboardDependencies = new DependencyContainer(new Dictionary <string, object> {
                { "clock", _data.MenuClock },
                { "cameraTransform", _data.CameraMount.transform }
            });

            _spawnpoints = GameObject.FindObjectOfType <SpawnpointDiscoverer>().Spawnpoints;
            var spawnpointBillboards = new List <GameObject>(_spawnpoints.Count);
            var billboardGroup       = new GameObject("SpawnpointBillboards");

            for (int i = 0; i < _spawnpoints.Count; i++)
            {
                var spawnpoint          = _spawnpoints[i];
                var spawnpointBillBoard = (GameObject)GameObject.Instantiate(_data.SpawnpointBillboardPrefab);
                spawnpointBillBoard.SetActive(spawnpoint.IsDiscovered);
                DependencyInjector.Default.Inject(spawnpointBillBoard, billboardDependencies);
                spawnpointBillBoard.SetParent(billboardGroup);
                spawnpointBillBoard.transform.Set(spawnpoint.Location.AsTransform);
                spawnpointBillBoard.SetActive(false);
                spawnpointBillboards.Add(spawnpointBillBoard);
            }

            _uiState = TypedDataCursor <SpawnScreenUIState> .Root(new SpawnScreenUIState(
                                                                      spawnpointUiList : new UIListState(spawnpointBillboards)));

            var spawnpointUiState = _uiState.To(s => s.SpawnpointUIList);

            var spawnpointIndexChanged = spawnpointUiState
                                         .To(s => s.HighlightedIndex)
                                         .OnUpdate
                                         .DistinctUntilChanged(IntComparer.Instance)
                                         .Skip(1);

            _data.SelectedSpawnpointName.text = _spawnpoints[spawnpointUiState.Get().HighlightedIndex].Name;
            spawnpointIndexChanged.Subscribe(i => {
                Fmod.PlayOneShot("event:/ui/drop_hover");
                var spawnpoint = _spawnpoints[i];
                _data.SelectedSpawnpointName.text = spawnpoint.Name;
                _data.CameraAnimator.LookTarget   = spawnpointBillboards[i].transform;
            });

            _uiList = new UISketch.NavigableUIList(
                spawnpointUiState,
                data.CameraManager.Rig.GetMainCamera(),
                spawnpointIndex => {
                _data.EventSystem.Emit(new Events.SpawnpointSelected(_spawnpoints[spawnpointIndex].Location));
            });
        }
示例#8
0
            public Editing(IStateMachine machine, Data data,
                           ParachuteStorage parachuteStorage, ITypedDataCursor <ParachuteStorageViewState> storageState) : base(machine)
            {
                _editorParachuteChanges = new ReplaySubject <Parachute>(1);

                _data = data;

                _editorState           = storageState.To(c => c.EditorState);
                _activeParachuteConfig = _editorState.To(editorState => editorState.Config);

                // Toggle editor camera off when a gizmo interaction starts to avoid feedback loops
                //_data._cameraRig.Initialize();

                storageState.OnUpdate
                // Store parachute every time it is changed (by sampling or throttling to reduce disk I/O)
                // Store parachute every time the use selects a different parachute
                .Throttle(TimeSpan.FromSeconds(2), Scheduler.ThreadPool)
                .ObserveOn(UnityThreadScheduler.MainThread)
                .Select(state => {
                    return(state.AvailableParachutes
                           .Where(p => p.IsEditable)
                           .Select(p => {
                        var config = GameObject.Instantiate(p);
                        var json = JsonUtility.ToJson(config, prettyPrint: true);
                        return new { config, json };
                    })
                           .ToList());
                })     // Copy object to prevent thread-unsafe editing
                .ObserveOn(Schedulers.FileWriterScheduler)
                .Subscribe(parachutes => {
                    parachuteStorage.DeleteAllStoredParachutes();
                    for (int i = 0; i < parachutes.Count; i++)
                    {
                        var parachute = parachutes[i];
                        parachuteStorage.StoreParachute(parachute.config, parachute.json);
                    }
                });

                data.ParachuteSelectionView.Initialize(storageState, data.GameSettingsProvider.IsVrActive);
                data.ParachuteSelectionView.BackToFlight += TransitToFlyingState;
            }
示例#9
0
        public void Initialize(ITypedDataCursor <EditorState> editorState, IObservable <Parachute> editorParachute)
        {
            if (!_isInitialized)
            {
                var configCursor = editorState.To(c => c.Config);

                var parachuteConfigViewModel = new ParachuteConfigViewModel(configCursor);
                _parachuteConfigView.Initialize(parachuteConfigViewModel);
                RegisterUIHover(_additionalSettingsParent.AddComponent <DefaultHoverEventSource>());

                var parachuteColor = configCursor.To(c => c.Color);
                _cellColorPicker.onValueChanged.AddListener(color => parachuteColor.Set(color));
                RegisterUIHover(_cellColorPicker.gameObject.AddComponent <DefaultHoverEventSource>());

                _selectedWidget = editorState.To(c => c.SelectedWidget);

                configDescription = new ConfigDescription(configCursor, _pilotTorsoScale);

                // TODO Use GUI camera?
                var mainCamera = _cameraManager.Rig.GetMainCamera();

                _radiusGizmo.InputRange               = configDescription.Radius;
                _pilotWeightGizmo.InputRange          = configDescription.PilotWeight;
                _weightShiftMagnitudeGizmo.InputRange = configDescription.WeightShiftMagnitude;
                _heightOffsetWidget.InputRange        = configDescription.HeightOffset;
                _rigAttachPosition.InputRange         = configDescription.RigAttachPosition;
                _riggingAngleGizmo.InputRange         = configDescription.RiggingAngle;
                _riggingAngleGizmo.Camera             = mainCamera;

                RegisterDraggable(_radiusGizmo.gameObject, WidgetId.Radius);
                RegisterDraggable(_pilotWeightGizmo.gameObject, WidgetId.PilotWeight);
                RegisterDraggable(_heightOffsetWidget.gameObject, WidgetId.HeightOffset);
                RegisterDraggable(_weightShiftMagnitudeGizmo.gameObject, WidgetId.WeightShiftMagnitude);
                RegisterGizmoHandlers(_riggingAngleGizmo, WidgetId.RiggingAngle);

                editorState.To(c => c.SelectedWidget)
                .OnUpdate
                .Subscribe(selectedWidget => {
                    // Render tooltip
                    //_rigAttachPositionText.gameObject.SetActive(selectedWidget == WidgetId.RigAttachPosition);

                    if (selectedWidget.HasValue)
                    {
                        var tooltip     = Tooltips[selectedWidget.Value];
                        var description = tooltip.Description + "\n\n<i>" + tooltip.Effect + "</i>";
                        _tooltipView.SetState(tooltip.Name, description);
                        _tooltipView.gameObject.SetActive(true);
                    }
                    else
                    {
                        _tooltipView.gameObject.SetActive(false);
                    }
                });

                var colliderSet = ColliderSet.Box("ParachuteUICellColliders", LayerMask.NameToLayer("UI"));

                editorState.To(c => c.IsEditing)
                .OnUpdate
                .Subscribe(isEditing => {
                    colliderSet.Parent.SetActive(isEditing);
                    _gizmosParent.SetActive(isEditing);
                    _additionalSettingsParent.SetActive(isEditing);
                    _cellColorPicker.gameObject.SetActive(isEditing);
                });

                configCursor.OnUpdate
                .CombineLatest(
                    _gameSettingsProvider.SettingChanges,
                    _activeLanguage.TableUpdates,
                    (config, settings, languageTable) => new { config, settings, languageTable })
                .Subscribe(x => {
                    _parachuteConfigView.SetState(x.languageTable.AsFunc);

                    var props = ParachuteProperties.FromConfig(x.config);
                    if (x.settings.Gameplay.UnitSystem == UnitSystem.Metric)
                    {
                        UpdateEditorState(x.config, props);
                    }
                    else
                    {
                        UpdateEditorState(x.config, props.ToImperial());
                    }
                });

                colliderSet.Parent.SetParent(gameObject);
                var canopyDragHandler = colliderSet.Parent.AddComponent <SurfaceDragHandler>();
                canopyDragHandler.Dragging += (camTransform, value) => {
                    var currentValue = configDescription.Volume.Value;
                    currentValue.x += value.x;
                    currentValue.z -= value.y;
                    configDescription.Volume.SetValue(currentValue);
                };
                _canopyHighlight.Highlightable = canopyDragHandler;

                RegisterDraggable(colliderSet.Parent, WidgetId.Area);
                editorParachute.Subscribe(p => {
                    _canopyHighlight.Renderer = p.CanopyMesh;

                    _rigAttachPosition.transform.position = p.Pilot.Torso.transform.position;
                    _rigAttachPosition.transform.rotation = p.Pilot.Torso.transform.rotation;

                    colliderSet.SetSize(p.Sections.Count);
                    for (var i = 0; i < p.Sections.Count; i++)
                    {
                        var cell = p.Sections[i].Cell;
                        colliderSet.UpdateCollider(i, cell.Collider);

                        var isLastCell = i == p.Sections.Count - 1;
                        if (isLastCell)
                        {
                            var gizmoTransform = cell.transform.MakeImmutable()
                                                 .TranslateLocally(0.8f)
                                                 .UpdateScale(Vector3.one)
                                                 .UpdateRotation(p.Root.rotation);
                            _radiusGizmo.transform.Set(gizmoTransform);
                        }
                    }
                });

//                _rigAttachPosition.OnPositionChanged += newPosition => {
//                    configDescription.RigAttachPosition.SetValue(newPosition);
//                };
//                _rigAttachPosition.ActiveAxes = ActiveAxis.X | ActiveAxis.Y | ActiveAxis.Z;
                RegisterDraggable(_rigAttachPosition.gameObject, WidgetId.RigAttachPosition);

                /*
                 * What are the problems that we need to solve:
                 * - Use a single source of truth to render the parachute
                 * - Use a single source of truth to render the GUI
                 * - The GUI widgets should not know more than just the value they need to update and
                 *   what the value is they need to render (use cursors into the app state to do this)
                 * - Use unity as a rendering engine but not as a logic engine (possibly too hard to do this now)
                 *
                 *
                 *
                 * Gizmo:
                 *
                 * - Show scaling and rotation widgets at all times and
                 *   couple them to an InputRange.
                 * - (optional) Draw box around selected thing
                 *
                 */

                _isInitialized = true;
            }
        }
示例#10
0
        public void Initialize(ITypedDataCursor <ParachuteStorageViewState> storage, bool isVrActive)
        {
            _openParachuteFolder.gameObject.SetActive(!isVrActive);
            _addParachute.gameObject.SetActive(!isVrActive);

            _backToFlight.onClick.AddListener(() => {
                if (BackToFlight != null)
                {
                    BackToFlight();
                }
            });

            var editorState     = storage.To(s => s.EditorState);
            var availableChutes = storage.To(s => s.AvailableParachutes);

            var             parachuteName       = editorState.To(s => s.Config).To(c => c.Name);
            Action <string> updateParachuteName = value => {
                parachuteName.Set(value.Limit(_parachuteNameCharLimit, ""));
            };

            _parachuteNameEditor.Limit        = _parachuteNameCharLimit;
            _parachuteNameEditor.TextChanged += updateParachuteName;

            _parachuteThumbnails = new ParachuteThumbnail[_maxParachutes];
            for (int i = 0; i < _maxParachutes; i++)
            {
                var parachuteThumbnail = GameObject.Instantiate(_parachuteThumbnailPrefab).GetComponent <ParachuteThumbnail>();
                parachuteThumbnail.transform.SetParent(_thumbnailRoot.transform);
                parachuteThumbnail.transform.localPosition = Vector3.zero;
                parachuteThumbnail.transform.localRotation = Quaternion.identity;
                parachuteThumbnail.transform.localScale    = Vector3.one;
                parachuteThumbnail.gameObject.SetActive(false);
                _parachuteThumbnails[i] = parachuteThumbnail;
            }

            var storageDir = storage.To(s => s.StorageDir);

            _openParachuteFolder.onClick.AddListener(() => {
                UnityFileBrowserUtil.Open(storageDir.Get());
            });

            Action <string> selectChute = parachuteId => {
                var state = storage.Get();
                if (state.EditorState.Config.Id != parachuteId)
                {
                    for (int i = 0; i < state.AvailableParachutes.Count; i++)
                    {
                        var parachute = state.AvailableParachutes[i];
                        if (parachute.Id == parachuteId)
                        {
                            editorState.Set(new ParachuteEditor.EditorState(parachute));
                        }
                    }
                }
            };

            Action <string> deleteChute = parachuteId => {
                var state = storage.Get();
                int selectedParachuteIndex = 0;
                for (int i = state.AvailableParachutes.Count - 1; i >= 0; i--)
                {
                    var parachute = state.AvailableParachutes[i];
                    if (parachute.Id == parachuteId)
                    {
                        state.AvailableParachutes.RemoveAt(i);
                        selectedParachuteIndex = Math.Max(i - 1, 0);
                        break;
                    }
                }
                if (state.EditorState.Config.Id == parachuteId)
                {
                    var defaultChute = state.AvailableParachutes[selectedParachuteIndex];
                    editorState.Set(new ParachuteEditor.EditorState(defaultChute));
                }
                availableChutes.Set(state.AvailableParachutes);
            };

            Action <string> cloneChute = parachuteId => {
                var state = storage.Get();
                if (state.AvailableParachutes.Count < _maxParachutes)
                {
                    ParachuteConfig existingParachute = null;
                    for (int i = 0; i < state.AvailableParachutes.Count; i++)
                    {
                        var parachute = state.AvailableParachutes[i];
                        if (parachute.Id == parachuteId)
                        {
                            existingParachute = parachute;
                        }
                    }

                    var newParachute = ParachuteConfig.CreateNew(existingParachute);
                    newParachute.Name = (existingParachute.Name + " Copy").Limit(_parachuteNameCharLimit, "");
                    state.AvailableParachutes.Add(newParachute);
                    availableChutes.Set(state.AvailableParachutes);
                    selectChute(newParachute.Id);
                }
            };

            var isEditingState = storage.To(s => s.EditorState).To(s => s.IsEditing);

            Action <string> editChute = parachuteId => {
                var    state = storage.Get();
                string currentlyEditingParachute = state.EditorState.IsEditing ? state.EditorState.Config.Id : null;
                if (currentlyEditingParachute == parachuteId)
                {
                    // Switch to read-only mode
                    isEditingState.Set(false);
                }
                else
                {
                    // Enable editing
                    // TODO Do selection and edit switching in one transformation
                    selectChute(parachuteId);
                    isEditingState.Set(true);
                }
            };

            _addParachute.onClick.AddListener(() => {
                var state = storage.Get();
                if (state.AvailableParachutes.Count < _maxParachutes)
                {
                    var newParachute  = ParachuteConfig.CreateNew(_parachuteConfigTemplate);
                    newParachute.Name = "New " + _parachuteConfigTemplate.Name;
                    state.AvailableParachutes.Add(newParachute);
                    availableChutes.Set(state.AvailableParachutes);
                    editorState.Set(new ParachuteEditor.EditorState(newParachute));
                }
            });

            storage.OnUpdate.Subscribe(state => {
                for (int i = 0; i < _parachuteThumbnails.Length; i++)
                {
                    var thumbnail       = _parachuteThumbnails[i];
                    thumbnail.OnSelect -= selectChute;
                    thumbnail.OnDelete -= deleteChute;
                    thumbnail.OnClone  -= cloneChute;
                    thumbnail.OnEdit   -= editChute;
                }

                _parachuteNameEditor.IsEditable = state.EditorState.IsEditing;
                _parachuteNameEditor.Text       = state.EditorState.Config.Name;

                for (int i = 0; i < _parachuteThumbnails.Length; i++)
                {
                    var thumbnail = _parachuteThumbnails[i];

                    if (i < state.AvailableParachutes.Count)
                    {
                        var parachute             = state.AvailableParachutes[i];
                        var isSelected            = state.EditorState.Config.Id == parachute.Id;
                        var parachuteControlState = new ParachuteThumbnail.ParachuteControlState(
                            parachute.Id,
                            parachute.Name,
                            isEditorAvailable: !isVrActive,
                            isSelected: state.EditorState.Config.Id == parachute.Id,
                            isEditable: parachute.IsEditable,
                            isDeletable: state.AvailableParachutes.Count > 1 && parachute.IsEditable,
                            isEditing: state.EditorState.IsEditing && isSelected);
                        thumbnail.SetState(parachuteControlState);
                        if (parachuteControlState.IsDeletable)
                        {
                            thumbnail.OnDelete += deleteChute;
                        }
                        if (parachuteControlState.IsEditable)
                        {
                            thumbnail.OnEdit += editChute;
                        }
                        thumbnail.OnSelect += selectChute;
                        thumbnail.OnClone  += cloneChute;
                        thumbnail.gameObject.SetActive(true);

                        if (parachuteControlState.IsSelected)
                        {
                            FirstObject = thumbnail.gameObject;
                        }
                    }
                    else
                    {
                        thumbnail.gameObject.SetActive(false);
                    }
                }
            });
        }