how to check UI rect inside Canvas rect - c#

How to check UI rect inside Canvas rect?
rect.contains(Vector2) is Vector2...
rect.overlaps(Rect) Will not be false unless it is completely outside...
void Update()
{
Vector2 pos;
var screenPos = Camera.main.WorldToScreenPoint(targetTransform.position + offset);
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, screenPos, uiCamera, out pos);
if (!CheckInsideRect(myRectTransform.rect,canvasRectTransform.rect))
{
myRectTransform.localPosition = pos;
}
}
The results I would like to get

You could do it "manually" by using some extension methods something like
public static class RectTransformExtensions
{
///<summary>
/// Returns a Rect in WorldSpace dimensions using <see cref="RectTransform.GetWorldCorners"/>
///</summary>
public static Rect GetWorldRect(this RectTransform rectTransform)
{
// This returns the world space positions of the corners in the order
// [0] bottom left,
// [1] top left
// [2] top right
// [3] bottom right
var corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
Vector2 min = corners[0];
Vector2 max = corners[2];
Vector2 size = max - min;
return new Rect(min, size);
}
///<summary>
/// Checks if a <see cref="RectTransform"/> fully encloses another one
///</summary>
public static bool FullyContains (this RectTransform rectTransform, RectTransform other)
{
var rect = rectTransform.GetWorldRect();
var otherRect = other.GetWorldRect();
// Now that we have the world space rects simply check
// if the other rect lies completely between min and max of this rect
return rect.xMin <= otherRect.xMin
&& rect.yMin <= otherRect.yMin
&& rect.xMax >= otherRect.xMax
&& rect.yMax >= otherRect.yMax;
}
}
See RectTransform.GetWorldCorners
So you would use it like
if (!canvasRectTransform.FullyContains(myRectTransform))
{
...
}

Related

Slow update time with tiles and MouseState MonoGame

Very new developer here.
In my program I have a randomly generating world map using a simplex noise library. On top of this I am attempting to draw a tilemap of transparent 4x4 tiles that appear slightly translucent when the mouse is hovering over one.
I've got this working but it takes about 3 whole seconds for the highlighted tile to update to the mouse's current position. Is there anything I could do to solve this?
This is my code for the MouseState check in the tile class:
public override void Update(GameTime gameTime)
{
_previousMouse = _currentMouse;
_currentMouse = Mouse.GetState();
var mouseRectangle = new Rectangle(_currentMouse.X, _currentMouse.Y, 1, 1);
_isHovering = false;
if (mouseRectangle.Intersects(Rectangle))
{
_isHovering = true;
if (_currentMouse.LeftButton == ButtonState.Released && _previousMouse.LeftButton == ButtonState.Pressed)
{
Click?.Invoke(this, new EventArgs());
}
}
}
Sorry if this is formatted wrong or badly asked, first post so still getting to grips with everything :)
Invert the logic:
Instead of checking thousands of tile objects against the mouse, apply the mouse to a single object.
Assuming you have a list or array of tile objects:
Add a new object to check for mouse hover and click:
public class MouseDetect(Tile[] tiles) // replace with List<> as needed
{
int PrevHover = -1; // used to unHover
// if (Area == Screen) make the next two lines `const`, so the compiler will remove all uses...
int AreaX = 0; //Area x offset
int AreaY = 0; //Area y offset
int AreaW = 800; //Area width
int AreaH = 480; //Area height
const int Grid = 4; // assumes square
const int GridW = AreaW / Grid;
// I Will assume the `Delegate Click` in `Tile` is public
public void Update(MouseState ms, MouseState oms), //_currentMouse = ms and _previousMouse = oms;
{
int mouseIndex = (ms.X - AreaX) % Gridw + (ms.Y - AreaY) / GridW;
tiles[PrevHover].Hover = false;
PrevHover = mouseIndex;
tiles[PrevHover].Hover = true;
//Check Release
if(tiles[PrevHover].Hover && ms.LeftButton == ms.ButtonState.Released && oms.LeftButton == ButtonState.Pressed)
tiles[PrevHover].Click(tiles[PrevHover], new EventArgs());
}
}
Remove the Update from the Tile class.
Notes for anyone reading this later:
Never call Mouse.GetState(); more than once per step.
Predefined or framework names such as Rectangle should never be used as an identifier.
i.e renamed and corrected to CollRectangle
if (CollRectangle.Contains(ms.Position))

