How to use SpriteFont in a generic class for C# MonoGame ?

I’m trying to create 2 classes for my UI system, i don’t want to know if a library in this style exist, i’m actualy in a training center, and i need to make a standalone project, it’s only a training purpose. So i Have a project build with MVC and State, everything is automatized and it’s working fine. I currently have 2 States (StateStartScreen and StateMainMenu), with every association to their respective Model, Controller, View.

So for my UI System, i have 2 classes Box<TEntity> : GUIBase and BoxContainer<TEntity> : Box<TEntity>, GUIBase only purpose is to set the game and state in the constructor, the purpose of those classes is to create a box with a Texture2D, a SpriteFont and other stuff later like Videos, effects. and in the case of BoxContainer, you can create multiple other instance of those classes and the position and size are set so element in BoxContainer are positionate with the main container size, so the base Box of BoxContainer. Those classes works fine for drawing a single Texture2D element, and the position of the element is good too but when there is more element it doesnt’ work for the next element and not at all for SpriteFont, it just don’t appear

    public enum VerticalAlignment { Top, Center, Bottom }
    public enum HorizontalAlignment { Left, Center, Right }
    public class Box<TEntity> : GUIBase
    {
        protected TEntity _boxContent;
        private string _boxPath;
        private string _text;

        protected Vector2 Size;
        protected Vector2 Position;
        private Vector2 _offset = Vector2.Zero;

        private HorizontalAlignment _horizontalAlignment;
        private VerticalAlignment _verticalAlignment;

        // CONSTRUCTOR FOR CONTENT ELEMENT
        public Box(GameBase game, State state, string boxPath, HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, VerticalAlignment verticalAlignment = VerticalAlignment.Top, Vector2 offset = default) : base(game, state)
        {
            _boxPath = boxPath;
            _horizontalAlignment = horizontalAlignment;
            _verticalAlignment = verticalAlignment;
            _offset = offset;
        }
        // OVERRIDE FOR SPRITEFONT TEXT
        public Box(GameBase game, State state, string boxPath, string text, HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, VerticalAlignment verticalAlignment = VerticalAlignment.Top, Vector2 offset = default) : this(game, state, boxPath, horizontalAlignment, verticalAlignment, offset)
        {
            if (typeof(TEntity).Equals(typeof(SpriteFont)))
            {
                _text = text;
            }
        }
        // LOAD THE CONTENT FOR ANY TYPES
        public override void LoadContent(ContentManager content)
        {
            if (typeof(TEntity).Equals(typeof(Texture2D)))
            {
                _boxContent = (TEntity)(object)content.Load<Texture2D>(_boxPath);
            }
            else if (typeof(TEntity).Equals(typeof(SpriteFont)))
            {
                _boxContent = (TEntity)(object)content.Load<SpriteFont>(_boxPath);
            }
            Size = new Vector2(GetContentWidth(), GetContentHeight());
            Position = CalculateBoxPosition(_horizontalAlignment, _verticalAlignment, BoxContainer<TEntity>.SizeContainer);
        }
        public override void Update(GameTime gameTime)
        {
        }
        // DRAW CONTENT FOR ANY TYPES
        public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            if (typeof(TEntity).Equals(typeof(Texture2D)))
            {
                spriteBatch.Draw((Texture2D)(object)_boxContent, Position, Color.White);
            }
            else if (_boxContent is SpriteFont spriteFont)
            {
                spriteBatch.DrawString(spriteFont, _text, Position, Color.White);
            }
        }
        // CALCULATE THE MAIN BOX POSITION FROM CONTAINER SIZE
        private Vector2 CalculateBoxPosition(HorizontalAlignment horizontal, VerticalAlignment vertical, Vector2 containerSize)
        {
            return new Vector2(CalculateHorizontal(horizontal, containerSize.X) + _offset.X, CalculateVertical(vertical, containerSize.Y) + _offset.Y);
        }
        private float CalculateHorizontal(HorizontalAlignment horizontal, float containerWidth)
        {
            return horizontal is HorizontalAlignment.Right ? containerWidth - Size.X : horizontal is HorizontalAlignment.Center ? (containerWidth - Size.X) / 2f : 0;
        }
        private float CalculateVertical(VerticalAlignment vertical, float containerHeight)
        {
            return vertical is VerticalAlignment.Bottom ? containerHeight - Size.Y : vertical is VerticalAlignment.Center ? (containerHeight - Size.Y) / 2f : 0;
        }
        // GET CURRENT CONTENT WIDTH
        private float GetContentWidth()
        {
            if (typeof(TEntity).Equals(typeof(Texture2D)))
            {
                return ((Texture2D)(object)_boxContent).Width;
            }
            if (typeof(TEntity).Equals(typeof(SpriteFont)))
            {
                SpriteFont spriteFont = (SpriteFont)(object)_boxContent;
                return spriteFont.MeasureString(_text).X; // Utilisez la largeur du texte mesuré avec le SpriteFont
            }
            return 0f;
        }
        // GET CURRENT CONTENT HEIGHT
        private float GetContentHeight()
        {
            if (typeof(TEntity).Equals(typeof(Texture2D)))
            {
                return ((Texture2D)(object)_boxContent).Height;
            }
            if (typeof(TEntity).Equals(typeof(SpriteFont)))
            {
                SpriteFont spriteFont = (SpriteFont)(object)_boxContent;
                return spriteFont.MeasureString(_text).Y; // Utilisez la hauteur du texte mesuré avec le SpriteFont
            }
            return 0f;
        }
    }
    public class BoxContainer<TEntity> : Box<TEntity>
    {
        public static Vector2 SizeContainer { get; private set; }

        private readonly ArrayList _elements;
        public BoxContainer(GameBase game, State state, string boxPath, HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, VerticalAlignment verticalAlignment = VerticalAlignment.Top, Vector2 offset = default) : base(game, state, boxPath, horizontalAlignment, verticalAlignment, offset)
        {
            _elements = new ArrayList();
        }

        public void AddElement(object element)
        {
            _elements.Add(element);
        }
        public void RemoveElement(object element)
        {
            _elements.Remove(element);
        }
        public override void LoadContent(ContentManager content)
        {
            base.LoadContent(content);
            SizeContainer = Size;

            foreach (var element in _elements)
            {
                if (element is Box<TEntity> box)
                {
                    box.LoadContent(content);
                }
                else if (element is BoxContainer<TEntity> container)
                {
                    container.LoadContent(content);
                }
            }
        }
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            foreach (var element in _elements)
            {
                if (element is Box<TEntity> box)
                {
                    box.Update(gameTime);
                }
                else if (element is BoxContainer<TEntity> container)
                {
                    container.Update(gameTime);
                }
            }
        }
        public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            base.Draw(gameTime, spriteBatch);
            foreach (var element in _elements)
            {
                if (element is Box<TEntity> box)
                {
                    box.Draw(gameTime, spriteBatch);
                }
                else if (element is BoxContainer <TEntity> container)
                {
                    container.Draw(gameTime, spriteBatch);
                }
            }
        }
    }

