How to clip a 2D sprite?

For my purposes scaling up a circle texture and displaying with 25% opacity works just fine for me wanting to show the range of a unit (the staircasing is fine for my purposes). BUT, I need to clip it so it doesn’t draw on the left (or the top) of the screen. Here’s a screen shot:


How do I clip this texture so it only draws on my map? The rectangle that describes the map is LeftMapOffset (for the X offset), TopMapOffset (for the Y offset), Map.width, Map.height).

If I can’t just clip the sprite I’ll have to reorder all the previous draws so that the red sprite is first.

Thanks for all the help!

Wrong question. Instead of clipping, you should just draw the circle under your UI.

Well, first I tried changing the z index for the red sprite but that does nothing.

If I can’t clip the sprite (easy solution) then I will have to reorder a whole lot of UI calls so that the red sprite is drawn first.

As you should’ve done from the beginning. :- )

It is possible to clip the sprites as you want but it’s just going to cause more problems down the line since you didn’t structure your game properly, so better do that now.

I do think your best bet is to handle your draw order correctly. In general you typically want UI to render last, while the circle itself is more likely to be the last thing drawn when your units draw. It’s worth a look to see if you can achieve this.

That said, it looks like you might have an alternative…

In SpriteBatch you can set a scissor rectangle, use that to keep your circle in the area you want. The scissor rect will clip anything drawn outside of it.

1 Like

This throws a runtime error:

Do you have a link to working ScissorsRectangle example code? My searches are turning up it doesn’t work with MonoGame (may be because of early versions).

And this is why you don’t give people what they ask - and rather what they actually need.

You might have to construct a new RazterizerState object and assign it instead of trying to modify a value on an existing one. The post I linked above was for XNA, MonoGame follows that API but has made a few changes.

That link was also from the first google hit I could find on the topic. You’ll have to explore this a bit I suspect. I would be interested in learning what you discover :slight_smile:

Ill write you a sample, its pretty simple to do, ill post a link in a bit :slight_smile:

Thanks. That’s very kind.

OK, it’s pretty simple, but you should be able to easily implement it in your code.

Scissor Rect off

Scissor Rect on

Underlying draw code looks like this:-

            Rectangle orgScissorRec = _spriteBatch.GraphicsDevice.ScissorRectangle;

            RasterizerState rasterizerState = new RasterizerState() { ScissorTestEnable = scissorRectOn };

            // Area I want to not be able to draw outside of
            Rectangle targetRect = new Rectangle(GraphicsDevice.Viewport.Width / 4, GraphicsDevice.Viewport.Height / 4, GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);

            // set it in the sprite batch
            _spriteBatch.GraphicsDevice.ScissorRectangle = targetRect;

            // Begin
            _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, rasterizerState);

            // Draw a red Box to show the target rectangle
            _spriteBatch.Draw(texture, targetRect, Color.Red);

            // Draw a box in the target rect..
            _spriteBatch.Draw(texture, new Rectangle(targetRect.X + 8, targetRect.Y + 8, 100,100), Color.Blue);

            // Draw a yellow recangle that over hangs the target rectangle, see how any part of id rawn out side of the target rect is culled.
            _spriteBatch.Draw(texture, new Rectangle(targetRect.Center.X, targetRect.Center.Y, (int)(targetRect.Width/1.5f), (int)(targetRect.Height / 1.5f)), Color.Yellow);

            _spriteBatch.End();

I have put it with my other samples, this is the only one in that repo using the latest MonoGame version, I really need to update them all really…

Anyway, you can find the code to the above here.

I hope this helps, happy learing :smiley:

2 Likes

Thanks for the code. So far I’m running into a compiler errors about spriteBatch.Start and End. Start has already been called so I can’t call Start again. But if call End and then Start and End it complains, too. Still working on it.

Remember, they are batches. Draw your main ui in one batch (begin/end) draw your scissor rect and circle in another.

Not sure i can help you more than that :slight_smile:

Ezra,

I would tend to agree with the other comments that it is probably worth just reordering your draw calls to draw the circle under the rest of your UI.

I wasn’t aware of scissor rectangle, that looks like a useful feature.

Another option would be to use the Draw overload that allows drawing only a portion of a sprite by changing the sourceRectangle (see image below of overload arguments). You would have to do some math to make everything lineup.

-Brett

Strongly in agreement with making sure you set up your draws so they are in the correct order. This circle should be drawn at the same time you draw the units, just after they are drawn so the circle is over top. Your UI should be the last thing you draw.

If you want to do a quick and dirty test, you can just create another SpriteBatch object (ideally not inside your draw loop) and just do a begin/end inside there. As long as you’re using immediate mode it should be fine. Using multiple, nested begin/end calls isn’t ideal, but it’s probably not the end of the world.

Test it, get it working, fix your drawing code :wink:

In LoadContent…

_mainBatch = new SpriteBatch(this.GraphicsDevice);
_testBatch = new SpriteBatch(this.GraphicsDevice);

_texture = new Texture2D(this.GraphicsDevice, 1, 1);
_texture.SetData<Color>(new Color[] { Color.White });

In Draw…

_mainBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
_mainBatch.Draw(_texture, new Rectangle(40, 40, 400, 300), Color.Orange);
_mainBatch.Draw(_texture, new Rectangle(90, 90, 600, 100), Color.MediumPurple);

RasterizerState rs = new RasterizerState() { ScissorTestEnable = true };
_testBatch.GraphicsDevice.ScissorRectangle = new Rectangle(40, 40, 400, 300);

_testBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, rasterizerState: rs);
_testBatch.Draw(_texture, new Rectangle(100, 100, 600, 100), Color.Purple);
_testBatch.End();

_mainBatch.End();

If you actually want him to fix his drawing code, you should not have given him the easier, wrong solution. People follow the path of least resistance, especially if they don’t fully understand what they are doing.

Hi guys. First, thanks for all the help / suggestions. I’m not kidding. I really appreciate it because I don’t know MonoGame at all.

Anyway, here’s my solution to the problem (because I couldn’t get any of the others to work):


I calculate the percentage of the circle that’s off the map and then clip that percentage from the Source rectangle in the Draw() call. You also have to offset the center of the circle by the amount that you’re offsetting the source crop, too. Crude but effective.

I’m in what I call the ‘Slog’ part of development on this game. It’s about to transition to Death March (the last few months). There’s strong interest in this game and I’m desperate to get it on Steam 1st quarter 2023. Thanks for all the help!

3 Likes

It’s not my place here to force my views on others, just to provide the best information I can to help and educate. Giving Ezra all the info and options here lets him decide what path forward he wants to take. If that’s not how I would do it, so what? It’s his game.

3 Likes