Gaps between objects in Unity 2d - c#

I'm making a game where world is made out of tiles and I have a problem with small spaces being between tiles.
Each tile is normal object with sprite renderer.
Tiles share one splited texture.
Image of the problem
I tried to lower 'pixels per unit' in texture proporties, but then tiles like water (which are semi-transparent) overlap.
Here's code for instantiating tiles:
foreach (Layer layer in level.layers)
{
foreach (SavedTile tile in layer.tiles)
{
GameObject newTile = Instantiate(prefabTile, transform);
newTile.transform.position = new Vector3(
newTile.transform.position.x,
newTile.transform.position.y,
newTile.transform.position.z
);
BoxCollider2D newTileCollider = newTile.GetComponent<BoxCollider2D>();
SpriteRenderer newTileRenderer = newTile.GetComponent<SpriteRenderer>();
newTile.transform.position = new Vector3(tile.position.x, tile.position.y);
newTile.transform.rotation = new Quaternion(0, tile.rotation, 0, 0);
if (tile.tile.hasGravity)
{
newTile.AddComponent<Rigidbody2D>();
}
newTileRenderer.sprite = tile.tile.states[tile.slectedState].Sprites[0];
newTileRenderer.color = new Color(1, 1, 1, tile.tile.states[tile.slectedState].opacity);
if (tile.tile.states[tile.slectedState].isColliding)
{
newTileCollider.size = tile.tile.states[tile.slectedState].collider.size;
newTileCollider.offset = tile.tile.states[tile.slectedState].collider.offset;
if (tile.tile.states[tile.slectedState].bouyancy > 0)
{
newTile.AddComponent<BuoyancyEffector2D>().density = tile.tile.states[tile.slectedState].bouyancy;
newTileCollider.usedByEffector = true;
newTileCollider.isTrigger = true;
}
} else
{
newTileCollider.enabled = false;
}
if (tile.tile.customScript != null)
{
Debug.Log(tile.tile.customScript.GetClass());
newTile.AddComponent(tile.tile.customScript.GetClass());
}
if (tile.tile.isOnTop)
{
newTileRenderer.sortingOrder = -10;
}
}
}

Related

Why does copying blendshapes change a bone transform?

I am trying to bake blendshapes in Unity:
store the original mesh bindposes and boneWeights
call Mesh.BakeMesh to bake the mesh's blendshapes into a static mesh
add blendshapes back to the new mesh
finally replace the SkinnedMeshRenderer's sharedMesh
It works except a single bone is seemingly randomly rotated incorrectly. I don't modify any of the transforms or change any bone settings.
Why is my tail bone rotated?
After some testing I found it definitely happens when I call AddBlendShapeFrame.
Code:
void Bake() {
Debug.Log("Baking...");
Mesh newMesh = new Mesh();
var originalMesh = skinnedMeshRenderer.sharedMesh;
var originalBlendShapeDetails = new List<BlendShapeDetails>();
for (var i = 0; i < originalMesh.blendShapeCount; i++) {
Vector3[] deltaVertices = new Vector3[originalMesh.vertexCount];
Vector3[] deltaNormals = new Vector3[originalMesh.vertexCount];
Vector3[] deltaTangents = new Vector3[originalMesh.vertexCount];
originalMesh.GetBlendShapeFrameVertices(i, 0, deltaVertices, deltaNormals, deltaTangents);
originalBlendShapeDetails.Add(new BlendShapeDetails() {
name = originalMesh.GetBlendShapeName(i),
weight = skinnedMeshRenderer.GetBlendShapeWeight(i),
deltaVertices = deltaVertices,
deltaNormals = deltaNormals,
deltaTangents = deltaTangents,
});
}
var originalBindposes = originalMesh.bindposes;
var originalBoneWeights = originalMesh.boneWeights;
Vector3[] baseVertices = new Vector3[originalMesh.vertexCount];
for (var i = 0; i < baseVertices.Length; i++) {
baseVertices[i] = new Vector3(0, 0, 0);
}
skinnedMeshRenderer.BakeMesh(newMesh);
newMesh.bindposes = originalBindposes;
newMesh.boneWeights = originalBoneWeights;
int idx = 0;
// add blendshapes
foreach (BlendShapeDetails blendShapeDetails in originalBlendShapeDetails) {
newMesh.AddBlendShapeFrame(blendShapeDetails.name, 100, blendShapeDetails.deltaVertices, null, null);
}
// set blendshapes to original weights
for (var i = 0; i < newMesh.blendShapeCount; i++) {
string name = newMesh.GetBlendShapeName(i);
var result = originalBlendShapeDetails.Find(item => item.name == name);
skinnedMeshRenderer.SetBlendShapeWeight(i, result.weight);
}
// store
skinnedMeshRenderer.sharedMesh = newMesh;
Debug.Log("Bake complete");
}