Multithreading Update with ReaderWriterLockSlim

I think I made a huge error when I first started designing my game and would give you some or even all of my code but it is just too complex. So please bear with me.
Now that I get to the more advanced stages of my design I am running into major trouble with threading.
During update I run 3 tasks simultaneously. Update, HitTesting and AI which are even split up into even more threads.
Updating needs to do the movement (including physics) and animation of all my objects.
HitTesting does all Hit testing between thousands of objects and still needs a lot of optimizing…. Things like Devide and conquer to get right.
AI issues commands to objects that are executed by the update cycle. Things like turn left or right, fire, etc.
And of course we have
draw… or in my case GetSprites during draw. That must have full priority over all but the update process.
And the yet unimplemented sound and message system.
As you can see this is not an optimal process for multitasking as they all function on the same objects but it needs to be.
So… I thought to implement System.Threading.ReaderWriterLockSlim
And here is my real question: How do I do that?
Since Update is the only writer to my draw data.
Drawing is a pure Reader
HitTesting might need to recalculate the boundingRectangle and Matrix but that does not influence Drawing
AI only needs to read position data and issue commands, read by Update on the next cycle and is separated in an group of separate classes (I call it master/puppet but probably has an official/better name)
Does it make sense to implement different ReaderWriterLockSlim objects to lock the different properties a thread could need?
I would like to have control during update to bypass a locked object (either by AI or HitTesting) and take the next one so that I can do it later when it is unlocked (or even skip it if update is taking too long but do it on the next cycle)
Does any of you know a book or site on advanced threading. Not the usual little examples I find everywhere so I can figure this out?
I have been stuck for more than a week now and I would like to proceed.
Any help appreciated.
This is the code I use for collision detection between objects. I converted it from a c++ example I found a long time ago but can't remember where.
public abstract class HitTestInfo : Object
{
static protected Random RND = new Random();
static protected Dictionary<String, Color[]> m_TextureDataDictionary;
public static Matrix GetMatrix(iSpriteInfo vDrawObject)
{
Matrix Transform =
Matrix.CreateTranslation(new Vector3(-vDrawObject.Origin, 0.0f)) *
Matrix.CreateScale(vDrawObject.Scale) *
Matrix.CreateRotationZ(vDrawObject.Angle) *
Matrix.CreateTranslation(new Vector3(vDrawObject.X, vDrawObject.Y, 0.0f));
return Transform;
}
/// <summary>
/// Calculates an axis aligned rectangle which fully contains an arbitrarily
/// transformed axis aligned rectangle.
/// </summary>
/// <param name="rectangle">Original bounding rectangle.</param>
/// <param name="transform">World transform of the rectangle.</param>
/// <returns>A new rectangle which contains the trasnformed rectangle.</returns>
public static Rectangle CalculateBoundingRectangle(Rectangle vrectangle,
Matrix transform)
{
Rectangle rectangle = vrectangle;
rectangle.X = 0;
rectangle.Y = 0;
// Get all four corners in local space
Vector2 leftTop = new Vector2(rectangle.Left, rectangle.Top);
Vector2 rightTop = new Vector2(rectangle.Right, rectangle.Top);
Vector2 leftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
Vector2 rightBottom = new Vector2(rectangle.Right, rectangle.Bottom);
// Transform all four corners into work space
Vector2.Transform(ref leftTop, ref transform, out leftTop);
Vector2.Transform(ref rightTop, ref transform, out rightTop);
Vector2.Transform(ref leftBottom, ref transform, out leftBottom);
Vector2.Transform(ref rightBottom, ref transform, out rightBottom);
// Find the minimum and maximum extents of the rectangle in world space
Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop),
Vector2.Min(leftBottom, rightBottom));
Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop),
Vector2.Max(leftBottom, rightBottom));
// Return that as a rectangle
return new Rectangle((int)min.X, (int)min.Y,
(int)(max.X - min.X), (int)(max.Y - min.Y));
}
/// <summary>
/// Determines if there is overlap of the non-transparent pixels between two
/// sprites.
/// </summary>
/// <param name="transformA">World transform of the first sprite.</param>
/// <param name="widthA">Width of the first sprite's texture.</param>
/// <param name="heightA">Height of the first sprite's texture.</param>
/// <param name="dataA">Pixel color data of the first sprite.</param>
/// <param name="transformB">World transform of the second sprite.</param>
/// <param name="widthB">Width of the second sprite's texture.</param>
/// <param name="heightB">Height of the second sprite's texture.</param>
/// <param name="dataB">Pixel color data of the second sprite.</param>
/// <returns>True if non-transparent pixels overlap; false otherwise</returns>
public static bool IntersectPixels(
Matrix transformA, int widthA, int heightA, Color[] dataA,
Matrix transformB, int widthB, int heightB, Color[] dataB)
{
// Calculate a matrix which transforms from A's local space into
// world space and then into B's local space
Matrix transformAToB = transformA * Matrix.Invert(transformB);
// When a point moves in A's local space, it moves in B's local space with a
// fixed direction and distance proportional to the movement in A.
// This algorithm steps through A one pixel at a time along A's X and Y axes
// Calculate the analogous steps in B:
Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);
// Calculate the top left corner of A in B's local space
// This variable will be reused to keep track of the start of each row
Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);
// For each row of pixels in A
for (int yA = 0; yA < heightA; yA++)
{
// Start at the beginning of the row
Vector2 posInB = yPosInB;
// For each pixel in this row
for (int xA = 0; xA < widthA; xA++)
{
// Round to the nearest pixel
int xB = (int)Math.Round(posInB.X);
int yB = (int)Math.Round(posInB.Y);
// If the pixel lies within the bounds of B
if (0 <= xB && xB < widthB &&
0 <= yB && yB < heightB)
{
// Get the colors of the overlapping pixels
Color colorA = dataA[xA + yA * widthA];
Color colorB = dataB[xB + yB * widthB];
// If both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
{
// then an intersection has been found
return true;
}
}
// Move to the next pixel in the row
posInB += stepX;
}
// Move to the next row
yPosInB += stepY;
}
// No intersection found
return false;
}
public static List<CollisionData> CollisionCheck<T1, T2>(List<T1> List1, List<T2> List2)
{
List<CollisionData> RetList = new List<CollisionData>();
foreach (T1 obj1 in List1)
{
iSpriteInfo SI1 = obj1 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix1 = SI1.Matrix;
Rectangle Rect1 = SI1.BoundingRectangle;
Color[] TextureData1 = SI1.TextureData;
foreach (T2 obj2 in List2)
{
iSpriteInfo SI2 = obj2 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix2 = SI2.Matrix;
Rectangle Rect2 = SI2.BoundingRectangle;
Color[] TextureData2 = SI2.TextureData;
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if (Rect1.Intersects(Rect2))
{
// Check collision with Player and planets
if (IntersectPixels(Matrix1, (int)SI1.DestinationRectangle.Width,
(int)SI1.DestinationRectangle.Height, TextureData1,
Matrix2, (int)SI2.DestinationRectangle.Width,
(int)SI2.DestinationRectangle.Height, TextureData2))
{
RetList.Add(new CollisionData(SI1, SI2));
}
}
}
}
}
}
return RetList;
}
public static List<CollisionData> CollisionCheck<T1, T2>(T1 Obj1, List<T2> List2)
{
List<CollisionData> RetList = new List<CollisionData>();
lock (Obj1)
{
lock (List2)
{
iSpriteInfo SI1 = Obj1 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix1 = SI1.Matrix;
Rectangle Rect1 = SI1.BoundingRectangle;
Color[] TextureData1 = SI1.TextureData;
foreach (T2 obj2 in List2)
{
iSpriteInfo SI2 = obj2 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix2 = SI2.Matrix;
Rectangle Rect2 = SI2.BoundingRectangle;
Color[] TextureData2 = SI2.TextureData;
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if (Rect1.Intersects(Rect2))
{
// Check collision with Player and planets
if (IntersectPixels(Matrix1, (int)SI1.DestinationRectangle.Width,
(int)SI1.DestinationRectangle.Height, TextureData1,
Matrix2, (int)SI2.DestinationRectangle.Width,
(int)SI2.DestinationRectangle.Height, TextureData2))
{
RetList.Add(new CollisionData(SI1, SI2));
}
}
}
}
}
}
}
return RetList;
}
public static bool CollisionCheck<T1, T2>(T1 Obj1, T2 Obj2)
{
Matrix Matrix1;
Rectangle Rect1;
Color[] TextureData1;
Matrix Matrix2;
Rectangle Rect2;
Color[] TextureData2;
iSpriteInfo SI1 = Obj1 as iSpriteInfo;
if (SI1 != null)
{
lock (SI1)
{
Matrix1 = SI1.Matrix;
Rect1 = SI1.BoundingRectangle;
TextureData1 = SI1.TextureData;
}
iSpriteInfo SI2 = Obj2 as iSpriteInfo;
if (SI1 != null)
{
lock (SI2)
{
Matrix2 = SI2.Matrix;
Rect2 = SI2.BoundingRectangle;
TextureData2 = SI2.TextureData;
}
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if (Rect1.Intersects(Rect2))
{
// Check collision with Player and planets
if (IntersectPixels(Matrix1, (int)SI1.DestinationRectangle.Width,
(int)SI1.DestinationRectangle.Height, TextureData1,
Matrix2, (int)SI2.DestinationRectangle.Width,
(int)SI2.DestinationRectangle.Height, TextureData2))
{
return true;
}
}
}
}
return false;
}
}
iSpriteInfo is defined like this
public interface iSpriteInfo
{
float X { get; set; }
float Y { get; set; }
float Angle { get; set; }
Vector2 Origin { get; set; }
float Scale { get; set; }
float Depth { get; set; }
Color Color { get; set; }
Boolean Visible { get; set; }
Rectangle SourceRectangle { get; set; }
Rectangle DestinationRectangle { get; set; }
Rectangle BoundingRectangle { get; }
Matrix Matrix { get; }
SpriteSheet SpriteSheet { get; set; }
int SpriteSheetNum { get;}
Color[] TextureData { get; set; }
Vector2 GetVector2 { get; }
Vector3 GetVector3 { get; }
}
Some calculations in Update may be performed by GPU using CUDA technology (https://developer.nvidia.com/gpu-computing-sdk)
I can recommend several steps, and i hope some of them will be usefull:
1) Split your four spritesheets into several smaller spritesheets by category (Asteroids, Ships, bullets etc). MS always says, that several smaller image sources is better that one huge.
2) Get rid of background tiles for star field etc. Use HLSL to create starfield, explosions and effects. GPU perfomance is near "unlimited" for that kind of tasks and actually is good alternative of using CUDA.
3) Split collision detection process on independence tasks:
a) between active units
b) between active units and inactive environment(using collision map)
In strategies units should precalculate their path. So its nesessary to detect path intersections to recalculate pathes and prevent collisions.
All collisions should recalculates only in users viewport zone.
Active units should check collisions only near themselves.

