Displaying to user what key to press

What is the general approach for when the players needs to press a key to do something?
The player can go to the game controls settings to see what buttons do.
For console this is usually displayed on an image of a controller.

For PC the player can assign a keyboard, mouse, and or controller key to perform an action.
So there could be 3 different possible inputs to perform an action.

Should this be shown in-game?

Or is it acceptable to expect the player to go to the settings to see what the possible actions are and the corresponding buttons?

If itā€™s something like walking up to an object and pressing an input to interact with it, usually a small icon of the required button would appear over/in front of the object when the player gets within range to interact with it.

1 Like

Users love having the option to change settings, but they hate looking at the settings page to figure out what the controls are. These days, users definitely expect to have the game gently teach them how to play during the first level. Not an obvious tutorial level, though.

2 Likes

We created a dynamic tutorial system that reads the user configured settings and display correct texts, it could be extended with images too.

2 Likes

Avoiding any tutorial discussion, I lean toward teaching. If there are minor actions that arenā€™t particularly necessary but are still cool, I sometimes like discovering those in the controls menu, but only if I know most of the controls already. Thereā€™s no reason it has to be like this though.

If a game is too complex to be showing button tips all the time, I like when a training mode is included.

I think it depends on who your target market is, how obscure the action is, and how established your control scheme is across other games.

For example, in many modern FPS games they expect the player to either know or look up the basic movement and fire controls, but for lesser used actions such as taking control of a fixed turret, getting in a vehicle, or opening a parachute youā€™ll typically see the key/button displayed when the action is available.

Games with more unique controls may have all of their controls taught to the player in a tutorial, as well as games targeting a younger audience. On the other end, more hard core games, like pretty much any grand strategy game from Paradox, just expects you to learn from friends or learn the interface from losing the game several times over your first 30 hours with it.

1 Like

I agree. Itā€™s hard seeing a ā€˜one size fits allā€™ solution here. Plus there is an element of personal preference. My Son hates games that force you though a learning session - he just wants to get on and play regardless.

For me, I would be happy with a hint icon that (a) is not mandatory, i.e. does not block the game running and (b) appears only the first time this hint type is encountered. As long as this is backed up by either a tutorial demo mode or an instructions page, I think that could be sufficient. But again, that would depend on the overall complexity of the game in question.

But for complex PC games, I would imagine that the availability somewhere of a keyboard controls sheet would seem a minimum requirement.

1 Like

I think it depends on how complicated your game is. If you only have one universal ā€œuseā€ button, I would go for forcing them to figure it out once, then they should know from then on. DOOM is a good example. In E1M1, you can not get out of the first room until you figure out that the spacebar opens the door. The door is very obvious, so youā€™ll go right to it, but there is no prompt so youā€™ll either have to figure it out or look it up. This cements it in the mind so youā€™ll never forget later.

However, if you have a game with lots of buttons doing different things, Iā€™d want a prompt. Halo has example, has a bunch of buttons: flashlight, grenade, reloadā€¦ so I find it nice to see the ā€œPress E to open door.ā€

How is something like that possible with the default input manager? I never could find that in the api. Or do you need a third party solution like rewired for that?

We use SteamVR it does not use any unity specific stuff at all, on top of that we have our own abstraction for Rift and Vive and in the future other hardware as well.

        protected abstract HashSet<ButtonInput> GetMappableButtons();

        public Button? CheckForPlayerMappableButtonPress(NVRHand[] hands)
        {
            var possibleButtons = GetMappableButtons();
            foreach (var hand in hands)
            {
                foreach (var input in possibleButtons)
                {
                    if (VirtualButtons.Contains((int)input.Button))
                    {
                        if (CheckForVirtualButton(hand, input)) return input.Button;
                        continue;
                    }

                    if (CheckForSteamVRButton(hand, input)) return input.Button;
                }
            }

            return null;
        }

GetMappableButtons is a abstract method implemented by Vive and Rift subclasses it returns the possible buttons that the user can map, for Rift it looks like this

        private static readonly HashSet<ButtonInput> mappableButtons = new HashSet<ButtonInput>(new[] { Button.A, Button.ApplicationMenu, Button.VirtualButtonCenter, Button.VirtualButtonUp, Button.VirtualButtonDown }.Select(btn => new ButtonInput{ Button = btn, Action = ButtonAction.PressDown}));

        protected override HashSet<ButtonInput> GetMappableButtons()
        {
            return mappableButtons;
        }

