Xna Isometric point to click movement - c#

I'm working on an isometric game (diamond grid) and I've stumbled across a minor problem regarding a character movement.
I'm using A* to find a path between 2 points and then I want to move my character from point A to point B going through all the tiles which form the path but I can't find a way to do this , I mean a simpler and accurate method.
So far I've scrapped this piece of code but it's kinda "rusty"
public void Destination(tile destination)
{
for (int i = 0; i < 8; i++)
{
if (AdjacentTile[i] == destination)
{
characterDirection = i;
}
}
animation.changeSpriteDirection(characterDirection); //After I found which adjacent tile is the next destination I change the character direction based on it's position (1 = North , 2 = Nort Est etc) .. so the Y of the Animation_sourceRectangle it's changed//
Vector2 Position;
Position.X = current_characterTile.X - destination.X;
Position.Y = current_characterTile.Y - destination.Y;
rotation = (float)Math.Atan2(-Position.X, Position.Y);
moveVector = (Vector2.Transform(new Vector2(0, -1), Matrix.CreateRotationZ(rotation))) * characterSpeed;
movingCommand = 1; // the character is supposed to be moving..
Move(); //this function moves the sprite until the *tile.i and tile.j* of the character is the same as tile.j and tile.i of the destination
//something like this
if ( characterTile.i == destination.i && characterTile.j == destination.j)
movingCommand = 0 //stop
else
character_Position += moveVector;
}
If anyone could give me a hint on what to do or help me I'll be very grateful.
Thank You.

Possibilities:
At each tile, determine the character's speed vector and also determine how much time it will take for the character to move to next tile. When that time elapses, immediately begin moving to the next tile. (This is what I implemented below.)
At each tile, determine the character's speed vector. Then, when the character is sufficiently close to the next tile (say, the difference between their X and Y coordinates is less than 2 pixels?), snap it to the tile and begin moving to the next tile. This will causes artifacts and be in general less precise.
A solution:
Let's assume you already ran your pathfinding algorithm and found a linked list of a tiles that you must go through to arrive at target. Let's also assume those tiles cannot become blocked partway through the movement (it is simple to modify the algorithm if they can, though).
I usually do something like this to handle this problem:
Run the pathfinding algorithm, which returns a List, if a path
exists.
character.Path = theListThatAStarReturned;
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
The beginMovingToTarget() method will determine the velocity vector and also determine the the time needed to arrive at the tile. When the time is reached, we immediately go to the next tile, until the Path is empty. Let's call this time variable character.timeToArrival.
Update():
if (!character.Moving) return; // Or just don't execute the rest of this code.
character.position += character.speed * elapsedSeconds;
character.timeToArrival -= elapsedSeconds;
// Did the character arrive in a tile?
if (character.timeToArrival <= 0)
{
// This will ensure the character is precisely in the tile, not a few pixels veered off.
character.position = character.movingToTile.position;
if (character.Path.Count == 0)
{
character.Moving = false;
// We are at final destination.
}
else
{
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
}
}
And the beginMovingToTarget(targetTile) function:
this.movingToTile = targetTile;
Vector2 direction;
direction = targetTile.position - this.position;
this.timeToArrival = direction.Length() / this.speedPerSeconds;
direction.Normalize();
direction *= this.speedPerSeconds;
this.speed = direction;
// Here, you may also want to change the character's animation, if you want to, or you may do that directly in the Draw() method based on its speed vector.
Make sure the division is in floats, not integers.

Related

How to compare between two lines?

