I am attempting to use an overlapSphere to determine whether a tile within a tile map contains a game object. Then, if the tile is not already occupied, a new game object will be spawned in that tile (if certain other conditions are met). I'm trying to prevent game objects from overlapping with one another. However, I am having trouble centering the spheres about the right location.
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Vector3 tilePos = tileMap.GetCellCenterWorld(new Vector3Int(x, y, 0));
if (terrainMap[x, y] == blocked)
{
bool spawn = detectOverlap(tilePos, obstCheckRad);
if (spawn)
{
GameObject obstacle = Instantiate(obstaclePrefab) as GameObject;
obstacle.transform.position = tilePos;
obstacle.transform.eulerAngles = new Vector3(0, 0, (Random.Range(0, 359)));
}
}
}
}
}
public bool detectOverlap (Vector3 center, float radius)
{
Collider[] collider = Physics.OverlapSphere(center, radius);
if (collider.Length == 0)
{
return true;
}
else
{
return false;
}
}
I am using "GetCellCenterWorld" to as the center of my overlapSpheres, however they always seem to originate in the bottom left corner of the map, causing this region of the map to be completely empty, and objects elswhere in the map to still overlap with one another. (see image)
My confusion is based around the fact that I am using the same vector3 (tilePos) to place down the game objects. The objects are all moved to the correct position, however, the overlapSpheres all stay at the bottom left.
Related
I am trying to implement K-means in Unity to cluster randomly spawned assets around a terrain. Once the K-means operation completes I instantiate the last centroid positions and instantiate a Capsule for all locations that belong to a cluster and parent the instantiated Capsules to the centroid position to see and understand the final clustering result. The problem I am getting is that once it's spawned all relevant locations, it begins to spawn more and more capsules which do not parent to any cluster, not sure why.
Also the centroids do not seem to select unique random positions within my list of data points; when inspecting the positions of the final centroids they all seem to be at the same place. If I set k = 4 the 4th centroid never seems to spawn. I am struggling to find out where I am going wrong trying to implement this and would appreciate any insight.
Script for K-means clustering + Spawning assets (done in same script)
K-means.cs
Calculating initial centroid position
Vector3 CentroidPos()
{
var random = new System.Random();
var pos = assetSpawnLocations[random.Next(assetSpawnLocations.Count)];
if(centroidsInUse.Contains(pos))
{
return CentroidPos();
} else
{
return pos;
}
}
// Using funtion in Clustering method
void Clustering()
{
Vector3 centroid1 = CentroidPos();
Vector3 centroid2 = CentroidPos();
Vector3 centroid3 = CentroidPos();
Vector3 centroid4 = CentroidPos();
//...
}
Recalculating centroid position after data points have been assigned to cluster
Vector3 RecalculateCentroid(List<Vector3> Data)
{
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
for (int i = 0; i < Data.Count; i++)
{
x += Data[i].x;
y += Data[i].y;
z += Data[i].z;
}
return new Vector3(((x) / Data.Count), ((y) / Data.Count), ((z) / Data.Count));
}
Spawning the last centroid after K iterations and relevant data point positions using Capsule
// Spawn the last cluster centre and all it's points.
void SpawnResult(List<Vector3> cluster, Vector3 lastCentroid)
{
if(cluster != null && lastCentroid != null && cluster.Count > 0)
{
var c = Instantiate(GameObject.CreatePrimitive(PrimitiveType.Cylinder), lastCentroid, Quaternion.identity);
c.name = "Centroid";
for (int i = 0; i < cluster.Count; i++)
{
var item = Instantiate(GameObject.CreatePrimitive(PrimitiveType.Capsule), cluster[i], Quaternion.identity);
item.transform.parent = c.transform;
}
}
}
I am using DrawGizmos to place cubes with a certain colour depending on the cluster they belong to however it only seems to display Yellow cubes which is for cluster3 (see full script for DrawGizmos function implementation) [Small section of the map shown to clearly see yellow cube but this is the same across the map without colour changing for other clusters]
I've looked through what seems like every question and resource there is for Triangle.NET trying to find an answer to how to insert a vertex into an existing triangulation. The closest I've gotten was in the discussion archives for Traingle.Net where someone asked a similar question (discussion id 632458) but unfortunately, the answer was not what I was looking for.
My goal here is to make a destructible wall in Unity where, when the player shoots the wall, it will create a hole in the wall (like in Rainbow Six Siege).
Here's what I did for my original implementation:
Create initial triangulation using the four corners of the wall.
When the player shoots, perform a raycast, if the raycast intersects with the wall then add the point of intersection to the polygon variable and re-triangulate the entire mesh using that variable.
Draw new triangulation on the wall as a texture to visualise what's happening.
Repeat.
As you can see, step 2 is the problem.
Because I re-triangulate the entire mesh every time the player hits the wall, the more times the player hits the wall the slower the triangulation gets as the number of vertices rises. This could be fine I guess, but I want destructible walls to play a major role in my game so this is not acceptable.
So, digging through the Triangle.Net source code, I find an internal method called InsertVertex. The summary for this method states:
Insert a vertex into a Delaunay triangulation, performing flips as necessary to maintain the Delaunay property.
This would mean I wouldn't have to re-triangulate every time the player shoots!
So I get to implementing this method, and...it doesn't work. I get an error like the one below:
NullReferenceException: Object reference not set to an instance of an object
TriangleNet.TriangleLocator.PreciseLocate (TriangleNet.Geometry.Point searchpoint, TriangleNet.Topology.Otri& searchtri, System.Boolean stopatsubsegment) (at Assets/Triangle.NET/TriangleLocator.cs:146)
I have been stuck on this problem for days and I cannot solve it for the life of me! If anyone who is knowledgeable enough with the Triangle.NET library would be willing to help me I would be so grateful! Along with that, if there is a better alternative to either the implementation or library I'm using (for my purpose which I outlined above) that would also be awesome!
Currently, how I've set up the scene is really simple, I just have a quad which I scaled up and added the script below to it as a component. I then linked that component to a shoot raycast script attached to the Main Camera:
How the scene is setup.
What it looks like in Play Mode.
The exact Triangle.Net repo I cloned is this one.
My code is posted below:
using UnityEngine;
using TriangleNet.Geometry;
using TriangleNet.Topology;
using TriangleNet.Meshing;
public class Delaunay : MonoBehaviour
{
[SerializeField]
private int randomPoints = 150;
[SerializeField]
private int width = 512;
[SerializeField]
private int height = 512;
private TriangleNet.Mesh mesh;
Polygon polygon = new Polygon();
Otri otri = default(Otri);
Osub osub = default(Osub);
ConstraintOptions constraintOptions = new ConstraintOptions() { ConformingDelaunay = true };
QualityOptions qualityOptions = new QualityOptions() { MinimumAngle = 25 };
void Start()
{
osub.seg = null;
Mesh objMesh = GetComponent<MeshFilter>().mesh;
// Add four corners of wall (quad in this case) to polygon.
//foreach (Vector3 vert in objMesh.vertices)
//{
// Vector2 temp = new Vector2();
// temp.x = map(vert.x, -0.5f, 0.5f, 0, 512);
// temp.y = map(vert.y, -0.5f, 0.5f, 0, 512);
// polygon.Add(new Vertex(temp.x, temp.y));
//}
// Generate random points and add to polygon.
for (int i = 0; i < randomPoints; i++)
{
polygon.Add(new Vertex(Random.Range(0.0f, width), Random.Range(0.0f, height)));
}
// Triangulate polygon.
delaunayTriangulation();
}
// When left click is pressed, a raycast is sent out. If that raycast hits the wall, updatePoints() is called and is passed in the location of the hit (hit.point).
public void updatePoints(Vector3 pos)
{
// Convert pos to local coords of wall.
pos = transform.InverseTransformPoint(pos);
Vertex newVert = new Vertex(pos.x, pos.y);
//// Give new vertex a unique id.
//if (mesh != null)
//{
// newVert.id = mesh.NumberOfInputPoints;
//}
// Insert new vertex into existing triangulation.
otri.tri = mesh.dummytri;
mesh.InsertVertex(newVert, ref otri, ref osub, false, false);
// Draw result as a texture onto the wall so to visualise what is happening.
draw();
}
private void delaunayTriangulation()
{
mesh = (TriangleNet.Mesh)polygon.Triangulate(constraintOptions, qualityOptions);
draw();
}
void draw()
{
Texture2D tx = new Texture2D(width, height);
// Draw triangulation.
if (mesh.Edges != null)
{
foreach (Edge edge in mesh.Edges)
{
Vertex v0 = mesh.vertices[edge.P0];
Vertex v1 = mesh.vertices[edge.P1];
DrawLine(new Vector2((float)v0.x, (float)v0.y), new Vector2((float)v1.x, (float)v1.y), tx, Color.black);
}
}
tx.Apply();
this.GetComponent<Renderer>().sharedMaterial.mainTexture = tx;
}
// Bresenham line algorithm
private void DrawLine(Vector2 p0, Vector2 p1, Texture2D tx, Color c, int offset = 0)
{
int x0 = (int)p0.x;
int y0 = (int)p0.y;
int x1 = (int)p1.x;
int y1 = (int)p1.y;
int dx = Mathf.Abs(x1 - x0);
int dy = Mathf.Abs(y1 - y0);
int sx = x0 < x1 ? 1 : -1;
int sy = y0 < y1 ? 1 : -1;
int err = dx - dy;
while (true)
{
tx.SetPixel(x0 + offset, y0 + offset, c);
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 > -dy)
{
err -= dy;
x0 += sx;
}
if (e2 < dx)
{
err += dx;
y0 += sy;
}
}
}
private float map(float from, float fromMin, float fromMax, float toMin, float toMax)
{
float fromAbs = from - fromMin;
float fromMaxAbs = fromMax - fromMin;
float normal = fromAbs / fromMaxAbs;
float toMaxAbs = toMax - toMin;
float toAbs = toMaxAbs * normal;
float to = toAbs + toMin;
return to;
}
}
Great news! I've managed to fix the issue. InsertVertex() doesn't actually add the new vertex to the list of vertices! So this means that when it tried to triangulate, it was trying to point to the new vertex but it couldn't (because that vertex wasn't in the list). So, to solve this, I just manually add my new vertex to the list of vertices in the mesh, before calling InsertVertex(). Note: When you do this, you also need to manually set the vertex's id. I set the id to the size of the list of vertices because I was adding all new vertices to the end of the list.
// When left click is pressed, a raycast is sent out. If that raycast hits the wall, updatePoints() is called and is passed in the location of the hit (hit.point).
public void updatePoints(Vector3 pos)
{
// Convert pos to local coords of wall. You don't need to do this, i do it because of my draw() method where i map everything out onto a texture and display it.
pos = transform.InverseTransformPoint(pos);
pos.x = map(pos.x, -0.5f, 0.5f, 0, 512);
pos.y = map(pos.y, -0.5f, 0.5f, 0, 512);
Vertex newVert = new Vertex(pos.x, pos.y);
// Manually add new vertex to list of vertices.
newVert.id = mesh.vertices.Count;
mesh.vertices.Add(newVert.id, newVert);
//Doing just the first line gave me a null pointer exception. Adding the two extra lines below it fixed it for me.
otri.tri = mesh.dummytri;
otri.orient = 0;
otri.Sym();
// Insert new vertex into existing triangulation.
mesh.InsertVertex(newVert, ref otri, ref osub, false, false);
// Draw result as a texture onto the wall so to visualise what is happening.
draw();
}
Hope this will help someone done the road!
I'm creating a basic simulator in Unity for my A-level Computer Science project. At the moment the user is able to draw a box (crate) object by selecting the associated tool and clicking and dragging to determine two opposite corners of the box, thus determining its dimensions.
The box consists of a single prefab which is instantiated and has its size changed accordingly. The code for it is as follows:
void Start () {
boxAnim = boxButton.GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
//sets the mouseDown and mouseHeld bools and the mouse position Vector3
mouseDown = Input.GetMouseButtonDown(0);
mouseHeld = Input.GetMouseButton(0);
mousePosition = Input.mousePosition;
//checks if the user has started to draw
if (mouseDown && !draw)
{
draw = true;
originalMousePosition = mousePosition;
}
//checking if the user has released the mouse
if (draw && !mouseHeld)
{
finalMousePosition = mousePosition;
draw = false;
if (boxAnim.GetBool("Pressed") == true) //if the box draw button is pressed
{
boxDraw(originalMousePosition, finalMousePosition); //draws crate
}
}
}
void boxDraw(Vector3 start, Vector3 end)
{
//asigns world coordinates for the start and end mouse positions
worldStart = Camera.main.ScreenToWorldPoint(start);
worldEnd = Camera.main.ScreenToWorldPoint(end);
if (worldStart.y >= -3.2f && worldEnd.y >= -3.2f)
{
//determines the size of box to be drawn
boxSize.x = Mathf.Abs(worldStart.x - worldEnd.x);
boxSize.y = Mathf.Abs(worldStart.y - worldEnd.y);
//crate sprite is 175px wide, 175/50 = 3.5 (50px per unit) so the scale factor must be the size, divided by 3.5
boxScaleFactor.x = boxSize.x / 3.5f;
boxScaleFactor.y = boxSize.y / 3.5f;
//initial scale of the box is 1 (this isn't necessary but makes reading program easier)
boxScale.x = 1 * boxScaleFactor.x;
boxScale.y = 1 * boxScaleFactor.y;
//creates a new crate under the name newBox and alters its size
GameObject newBox = Instantiate(box, normalCoords(start, end), box.transform.rotation) as GameObject;
newBox.transform.localScale = boxScale;
}
}
Vector3 normalCoords(Vector3 start, Vector3 end)
{
//takes start and end coordinates as position coordinates and returns a world coordinate coordinate for the box
if(end.x > start.x)
{
start.x = start.x + (Mathf.Abs(start.x - end.x) / 2f);
}
else
{
start.x = start.x - (Mathf.Abs(start.x - end.x) / 2f);
}
if(end.y > start.y)
{
start.y = start.y + (Mathf.Abs(start.y - end.y) / 2f);
}
else
{
start.y = start.y - (Mathf.Abs(start.y - end.y) / 2f);
}
start = Camera.main.ScreenToWorldPoint(new Vector3(start.x, start.y, 0f));
return start;
}
In a similar manner, I want to be able to have a 'ramp' object be able to be created, so that the user can click and drag to determine the base width, then click again to determine the angle of elevation/ height, (the ramp will always be a right angled triangle.) The problem lies in that I want to have the ramp as a sprite I have created, rather than just a basic block colour. A single sprite however would only have a single angle of elevation, and no transform would be able to alter this (as far as I'm aware.) Obviously I don't want to have to create a different sprite for each angle, so is there anything I can do?
The solution I was thinking was if there was some sort of feature whereby I could alter the nodes of a vector image in the code, but I'm pretty sure this doesn't exist.
EDIT: Just to clarify this is a 2D environment, the code includes Vector3s just because that’s what I’m used to
You mention Sprite which is a 2D object (well, its actually very much alike a Quad which counts as 3D) but you reference full 3D in other parts of your question and in your code, which I think was confusing people, because creating a texture for a sprite is a very different problem. I am assuming you mentioned Sprite by mistake and you actually want a 3D object (Unity is 3D internally most of the time anyways), it can only have one side if you want
You can create 3D shapes from code no problems, although you do need to get familiar with the Mesh class, and mastering creating triangles on the fly takes some practice
Here's a couple of good starting points
https://docs.unity3d.com/Manual/Example-CreatingaBillboardPlane.html
https://docs.unity3d.com/ScriptReference/Mesh.html
I have a solution to part of the problem using meshes and a polygon collider. I now have a function that will create a right angled triangle with a given width and height and a collider in the shape of that triangle:
using UnityEngine;
using System.Collections;
public class createMesh : MonoBehaviour {
public float width = 5f;
public float height = 5f;
public PolygonCollider2D polyCollider;
void Start()
{
polyCollider = GetComponent<PolygonCollider2D>();
}
// Update is called once per frame
void Update () {
TriangleMesh(width, height);
}
void TriangleMesh(float width, float height)
{
MeshFilter mf = GetComponent<MeshFilter>();
Mesh mesh = new Mesh();
mf.mesh = mesh;
//Verticies
Vector3[] verticies = new Vector3[3]
{
new Vector3(0,0,0), new Vector3(width, 0, 0), new Vector3(0,
height, 0)
};
//Triangles
int[] tri = new int[3];
tri[0] = 0;
tri[1] = 2;
tri[2] = 1;
//normals
Vector3[] normals = new Vector3[3];
normals[0] = -Vector3.forward;
normals[1] = -Vector3.forward;
normals[2] = -Vector3.forward;
//UVs
Vector2[] uv = new Vector2[3];
uv[0] = new Vector2(0, 0);
uv[0] = new Vector2(1, 0);
uv[0] = new Vector2(0, 1);
//initialise
mesh.vertices = verticies;
mesh.triangles = tri;
mesh.normals = normals;
mesh.uv = uv;
//setting up collider
polyCollider.pathCount = 1;
Vector2[] path = new Vector2[3]
{
new Vector2(0,0), new Vector2(0, height), new Vector2(width, 0)
};
polyCollider.SetPath(0, path);
}
}
I just need to put this function into code very similar to my code for drawing a box so that the user can specify width and height.
I am using a tile map where if a moving platform hits a tile it should go another direction. I am using a list for both the tilemap and platform, this helps keep things neat.
The image below shows the platform, it is constantly in motion and when it collides with one of the black circles it should change directions and head the opposite way it was going.
Unfortunately I am having a problem where I need to find the correct platform so it will go the other direction. I created the list in the main class and do not know how to call upon the list inside a another class besides the main.
Question:
How would I call upon a list that is holding multiple objects going in different directions, in different position, and is created in the main class, so that I can know if the platform is colliding with a tile?
Simply put, how do I make the platform collide with a tile?
Here is the process that the tile map must go through to be used and called:
To make the tile map two classes are used, Block class and Game1 class (the main class).
Inside the Block class the Texture, Position, and BlockState (which decides what thing it will do)
public Block(Texture2D Texture, Vector2 Position, int BlockState)
{
this.Texture = Texture;
this.Position = Position;
this.BlockState = BlockState;
}
Then the Block class will use a method that uses the Player class.
public Player BlockCollision(Player player)
{
Rectangle top = new Rectangle((int)Position.X + 5, (int)Position.Y - 10, Texture.Width - 10, 10);
Rectangle bottom = new Rectangle((int)Position.X + 5, (int)Position.Y + Texture.Height, Texture.Width - 10, 10);
Rectangle left = new Rectangle((int)Position.X - 10, (int)Position.Y + 5, 10, Texture.Height - 10);
Rectangle right = new Rectangle((int)Position.X + Texture.Width, (int)Position.Y + 5, 10, Texture.Height - 10);
if (BlockState == 1 || (BlockState == 2 && !player.goingUp))
{
if (top.Intersects(new Rectangle((int)player.Position.X, (int)player.Position.Y, player.Texture.Width, player.Texture.Height)))
{
if (player.Position.Y + player.Texture.Height > Position.Y && player.Position.Y + player.Texture.Height < Position.Y + Texture.Height / 2)
{
player.Position.Y = player.ground = Position.Y - player.Texture.Height;
player.Velocity.Y = 0;
player.isJumping = false;
player.Time = 0;
player.botCollision = true;
}
}
}
return player;
}
Then the Draw method.
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, new Rectangle((int)Position.X, (int)Position.Y, Texture.Width, Texture.Height), Color.White);
}
We then move over to the main class Game1.
I create the Lists for both Blocks and the Platforms.
List<Block> Blocks;
List<MovingPlatform> Platforms;
Using a char system I create the map.
List<char[,]> Levels = new List<char[,]>();
public int tileWidth, tileHeight;
Then in the Initialize method I call on Lists and create the map.
protected override void Initialize()
{
Blocks = new List<Block>();
Platforms = new List<MovingPlatform>();
char[,] Level1 = {{'.','.','#'},
{'.','.','#'},
{'.','.','#'}};
Levels.Add(Level1);
base.Initialize();
}
Then a void LoadLevel is called on.
void LoadLevel(int level)
{
Blocks.Clear();
Platforms.Clear();
player.Position = Vector2.Zero;
tileWidth = Levels[level].GetLength(1);
tileHeight = Levels[level].GetLength(0);
Texture2D blockSpriteA = Content.Load<Texture2D>("blockA2");
for (int x = 0; x < tileWidth; x++)
{
for (int y = 0; y < tileHeight; y++)
{
//Background
Blocks.Add(new Block(background, new Vector2(x * 50, y * 50), 0));
//Impassable Blocks
if (Levels[level][y, x] == '#')
{
Blocks.Add(new Block(blockSpriteA, new Vector2(x * 50, y * 50), 1));
}
//Vertical Moving Platform
if (Levels[level][y, x] == '=')
{
Platforms.Add(new MovingPlatform(platform, new Vector2(x * 50, y * 50), 3.0f, true));
}
Then In the Update I call on the lists.
foreach (Block b in Blocks)
{
player = b.BlockCollision(player);
}
foreach (MovingPlatform m in Platforms)
{
player = m.BlockCollision(player);
m.Update(gameTime);
}
Finally the Draw method calls on them.
foreach (Block b in Blocks)
{
b.Draw(spriteBatch);
}
foreach (MovingPlatform m in Platforms)
{
m.Draw(spriteBatch);
}
I think I understand your question, I am not sure, but I am going to hazard an answer anyway.
I think what you need to do is to move stuff out of the main Game class. You have a concept of a map and a bunch of stuff that belongs to a map (platforms, blocks) so it's a good idea to encapsulate this in its own class, eg:
class Map
{
public List<Block> Blocks;
public List<MovingPlatform> Platforms;
void LoadLevel(int level)
{
///
}
}
Now it makes the code cleaner, but also you now have a Map object you can pass around.
So for example to enable a MovingPlatform to have access to everything on a map, you just need to pass a reference to a map as a parameter to the MovingPlatform constructor which saves it away to a private field. Eg:
class MovingPlatform
{
Map _map;
public MovingPlatform(Map map, Vector2 position, ...)
{
_map = map;
}
public void Update(GameTime gameTime)
{
//move platform
//detect collision
//have access to all the blocks and other platforms on map
//_map.Blocks;
//_map.Platforms;
}
}
So in the map class LoadLevel() method you'd pass in 'this' as the first parameter of the MovingPlatform constructor:
class Map
{
void LoadLevel(int level)
{
//Vertical Moving Platform
if (Levels[level][y, x] == '=')
{
Platforms.Add(new MovingPlatform(this, ...));
}
}
}
You can add other map specific things (even a reference to the player?) to the Map class and the MovingPlatforms will automatically have access to them.
This is called Dependency Injection. Your MovingPlatform class has a dependency on the map and you are injecting that dependency in through the constructor.
Edit:
For collision you could add a Bounds property to Tile and MovingPlatform class. That makes it easier to get the current rectangle bounds of them. Also an IntersectsTile method in MovingPlatform that will return true if the platform intersects a specified tile.
class Tile
{
Rectangle Bounds
{
get
{
return new Rectangle((int)Position.X, (int)Position.Y, tileWidth, tileHeight);
}
}
}
class MovingPlatform
{
Rectangle Bounds
{
get
{
return new Rectangle((int)Position.X, (int)Position.Y, platformWidth, platformHeight);
}
}
//returns true if this platform intersects the specified tile
bool IntersectsTile(Tile tile)
{
return Bounds.Intersects(tile.Bounds);
}
}
Then in the MovingPlatfom class in the Update method where the platform is moved each frame, check if there is a collision after the movement. If there is a collision then back out the movement and reverse the platform direction, so next frame it will move the opposite way.
class MovingPlatform
{
void Update(Tile[] collidableTiles)
{
Position += direction;
//test collision
bool isCollision = CollisionTest(collidableTiles);
if (isCollision)
{
//undo the movement and change direction
Position -= direction;
direction = newDirection;
}
}
//returns true if this platform intersects with any of the specified tiles
bool CollisionTest(Tile[] tiles)
{
foreach (Tile tile in tiles)
if (IntersectsTile(tile))
return true;
return false;
}
}
For the above to work you need to pass in a list of collidable tiles on the map to the MovingPlatform update. Call each MovingPlatform in the map class, passing it the list of collidable tiles.
class Map
{
List<Tile> _collidableTiles;
void Update(GameTime gameTime)
{
foreach (MovingPlatform platform in MovingPlatforms)
{
platform.Update(_collidableTiles);
}
}
}
That's one way of doing it. A better way though would be instead of passing in collidable tiles, get the MovingPlatform to grab tiles around it and test those.
class MovingPlatform
{
void Update()
{
Position += direction;
//test collision
Tile[] tiles = _map.GetNearbyTiles(Position);
foreach (Tile tile in tiles)
if (tile.IsSolid)
if (IntersectsTile(tile))
{
//undo the movement and change direction
Position -= direction;
direction = newDirection;
break;
}
}
}
So that way all tiles have an IsSolid property and any that are set to true will cause moving platforms to collide with them.
class Tile
{
bool IsSolid;
}
I am having some problems with collision detection, 2 have 2 types of objects aside from the player it self. Tiles and what I Call MapObjects.
The tiles are all 16x16, where the MapObjects can be any since, but is my problem case are they as well 16x16.
When my player runs apon the mapobjects or tiles dose it get very jaggy. The player is unable to move right, and will get warped forward when moving left.
I have found the problem, and that is my collision detection will move the player left/right if colliding the object from the side, and up/down if collision from up/down.
Now imagen that my player is sating on 2 tiles, at 10,12 and 11,12 and the player is mostly standing on the 11,12 tile. The collision detection will first run on then 10,12 tile, it calculates the collision depth, and finds that is is a collision from the side, and therefore more the object to the right. After will it do collision detection with 11,12 here will it move the character up. So the player will not fall down, but are unable to move right. And when moving left will the same problem make the player warp forward.
This problem have been bugging me for a few days now, and I just can't find a solution!
Here is my code that dose the collision detection.
public void ApplyObjectCollision(IPhysicsObject obj, List<IComponent> mapObjects, TileMap map)
{
PhysicsVaraibales physicsVars = GetPhysicsVariables();
Rectangle bounds = ((IComponent)obj).GetBound();
int leftTile = (int)Math.Floor((float)bounds.Left / map.GetTileSize());
int rightTile = (int)Math.Ceiling(((float)bounds.Right / map.GetTileSize())) - 1;
int topTile = (int)Math.Floor((float)bounds.Top / map.GetTileSize());
int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / map.GetTileSize())) - 1;
// Reset flag to search for ground collision.
obj.IsOnGround = false;
// For each potentially colliding tile,
for (int y = topTile; y <= bottomTile; ++y)
{
for (int x = leftTile; x <= rightTile; ++x)
{
IComponent tile = map.Get(x, y);
if (tile != null)
{
bounds = HandelCollision(obj, tile, bounds, physicsVars);
}
}
}
// Handel collision for all Moving objects
foreach (IComponent mo in mapObjects)
{
if (mo == obj)
continue;
if (mo.GetBound().Intersects(((IComponent)obj).GetBound()))
{
bounds = HandelCollision(obj, mo, bounds, physicsVars);
}
}
}
private Rectangle HandelCollision(IPhysicsObject obj, IComponent objb, Rectangle bounds, PhysicsVaraibales physicsVars)
{
// If this tile is collidable,
SpriteCollision collision = ((IComponent)objb).GetCollisionType();
if (collision != SpriteCollision.Passable)
{
// Determine collision depth (with direction) and magnitude.
Rectangle tileBounds = ((IComponent)objb).GetBound();
Vector2 depth = bounds.GetIntersectionDepth(tileBounds);
if (depth != Vector2.Zero)
{
float absDepthX = Math.Abs(depth.X);
float absDepthY = Math.Abs(depth.Y);
// Resolve the collision along the shallow axis.
if (absDepthY <= absDepthX || collision == SpriteCollision.Platform)
{
// If we crossed the top of a tile, we are on the ground.
if (obj.PreviousBound.Bottom <= tileBounds.Top)
obj.IsOnGround = true;
// Ignore platforms, unless we are on the ground.
if (collision == SpriteCollision.Impassable || obj.IsOnGround)
{
// Resolve the collision along the Y axis.
((IComponent)obj).Position = new Vector2(((IComponent)obj).Position.X, ((IComponent)obj).Position.Y + depth.Y);
// If we hit something about us, remove all velosity upwards
if (depth.Y > 0 && obj.IsJumping)
{
obj.Velocity = new Vector2(obj.Velocity.X, 0);
obj.JumpTime = physicsVars.MaxJumpTime;
}
// Perform further collisions with the new bounds.
return ((IComponent)obj).GetBound();
}
}
else if (collision == SpriteCollision.Impassable) // Ignore platforms.
{
// Resolve the collision along the X axis.
((IComponent)obj).Position = new Vector2(((IComponent)obj).Position.X + depth.X, ((IComponent)obj).Position.Y);
// Perform further collisions with the new bounds.
return ((IComponent)obj).GetBound();
}
}
}
return bounds;
}