This site uses cookies to ensure you get the best experience. More info
Got it!

Handling mouse Input



Mouse Input

Until now, the Praedium’s engine supported only keyboard input and that is absolutely enough for a typical text-based game.
But some of them actually go as far as to implement some mouse input handling as an alternative or part of the actual game controls.

As I’ve stated already in the first creative highlight, some mouse events, such as hovering, would be very useful to easily show the player some hints about different objects.

Also, nowadays I was wondering about player controls to also support mouse input or even restrict them to mouse input. This seems like a better approach to me, due to keyboard controls feeling a little bit too awkward in this kind of game.

Another point for this change is that I have an idea of changing the initial vision of typical Harvest Moon styled game. Typically, you control a single character and I have an idea to introduce multiple character managing - for example a whole family on a farm.

This changes the gameplay a lot and introduces a somewhat more fresh idea.

All in all, the mouse input handling seemed like a good issue to tackle, so let’s get into it!

Integrating mouse events

Just as before, I’d like to keep away from specific frameworks so the engine becomes more flexible. That is why we need to define an enumeration type that will represent different mouse buttons.
For now we will stick with left, middle and right mouse buttons.

public enum MouseButton
{
    None,
    Left,
    Middle,
    Right
}

Having defined that, we follow the pattern and define a MouseInfo structure containing all important information received from given mouse event, be it a button press, release or simply mouse move.

public struct MouseInfo
{
    public MouseButton Button;
    public MouseEventType Type;
    public Vector2D Position;
    public bool Down;
}

As you can see the structure has information about mouse position, which mouse button (if such is present) the event is about and whether that button is down or not. There is also an enumeration type introduced that specifies which type of event we are dealing with.

public enum MouseEventType
{
    Move,
    ButtonUp,
    ButtonDown
}

In case of keyboard events we only had to check if the Down flag was true or false and we knew everything. But in this case we do not know if given MouseInfo instance came from a mouse move event or a simple button event.

This structure is used in the custom MouseInfoEventArgs that will be passed inside the engine on any of the events listed above.

public class MouseInfoEventArgs : EventArgs
{        
    public MouseInfoEventArgs(MouseInfo info)
    {
        MouseInfo = info;
    }

    public MouseInfo MouseInfo
    {
        get;
        private set;
    }
}

Now, the Game class needs to define these events and event handlers to which game objects could also react.

public event EventHandler<MouseInfoEventArgs> MouseUp;
public event EventHandler<MouseInfoEventArgs> MouseDown;
public event EventHandler<MouseInfoEventArgs> MouseMove;

public void HandleMouse(MouseInfo info)
{
    if(info.Type == MouseEventType.Move)
    {
        UI.ApplyMousePosition(info.Position);
        OnMouseMove(info);
    }
    else
    {
        UI.ApplyMouseInfo(info);

        if (info.Down)
        {
            OnMouseDown(info);
        }
        else
        {
            OnMouseUp(info);
        }
    }
}

The game calls the UI instance and tells it to apply the received mouse information. In case of mouse move events, we only apply the mouse position just to be safe.

The UI contains a <MouseButton, bool> dictionary to keep track of which button is pressed at current moment and which is not. It also keeps track of current mouse position.

Having all this set up and properly implemented within the game’s form we may move along and show some example on how to use this.

Mouse selection

The example in question will be an implementation of simple rectangular mouse selection commonly used within strategy games to select multiple units.

Whenever the player presses the left mouse button and then moves the mouse while holding the button down, a rectangle is drawn to show the selection he is creating. When the mouse button is released, the selection is created, and units selected (if any were within the selection rectangle).

Firstly, let’s define a new game object, one that will handle these mouse controls.

This object will be keeping track the selection’s position - the starting and ending point, and based on that configure the renderer on the fly to render a simple rectangle.

While BoxRenderer has not been yet introduced by me in the details, to put it simply - it does what you expect it to. Based on given parameters it renders a box in the terminal using some special characters representing lines.

