Bounding the camera to the map - c#

I have a 2D game in unity, and I have a camera that follow my main character.
Currently, when I, for example, move the character to the left, I can see what's beyond the map, like so:
Say the map size is 15X15. I have a script generating 15X15 blocks of size 1X1, meaning the map bounds are (-1, -1) -> (15, 15).
There are two things I want to accomplish:
1) Have the camera not go out of the map bounds
2) When the map size is SMALLER than the camera, have the camera change its size.
Example for point 2)
The map is 5 columns and 15 rows, thus it's very narrow compared to the camera, like so:
or even 10 columns and 3 rows, like so:
I would want the size of the camera to change so it won't be neither wider or taller than the map.
How do I do those two things?
This is the github to the current scripts in the game. The CameraController script is where the additions are supposed to be

The orthographic size defines half of the vertical size, the horizontal size is based on aspect ratio.
From there you can define what should be the biggest value when centered.
The script below should get you going. As long as your level are rectangle (that obviously includes square), it will go fine:
public float speed = 2f;
public Transform min;
public Transform max;
private float aspect;
private float maxSize;
private void Start ()
{
this.aspect = Camera.main.aspect;
this.maxSize = max.position.x <= max.position.y ? max.position.x /2f / this.aspect :max.position.y / 2f;
}
private void Update ()
{
float size = Input.GetAxis ("Mouse ScrollWheel");
Camera.main.orthographicSize += size;
if (Camera.main.orthographicSize > maxSize)
{
Camera.main.orthographicSize = maxSize;
}
float x = Input.GetAxis ("Horizontal");
float y = Input.GetAxis ("Vertical");
Vector3 position = this.transform.position;
position.x += x * Time.deltaTime * this.speed;
position.y += y * Time.deltaTime * this.speed;
float orthSize = Camera.main.orthographicSize;
if (position.x < (this.min.position.x + orthSize * this.aspect))
{
position.x = this.min.position.x + orthSize * this.aspect;
}
else if (position.x > (this.max.position.x - orthSize * this.aspect))
{
position.x = this.max.position.x - orthSize * this.aspect;
}
if (position.y < (this.min.position.y + orthSize))
{
position.y = this.min.position.y + orthSize;
}
else if(position.y > (this.max.position.y - orthSize))
{
position.y = this.max.position.y - orthSize;
}
this.transform.position = position;
}
the idea is that you have two empty game objects place at bottom left and upper right. Bottom left is dragged into min and upper right goes int max. The speed variable is just how fast the camera translates. This is attached to the camera object. There is no limit for min size of camera, but you can do it.
The point is just that you get this going for your own project as this is more generic.
The rest is just considering the camera position, current size and aspect ratio (you cannot change that at runtime or you'd have to modify the code).
EDIT:
If you wish to bound the camera zoom to the movement,remove the first lines about size and add the following after you get the horizontal/vertical movement:
if (x != 0f || y != 0f) {
Camera.main.orthographicSize = Mathf.MoveTowards (Camera.main.orthographicSize, largeSize, Time.deltaTime);
} else {
Camera.main.orthographicSize = Mathf.MoveTowards (Camera.main.orthographicSize, size, Time.deltaTime);
}
Think of declaring the size, largeSize variables and set them to your need.

Related

How to move 2D Object within camera view boundary

I have a scene that my camera doesn't follow my player. When player reaches the end of camera I want player to can't go further (out of camera view). How can I do this?
My codes for movement
public class PlayerBlueController : MonoBehaviour {
public float speed;
private float x;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void FixedUpdate () {
x = Input.GetAxis ("Horizontal") / 100 * speed;
transform.Translate (x,0,0);
}
}
As you can see from this. It gets out of camera's view.
I noticed you used a Collider2D. You should be using Rigidbody2D.MovePosition instead of transform.Translate or you'll likely run into issues when transform.Translate is used.
1.Take the final move position and convert it to new position in ViewPortPoint with Camera.main.WorldToViewportPoint
2.Apply a limit with Mathf.Clamp to the result in #1.
3.Convert the ViewPortPoint back to world point with Camera.main.ViewportToWorldPoint.
4.Finally, move it with Rigidbody2D.MovePosition.
The code below is modified from this answer to include restriction to screen boundary.
Move without Rigidbody:
Use only if collision and physics are NOT required:
public float speed = 100;
public Transform obj;
public void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//Move only if we actually pressed something
if ((h > 0 || v > 0) || (h < 0 || v < 0))
{
Vector3 tempVect = new Vector3(h, v, 0);
tempVect = tempVect.normalized * speed * Time.deltaTime;
Vector3 newPos = obj.transform.position + tempVect;
checkBoundary(newPos);
}
}
void checkBoundary(Vector3 newPos)
{
//Convert to camera view point
Vector3 camViewPoint = Camera.main.WorldToViewportPoint(newPos);
//Apply limit
camViewPoint.x = Mathf.Clamp(camViewPoint.x, 0.04f, 0.96f);
camViewPoint.y = Mathf.Clamp(camViewPoint.y, 0.07f, 0.93f);
//Convert to world point then apply result to the target object
obj.position = Camera.main.ViewportToWorldPoint(camViewPoint);
}
Move Object with Rigidbody2D:
Use if collision and physics are required:
public float speed = 100;
public Rigidbody2D rb;
public void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//Move only if we actually pressed something
if ((h > 0 || v > 0) || (h < 0 || v < 0))
{
Vector3 tempVect = new Vector3(h, v, 0);
tempVect = tempVect.normalized * speed * Time.deltaTime;
//rb.MovePosition(rb.transform.position + tempVect);
Vector3 newPos = rb.transform.position + tempVect;
checkBoundary(newPos);
}
}
void checkBoundary(Vector3 newPos)
{
//Convert to camera view point
Vector3 camViewPoint = Camera.main.WorldToViewportPoint(newPos);
//Apply limit
camViewPoint.x = Mathf.Clamp(camViewPoint.x, 0.04f, 0.96f);
camViewPoint.y = Mathf.Clamp(camViewPoint.y, 0.07f, 0.93f);
//Convert to world point then apply result to the target object
Vector3 finalPos = Camera.main.ViewportToWorldPoint(camViewPoint);
rb.MovePosition(finalPos);
}
image not respond .
but you can check player location
x = Input.GetAxis ("Horizontal") / 100 * speed;
if(gameobject.transform.x > someValue)
x=0
gameobject will be OBJECT in scene that u attach class to it.
another way is place 2 empty gameobject with collider as invisibleWall and get collider to player
Possible solution is:
1.Get the coordinates of your screen cornes (top left, top right, bottom left, bottom right). You can get this coordinates using Screen.height and Screen.width.
2.Convert this coordinates using the camera you need with Camera.ScreenToWorldPoint.
3.Get your player coordinates and check that they are inside the rect which is formed by 4 coordinates of the screen corners.
You need to limit your transform's position based on the edges of the camera. Here is an answer describing the different coordinate systems in unity
You're probably looking to do something like this:
float xMin = Camera.main.ViewportToWorldPoint(Vector3.zero).x;
float xMax = Camera.main.ViewportToWorldPoint(Vector3.one).x;
Vector3 currentPos = transform.position;
float dx = Input.GetAxis ("Horizontal") / 100 * speed;
Vector3 desiredPos = new Vector3(currentPos.x + dx, currentPos.y, currentPos.z);
Vector3 realPos = desiredPos;
if(desiredPos.x > xMax)
realPos.x = xMax;
else if(desiredPos.x < xMin)
realPos.x = xMin;
transform.position = realPos;
Read up here for more info on ViewportToWorldPoint(), it's extremely useful to become comfortable with the different coordinate spaces and how you can convert between them.