I don’t get any error when debugging, i try to draw a basic SpriteFont in my ViewStartScreen with the same .spritefont and it worked fine, so i presume the problem is in a if(typeof condition) or maybe in the setting of position and size, then i tried to put multiple items in a BoxContainer and it wont work fine, the playButton is well place but not the loadButton in the ViewMainMenu and for the SpriteFont, it only appear when i create a simple Box, here is some screen and the code of the 2 views

    public class ViewStartScreen : ViewBase
    {
        private BoxContainer<Texture2D> background;
        private Box<Texture2D> logo;
        private Box<SpriteFont> pressAny;
        private Box<SpriteFont> pressAny2;

        public ViewStartScreen(GameBase game, State currentState) : base(game, currentState) { }

        public override void LoadContent(ContentManager content)
        {
            background = new BoxContainer<Texture2D>(_game, _currentState, "image/main-background");
            logo = new Box<Texture2D>(_game, _currentState, "image/logo", HorizontalAlignment.Center, VerticalAlignment.Top, new Vector2(0, 100));
            pressAny = new Box<SpriteFont>(_game, _currentState, "text/PressAnyKey", "PRESS ANY DON'T WORK KEY", HorizontalAlignment.Center);
            pressAny2 = new Box<SpriteFont>(_game, _currentState, "text/PressAnyKey", "PRESS ANY NEARLY WORK KEY", HorizontalAlignment.Center, VerticalAlignment.Center);

            background.AddElement(logo);
            background.AddElement(pressAny);

            background.LoadContent(content);
            pressAny2.LoadContent(content);
        }
        public override void Update(GameTime gameTime)
        {
            background.Update(gameTime);
            pressAny2?.Update(gameTime);
        }
        public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            background.Draw(gameTime, spriteBatch);
            pressAny2.Draw(gameTime,spriteBatch);
        }
    }