[RequireComponent(typeof(MouseSelectionHandler))]
[RequireComponent(typeof(BoxRenderer))]
public class MouseController : GameObject
{
    public bool ProcessingSelection
    {
        get;
        private set;
    }

    private Vector2D startPosition;
    private Vector2D endPosition;

    private BoxRenderer renderer;

    protected override void OnStart()
    {
        startPosition = endPosition = Vector2D.Zero;
        ProcessingSelection = false;

        renderer = GetComponentOfType(typeof(BoxRenderer)) as BoxRenderer;

        renderer.Size = Vector2D.Zero;
        renderer.Units = Units.Viewport;
    }

    public void StartSelection(Vector2D startPos)
    {
        startPosition = startPos;
        endPosition = startPos;
                    
        ProcessingSelection = true;
    }

    public void ChangeSelection(Vector2D targetPos)
    {
        endPosition = targetPos;

        renderer.Position = new Vector2D(Math.Min(startPosition.X, endPosition.X), Math.Min(startPosition.Y, endPosition.Y));
        renderer.Size = new Vector2D(Math.Abs(startPosition.X - endPosition.X) + 1, Math.Abs(startPosition.Y - endPosition.Y) + 1);
    }

    public void EndSelection(Vector2D endPos)
    {
        endPosition = endPos;
        renderer.Size = Vector2D.Zero;

        ProcessingSelection = false;
    }
}

Interacting with the MouseController is made by calling StartSelection, ChangeSelection and EndSelection methods.

These methods will be called from a new MouseSelectionHandler component that will be taking use of the new event handlers.

public class MouseSelectionHandler : Component
{
    public MouseController Controller
    {
        get;
        private set;
    }

    private Vector2D lastPosition;

    protected override void OnStart()
    {
        Controller = (MouseController)GameObject;

        Game.MouseDown += Game_MouseDown;
        Game.MouseUp += Game_MouseUp;
        Game.MouseMove += Game_MouseMove;
    }

    void Game_MouseMove(object sender, MouseInfoEventArgs e)
    {
        if(Controller.ProcessingSelection && lastPosition != e.MouseInfo.Position)
        {
            lastPosition = e.MouseInfo.Position;
            Controller.ChangeSelection(lastPosition);
        }
    }

    void Game_MouseUp(object sender, MouseInfoEventArgs e)
    {
        if (e.MouseInfo.Button == MouseButton.Left && Controller.ProcessingSelection)
        {
            Controller.EndSelection(e.MouseInfo.Position);
        }
    }

    void Game_MouseDown(object sender, MouseInfoEventArgs e)
    {
        if (e.MouseInfo.Button == MouseButton.Left && !Controller.ProcessingSelection)
        {
            Controller.StartSelection(e.MouseInfo.Position);
        }
    }

    public override void Update()
    { }
}

The code is pretty much straightforward. In the OnStart setup callback, we connect the proper methods to event handlers.

Whenever the game registers a mouse movement, we check if we are currently processing a selection and the mouse position parsed to terminal units changed. If that is true, we call the controller and tell it to change the selection.

On left mouse button release, we end the selection and this would be the moment when we actually would look for some units and mark them as selected.

On left mouse button press, we check if controller isn’t already processing a selection and if not, we tell it to start processing, starting from the event’s mouse position.

Clean and straight to the point! And now behold the results:

Mouse selection example Mouse selection example.

Wrapping up

Today I have walked you through the implementation of mouse input handling within the Praedium’s engine and it’s usage in practice within the Praedium’s game code.

I hope you find the code to be straightforward and clean as that is also very important.
Again, the engine is being kept framework-agnostic for flexibility.

Having implemented the creation of the selection rectangle and rendering of it, I can continue to implement some actual controls.
Of course the point and click controls will need some path finding which I’m going to implement next up.

That’s it for today’s post! See you next time!