I have a code that allows me to draw lines and limit the number of lines that can be drawn.
My problem is that I want to create a line (with for example line renderer)
and then allow the user to try drawing a similar (not necessarily exactly the same) line and the code needs to know according to the setting if the line is similar enough or not, but I can't figure it.
I would appreciate any tips.
public class DrawLine : MonoBehaviour
{
public GameObject linePrefab;
public GameObject currentLine;
public LineRenderer lineRenderer;
public EdgeCollider2D edgeCollider;
public List<Vector2> fingerPositions;
public Button[] answers;
public bool isCurrButtonActive;
int mouseButtonState = 0;
void Update()
{
Debug.Log(rfgrhe);
if (isCurrButtonActive)
{
if (Input.GetMouseButtonDown(0))
{
if (mouseButtonState == 0)
{
CreateLine();
}
}
if (Input.GetMouseButtonUp(0))
{
mouseButtonState++;
}
if (Input.GetMouseButton(0))
{
if (mouseButtonState == 1)
{
Debug.Log(Input.mousePosition.ToString());
if (Input.mousePosition.x < 100 || Input.mousePosition.y > 420 || Input.mousePosition.x > 660 || Input.mousePosition.y < 7)
{
return;
}
Vector2 tempFingerPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Vector2.Distance(tempFingerPos, fingerPositions[fingerPositions.Count - 1]) > .1f)
{
UpdateLine(tempFingerPos);
}
}
}
}
}
void CreateLine()
{
mouseButtonState++;
currentLine = Instantiate(linePrefab, Vector3.zero, Quaternion.identity);
lineRenderer = currentLine.GetComponent<LineRenderer>();
edgeCollider = currentLine.GetComponent<EdgeCollider2D>();
fingerPositions.Clear();
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
lineRenderer.SetPosition(0, fingerPositions[0]);
lineRenderer.SetPosition(1, fingerPositions[1]);
edgeCollider.points = fingerPositions.ToArray();
}
void UpdateLine(Vector2 newFingerPos)
{
fingerPositions.Add(newFingerPos);
lineRenderer.positionCount++;
lineRenderer.SetPosition(lineRenderer.positionCount - 1, newFingerPos);
edgeCollider.points = fingerPositions.ToArray();
}
public void ActivateCurrentButton()
{
// Debug.Log(isCurrButtonActive);
isCurrButtonActive = true;
for (int i = 0; i < answers.Length; i++)
{
if (answers[i].CompareTag("onePoint"))
{
answers[i].GetComponent<MapLvl>().isCurrButtonActive = false;
}
else if (answers[i].CompareTag("TwoPoints"))
{
answers[i].GetComponent<DrawLine>().isCurrButtonActive = false;
}
}
}
}
For example in that case, the blue line is the correct one, the green and the red ones are two options of an answer from the user.
What I want is that the program will acknolage only the green line as a correct answer.
EDIT: Since it's clearer now what we want, here's a way to achieve it:
The function float DifferenceBetweenLines(Vector3[], Vector3[]) below gives you a measure of the "distance between the two lines".
It walks along the line to match with a maximum step length, and for each point, computes the distance from the closest point on the draw line.
It sums the squares of those distances and divide them by the length of the line to match (don't ask me to explain this with mathematical rigor).
The smaller the return value, the closer the first line matches the second -- the threshold is yours to decide.
float DifferenceBetweenLines(Vector3[] drawn, Vector3[] toMatch) {
float sqrDistAcc = 0f;
float length = 0f;
Vector3 prevPoint = toMatch[0];
foreach (var toMatchPoint in WalkAlongLine(toMatch)) {
sqrDistAcc += SqrDistanceToLine(drawn, toMatchPoint);
length += Vector3.Distance(toMatchPoint, prevPoint);
prevPoint = toMatchPoint;
}
return sqrDistAcc / length;
}
/// <summary>
/// Move a point from the beginning of the line to its end using a maximum step, yielding the point at each step.
/// </summary>
IEnumerable<Vector3> WalkAlongLine(IEnumerable<Vector3> line, float maxStep = .1f) {
using (var lineEnum = line.GetEnumerator()) {
if (!lineEnum.MoveNext())
yield break;
var pos = lineEnum.Current;
while (lineEnum.MoveNext()) {
Debug.Log(lineEnum.Current);
var target = lineEnum.Current;
while (pos != target) {
yield return pos = Vector3.MoveTowards(pos, target, maxStep);
}
}
}
}
static float SqrDistanceToLine(Vector3[] line, Vector3 point) {
return ListSegments(line)
.Select(seg => SqrDistanceToSegment(seg.a, seg.b, point))
.Min();
}
static float SqrDistanceToSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point) {
var projected = ProjectPointOnLineSegment(linePoint1, linePoint1, point);
return (projected - point).sqrMagnitude;
}
/// <summary>
/// Outputs each position of the line (but the last) and the consecutive one wrapped in a Segment.
/// Example: a, b, c, d --> (a, b), (b, c), (c, d)
/// </summary>
static IEnumerable<Segment> ListSegments(IEnumerable<Vector3> line) {
using (var pt1 = line.GetEnumerator())
using (var pt2 = line.GetEnumerator()) {
pt2.MoveNext();
while (pt2.MoveNext()) {
pt1.MoveNext();
yield return new Segment { a = pt1.Current, b = pt2.Current };
}
}
}
struct Segment {
public Vector3 a;
public Vector3 b;
}
//This function finds out on which side of a line segment the point is located.
//The point is assumed to be on a line created by linePoint1 and linePoint2. If the point is not on
//the line segment, project it on the line using ProjectPointOnLine() first.
//Returns 0 if point is on the line segment.
//Returns 1 if point is outside of the line segment and located on the side of linePoint1.
//Returns 2 if point is outside of the line segment and located on the side of linePoint2.
static int PointOnWhichSideOfLineSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point){
Vector3 lineVec = linePoint2 - linePoint1;
Vector3 pointVec = point - linePoint1;
if (Vector3.Dot(pointVec, lineVec) > 0) {
return pointVec.magnitude <= lineVec.magnitude ? 0 : 2;
} else {
return 1;
}
}
//This function returns a point which is a projection from a point to a line.
//The line is regarded infinite. If the line is finite, use ProjectPointOnLineSegment() instead.
static Vector3 ProjectPointOnLine(Vector3 linePoint, Vector3 lineVec, Vector3 point){
//get vector from point on line to point in space
Vector3 linePointToPoint = point - linePoint;
float t = Vector3.Dot(linePointToPoint, lineVec);
return linePoint + lineVec * t;
}
//This function returns a point which is a projection from a point to a line segment.
//If the projected point lies outside of the line segment, the projected point will
//be clamped to the appropriate line edge.
//If the line is infinite instead of a segment, use ProjectPointOnLine() instead.
static Vector3 ProjectPointOnLineSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point){
Vector3 vector = linePoint2 - linePoint1;
Vector3 projectedPoint = ProjectPointOnLine(linePoint1, vector.normalized, point);
switch (PointOnWhichSideOfLineSegment(linePoint1, linePoint2, projectedPoint)) {
case 0:
return projectedPoint;
case 1:
return linePoint1;
case 2:
return linePoint2;
default:
//output is invalid
return Vector3.zero;
}
}
The math functions at the end are from 3d Math Functions - Unify Community Wiki
Here is how it can be used to compare a LineRenderer against another:
Array.Resize(ref lineBuffer1, lineRenderer1.positionCount);
Array.Resize(ref lineBuffer2, lineRenderer2.positionCount);
lineRenderer1.GetPositions(lineBuffer1);
lineRenderer2.GetPositions(lineBuffer2);
float diff = DifferenceBetweenLines(lineBuffer1, lineBuffer2);
const float threshold = 5f;
Debug.Log(diff < threshold ? "Pretty close!" : "Not that close...");
A few things to consider:
The performance of SqrDistanceToLine could definitely be improved on
You get a measure of how close the first line matches the second, not the other way around -- that is, the first line can be longer or go for a walk mid-way as long as it comes back on track and "covers" the other line closely enough. You can solve this by calling DifferenceBetweenLines a second time, swapping the arguments, and taking the biggest result of them two.
We could work with Vector2 instead of Vector3
Original answer:
Similar?
As #Jonathan pointed out, you need to be more precise about "similar enough":
does similarity in size matter ?
does orientation matter ?
do similarity in proportions matter (or only the "changes of direction" of the line) ?
...
As you might guess, the fewer of those criteria matter, the harder it will be; because
your concept of similarity will become more and more abstract from the raw positions
you've got in the first place.
For example, if the user needs to draw a cross, with exactly two strokes,
that cover more or less a defined area, the task is as easy as it gets:
You can measure the distance between the area's corners and each stroke's first
and last points, and check that the lines are kind of straight.
If you want to check if the user drew a perfect heart-shape, in any orientation,
it's noticeably trickier...
You might have to resort to a specialized library for that.
Another thing to consider is, does the user really need to make a line similar to another one,
or should it only be close enough that it can be differentiated from other possible lines?
Consider this example:
The user needs to draw either a cross (X) or a circle (O):
If there is only one stroke that comes back close to the starting point, assume a circle.
If there is strokes whose general directions are orthogonal, assume a cross.
In this case, a more involved system would probably be overkill.
A few "raw pointers"
Assuming simple requirements (because assuming the opposite, I wouldn't be able to help much),
here are a few elements:
Exact match
The user has to draw on top of a visible line: this is the easiest scenario.
For each point of his line, find out the distance from the closest point on the reference line.
Sum the square of those distances -- for some reason it works better than summing the distances
themselves, and it's also cheaper to compute the square distance directly.
LineRenderer.Simplify
Very specific to your use-case, since you're using Unity's LineRenderer,
it's worth knowing that it packs a Simplify(float) method, that decreases the
resolution of your curve, making it easier to process, and particularly effective
if the line to match is made of somewhat straight segments (not complex curves).
Use the angles
Sometimes you'll want to check the angles between the different sub-segments of your line,
instead of their (relative) lengths. It will measure changes in direction regardless
of the proportions, which can be more intuitive.
Example
A simple example that detects equilateral triangles:
LineRenderer.Simplify
close the shape if the ends are close enough
check the angles are ~60deg each:
For arbitrary lines, you could run the line to match through the same "filter" as the lines the user draws, and compare the values. It will be yours to decide what properties matter most (angles/distances/proportions...), and what's the threshold.
Personally I would take points along the users line and then figure out the angles on the lines and if the average angle is within a specific range then it is acceptable. If the points you draw angles from are close enough together then you should have a pretty accurate idea whether the user is close to the same line.
Also, if the line needs to be in a particular area then you can just check and make sure the line is within a specified distance of the "control" line. The math for these should be pretty simple once you have the points. I am sure there are many other ways to implement this, but I personally would do this. Hope this helps!

Getting the "border Y-values" in Unity 2D

My problem is pretty simple, but yet I struggle to find an answer. Brief context, I have a sprite that moves accross the Y-axis according to microphone input. Beforehand, I prompted the user to record their lowest and highest note, calculated the frequency of these two notes and am using it as a reference for positioning the sprite on the Y-axis.
Let's say the lowest note is 100 Hz and the highest one 400 Hz. So if the player makes a tone of 100 Hz, the sprite should move down to the bottom of the Y-axis. To move back to the center (Y-position 0), the player would have to make a tone of 250 Hz (midpoint between 100 and 400).
So we know that for that player, 250 Hz equals to Y-position 0. But I need to know the Y-position equivalents of the lowest note (bottom edge) and highest note (top edge). When I move the sprite manually to the top edge and look at the Y-value in the inspector, it's apparently 4.58. But I'm not sure if hard-coding 4.58 would scale well across different screen-sized devices.
Screenshot here: https://i.ibb.co/pjrkSNV/Capture.png
I ideally want to have a method called FrequencyToY(float frequency) that converts a frequency value to the corresponding Y-value on the axis. I saved the lowest and highest frequency values in PlayerPrefs. Important note about the sprite movement, I don't want gravity. The bird should just smoothly move to the corresponding Y-position every time the player produces a tone, and stay in place otherwise.
This is my current script attached to my sprite:
public class Player : MonoBehaviour
{
public AudioSource audioPlayer;
public AudioMixer masterMixer;
private float[] _spectrum;
private float _fSample;
private Transform playerTransform;
void Start()
{
playerTransform = transform;
//Code for microphone loop
masterMixer.SetFloat("masterVolume", -80f);
_spectrum = new float[AudioAnalyzer.QSamples];
_fSample = AudioSettings.outputSampleRate;
audioPlayer.clip = Microphone.Start("", true, 10, 44100);
audioPlayer.loop = true;
while(!(Microphone.GetPosition("") > 0)) { }
audioPlayer.Play();
}
void Update()
{
//Calculate frequency of currently detected tones
audioPlayer.GetSpectrumData(_spectrum, 0, FFTWindow.BlackmanHarris);
float pitchVal = AudioAnalyzer.calculateFrequency(ref _spectrum, _fSample);
if(pitchVal != 0)
{
if (pitchVal < PlayerPrefs.GetFloat("lowestFrequency"))
pitchVal = PlayerPrefs.GetFloat("lowestFrequency");
else if (pitchVal < PlayerPrefs.GetFloat("highestFrequency"))
pitchVal = PlayerPrefs.GetFloat("highestFrequency");
//This is how I'd like to call the function
//But if someone could change this and make the sprite actually
//"move" to that point instead of just popping there it would be awesome!
transform.position = new Vector2(0, FrequencyToY(pitchVal));
}
}
//Converts frequency to position on Y-axis
public float FrequencyToY(float frequency)
{
float x = 0;
return x;
}
}
You need to define the range of your Y coordinates that you want to use (say, 0 -> 100) and then scale the pitch you're given against their scale (so what % on 0 - 100 of their scale) and then use that % as a point on Y.
Thus, if they give you a note that's exactly in the middle of their scale (50%) and your Y coordinates in game range from 0-10, you want to put them at 5.

AABB vs Circle collision in custom physics engine

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.

XNA Tile based Game: Checking collision between 2 objects using their grid position inside a 2D array

I was wondering if anyone could help me figure out how to check collisions between 2 objects inside a 2d array.
I'm trying to make a simple 2D top down game. The map is made up of a 20x20 2D array of squares. The player and the enemies are all squares and each take up one square inside the grid. The grid is made up of multiple squares of different types. So far I have floor squares(which the player and enemies will be allowed to move along), Wall squares(Which the player and enemies cannot move past or along) and then the enemy and players squares. Here is a screenshot of it:
I create and initialise the 2D array in the Game Class. Using numbers from 0-3 I can select what each square will contain:
0- Floor square 1- Wall square 2- Enemy Square 3- player Square.
So firstly I have the player colliding with the wall squares by simply using a nested for loop and checking the grid positions Above, Below, Left and Right of the player to see if the the value within the 2D array is 1. If it isn't then that means it's not a wall which means the player is allowed to move.
However I can't use this method when checking collision between the player and enemy as I only use the value inside the 2D array(2 and 3) to choose where to originally draw the enemy and player squares. This means that the grid position that they are both currently on at any time contains a value of 0.
I thought I would go about it by storing the the "grid Position" of each player and enemy object. That way I could just check each grid block around the player to see if it was equal to the grid position of an enemy. If it was equal then I would set the movement vector that I use to move the player in that specific direction to (0,0) preventing them from moving in that direction. When the enemy is no longer within one block of the player then the movement vector is given back it's original value.
I've had some success with this but it only seems to work with one enemy object. With one enemy I can't pass through it from any angle but with the others I can pass right through them.
Note: It seems that the enemy furthest down the screen is the only enemy that the player square can collide with.
I've used break points and I can see that when I get close to any of the enemies I can pass through, it does actually run the check to see if the square next to the player is an enemy but it doesn't actually prevent the player from walking through the enemy.
So my question is, am I going about doing the collision wrong, is there a simpler way to do it? Or if perhaps there may be a mistake in my code which is stopping it from working?
Below is some of my code:
This first sample is for the player/enemy Collision. This is contained in the enemy class. As I stated before, I use the player and enemies grid position to check to see if they are within one square of each other, if they are then I set the players movement vector to 0,0. When they are no longer within one square of each other I reset the movement vector back to it's original value.
public void CheckCollision(Player playerObject)
{
//Check above player
if (playerObject.PlayerGridPosition.Y - 1 == gridPosition.Y && playerObject.PlayerGridPosition.X == gridPosition.X)
{
//north is a vector2 variable that I use to add to the players position in order to move them about the screen. I use it to update both the players position
//and the grid position. North, South, East and West are all similar except the values contained in each are for a specific direction
playerObject.north.X = 0;
playerObject.north.Y = 0;
//This bool is used to check for when an enemy is beside and no longer beside the player.
besidePlayer = true;
}
//Check below player
if (playerObject.PlayerGridPosition.Y + 1 == gridPosition.Y && playerObject.PlayerGridPosition.X == gridPosition.X)
{
playerObject.south.X = 0;
playerObject.south.Y = 0;
besidePlayer = true;
}
//Check to right of player
if (playerObject.PlayerGridPosition.Y == gridPosition.Y && playerObject.PlayerGridPosition.X + 1 == gridPosition.X)
{
playerObject.east.X = 0;
playerObject.east.Y = 0;
besidePlayer = true;
}
//This if statement just checks to see if any of the enemies are within a squares space of the player, if they are not then the besidePlayer bool is set to false
else if (playerObject.PlayerGridPosition.Y != gridPosition.Y && playerObject.PlayerGridPosition.X + 1 != gridPosition.X && playerObject.PlayerGridPosition.Y - 1 != gridPosition.Y && playerObject.PlayerGridPosition.X != gridPosition.X && playerObject.PlayerGridPosition.Y + 1 != gridPosition.Y && playerObject.PlayerGridPosition.X != gridPosition.X)
{
besidePlayer = false;
}
//When an enemy is no longer beside the player then we can reset all the North, South, East and West vector velues back to their original values.
if (besidePlayer == false)
{
playerObject.north.X = 0;
playerObject.north.Y = -1;
playerObject.south.X = 0;
playerObject.south.Y = 1;
playerObject.east.X = 1;
playerObject.east.Y = 0;
}
}
This next piece of code is for where I set the values inside the 2D array and create the layout of the level. It's also where I create the enemy objects and set the player objects position and grid position, using the row,col of their specific "number" on the grid in order to choose where to draw them.
Note: The rows and columns are flipped as otherwise the level gets drawn sidewards.
public void LoadLevels(int level)
{
//More levels will be added in later.
if(level == 1)
{
//Here I set the values inside the 2D array "grid". It is a 20x20 array.
//0 = Floor Square, 1 = Wall square, 2 = Enemy Square, 3 = Player Square
grid = new int[maxRows, maxCols]
{
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1},
{1,0,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
{1,0,1,0,1,1,0,1,0,1,1,0,1,1,1,1,1,1,0,1},
{1,0,1,2,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
{1,0,1,0,1,1,0,1,0,0,0,0,1,1,1,0,1,1,1,1},
{1,0,1,0,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1},
{1,1,1,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,1,1,0,1},
{1,1,1,1,1,1,1,1,0,0,0,3,0,0,0,2,0,0,0,1},
{1,0,1,0,0,0,0,1,0,1,0,1,0,1,0,1,1,1,0,1},
{1,2,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
{1,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
{1,0,1,2,1,1,0,1,0,1,0,1,0,1,0,0,0,0,0,1},
{1,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
{1,0,1,0,1,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1},
{1,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,1,1,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
//Cycle through the array with a nested if
for (int i = 0; i < maxCols; i++)
{
for (int j = 0; j < maxRows; j++)
{
//If the the value at grid[i, j] is a 2 then create an enemy at that position
if (grid[i, j] == 2)
{
enemyList.Add(new Enemies(Content.Load<Texture2D>("redSquare"), new Vector2(j * squareWidth, i * squareHeight), new Vector2(j, i)));
//Reset the value at this position to 0 so that when the player/enemy moves from this spot, a floor tile will be drawn instead of a blank space.
grid[i, j] = 0;
}
//If the value is 3 then set the players position and grid position to the value based of [i, j] values.
if (grid[i, j] == 3)
{
playerObject.PlayerPosition = new Vector2(i * squareWidth, j * squareHeight);
playerObject.PlayerGridPosition = new Vector2(i, j);
grid[i, j] = 0;
}
}
}
}
if (level == 2)
{
}
}
However I can't use this method when checking collision between the
player and enemy as I only use the value inside the 2D array(2 and 3)
to choose where to originally draw the enemy and player squares.
Perhaps this exposes a flaw in your design that you should fix rather than work around. I don't have enough of the code to tell exactly, but it seems like you are duplicating information inside the Player class or perhaps another grid. Based on your description, a 2D grid containing values 0-1-2-3 for each square fully describes the state of the game. Why not just use that Grid as the single source of truth for everything?
It's hard to find where the actual bug without seeing the complete code & debug it directly, but I feel that your approach is too complicated.
Personally, I will follow the following logic/pseudocode:
loading () {
load level to array, no need to perform additional modification
}
game_update () {
for each tile within the array {
if ( tile is player ) {
read user's input
temp <- compute the next position of the player
if ( temp is floor ) then { current player position <- temp } // move allowed, update its position
else { the player must stay on its current position } // move rejected
}
if ( tile is enemy ) {
temp <- compute the next position of this enemy
if ( temp is floor ) then { this enemy position <- temp } // move allowed, update its position
else { this enemy must stay on its current position } // move rejected
}
}
}
game_draw () {
for each tile within the array {
draw the current tile
}
}
This way, any ilegal move will be blocked before it occurs.
Side note: You seem to be missing code for west. If that is unintentional, it should be an easy fix.
I think what may be happening is as soon it one enemy isn't next to the player, it allows the player to walk in all directions again, regardless of restrictions put on by earlier monsters.
If I am correct what is happening is it checks every enemy in descending screen position and blocks the player if then needed. However, if any of the remaining enemies aren't next to the player, the change is undone when the check for that enemy is preformed and the player is allowed to walk through again. This doesn't happen for the last enemy, because their are no more non-adjacent enemies to undo its blocking.
Try changing it so that it resets the allowed movements at the beginning of every frame, as opposed to every time it finds a monster not next to the player.

Moving mouse in direction and rate

I'm trying to create a simple mouse emulator controlled by a joystick's right thumbstick. I was trying to have the mouse move in the direction the stick pointed with a smooth gradient of pressure values dictating speed, but I've hit a number of snags when trying to do so.
The first is how to accurately translate the angle into accurate X and Y values. I can't find a way to implement the angle correctly. The way I have it, the diagonals are likely to move considerably faster than the cardinals.
I was thinking I need something like Math.Cos(angle) for the X values, and Math.Sin(angle) for the Y values to increment the mouse, but I can't think of a way to set it up.
The second, is smooth movement of the mouse, and this is probably the more important of the two. Since the SetPosition() function only works with integers, the rate at which pixels move over time seems very limited. The code I have is very basic, and only registers whole number values of 1-10. That not only creates small 'jumps' in acceleration, but limits diagonal movement as well.
The goal would to have something like 10 pixels-per-second, with the program running at 100hz, and each cycle outputting 0.1 pixel movement.
I'd imagine I might be able to keep track of the pixel 'decimals' for the X and Y values and add them to the axes when they build to whole numbers, but I'd imagine there's a more efficient way to do so and still not anger the SetPosition() function.
I feel like Vector2 objects should get this done, but I don't know how the angle would fit in.
Sample code:
//Poll Gamepad and Mouse. Update all variables.
public void updateData(){
padOne = GamePad.GetState(PlayerIndex.One, GamePadDeadZone.None);
mouse = Mouse.GetState();
currentStickRX = padOne.ThumbSticks.Right.X;
currentStickRY = padOne.ThumbSticks.Right.Y;
currentMouseX = mouse.X;
currentMouseY = mouse.Y;
angle = Math.Atan2(currentStickRY, currentStickRX);
vectorX = (int)( currentStickRX*10 );
vectorY = (int)( -currentStickRY*10 );
mouseMoveVector.X = vectorX;
mouseMoveVector.Y = vectorY;
magnitude = Math.Sqrt( Math.Pow( (currentStickRX - 0), 2 ) + Math.Pow( (currentStickRY - 0), 2 ) );
if (magnitude > 1){
magnitude = 1;
}
//Get values not in deadzone range and re-scale them from 0-1
if(magnitude >= deadZone){
activeRange = (magnitude - deadZone)/(1 - deadZone);
}
Console.WriteLine(); //Test Code
}
//Move mouse in in direction at specific rate.
public void moveMouse(){
if (magnitude > deadZone){
Mouse.SetPosition( (currentMouseX + vectorX), (currentMouseY + vectorY));
}
previousStickRX = currentStickRX;
previousStickRY = currentStickRY;
previousActiveRange = activeRange;
}
Note: I'm using all the xna frameworks.
Anyway, apologies if I'm explaining these things incorrectly. I haven't been able to find a good resource for this, and the vector examples I searched only move in integer increments and from point A to B.
Any help with any part of this is greatly appreciated.
I haven't tried it myself but from my point of view, you should normalize the pad axis after reading them, that way diagonals would move the same speed as cardinals. And for the second part, I would keep track of the mouse in floating variables, such as a Vector2 and do the cast (maybe rounding better) when setting the mouse position.
public void Start()
{
mousePosV2 = Mouse.GetState().Position.ToVector2();
}
public void Update(float dt)
{
Vector2 stickMovement = padOne.ThumbSticks.Right;
stickMovement.Normalize();
mousePosV2 += stickMovement*dt*desiredMouseSpeed;
/// clamp here values of mousePosV2 according to Screen Size
/// ...
Point roundedPos = new Point(Math.Round(mousePosV2.X), Math.Round(mousePosV2.Y));
Mouse.SetPosition(roundedPos.X, roundedPos.Y);
}

Categories

Resources