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;
Related
I recently started using Unity and C# and am currently working on a Vertical 2D mobile Game. I'm struggling to get my background to scale with different aspect ratios. The background sprite is 19,5/9 and the playable area is 16/9. At the moment the background is scaling to fit the top and bottom of the screen, but the idea is to have the background anchored to the sides and bottom and for the view to extend upwards if needed (Hence the tall sprite). Any ideas? Thanks in advance.
Here is the code im trying, its attached to the Camera.
public SpriteRenderer background;
private void Start()
{
float screenRatio = (float)Screen.width / (float)Screen.height;
float targetRatio = background.bounds.size.x / background.bounds.size.y;
if(screenRatio >= targetRatio)
{
Camera.main.orthographicSize = background.bounds.size.y / 2;
}
else
{
float differenceInSize = targetRatio / screenRatio;
Camera.main.orthographicSize = background.bounds.size.y / 2 * differenceInSize;
}
}
My solution is to use a worldspace UI canvas.
Set the canvas to world space, place it at the desired depth in your scene (dimensions won't matter as we can set them in the script), and either add an image object as a child or add an image component to the canvas object. Add your sprite as the source for the image like so.
void Awake()
{
RectTransform rt = GetComponent<RectTransform>();
rt.position = new Vector3(0, 0, rt.position.z);
float camHeight = Camera.main.orthographicSize * 2;
rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, camHeight);
float targetRectWidth = camHeight * Camera.main.aspect;
rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, targetRectWidth);
}
The Steps are:
sets the rect transform to be positioned in the centre of the screen at whatever depth you set it at in the editor.
get 2xCamera height (as that is the distance from the top to bottom of the screen)
sets the anchors so that it lines the UI object up with the top and bottom of the screen
gets the target width by multiplying the height by the aspect ratio of the camera
sets the correct width based the target width
This can be done in update instead of Awake or Start to dynamically size the background while playing if necessary.
Here it is at 1080p 16:9 and at 5:4.The red cube is to show that it is in the background behind objects in the scene.
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;
What i want to do is make a title like Terraria just the rocking back and forth side of it not the graphics side i know that its just a .png rocking back and forth but could anyone help me and other people who read this and what to know how to do it?
So what i would like is to learn how to make a rocking back and forth image like the title displayed in Terraria?
Something like this for the people who don't know what Terraria is.
http://www.youtube.com/watch?v=3K8PMG42l3M
It appears that the logo is rotating and changing its size over non-equal intervals of time.
First, you need to get familiar with this method:
SpriteBatch.Draw Method (Texture2D, Vector2, Nullable, Color, Single, Vector2, Single, SpriteEffects, Single)
The parameters are:
Texture2D texture, // texture of your logo
Vector2 position, // where to draw
Nullable<Rectangle> sourceRectangle, // null
Color color, // Color.White
float rotation, // you will be changing this
Vector2 origin, // and this
float scale, // also this
SpriteEffects effects, // SpriteEffects.None
float layerDepth // 0
Use these variables:
float rotation = 0,
rotationSpeed = 0.002f, // this is how much rotation will change each frame
maximumAngle = 0.1f,
minimumAngle = -0.1f,
rotationDirection = 1,
scale = 1f, // 1 means 100%, 0.95f = 95%
scaleChange = 0.005f, // this may seem not much, but it's enough
maxScale = 1.1f,
minScale = 0.9f,
scaleDirection = 1;
Just put DrawLogo(); in your main Draw() method.
void DrawLogo()
{
// these two very similar pieces of code will control scale and rotation
if (rotationDirection > 0)
{
if (rotation < maximumAngle)
rotation += rotationSpeed;
else
rotationDirection = -rotationDirection;
}
else
if (rotation > minimumAngle)
rotation -= rotationSpeed;
else
rotationDirection = -rotationDirection;
if (scaleDirection > 0)
{
if (scale < maxScale)
scale += scaleChange;
else
scaleDirection = -scaleDirection;
}
else
if (scale > minScale)
scale -= scaleChange;
else
scaleDirection = -scaleDirection;
Texture2d t2d = logoTexture;
spriteBatch.Draw(t2d,
centerScreen, // change this to `new Vector2(123, 456)`
null, // null means draw entire texture
Color.White, // no tinting
rotation, // the rotation we calculate above
new Vector2(t2d.Width / 2, t2d.Height / 2),
// this sets rotation point to the center of the texture
scale, // the scale we calculate above
SpriteEffects.None, // you can mirror your texture with this
0); // I usually leave it zero :p
}
This is tested and works just fine :)
You mean the effect we can see at about 1:16 (and probably also at other times), when you choose stuff in the menus?
Concept
As far as I can see, you can do this with simple rotations and scaling. So, if you do not want to make an animated gif (which you suppose it is), you can just do it inside your XNA code. Take a png or gif with alpha-channel (so that the non-text is transparent).
Then, when you draw it on the screen with spriteBatch.draw() you can choose one of the overloads that support scaling and rotation.
Then you have to set:
the rotation you want to have (which will be a rotation over time)
the origin (to the center of the image)
the scale (which will be scaling over time)
As the clock is sent to the update() method as far as I remember XNA, we will have to update the rotation and scale of the image there. We need the clock, because we cannot just set rotaton = 10° and XNA will handle everything for us. We have to calculate the current rotation in each time step ourselves. E.g. if a full rotation shall endure 10 seconds and 5 seconds have passed, then you know you have a half rotation. So we would tell XNA: Set our rotation to 180° now, and in the next time step, we might tell: Set our rotation to 190° now.
The basic concept is:
Calculate how much part of rotation/scale we have done in the current time step
Tell XNA to adjust this rotation/scale in this time step
Iterate these two steps again and again
Implementation
I think the best thing to do here, is using a sin() or cos() function for the scaling and rotation. The good things about them:
they have positive and negative values as well (so we can easily rotate in both directions)
they are smooth, meaning your rotation and scaling will not look too abrupt at the end of the rotation/scaling
I hope my maths is correct here. I will explain everything, so others can correct me if something is wrong. Or also you can find out, if something is wrong. We will use a sin() here, because it starts at 0, which in our case means that nothing should happen. That’s what we want: We want to begin at a situation where nothing happens.
Now, sin() has a cycle time of 2*PI. Of course, we do not want a scaling to last 2*PI, but rather something like 1000 milliseconds. We cannot change the definition of Math.Sin() in C#, but we can change the value we throw inside. So when we mean 1000 milliseconds, we will give Math.Sin() 2PI and when we mean 500 milliseconds, we give it PI.
We would define these member variables:
// define some variables for rotation and scale speed, in milliseconds
int fullRotationTime = 1000; // max rotation will be reached after 1 second
float maxRotationAngle = MathHelper.ToRadians(10); // we will rotate by 10 degree up and down
int rotationTimePassed = 0;
float currentRotationAngle = 0;
int fullScaleTime = 1000; // max scale will be reached after 1 second
float maxScaleSize = 1.2f; // we will scale to 20% larger max
int scaleTimePassed = 0;
float currentScaleFactor = 1.0;
And in the Update() method, we calculate how much of our rotation we already have done.
protected virtual void Update(GameTime gameTime)
{
int milliseconds = gameTime.ElapsedGameTime.TotalMilliseconds;
// these are the milliseconds in the current rotation
rotationTimePassed += milliseconds;
scaleTimePassed += milliseconds;
if (rotationTimePassed >= fullRotationTime)
rotationTimePassed %= fullRotationTime;
if (scaleTimePassed >= fullScaleTime)
scaleTimePassed %= fullScaleTime;
float rotationTimeAdjustedToTwoPi = ((float)rotationTimePassed)/fullRotationTime * 2* Math.PI);
currentRotationAngle = maxRotationAngle * Math.Sin(rotationTimeAdjustedToTwoPi);
// we do not want the scaling to be negative, thus we add 1 to the whole and
// divide by 2. Then the maximum will be 1 and the minimum 0
float scaleTimeAdjustedToTwoPi = ((float)scaleTimePassed)/fullScaleTime * 2* Math.PI);
currentScaleFactor = maxScaleSize * (Math.Sin(scaleTimeAdjustedToTwoPi) + 1)/2;
}
Then, in the Draw() method we can take the values calculated before and display our rotated and scaled image.
protected virtual void Draw()
{
spriteBatch.Begin();
spriteBatch.Draw(texture,
new Vector2(50, 50),
null,
Color.White,
currentRotationAngle,
new Vector2(texture.width/2, texture.height/2),
currentScaleFactor,
SpriteEffects.None,
0
);
spriteBatch.End();
}
It’s not tested, so there might even be syntax errors, but I at least the basic idea should be correct and I think the important thing is that you understand how it can be done conceptually.
Variable time steps
It’s easy to integrate the variable time steps user1306322 has mentioned into the code above. We had these if-conditions where we checked if the current time-slice is over, like this: if (rotationTimePassed >= fullRotationTime).
Now it we want to make the time-slices variable length, just adjust a new time-slice based on a random number here. Like this:
var rand = new Random();
if (rotationTimePassed >= fullRotationTime)
{
rotationTimePassed %= fullRotationTime;
// next rotation might take between 0.5 and 2.5 seconds
fullRotationTime = rand.next(500, 2500);
}
I'm working on an RPG game that has a Top-Down view. I want to load a picture into the background which is what the character is walking on, but so far I haven't figured out how to correctly have the background redraw so that it's "scrolling". Most of the examples I find are auto scrolling.
I want the camera to remained centered at the character until you the background image reaches its boundaries, then the character will move without the image re-drawing in another position.
Your question is a bit unclear, but I think I get the gist of it. Let's look at your requirements.
You have an overhead camera that's looking directly down onto a two-dimensional plane. We can represent this as a simple {x, y} coordinate pair, corresponding to the point on the plane at which the camera is looking.
The camera can track the movement of some object, probably the player, but more generally anything within the game world.
The camera must remain within the finite bounds of the game world.
Which is simple enough to implement. In broad terms, somewhere inside your Update() method you need to carry out steps to fulfill each of those requirements:
if (cameraTarget != null)
{
camera.Position = cameraTarget.Position;
ClampCameraToWorldBounds();
}
In other words: if we have a target object, lock our position to its position; but make sure that we don't go out of bounds.
ClampCameraToBounds() is also simple to implement. Assuming that you have some object, world, which contains a Bounds property that represents the world's extent in pixels:
private void ClampCameraToWorldBounds()
{
var screenWidth = graphicsDevice.PresentationParameters.BackBufferWidth;
var screenHeight = graphicsDevice.PresentationParameters.BackBufferHeight;
var minimumX = (screenWidth / 2);
var minimumY = (screnHeight / 2);
var maximumX = world.Bounds.Width - (screenWidth / 2);
var maximumY = world.Bounds.Height - (screenHeight / 2);
var maximumPos = new Vector2(maximumX, maximumY);
camera.Position = Vector2.Clamp(camera.Position, minimumPos, maximumPos);
}
This makes sure that the camera is never closer than half of a screen to the edge of the world. Why half a screen? Because we've defined the camera's {x, y} as the point that the camera is looking at, which means that it should always be centered on the screen.
This should give you a camera with the behavior that you specified in your question. From here, it's just a matter of implementing your terrain renderer such that your background is drawn relative to the {x, y} coordinate specified by the camera object.
Given an object's position in game-world coordinates, we can translate that position into camera space:
var worldPosition = new Vector2(x, y);
var cameraSpace = camera.Position - world.Postion;
And then from camera space into screen space:
var screenSpaceX = (screenWidth / 2) - cameraSpace.X;
var screenSpaceY = (screenHeight / 2) - cameraSpace.Y;
You can then use an object's screen space coordinates to render it.
Your can represent the position in a simple Vector2 and move it towards any entity.
public Vector2 cameraPosition;
When you load your level, you will need to set the camera position to your player (Or the object it should be at)
You will need a matrix and some other stuff, As seen in the code below. It is explained in the comments. Doing it this way will prevent you from having to add cameraPosition to everything you draw.
//This will move our camera
ScrollCamera(spriteBatch.GraphicsDevice.Viewport);
//We now must get the center of the screen
Vector2 Origin = new Vector2(spriteBatch.GraphicsDevice.Viewport.Width / 2.0f, spriteBatch.GraphicsDevice.Viewport.Height / 2.0f);
//Now the matrix, It will hold the position, and Rotation/Zoom for advanced features
Matrix cameraTransform = Matrix.CreateTranslation(new Vector3(-cameraPosition, 0.0f)) *
Matrix.CreateTranslation(new Vector3(-Origin, 0.0f)) *
Matrix.CreateRotationZ(rot) * //Add Rotation
Matrix.CreateScale(zoom, zoom, 1) * //Add Zoom
Matrix.CreateTranslation(new Vector3(Origin, 0.0f)); //Add Origin
//Now we can start to draw with our camera, using the Matrix overload
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default,
RasterizerState.CullCounterClockwise, null, cameraTransform);
DrawTiles(spriteBatch); //Or whatever method you have for drawing tiles
spriteBatch.End(); //End the camera spritebatch
// After this you can make another spritebatch without a camera to draw UI and things that will not move
I added the zoom and rotation if you want to add anything fancy, Just replace the variables.
That should get you started on it.
However, You will want to make sure the camera is in bounds, and make it follow.
Ill show you how to add smooth scrolling, However if you want simple scrolling see this sample.
private void ScrollCamera(Viewport viewport)
{
//Add to the camera positon, So we can see the origin
cameraPosition.X = cameraPosition.X + (viewport.Width / 2);
cameraPosition.Y = cameraPosition.Y + (viewport.Height / 2);
//Smoothly move the camera towards the player
cameraPosition.X = MathHelper.Lerp(cameraPosition.X , Player.Position.X, 0.1f);
cameraPosition.Y = MathHelper.Lerp(cameraPosition.Y, Player.Position.Y, 0.1f);
//Undo the origin because it will be calculated with the Matrix (I know this isnt the best way but its what I had real quick)
cameraPosition.X = cameraPosition.X -( viewport.Width / 2);
cameraPosition.Y = cameraPosition.Y - (viewport.Height / 2);
//Shake the camera, Use the mouse to scroll or anything like that, add it here (Ex, Earthquakes)
//Round it, So it dosent try to draw in between 2 pixels
cameraPosition.Y= (float)Math.Round(cameraPosition.Y);
cameraPosition.X = (float)Math.Round(cameraPosition.X);
//Clamp it off, So it stops scrolling near the edges
cameraPosition.X = MathHelper.Clamp(cameraPosition.X, 1f, Width * Tile.Width);
cameraPosition.Y = MathHelper.Clamp(cameraPosition.Y, 1f, Height * Tile.Height);
}
Hope this helps!
I have a pretty annoying problem. I would like to create a drawing program, using winform + XNA combo.
The most important part would be to transform the mouse position into the XNA drawn grid - I was able to make it for the translations, but it only work if I don't zoom in - when I do, the coordinates simply went horrible wrong.
And I have no idea what I doing wrong. I tried to transform with scaling matrix, transform with inverse scaling matrix, multiplying with zoom, but none seems to work.
In the beginning (with zoom value = 1) the grid starts from (0,0,0) going to (Width, Height, 0). I was able to get coordinates based on this grid as long as the zoom value didn't changed at all. I using a custom shader, with orthographic projection matrix, identity view matrix, and the transformed world matrix.
Here are the two main methods:
internal void Update(RenderData data)
{
KeyboardState keyS = Keyboard.GetState();
MouseState mouS = Mouse.GetState();
if (ButtonState.Pressed == mouS.RightButton)
{
camTarget.X -= (float)(mouS.X - oldMstate.X) / 2;
camTarget.Y += (float)(mouS.Y - oldMstate.Y) / 2;
}
if (ButtonState.Pressed == mouS.MiddleButton || keyS.IsKeyDown(Keys.Space))
{
zVal += (float)(mouS.Y - oldMstate.Y) / 10;
zoom = (float)Math.Pow(2, zVal);
}
oldKState = keyS;
oldMstate = mouS;
world = Matrix.CreateTranslation(new Vector3(-camTarget.X, -camTarget.Y, 0)) * Matrix.CreateScale(zoom / 2);
}
internal PointF MousePos
{
get
{
Vector2 mousePos = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
Matrix trans = Matrix.CreateTranslation(new Vector3(camTarget.X - (Width / 2), -camTarget.Y + (Height / 2), 0));
mousePos = Vector2.Transform(mousePos, trans);
return new PointF(mousePos.X, mousePos.Y);
}
}
The second method should return the coordinates of the mouse cursor based on the grid (where the (0,0) point of the grid is the top-left corner.).
But is just don't work. I deleted the zoom transformation from the matrix trans, as I wasn't able to get any useful results (most of the time, the coordinates were horribly wrong, mostly many thousands when the grid's size is 500x500).
Any ideas, or suggestions? I've been trying to solve this simple problem for two days now :\
Take a look at the GraphicsDevice.Viewport.Unproject method for converting screen space locations in to world space, it basically goes through your world, view, projection transformations in reverse order.
as for your zooming issue, instead of scaling the world transform why not move the camera closer to the object that you're viewing?