Can't combine mesh from mesh create on runtime

I have this code :
public void BrowseColliderToCreateMesh (PolygonCollider2D polygonColliderAdded){
//browse all path from collider
pathCount=polygonColliderAdded.pathCount;
CombineInstance[] combine = new CombineInstance[pathCount];
for (int i = 0; i < pathCount; i++)
{
Vector2[] path = polygonColliderAdded.GetPath(i);
Polygon2D polygon = Polygon2D.Contour(path);
Triangulation2D triangulation = new Triangulation2D(polygon, 22.5f);
// build a mesh from triangles in a Triangulation2D instance
singleMesh = triangulation.Build();
combine[i].mesh = singleMesh;
}
testDelaunay.GetComponent<MeshFilter>().mesh = new Mesh;
testDelaunay.GetComponent<MeshFilter>().mesh.CombineMeshes(combine);
}
1- I have a list of point from a polygonCollider2D, divide in 3 :
2- I loop through these path to generate mesh with Delaunay.
For 1 mesh it work well, but I can't find a way to combine it.
Example from unity use some other children gameobject that I don't have...
Does someone has a solution ?
I finally find something which is not optimize but which work :
private Mesh CombineMeshes(List<Mesh> meshes)
{
var combine = new CombineInstance[meshes.Count];
for (int i = 0; i < meshes.Count; i++)
{
combine[i].mesh = meshes[i];
combine[i].transform = transform.localToWorldMatrix;
}
var mesh = new Mesh();
mesh.CombineMeshes(combine);
return mesh;
}
public void BrowseColliderToCreateMesh (PolygonCollider2D polygonColliderAdded){
pathCount=polygonColliderAdded.pathCount;
for (int i = 0; i < pathCount; i++)
{
if(i==0){
Vector2[] path = polygonColliderAdded.GetPath(i);
Polygon2D polygon = Polygon2D.Contour(path);
Triangulation2D triangulation = new Triangulation2D(polygon, 22.5f);
// build a mesh from triangles in a Triangulation2D instance
singleMesh = triangulation.Build();
}else if (i==1){
Vector2[] path = polygonColliderAdded.GetPath(i);
Polygon2D polygon = Polygon2D.Contour(path);
Triangulation2D triangulation = new Triangulation2D(polygon, 22.5f);
// build a mesh from triangles in a Triangulation2D instance
newMesh = triangulation.Build();
combineMesh=CombineMeshes(new List<Mesh> { newMesh, singleMesh });
}else if(i>1){
Vector2[] path = polygonColliderAdded.GetPath(i);
Polygon2D polygon = Polygon2D.Contour(path);
Triangulation2D triangulation = new Triangulation2D(polygon, 22.5f);
newMesh = triangulation.Build();
combineMesh=CombineMeshes(new List<Mesh> { newMesh, combineMesh });
}
}
GetComponent<MeshFilter>().mesh = combineMesh;
}

Add ILPoints to ILPlotCube from user input (by mouse)

