1
votes

In case it matters, I am using XNA 3.1, although if 4.0 would help greatly I may consider moving.

I am trying to draw a textbox in my game. I'd like to be able to have the text displayed only inside the textbox (so that if the string is longer, at the edge of the textbox only perhaps a partial character is shown).

The problem I have is that either the text isn't clipped, in which case it just carries on outside the bounds of the textbox, or using the suggestion described here -- http://social.msdn.microsoft.com/forums/en-US/xnagamestudioexpress/thread/9c395c84-2257-4103-b75c-d9378425cc09 -- I found that if I use the code:

spriteBatch.GraphicsDevice.RenderState.ScissorTestEnable = true;
spriteBatch.GraphicsDevice.ScissorRectangle = myTextBox.GetRectangle();

I can get it clipping inside the text box exactly as I would like but unfortunately, it doesn't show anything else within the game at all! The post that suggests using the ScissorRectangle says it needs to be done on each SpriteBatch. This led me to think I need a new SpriteBatch just for this bit. I tried creating a new spriteBatch using the existing one's graphics device (new SpriteBatch(spriteBath.GraphicsDevice)), and then modifying that, but since it's a reference it had the same problem, so I felt I need to either clone the SpriteBatch or GraphicsDevice objects somehow, or create a new GraphicsDevice (I wasn't sure what to put in the latter's parameters). Clone methods aren't available, and something tells me I'm complicating this somewhat, so that's why I've put all this background in.

If I want to display a textbox with clipped text, is a new SpriteBatch with this ScissorRectangle the best way forward (it does seem to work well), and if so, how do I go about getting a new SpriteBatch so that I can just display my textbox clipped like this, but the rest of the game draws fine? I tried creating a new spritebatch at the same time as I create the main game's one, but because the GraphicsDevice had to be the same, I couldn't make it so one changed and and the other didn't.

Thanks very much. If I've missed any useful details please let me know -- at this point I've no idea what might be relevant.

4

4 Answers

3
votes

you don't need a new spritebatch...

maybe you are not disabling scissortest ...

to draw your clipped text you have to do something similar to this:

  spriteBatch.GraphicsDevice.RenderState.ScissorTestEnable = true;
  spriteBatch.GraphicsDevice.ScissorRectangle = myTextBox.GetRectangle();
  spriteBacth.Begin();
  spriteBatch.DrawString(...);       
  spriteBacth.End();
  spriteBatch.GraphicsDevice.RenderState.ScissorTestEnable = false;      

it can be done with stencil buffer too, but is a little more tricky...

this is my implementation test with stencil: http://www.youtube.com/watch?v=-xUMC7sPAx0

and this is my gui implementation with the scissor test: http://www.youtube.com/watch?v=3pakgHjnScs

3
votes

SpriteBatch does not "own" that particular GraphicsDevice. That GraphicsDevice is shared by all the graphics resources in your game (that's why it's the device). Generally speaking there should only ever be one instance of GraphicsDevice.

So when you are turning on scissor testing, you are turning it on for all of your SpriteBatch objects.

The answer is pretty simple: Turn it back off again!

You are right that you need to do this in a separate sprite batch - it just doesn't need to be a separate SpriteBatch object! Just a separate Begin()/End() block (this is a "batch").

Changing the render states that SpriteBatch uses in XNA pre-4.0 is kind of tricky. This article lists which ones get modified by SpriteBatch. Note that ScissorTestEnable is not one of them, so it should be set outside of a batch.

This is because SpriteBatch can queue up drawing right up until End is called - so state changes that you make on the GraphicsDevice and calls to Draw within a single batch can happen in a different order than you expect.

(The states that SpriteBatch does manage need to be modified differently in XNA pre-4.0. It's a bit ugly. You have to Begin() with SpriteSortMode.Immediate and then make your changes.)

In XNA 4.0 you would pass a RasterizerState with ScissorTestEnable set to the value you desired to the Begin() call for each batch. It's a lot easier to make sense of.

1
votes

There's a concept called "Sprite batching" and the "spritebatch" object. While the two are closely related, I think you misunderstood the concept for the object.

Specifically, when you spritebatch.Begin and until you spritebatch.End you create (at least) 1 sprite batch. Many settings for this batch can be set to different values than any other batch you might create.

If you want to create more batches, you just need more spritebatch.Begin ... .End sequences.

In this case, since the clipping is a sprite batch effect, you have to send everything that's to be clipped in one or more batches that are constructed (.Begin) and terminated (.End) - and send those separately from any other batches that you don't want to be clipped.

In short, you'll most likely:

  • Render your world (one sprite batch)
  • Render your HUD (another or even possibly the same batch)
  • Set the clipping mask for use with ...
  • Render the clipped area (the text)
  • Unset the clipping mask (because otherwise it's still set the next time you call "Draw")

Here's hoping this help your understanding of the advice that was given to you.

0
votes

You might consider using a RenderTarget2D to render the text into a Texture2D, then you could render only a part of this texture using

SpriteBatch.Draw(Texture2D texture, Rectangle destination, Rectangle source, Color color)

For more information on how to use RenderTarget2D check out this link: http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.rendertarget%28v=xnagamestudio.31%29.aspx

By the way, it's totally worth switching to XNA 4.0 - there are only slight syntax changes to some methods, but the overall improvement is great.