Moving object 2d with viewmatrix

I'm working on a small game based on the movements of the military in ww2. I first started to make a camera to look around and then I wrote a programm to slowly move a platoon towarts where i clicked and then stop if it was there. UNtil there everything works totally fine, but then I tried to get it to work while having moved the camera so the matrix wasn't the standard matrix. I haven't got the mooving of the platoon to the right location to work.
I'd greatly appreciate it if someone could tell me what I have to do to get it working
moving Code:
public void Update(MouseState mouse, GameTime gameTime,Matrix matrix)
{
if (mouse.LeftButton == ButtonState.Pressed)
{
mousePos = new Vector2(mouse.X, mouse.Y);
Vector2.Transform(mousePos, matrix);
oldPos = testObjPos;
Vector2.Transform(oldPos, matrix);
Difference = mousePos - oldPos;
Difference.Normalize();
}
testObjPos += Difference * (float)gameTime.ElapsedGameTime.TotalSeconds * 20;
if (testObjPos.X > mousePos.X - 1 && testObjPos.X < mousePos.X + 1 &&
testObjPos.Y > mousePos.Y - 1 && testObjPos.Y < mousePos.Y + 1)
Difference = new Vector2(0, 0);
}
and maybe my camera class is the problem
Camera COde
float moveSpeed = 300;
Vector2 position;
Matrix viewMatrix;
int screenwidth, screenheight;
public void Update(KeyboardState keyState, GameTime gameTime)
{
if (keyState.IsKeyDown(Keys.W))
position.Y -= moveSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
if (keyState.IsKeyDown(Keys.S))
position.Y += moveSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
if (keyState.IsKeyDown(Keys.A))
position.X -= moveSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
if (keyState.IsKeyDown(Keys.D))
position.X += moveSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
if (position.X < -1000)
position.X = -1000;
if (position.Y < -1000)
position.Y = -1000;
if (position.X > 1000)
position.X = 1000;
if (position.Y > 1000)
position.Y = 1000;
viewMatrix = Matrix.CreateTranslation(new Vector3(-position, 0));
}
Thanks for taking the time to look at it already.
I assume that matrix is your view matrix. This matrix is used to transform positions from world space to camera space.
Your mouse position is in view space and you want to transform it to world space, hence in the other way. Therefore, you need the inverse of matrix. Furthermore, the Vector2.Transform() method returns the transformed vector. It does not change the original one. So right now, you don't use any transformed positions:
var invMatrix = Matrix.Invert(matrix);
mousePos = Vector2.Transform(mousePos, invMatrix);
You probably do not want to transform oldPos because it already is in world space (probably, I can't tell definitely from the code snippet).
By the way, there is an easier way to check if testObjPos is near mousePos. Just check the length of the difference vector:
if((testObjPos - mousePos).Length() < 1)
//stop

Keeping my object from rolling out of the screen

I am using the tilt function on a phone to control an object to roll left and right. I do not want it roll anything beyond the screen width.
As in the simple illustration below, the dotted lines represent the width of the screen. The 'O' is the object and the Max signs indicate the maximum point the object is allowed to roll to.
Max--------O--------Max
But currently using my code, the object still rolls out of the screen. Also tried testing both height n width and ended up the same result where the object rolls out of the screen. Please advice what I am doing wrong. Thank you.
public float speed = 10.0F;
void Update()
{
Vector3 dir = Vector3.zero;
dir.x = Input.acceleration.x;
if (dir.sqrMagnitude > 1)
dir.Normalize();
dir *= Time.deltaTime;
if (!(Mathf.Round(dir.x) > Mathf.Round(Screen.width/2)) || !(Mathf.Round(dir.x) < -Mathf.Round(Screen.width/2)))
{
transform.Translate(dir * speed);
}
}
**Updated
public float speed = 10.0F;
void Update()
{
Vector3 dir = Vector3.zero;
dir.x = Input.acceleration.x;
if (dir.sqrMagnitude > 1)
dir.Normalize();
dir *= Time.deltaTime;
//transform.Translate(dir * speed);
if (transform.position.x < 0)
{
transform.position = new Vector3(0, this.transform.position.y, this.transform.position.z);
}
else if (transform.position.x > Screen.width)
{
transform.position = new Vector3(Screen.width, this.transform.position.y, this.transform.position.z);
}
else
{
transform.Translate(dir * speed);
}
}
I think thereare several ways you can go about checking the transfomrs position.
first off if you were using a 2d camera you could use a method like
leftBounds = Camera.x - (Camera.pixelWidth/2);
however because ortho camera angles are not set at any particulare size at x distace from camera they are hard to calculate.
i have seen some instances were coliders on the camera just outside the render rand were placed as camera children
adding a colision mask to only affect the appropriate game object would be best.

Rotation over time in 3D

Im working with Xna 4, doing a game where i have a game object (spaceship) that moves in a 3D world on the Y axis = 0 plane.. Aka 2.5D..
Until now i used a very complex angle calculation to create a rotation between 2 vectors, yet that algorithm lacks the ability to take into account that the object already is rotated. so the results get funkey..
Therefore i was hoping that someone, could show me a smart and easily implementable way to use Matrices and vector math, to do such a rotation over time thingy.
What i noticed in previous searches, is that people have the following variables in their object classes:
- Position vector3
- right vector3
- up vector3
- Rotation matrix
- transformMatrix matrix
- velocity vector3
- etc..
often i ask myself why its needed to have that many variables for a simple current position.. or maybe im not understanding.. anyways..
I have the position, rotation and transformsMatrix currently, and would like to know what else i need and HOW to calculate it, and then how you would implement JUST the rotation over time.
The method that is called by my right-click movement command trig sends a vector3 position on the Y = 0 plane of where the click happened.
public void MoveCommand(Vector3 pos){ }
ive tested this, and the "pos" given is accurate. Any help will be apreciated ..
You should check the Matrix.CreateRotationX Y or Z acording to the rotation that you want.
X,Y or Z is the axis of the rotation,
If you choose Y you will see a "2D" rotation (yaw) because that is the axis that you are using as depth.
If you choose X or Z axis you will see "3D" rotations (pitch and roll)
The code should look like this:
WorldMatrix = Rotations * Translation
where
Rotations = Matrix.CreateRotationX(angleRadians)
and
Translation = Matrix.CreateTranslation(Position);
The world matrix is the matrix that is affecting your model, the view and projection depends on the camera
Now if you want to know the angle between vectors you should check the dot product or the atan2 function because you are in 2D
Vector3 Position;
float Rotation;
Matrix World
{
get
{
return Matrix.CreateRotationZ(Rotation) * Matrix.CreateTranslation(Position);
}
}
public void RotateInstantly(Vector3 position)
{
Rotation = Math.Atan2(Position.Y - position.Y, Position.x - position.x);
}
public void RotateIncremently(Vector3 position, float maxStep)
{
float targetRotation = Math.Atan2(Position.Y - position.Y, Position.x - position.x);
float diff = targetRotation - Rotation;
if (Math.Abs(diff) > maxStep)
{
if (targetRotation > Rotation)
Rotation += maxStep;
else
Rotation -= maxStep;
}
else
Rotation = targetRotation;
}
You can use the RotateIncremently like this:*
float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
float maxRotationVelocity = Math.TwoPi; //2* Pi is one revolution.
RotateIncremently(target.Position, maxRotationVelocity * dt);
Thanks to Stig-Rune SkansgÄrd's reply (if your danish hi5), i fixed my old Angle calculation and got it to work in every case.. So i thought i would answer my own question with the solution that works for me, so that future visitors can benefit of it.. This is a snippet of a very large Ship class + a helper method to calculate the angle:
public static float CalculateAngleFromVectors(Vector3 v1, Vector3 v2) {
float x = 0;
float X = v2.X - v1.X,
Z = v2.Z - v1.Z;
if (Z == 0) {
x = (X < 0 ? 90 : 270);
} else if (X == 0) {
x = (Z < 0 ? 180 : -180);
} else {
float temp = MathHelper.ToDegrees((float)Math.Atan(X / Z));
if (X < 0) {
x = (Z < 0 ? Math.Abs(temp) : 180 - Math.Abs(temp));
} else {
x = (Z < 0 ? 360 - Math.Abs(temp) : Math.Abs(temp) + 180);
}
}
return x;
}
this method gets you the float angle (in degrees) to rotate your ship (from the standard 0 degrees starting point)..
to use it, simply make some update / animation method in your class that does something like this:
float desiredRotation = 0, currentRotation = 0, totalElapsed = 0, timePerFrame = 0.05f;
if (desiredRotation != 0) {
totalElapsed += elapsed;
if (totalElapsed > timePerFrame) {
if (isRotationComplete()) {
rotX += MathHelper.ToRadians(desiredRotation);
currentRotation = desiredRotation = 0;
} else if (desiredRotation > currentRotation) {
currentRotation += shipTurnSpeed;
} else if (desiredRotation < currentRotation) {
currentRotation -= shipTurnSpeed;
}
totalElapsed -= timePerFrame;
}
}
EDIT: and the completion check:
private bool isRotationComplete() {
bool b = false;
if (desiredRotation > currentRotation && currentRotation + shipTurnSpeed > desiredRotation) {
b = true;
} else if (desiredRotation < currentRotation && currentRotation - shipTurnSpeed < desiredRotation) {
b = true;
}
return b;
}
essentially what this does is to always check wether or not DesiredRotation is bigger than 0.. if it is, then that means the player has given the command to rotate (or the AI).. CurrentRotation in my example is ofc how much has been rotated since a rotation command was last given, and is set to 0 once the rotation is complete.. You should have a Rotations matrix that uses a different variable to display the rotation with.. mine is this:
public float rotX { get; set; }
public float rotY { get; set; }
public Vector3 position { get; set; }
public Matrix Transform {
get {
return (Matrix.Identity *
Matrix.CreateScale(scale) *
Matrix.CreateRotationY(MathHelper.Pi) *
Rotation *
Matrix.CreateTranslation(position));
}
}
public float ShipCurRotation { get { return (rotX + MathHelper.ToRadians(currentRotation)); } }
public Matrix Rotation { get { return (Matrix.CreateRotationY(ShipCurRotation) * Matrix.CreateRotationX(rotY)); } }
The rotX variable is set in my animation when the rotation is complete, and also at Init.. And here is how i use the rotation angle that my first code snippet generates for me:
public void MoveToPosition(Vector3 pos) {
desiredRotation = (CalculateAngleFromVectors(position, pos) - MathHelper.ToDegrees(rotX));
isMoving = true;
}
.. this makes a smooth customizable rotation, transforms and movement setup.. In a XZ plane ofc.. the Y axis is UP and always 0..
Feel free to comment on this, if you have suggestions or changes or ideas to make things even better.. Im always open for improvements.. Thanks for the replies, and hope this helps alot of new developers, took me forever to gather this stuff from the web..
PS. the rotation can be applied directly to rotX for an instant rotation, bypassing the animation and turnspeed..
WithRegards
MatriXz

Collision Detection between player and tiles

I am trying to implement basic (for now) collision detection into my platformer. I have tiles that are each 16 x 16 in size. The character is 32 x 32 pixels in size and has its own bounding box. Now, in my Tile class, I have a bool, isSolid. Each of these tiles in my array also have a rect for their respective bounding boxes.
I am checking to see if there's an intersection between the player and tiles by doing:
if (player.GetBoundingBox().Intersects(map.tiles[(int)player.position.Y / 16,
(int)player.position.X / 16].bounds) && map.tiles[(int)player.position.Y / 16,
(int)player.position.X / 16].isSolid)
{
...
}
Now, my problem is that this is extremely inaccurate as I'm rounding off the position. I'm tired as heck right now and for the life of me I can't figure out how to properly do this. What is the best way to approach this issue?
Well this might not be exactly "basic", It works very nicely and dosen't have any problems because it calculates the X axis and Y axis seperatley, this collision structure will help you later on. (I switched to this from the old Platformer Starter kit code, which was very glitchy)
Assuming you already have methods for gravity, lets get started.
This should be after your falling and velocity logic, It will see what axises need to be checked.
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; //If you havent already, get the elapsed time
if (velocity.X != 0f)
{
Position += velocity.X * Vector2.UnitX * elapsed;
HandleCollisions(CollisionDirection.Horizontal);
}
if (velocity.Y != 0f)
{
Position += velocity.Y * Vector2.UnitY * elapsed;
HandleCollisions(CollisionDirection.Vertical);
}
Now for the very important HandleCollisons method
private void HandleCollisions(CollisionDirection direction)
{
// Get the player's bounding rectangle and find neighboring tiles.
Rectangle bounds = player.GetBoundingBox();
int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;
// Reset flag to search for ground collision.
isOnGround = false;
// For each potentially colliding tile,
for (int y = topTile; y <= bottomTile; ++y)
{
for (int x = leftTile; x <= rightTile; ++x)
{
Rectangle tileBounds = Level.GetBounds(x, y);
// If this tile is collidable,
bool IsSolid = map.tiles[x,y].IsSolid;
Vector2 depth;
if (isSolid && TileIntersectsPlayer(BoundingRectangle, tileBounds, direction, out depth))
{
if ((collision == ItemCollision.Platform && movement.Y > 0))
continue;
isOnGround = true;
if (isSolid || isOnGround)
{
if (direction == CollisionDirection.Horizontal)
{
position.X += depth.X;
}
else
{
isOnGround = true;
position.Y += depth.Y;
}
}
}
}
}
// Save the new bounds bottom.
previousBottom = bounds.Bottom;
}
public static bool TileIntersectsPlayer(Rectangle player, Rectangle block, CollisionDirection direction, out Vector2 depth)
{
depth = direction == CollisionDirection.Vertical ? new Vector2(0, player.GetVerticalIntersectionDepth(block)) : new Vector2(player.GetHorizontalIntersectionDepth(block), 0);
return depth.Y != 0 || depth.X != 0;
}
Thats it for that! It will detect collisons, but we need to allow it to figure out how much to push the player back up once it collides! You will need these two extension methods.
public static float GetHorizontalIntersectionDepth(this Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfWidthA = rectA.Width / 2.0f;
float halfWidthB = rectB.Width / 2.0f;
// Calculate centers.
float centerA = rectA.Left + halfWidthA;
float centerB = rectB.Left + halfWidthB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceX = centerA - centerB;
float minDistanceX = halfWidthA + halfWidthB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceX) >= minDistanceX)
return 0f;
// Calculate and return intersection depths.
return distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
}
public static float GetVerticalIntersectionDepth(this Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfHeightA = rectA.Height / 2.0f;
float halfHeightB = rectB.Height / 2.0f;
// Calculate centers.
float centerA = rectA.Top + halfHeightA;
float centerB = rectB.Top + halfHeightB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceY = centerA - centerB;
float minDistanceY = halfHeightA + halfHeightB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceY) >= minDistanceY)
return 0f;
// Calculate and return intersection depths.
return distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
}
Note you may need to modify this a bit, as the players position is the BOTTOM left. Also a collision enum is needed, for vertical and horizontal. Please tell me if anything seems missing in this.

Categories

Resources