We also define default configs, here for rift

        protected override void ApplyDefaultCommandMap(CommandMap commandMap)
        {
            commandMap
                .Add(Command.BoltRelease, Button.VirtualButtonUp.PressDown())
                .Add(Command.MagRelease, Button.VirtualButtonDown.PressDown())
                .Add(Command.ChangeFireMode, Button.VirtualButtonCenter.PressDown())
                .Add(Command.Calibrate, Button.Trigger.PressDown())
                .Add(Command.GripItem, Button.Grip.PressDown())
                .Add(Command.GripWeapon, Button.Grip.PressDown())
                .Add(Command.GripFrontGrip, Button.Grip.PressDown())
                .Add(Command.GripInteractable, Button.Trigger.PressDown())
                .Add(Command.DropItem, Button.Grip.PressDown())
                .Add(Command.DropWeapon, Button.Grip.PressDown())
                .Add(Command.DropFrontGrip, Button.Grip.PressDown())
                .Add(Command.DropInteractable, Button.Grip.PressDown())
                .Add(Command.PullGrenadeLever, Button.Touchpad.PressDown())
                .Add(Command.ReleaseAction, Button.Touchpad.IsPressed())
                .Add(Command.SameHandMagRelease, Button.Touchpad.PressDown())
                .Add(Command.ShowMenu, Button.ApplicationMenu.PressDown())
                .Add(Command.DetachAttachment, Button.Grip.PressDown())
                .Add(Command.StartPhysicalHand, Button.Trigger.PressDown())
                .Add(Command.StopPhysicalHand, Button.Trigger.PressUp())
                .Add(Command.Sprint, Button.Touchpad.IsPressed())
                .Add(Command.ToggleAttachment, Button.Touchpad.PressUp())
                .Add(Command.ReleaseDrum, Button.VirtualButtonUp.PressDown())
                .Add(Command.CockHammer, Button.VirtualButtonDown.PressDown())
                .Add(Command.UncockHammer, Button.VirtualButtonDown.IsPressed());
        }

Lastly we have a table over mappable commands

private static readonly Command[] mappableCommands = new[] { Command.BoltRelease, Command.ChangeFireMode, Command.MagRelease, Command.ReleaseAction };

Now the UI can query the underlying domain and wait for a button to be pressed, we also have something we call commandGrouping, commands that cant share the same button. If the user select the same button the config turns read and he cant save the config.

3491835--278075--upload_2018-5-10_11-34-20.jpg
edit: And offcourse the UI isnt hardcoded, the list of command mappings is databound

        protected virtual void Start()
        {
            EventBus.Instance.Subscribe(this);

            var template = GetComponentInChildren<CommandMapping>();
            template.gameObject.SetActive(false);
           
            var invalidColor = GetComponentInParent<MainMenu>().InvalidColor;

            maps = mappableCommands
                .Select(cmd => Instantiate(template).Init(cmd, invalidColor))
                .ToDictionary(cm => cm.Command, cm => cm);

            foreach (var item in maps.Values)
                item.transform.SetParent(transform, false);
        }

The missing component now is UI friendly captions both for commands and buttons, for commands we just ā€œWordifyā€ the enums so Command.BoltRelease becomes Bolt release, you can also completely override the caption if you add the command to a Command > string dictionary. For the buttons we only have a button > string dictionary.

        private static readonly Dictionary<Button, string> captions = new Dictionary<Button, string>
        {
            { Button.VirtualButtonUp, "Stick up" },
            { Button.VirtualButtonRight, "Stick right" },
            { Button.VirtualButtonDown, "Stick down" },
            { Button.VirtualButtonLeft, "Stick left" },
            { Button.Touchpad, "Stick" },
            { Button.VirtualButtonCenter, "Stick" },
            { Button.A, "A" },
            { Button.ApplicationMenu, "B" },
        };

So there sadly aint no magic going on that we can call a singel method in some Unity API :smile:

Oh then from the Tutorial we do something like this

    [CreateAssetMenu(menuName = "Tutorial/ReleaseBoltStep")]
    public class ReleaseBoltStep : TutorialStep
    {
        public bool HasBoltRelease;

        public override IEnumerator Execute()
        {
            var firearm = Get<Firearm>();

            ShowPopup(firearm.Slide.transform, firearm.Slide.Ready || !HasBoltRelease ? string.Format("Grab the slide by {0}, draw the slide and release.", GetInteractionCaption(firearm.Slide.ItemType)) : string.Format("Release the bolt by pressing {0}", GetCommandCaption(Command.BoltRelease)) );

            while (firearm.Bullet == null)
                yield return null;
        }

    }

Thanks a lot for the detailed response! I was hoping there was something like Input.GetPrimaryBindingForButton("Jump") in the default API and Iā€™m just too blind to find it. As far as I can tell there is no way to do something like that without also rolling your own custom key rebinding solution like you did.

For a complex game you might need it anyway? Just the fact that the built int system using Static methods and strings turns me off :smile:

Just found this free on the asset store. Allows you to rebind keys at runtime so Iā€™m assuming you can also display the button?

Iā€™m gonna try it out tonight.