Result ViewStartScreen :

The visible text in the top of the window is pressAny2, pressAny is not visible or out of window range

    public class ViewMainMenu : ViewBase
    {
        private BoxContainer<Texture2D> background;
        private BoxContainer<Texture2D> menuOverlay;
        private Box<Texture2D> menuIcon;
        private BoxContainer<Texture2D> playButton;
        private BoxContainer<Texture2D> loadButton;
        public ViewMainMenu(GameBase game, State currentState) : base(game, currentState) { }

        public override void LoadContent(ContentManager content)
        {
            background = new BoxContainer<Texture2D>(_game, _currentState, "image/main-background");
            menuOverlay = new BoxContainer<Texture2D>(_game, _currentState, "overlay/menu-overlay");
            menuIcon = new Box<Texture2D>(_game, _currentState, "image/logo", HorizontalAlignment.Center, VerticalAlignment.Top, new Vector2(0, 30));
            playButton = new BoxContainer<Texture2D>(_game, _currentState, "image/active-button", HorizontalAlignment.Center, VerticalAlignment.Bottom, new Vector2(0,-200));
            loadButton = new BoxContainer<Texture2D>(_game, _currentState, "image/active-button", HorizontalAlignment.Center, VerticalAlignment.Bottom, new Vector2(0,0));

            menuOverlay.AddElement(menuIcon);
            menuOverlay.AddElement(playButton);
            menuOverlay.AddElement(loadButton);
            background.AddElement(menuOverlay);

            background.LoadContent(content);

        }
        public override void Update(GameTime gameTime)
        {
            background.Update(gameTime);
        }
        public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            background.Draw(gameTime, spriteBatch);
        }
    }

Result ViewMainmenu :

And that is what i want to do with those classes :

I’m still a junior developer in training so it’s possible for me to make basic error but from what i can see and my experience i didn’t find anything. Thank you for your time.

PS : Sorry for my bad english, im not native, if you didn’t understand something don’t hesitate to ask me

hi

Did you try to put a break point inside to check if it is being called?

else if (_boxContent is SpriteFont spriteFont)
            {
                spriteBatch.DrawString(spriteFont, _text, Position, Color.White);  //<- break point here
            }

the other thing that may be happening is that the text is rendered below the image, check if the draw call happens.

Thanks for the reply ! I’ve just finish to remade my Box class, now i can align every type of Box perfectly, in any part of the screen, but for the spritefont it simply not called, so i reread my code and i maybe found the issue, when i set the BoxContainer for the SpriteFont, i set a BoxContainer : Texture2D and then i add a Box : SpriteFont in it and here is my actual code in BoxContainer

public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            base.Draw(gameTime, spriteBatch);
            foreach (var element in _elements)
            {
                if (element is Box<TEntity> box)
                {
                    box.Draw(gameTime, spriteBatch);
                }
                else if (element is BoxContainer <TEntity> container)
                {
                    container.Draw(gameTime, spriteBatch);
                }
            }
        }

I tried to put object instead of TEntity but i can only see the Background with that

Not quite sure what is wrong with only this portion of code. If you can add more code I may be able to find the error.

In any case, I made a UI for my game, and instead of having a Box, I made a Button, the button has a property for text, so I can add text to the button, this I think makes things more clear than looking at the types of object to render. Having the text being part of the button makes it easier to align the text inside the bounding box of the button and alignments, having them separated as you are trying to do will make things more complicated and harder to debug.

