Speech Bubble Sample
So I allowed myself to get sidetracked to throw together a sample to answer a question in the App Hub forums. Since I love RPG games, both playing and developing, this will probably be useful to me sometime in the future so that’s how I’m justifying it.
The sample, which you can get from here, shows a quick semi-dynamic speech bubble with a couple of options. The class is less than 150 lines, allows however many lines of text you want, shows a graphic to let the user know if there’s more text that the character has to provide, and shows a pointer to the character that can be placed on either the left or right side of the bottom of the bubble. It uses more of my fantastic programmer art, but should work with whatever graphics you replace it with, assuming you don’t deviate from how I’ve set up the graphics. I could have used just one corner graphic and rotated for the other 3 sides, but I was lazy. ![]()
Here’s the class in all its glory:
public enum PointerType { None, Left, Right } public class SpeechBubble { private int _width; private Vector2 _location; private string[] _text; private SpriteFont _font; //bubble textures private Texture2D _bottomBorder; private Texture2D _interior; private Texture2D _leftBorder; private Texture2D _leftBottomCorner; private Texture2D _leftTopCorner; private Texture2D _rightBorder; private Texture2D _rightBottomCorner; private Texture2D _rightTopCorner; private Texture2D _topBorder; private bool _more; private Texture2D _moreGraphic; private PointerType _pointerType; private Texture2D _pointer; public SpeechBubble(ContentManager content, int width, Vector2 location, string[] text, bool more = false, PointerType pointerType = PointerType.None) { _font = content.Load<SpriteFont>("font"); _bottomBorder = content.Load<Texture2D>("bottomBorder"); _interior = content.Load<Texture2D>("interior"); _leftBorder = content.Load<Texture2D>("leftBorder"); _leftBottomCorner = content.Load<Texture2D>("leftBottomCorner"); _leftTopCorner = content.Load<Texture2D>("leftTopCorner"); _rightBorder = content.Load<Texture2D>("rightBorder"); _rightBottomCorner = content.Load<Texture2D>("rightBottomCorner"); _rightTopCorner = content.Load<Texture2D>("rightTopCorner"); _topBorder = content.Load<Texture2D>("topBorder"); _moreGraphic = content.Load<Texture2D>("more"); _pointer = content.Load<Texture2D>("pointer"); _location = location; _width = width; _text = text; _more = more; _pointerType = pointerType; } public void Draw(SpriteBatch sb) { //top sb.Draw(_leftTopCorner, _location, Color.White); sb.Draw(_topBorder, new Rectangle((int)_location.X + _leftTopCorner.Width, (int)_location.Y, _width - _leftTopCorner.Width * 2, _leftTopCorner.Height), Color.White); sb.Draw(_rightTopCorner, _location + new Vector2(_width - _rightTopCorner.Width, 0), Color.White); //lines for (int i = 0; i < _text.Length + (_more ? 1 : 0); i++) { sb.Draw(_leftBorder, new Vector2(_location.X, _location.Y + _leftTopCorner.Height + (i * _leftBorder.Height)), Color.White); sb.Draw(_interior, new Rectangle((int)_location.X + _leftBorder.Width, (int)_location.Y + _leftTopCorner.Height + (i * _leftBorder.Height), _width - _leftBorder.Width * 2, _leftBorder.Height), Color.White); sb.Draw(_rightBorder, new Vector2(_location.X + _width - _rightBorder.Width, _location.Y + _leftTopCorner.Height + (i * _leftBorder.Height)), Color.White); //leave space for more graphic if necessary if (i < _text.Length) sb.DrawString(_font, _text[i], new Vector2((int)_location.X + _leftBorder.Width, (int)_location.Y + _leftTopCorner.Height + (i * _leftBorder.Height)), Color.Black); } //bottom sb.Draw(_leftBottomCorner, _location + new Vector2(0, _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height), Color.White); switch(_pointerType) { case PointerType.Left: { sb.Draw(_pointer, _location + new Vector2(_leftBottomCorner.Width, _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height), Color.White); sb.Draw(_bottomBorder, new Rectangle((int)_location.X + _leftBorder.Width + _pointer.Width, (int)_location.Y + _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height, _width - _leftBorder.Width - _pointer.Width - _rightBorder.Width, _bottomBorder.Height), Color.White); break; } case PointerType.Right: { sb.Draw(_bottomBorder, new Rectangle((int)_location.X + _leftBorder.Width, (int)_location.Y + _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height, _width - _leftBorder.Width - _pointer.Width - _rightBorder.Width, _bottomBorder.Height), Color.White); sb.Draw(_pointer, _location + new Vector2(_width - _pointer.Width - _rightBorder.Width, _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height), Color.White); break; } case PointerType.None: { sb.Draw(_bottomBorder, new Rectangle((int)_location.X + _leftBorder.Width, (int)_location.Y + _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height, _width - _leftBorder.Width - _rightBorder.Width, _bottomBorder.Height), Color.White); break; } } sb.Draw(_rightBottomCorner, _location + new Vector2(_width - _rightBottomCorner.Width, _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height), Color.White); if (_more) sb.Draw(_moreGraphic, new Vector2(_location.X + _width - _rightBorder.Width - _moreGraphic.Width, _location.Y + _leftTopCorner.Height + (_text.Length * _leftBorder.Height)), Color.White); } }
Creating an instance is fairly straightforward:
SpeechBubble _bubble1; SpeechBubble _bubble2; SpeechBubble _bubble3; protected override void LoadContent() { ... _bubble1 = new SpeechBubble(Content, 150, new Vector2(50, 50), new string[] { "This is a test", "This is only a test", "More text follows..." }, true); _bubble2 = new SpeechBubble(Content, 150, new Vector2(250, 250), new string[] { "This is a test", "This is only a test", "Nothing here" },false, PointerType.Left); _bubble3 = new SpeechBubble(Content, 150, new Vector2(150, 450), new string[] { "Another test", "Still testing", "More follows..." }, true, PointerType.Right); }
Simply call the Draw method of the class in your own draw code:
protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); _bubble1.Draw(spriteBatch); _bubble2.Draw(spriteBatch); _bubble3.Draw(spriteBatch); spriteBatch.End(); base.Draw(gameTime); }
Feel free to let me know if I missed something that would be useful in the class.