Moving in a grid with certain number of steps - Unity GameDev - c#

I built a 3D Chess game which works flawlessly. But I would like to make some changes to the movement.
The piece is supposed to walk a number of tiles. For example with a range of 3 it can either move 3 to one direction (left for example), or 2 left 1 up/down, or 1 left 2 up/down.
Which minor change do I have to implement in my code for it to work?
private Vector2Int[] directions = new Vector2Int[]
{
Vector2Int.left,
Vector2Int.up,
Vector2Int.right,
Vector2Int.down,
new Vector2Int(1, 1),
new Vector2Int(1, -1),
new Vector2Int(-1, 1),
new Vector2Int(-1,- 1),
};
public override List<Vector2Int> SelectAvaliableSquares()
{
avaliableMoves.Clear();
float range = Board.BOARD_SIZE;
foreach (var direction in directions)
{
for (int i = 1; i <= range; i++)
{
Vector2Int nextCoords = occupiedSquare + direction * i;
Piece piece = board.GetPieceOnSquare(nextCoords);
if (!board.CheckIfCoordinatesAreOnBoard(nextCoords))
break;
if (piece == null)
TryToAddMove(nextCoords);
else if (!piece.IsFromSameTeam(this))
{
TryToAddMove(nextCoords);
break;
}
else if (piece.IsFromSameTeam(this))
break;
}
}
return avaliableMoves;
}
The given code is an example of the movement of an ordinary queen.
Added a picture to demonstrate the movement.
enter image description here

Find the center of each chess tile and hold them all in a 2D array. Calculate which tiles the unit must traverse in accordance with its type and location. Then when you move, have it move first along the X axis and then the Y axis or vice versa. If it moves diagonally just move along both axes at the same time.

Related

Overlapped Area between two Colliders

In the above Image there are two Cubes with a Rigidbody and a BoxCollider attached to each of them. Let's say we have an animation in which the cube on top moves and overlaps the cube which is below it. The Cube on top moves some distance on y-axis and the cube below doesn't move. How do I calculate the yellow area,i.e, the area which shows overlapping?
Thank You.
Assuming these are always cubes and world axis aligned you could probably simply do
public Vector3 OverlapArea(BoxCollider a, BoxCollider b)
{
// get the bounds of both colliders
var boundsA = a.bounds;
var boundsB = b.bounds;
// first heck whether the two objects are even overlapping at all
if(!boundsA.Intersects(boundsB))
{
Vector3.zero;
}
// now that we know they at least overlap somehow we can calculate
// get the bounds of both colliders
var boundsA = a.bounds;
var boundsB = b.bounds;
// get min and max point of both
var minA = boundsA.min; (basically the bottom-left-back corner point)
var maxA = boundsA.max; (basically the top-right-front corner point)
var minB = boundsB.min;
var maxB = boundsB.max;
// we want the smaller of the max and the higher of the min points
var lowerMax = new Vector3.Min(maxA, maxB);
var higherMin = new Vector3.Max(minA, minB);
// the delta between those is now your overlapping area
return lowerMax - higherMin;
}
and if you want the area in m³ as usual just do
var overlap = OverlapArea(colliderA, colliderB);
var overlapArea = overlap.x * overlap.y * overlap.z;
See
Collider.bounds
Bounds.Intersects
Bounds.min / Bounds.max
Vector3.Min / Vector3.Max
Anything beyond that (rotated and differently shaped objects) would probably fit better for the Mathematics community

Tilemap.HasTile() always returns false for the top layer of tilemap ONLY

I am trying to make a building game, where you can build anywhere that there is no builds and touching the ground. Here is my code
private void Update()
{
if (buildMode)
{
playerScript.enabled = false;
Vector3 point = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Input.GetMouseButtonDown(0))
{
buildOverlay.ClearAllTiles();
Vector3Int selectedTile = buildOverlay.WorldToCell(point);
selectedTile.z = 0;
if (Vector3.Distance(GameObject.Find("Player").transform.position, selectedTile) < buildDistance)
{
//BoundsInt bounds = collidableTilemap.cellBounds;
//TileBase[] allTiles = collidableTilemap.GetTilesBlock(bounds);
//for (int x = 0; x < bounds.size.x; x++)
//{
// for (int y = 0; y < bounds.size.y; y++)
// {
// TileBase tile = allTiles[x + y * bounds.size.x];
// Debug.Log(collidableTilemap.HasTile(new Vector3Int(x, y, 0)));
// if (collidableTilemap.HasTile(new Vector3Int(x, y, 0)))
// {
// buildOverlay.SetTile(selectedTile, notplaceable);
// }
// else
// {
// buildOverlay.SetTile(selectedTile, placeable);
// }
// }
//}
selectedTile.z = 0;
Debug.Log(selectedTile);
Debug.Log(collidableTilemap.HasTile(selectedTile));
if (!collidableTilemap.HasTile(selectedTile))
{
buildOverlay.SetTile(selectedTile, placeable);
}
else
{
buildOverlay.SetTile(selectedTile, notplaceable);
}
}
}
else
{
playerScript.enabled = true;
}
}
}
What this code does so far is turn off the ability to move and check for an overlapping tile. I currently have the variable buildDistance set to infinity, so thats not the problem.
Here are some images:
Unity thinks that the first layer is not there. Here is the scene view to prove that those blocks are in the same tilemap:
This code is supposed to see if a tile exists in the coordinates where the player wants to place. It works fine except for the first layer. Please help!
I'm pretty new to Unity so don't mind my noob mistakes. Thanks!
[EDIT]: I've changed my terrain a bit, and realized a couple new things:
This block is red
This block is green.
I can not build anywhere on this row, except for when the stone ends:
I can build here:
WHAT IS GOING ON!!!!!????!!???
It's hard to say for certain but It seems that the conversion from world space to grid coordinate is off-by-one at least in the y dimension and i would guess in the x direction also.
I believe the best candidate for the bug is this line here
Vector3Int selectedTile = buildOverlay.WorldToCell(point);
This kind of casting from float to int actually won't round the number but will instead floor it. Unity often places it's tilemaps so tiles are 0.5m misaligned with the world grid and because of this flooring the position might be causing these problems.
I would suggest trying
Vector3Int selectedTile = buildOverlay.WorldToCell(Vector3Int.RoundToInt(point))
or if that does not help you could try the uglier
Vector3Int selectedTile = buildOverlay.WorldToCell(point+Vector3.one*0.5f);
(if this still doesn't work you could ommit the 0.5f)
Not the prettiest of solutions but I think this is where your problem is I'd have a play about with it.
So basically it is 1 unit off of the y axis, so simply subtract 1 y unit.
Replace this line:
if (!collidableTilemap.HasTile(selectedTile)
with
if (!collidableTilemap.HasTile(selectedTile - new Vector3Int(0, 1, 0)))
This will basically negate the offset effect that Unity puts on tilemaps from flooring.

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.

Xna Isometric point to click movement

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.

Categories

Resources