Alright, so I am pretty new to XNA programming and I am trying to code a platformer. I have implemented pixel-perfect collision but it seems to fail for no apparent reason sometimes (I couldn't figure out a pattern) and the hero sprite goes through platforms.
static bool IntersectsPixel(Rectangle rect1, Color[] data1, Rectangle rect2, Color[] data2)
{
int top = Math.Max (rect1.Top, rect2.Top);
int bottom = Math.Min(rect1.Bottom, rect2.Bottom);
int left = Math.Max (rect1.Left,rect2.Left);
int right = Math.Min(rect1.Right,rect2.Right);
//Top
for(int y = top; y<bottom;y++)
for (int x = left; x < right; x++)
{
Color color1 = data1[x-rect1.Left + (y-rect1.Top) * rect1.Width];
Color color2 = data2[x - rect2.Left + (y - rect2.Top) * rect2.Width];
if (color1.A != 0 && color2.A != 0)
return true;
}
return false;
}
And here's the Update Method
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
foreach(Platform platform in platformList)
{
Rectangle check = new Rectangle(hero.GetRectangle().X - 300, hero.GetRectangle().Y - 300, 600, 600);
if(check.Intersects(platform.rectangle))
if (IntersectsPixel(hero.GetRectangle(), hero.textureData, platform.rectangle, platform.platformTextureData))
{
int direction = CheckDirection(platform,hero);
if (hero.hasJumped == true && direction == 3 && hero.velocity.Y <= 0 )
{
hero.velocity.Y = 0f;
hero.SetPosition(new Vector2((float)hero.GetPosition().X, (float)platform.rectangle.Bottom));
break;
}
else
if (direction == 4 && hero.velocity.X >= 0)
{
hero.velocity.X = 1;
hero.SetPosition(new Vector2((float)platform.rectangle.Left - (float)hero.GetRectangle().Width, (float)hero.GetPosition().Y));
break;
}
else
if (direction == 2 && hero.velocity.X <= 0)
{
hero.velocity.X = -1;
hero.SetPosition(new Vector2((float)platform.rectangle.Right - 1, (float)hero.GetPosition().Y));
break;
}
else
if (direction == 1 && hero.velocity.Y >= 0)
{
hero.velocity.Y = 0;
hero.hasJumped = false;
hero.SetPosition(new Vector2((float)hero.GetRectangle().X, (float)platform.rectangle.Y - (float)hero.GetRectangle().Height + 1));
hero.SetRectangle(new Rectangle((int)hero.GetPosition().X, (int)hero.GetPosition().Y, (int)hero.GetSize().X, (int)hero.GetSize().Y));
break;
}
}
}
hero.Update(gameTime);
camera.Update(gameTime, hero, screenBounds);
base.Update(gameTime);
}
And here's the direction check:
private int CheckDirection(Platform platform,Hero hero)
{
int distance = Math.Abs(platform.rectangle.Top - hero.GetRectangle().Bottom);
int direction = 1; //Top
if (distance > Math.Abs(platform.rectangle.Right - hero.GetRectangle().Left))
{
distance = Math.Abs(platform.rectangle.Right - hero.GetRectangle().Left);
direction = 2;
}
if (distance > Math.Abs(platform.rectangle.Bottom - hero.GetRectangle().Top))
{
distance = Math.Abs(platform.rectangle.Bottom - hero.GetRectangle().Top);
direction = 3;
}
if (distance > Math.Abs(platform.rectangle.Left - hero.GetRectangle().Right))
{
direction = 4;
distance = Math.Abs(platform.rectangle.Left - hero.GetRectangle().Right);
}
return direction;
}
Those are all my functions related to collision detection. If you happen to have any ideea of what might cause this, please let me know.
Thank you very much !
One thing I see that might be a possible reason is that right in the beginning where you build a box around both objects:
int top = Math.Max (rect1.Top, rect2.Top);
int bottom = Math.Min(rect1.Bottom, rect2.Bottom);
int left = Math.Max (rect1.Left,rect2.Left);
int right = Math.Min(rect1.Right,rect2.Right);
Unless you flipped it, typically XNA's Y grows positive as you go down (not up). This means you would actually want to take the Max of Bottom, and the Min of Top, since Top will always be less than Bottom. It looks like you already knew that though by the look of this line:
for(int y = top; y<bottom;y++)
I'll keep looking (it's difficult to test without the actual project). Another recommendation I'd give you is to do some debug drawing. Draw where the intersection functions thinks that pixels are touching or not touching.
Because of lag, Your character could be moving more than the blocks height per frame, so the collision may think it is in between blocks, and cause it to fly through.
https://gamedev.stackexchange.com/questions/30458/platformer-starter-kit-collision-issues
Check that out, It solved the problem for me.
Either slowing down movement, or clamping the change in position to the size of a block should solve this.
Related
I have some NPCs in a 2D matrix that wrap's around, and I have to make them follow the enemy faction NPCs using the toroidal map.
I've tried a couple of solutions but they all give me weird behaviors working only to a certain x or y value of the grid and then instead of following they go back one tile.
this is what I have right now to decide which way to go:
public Position GetNextStepTowards(Position origin, Position target)
{
Position nextStep = new Position(0, 0);
float dx = MathF.Abs(target.X - origin.X);
float dy = MathF.Abs(target.Y - origin.Y);
if (dx > mapXSize / 2) nextStep.X = -1;
else if (dx < mapXSize / 2) nextStep.X = 1;
if (dy > mapYSize / 2) nextStep.Y = 1;
else if (dy < mapYSize / 2) nextStep.Y = -1;
return nextStep;
}
And a Position is:
public struct Position
{
public int X { get; set; }
public int Y { get; set; }
public Position(int x, int y)
{
this.X = x;
this.Y = y;
}
}
The NPCs can only move one cell (Moore's) therefore the movement vector should only be values between -1 and 1.
Thank you for the help in advance!
If we consider the X-axis, there are two cases as shown in the diagram below:
In the first case (top) the target is to the right of the origin. In this case, moving to the right is direct, and moving to the left is toroidal.
In the second case (bottom) the target is to the left of the origin. In this case, moving to the left is direct, and moving to the right is toroidal.
So the code needs to check the relative positions of the origin and target, and then compute the left and right distances appropriately. The smaller distance determines the direction and magnitude of deltaX. The same logic applies to deltaY.
Then if deltaX and deltaY have the same magnitude, we move along the diagonal. Otherwise, we move in the direction with the larger delta.
private int ComputeDelta(int src, int dst, int mapSize)
{
int increasing, decreasing;
if (dst >= src)
{
increasing = dst - src; // increasing direction is direct
decreasing = (mapSize + src) - dst; // decreasing direction is toroidal
}
else
{
increasing = (mapSize + dst) - src; // increasing direction is toroidal
decreasing = src - dst; // decreasing direction is direct
}
if (increasing <= decreasing) { return increasing; }
else { return -decreasing; }
}
public Position GetNextStepTowards(Position origin, Position target)
{
Position nextStep = new Position(0, 0);
// compute the distances
int dx = ComputeDelta(origin.X, target.X, mapXSize);
int dy = ComputeDelta(origin.Y, target.Y, mapYSize);
// keep the dominant distance, and clear the other distance
// keep both if they're equal
if (dx*dx > dy*dy) { dy = 0; }
else if (dx*dx < dy*dy) { dx = 0; }
// normalize the distances so they are -1, 0, or 1
nextStep.X = dx.CompareTo(0);
nextStep.Y = dy.CompareTo(0);
return nextStep;
}
So after a while, I came up with this solution, a bit clunky in my opinion:
public Position GetNextStepTowards(Position origin, Position target)
{
// Returned Position
Position nextStep = new Position(0, 0);
int dx = target.X - origin.X;
int dy = target.Y - origin.Y;
// Toroidal distance
if (dx > mapXSize / 2) dx = mapXSize - dx;
if (dy > mapYSize / 2) dy = mapXSize - dy;
// First verify whether the difference in positions is
// greater on the X or Y axis.
// Then check if the target is lower/higher/forwards/backwards
if (MathF.Pow(dx, 2) > MathF.Pow(dy, 2))
{
if (dx > 0) nextStep.X = 1;
else if (dx < 0) nextStep.X = -1;
}
else if (MathF.Pow(dy, 2) > MathF.Pow(dx, 2))
{
if (dy > 0) nextStep.Y = 1;
else if (dy < 0) nextStep.Y = -1;
}
// If the difference in the X and Y axis are the same,
// move diagonally
// use CompareTo do decide what direction in specific.
else if ((int)MathF.Pow(dx, 2) == (int)MathF.Pow(dy, 2))
{
nextStep.X = 1 * target.X.CompareTo(origin.X);
nextStep.Y = 1 * target.Y.CompareTo(origin.Y);
}
return nextStep;
}
I created my tile map and my player with movement.
I'm now trying to create the collision and I feel i'm on the right track.
Here is how I've created the map.
List<Texture2D> tileTextures = new List<Texture2D>();
int tileWidth = 60;
int tileHeight = 60;
public int[,] Map = new int[,]
{
{2,2,2,2,2,2,2,2,2,2},
{2,2,2,2,1,2,2,2,2,2},
{2,2,2,2,2,2,2,2,2,2},
{2,2,2,2,2,2,2,2,2,2},
};
public void Draw(SpriteBatch spriteBatch)
{
int tileMapWidth = Map.GetLength(1);
int tileMapHeight = Map.GetLength(0);
for (int x = 0; x < tileMapWidth; x++)
{
for (int y = 0; y < tileMapHeight; y++)
{
int textureIndex = Map[y, x];
Texture2D texture = tileTextures[textureIndex];
spriteBatch.Draw(
texture,
source = new Rectangle(x *myTile.Width,
y * myTile.Height,
tileWidth,
tileHeight),
Color.White);
}
}
}
I am checking the 2d array coords with this condition and checking to see if a specific tile is there, where I can then set my previous location if it is true.
I'm currently testing on 1 tile atm.
public void Update(GameTime gameTime)
{
prevPosition = position;
input(gameTime);
if(tiles.Map[(int)playerPosition.X/60,(int)playerPosition.Y/60] == 1)
{
position = prevPosition;
}
}
However my player position keeps going out of the index bounds of the 2D array and I believe I need to scale it down so that it stops this, I've tried dividing the play coords by the width of the tiles but that hasn't worked.
If anyone can help me with the correct scaling I would be very appreciative.
This will happen if your player's position is like -x,y or x,-y or maybe -x,-y. Yor approach might be better if you'd make a fuction like this one
public bool CollidesWithWall(int x, int y)
{
if(x < 0 || x > *matrix width* - 1) return false;
if(y < 0 || y > *matrix height* -1) return false;
if (Map[x,y] == 1) return true;
return false;
}
and use it insead of the line tiles.Map[(int)playerPosition.X/60,(int)playerPosition.Y/60]
Or, if you need the type of tile returned
public int CollidesWithWall(int x, int y)
{
if(x < 0 || x > *matrix width* - 1) return -1;
if(y < 0 || y > *matrix height* -1) return -1;
return Map[x,y];
}
By doing it this way, you'll know if you stumbled upon a health potion (just set it's ID to like 3) or a wall (with ID of 1 or something, that's tottaly up to you) and if it is 0, it's empty space (or maybe -1). Notice that the "-1" part is totaly up to you. Just write down a list of id's that youll have and which items they present.
Other suggestions
Try if(tiles.Map[(int)(playerPosition.X/60f),(int)(playerPosition.Y/60f)] == 1)
I am currently writing an OpenGL application using the SharpGL library and I am trying to simply create a 3x3x3 set of cubes arranged in a symmetric grid.
I am currently seeing some strange behaviour exhibited in the following picture:
This has me completely stumped as I can see no reason why the code is missing out the last 3 blocks. The method in charge of creating the cube looks like this:
private void CreateCube2(OpenGL gl, int cubeSize)
{
gl.PushMatrix();
const float spacing = 2.5f;
for (int z = 0; z < cubeSize; z++)
{
for (int y = 0; y < cubeSize; y++)
{
for (int x = 0; x < cubeSize; x++)
{
var cube = new Cube();
ColourCube(cube, cubeSize, x, y, z);
cube.Render(gl, RenderMode.Render);
gl.Translate(spacing, 0, 0);
}
gl.Translate(-spacing * cubeSize, spacing, 0);
}
gl.Translate(0, -spacing * cubeSize, spacing);
}
gl.PopMatrix();
}
where the definition of ColourCube is as follows:
private bool m_blackCubeMiddle = true;
private void ColourCube(Cube cube, int size, int x, int y, int z)
{
cube.Faces[0].Material = (!m_blackCubeMiddle || y == 0) ? WhiteMaterial : BlackMaterial; // Bottom
cube.Faces[1].Material = (!m_blackCubeMiddle || y == size - 1) ? YellowMaterial : BlackMaterial; // Top
cube.Faces[2].Material = (!m_blackCubeMiddle || x == size - 1) ? GreenMaterial : BlackMaterial; // Right
cube.Faces[3].Material = (!m_blackCubeMiddle || x == 0) ? BlueMaterial : BlackMaterial; // Left
cube.Faces[4].Material = (!m_blackCubeMiddle || z == 0) ? OrangeMaterial : BlackMaterial; // Front
cube.Faces[5].Material = (!m_blackCubeMiddle || z == size - 1) ? RedMaterial : BlackMaterial; // Back
}
The entire project can be downloaded from here.
The strange behaviour is caused by a bug in SharpGL 'Polygon.Render' method. It is not caused by your own code. When you call Triangulate on the cube, it already shows 27 cubes on screen in the correct positions. But even then, SharpGL incorrectly renders one of the cubes.
Having looked at the source code for ShapGL's Cube & Polygon classes I'ld suggest to write your own cube class. The implementation of the Cube class, that you are using now, is absolutely horible from several standpoints (GL_POLYGON (deprecated), immediate mode vertex submission (deprecated))
i'm making my first game on XNA for my class. I'm trying to make the monsters move left and right automatically. I have total 4 monsters for now. I'm trying to get them move left then right within the screen.
//Monster movements
for (int i = 0; i < numOfMonster; i++)
{
if (destinationMonster[i].X >= screenWidth - 60)
{
while(destinationMonster[i].X != -10)
moveLeft = true;
}
else
{
moveRight = true;
}
if (moveLeft)
{
int temp = destinationMonster[i].X;
temp = destinationMonster[i].X - monsterSpeed;
//This prevents the object passing the screen boundary
if (!(temp < -10))
{
destinationMonster[i].X = temp;
}
moveLeft = false;
}
if (moveRight)
{
int temp = destinationMonster[i].X;
temp = destinationMonster[i].X + monsterSpeed;
//This prevents the object passing the screen boundary
if (!(temp > screenWidth - 50))
{
destinationMonster[i].X = temp;
}
moveRight = false;
}
}
Your first problem is your while statement, once you enter it you are not going to exit because you are not changing your X value. If it were me I would have a bool array variable corresponding to each of your monsters. I would also change your conditional to trigger the change of the boolean value once the monster has reached the extents at either end. Something like this.
if (destinationMonster[i].X >= screenWidth - 60)
{
moveRight[i] = false ;
}
else if (destinationMonster[i].X <= -10)
{
moveRight[i] = true ;
}
I am brand new to XNA and C# (I started 2 days ago, so please forgive the sloppy programming), and I have run into a problem with a simple game I am making. I can't seem to find the solution in any of the online tutorials. I am trying to utilize rectangle collision detection but I cannot get it to work properly. The character falls completely through the floor and it seems there is no collision ever registering in any place. I cannot find my logic error here. Below I posted a bit of my code that pertains to the collision detection. I can post more if necessary. Thank you for the help in advance!
Level.cs
//The method below is called in the initial LoadContent method in Game1.cs. 'LoadBlocks()' is supposed to read in a text file and setup the level's "blocks" (the floor or platforms in the game).
public void LoadBlocks(int level){
if (level == 0)
{
fileName = "filepath";
}
System.IO.StreamReader file = new System.IO.StreamReader(fileName);
while ((line = file.ReadLine()) != null)
{
for (int i = 0; i < 35; i++)
{
rectBlock = new Rectangle((int)positionBlock.X, (int)positionBlock.Y, width / 30, height / 30);
if (line.Substring(i, 1).Equals(","))
{
positionBlock.X += (width / 100) * 24;
}
if (line.Substring(i, 1).Equals("#"))
{ //block -> (bool isPassable, Texture2D texture, Rectangle rectangle, Vector2 position)
block = new Block(false, textureBlock, rectBlock, positionBlock);
blocks.Add(block);
positionBlock.X += (width / 100) * 24;
}
}
positionBlock.Y += (height / 100) * 8;
positionBlock.X = 0;
}
}
//This method below updates the character 'bob' position and velocity.
public void UpdatePlayer()
{
bob.position += bob.velocity;
bob.rectangle = new Rectangle((int)bob.position.X, (int)bob.position.Y, width / 30, height / 30);
float i = 1;
bob.velocity.Y += 0.15f * i;
foreach (Block block in blocks)
{
if (bob.isOnTopOf(bob.rectangle, block.rectangle))
{
bob.velocity.Y = 0f;
bob.hasJumped = false;
}
}
}
Character.cs
//Here is my whole Character class for 'bob'
public class Character
{
int height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
int width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
int health;
String name;
bool gender;
Texture2D texture;
public Vector2 position;
public Vector2 velocity;
public bool hasJumped;
public Rectangle rectangle;
public Character(int newHealth, String newName, bool newGender, Texture2D newTexture, Vector2 newPosition)
{
health = newHealth;
gender = newGender;
name = newName;
texture = newTexture;
position = newPosition;
hasJumped = true;
rectangle = new Rectangle(0,0, width/30,height/30);
velocity = Vector2.Zero;
}
public bool isOnTopOf(Rectangle r1, Rectangle r2)
{
const int penetrationMargin = 5;
return (r1.Bottom >= r2.Top - penetrationMargin &&
r1.Bottom <= r2.Top &&
r1.Right >= r2.Left + 5 &&
r1.Left <= r2.Right - 5);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, rectangle,null, Color.White,0,position,SpriteEffects.None,0);
}
}
Sorry if this is too much posted code or is confusing, but any help is greatly appreciated! Thanks!
Your test condition for intersection appears to be incorrect, at least. There might be other problems with the code (there are several just giving a quick look at it), but let's focus on this:
r1.Bottom >= r2.Top - penetrationMargin &&
r1.Bottom <= r2.Top &&
r1.Right >= r2.Left + 5 &&
r1.Left <= r2.Right - 5;
First, don't code what exists in the library for you. Xna Rectangles have an Intersects method, so scrap that code and use it instead.
We'll look at the condition anyway:
r1's Bottom is below r2's Top minus a margin AND
r1's Bottom is above r2's Top AND (...)
This is already impossible. r1.Bottom cannot be both below and above r2.Top.
Further, even if all the comparisons were correct, you are using AND everywhere (&&), which means that the condition is only true if all 4 of them are true. Basically, you are not testing for intersection, you are testing for containement, i.e. you are testing that r1 is entirely within r2. An intersection test would join the conditions using OR (||) not AND (&&).
But again, although you might want to code it yourself for educative purposes, the best solution from an engineering perspective is to use what the library provides, here Rectangle.Intersects.
I figured it out, but I'm not sure why this actually works. Something was wrong with my draw method. I originally had:
spriteBatch.Draw(texture, rectangle, null, Color.White, 0, position, SpriteEffects.None, 0);
as my method but then I changed this to
spriteBatch.Draw(texture, rectangle, Color.White);
and now everything works fine.