I decided to move my game from windowed to fullscreen mode and that's the first problem I face. I'm looking for a way of resizing all of my sprites based on screen resolution. My background is now in the (0, 0) coordinates, but I need to have it and all sprites to scale with some kind of fixed aspect ratio (16:9 preferred). And resize them to that portion that the background is stretched to fill the screen. And not more, not less.
I've looked into some online tutorials but I really couldn't understand the concept they used. Can you explain how you would to that? I read using a RenderTarget2D and passing it to a spriteBatch.Begin() call, has some kind of effect, but there's got to be more code.
I'm not looking to supporting resolution change option, but adapting the sprites to the current resolution.
It sounds like you're talking about resolution independence.
The general idea is to make your game using a virtual resolution and scale it up or down to fit the actual resolution of the screen.
var scaleX = (float)ActualWidth / VirtualWidth;
var scaleY = (float)ActualHeight / VirtualHeight;
var matrix = Matrix.CreateScale(scaleX, scaleY, 1.0f);
_spriteBatch.Begin(transformMatrix: matrix);
For example, if your virtual resolution was 800x480 you would simply render all your sprites relative to that. Then before rendering the sprite batch, create a transformation matrix to pass into the Begin call.
The other thing you should know is that you'll need to scale the mouse / touch input coordinates in reverse to deal with them in the virtual resolution. In the Update method you can scale the mouse position in reverse like this:
var mouseState = Mouse.GetState(); // you're probably already doing this
var mousePosition = new Vector2(mouseState.X, mouseState.Y);
var scaledMousePosition = Vector2.Transform(mousePosition, Matrix.Invert(matrix));
Then you can use the scaled value in all the places you're currently using mouseState.X and mouseState.Y.
It gets more complicated if want to implement letterboxing or pillarboxing. Take a look at the Viewport Adapters in MonoGame.Extended if you want to know how that works.
You have a texture with size (W, H), to be put in the position (X, Y), according to a scale (sW, sH). Initially, the scale was (1, 1), so the sprite would be positioned in the rectangle (X, Y, W, H).
Now, let's say the initial resolution was 800x600, but you now want a resolution of 1440x900. If 800 -> sW = 1, 1440 -> sW = 1440/800 = 1.8. Similar, our new sH is 1.5.
What this is saying is: if something was supposed to be on the X-coordinate 500 on the initial resolution, it is now on 500*1.8 = 900 X position on the new resolution. This is clear for the edge: if something was on X=800 previously, it is now on 800*1.8 = 1440, still on the edge of the screen!
All said and done, we simply have to multiply. Going back to the first paragraph, we can say that a rectangle (X, Y, W, H) can be rescaled by a scale (sW, sH) to (X * sW, Y * sH, W * sW, H * sH).
This is of course calculated by assuming the original resolution is scaled by (1, 1), don't forget this!
Related
Say I have a GameObject inside a 2D scene and a camera. And I want to change the size and position of the camera so that even when the screen resolution changes, the object will still be visible. So, how can I do that?
TL;DR: Scroll down to the bottom for the code.
First up, we must set the position of the camera to the middle of the object so the scaling of the camera would be easier.
Second, to scale the camera, we're going to change the orthographicSize of the camera in our script (Which is the Size attribute in the Camera component). But how do we calculate the attribute?
Basically, the Size attribute here is half the height of the camera. So, for example, if we set the Size to 5, that mean the height of camera going to be 10 Unity Unit (which is something I made up so you can understand easier).
So, it seems like we just have to get the height of the object, divide it by 2 and set the Size of the camera to the result, right? (1)
Well, not really. You see, while it might work on certain cases, when the width of the object is way, way longer than the screen, and the height if way, way shorter, then the camera would not able to see all of the object.
But why is that? Now, let's say that our camera has a width/height of 16/9, and our object is 100/18. That means if we scale using the height, our camera's width/height would be 32/18, and while it's enough to cover the height, it isn't enough to cover the width. So, another approach is to calculate using the width
By taking the width of the object, divide it by the width of the camera and then multiply with the height of the camera (then of course, divide by 2). We would be able to fit the whole width of the object. (Because of... ratio or something) (2)
BUT AGAIN, it has the same problem as our first approach, but the object being too tall instead too wide.
So, to solve this, we just have to place a check if the first approach (see (1)) if the object is being overflowed, and if it is, then we just use the second approach instead (see (2)). And that's it
And here's the code btw:
// replace the `cam` variable with your camera.
float w = <the_width_of_object>;
float h = <the_height_of_object>;
float x = w * 0.5f - 0.5f;
float y = h * 0.5f - 0.5f;
cam.transform.position = new Vector3(x, y, -10f);
cam.orthographicSize = ((w > h * cam.aspect) ? (float)w / (float)cam.pixelWidth * cam.pixelHeight : h) / 2;
// to add padding, just plus the result of the `orthographicSize` calculation with number, like this:
// | |
// V V
// ... cam.pixelHeight : h) / 2 + 1
I'm writing an Android game and as I wanted it to be played in portrait mode I want the scale of the objects to remain the same in regards to the screen width. I think I managed to do that with this code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Android;
public class PowerPosition : MonoBehaviour
{
void Start()
{
float w = Screen.width;
float h = Screen.height;
Vector2 position = (Camera.main.ScreenToWorldPoint(new Vector2(w * 0.9f, w * 0.53f)));
float ratio = (w / h) / (9f / 16f);
gameObject.transform.position = new Vector3(position.x * ratio, position.y * ratio, 0);
gameObject.transform.localScale = new Vector3(gameObject.transform.localScale.x * ratio, gameObject.transform.localScale.y * ratio, gameObject.transform.localScale.z * ratio);
The issue is with the positioning. It changes and objects that should have relative distances from each other get messed up. Objects that should appear one above the other become too close and overlap.
The "position * ratio" was a test, it doesn't work well either with or without that. Here for example I had to items that I wanted to keep a consistent distance from the lower right end of the screen.
How can you fix that?
You can use percentage calculation as orientationbase to scale. That represents a relational scaleability.
There is a false friend in your mind. The displays haves different dots per inch. Will say, this value may be included your calculation.
I will try to explain it differently.
A device has a screen resolution of X-axis, Y-axis, color depth and ppi / dpi.
You can buy a display with 1920x1080xColors with 6 "or 32". With large displays you have a lower pixel density compared to small displays.
A device itself only changes X and Y if you have an orientation sensor.
Landscape 1920x1080 becomes portrait 1080x1920.
When I take an iPhone, the display has a higher pixel density than the cheaper Android devices.
In mathematics there is the famous three-sentence that is suitable for calculating proportions.
You can query the value for the pixel density from the system. You can use this value for rescaling your buttons.
I work mostly with horizontal centering. So I keep the symmetry.
Using OpenTK, I've created a window (800x600) with a vertical FOV of 90°.
I want to make a 2D game with a background image that fits on the whole screen.
What I want is the plane at a variable z coordinate as a RectangleF.
Currently my code is:
var y = (float)(Math.Tan(Math.PI / 4) * z);
return new RectangleF(aspectRatio * -y, -y, 2 * aspectRatio * y, 2 * y);
The rectangle calculated by this is always a little to small, this effect seems to decrease with z increasing.
Hoping someone will find my mistake.
I want to make a 2D game with a background image that fits on the whole screen.
Then don't bother with perspective calculations. Just switch to an orthographic projection for drawing the background, disabling depth writes. Then switch to a perspective projection for the rest.
OpenGL is not a scene graph, it's a statefull drawing API. Make use of that fact.
To make a 2D game using OpenGL, you should use an orthographic projection, like this tutorial shows.
Then its simple to fill the screen with whatever image you want because you aren't dealing with perspective.
However, IF you were to insist on doing things the way you say, then you'd have to gluProject the 4 corners of your screen using the current modelview matrix and then draw a quad in 3D space with those corners. Even with this method, it is likely that the quad might not cover the entire screen sometimes due to floating point errors.
I'm having a lot of trouble googling for this because I don't really know the terminology for it. This HAS to be a common problem though. I want to iterate through a sprite sheet, but I want it to IGNORE the transparent background around the sprite and not include that as part of what's displayed.
For example, if I have a few frames, I want to iterate through each frame and load that image. Then, in that frame, I want to narrow it down by drawing a rectangle around the image itself, not the extra "background". This would probably be accomplished by finding the corners of the sprite which are non-transparent pixels (Not sure how this part works).
Does this make sense? Again, not sure exactly which words to use here...let me know if this is unclear.
The goal here is loading sprites that are exactly square with other frames, so they won't wobble or bounce unintentionally.
Thanks much!!
I am working on my first game as well and I had a similar problem with the transparent areas around my sprites, in this case for collisions.
What I did was set it up so that each sprite has a position, a height, a width and Padding for X and Y.
Vector2 position = new Vector2(100,100);
int frameHeight = 48;
int frameWidth = 48;
int paddingX = 4;
int paddingY = 3;
With that info you can get what you need, for instance for the rectangle that represents the bounding box around the sprite I can use:
boundingRectangle = new Rectangle(
(int)position.X + paddingX,
(int)position.Y + paddingY,
frameWidth - (paddingX * 2),
frameHeight - (paddingY * 2));
I read this in XNA 4.0 Game Development by Example by Kurt Jaegers (Which has been a ton of help for me)
I've written out a detailed description of my problem here:
http://www.codebot.org/articles/?doc=9574
The basic gist of my question is what is the best way to get XNA to behave like my OpenGL apps, in that I want content stretched to fill a window based on my designed proportions rather than the actual window size.
Further information, this problem relates to varying window or viewport size. In my previous OpenGL apps I would allow uses to switch between windowed and fullscreen mode, and I'd also allow windows to be resized. The problem I am running into with XNA is handling different fullscreen and windowed sizes. In OpenGL I'd detect a when window was resized and adjust the viewport so that the field of view was always fixed to a resolution aspect ratio. I would also create a 2D projection drawing, using the glOrtho function, to a fixed resolution.
The XNA examples I've worked through using SpriteBatch and SpriteFont, text and sprites seem to render in screen pixels. That is, all 2D output is rendered with square pixels and no stretching. In my XNA apps I'd rather they stretch to fill a window in the proportions I've designed. My question is, how can 2D and 3D stretching and filling, like I've done in OpenGL, best be done in XNA?
For 3D content using BasicEffect (and other effects that implement IEffectMatrices as explained here) you can use the appropriate members to set your World, View and Projection matrices as you like.
So where in your OpenGL code you have this:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(FieldOfView, Width / Height, 1, 1000);
The equivalent in XNA is to set a projection matrix on the effect, like so:
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
FieldOfView, Width / Height, 1, 1000);
Now, for 2D. Here's what you might have in OpenGL:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, height, 0, 0, 1);
If you're using an effect (even with SpriteBatch, as explained here), the basic idea is the same as with 3D:
effect.Projection = Matrix.CreateOrthographicOffCenter(
0, width, height, 0, 0, 1);
Now, if you're using SpriteBatch without a custom effect (and I would recommend this, if you don't actually need a custom effect), you have to bare in mind that, by default, SpriteBatch uses an projection equivalent to:
effect.Projection = Matrix.CreateTranslation(-0.5f, -0.5f, 0)
* Matrix.CreateOrthographicOffCenter(0,
GraphicsDevice.Viewport.Width,
GraphicsDevice.Viewport.Height, 0, 0, 1);
Which gives a "client space" (top left is (0,0)) coordinate system, aligned to pixel centres.
If you want to adjust that space, you may pass in a transformation matrix to SpriteBatch.Begin (this overload).
So to get the effect you are after (where a fixed number of world units appear on screen, no matter the screen size), you can to counter-act the built-in projection from client-space with this transformation:
Matrix.CreateScale(GraphicsDevice.Viewport.Width / 640f,
GraphicsDevice.Viewport.Height / 480f, 1f);
(Assuming you want your visible world space to be 640 by 480.)
I recommend having a look through the documentation for XNA's Matrix on MSDN, to see what kind of matrices you can create.
For 2D drawing I added this to my LoadContent() method, where effect is private field in my Game class ...
effect = new BasicEffect(GraphicsDevice)
{
TextureEnabled = true,
VertexColorEnabled = true
};
And then added this inside my Draw() method ...
effect.Projection = Matrix.CreateTranslation(-0.5f, -0.5f, 0) *
Matrix.CreateOrthographicOffCenter(0, 640, 480, 0, 0, 1);
batch.Begin(0, null, null, null, RasterizerState.CullNone, effect);
It seems to work fine. Now 2D images and fonts are scaled correctly when the window is resized. You recommended not using a custom effect. Is creating an instance of BasicEffect what you meant, or did you mean something else? That is I don't see how to create a custom project matrix without using an effect instance.