I'm building a small top down shooter in XNA using C#, and I am trying to implement per-pixel collision detection. I have the following code to do that, along a standard bounding box detection that returns the rectangle containing the collision.
private bool perPixel(Rectangle object1, Color[] dataA, Rectangle object2, Color[] dataB)
{
//Bounds of collision
int top = Math.Max(object1.Top, object2.Top);
int bottom = Math.Min(object1.Bottom, object2.Bottom);
int left = Math.Max(object1.Left, object2.Left);
int right = Math.Min(object1.Right, object2.Right);
//Check every pixel
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
//Check alpha values
Color colourA = dataA[(x - object1.Left) + (y - object1.Top) * object1.Width];
Color colourB = dataB[(x - object2.Left) + (y - object2.Top) * object2.Width];
if (colourA.A != 0 && colourB.A != 0)
{
return true;
}
}
}
return false;
}
I'm pretty sure that that will work, but I am also trying to get some of the objects to check against from a sprite sheet, and I'm trying to use this code to get the colour data, but it is getting an error saying that "The size of the data passed in is too large or small for this resource".
Color[] pacmanColour = new Color[frameSize.X * frameSize.Y];
pacman.GetData(0, new Rectangle(currentFrame.X * frameSize.X, currentFrame.Y * frameSize.Y, frameSize.X, frameSize.Y), pacmanColour,
currentFrame.X * currentFrame.Y, (sheetSize.X * sheetSize.Y));
What am I doing wrong?
Let me show you my method for dealing with Texture2D Colors
I used the following technique for loading premade structures from files
//Load the texture from the content pipeline
Texture2D texture = Content.Load<Texture2D>("Your Texture Name and Directory");
//Convert the 1D array, to a 2D array for accessing data easily (Much easier to do Colors[x,y] than Colors[i],because it specifies an easy to read pixel)
Color[,] Colors = TextureTo2DArray(texture);
And the function...
Color[,] TextureTo2DArray(Texture2D texture)
{
Color[] colors1D = new Color[texture.Width * texture.Height]; //The hard to read,1D array
texture.GetData(colors1D); //Get the colors and add them to the array
Color[,] colors2D = new Color[texture.Width, texture.Height]; //The new, easy to read 2D array
for (int x = 0; x < texture.Width; x++) //Convert!
for (int y = 0; y < texture.Height; y++)
colors2D[x, y] = colors1D[x + y * texture.Width];
return colors2D; //Done!
}
It will return a simple to use 2D array of colors, So you can simply check if Colors[1,1] (For pixel 1,1) equals whatever.
Related
I'm new to Unity 3D and trying to split a texture2D sprite that contains an audio waveform in a Scroll Rect. The waveform comes from an audio source imported by the user and added to a scroll rect horizontally like a timeline. The script that creates the waveform works but the variable of the width (that came from another script, but this is not the problem) exceeds the limits of a Texture2D, only if I put manually a width less than 16000 the waveform appear but not to the maximum of the scroll rect. Usually, a song with 3-4min has a width of 55000-60000 width, and this can't be rendered. I need to split that waveform texture2D sprite horizontally into multiple parts (or Childs) together and render them only when appearing on the screen. How can I do that? Thank you in advance.
This creates the Waveform Sprite, and should split the sprite into multiple sprites and put together horizontally, render them only when appear on the screen):
public void LoadWaveform(AudioClip clip)
{
Texture2D texwav = waveformSprite.GetWaveform(clip);
Rect rect = new Rect(Vector2.zero, new Vector2(Realwidth, 180));
waveformImage.sprite = Sprite.Create(texwav, rect, Vector2.zero);
waveformImage.SetNativeSize();
}
This creates the waveform from an audio clip (getting from the internet and modifying for my project) :
public class WaveformSprite : MonoBehaviour
{
private int width = 16000; //This should be the variable from another script
private int height = 180;
public Color background = Color.black;
public Color foreground = Color.yellow;
private int samplesize;
private float[] samples = null;
private float[] waveform = null;
private float arrowoffsetx;
public Texture2D GetWaveform(AudioClip clip)
{
int halfheight = height / 2;
float heightscale = (float)height * 0.75f;
// get the sound data
Texture2D tex = new Texture2D(width, height, TextureFormat.RGBA32, false);
waveform = new float[width];
Debug.Log("NUMERO DE SAMPLES: " + clip.samples);
var clipSamples = clip.samples;
samplesize = clipSamples * clip.channels;
samples = new float[samplesize];
clip.GetData(samples, 0);
int packsize = (samplesize / width);
for (int w = 0; w < width; w++)
{
waveform[w] = Mathf.Abs(samples[w * packsize]);
}
// map the sound data to texture
// 1 - clear
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
tex.SetPixel(x, y, background);
}
}
// 2 - plot
for (int x = 0; x < width; x++)
{
for (int y = 0; y < waveform[x] * heightscale; y++)
{
tex.SetPixel(x, halfheight + y, foreground);
tex.SetPixel(x, halfheight - y, foreground);
}
}
tex.Apply();
return tex;
}
}
Instead of reading all the samples in one loop to populate waveform[], read only the amount needed for the current texture (utilizing an offset to track position in the array).
Calculate the number of textures your function will output.
var textureCount = Mathf.CeilToInt(totalWidth / maxTextureWidth); // max texture width 16,000
Create an outer loop to generate each texture.
for (int i = 0; i < textureCount; i++)
Calculate the current textures width (used for the waveform array and drawing loops).
var textureWidth = Mathf.CeilToInt(Mathf.Min(totalWidth - (maxTextureWidth * i), maxWidth));
Utilize an offset for populating the waveform array.
for (int w = 0; w < textureWidth; w++)
{
waveform[w] = Mathf.Abs(samples[(w + offset) * packSize]);
}
With offset increasing at the end of the textures loop by the number of samples used for that texture (ie texture width).
offset += textureWidth;
In the end the function will return an array of Texture2d instead of one.
I have an image (attached) which I'm using as a test. I'm trying to get and store all the colours of each pixel in an array.
I use the below code to do this;
Texture2D tex = mapImage.mainTexture as Texture2D;
int w = tex.width;
int h = tex.height;
Vector4[,] vals = new Vector4[w, h];
Color[] cols = tex.GetPixels();
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
if(cols[y+x] != Color.black)
{
Debug.Break();
}
vals[x, y] = cols[(y + x)];
}
}
Where mapImage is a public Material variable which I drag in into the scene on the prefab. As you can see, I've added a debug test there to pause the editor if a non-black colour is reached. This NEVER gets hit ever.
Interestingly, I've got another script which runs and tells me the colour values (GetPixel()) at the click position using the same image. It works fine (different methods, but both ultimately use the same material)
I'm at a loss as to why GetPixels() is always coming out black?
I've also been considering just loading the image data into a byte array, then parsing the values into a Vector4, but hoping this will work eventually.
You aren't indexing into the Color array properly. With the indices you are using, y+x, you keep checking the same values on the lowest rows of the texture, never getting past a certain point.
Instead, when calculating the index, you need to multiply the row that you are on by the row length and add that to the column you are on:
Texture2D tex = mapImage.mainTexture as Texture2D;
int w = tex.width;
int h = tex.height;
Vector4[,] vals = new Vector4[w, h];
Color[] cols = tex.GetPixels();
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
int index = y * w + x;
vals[x, y] = cols[index];
}
}
From the documentation on GetPixels:
The returned array is a flattened 2D array, where pixels are laid out left to right, bottom to top (i.e. row after row). Array size is width by height of the mip level used. The default mip level is zero (the base texture) in which case the size is just the size of the texture. In general case, mip level size is mipWidth=max(1,width>>miplevel) and similarly for height.
I want a way to find out if, for example, the pixel at Vector2(2, 5) on the game window is color Color.Red, or some other color or set of coordinates. How can I do this?
Convert texture into array, and then based on coordinated find specified pixel and get color. Example can be found here on Reimers XNA webpage.
private Color[,] TextureTo2DArray(Texture2D texture)
{
Color[] colors1D = new Color[texture.Width * texture.Height];
texture.GetData(colors1D);
Color[,] colors2D = new Color[texture.Width, texture.Height];
for (int x = 0; x < texture.Width; x++)
{
for (int y = 0; y < texture.Height; y++)
{
colors2D[x, y] = colors1D[x + y * texture.Width];
}
}
return colors2D;
}
converting color to argb
public static string ToHex(this Color color, bool includeHash)
{
string[] argb = {
color.A.ToString("X2"),
color.R.ToString("X2"),
color.G.ToString("X2"),
color.B.ToString("X2"),
};
return (includeHash ? "#" : string.Empty) + string.Join(string.Empty, argb);
}
You should first read the backbuffer (using GraphicsDevice.GetBackBufferData()) of the graphics device into a texture and then inspect the texture as described above.
Im a C#/XNA student and I've recently been working on an isometric tile engine and so far it works fairly well. But im having problem trying to figure out on how to do collision, this is what my tile engine does at the moment:
Draws the world from an image and place a tile depending on what color is on my image. For instance color red would draw a grass tile. (Tiles are 64x32)
Camera following player, and my draw loop only draws what the camera sees.
This is how my game looks if that would be of any help:
I don't know what sort of collision would work best. Should i do collision points, or intersects or any other sort of collision. I've read somewhere that you could do Worldtoscreen/Screentoworld but im far to inexperienced and don't know how that works nor how the code would look like.
Here is my code drawing tiles etc:
class MapRow
{
public List<MapCell> Columns = new List<MapCell>();
}
class TileMap
{
public List<MapRow> Rows = new List<MapRow>();
public static Texture2D image;
Texture2D tileset;
TileInfo[,] tileMap;
Color[] pixelColor;
public TileMap(string TextureImage, string Tileset)
{
tileset = Game1.Instance.Content.Load<Texture2D>(Tileset);
image = Game1.Instance.Content.Load<Texture2D>(TextureImage);
pixelColor = new Color[image.Width * image.Height]; // pixelColor array that is holding all pixel in the image
image.GetData<Color>(pixelColor); // Save all the pixels in image to the array pixelColor
tileMap = new TileInfo[image.Height, image.Width];
int counter = 0;
for (int y = 0; y < image.Height; y++)
{
MapRow thisRow = new MapRow();
for (int x = 0; x < image.Width; x++)
{
tileMap[y, x] = new TileInfo();
if (pixelColor[counter] == new Color(0, 166, 81))
{
tileMap[y, x].cellValue = 1;//grass
}
if (pixelColor[counter] == new Color(0, 74, 128))
{
tileMap[y, x].cellValue = 2;//water
}
if (pixelColor[counter] == new Color(255, 255, 0))
{
tileMap[y, x].cellValue = 3;//Sand
}
tileMap[y, x].LoadInfoFromCellValue();//determine what tile it should draw depending on cellvalue
thisRow.Columns.Add(new MapCell(tileMap[y, x]));
counter++;
}
Rows.Add(thisRow);
}
}
public static int printx;
public static int printy;
public static int squaresAcross = Settings.screen.X / Tile.TileWidth;
public static int squaresDown = Settings.screen.Y / Tile.TileHeight;
int baseOffsetX = -32;
int baseOffsetY = -64;
public void draw(SpriteBatch spriteBatch)
{
printx = (int)Camera.Location.X / Tile.TileWidth;
printy = (int)Camera.Location.Y / Tile.TileHeight;
squaresAcross = (int)Camera.Location.X / Tile.TileWidth + Settings.screen.X / Tile.TileWidth;
squaresDown = 2*(int)Camera.Location.Y / Tile.TileHeight + Settings.screen.Y / Tile.TileHeight + 7;
for (printy = (int)Camera.Location.Y / Tile.TileHeight; printy < squaresDown; printy++)
{
int rowOffset = 0;
if ((printy) % 2 == 1)
rowOffset = Tile.OddRowXOffset;
for (printx = (int)Camera.Location.X / Tile.TileWidth; printx < squaresAcross; printx++)
{
if (tileMap[printy, printx].Collides(MouseCursor.mousePosition))
Console.WriteLine(tileMap[printy, printx].tileRect);
foreach (TileInfo tileID in Rows[printy].Columns[printx].BaseTiles)
{
spriteBatch.Draw(
tileset,
tileMap[printy, printx].tileRect = new Rectangle(
(printx * Tile.TileStepX) + rowOffset + baseOffsetX,
(printy * Tile.TileStepY) + baseOffsetY,
Tile.TileWidth, Tile.TileHeight),
Tile.GetSourceRectangle(tileID.cellValue),
Color.White,
0.0f,
Vector2.Zero,
SpriteEffects.None,
tileID.drawDepth);
}
}
}
}
}
Why don't you just draw stuff just like in normal tile based games, and then rotate the camera with a 45degree? Of course then you'd need to make your graphics a bit odd, but would be easier to handle the tiles.
But if you prefer your way, then I'd suggest using simple math to calculate the "tile to the right", "tile to the left" , "tile to the up" and "tile to the down" ones, you know, the tiles around the player(or another tile). You can simply work with your lists, and with some math, basic math, like getting the next tile, is quite simple.
Edit:
You could get the player's next position's tile value with a code something like this:
tileMap[Math.Floor((player.y+playerVelociy.Y)/tileHeight)]
[Math.Floor((player.x+playerVelocity.X)/tileWidth)]
In this code, I assume that the first tile is at 0,0 and you're drawing to right and down. (If not, then just change the Math.Floor to Math.Ceil)
THIS link could help you get the idea, however it's in AS3.0, only the syntax is different.
I want to draw a 2d, filled, circle. I've looked everywhere and cannot seem to find anything that will even remotely help me draw a circle. I simply want to specify a height and width and location on my canvas.
Anyone know how?
Thanks!
XNA doesn't normally have an idea of a canvas you can paint on. Instead you can either create a circle in your favorite paint program and render it as a sprite or create a series vertexes in a 3D mesh to approximate a circle and render that.
You could also check out the sample framework that Jeff Weber uses in Farseer:
http://www.codeplex.com/FarseerPhysics
The demos have a dynamic texture generator that let's him make circles and rectangles (which the samples then use as the visualization of the physics simulation). You could just re-use that :-)
Had the same problem, as others already suggested you need to draw a square or rectangle with a circle texture on it. Here follows my method to create a circle texture runtime. Not the most efficient or fancy way to do it, but it works.
Texture2D createCircleText(int radius)
{
Texture2D texture = new Texture2D(GraphicsDevice, radius, radius);
Color[] colorData = new Color[radius*radius];
float diam = radius / 2f;
float diamsq = diam * diam;
for (int x = 0; x < radius; x++)
{
for (int y = 0; y < radius; y++)
{
int index = x * radius + y;
Vector2 pos = new Vector2(x - diam, y - diam);
if (pos.LengthSquared() <= diamsq)
{
colorData[index] = Color.White;
}
else
{
colorData[index] = Color.Transparent;
}
}
}
texture.SetData(colorData);
return texture;
}
Out of the box, there's no support for this in XNA. I'm assuming you're coming from some GDI background and just want to see something moving around onscreen. In a real game though, this is seldom if ever needed.
There's some helpful info here:
http://forums.xna.com/forums/t/7414.aspx
My advice to you would be to just fire up paint or something, and create the basic shapes yourself and use the Content Pipeline.
Another option (if you want to use a more complex gradient brush or something) is to draw a quad aligned to the screen and use a pixel shader.
What I did to solve this was to paint a rectangular texture, leaving the area of the rectangle which doesn't contain the circle transparent. You check to see if a point in the array is contained within a circle originating from the center of the rectangle.
Using the color data array is a bit weird because its not a 2D array. My solution was to bring in some 2D array logic into the scenario.
public Texture2D GetColoredCircle(float radius, Color desiredColor)
{
radius = radius / 2;
int width = (int)radius * 2;
int height = width;
Vector2 center = new Vector2(radius, radius);
Circle circle = new Circle(center, radius,false);
Color[] dataColors = new Color[width * height];
int row = -1; //increased on first iteration to zero!
int column = 0;
for (int i = 0; i < dataColors.Length; i++)
{
column++;
if(i % width == 0) //if we reach the right side of the rectangle go to the next row as if we were using a 2D array.
{
row++;
column = 0;
}
Vector2 point = new Vector2(row, column); //basically the next pixel.
if(circle.ContainsPoint(point))
{
dataColors[i] = desiredColor; //point lies within the radius. Paint it.
}
else
{
dataColors[i] = Color.Transparent; //point lies outside, leave it transparent.
}
}
Texture2D texture = new Texture2D(GraphicsDevice, width, height);
texture.SetData(0, new Rectangle(0, 0, width, height), dataColors, 0, width * height);
return texture;
}
And here's the method to check whether or not a point is contained within your circle:
public bool ContainsPoint(Vector2 point)
{
return ((point - this.Center).Length() <= this.Radius);
}
Hope this helps!
public Texture2D createCircleText(int radius, GraphicsDevice Devise,Color color,int tickenes)
{
Texture2D texture = new Texture2D(Devise, radius, radius);
Color[] colorData = new Color[radius * radius];
if (tickenes >= radius) tickenes = radius - 5;
float diam = radius / 2f;
float diamsq = diam * diam;
float intdiam = (radius-tickenes) / 2f;
float intdiamsq = intdiam * intdiam;
for (int x = 0; x < radius; x++)
{
for (int y = 0; y < radius; y++)
{
int index = x * radius + y;
Vector2 pos = new Vector2(x - diam, y - diam);
if (pos.LengthSquared() <= diamsq)
{
colorData[index] = color;
}
else
{
colorData[index] = Color.Transparent;
}
if (pos.LengthSquared() <= intdiamsq)
{
colorData[index] = Color.Transparent;
}
}
}
texture.SetData(colorData);
return texture;
}