I'm using a single sprite sheet image as the main texture for my breakout game. The image is this:
My code is a little confusing, since I'm creating two elements from the same Texture using a Point, to represent the element size and its position on the sheet, a Vector, to represent its position on the viewport and a Rectangle that represents the element itself.
Texture2D sheet;
Point paddleSize = new Point(112, 24);
Point paddleSheetPosition = new Point(0, 240);
Vector2 paddleViewportPosition;
Rectangle paddleRectangle;
Point ballSize = new Point(24, 24);
Point ballSheetPosition = new Point(160, 240);
Vector2 ballViewportPosition;
Rectangle ballRectangle;
Vector2 ballVelocity;
My initialization is a little confusing as well, but it works as expected:
paddleViewportPosition = new Vector2((GraphicsDevice.Viewport.Bounds.Width - paddleSize.X) / 2, GraphicsDevice.Viewport.Bounds.Height - (paddleSize.Y * 2));
paddleRectangle = new Rectangle(paddleSheetPosition.X, paddleSheetPosition.Y, paddleSize.X, paddleSize.Y);
Random random = new Random();
ballViewportPosition = new Vector2(random.Next(GraphicsDevice.Viewport.Bounds.Width), random.Next(GraphicsDevice.Viewport.Bounds.Top, GraphicsDevice.Viewport.Bounds.Height / 2));
ballRectangle = new Rectangle(ballSheetPosition.X, ballSheetPosition.Y, ballSize.X, ballSize.Y);
ballVelocity = new Vector2(3f, 3f);
And the drawing:
spriteBatch.Draw(sheet, paddleViewportPosition, paddleRectangle, Color.White);
spriteBatch.Draw(sheet, ballViewportPosition, ballRectangle, Color.White);
The problem is I can't detect the collision properly, using this code:
if(ballRectangle.Intersects(paddleRectangle))
{
ballVelocity.Y = -ballVelocity.Y;
}
What am I doing wrong?
You're testing collision based on sourceRectangles for the sprite sheet texture. Those rectangles (paddleRectangle, ballRectangle) are defined in terms of texture coordinates - that is where those sprites are on the sheet. It makes no sense to test those rectangles for collision.
You need to use screen coordinates for collision, that is, you need different rectangles defined with screen positions:
Rectangle paddleViewportRectangle = new Rectangle(paddleViewportPosition.X,
paddleViewportPosition.Y,
paddleSize.X,
paddleSize.Y);
Rectangle ballViewportRectangle = new Rectangle(ballViewportPosition.X,
ballViewportPosition.Y,
ballSize.X,
ballSize.Y);
if(ballViewportRectangle.Intersects(paddleViewportRectangle))
{
ballVelocity.Y = -ballVelocity.Y;
}
Related
I have to create 2D map in unity using single image. So, i have one .png file with 5 different pieces out of which I have to create a map & i am not allowed to crop the image. So, how do create this map using only one image.
I am bit new to unity, i tried searching but didn't find exactly what i am looking for. Also tried, tilemap using Pallet but couldn't figure out how to extract only one portion of the image.
You can create various Sprites from the given texture on the fly in code.
You can define which part of a given Texture2D shall be used for the Sprite using Sprite.Create providing the rect in pixel coordinates of the given image. Remember however that in unity texture coordinates start bottom left.
Example use the given pixel coordinate snippet of a texture for the attached UI.Image component:
[RequireComponent(typeof(Image))]
public class Example : MonoBehaviour
{
// your texture e.g. from a public field via the inspector
public Texture2D texture;
// define which pixel coordinates to use for this sprite also via the inspector
public Rect pixelCoordinates;
private void Start()
{
var newSprite = Sprite.Create(texture, pixelCoordinates, Vector2.one / 2f);
GetComponent<Image>().sprite = newSprite;
}
// called everytime something is changed in the Inspector
private void OnValidate()
{
if (!texture)
{
pixelCoordinates = new Rect();
return;
}
// reset to valid rect values
pixelCoordinates.x = Mathf.Clamp(pixelCoordinates.x, 0, texture.width);
pixelCoordinates.y = Mathf.Clamp(pixelCoordinates.y, 0, texture.height);
pixelCoordinates.width = Mathf.Clamp(pixelCoordinates.width, 0, pixelCoordinates.x + texture.width);
pixelCoordinates.height = Mathf.Clamp(pixelCoordinates.height, 0, pixelCoordinates.y + texture.height);
}
}
Or you get make a kind of manager class for generating all needed sprites once e.g. in a list like
public class Example : MonoBehaviour
{
// your texture e.g. from a public field via the inspector
public Texture2D texture;
// define which pixel coordinates to use for this sprite also via the inspector
public List<Rect> pixelCoordinates = new List<Rect>();
// OUTPUT
public List<Sprite> resultSprites = new List<Sprite>();
private void Start()
{
foreach(var coordinates in pixelCoordinates)
{
var newSprite = Sprite.Create(texture, coordinates, Vector2.one / 2f);
resultSprites.Add(newSprite);
}
}
// called everytime something is changed in the Inspector
private void OnValidate()
{
if (!texture)
{
for(var i = 0; i < pixelCoordinates.Count; i++)
{
pixelCoordinates[i] = new Rect();
}
return;
}
for (var i = 0; i < pixelCoordinates.Count; i++)
{
// reset to valid rect values
var rect = pixelCoordinates[i];
rect.x = Mathf.Clamp(pixelCoordinates[i].x, 0, texture.width);
rect.y = Mathf.Clamp(pixelCoordinates[i].y, 0, texture.height);
rect.width = Mathf.Clamp(pixelCoordinates[i].width, 0, pixelCoordinates[i].x + texture.width);
rect.height = Mathf.Clamp(pixelCoordinates[i].height, 0, pixelCoordinates[i].y + texture.height);
pixelCoordinates[i] = rect;
}
}
}
Example:
I have 4 Image instances and configured them so the pixelCoordinates are:
imageBottomLeft: X=0, Y=0, W=100, H=100
imageTopLeft: X=0, Y=100, W=100, H=100
imageBottomRight: X=100, Y=0, W=100, H=100
imageTopRight: X=100, Y=100, W=100, H=100
The texture I used is 386 x 395 so I'm not using all of it here (just added the frames the Sprites are going to use)
so when hitting Play the following sprites are created:
I'm having trouble getting my head around the colour/material system of C# WPF projects, currently I am updating the colour of an entire system of points on each update of the model when I would instead like to just update the colour of a single point (as it is added).
AggregateSystem Class
public class AggregateSystem {
// stack to store each particle in aggregate
private readonly Stack<AggregateParticle> particle_stack;
private readonly GeometryModel3D particle_model;
// positions, indices and texture co-ordinates for particles
private readonly Point3DCollection particle_positions;
private readonly Int32Collection triangle_indices;
private readonly PointCollection text_coords;
// brush to apply to particle_model.Material
private RadialGradientBrush rad_brush;
// ellipse for rendering
private Ellipse ellipse;
private RenderTargetBitmap render_bitmap;
public AggregateSystem() {
particle_stack = new Stack<AggregateParticle>();
particle_model = new GeometryModel3D { Geometry = new MeshGeometry3D() };
ellipse = new Ellipse {
Width = 32.0,
Height = 32.0
};
rad_brush = new RadialGradientBrush();
// fill ellipse interior using rad_brush
ellipse.Fill = rad_brush;
ellipse.Measure(new Size(32,32));
ellipse.Arrange(new Rect(0,0,32,32));
render_bitmap = new RenderTargetBitmap(32,32,96,96,PixelFormats.Pbgra32));
ImageBrush img_brush = new ImageBrush(render_bitmap);
DiffuseMaterial diff_mat = new DiffuseMaterial(img_brush);
particle_model.Material = diff_mat;
particle_positions = new Point3DCollection();
triangle_indices = new Int32Collection();
tex_coords = new PointCollection();
}
public Model3D AggregateModel => particle_model;
public void Update() {
// get the most recently added particle
AggregateParticle p = particle_stack.Peek();
// compute position index for triangle index generation
int position_index = particle_stack.Count * 4;
// create points associated with particle for circle generation
Point3D p1 = new Point3D(p.position.X, p.position.Y, p.position.Z);
Point3D p2 = new Point3D(p.position.X, p.position.Y + p.size, p.position.Z);
Point3D p3 = new Point3D(p.position.X + p.size, p.position.Y + p.size, p.position.Z);
Point3D p4 = new Point3D(p.position.X + p.size, p.position.Y, p.position.Z);
// add points to particle positions collection
particle_positions.Add(p1);
particle_positions.Add(p2);
particle_positions.Add(p3);
particle_positions.Add(p4);
// create points for texture co-ords
Point t1 = new Point(0.0, 0.0);
Point t2 = new Point(0.0, 1.0);
Point t3 = new Point(1.0, 1.0);
Point t4 = new Point(1.0, 0.0);
// add texture co-ords points to texcoords collection
tex_coords.Add(t1);
tex_coords.Add(t2);
tex_coords.Add(t3);
tex_coords.Add(t4);
// add position indices to indices collection
triangle_indices.Add(position_index);
triangle_indices.Add(position_index + 2);
triangle_indices.Add(position_index + 1);
triangle_indices.Add(position_index);
triangle_indices.Add(position_index + 3);
triangle_indices.Add(position_index + 2);
// update colour of points - **NOTE: UPDATES ENTIRE POINT SYSTEM**
// -> want to just apply colour to single particles added
rad_brush.GradientStops.Add(new GradientStop(p.colour, 0.0));
render_bitmap.Render(ellipse);
// set particle_model Geometry model properties
((MeshGeometry3D)particle_model.Geometry).Positions = particle_positions;
((MeshGeometry3D)particle_model.Geometry).TriangleIndices = triangle_indices;
((MeshGeometry3D)particle_model.Geometry).TextureCoordinates = tex_coords;
}
public void SpawnParticle(Point3D _pos, Color _col, double _size) {
AggregateParticle agg_particle = new AggregateParticle {
position = _pos, colour = _col, size = _size;
}
// push most-recently-added particle to stack
particle_stack.Push(agg_particle);
}
}
where AggregateParticle is a POD class consisting of Point3D position, Color color and double size fields which are self-explanatory.
Is there any simple and efficient method to update the colour of the single particle as it is added in the Update method rather than the entire system of particles? Or will I need to create a List (or similar data structure) of DiffuseMaterial instances for each and every particle in the system and apply brushes for the necessary colour to each?
[The latter is something I want to avoid at all costs, partly due to the fact it would require large structural changes to my code, and I am certain that there is a better way to approach this than that - i.e. there MUST be some simple way to apply colour to a set of texture co-ordinates, surely?!.]
Further Details
AggregateModel is a single Model3D instance corresponding to the field particle_model which is added to a Model3DGroup of the MainWindow.
I should note that what I am trying to achieve, specifically, here is a "gradient" of colours for each particle in an aggregate structure where a particle has a Color in a "temperature-gradient" (computed elsewhere in the program) which is dependent upon order in which it was generated - i.e. particles have a colder colour if generated earlier and a warmer colour if generated later. This colour list is pre-computed and passed to each particle in the Update method as can be seen above.
One solution I attempted involved creating a separate AggregateComponent instance for each particle where each of these objects has an associated Model3D and thus a corresponding brush. Then an AggregateComponentManager class was created which contained the List of each AggregateComponent. This solution works, however it is horrendously slow as each component has to be updated every time a particle is added so memory usage explodes - is there a way to adapt this where I can cache already rendered AggregateComponents without having to call their Update method each time a particle is added?
Full source code (C# code in the DLAProject directory) can be found on GitHub: https://github.com/SJR276/DLAProject
We create WPF 3D models for smallish point clouds (+/- 100 k points) where each point is added as an octahedron (8 triangles) to a MeshGeometry3D.
To allow different colors for different points (we use this for selecting one or a subset of points) in such a point cloud, we assign texture coordinates from a small Bitmap.
At a high level we have some code like this:
BitmapSource bm = GetColorsBitmap(new List<Color> { BaseColor, SelectedColor });
ImageBrush ib = new ImageBrush(bm)
{
ViewportUnits = BrushMappingMode.Absolute,
Viewport = new Rect(0, 0, 1, 1) // Matches the pixels in the bitmap.
};
GeometryModel3D model = new GeometryModel3D { Material = new DiffuseMaterial(ib) };
and now the texture coordinates are just
new Point(0, 0);
new Point(1, 0);
... etc.
The colors Bitmap comes from:
// Creates a bitmap that has a single row containing single pixels with the given colors.
// At most 256 colors.
public static BitmapSource GetColorsBitmap(IList<Color> colors)
{
if (colors == null) throw new ArgumentNullException("colors");
if (colors.Count > 256) throw new ArgumentOutOfRangeException("colors", "More than 256 colors");
int size = colors.Count;
for (int j = colors.Count; j < 256; j++)
{
colors.Add(Colors.White);
}
var palette = new BitmapPalette(colors);
byte[] pixels = new byte[size];
for (int i = 0; i < size; i++)
{
pixels[i] = (byte)i;
}
var bm = BitmapSource.Create(size, 1, 96, 96, PixelFormats.Indexed8, palette, pixels, 1 * size);
bm.Freeze();
return bm;
}
We also go to some effort to cache and reuse the internal Geometry structure when updating the point cloud.
Finally we display this with the awesome Helix Toolkit.
I am currently making a level editor where the user imports tiles from a file, and it currently works, except for the fact that I want the pixels per unit for each imported sprite to change to 32
Here is my code:
//Get tiles from file
StreamReader reader = new StreamReader(Application.dataPath + "/../Maps/" + mapName + "/Tiles/tiles.txt");
string line = reader.ReadLine ();
while (!string.IsNullOrEmpty (line)) {
string[] param = line.Split (',');
foreach (TileTexture t in tileTextures) {
if (t.name == param [0]) {
Sprite sprite = Sprite.Create (t.texture, new Rect (0, 0, t.texture.width, t.texture.height), new Vector2 (0, 0));
sprite.pixelsPerUnit = 32;//THIS LINE DOESNT WORK, GIVES READONLY ERROR
Tile tile = new Tile (param[0], sprite, new Vector2(float.Parse(param[1]), float.Parse(param[2])));
tile.sprite.texture.filterMode = FilterMode.Point;
tiles.Add (tile);
}
}
line = reader.ReadLine ();
}
Looking at the function Sprite.Create() we see that the function signature is
public static Sprite Create(Texture2D texture,
Rect rect,
Vector2 pivot,
float pixelsPerUnit = 100.0f,
uint extrude = 0,
SpriteMeshType meshType = SpriteMeshType.Tight,
Vector4 border = Vector4.zero);
We see that we can pass the pixelsPerUnit as an optional parameter into the function. You can only do this here, and you cannot change it later, because, as you have found out, the field pixelsPerUnit is readonly field (meaning it cannot be changed). So, you just need to pass in your 32f here. Correct code would be
if (t.name == param [0]) {
Sprite sprite = Sprite.Create (t.texture, new Rect (0, 0, t.texture.width, t.texture.height), new Vector2 (0, 0), 32f);
Tile tile = new Tile (param[0], sprite, new Vector2(float.Parse(param[1]), float.Parse(param[2])));
tile.sprite.texture.filterMode = FilterMode.Point;
tiles.Add (tile);
}
I would like to now how to detect collision with lists of rectangles.
Rectanlge Player;
List<Rectangle> BlockHitBox = new List<Rectangle>();
I don't know the proper syntax for.
BlockHitBox.Intersect<>();
The code should detect if the rectangle hit boxes collide with the player hit box.
My goal is to make a room from small rectangles that the player cant pass through them so i need to detect more then one collision at a time ( for corners)
You can use the IntersectsWith method of the Rectangle object like so:
var commonSize = new Size(100, 100);
var player = new Rectangle(new Point(0,0), commonSize);
var blockHitBox = new List<Rectangle>
{
new Rectangle(new Point(0, 100), commonSize), // This one will not collide
new Rectangle(new Point(100, 0), commonSize), // This one will not collide
new Rectangle(new Point(0, 99), commonSize) // This one will collide
};
bool collision = blockHitBox.Any(item => item.IntersectsWith(player));
I am trying to create a function in one of my helper classes that will take a texture (left) and then generate an alpha mask (right)
Here's what I have so far:
public Texture2D CreateAlphaMask(Texture2D texture)
{
if (texture == null)
return null;
{
RenderTarget2D target = new RenderTarget2D(Device, texture.Width, texture.Height);
Device.SetRenderTarget(target);
Device.Clear(Color.Black);
using (SpriteBatch batch = new SpriteBatch(Device))
{
BlendState blendState = new BlendState()
{
AlphaBlendFunction = BlendFunction.Max,
AlphaSourceBlend = Blend.One,
AlphaDestinationBlend = Blend.One,
ColorBlendFunction = BlendFunction.Add,
ColorSourceBlend = Blend.InverseDestinationColor,
ColorDestinationBlend = Blend.Zero,
BlendFactor = Color.White,
ColorWriteChannels = ColorWriteChannels.All
};
batch.Begin(0, blendState);
batch.Draw(texture, Vector2.Zero, Color.White);
batch.End();
}
Device.SetRenderTarget(null);
return target;
}
}
What should be happening is that if alpha=0, then the pixel is black, and if alpha=1, then the pixel is white (and interpolated between these values if needed).
However, I can't seem to make it go "whiter" than the base image on the left. That is, if I set it to blend white, then at most it will go to the grey tones that I have, but never brighter. This isn't something I can create in advance, either, as it must be calculated during the game.