Drawing a straight line from points

I am trying to make a simple chart application and I have stumbled upon a problem. I have an array of points which represent a 45 degree line (y = x) of 100 points. I then try to draw the line by converting each point's value to its pixel value and then draw lines from one point to another using graphic.DrawLines().
The the basic code:
struct MyPoint
{
double X { set; get; }
double Y { set; get; }
}
List<MyPoint> Values;
List<System.Drawing.Point> Points;
for (int i = 0; i < Values.Count; i++)
{
// convert point value to pixel coordinate
int x = (int)((Values[i].X - MinimumX) * (double)Control.Width / (MaximumX - MinimumX) + 0.5);
int y = (int)((Values[i].Y - MinimumY) * (double)Control.Height / (MaximumY - MinimumY)) + 0.5;
Points.Add(new System.Drawing.Point(x, y));
}
graphic.DrawLines(pen, Points.ToArray());
Note: MaximumX and MaximumY are 100 (number of points) and minimums are 0.
The thing is that this kind of line is not very straight :) So my question is, how would I draw a straight line through those points, so that it would look the same as
graphic.DrawLine(pen, 0, 0, Control.Width-1, Control.Height-1);
Thanks in advance!
Since you are doing a graph, I have a basic framework for fitting values within a control like this example:
public struct MyPoint
{
public double X { set; get; }
public double Y { set; get; }
public PointF ToPoint()
{
return new PointF((float)X, (float)Y);
}
}
/// <summary>
/// For http://stackoverflow.com/questions/19459519/drawing-a-straight-line-from-points
/// </summary>
public partial class Form1 : Form
{
List<MyPoint> points;
public Form1()
{
InitializeComponent();
// Initialize points to draw
points=new List<MyPoint>(100);
for (int i=0; i<=100; i++)
{
double θ=2*Math.PI*(i/100.0);
double x=(1+θ/Math.PI)*Math.Cos(θ);
double y=(1+θ/Math.PI)*Math.Sin(θ);
points.Add(new MyPoint() { X=x, Y=y });
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
// smooth graphics
e.Graphics.SmoothingMode=SmoothingMode.AntiAlias;
// set margins inside the control client area in pixels
var margin=new System.Drawing.Printing.Margins(16, 16, 16, 16);
// set the domain of (x,y) values
var range=new RectangleF(-3, -3, 6, 6);
// scale graphics
ScaleGraphics(e.Graphics, pictureBox1, range, margin);
// convert points to pixels
PointF[] pixels=points.Select((v) => v.ToPoint()).ToArray();
// draw arrow axes
using (var pen=new Pen(Color.Black, 0))
{
pen.EndCap=System.Drawing.Drawing2D.LineCap.ArrowAnchor;
e.Graphics.DrawLine(pen, range.Left, 0.0f, range.Right, 0.0f);
e.Graphics.DrawLine(pen, 0.0f, range.Top, 0.0f, range.Bottom);
}
// draw bounding rectangle (on margin)
using (var pen=new Pen(Color.LightGray, 0))
{
pen.DashStyle=DashStyle.Dash;
e.Graphics.DrawRectangle(pen, Rectangle.Round(range));
}
// draw curve
using (var pen = new Pen(Color.Blue, 0))
{
//e.Graphics.DrawLines(pen, pixels);
e.Graphics.DrawCurve(pen, pixels);
}
}
/// <summary>
/// Scales the Graphics to fit a Control, given a domain of x,y values and side margins in pixels
/// </summary>
/// <param name="g">The Graphics object</param>
/// <param name="control">The Control</param>
/// <param name="domain">The value domain</param>
/// <param name="margin">The margin</param>
void ScaleGraphics(Graphics g, Control control, RectangleF domain, Margins margin)
{
// Find the drawable area in pixels (control-margins)
int W=control.Width-margin.Left-margin.Right;
int H=control.Height-margin.Bottom-margin.Top;
// Ensure drawable area is at least 1 pixel wide
W=Math.Max(1, W);
H=Math.Max(1, H);
// Find the origin (0,0) in pixels
float OX=margin.Left-W*(domain.Left/domain.Width);
float OY=margin.Top+H*(1+domain.Top/domain.Height);
// Find the scale to fit the control
float SX=W/domain.Width;
float SY=H/domain.Height;
// Transform the Graphics
g.TranslateTransform(OX, OY);
g.ScaleTransform(SX, -SY);
}
private void pictureBox1_SizeChanged(object sender, EventArgs e)
{
// redraw on resize
pictureBox1.Refresh();
}
}
I think the problem is because your points are Integers. Precisions are lost.
Trying using
float x = (float)((Values[i] ...
List<System.Drawing.PointF> PointFs;
instead of
int x = (int)((Values[i] ...
List<System.Drawing.Point> Points;
You probably need something like this:
private void DrawLine()
{
int xInitial = 0, yInitial = 0, xFinal = Control.Width - 1, yFinal = Control.Height - 1;
int dx = xFinal - xInitial, dy = yFinal - yInitial, steps, k, xf, yf;
float xIncrement, yIncrement, x = xInitial, y = yInitial;
if (Math.Abs(dx) > Math.Abs(dy))
steps = Math.Abs(dx);
else
steps = Math.Abs(dy);
xIncrement = dx / (float)steps;
yIncrement = dy / (float)steps;
for (k = 0; k < steps; k++)
{
x += xIncrement;
xf = (int)x;
y += yIncrement;
yf = (int)y;
Points.Add(new System.Drawing.Point(xf, yf));
}
graphic.DrawLines(pen, Points.ToArray());
}
For more details see:
Bresenham's line algorithm
If your points already calculated correctly by algorithm, than simple:
int x = (int) Values[i].X;
int y = (int) Values[i].Y;
Should be enough.
here is my solution:
System.Drawing.Pen myPen;
myPen = new System.Drawing.Pen(System.Drawing.Color.Red);
System.Drawing.Graphics formGraphics = this.CreateGraphics();
formGraphics.DrawLine(myPen, 0, 0, 200, 200);
myPen.Dispose();
formGraphics.Dispose();

2D Per pixel collision detection not working

I would like to say sorry in advance for the amount of code I will post, but I can't seem to get my collision detection to work, the player and the objects pass through each other with no effect when I play test.
I receive 0 warnings or errors, but the Player playerBot object does not seem to interact with any of the level items I have imported from GLEED2D.
One thing I did was to store the values of my item's rectangles and color arrays in lists so they could be all easily iterated through, maybe this is the source of the problem.
If you can spot why my code is not working I will be hugely grateful. I have removed any code that is definitely not relevant, and I am running VS 2010 with GLEED2D 1.3 if that helps.
Thanks again.
// Item Class ImageItem Downcast
public class ImageItem : Item
{
public Texture2D Texture;
}
// Level
Level level;
// Ints
int iNumOfItems = 0;
int iTextureDataListNum = 0;
int iRectangleListNum = 0;
// Lists
List<Color []> itemTextureDataList = new List<Color[]>();
List<Rectangle> itemRectangleList = new List<Rectangle>();
protected override void Initialize()
{
if (filename.Length > 0) level = Level.FromFile(filename, Content);
else level = Level.FromFile("level1.xml", Content);
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
iNumOfItems =+ 1;
}
}
// Creates Player Ship
playerBot = new Player(new Vector2(400f, 240f), new Vector2(0f, 0f));
base.Initialize();
}
protected override void LoadContent()
{
Texture2D pixel = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
pixel.SetData(new[] { Color.White });
spriteBatch = new SpriteBatch(GraphicsDevice);
// Player Bot
playerBot.LoadContent(Content, "Images/Player Bot Sprite Sheet", 40, 40, 4);
// Assigns level textures color data to array
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
ImageItem imageItem = item as ImageItem;
if (imageItem != null)
{
Texture2D texture = imageItem.Texture;
itemTextureDataList[iTextureDataListNum] = new Color[imageItem.Texture.Width * imageItem.Texture.Height];
imageItem.Texture.GetData(itemTextureDataList[iTextureDataListNum]);
iTextureDataListNum++;
}
}
}
// Creates a rectangle for every level texture
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
ImageItem imageItem = item as ImageItem;
if (imageItem != null)
{
itemRectangleList[iRectangleListNum] = new Rectangle((int)imageItem.Position.X, (int)imageItem.Position.Y, imageItem.Texture.Width, imageItem.Texture.Height);
iRectangleListNum++;
}
}
}
spriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void Update(GameTime gameTime)
{
// Player Update
playerBot.Update(gameTime);
((Sprite)playerBot).Update(gameTime);
// Check for player collisons with level
for (int i = 0; i < iNumOfItems - 1; i++)
{
if (IntersectPixels(playerBot.colRectangle, playerBot.textureDataArray, itemRectangleList[i], itemTextureDataList[i]) == true)
{
playerBot.StopMovement();
}
}
base.Update(gameTime);
}
// Level Collision Detection Method
static bool IntersectPixels(Rectangle rectangleA, Color[] dataA, Rectangle rectangleB, Color[] dataB)
{
// Find the bounds of the rectangle intersection
int top = Math.Max(rectangleA.Top, rectangleB.Top);
int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
int left = Math.Max(rectangleA.Left, rectangleB.Left);
int right = Math.Min(rectangleA.Right, rectangleB.Right);
// Check every point within the intersection bounds
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
// Get the color of both pixels at this point
Color colorA = dataA[(x - rectangleA.Left) + (y - rectangleA.Top) * rectangleA.Width];
Color colorB = dataB[(x - rectangleB.Left) + (y - rectangleB.Top) * rectangleB.Width];
// If both pixels are not completely transparent
if (colorA.A != 0 && colorB.A != 0)
{
// Then an intersection has been found
return true;
}
}
}
// No intersection fond
return false;
}
// Sprite Class
public void Update(GameTime gameTime)
{
textureDataArray = new Color[texture.Width * texture.Height];
texture.GetData(textureDataArray);
// Player Class
public void StopMovement()
{
velocity.X *= -1;
velocity.Y *= -1;
}
The first thing I'll say here is, you should aim to be more Object Oriented in your approach. Storing a list of textures and a list of rectangles alongside your list of Items is "bad" technique and going to eventually cause you some massive headaches when it comes to debugging.
So first of all, instead of having a list of Color[] and a list of Rectangle, add one Color[] and one Rectangle to your ImageItem class and work with those instead, or at least create a little class called "CollisionData" that has a Rectangle and a Color[] and store those in a single list.
Secondly, note that there is a Rectangle.Intersect(Rectangle A, Rectangle B) that gets your the rectangle of intersection. So you can tidy up your code a bit by using that.
Your color checking can be simplified to (ColorA.A * ColorB.A != 0) as either being zero will
cause the result to be zero.
Regarding not getting any errors, put a breakpoint at the start of the collision checking loop. Does the application break? If yes, what is the value of iNumItems? (you can hover over it to see the current value at point of breaking). If no, then that section of code isn't being reached. Put another breakpoint a bit further back and a bit further back until it gets hit, then figure out why the code isn't executing.

Tile Rectangle Collisions

I have a map that is 1280 by 1280 pixels.
I am drawing tiles on the screen that are 32 by 32 pixels.
I also give each tile a rectangle of 32 by 32 pixels.
Each tile also has a bool telling me if it is blocked or not.
My sprite has a rectangle of 32 by 32 pixels.
When the sprite and tile intersect the top left most pixel of the tile is the only one that makes contact with the sprites rectangle.
I'm tried inflating the rectangle with no change.
Core.playerOneSpriteRectangle = new Rectangle((int)Core.playerOneSprite.Position.X, (int)Core.playerOneSprite.Position.Y, Engine.TileWidth, Engine.TileHeight);
#region Player One Controls
// Player One Controls
Tile tile;
Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);
if (keyState.IsKeyDown(Keys.W) || (gamePadOneState.DPad.Up == ButtonState.Pressed))
{
Core.playerOneSprite.CurrentAnimation = AnimationKey.Up;
foreach (var layer in tileMapLayers)
{
for (var y = 0; y < layer.Height; y++)
{
destination.Y = y * Engine.TileHeight;
for (var x = 0; x < layer.Width; x++)
{
tile = layer.GetTile(x, y);
destination.X = x * Engine.TileWidth;
// Check Collision With Tiles
if (Core.playerOneSpriteRectangle.Intersects(destination) && tile.TileBlocked)
{
playerOneMotion.Y = destination.Bottom;
//Console.WriteLine("Intersected with" + destination + tile.TileBlocked);
}
else if (Core.playerOneSpriteRectangle.Intersects(destination) && tile.TileBlocked == false)
{
playerOneMotion.Y = -1f;
}
}
}
}
}
I'm trying to create rectangles for each tile so my sprite doesn't walk through them.
UPDATE
This is the code I currently have after modifying it with suggestions.
Tile tile;
Point playertile = new Point((int)Core.playerOneSprite.Position.X / Engine.TileWidth, (int)Core.playerOneSprite.Position.Y / Engine.TileHeight);
if (keyState.IsKeyDown(Keys.W) || (gamePadOneState.DPad.Up == ButtonState.Pressed))
{
Core.playerOneSprite.CurrentAnimation = AnimationKey.Up;
foreach (var layer in GraelenAreaOne.graelenAreaOneSplatterTileMapLayers)
{
tile = layer.GetTile(playertile.X, playertile.Y - 1);
// Check Collision With Tiles
if (tile.TileBlocked)
{
playerOneMotion.Y = 0;
//Console.WriteLine("Intersected with" + destination + tile.TileBlocked);
}
else
{
playerOneMotion.Y = -1;
}
}
}
I am now able to collide with tiles however my sprites rectangle is not intersecting properly. Position 0,0 of the sprite texture is in the top left most corner and it only a 1x1 pixel.
Why isn't the rectangle encompassing the entire texture as I have it set to 32x32?
You should just check whether the tile you want to go to is walkable or not, without having to bother yourself with rectangles.
Some pseudo code :
// Convert pixel coordinates to tile coords
Point playerTile = new Point(player.X / tileWidth, player.Y / tileHeight);
Point targetTile = new Point(target.X / tileWidth, target.Y / tileHeight);
// Take a decision
if(Direction == Up)
var tile = layer.GetTile(playerTile.X, playerTile.Y - 1);
if(tile == walkable)
// Move to your tile
else
...
Additionally, here's some code I wrote a while ago which might interest you in optimizing the drawing of your level by drawing only draws what's visible.
https://gamedev.stackexchange.com/questions/29121/organize-a-game-set/29930#29930
Note : https://gamedev.stackexchange.com/ is definitely a better place for these kind of questions.
EDIT
Here's a quick example that works for me :
Note that I return when player cannot move to tile.
Tiles : 0 is walkable, 1 is a wall, 2 is player
using System;
using System.Linq;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input.Touch;
using Point = Microsoft.Xna.Framework.Point;
namespace SlXnaApp1
{
public partial class GamePage : PhoneApplicationPage
{
private readonly GameTimer _timer;
private int[] _levels;
private Point _player;
private Texture2D _t1;
private Texture2D _t2;
private Texture2D _t3;
private Texture2D[] _textures;
private ContentManager _contentManager;
private SpriteBatch _spriteBatch;
public GamePage()
{
InitializeComponent();
// Get the content manager from the application
_contentManager = (Application.Current as App).Content;
// Create a timer for this page
_timer = new GameTimer();
_timer.UpdateInterval = TimeSpan.FromTicks(333333);
_timer.Update += OnUpdate;
_timer.Draw += OnDraw;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Set the sharing mode of the graphics device to turn on XNA rendering
var graphicsDevice = SharedGraphicsDeviceManager.Current.GraphicsDevice;
graphicsDevice.SetSharingMode(true);
// Create a new SpriteBatch, which can be used to draw textures.
_spriteBatch = new SpriteBatch(graphicsDevice);
// TODO: use this.content to load your game content here
_t1 = new Texture2D(graphicsDevice, 32, 32);
_t1.SetData(Enumerable.Repeat(Color.Red, 32*32).ToArray());
_t2 = new Texture2D(graphicsDevice, 32, 32);
_t2.SetData(Enumerable.Repeat(Color.Green, 32*32).ToArray());
_t3 = new Texture2D(graphicsDevice, 32, 32);
_t3.SetData(Enumerable.Repeat(Color.Blue, 32*32).ToArray());
_textures = new[] {_t1, _t2, _t3};
_levels = new int[4*4]
{
2, 0, 0, 0,
0, 0, 1, 0,
1, 1, 1, 0,
0, 0, 0, 1,
};
_player = new Point();
TouchPanel.EnabledGestures = GestureType.Flick;
// Start the timer
_timer.Start();
base.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
// Stop the timer
_timer.Stop();
// Set the sharing mode of the graphics device to turn off XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
base.OnNavigatedFrom(e);
}
/// <summary>
/// Allows the page to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
private void OnUpdate(object sender, GameTimerEventArgs e)
{
Vector2 vector = new Vector2();
while (TouchPanel.IsGestureAvailable)
{
var gestureSample = TouchPanel.ReadGesture();
var vector2 = gestureSample.Delta;
vector2.Normalize();
vector = new Vector2((float) Math.Round(vector2.X), (float) Math.Round(vector2.Y));
}
Direction direction = new Direction();
if (vector.X > 0) direction = Direction.Right;
else if (vector.X < 0) direction = Direction.Left;
else if (vector.Y < 0) direction = Direction.Up;
else if (vector.Y > 0) direction = Direction.Down;
Point newPos = new Point();
switch (direction)
{
case Direction.None:
return;
case Direction.Up:
if (GetTile(_player.X, _player.Y - 1) == 0)
newPos = new Point(_player.X, _player.Y - 1);
else return;
break;
case Direction.Down:
if (GetTile(_player.X, _player.Y + 1) == 0)
newPos = new Point(_player.X, _player.Y + 1);
else return;
break;
case Direction.Left:
if (GetTile(_player.X - 1, _player.Y) == 0)
newPos = new Point(_player.X - 1, _player.Y);
else return;
break;
case Direction.Right:
if (GetTile(_player.X + 1, _player.Y) == 0)
newPos = new Point(_player.X + 1, _player.Y);
else return;
break;
default:
throw new ArgumentOutOfRangeException();
}
SetTile(_player.X, _player.Y, 0);
SetTile(newPos.X, newPos.Y, 2);
_player = newPos;
}
private int GetTile(int x, int y)
{
return _levels[y*4 + x];
}
private void SetTile(int x, int y, int value)
{
_levels[y*4 + x] = value;
}
/// <summary>
/// Allows the page to draw itself.
/// </summary>
private void OnDraw(object sender, GameTimerEventArgs e)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
_spriteBatch.Begin();
for (int i = 0; i < _levels.Length; i++)
{
var tile = _levels[i];
Point point = new Point(i%4, i/4);
var texture2D = _textures[tile];
_spriteBatch.Draw(texture2D, Vector2.Multiply(new Vector2(point.X, point.Y), 32), Color.White);
}
_spriteBatch.End();
}
private enum Direction
{
None,
Up,
Down,
Left,
Right
}
}
}
Also, due to the way I built my level as a 1-dimensional array, the tile moves down when for instance it is at x=3, y=0 and going to the right; this is unintended at all but is normal as I don't check bounds. You'd want to keep your level as a 2-dimensional array for simplicity.

Categories