No need mate, i’ve just solve the issue, it was linked to my generic TEntity when i AddElement, Load, Draw and Update i just needed to specify every combination type of Box instead of TEntity in my BoxContainer now everything works perfectly i just need to put everything in the background and every Box and BoxContainer are positioned with the ParentContainer even if its a Box SpriteFont in a BoxContainer Texture2D like this

    public class ViewMainMenu : ViewBase
    {
        private BoxContainer<Texture2D> background;
        private BoxContainer<Texture2D> menuOverlay;

        private Box<Texture2D> menuIcon;

        private BoxContainer<Texture2D> playButton;
        private Box<SpriteFont> playText;

        private BoxContainer<Texture2D> loadButton;
        private Box<SpriteFont> loadText;

        private BoxContainer<Texture2D> optionsButton;
        private Box<SpriteFont> optionsText;

        private BoxContainer<Texture2D> creditsButton;
        private Box<SpriteFont> creditsText;

        private BoxContainer<Texture2D> quitButton;
        private Box<SpriteFont> quitText;

        public ViewMainMenu(GameMain game, State currentState) : base(game, currentState) { }

        public override void LoadContent(ContentManager content)
        {
            background = new BoxContainer<Texture2D>(_game, _currentState, "image/main-background");
            menuOverlay = new BoxContainer<Texture2D>(_game, _currentState, "overlay/menu-overlay");

            menuIcon = new Box<Texture2D>(_game, _currentState, "image/logo", HorizontalAlignment.Center, VerticalAlignment.Top, 0.5f, new Vector2(0,50));

            playButton = new BoxContainer<Texture2D>(_game, _currentState, "image/active-button", HorizontalAlignment.Center, VerticalAlignment.Center, 1, new Vector2(0, -100));
            playText = new Box<SpriteFont>(_game, _currentState, "text/PressAnyKey", "PLAY", HorizontalAlignment.Center, VerticalAlignment.Center);

            loadButton = new BoxContainer<Texture2D>(_game, _currentState, "image/active-button", HorizontalAlignment.Center, VerticalAlignment.Center);
            loadText = new Box<SpriteFont>(_game, _currentState, "text/PressAnyKey", "LOAD", HorizontalAlignment.Center, VerticalAlignment.Center);

            optionsButton = new BoxContainer<Texture2D>(_game, _currentState, "image/active-button", HorizontalAlignment.Center, VerticalAlignment.Center, 1, new Vector2(0, 100));
            optionsText = new Box<SpriteFont>(_game, _currentState, "text/PressAnyKey", "OPTIONS", HorizontalAlignment.Center, VerticalAlignment.Center);

            creditsButton = new BoxContainer<Texture2D>(_game, _currentState, "image/active-button", HorizontalAlignment.Center, VerticalAlignment.Center, 1, new Vector2(0, 200));
            creditsText = new Box<SpriteFont>(_game, _currentState, "text/PressAnyKey", "CREDITS", HorizontalAlignment.Center, VerticalAlignment.Center);

            quitButton = new BoxContainer<Texture2D>(_game, _currentState, "image/active-button", HorizontalAlignment.Center, VerticalAlignment.Bottom, 1, new Vector2(0, -100));
            quitText = new Box<SpriteFont>(_game, _currentState, "text/PressAnyKey", "QUIT", HorizontalAlignment.Center, VerticalAlignment.Center);

            playButton.AddElement(playText);
            loadButton.AddElement(loadText);
            optionsButton.AddElement(optionsText);
            creditsButton.AddElement(creditsText);
            quitButton.AddElement(quitText);

            menuOverlay.AddElement(menuIcon);

            menuOverlay.AddElement(playButton);
            menuOverlay.AddElement(loadButton);
            menuOverlay.AddElement(optionsButton);
            menuOverlay.AddElement(creditsButton);
            menuOverlay.AddElement(quitButton);

            background.AddElement(menuOverlay);

            background.LoadContent(content);

        }
        public override void Update(GameTime gameTime)
        {
            background.Update(gameTime);
        }
        public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            background.Draw(gameTime, spriteBatch);
        }
    }

And here is the result :

And if i juste change this line like this

menuOverlay = new BoxContainer<Texture2D>(_game, _currentState, "overlay/menu-overlay", HorizontalAlignment.Right);

Here is the result :

1 Like

good! glad to hear you solved the issue