I am new to ILNumerics, and I have a problem with some simple activity. I want to add Points to the Plot on user mouse input. User clicked for example point 5,4 on the plot, and this point have to draw on this position.
I had some problems to transform location of a mouse to the plot coordinates, and I found the solution here, but it's not ideal for me. What I have done:
private void ilpGraph_Load(object sender, EventArgs e)
{
var scene = new ILScene();
var plotCube = scene.Add(
new ILPlotCube()
{
new PointsGroup()
});
plotCube.Limits.Set(new Vector3(0, 0, 0), new Vector3(10, 10, 0));
plotCube.MouseClick += plotCube_MouseClick;
ilpGraph.Scene = scene;
}
void plotCube_MouseClick(object sender, ILMouseEventArgs e)
{
var pos = new Vector3(0, 0, 0);
ILGroup plotcubeGroup = sender as ILGroup;
ILGroup group = plotcubeGroup.Children.Where(item => item.Tag == "PointsGroup").First() as ILGroup;
if (group != null)
{
// walk up to the next camera node
Matrix4 trans = group.Transform;
while (!(group is ILCamera) && group != null)
{
group = group.Parent;
// collect all nodes on the path up
trans = group.Transform * trans;
}
if (group != null && (group is ILCamera))
{
// convert args.LocationF to world coords
// The Z coord is not provided by the mouse! -> choose arbitrary value
pos = new Vector3(e.LocationF.X * 2 - 1, e.LocationF.Y * -2 + 1, 0);
// invert the matrix.
trans = Matrix4.Invert(trans);
// trans now converts from the world coord system (at the camera) to
// the local coord system in the 'target' group node (surface).
// In order to transform the mouse (viewport) position, we
// left multiply the transformation matrix.
pos = trans * pos;
}
}
// it's for testing now
var pg = (sender as ILPlotCube).Plots.Children[0];
var t = ((PointsGroup)pg).Points.Positions;
int dc = t.DataCount;
t.Update(dc, 1, new[] { pos.X, pos.Y, 0.0f });
e.Refresh = true;
}
public class PointsGroup : ILGroup
{
private ILPoints _points;
public PointsGroup()
: base((object)"PointsGroup", new Vector3?(), 0.0, new Vector3?(), new Vector3?(), new RenderTarget?())
{
}
internal PointsGroup(PointsGroup source) : base((ILGroup) source)
{
}
public ILPoints Points
{
get
{
if (_points == null)
return new ILPoints();
return _points;
}
set { _points = value; }
}
protected override ILNode CreateSynchedCopy(ILNode source)
{
return (ILNode)new PointsGroup();
}
public override ILNode Copy()
{
return (ILNode)new PointsGroup(this);
}
public override ILNode Synchronize(ILNode copy, ILSyncParams syncParams)
{
return base.Synchronize(copy, syncParams);
}
}
What is the problem:
If I don't define PointsGroup class, I can't localize mouse coordinates on plot (ILPoints is not an ILGroup and I don't know how to do it differently).
With the PointsGroup coordinates are calculated good, but the coordinates are not remembered and not shown on the plot. I know why it happens, but I really don't know how to fix this.
How to extend my PointsGroup class to get my values remembered and show on plot or how to modify coordinates calculation to work on ILPoints objects?
Can anyone help me? Please

My sprites does connect, but in the middle you can still ''fall through''

I got 2 sprites and a ball. I started a sprite at the point 0,400 and one on 250,400. Both sprites are the same and they are 250 long and 10high (in my WorkshopContent). I use a ball to bounce on the sprites which works. But when the ball is in the middle of the sprites (around 250,400) the ball falls through. I have no idea why.
My ball is 29,30.
protected override void Initialize()
{
ball1 = new StuiterBall();
ball1.texture = "voetbal";
ball1.Position = new Vector2(0, 150);
allSprites.Add(ball1);
Obstakel obstakel1 = new Obstakel();
obstakel1.Position = new Vector2(0, 400);
obstakel1.texture = "witterechthoek";
allSprites.Add(obstakel1);
allSpriteObstakels1.Add(obstakel1);
Obstakel obstakel3 = new Obstakel();
obstakel3.Position = new Vector2(250, 400);
obstakel3.texture = "witterechthoek";
allSprites.Add(obstakel3);
allSpriteObstakels1.Add(obstakel3);
}
private void checkCollisions()
{
Rectangle rectball1 = new Rectangle((int)ball1.Position.X, (int) ball1.Position.Y, 30, 29);
foreach (ISprite s in allSpriteObstakels1) {
Rectangle rectSprite = new Rectangle((int)s.Position.X, (int)s.Position.Y, 250, 10);
Rectangle overlap = Rectangle.Intersect(rectball1, rectSprite);
if (!overlap.IsEmpty)
{
s.CollisionWith(ball1);
ball1.CollisionWith(s);
}
}
}
Stuiterball.cs, collisionwith method:
public void CollisionWith(ISprite s)
{
Speed.Y = -Speed.Y;
}
The method you want to use is Rectangle.Intersects():
bool isCollision = rectball1.Intersects(rectSprite);
if (isCollision)
{
s.CollisionWith(ball1);
ball1.ColisionWith(s);
}

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