I'm making a GUI for my game and now I'm stuck on animations. I need to scale a font up when the mouse hovers over it and scale it down when it's not. Here's my code:
// Update()
if (!IsDisabled)
{
elapsedSecondsFast = (float)gameTime.ElapsedGameTime.TotalSeconds * 3;
if (Size.Contains(InputManager.MouseRect))
{
scale += elapsedSecondsFast;
if (scale >= 1.05f) scale = 1.05f;
}
else
{
scale -= elapsedSecondsFast;
if (scale <= 1.0f) scale = 1.0f;
}
}
// Draw()
if ((PrimaryFont != null) && (SecondaryFont != null) && (!string.IsNullOrEmpty(Text)))
{
if (IsHovered) TextOutliner.DrawBorderedText(spriteBatch, SecondaryFont, Text, new Vector2(TextRectangle.X, TextRectangle.Y), ForeColor, 0.0f, new Vector2((SecondaryFont.MeasureString(Text).X / 2 * scale - SecondaryFont.MeasureString(Text).X / 2), (SecondaryFont.MeasureString(Text).Y / 2 * scale - SecondaryFont.MeasureString(Text).Y / 2)), scale);
else TextOutliner.DrawBorderedText(spriteBatch, PrimaryFont, Text, new Vector2(TextRectangle.X, TextRectangle.Y), ForeColor, 0.0f, new Vector2(PrimaryFont.MeasureString(Text).X / 2 * scale - PrimaryFont.MeasureString(Text).X / 2, (PrimaryFont.MeasureString(Text).Y / 2 * scale - PrimaryFont.MeasureString(Text).Y / 2)), scale);
}
The above is a GUIElement class which is inherited by my Button class. Let me explain my code briefly:
PrimaryFont and SecondaryFont are 2 SpriteFonts that use the same
font but a different size. This gives me the scale up/down animation I need without blurring my PrimaryFont.
TextRectangle and Size are 2 different Rectangles. Since my button has a texture and text I decided not to draw text on the texture file but have the game position my text over the texture to "fake" the effect. So TextRectangle is the size and location of button text and Size is size and location of button texture. TextRectangle has its center point in the center of the Button texture. So far I have been using magic numbers to achieve this. This is the core of the problem here.
You can see my origin, I passed it to the DrawBorderedText method of my TextOutliner class. The attributes are in the same order as if it were a spriteBatch.DrawString() call, only without SpriteEffects and layerDepth.
The Problem
Since I'm scaling the font (origin = center I think) it will no longer be in the center of the button. And since I have been using magic numbers to position the un-scaled text over the center of the button texture, I don't want to be forced to do the same thing for scaled text. I'm looking for an algorithm that would always position the text in the middle of my 270x72 texture, no matter if the text is scaled or not, while keeping the scale animation shown above, for each instance of the Button class. Preferably to have its origin point in the center.
Edit
So should I draw like this:
if (IsHovered) TextOutliner.DrawBorderedText(spriteBatch, SecondaryFont, Text, new Vector2(TextRectangle.X, TextRectangle.Y), ForeColor, 0.0f, new Vector2((SecondaryFont.MeasureString(Text).X / 2), (SecondaryFont.MeasureString(Text).Y / 2)), scale);
else TextOutliner.DrawBorderedText(spriteBatch, PrimaryFont, Text, new Vector2(TextRectangle.X, TextRectangle.Y), ForeColor, 0.0f, new Vector2(PrimaryFont.MeasureString(Text).X / 2, (PrimaryFont.MeasureString(Text).Y / 2)), scale);
and then draw the button's text at btn.Size.Width / 2, btn.Size.Height / 2, (int)MainGame.GameFontLarge.MeasureString("Play").X / 2, (int)MainGame.GameFontLarge.MeasureString("Play").Y / 2
So I eventually found the algorithm by myself and finally eliminated the need of using magic numbers for position of the text. Here's my technique:
TextOutliner.DrawBorderedText(spriteBatch, Font, Text, new Vector2(Size.X + ((Size.Width - Font.MeasureString(Text).X) / 2), Size.Y + ((Size.Height - Font.MeasureString(Text).Y)) / 2), ForeColor, 0.0f, new Vector2((Font.MeasureString(Text).X / 2 * scale - Font.MeasureString(Text).X / 2), (Font.MeasureString(Text).Y / 2 * scale - Font.MeasureString(Text).Y / 2)), scale);
I couldn't take the scale out of the origin equation as #LibertyLocked suggested, because the font was scaling from top-left point upon Mouse Hover and not the center as I want it to.
Related
I have wrote this code that will create a rounded corner border around a panel.
public void DrawRoundRect(Graphics g, Pen p, float X, float Y, float width, float height, float radius)
{
GraphicsPath gp = new GraphicsPath();
//Upper-right arc:
gp.AddArc(X + width - (radius * 2), Y, radius * 2, radius * 2, 270, 90);
//Lower-right arc:
gp.AddArc(X + width - (radius * 2), Y + height - (radius * 2), radius * 2, radius * 2, 0, 90);
//Lower-left arc:
gp.AddArc(X, Y + height - (radius * 2), radius * 2, radius * 2, 90, 90);
//Upper-left arc:
gp.AddArc(X, Y, radius * 2, radius * 2, 180, 90);
gp.CloseFigure();
g.DrawPath(p, gp);
gp.Dispose();
}
private void panel_Paint(object sender, PaintEventArgs e)
{
Graphics v = e.Graphics;
DrawRoundRect(v, Pens.White, e.ClipRectangle.Left, e.ClipRectangle.Top, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1, 10);
base.OnPaint(e);
}
It works fine, until it goes off screen and this happens:
Does anyone know what I've done wrong, or how to fix the issue?
You misunderstand what ClipRectangle means.
It doesn't tell you how big the panel is - it tells you which part of the panel to redraw. As you move the form off-screen, it needs to redraw the parts that changed monitors - but not the ones that remain on the original one. So instead of redrawing the whole panel (when ClipRectangle is the whole area of the panel, and your code works), it tells you to only redraw a part of it - and ClipRectangle is much smaller than the panel you're trying to draw.
ClipRectangle is really an optimization feature. If you don't care about that, you can pretty much ignore it completely - just use 0, 0, panel.Width, panel.Height.
You have two problems. First is relying on e.ClipRectangle, that is not the size of the panel. You need to use panel.ClientRectangle instead. That's what causes the smearing when you drag it off the screen and back.
You haven't found the next one yet, it is much more subtle. Panel was designed to be a container control and it optimizes its painting. Redrawing only the parts that get revealed when it is resized. Pretty important, makes resizing a window more bearable. That however will not work well for details like this, the rounded corners will not get properly repainted. Looks like a smearing effect as well, less pronounced than what you have now. It will happen whenever you resize the panel, typically with the Dock or Anchor property.
Proper way to do that is to derive your own class from Panel, set the ResizeRedraw property to true in the constructor. You typically almost always also want to set DoubleBuffered to true to suppress the visible flicker you now have. Or use the panel's Resize event, call panel.Invalidate().
First off I have looked around and I can see many posts about this and they all point towards the Z position of the text, however I have changed this to minus and positive and my text is always drawn behind my GUITexture.
So this is what I have setup
My GUI has 4 text boxes
Score
Lives
Level
Time
Now I have an object called GameManager which uses this code below to draw my two GUI sprites
void OnGUI()
{
float screenHeight = Screen.height / 12f * 1.5f;
GUI.DrawTexture (new Rect (0, 0, Screen.width * 2, screenHeight), textureBand);
GUI.DrawTexture (new Rect (0, Screen.height - screenHeight, Screen.width * 2, screenHeight), textureBand);
}
However what ever I do my text is always drawn below my GUITexture so I can never see my text, could I get a little help with this one.
If you're drawing into the same location, you need to specify the depth of each draw to make sure they're sorted correctly. Take a look at the unity docs here:
http://docs.unity3d.com/ScriptReference/GUI-depth.html
Set a higher depth value for the textures you want drawn further back (behind the text) like so:
GUI.depth = 1;
My scene is 2048 x 1152, and the camera never moves. When I create a rectangle with the following:
timeBarRect = new Rect(220, 185, Screen.width / 3, Screen.height / 50);
Its position changes depending on the resolution of my game, so I can't figure out how to get it to always land where I want it on the screen. To clarify, if I set the resolution to 16:9, and change the size of the preview window, the game will resize at ratios of 16:9, but the bar will move out from where it's supposed to be.
I have two related questions:
Is it possible to place the Rect at a global coordinate? Since the screen is always 2048 x 1152, if I could just place it at a certain coordinate, it'd be perfect.
Is the Rect a UI element? When it's created, I can't find it in the hierarchy. If it's a UI element, I feel like it should be created relative to a canvas/camera, but I can't figure out a way to do that either.
Update:
I am realizing now that I was unclear about what is actually being visualized. Here is that information: Once the Rect is created, I create a texture, update the size of that texture in Update() and draw it to the Rect in OnGui():
timeTexture = new Texture2D (1, 1);
timeTexture.SetPixel(0,0, Color.green);
timeTexture.Apply();
The texture size being changed:
void Update ()
{
if (time < timerMax) {
playerCanAttack = false;
time = time + (10 * Time.deltaTime);
} else {
time = timerMax;
playerCanAttack = true;
}
The actual visualization of the Rect, which is being drawn in a different spot depending on the size of the screen:
void OnGUI(){
float ratio = time / 500;
float rectWidth = ratio * Screen.width / 1.6f;
timeBarRect.width = rectWidth;
GUI.DrawTexture (timeBarRect, timeTexture);
}
I don't know that I completely understand either of the two questions I posed, but I did discover that the way to get the rect's coordinates to match the screen no matter what resolution was not using global coordinates, but using the camera's coordinates, and placing code in Update() such that the rect's coordinates were updated:
timeBarRect.x = cam.pixelWidth / timerWidth;
timeBarRect.y = cam.pixelHeight / timerHeight;
I have a 160 x 80 button in a window with resolution of 1280 x 720. I draw it as followed(I paraphrase a bit here):
currentwindowwidth = 1280;
// The scale in this instance would be 1.0f
scale = currentwindowwidth / 1280f;
b.position = new Vector2(640, 650) * scale;
b.origin = new Vector2(80, 40) * scale;
spriteBatch.Draw(b.texture, b.pos, null, Color.White, 0, b.ori, scale, SpriteEffects.None, 0);
When I draw the window at 1280 x 720, the button is drawn in the correct location and the button detection:
if (mousepos.X >= b.pos.X - b.ori.X && mousepos.X <= b.pos.X + b.ori.X)
{
if (mousepos.Y >= b.pos.Y - b.ori.Y && mousepos.Y <= b.pos.Y + b.ori.Y)
{
HandleButton(b);
}
}
works 100%. The button detects my mouse clicks to pixel perfect precision, and the texture2d is drawn faithfully to the detection box.
However, when I scale up to 1920 x 1080 (same aspect ratio), the button is drawn in the incorrect location:
currentwindowwidth = 1920;
// The scale would be 1.5f here
scale = currentwindowwidth / 1280f;
b.position = new Vector2(640, 650) * scale;
b.origin = new Vector2(80, 40) * scale;
spriteBatch.Draw(b.texture, b.pos, null, Color.White, 0, b.ori, scale, SpriteEffects.None, 0);
This will now give me a position vector of (960, 975) and an origin of (120, 60)(<-- which I find in the "Locals" tab in visual studio). After doing some calculations these are the correct values, and the button detection works exactly where I want it to work. HOWEVER the sprite is not drawn in the correct location, it is drawn (-60, -30) pixels offset from where it should be. When scaled up the button detection works in the correct location(840-1080, 915-1035) but the texture2d is drawn in the wrong place.
This happens as well when I scale the screen to another 16:9 resolution such as 1366 x 768, except not (-60, -30) off but proportionally (-8.0625, -4.03125) and (-30, -15) at 1600 x 900.
This effect is also witnessed when I use resolutions smaller than 1280 x 720 (854 x 480), but in the opposite direction (a positive offset vector instead of a negative one).
In every 16:9 resolution the button detection works as calculated, but when I scale the texture2d it is drawn in the incorrect place. My only guess is that the scale scale's the texture2d in ways I am not aware of.
If anybody else has any experience on the issue I would much appreciate it,
Ken
The origin parameter is specified in pixels from the top-left corner of the sprite texture. It gets applied before the sprite is scaled or positioned - you don't need to scale origin with the screen!
Also, it may be worth considering passing a scaling matrix to SpriteBatch.Begin, to scale up your entire scene, instead of manually scaling and positioning every sprite.
You will probably need to re-work your maths for figuring out the click-bounds in either case.
Note that if you use a matrix for scaling your whole scene, that matrix can be inverted to go back from screen (client) coordinates to scene (world) coordinates.
I am trying to center a rectangle on a point and can't figure out the math needed to do it.
The context this is being used in is a form which paints a bitmap and allows the user to zoom in at a specified point, and pan/scroll while zoomed in.
Here is my code which current works for centering the CanvasBounds on the middle of the ClientRectangle:
private void UpdateCanvas()
{
int canvasWidth = (int)(_bitmap.Width * _zoomRatio);
int canvasHeight = (int)(_bitmap.Height * _zoomRatio);
Point canvasLocation = new Point((ClientRectangle.Width - canvasWidth) / 2, (ClientRectangle.Height - canvasHeight) / 2);
CanvasBounds = new Rectangle(canvasLocation, new Size(canvasWidth, canvasHeight));
}
_zoomRatio is the zoom which adjusts the size of the canvas. 1.0 would be 100%, 2.0 is 200%, etc.
Basically instead I want to feed this function a point taken by mouse input, and use that point as the center for the canvasBounds rectangle. Then when the user manipulates the horizontal and vertical scroll bars, it can change the _centerPoint and update the CanvasBounds.
I think you need to just offset your "point" with your canvas size:
private void UpdateCanvas(Point mousePoint)
{
int canvasWidth = (int)(_bitmap.Width * _zoomRatio);
int canvasHeight = (int)(_bitmap.Height * _zoomRatio);
int canvasX = (mousePoint.X - (canvasWidth / 2));
int canvasY = (mousePoint.Y - (canvasHeight / 2));
CanvasBounds = new Rectangle(canvasX, canvasY, canvasWidth, canvasHeight);
}
If that's not what you are looking for, maybe edit your post with a simple screen shot.