One of the very first things I created was a basic menu. The game is built on game states which represent scenes, like the main menu, the settings menu, the actual gameplay state, victory screen and so on. For every part of the game that used a lot of different components compared to a previous part, I would create a new state. This concept is actually still in use and architecturally speaking, I consider it a good choice.
Until I already created button and panel classes, I didn’t think of using a library for the UI elements. After a quick search online, I still decided to implement all the other UI componemts by myself as well. The toughest so far was the slider/scrollbar.
Scroll bar
In the end, I implemented a way to lock the slider to the position of the mouse cursor.
public float SliderPositionY { get; set; }
In the update loop, the scrollbar updates its position when the user holds down the slider, as in IsSliderLocked:
public override void Update(GameTime gameTime) { if (IsSliderLocked) { int mousePositionY = Mouse.GetState().Y - 14; SliderPositionY = MathHelper.Clamp(mousePositionY, Position.Y, Position.Y + Size.Y - 14); } }
The slider’s vertical position is defined by SliderPositionY property. The tricky part was the detection of when the user is holding down the left mouse button and is dragging the slider at the same time. Note that the slider should still adjust its position to the mouse cursor even when the mouse isn’t on top of the slider anymore (physical rectangular intersecting).
– the scroll bar in the settings menu –
It sounds easy at first, but you have to keep several things in mind as you can see. However, I still managed to get them working properly.
To adjust values when the scrollbar is moved either by the user or by the application (for example restoring a specific scroll position), I created two methods. The Value property stores the current value of the scroll bar where the minimum (Value = 0) is at the top and the maximum is at the bottom (Value = the height of the scrollbar).
public int Value { get; set; }
These methods are used to adjust either the value or the position, which ever should be changed.
private void SetValueFromPosition() { double percentalValue = Math.Round((SliderPositionY - Position.Y) / (Size.Y - 14), 2); this.Value = (int)Math.Floor(percentalValue * (MaxValue - MinValue) + MinValue); } private void SetPositionFromValue() { SliderPositionY = (PercentalValue * Size.Y) + Position.Y; }
As displayed in the Update() method, the slider position has an offset of 14 pixels. This, of course, has to be considered when calculating the slider’s Value based on the vertical position.
Conclusion
This implementation may not be the best thing since sliced bread, but it certainly does its job. Also, as I see it, it most definitely doesn’t hurt to re-invent the wheel from time to time, as you gain more experience and learn how the wheel was built in the first place
With that said, I recommend creating your own UI components such as buttons, labels, text boxes, panels and all the others if you have the time and will for it.
Thanks for the read, I will be posting updates about the game regularly. Not every blog entry will be technical. Watch out for the “Technical” tag if you enjoyed this though.
Buttons
Naturally, certain components require a less complex structure or implementation. The Button for example is straight forward. Check out the Draw() method.
public override void Draw(SpriteBatch spriteBatch, GameTime gameTime) { if (!CanDraw()) return; Vector2 textSize = Font.MeasureString(Text); if (!Enabled) { spriteBatch.DrawStretched(ClickedTexture, new Vector2((int) Position.X, (int) Position.Y + 4), new Point(Size.X, Size.Y - 4), BackgroundColor, CornerSize, Rotation, SpriteEffects); spriteBatch.DrawString(Font, Text, Position + new Vector2((Size.X - textSize.X) / 2, (Size.Y - textSize.Y) / 2), Color.LightGray); return; } if (IsHovered) { if (IsClicked) { spriteBatch.DrawStretched(ClickedTexture, new Vector2((int)Position.X, (int)Position.Y + 4), new Point(Size.X, Size.Y - 4), BackgroundColor, CornerSize, Rotation, SpriteEffects); spriteBatch.DrawString(Font, Text, Position + new Vector2((Size.X - textSize.X) / 2, (Size.Y - textSize.Y) / 2 + 4), IsHovered ? HoveredFontColor : FontColor); } else { spriteBatch.DrawStretched(HoveredTexture, Position, Size, BackgroundColor, CornerSize, Rotation, SpriteEffects); spriteBatch.DrawString(Font, Text, Position + new Vector2((Size.X - textSize.X) / 2, (Size.Y - textSize.Y) / 2), IsHovered ? HoveredFontColor : FontColor); } } else { spriteBatch.DrawStretched(Texture, Position, Size, BackgroundColor, CornerSize, Rotation, SpriteEffects); spriteBatch.DrawString(Font, Text, Position + new Vector2((Size.X - textSize.X) / 2, (Size.Y - textSize.Y) / 2), FontColor); } }
It’s easy to recognize similarities between this class and the default functionalities you would except a button to have. The drawing depends on whether the button is hovered, clicked or active/focused (hovered and clicked) or none of the above.
As before, I often fiddle around with the exact position of textures by adding a few pixels here and there. To my knowledge, those magical numbers should always be avoided. Though I can’t deny that it sometimes makes things much easier by just adding some pixels instead of manipulating the texture dimensions for example.