This method that draws my tiles seems to be quite slow, Im not sure exactly whats wrong, it belive my culling method isnt working and is drawing stuff offscreen, but im not completeley sure. Here it is:
// Calculate the visible range of tiles.
int left = (int)Math.Floor(cameraPosition.X / 16);
int right = left + spriteBatch.GraphicsDevice.Viewport.Width / 16;
right = Math.Min(right, Width) + 1; // Width -1 originally - didn't look good as tiles drawn on screen
if (right > tiles.GetUpperBound(0))
right = tiles.GetUpperBound(0) + 1; // adding 1 to get the last right tile drawn
int top = (int)Math.Floor(cameraPosition.Y / 16);
int bottom = left + spriteBatch.GraphicsDevice.Viewport.Height/ 16;
bottom = Math.Min(bottom, Height) + 1; // Height -1 originally - didn't look good as tiles drawn on screen
if (bottom > tiles.GetUpperBound(1))
bottom = tiles.GetUpperBound(1) + 1; // adding 1 to get the last bottom tile drawn
// For each tile position
for (int y = top; y < bottom; ++y)
{
for (int x = left; x < right; ++x)
{
// If there is a visible tile in that position, draw it
if (tiles[x, y].BlockType.Name != "Blank")
{
Texture2D texture = tileContent["DirtBlock_" + getTileSetType(tiles,x,y)];
spriteBatch.Draw(texture, new Vector2(x * 16, y * 16), Color.White);
if (isMinimap)
spriteBatch.Draw(pixel, new Vector2(30+x, 30+y), Color.White);
}
}
}
GetTileSetTypes is a function to get what tiles are around it, for different textures, like DirtBlock_North, DirtBlock_Center, etc.
Tile content is just a class with my block textures.
Try changing SpriteBatch.Begin to defered and combining all of the tiles onto one texture.
See this GameDev question for info about why deferred is most likely the fastest option for you.
Also realize that every time you draw a new texture you have to take the old one out of the GPU and put the new one in. This process is called texture swapping and usually isn't an issue but you are swapping textures twice per tile which is likely to impact performance noticeably.
This can be fixed by combining multiple sprites onto one texture and using the source rectangle argument. This allows you to draw multiple sprites without a texture swap. There are a few OSS libraries for this. Sprite Sheet Packer is my personal favorite.
Unfortunantly without the project and a profiler I'm just guessing; however, these are the two biggest gotchas for rendering tilemaps I know of. I can't really see anything wrong from here. Below is the code I use to draw my tile maps and as you see its very similar to yours.
If all else fails I would suggest using a profiler to figure out which bits are running slowly.
//Init the holder
_holder = new Rectangle(0, 0, TileWidth, TileHeight);
//Figure out the min and max tile indices to draw
var minX = Math.Max((int)Math.Floor((float)worldArea.Left / TileWidth), 0);
var maxX = Math.Min((int)Math.Ceiling((float)worldArea.Right / TileWidth), Width);
var minY = Math.Max((int)Math.Floor((float)worldArea.Top / TileHeight), 0);
var maxY = Math.Min((int)Math.Ceiling((float)worldArea.Bottom / TileHeight), Height);
for (var y = minY; y < maxY; y++) {
for (var x = minX; x < maxX; x++) {
_holder.X = x * TileWidth;
_holder.Y = y * TileHeight;
var t = tileLayer[y * Width + x];
spriteBatch.Draw(
t.Texture,
_holder,
t.SourceRectangle,
Color.White,
0,
Vector2.Zero,
t.SpriteEffects,
0);
}
}
Related
I am procedurally generating textures (in Unity3D using C#), each of which are bound to an Unlit/Texture Material attached to a plane with Offset 0, 0 and Tiling 1, 1. When I generate the textures, they work great with many resolutions and very poorly with others.
Here is what it looks like when it works:
And here is what it looks like when it doesn't:
I have messed around with the Texture2D() mipchain argument, the Apply() updateMipmaps argument, and filterMode types. None have been to any avail. Here is my code below:
// GENERATE AND APPLY TEXTURE
Texture2D texture = new Texture2D(width, height, TextureFormat.ARGB32, false); // width and height are integers
texture.filterMode = FilterMode.Point;
// 0 -> width/height
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
// 0 -> 1
float i = ((float)x / (float)width) * (float)width / (float)height;
float j = (float)y / (float)height;
bool open = ((Mathf.PerlinNoise((offset.x + i) * stretch, (offset.y + j) * stretch)) < clamp); // offset is a Vector2Int; stretch is a float; and clamp is a float
Color col = open ? empty : full; // empty and full are white and black, respectively
texture.SetPixel(x, y, col);
}
}
texture.Apply();
mat.SetTexture("_MainTex", texture);
What am I missing? Surely there's a way to make the sprite look normal. Another note is sometimes after initially generating the texture, the first time I regenerate the same texture after, it fixes some minor horizontal rendering issues. Any help would be greatly appreciated—thank you!
I'm setting up an automatic system to be able to attach a sprite and it will gather all its colours and the world position of each sprite. A list/class of all the colours used has been set up but how would get the position of all these sprites?
I have already tried doing this mathematically like getting the complete size of the sprite and then working out the size of each pixel and then working out the position from that. But this seems flawed due to the position of the sprite possibly changing.
Sprite ColouredSpriteTexture = ColoredSprite.GetComponent<SpriteRenderer>().sprite;
Texture2D ColouredTexture = ColouredSpriteTexture.texture;
float XsizeF = ColoredSprite.transform.localScale.x;
int Xsize = (int)XsizeF;
float YsizeF = ColoredSprite.transform.localScale.y;
int Ysize = (int)YsizeF;
List<Color> TempList = new List<Color>();
//Could spawn pixels by getting x and y size and dividing them by 100 50/100 = 0.50f
//if the tile has a color then spawn pixel if not 0.50 += 0.50
//TODO test if this logic will work
float PixelSize = XsizeF / 100;
float currentPos = PixelSize;
for (int x = 0; x < Xsize; x++)
{
for (int y = 0; y < Ysize; y++)
{
int listAmount = TempList.Count;
Color ColoredTex = ColouredTexture.GetPixel(x, y);
float TextureAlpha = ColoredTex.a;
if (!TempList.Contains(ColoredTex) && TextureAlpha != 0)
{
TempList.Add(ColoredTex);
ColorByNumber tempColor = new ColorByNumber();
tempColor.Color = ColoredTex;
tempColor.ColorNumber = listAmount;
ColorOptions.Add(tempColor);
}
if(TextureAlpha == 1)
{
GameObject ColorPixel = Instantiate(PixelPrefab);
ColorPixel.transform.localScale = new Vector3(XsizeF, YsizeF, 0);
ColorPixel.transform.SetParent(this.transform);
ColorPixel.name = "Pixel (" + x.ToString() + "," + y.ToString() + ")";
}
}
}
All I would need is somehow each pixel returning its position so I can store this data and be able to spawn anything on top of this pixel.
I haven't had a chance to test this math yet so there may be some mistakes in it:
Every graphical image in Unity has a PPU, this and the object scale are going to be a huge factor. For argument sake I am going to clearly define these for 1 object.
Image dimensions : 128x128
PPU: 64
Scale: 1,1,1
Object Bounds: would
come from the renderer, which I am unsure if that bounds already
takes in account the scale(Most likely) however in the case you
cannot use that you can calculate the ObjectBoundsWidth or height
just by dividing the width or height of the texture by the PPU.
This should give you bounds of the texture in world space.
We are also going to make an assumption that we are only working on the X and Y axis and ignore the Z axis, if you want to use Z instead of Y then just make the necessary changes to be Z Scale and Z position and Z Bounds.
World position of a pixel located at 2,10. Per the documentation the pixel coordinates start at the lower left this means 0,0 is the bottom left corner, and 2,10 is 2 pixels left and 10 pixels up.
EDIT:
So I plugged all of this into a google sheet and determined the previous algorithm I provided was wrong here is the correct one in a pseudo code format
// This function takes in either the x or y, and the width or height of
// the bounds, then the x or y position of the object attached to.
// It also assumes the pivot is the center of the sprite.
float CalculateWorldPosOfPixelCoordinate(int coord, float boundsSize, float position, float scale)
{
float PixelInWorldSpace = 1.0f / PPU;
float startPos= position - (boundsSize* 0.5f * scale);
return startPos + (PixelInWorldSpace * coord) * scale;
}
This is using objectBounds we determined ourselves that is why we are multiply by scale.
this would give use a world position of: -0.97, -0.84
The algorithm i believe is the same for Y, just replace the coord with the Y position, and the bounds with the height instead of the width.
Like I said this could be wrong as I havent had a chance to test it, this also does not account for rotation either.
Alright, so today I decided to try to further optimize my collision detection code for my tile engine.
This is what I did:
Circle class checks if there are points within range. If there are, then check for collision between player and tile.
Code:
int tileWidth = 128;
int tileHeight = 128;
int[,] Layer3 = { 1, 1, 1, etc... };
int tileMapWidth = Layer3.GetLength(1);
int tileMapHeight = Layer3.GetLength(0);
Rectangle tile, tile2;
for (int x = 0; x < tileMapWidth; x++)
{
for (int y = 0; y < tileMapHeight; y++)
{
int wallIndex = Layer3[y, x];
if (wallIndex == 1) //Full-sized Tile Collision (128 x 128)
{
if (collisionCircle.Contains(new Vector2(x * tileWidth + (tileWidth / 2) + (int)Player.camera.Position.X,
y * tileHeight + (tileHeight / 2) + (int)Player.camera.Position.Y))) //+ tile / 2 is for centering the point
{
tile = new Rectangle(x * tileWidth + (int)Player.camera.Position.X, y * tileHeight + (int)Player.camera.Position.Y, tileWidth, tileHeight);
Collide(tile);
}
}
}
}
This would check throughout layer3 if there is a "1". If there is, assign rectangle and check for collision if point is inside collision radius.
Also, I checked this code(with a draw method), and I know it's working properly, at least the behavior.
I added in about 120,000(32 x 3888) tiles to try to make it lag, and before the code, it lagged a little bit. But after I added in the code, it lagged even more so.
I thought that since it would only check for collision between tiles(points) that are within the radius it wouldn't even remotely lag, but that's not the case...
Any help/ideas on how to optimize this would be great.
Thanks a lot,
Shyy
EDIT:
Cirlce.Contains() code:
public bool Contains(Vector2 Point)
{
return ((Point - position).Length() <= radius);
}
I used a circle because I've heard it's faster than using a rectangle.
Another possible optimization is instead of
return ((Point - position).Length() <= radius);
use
return ((Point - position).LengthSquared() <= radius * radius);
This is faster because Vector2.Length() has to perform a costly square root operation. Vector2.LengthSquared() does not have to perform that slow operation. The radius has to be multiplied by itself to account for the length from the vector being squared.
It sounds like you're trying to determine what tiles you don't need to use for collision with the player. Another optimization you could do is that if a tile at (X=5,Y=5) is above and to the left of the player, then you don't need to check a tile at (X=4,Y=4). Similarly if (X=5,Y=5) is below and to the right, (X=6,Y=6) is guaranteed to be too far as well. Try to determine when you've passed the player and no longer need to check collisions.
I suggest to loop only over visible tiles in screen to check collision using movement offset.
i will try something from my head..
for x as integer = 0 + offSetX to tilesInWidth + offSetX
for y as integer = 0 + offSetY to tilesInHeight + offSetY
if player.insideCircle(player.position, radius) '
object = layer(y,x);
if player.collideWith(object) then Collide()
end if
next
next
I am writing a SharpDx.Toolkit(XNA) Game. I searched in the internet how to program a collision detection, and I've found this:
static bool perPixel(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++)
{
if (dataA[(x - rectangleA.Left) + (y - rectangleA.Top) * rectangleA.Width] != new Color(0, 0, 0, 0) && dataB[(x - rectangleB.Left) + (y - rectangleB.Top) * rectangleB.Width] != new Color(0, 0, 0, 0))
{
// 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 found
return false;
}
This code works fine, but only on non-rotating sprites. So I tried to add a Matrix Transformation, but it doesn't work.
Does anyone has an idea, how to do this?
Thank you,
Christian
EDIT:The game is an 2D Game.
Per-pixel collision is overkill in most cases and i would not recommend it for anything else than programming practice.
You can almost always apply other methods to find collissions which introduces less bugs and require less performance.
You can either use a physics-engine or find another method.
Some methods i believe would work well in your case:
OBB-OBB collision
Circle-Circle collision
With these methods you just check if the bounds intersect instead of checking every single pixel against each-other.
I've been working on drawing an isometric map with C# / XNA Game Studio, and while I've gotten pretty far it doesn't look quite right and I was wondering if anybody can help.
Here's the code I have for drawing the map:
public void Draw(SpriteBatch theBatch, int drawX, int drawY)
{
if ((drawY % 2 == 0))
theBatch.Draw(tileTexture, new Rectangle((drawX * width), (drawY * length / 2), width, length), Color.White);
else
theBatch.Draw(tileTexture, new Rectangle(((drawX * width) + (width / 2)), (drawY * length / 2), width, length), Color.White);
}
The code within this method acts as if it were inside a nested for loop, drawing left to right, top to bottom. When the y-value is odd, the row is shifted over to fit, however it looks a bit off.
This is the produced output for an 8x5 map:
As you can see, it doesn't quite look right, and I'm not sure if its an issue with the math in my code, or if it has to do with the order everything is being drawn in. I'm very new to C# and working with sprites, so any help would be greatly appreciated.
Because it might be helpful, here is the other relevant parts of code which draw the map.
The entire Tile Class:
public class Tile
{
// Dimension variables
int height;
int width;
int length;
String type;
Texture2D tileTexture;
Vector2 coordinates;
///
/// Tile Constructor
///
public Tile(ContentManager theContent, String theType, Vector2 theCoordinates)
{
width = 68;
length = 46;
type = theType;
coordinates = theCoordinates;
// Sets the right texture to the texture ref
if (theType == "grass")
tileTexture = theContent.Load<Texture2D>(#"Tiles\iso_grass");
}
///
/// Draws the tile at the given location
///
public void Draw(SpriteBatch theBatch, int drawX, int drawY)
{
if ((drawY % 2 == 0))
theBatch.Draw(tileTexture, new Rectangle((drawX * width), (drawY * length / 2), width, length), Color.White);
else
theBatch.Draw(tileTexture, new Rectangle(((drawX * width) + (width / 2)), (drawY * length / 2), width, length), Color.White);
}
}
The TileRow class, which holds one row of tiles.
public class TileRow
{
public List<Tile> Row = new List<Tile>();
public int rowLength;
public TileRow(int theLength, int yIndex, ContentManager theContent)
{
rowLength = theLength;
Tile thisTile;
// Here the tiles are created and added to the row
for (int x = 0; x < rowLength; x++)
{
thisTile = new Tile(theContent, "grass", new Vector2(x, yIndex));
Row.Add(thisTile);
}
}
///
/// Draw -- invokes the draw method of each tile in the row
///
public void DrawRow(SpriteBatch theBatch, int currentY)
{
for (int x = 0; x < rowLength; x++)
{
Row[x].Draw(theBatch, currentY, x);
}
}
}
}
and the MapStruct class, which holds all the rows
public class MapStruct
{
public List<TileRow> allRows = new List<TileRow>();
int depth;
// Constructor
public MapStruct(ContentManager theContent, int theDepth, int rowLength)
{
depth = theDepth;
TileRow thisRow;
// Here we make a row of tiles for each level of depth
for (int y = 0; y < depth; y++)
{
thisRow = new TileRow(rowLength, depth, theContent);
allRows.Add(thisRow);
}
}
///
/// Draw - this method invokes the draw method in each tile row, which then draws each tile
///
public void DrawMap(SpriteBatch theBatch)
{
for (int y = 0; y < depth; y++)
{
allRows[y].DrawRow(theBatch, y);
}
}
}
Any help on how I could fix this issue, as well as advice on how I could improve my code would be greatly appreciated!
Looks like your loop adds a little to much to the Y on each row.
I found this variable in your Tile function.
length = 46;
I havent checked, but I believe "length" is the height of the tile? if so, try ajusting it a bit. Perhaps, you've forgotten to minus the height of the tile. So if the side of the tile is like 6 pixels, then the length for offset pr. row is only 40.
Also remember to plot from top and down, since the tiles nearest camera has to be plotted last, to make the depth illusion.
I beleive BerggreenDK is right, but your comment make it seem you misunderstood his answer. Your tiles need to be drawn at an Y offset that only includes the green surface areas "screen height". So, draw the full tile size, but offset the rows with length - 5 (Where 10 is the estimated offset and 5 is half that, as each row should be offset by only half the distance).
theBatch.Draw(tileTexture, new Rectangle((drawX * width), (drawY * (length) / 2), width, length - 5), Color.White);
...if I got the parameters right.
Also, the rows need to be drawn from back to front, but they seem to be drawn from left to right? I don't know XNA, so can't show code to fix that...when I look closely some tiles in a row "further away" is over-drawn by a tile "closer". I don't know XNA and don't get how the drawing order is determined, so can't offer a fix for that...