Edit2: See below for new problem.
I'm creating a Windows Phone 7 game and have run into some difficulty. What I'm trying to do is use touchscreen coordinates converted to radians to fire an object upwards at an angle. That part works fine.
However, I cannot figure out how to make the object bounce off the side walls. I had it working in an earlier implementation, and it was simply a matter of negating the Y value when colliding with a wall. Unfortunately, it's not so easy this time. When the object hits the wall, it bounces off the wall for a short distance before changing direction towards the wall again.
How can I structure my code so it stops getting pushed into the wall?
Thank you.
//Move bubble
public void MoveBubble(Bubble b, double radians)
{
if (b.stop == false)
{
{
//Move bubble 'up'
Y = (float)(speed * Math.Cos(radians));
X = (float)(speed * Math.Sin(radians));
}
b.bubblePosX += X;
b.bubblePosY += Y;
//Problem area
if(b.bubblePosY <= 0)
Y-=Y;
if(b.bubblePosY >= 350)
Y-=Y;
}
//Calculate Angle
private void CalculateAngle(int touchX, int touchY)
{
radians = (Math.Atan2(touchX - bubbleStart.X, touchY - bubbleStart.Y));
}
Edit2: Just wanted to add the latest code. Now the ball is bouncing off the walls perfectly, but I'm having an issue with the direction bool being initialized to true. Sometimes, when firing the object, the angle is reversed. How can set the initial bool to the appropriate true/false value?
//Move bubble
public void MoveBubble(Bubble bubble, Vector2 distance)
{
if (bubble.stop == false)
{
float velocityX = speed * distance.X;
float velocityY = speed * distance.Y;
bubble.X += velocityX;
if (direction == true)
bubble.Y += velocityY;
if (direction == false)
bubble.Y -= velocityY;
if (bubble.Y <= 0)
direction = !direction;
if (bubble.Y >= 350)
direction = !direction;
}
}
You're not negating the Y value, you're just subtracting its value from itself. Perhaps you meant to write:
Y = -Y;
Or am I missing something?
You aren't reversing the direction of the bubble movement. the most suitable solution for changing at that moment the direction would be:
public void MoveBubble(Bubble b, double radians)
{
if (b.stop == false)
{
{
//Move bubble 'up'
Y = (float)(speed * Math.Cos(radians));
X = (float)(speed * Math.Sin(radians));
}
b.bubblePosX += X;
//Problem area
if(b.bubblePosY <= 0 || b.bubblePosY >= 350)
b.bubblePosY -= Y;
else
b.bubblePosY += Y;
}
}
That portion of code would move backwards the bubble just for the next frame, to get the bubble change absolutely the direction of its movement, you would have to recalculate the radians getting the point where the bubble finds a collision and the old touch points.
In resume, in each point of collision, you would have to recalculate the direction is getting your bubble, if you goal this, you can have the whole problem solved, reflect direction in case of collision.
I was looking for some code I had, but I found I've lost it, but I hope mi explanation could help.
See you
You have to change the location and the velocity.
If you have a position of (0, -1) and a velocity of (1, -1) and you can't have negative values, you have to switch the y-value (as to make up for the velocity) to (0, 1) and change the velocity to it's negative (1, 1).
so, loc(ation) and vel(ocity) has to change for instance like:
loc(0, 1), vel(0, -1) // OK
loc(0, 0), vel(0, -1) // switch vel to y=-y; (so -1 becomes 1)
loc(0, -2), vel(1, -3) // switch loc to it's positive value, loc(0, 2)
// and the vel to it's opposite (1, 3)
loc(-1, -3), vel(-2, -4) // loc is set to (1, 3) an vel to (2, 4)
// since both X and Y have been breached
Do this (assuming X and Y are velocity):
if(b.bubblePosY <= 0 && Y < 0)
Y = -Y;
if(b.bubblePosY >= 350 && Y > 0)
Y = -Y;
What is happening in your code is that your bubbles are getting stuck in your wall. The code above only reflects the bubble's direction if it is moving into the wall. Once it is moving out of the wall, it ignores that wall.
Additionally, rather than calculating the angle from the difference in positions and then converting it back to a direction vector, try simply normalizing difference in positions to get a direction vector (a unit vector; length = 1):
Vector2.Normalize(new Vector2(touchX - bubbleStart.X, touchY - bubbleStart.Y))
You should be storing either velocity vector or the direction vector between frames, so that when you reflect your bubble it stays going in that direction until it is reflected again.
Related
I wanted to a cube do not rotate more than 60 degrees so it dosn't rotate to more
I tried to use this
if(gameObject.GetComponent<Transform>().rotation.x >= 60 || gameObject.GetComponent<Transform>().rotation.x <= -60 || gameObject.GetComponent<Transform>().rotation.z >= 60 || gameObject.GetComponent<Transform>().rotation.z <= -60)
{
gameObject.GetComponent<Transform>().rotation = new Quaternion(0, 0, 0, 0);
}
to check the rotation.
It didn't work so I print what rotation is getting in the x coordinate
and it said cordinates like:
5.98324, 7.39482, -1.983495
and I was just moving the x position not the rotation and It change it.
So how do I get the x rotation that it sais in the component transform?
I think the way you approached is fine but not suitable for the scenario. Euler Angles should be handled in order to control the Transformation.
simple understanding be like:
// declare your cube rotation
float moveSpeed = 5f // speed in which cube rotates
float rotatecube = moveSpeed* Time.deltaTime* 10;
//Your condition be like
if(transform.eulerAngles.z < (.....declare the requirement.....))
transform.Rotate(vector3.forward * rotateCube);
If you still face any issue related to angles console.log() is the better way for self understandings.
Hope it clarifies..
You are interested in the euler angles of the rotation, not the quaternion components of the rotation. You should start by referring to transform.eulerAngles.x instead of transform.rotation.x or transform.eulerAngles.z instead of transform.rotation.z.
By the way, it's best to call GetComponent as few times as you can get away with as it's an expensive operation. You should rather call it once and assign the result to a variable such as this:
Transform cubeTransform = gameObject.GetComponent<Transform>();
if(cubeTransform.eulerAngles.x ...)
{
...
}
Additionally, you don't even need to use GetComponent to access the transform of the gameObject the script is attached to. Instead, you can just use transform, e.g.:
if ( transform.eulerAngles.x >= 60 || transform.eulerAngles.x <= -60
|| transform.eulerAngles.z >= 60 || transform.eulerAngles.z <= -60)
{
transform.rotation = Quaternion.identity;
}
Basicaly, you want to make limit of rotation of any axis - x,y, or z.
if yes, then here is the solution :
Program:
Rotating A cube not more that 60 degree in x and z only.
var rotValue += Time.deltaTime;
Cube.transform.rotaion = Quaternion.Eular(Mathf.Clamp(rotaValue,0,60), 0, Mathf.Clamp(rotaValue,0,60));
you can use Mathf.clamp for Clamping any Float value Between min and max.
hope, problem solved.
Skip all the above comments. I found a way to get the exact rotation values as in INSPECTOR. I am showing for angle (x). The code is as follows:
public Transform GameObject;
void Update()
{
float Rotation;
if(GameObject.eulerAngles.x <= 180f)
{
Rotation = GameObject.eulerAngles.x;
}
else
{
Rotation = GameObject.eulerAngles.x - 360f;
}
}
I have followed this tutorial: https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331 to create a 2d physics engine in c# (he works in almost all the time wrong and inconsistent pseudo c++) I've got Circle vs Circle collision and AABB vs AABB collision working fine. But when trying the AABB vs Circle collison (below) the two rigidbodies just stick together and slowly move glitchy in one direction.
I would be super thankful if someone could help me with this as I have spent days and still don't know what's causing the error.
If someone needs more information from my code, I'd be happy to provide it.
public static bool AABBvsCircle(ref Collision result) {
RigidBody AABB = result.a.Shape is AABB ? result.a : result.b;
RigidBody CIRCLE = result.b.Shape is Circle ? result.b : result.a;
Vector2 n = CIRCLE.Position - AABB.Position;
Vector2 closest = n;
float x_extent = ((AABB)AABB.Shape).HalfWidth;
float y_extent = ((AABB)AABB.Shape).HalfHeight;
closest.X = Clamp(-x_extent, x_extent, closest.X);
closest.Y = Clamp(-y_extent, y_extent, closest.Y);
bool inside = false;
if (n == closest) {
inside = true;
if (Abs(n.X) > Abs(n.Y)) {
// Clamp to closest extent
if (closest.X > 0)
closest.X = x_extent;
else
closest.X = -x_extent;
}
// y axis is shorter
else {
// Clamp to closest extent
if (closest.Y > 0)
closest.Y = y_extent;
else
closest.Y = -y_extent;
}
}
Vector2 normal = n - closest;
float d = normal.LengthSquared();
float r = ((Circle)CIRCLE.Shape).Radius;
// Early out of the radius is shorter than distance to closest point and
// Circle not inside the AABB
if (d > (r * r) && !inside)
return false;
// Avoided sqrt until we needed
d = (float)Sqrt(d);
if (inside) {
result.normal = -normal / d;
result.penetration = r - d;
}
else {
result.normal = normal / d;
result.penetration = r - d;
}
return true;
}
edit 1 collison resolution method in "Collision" struct
public void Resolve() {
Vector2 rv = b.Velocity - a.Velocity;
float velAlongNormal = Vector2.Dot(rv, normal);
if (velAlongNormal > 0)
return;
float e = Min(a.Restitution, b.Restitution);
float j = -(1 + e) * velAlongNormal;
j /= a.InvertedMass + b.InvertedMass;
Vector2 impulse = j * normal;
a.Velocity -= a.InvertedMass * impulse;
b.Velocity += b.InvertedMass * impulse;
const float percent = 0.2f; // usually 20% to 80%
const float slop = 0.01f; // usually 0.01 to 0.1
Vector2 correction = Max(penetration - slop, 0.0f) / (a.InvertedMass + b.InvertedMass) * percent * normal;
if (float.IsNaN(correction.X) || float.IsNaN(correction.Y))
correction = Vector2.Zero;
a.Position -= a.InvertedMass * correction;
b.Position += b.InvertedMass * correction;
}
Before doing any detailed examining of the code logic, I spotted this potential mistake:
result.normal = -normal / d;
Since d was set to normal.LengthSquared and not normal.Length as it should be, the applied position correction could either be (much) smaller or (much) bigger than intended. Given that your objects are "sticking together", it is likely to be the former, i.e. d > 1.
(The fix is of course simply result.normal = -normal / Math.Sqrt(d);)
Note that the above may not be the only source of error; let me know if there is still undesirable behavior.
Although your tag specifies C#; here are basic AABB to AABB & AABB to Circle collisions that are done in C++ as these are take from: LernOpenGL:InPractice:2DGame : Collision Detection
AABB - AABB Collsion
// AABB to AABB Collision
GLboolean CheckCollision(GameObject &one, GameObject &two) {
// Collision x-axis?
bool collisionX = one.Position.x + one.Size.x >= two.Position.x &&
two.Position.x + two.Size.x >= one.Position.x;
// Collision y-axis?
bool collisionY = one.Position.y + one.Size.y >= two.Position.y &&
two.Position.y + two.Size.y >= one.Position.y;
// Collision only if on both axes
return collisionX && collisionY;
}
AABB To Circle Collision Without Resolution
// AABB to Circle Collision without Resolution
GLboolean CheckCollision(BallObject &one, GameObject &two) {
// Get center point circle first
glm::vec2 center(one.Position + one.Radius);
// Calculate AABB info (center, half-extents)
glm::vec2 aabb_half_extents(two.Size.x / 2, two.Size.y / 2);
glm::vec2 aabb_center(
two.Position.x + aabb_half_extents.x,
two.Position.y + aabb_half_extents.y
);
// Get difference vector between both centers
glm::vec2 difference = center - aabb_center;
glm::vec2 clamped = glm::clamp(difference, -aabb_half_extents, aabb_half_extents);
// Add clamped value to AABB_center and we get the value of box closest to circle
glm::vec2 closest = aabb_center + clamped;
// Retrieve vector between center circle and closest point AABB and check if length <= radius
difference = closest - center;
return glm::length(difference) < one.Radius;
}
Then in the next section of his online tutorial he shows how to do Collision Resolution using the above method found here: LearnOpenGL : Collision Resolution
In this section he adds an enumeration, another function and an std::tuple<> to refine the above detection system while trying to keep the code easier & cleaner to manage and read.
enum Direction {
UP,
RIGHT,
DOWN,
LEFT
};
Direction VectorDirection(glm::vec2 target)
{
glm::vec2 compass[] = {
glm::vec2(0.0f, 1.0f), // up
glm::vec2(1.0f, 0.0f), // right
glm::vec2(0.0f, -1.0f), // down
glm::vec2(-1.0f, 0.0f) // left
};
GLfloat max = 0.0f;
GLuint best_match = -1;
for (GLuint i = 0; i < 4; i++)
{
GLfloat dot_product = glm::dot(glm::normalize(target), compass[i]);
if (dot_product > max)
{
max = dot_product;
best_match = i;
}
}
return (Direction)best_match;
}
typedef std::tuple<GLboolean, Direction, glm::vec2> Collision;
However there is a slight change to the original CheckCollsion() function for AABB to Circle by changing its declaration/definition to return a Collision instead of a GLboolean.
AABB - Circle Collision With Collision Resolution
// AABB - Circle Collision with Collision Resolution
Collision CheckCollision(BallObject &one, GameObject &two) {
// Get center point circle first
glm::vec2 center(one.Position + one.Radius);
// Calculate AABB info (center, half-extents)
glm::vec2 aabb_half_extents(two.Size.x / 2, two.Size.y / 2);
glm::vec2 aabb_center(two.Position.x + aabb_half_extents.x, two.Position.y + aabb_half_extents.y);
// Get difference vector between both centers
glm::vec2 difference = center - aabb_center;
glm::vec2 clamped = glm::clamp(difference, -aabb_half_extents, aabb_half_extents);
// Now that we know the the clamped values, add this to AABB_center and we get the value of box closest to circle
glm::vec2 closest = aabb_center + clamped;
// Now retrieve vector between center circle and closest point AABB and check if length < radius
difference = closest - center;
if (glm::length(difference) < one.Radius) // not <= since in that case a collision also occurs when object one exactly touches object two, which they are at the end of each collision resolution stage.
return std::make_tuple(GL_TRUE, VectorDirection(difference), difference);
else
return std::make_tuple(GL_FALSE, UP, glm::vec2(0, 0));
}
Where the above functions or methods are called within this function that does the actually logic if a collision is detected.
void Game::DoCollisions()
{
for (GameObject &box : this->Levels[this->Level].Bricks)
{
if (!box.Destroyed)
{
Collision collision = CheckCollision(*Ball, box);
if (std::get<0>(collision)) // If collision is true
{
// Destroy block if not solid
if (!box.IsSolid)
box.Destroyed = GL_TRUE;
// Collision resolution
Direction dir = std::get<1>(collision);
glm::vec2 diff_vector = std::get<2>(collision);
if (dir == LEFT || dir == RIGHT) // Horizontal collision
{
Ball->Velocity.x = -Ball->Velocity.x; // Reverse horizontal velocity
// Relocate
GLfloat penetration = Ball->Radius - std::abs(diff_vector.x);
if (dir == LEFT)
Ball->Position.x += penetration; // Move ball to right
else
Ball->Position.x -= penetration; // Move ball to left;
}
else // Vertical collision
{
Ball->Velocity.y = -Ball->Velocity.y; // Reverse vertical velocity
// Relocate
GLfloat penetration = Ball->Radius - std::abs(diff_vector.y);
if (dir == UP)
Ball->Position.y -= penetration; // Move ball bback up
else
Ball->Position.y += penetration; // Move ball back down
}
}
}
}
// Also check collisions for player pad (unless stuck)
Collision result = CheckCollision(*Ball, *Player);
if (!Ball->Stuck && std::get<0>(result))
{
// Check where it hit the board, and change velocity based on where it hit the board
GLfloat centerBoard = Player->Position.x + Player->Size.x / 2;
GLfloat distance = (Ball->Position.x + Ball->Radius) - centerBoard;
GLfloat percentage = distance / (Player->Size.x / 2);
// Then move accordingly
GLfloat strength = 2.0f;
glm::vec2 oldVelocity = Ball->Velocity;
Ball->Velocity.x = INITIAL_BALL_VELOCITY.x * percentage * strength;
//Ball->Velocity.y = -Ball->Velocity.y;
Ball->Velocity = glm::normalize(Ball->Velocity) * glm::length(oldVelocity); // Keep speed consistent over both axes (multiply by length of old velocity, so total strength is not changed)
// Fix sticky paddle
Ball->Velocity.y = -1 * abs(Ball->Velocity.y);
}
}
Now some of the code above is GameSpecific as in the Game class, Ball class, Player etc. where these are considered and inherited from a GameObject, but the algorithm itself should provide useful as this is exactly what you are looking for but from a different language. Now as to your actually problem it appears you are using more than basic motion as it appears you are using some form of kinetics that can be seen from your Resolve() method.
The overall Pseudo Algorithm for doing AABB to Circle Collision with Resolution would be as follows:
Do Collisions:
Check For Collision: Ball With Box
Get Center Point Of Circle First
Calculate AABB Info (Center & Half-Extents)
Get Difference Vector Between Both Centers
Clamp That Difference Between The [-Half-Extents, Half-Extents]
Add The Clamped Value To The AABB-Center To Give The Point Of Box Closest To The Circle
Retrieve & Return The Vector Between Center Circle & Closest Point AABB & Check If Length Is < Radius (In this case a Collision).
If True Return tuple(GL_TRUE, VectorDirection(difference), difference))
See Function Above For VectorDirection Implementation.
Else Return tuple(GL_FALSE, UP, glm::vec2(0,0))
Perform Collision Resolution (Test If Collision Is True)
Extract Direction & Difference Vector
Test Direction For Horizontal Collision
If True Reverse Horizontal Velocity
Get Penetration Amount (Ball Radius - abs(diff_vector.x))
Test If Direction Is Left Or Right (W,E)
If Left - Move Ball To Right (ball.position.x += penetration)
Else Right - Move Ball To Left (ball.position.x -= penetration)
Else Test Direction For Vertical Collision
If True Reverse Vertical Velocity
Get Penetration Amount (Ball Radius - abs(diff_vector.y))
Test If Direction Is Up Or Down (N,S)
If Up - Move Ball Up (ball.position.y -= penetration)
Else Down - Move Ball Down (ball.position.y += penetration)
Now the above algorithm shown assumes that the boxes are not rotated and that their top & bottom edges are parallel with the horizontal and that their sides are parallel with the left and right edges of the window-screen coordinates. Also in the bottom section with the vertical displacement this also assumes that the top left corner of the screen - the first pixel is (0,0), thus the opposite operation for vertical displacement. This also assumes 2D collisions and not 3D Ridged or Ragdoll type collisions. You can use this to compare against your own source - implementation, but as far as just looking at your code without running it through a debugger it is extremely hard for me to see or find out what is actually causing your bug. I hope this provides you with the help that you need.
The above code from the mentioned OpenGL tutorial website does work as I have tested it myself. This algorithm is of the simplest of collision detections and is by far no means a comprehensive system and it still has caveats or pitfalls not mentioned here, but does suffice for the application it was used in. If you need more information about Collision Detections there is a few chapters that can be read in Ian Millington's book Game Physics Engine Development Although his book is based on a generalized 3D Physics Engine and only briefly discuses Collision Detection as their are full Books dedicated to the growing popularity of such complex beasts.
I am using the Leap Motion device to get usable data on the position and orientation of my hand. At the moment I am having trouble with the orientation segment of the coding. The Leap API only has code for frame by frame rotation, however, it also provides a normal vector (normal to the palm of the hand) and a pointer vector (pointing in the direction from the palm outwards towards the fingers). These two vectors are perpendicular.
The vectors:
Leap.Vector normal = current.PalmNormal;
Leap.Vector pointer = current.Direction;
More information can be found on the Leap Hand API: https://developer.leapmotion.com/documentation/Languages/CSharpandUnity/API/class_leap_1_1_hand.html
Converting to Unity Vector3 class:
Vector3 normalU = new Vector3();
normalU.x = normal.x;
normalU.y = normal.y;
normalU.z = normal.z;
Vector3 pointerU = new Vector3();
pointerU.x = pointer.x;
pointerU.y = pointer.y;
pointerU.z = pointer.z;
I use these to vectors to calculate the Euler Angles orientation of my hand (a rotation of theta_x degrees about the x-axis, theta_y degrees about the y-axis, and theta_z degrees about the z-axis) using the code:
float rXn = Mathf.Atan (normalU.y/normalU.z);
rXn *= (180f/Mathf.PI);
if ((normalU.y < 0 && normalU.z < 0) || (normalU.y > 0 && normalU.z < 0))
{
rXn += 180f;
}
float rZn = Mathf.Atan (normalU.y/normalU.x);
rZn *= (180f/Mathf.PI);
if ((normalU.y < 0 && normal.x > 0) || (normalU.y > 0 && normalU.x > 0))
{
rZn += 180f;
}
float rYn = Mathf.Atan (pointerU.x/pointerU.z);
rYn *= (180f/Mathf.PI);
if ((pointerU.x > 0 && pointerU.z > 0) || (pointerU.x < 0 && pointerU.z > 0))
{
rYn += 180f;
}
The Euler Angles are then converted to a Quaternion and implemented using the code:
Quaternion rotation = Quaternion.Euler (-rZn+90, -rYn, rXn+90);
rigidbody.MoveRotation (rotation);
More information on the Unity Quaternion class can be found here: http://docs.unity3d.com/Documentation/ScriptReference/Quaternion.html
As I coded this, I tested each axis of rotation individually, commenting out the others (setting them to 0), and they worked properly. However, when I implemented all three at once, the behaviors of rotations around an individual axis changed, which confuses me. Why would including recognition of rotation about the y-axis change the way rotation about the x-axis occurs?
As each individual axis of rotation worked when the others were commented out (and set to 0), I think the problem lies in the way the Euler Angles are converted to a Quaternion. I do not have a great understanding of the way Quaternions are used to represent rotations, however I am confused as to why changing the value of the angle of rotation about the y-axis would change the angle of rotation about the x-axis.
Thanks for your help.
The order of rotation is relevant, and this might be what causes your confusion. Imagine a point on the x-axis at (1, 0, 0). When we now do a rotation of 90° around the x axis, nothing happens. Then we do a rotation of 90° around the y axis, which makes the point lie on the positive z-axis. If we change the order of rotation, the point will end on the y axis. Depending on the way your functions are implemented, they require a certain order of rotation to get the expected results.
It's not perfect, but I am getting pretty good results with:
private void UpdateHandNormal( Hand hand, Transform marker )
{
float y = Mathf.Atan( hand.Direction.x / hand.Direction.z );
if( float.IsNaN( y ) ) y = 0;
marker.localRotation = new Quaternion( hand.PalmNormal.z, -y, hand.PalmNormal.x, 1 ) ;
}
Where hand is the Hand instance from the leap controller and marker is a simple rectangle representing the hand rotation.
I was getting NaN for y so I added the set to 0 check.
ath
J.
I've got quite a lot of code here, but it's relatively straightforward.
This is all snippets from different classes, all references are right, but I think I've done a math-based error somewhere and I can't find it. It always finds a collision on the y axis a pixel before it should. I haven't tried it with different X axis positions but it seems to fall past blocks next to it fine.
The struct "mapSection" just contains two Vector2s- A top-left block and bottom-left block coordinate.
tileManager.def_ts is the default tile width and height (32). The player's size is 32x64.
The toWorldSpace function does nothing right now other than return so that's not the problem.
When I say block coordinate I mean which index the block is in the tile array (Ex 0, 0 is the first block, 0, 1 is the second block on the Y axis, 1, 3 is 1 block in on the X axis and 3 on the Y axis, I do not mean actual pixels.)
From tile engine class:
public mapSection toMapMinMax(Vector2 position, Vector2 size)
{
position = toWorldSpace(position);
position.X = (float)Math.Floor(position.X / tileManager.def_ts);
position.Y = (float)Math.Floor(position.Y / tileManager.def_ts);
size.X = (float)Math.Floor(size.X / tileManager.def_ts);
size.Y = (float)Math.Floor(size.Y / tileManager.def_ts);
return new mapSection(position, position + size);
}
public bool collision(Vector2 screenPosition, Vector2 size)
{
mapSection mapCollisionPossibilities = toMapMinMax(screenPosition, size);
for (int y = (int)mapCollisionPossibilities.topLeft.Y; y <= mapCollisionPossibilities.bottomRight.Y; y++)
{
for (int x = (int)mapCollisionPossibilities.topLeft.X; x <= mapCollisionPossibilities.bottomRight.X; x++)
{
if (x >= 0 && y >= 0 && y < tiles.Count && x < tiles[y].Count)
{
if (tileManager.tileTypes[tiles[y][x]].collideable == true)
{
return true;
}
}
}
}
return false;
}
And this is the code from the player class:
if (!tEngine.collision(position + new Vector2(0, 1), new Vector2(32, 64)))
{
position.Y += 1;
}
I add "Vector2(0, 1)" because I want to see if there's a collision a pixel further down; so that he falls until he hits something. It's very basic right now but it's only to test the collision engine, which isn't working.
There's a picture of the error. You can see the player is a pixel too high.
In the picture, "X:" is the top-left block coordinate on X axis, "X2:" is the bottom-right block coordinate on the X axis, and same with "Y:" and "Y2: except Y axis. They're read from the mapSection directly.
If anyone can notice why this is happening, it would be massively appreciated.
Thanks.
If you cannot understand any section of the code just post in the comments and I'll be happy to explain, or if you think I've been a bit too unspecific in some area of this post.
EDIT: For the tile coordinates issue, your toMapMinMax code should be more like this:
EDIT2: have subtracted (1, 1) from bottomRight, since it is a size we are adding.
public mapSection toMapMinMax(Vector2 position, Vector2 size)
{
Vector2 topLeft = position;
Vector2 bottomRight = position + size - new Vector2(1, 1);
topLeft.X = (float)Math.Floor(topLeft.X / tileManager.def_ts);
topLeft.Y = (float)Math.Floor(topLeft.Y / tileManager.def_ts);
bottomRight.X = (float)Math.Floor(bottomRight.X / tileManager.def_ts);
bottomRight.Y = (float)Math.Floor(bottomRight.Y / tileManager.def_ts);
return new mapSection(topLeft, bottomRight);
}
Also, I was wrong in my above comment; you do want <= signs in your two for loops, because most of the time you will be checking 6 tiles.
for the off-by-one-pixel issue:
In order for you to see the character off by some amount of pixels, the draw code and the collision code must be different. If they were identical, for example if they were both off by 15 pixels (you collide 15 pixels too early, but you are also drawing 15 pixels ahead), you wouldn't see any change.
The 1 pixel gap indicates a 1 pixel difference between the draw coordinate calculation and the collision coordinate calculation. This 1 pixel difference is most likely caused by differences in rounding, probably that you are calling Math.Floor in the collision code, but are not rounding the coordinates in the draw code. (I would guess you are probably just passing the position Vector2 straight to the SpriteBatch.Draw method).
Basically what I'm trying to do is shade a 2D heightmap using a very very basic raycasting system that basically just checks if the ray is intercepted before it should be to shade it. However it's not working correctly and I've been banging my head for several hours now on this so I figured it couldn't hurt to turn it over to you guys, because I think it's probably something either so blindingly obvious that I won't see it or so complex that I'll never wrap my head around it.
I have a map like this:
And the raycasting is giving me this (keep in mind it's just debug colors; red is ray interception, but before intended position (so shading), blue would be ray interception in the correct place (so highlights or just as-is), and yellow means that point had no ray interaction at all before the while loop cut-out).
The result should be with red on backfacing slopes and areas behind large mountains (shadows) and blue on sun-facing slopes (highlights). There should not be any yellow. So this image indicates that either all of the rays are hitting the wrong place, or the rays are being intersected ALWAYS somewhere else before they reach their target, which is impossible.
At this point I highly suspect the problem is with my trig.
Here's the Ray class:
class Ray
{
public Vector2 Position;
public Vector2 Direction; // Think in XZ coordinates for these (they are on a perpendicular plane to the heightmap)
// Angle is angle from horizon (I think), and height is height above zero (arbitrary)
public float Angle, Height;
private TerrainUnit[,] Terrainmap;
private float U, V;
public Ray(ref TerrainUnit[,] Terrainmap, float height, float angle)
{
this.Terrainmap = Terrainmap;
this.Angle = angle;
this.Height = this.V = height;
// Create new straight vector
this.Direction = new Vector2(0, 1);
// Rotate it to the values determined by the angle
this.Direction = Vector2.Transform(Direction, Matrix.CreateRotationX(Angle));
//this.Direction = new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle));
// Find the horizontal distance of the origin-destination triangle
this.U = V / (float)Math.Tan(Angle);
// Bleh just initialize the vector to something
this.Position = new Vector2(U, V);
}
public void CastTo(int x, int y)
{
// Get the height of the target terrain unit
float H = (float)Terrainmap[x, y].Height;
// Find where the ray would have to be to intersect that terrain unit based on its angle and height
Position = new Vector2(x - U, H + V);
float Z = 1000 * (float)Terrainmap[0, y].Height;
// As long as the ray is not below the terrain and not past the destination point
while (Position.Y > Z && Position.X <= x)
{
// If the ray has passed into terrain bounds update Z every step
if (Position.X > 0) Z = 1000 * (float)Terrainmap[(int)Position.X, y].Height;
Position.X += Direction.X;
Position.Y += Direction.Y;
}
Terrainmap[x, y].TypeColor = Color.Yellow;
if ((int)Position.X == x) Terrainmap[x, y].TypeColor = Color.Blue;
else Terrainmap[x, y].TypeColor = Color.Red;
}
}
Also just as a formality, the function that is casting each ray and how I am calling that:
if (lighting) CastSunRays(1f, MathHelper.PiOver4);
private void CastSunRays(float height, float angle)
{
Ray ray = new Ray(ref Terrainmap, height, angle);
for (int x = 0; x < Width; x++)
for (int y = 0; y < Height; y++)
ray.CastTo(x, y);
}
I ended up using a much simpler approach with Bresenham's Line Algorithm to find the intercept point; I imagine it's much faster and more efficient than the way I was trying to do it would have been.
My guess is that when your Direction vector is applied to Position, it oversteps the lower limit (Position.Y > -1) before it has a chance to hit the surface (Position.Y <= Terrainmap[(int)Position.X, y].Height).
You could try to decrease the lower limit, or re-order your if/while tests.
Another problem might be that the Direction Vector is too large in comparison to your height-range. The distance between two neighboring pixels is 1, while the whole range of height differences is contained in the range (-1,1). This gives a very flat surface from the ray-casters point of view. When the Direction vector is applied to the Position vector is takes a relatively small step over the length, and a relatively large step over the height.
#Maltor: I actually wanted to comment your own answer, but due to my reputation am not currently able to.
I also used the bresenham's line approach and decreased calculation time to 1/10!
A running example of that can be viewed at my github project TextureGenerator-Online.
The terrain tool uses this approach.
See function setTerrainShadow() at tex_terrain.js