My grid is on an array and I iterate through each grid tile to check to see if it is null and if the one above it isn't it will drop the block to the one below.
The code worked perfectly fine when I did this instantly, but once I added a coroutine so I drop the blocks slowly it stopped working. I'm fairly sure it's because the loop is checking blocks before they are set properly but I'm not sure how to go about fixing that problem.
private void UpdateBoard()
{
// Need to figure out how to adjust my grid objects when a word has been destroyed.
for (int x = 0; x < grid.width; x++)
{
for (int y = 0; y < grid.height - 1; y++)
{
if (grid.tiles[x, y] == null && grid.tiles[x, y + 1] != null)
{
StartCoroutine(BlockFall( x, y + 1 ));
// grid.tiles[x, y + 1].transform.position = new Vector2(grid.tiles[x, y + 1].transform.position.x, grid.tiles[x, y + 1].transform.position.y - 1);
}
}
}
}
public IEnumerator BlockFall(int posX, int posY)
{
float startY = 1;
grid.tiles[posX, posY].pos.y = grid.tiles[posX, posY].pos.y - 1;
grid.tiles[posX, posY - 1] = grid.tiles[posX, posY];
while(startY > 0)
{
startY -= 0.25f;
grid.tiles[posX, posY].transform.position = new Vector2(grid.tiles[posX, posY].transform.position.x, grid.tiles[posX, posY].transform.position.y - 0.25f);
yield return new WaitForSeconds(0.1f);
}
grid.tiles[posX, posY] = null;
}
Those are the two functions that are important. It's a bit messy right now perhaps, but it worked.
What happens now, is that the first block will fall, but the ones above it won't. That was working when instant though.
Try running the whole loop in the Coroutine or using "yield return new WaitForEndOfFrame();" at the beginning of the Coroutine. Maybe this way you will find whats the issue.
Coroutines are not sync with unity update functions in case you have code in Update(), FixedUpdates()... and you may face problems because of that.
All right, I fixed the issue and I guess I didn't give enough information to solve the problem the way I ended up doing it but I'll add what I did.
I have an activeLetter variable which contains the current block's information. I moved my UpdateBoard() into a FixedUpdate function and then set a check to make sure it ignored the activeLetter position.
It works perfect now.
Related
My algorithm for calculating which block a player is looking at (voxel based world) is not working correctly. I have adapted it from this tutorial from 2D to 3D. At times it shows the correct block but other times it either returns nothing when it should or something in a completely different direction, why is this happening?
public (Block, Box?) GetLookAtBlock(Vector3 pos, Vector3 look) {
try {
look = look.Normalized() * 4;
float deltaX = Math.Abs(look.Normalized().X);
float deltaY = Math.Abs(look.Normalized().Y);
float deltaZ = Math.Abs(look.Normalized().Z);
int stepX, stepY, stepZ;
float distX, distY, distZ;
if (look.X < 0) {
distX = (pos.X - SandboxMath.RoundDown(pos.X)) * deltaX;
stepX = -1;
} else {
distX = (SandboxMath.RoundDown(pos.X) + 1 - pos.X) * deltaX;
stepX = 1;
}
if (look.Y < 0) {
distY = (pos.Y - SandboxMath.RoundDown(pos.Y)) * deltaY;
stepY = -1;
} else {
distY = (SandboxMath.RoundDown(pos.Y) + 1 - pos.Y) * deltaY;
stepY = 1;
}
if (look.Z < 0) {
distZ = (pos.Z - SandboxMath.RoundDown(pos.Z)) * deltaZ;
stepZ = -1;
} else {
distZ = (SandboxMath.RoundDown(pos.Z) + 1 - pos.Z) * deltaZ;
stepZ = 1;
}
int endX = SandboxMath.RoundDown(pos.X + look.X);
int endY = SandboxMath.RoundDown(pos.Y + look.Y);
int endZ = SandboxMath.RoundDown(pos.Z + look.Z);
int x = (int)pos.X;
int y = (int)pos.Y;
int z = (int)pos.Z;
Block start = GetBlock(x, y, z);
if (start != 0) {
return (start, new Box(new Vector3(x, y, z), new Vector3(x + 1, y + 1, z + 1)));
}
while (x != endX && y != endY && z != endZ) {
if (distX < distY) {
if (distX < distZ) {
distX += deltaX;
x += stepX;
} else {
distZ += deltaZ;
z += stepZ;
}
} else {
if (distY < distZ) {
distY += deltaY;
y += stepY;
} else {
distZ += deltaZ;
z += stepZ;
}
}
Block b = GetBlock(x, y, z);
if (b != 0) {
return (b, new Box(new Vector3(x, y, z), new Vector3(x + 1, y + 1, z + 1)));
}
}
return (0, null);
} catch (IndexOutOfRangeException) {
return (0, null);
}
}
your DDA have two issues I can see from the first look:
work only if Z is the major axis
so only if you are in camera space or have fixed camera looking in Z direction
your deltas are weird
why:
delta? = abs(1 / look.Normalized().?);
I would expect:
delta? = abs(look.Normalized().?);
I do not code in C# so I am not confident to repair your code however here is my C++ template for n-dimensional DDA so just compare and repair yours according it ...
template<const int n>class DDA
{
public:
int p0[n],p1[n],p[n];
int d[n],s[n],c[n],ix;
DDA(){};
DDA(DDA& a) { *this=a; }
~DDA(){};
DDA* operator = (const DDA *a) { *this=*a; return this; }
//DDA* operator = (const DDA &a) { ..copy... return this; }
void start()
{
int i;
for (ix=0,i=0;i<n;i++)
{
p[i]=p0[i]; s[i]= 0; d[i]=p1[i]-p0[i];
if (d[i]>0) s[i]=+1;
if (d[i]<0){ s[i]=-1; d[i]=-d[i]; }
if (d[ix]<d[i]) ix=i;
}
for (i=0;i<n;i++) c[i]=d[ix];
}
void start(double *fp0) // this will add the subpixel offset according to first point as double
{
int i; start();
for (i=0;i<n;i++)
{
if (s[i]<0) c[i]=double(double(d[ix])*( fp0[i]-floor(fp0[i])));
if (s[i]>0) c[i]=double(double(d[ix])*(1.0-fp0[i]+floor(fp0[i])));
}
}
bool update()
{
int i;
for (i=0;i<n;i++){ c[i]-=d[i]; if (c[i]<=0){ c[i]+=d[ix]; p[i]+=s[i]; }}
return (p[ix]!=p1[ix]+s[ix]);
}
};
start() init the variables and position for the DDA (from p0,p1 control points) and the update() is just single step of the DDA ... Resulting iterated point is in p
s is the step, d is delta, c is counter and ix is major axis index.
The usage is like this:
DDA<3> A; // 3D
A.p0[...]=...; // set start point
A.p1[...]=...; // set end point
for (A.start();A.update();)
{
A.p[...]; // here use the iterated point
}
DDA go through
well DDA is just interpolation (rasterization) of integer positions on some line between two endpoints (p0,p1). The line equation is like this:
p(t) = p0 + t*(p1-p0);
t = <0.0,1.0>
however that involves floating math and we want integer so we can rewrite to something like this:
dp = p1-p0
D = max (|dp.x|,|dp.y|,|dp.z|,...)
p.x(i) = p0.x + (dp.x*i)/D
p.y(i) = p0.y + (dp.y*i)/D
p.z(i) = p0.z + (dp.z*i)/D
...
i = { 0,1,...D }
where i,D is matching the major axis (the one with biggest change). If you look closer we are using *i/D Which is slow operation and we usually want speed so we can rewrite the therm (exploiting the fact that i goes from 0 to D in order) into something like this (for x axis only the others will be the same with different indexes ...):
p.x=p0.x; // start position
s.x=0; d.x=p1.x-p0.x; // step and abs delta
if (d.x>0) s.x=+1;
if (d.x<0){ s.x=-1; d.x=-d.x; }
D = max(d.x,d.y,d.z,...); // major axis abs delta
c.x=D; // counter for the iteration
for (i=0;i<D;i++)
{
c.x-=d.x; // update counter with axis abs delta
if (c.x<=0) // counter overflowed?
{
c.x+=D; // update counter with major axis abs delta
p.x+=s.x; // update axis by step
}
}
Now take a closer look at the counter c which we are adding the D and substracting d.x which is the i/D rewrited into D iterations. All the other axises are computed in the same manner you just need to add counter c step s and abs delta d for each axis ...
btw if it helps look at this:
volumetric GLSL back ray tracer
which is (I assume) what you are doing but implemented in GLSL shader (see the fragment code) however it does not use DDA instead it is adding unit direction vector to initial position until hit something or end of voxel space ...
btw its based on:
Ray Casting with different height size
just like the link of yours.
[Edit] wrong hits (guessed from your comments)
That has most likely nothing to do with DDA. Its more like edge case when you are standing directly on cell crossing or have wrongly truncated position or have wrongly z-sorted the hits. I remember I was having trouble with it. I ended up with very weird solution in GLSL see the Link above and see the fragment code. Look for
// YZ plane voxels hits
its directly after the non "DDA" ray casting code. It detects which plane of the voxel will be hit I think you should do something similar. It was weird as in 2D DOOM (also in links above) I had no such problems... but that was due to fact they were solved by different math used (suitable only for 2D).
The GLSL code just before the casting of ray changes a position a bit to avoid edge cases. pay attention to the floor and ceil but mine works on floats so it would need some tweaking for int math. Luckily I was repairing my other ray casting engine based on this:
Comanche Voxel space ray casting
And the solution is to offset the DDA by subpixel start position of the ray. I updated the DDA code above the new usage is:
DDA<3> A; // 3D
A.p0[...]=...; // set start point
A.p1[...]=...; // set end point
for (A.start(start_point_as_double[3]);A.update();)
{
A.p[...]; // here use the iterated point
}
Also on second taught make sure that in your DDA the c,d,s are integers if they are floating instead then it might cause the problems you are describing too...
Currently I try to write a 2D roulette wheel in C# using Monogame as framework.
I'm quiet experienced with Monogame but I never had to manage fast moving textures to stick together tho.
The idea is that 3 textures(black, red, green) are connected to each other in a line of 15 objects.
That's fine as long as I don't start to scroll all these objects to actually "make the wheel move".
After moving for a while, the textures are no more connected to each other. They get some space in between or start to overlap.
I made a short Video to clarify the problem: https://puu.sh/vwbyU/d396c6ad99.mp4 (It's about 10 MB, some Browsers may have download it before it shows up)
This is the most important code:
const int _roulleteOffset = 170; // X coordinate to start at
Sprite[] fieldList = new Sprite[15]; // Represents my wheel (Array of my roulette fields/textures)
Rectangle scrollArea; // Fixed Area for the complete fieldList
float scrollSpeed = 0.0f; // Scrollspeed of the wheel. 0 on start.
// First I call the Content.Load to fill fieldList.Texture then
// this is called to position the "wheel" objects
private void AdditionalInit()
{
for (int i = 0; i < fieldList.Length; i++)
{
fieldList[i].Position = new Vector2(_roulleteOffset + i * fieldList[i].Texture.Width, Statics.GAME_WINDOW_HEIGHT / 2 - fieldList[i].Texture.Height / 2);
}
scrollArea = new Rectangle((int)fieldList[0].Position.X, (int)fieldList[0].Position.Y, fieldList[0].Texture.Height * fieldList.Length + 30, fieldList[0].Texture.Height);
}
// This Method is called in Update() - And I guess the problem has to be fixed here
private void ScrollRoulette()
{
for (int i = 0; i < fieldList.Length; i++)
{
fieldList[i].position.X += scrollSpeed; // scrollSpeed is set with pressing + or - on keyboard
if (fieldList[i].Position.X >= scrollArea.Width + fieldList[i].Texture.Width)
{
// After leaving the "scrollArea" set it to the start of it (Leaving on right side and entering at the left side again)
fieldList[i].position.X = scrollArea.X - fieldList[i].Texture.Width;
}
}
}
// The part of my Draw Method. But I don't think that I made a mistake here
public override void Draw(GameTime gameTime)
{
Statics.SPRITEBATCH.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
for (int i = 0; i < fieldList.Length; i++)
{
Statics.SPRITEBATCH.Draw(fieldList[i].Texture, fieldList[i].Position, null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);
}
Statics.SPRITEBATCH.End();
}
Just watch the Video to understand what I'm talking about. Maybe you can help me with it. Even on slow speed the textures starts to disconnect or overlap over time.
With help from another german forum I resolved the problem! Thanks god. Took me a whole day these 1 line. I just had to adjust the positioning of the incoming tile. Not just set pos.x to the start.
private void ScrollRoulette()
{
for (int i = 0; i < fieldList.Length; i++)
{
fieldList[i].position.X += scrollSpeed;
if (fieldList[i].Position.X >= scrollArea.Width + (fieldList[i].Texture.Width * 2))
{
// This works now
fieldList[i].position.X = scrollArea.X + (fieldList[i].Position.X - (scrollArea.Width + scrollArea.X) );
}
}
}
I've spent days trying different techniques to get two X by Z arrays to talk to one-another and have decided to redo it all by making a 2 by X by Z array instead. 0,x,z contains a grid of cubes (prefab Cell) which will store values to be used later for NPC navigation, while 1,x,z contains a grid of tall cuboids (prefab ObstBlock) which are used to detect walls in the scene. If there's a wall, ObstBlock's int Obstructed will become 1. It is this int that I'm trying to reference in the main script.
All Cells and ObstBlocks are generated by an empty object (named "Grid") running GridScript. All Cells run CellScript and all ObstBlocks run ObstBlockScript. Here are my attempts in GridScript:
void DetectObstructions(){
for (int x=0; x<GridSize.x; x++) {
for (int z=0; z<GridSize.z; z++) {
Transform cell;
Transform block;
cell = Grid [0, x, z];
block = Grid [1, x, z];
if (block.GetComponent<ObstBlockScript> ().Obstructed == 1) {
cell.GetComponent<CellScript>().Weight = 1000;
}
else{
cell.GetComponent<CellScript>().Weight = 500;
}
cell.GetComponent<CellScript>().Obstructed = block.GetComponent<ObstBlockScript>().Obstructed;
}
}
}
I get no errors and this appears when I try to use the Debug tool: This request is not supported by the protocol version implemented by the debuggee.
As requested, the grid creation part of the script:
void CreateGrid(){
Grid = new Transform[2,(int)GridSize.x,(int)GridSize.z];
for (int x=0; x<GridSize.x; x++) {
for (int z=0; z<GridSize.z; z++) {
Transform newBlock;
newBlock = (Transform)Instantiate (ObstBlockPrefab, new Vector3 (x/4f, 1.0f, z/4f), Quaternion.identity);
//newBlock.name = string.Format("({0},1,{1})",x,z);
newBlock.parent = transform;
newBlock.GetComponent<ObstBlockScript>().Position = new Vector3(x/4f,1.0f,z/4f);
Transform newCell;
Grid[1,x,z] = newBlock;
newCell = (Transform)Instantiate (CellPrefab, new Vector3 (x/4f, -0.125f, z/4f), Quaternion.identity);
//newCell.name = string.Format("({0},0,{1})",x,z);
newCell.parent = transform;
newCell.GetComponent<CellScript>().Position = new Vector3(x/4f,-0.125f,z/4f);
//if(GameObject.Find("ObstGrid").GetComponent<ObstructionScript> ().ObstGrid[x,z]./*GameObject.*/Find ("ObstBlock").GetComponent<ObstBlockScript>().obstructed == 1){
// newCell.GetComponent<CellScript>().Weight = 83;
//}
Grid[0,x,z] = newCell;
}
}
}
Your code looks pretty straight forward to me. So unless something funky is happening inside CellScript or ObstBlockScript that resets the values, I'd have to guess DetectObstructions() get's executed too early, before any of the ObstBlockScript.Obstructed are set to "1".
This should be easyish to debug using break points or at least by adding Debug.Log to DetectObstructions() to see if any of the ObstBlockScript.Obstructed are 1 at that time the method is called.
I've been racking my mind on this for days. When updating entities, I check for collision, and if they collide according to the code below, their appropriate motion value is set to 0. Except for some reason, this code doesn't work for checking with collision to its right. If there is a 2 tall wall in front of the entity, it will disregard it. However, it seems to work right if I check below it, but then it will stop completely.
Collision code:
public static bool CollidesRight(Level level, Vector2 position, Vector2 motion, int width, int height)
{
Vector2 result = position + motion;
int x1 = (int)(result.X / (float)Tile.TILE_WIDTH);
int x2 = (int)((result.X + (float)width) / (float)Tile.TILE_WIDTH) + 1;
int y1 = (int)(result.Y / (float)Tile.TILE_HEIGHT);
int y2 = (int)((result.Y + (float)height) / (float)Tile.TILE_HEIGHT);
AABB resultBB = new AABB(result, width, height);
for (int i = x1; i < x2; i++)
{
for (int j = y1; j < y2; j++)
{
if (level.GetTileAt(i, j) != 0 && (Tile.TileList[level.GetTileAt(i, j)].IsSolid() || Tile.TileList[level.GetTileAt(i, j)].HasSolidTop()))
{
AABB tile = new AABB(new Vector2(i * 64f, j * 64f), 64, 64);
Console.WriteLine(tile);
return resultBB.Intersects(tile);
}
}
}
return false;
}
Here's my entity's updating code:
public virtual void Tick()
{
lastMotion = motion;
lastPosition = position;
if (Collision.CollidesBottom(level, position, motion, width, height))
{
onGround = true;
}
else if(!Collision.CollidesBottom(level, position, motion, width, height))
{
onGround = false;
}
if (onGround && !isJumping)
{
motion.Y = 0f;
fallDistance = 0;
}
else if(!onGround)
{
fallDistance++;
motion.Y += gravity * (fallDistance * 0.005f);
}
if (isJumping)
{
timeJumping++;
if (timeJumping > 32)
{
timeJumping = 0;
isJumping = false;
}
else
{
motion.Y = -2.25f;
}
}
position.Y += motion.Y;
if (motion.X != 0)
{
if (motion.X < 0)
{
motion.X += friction;
}
if (motion.X > 0)
{
motion.X -= friction;
}
}
if ((motion.X > 0 && Collision.CollidesRight(level, position, motion, width, height)) || (motion.X < 0 && Collision.CollidesLeft(level, position, motion, width, height)))
{
motion.X = 0f;
}
position.X += motion.X;
//MoveEntity(motion);
}
I have 3 options for you.
First, Edit - nevermind about this one, i misread your code I think you should add your X motion before doing the collision check, since the collision check uses it to work. If you don't want to add it to the actual player motion, at least pass the potential motion into the function. That alone might fix everything. But there's another problem.
Second, and I'm pretty sure it's this one, I suspect that your position.Y is frequently truncated to an Int when your OnGround bool is set. Which means it's minimum will be a whole unit and the next unit up will be a whole unit. And in your AABB code from pastebin:
if (bb.max.Y <= min.Y || bb.min.Y >= max.Y)
{
return false;
}
This code will return false even if the X-coordinate related code detects a collision. You should modify the logic in here so that the function returns true if there is a collision, instead of returning false on two independent checks.
Third, assuming your width is 1 unit and your tiles are also 1 unit wide, with a block at x=2 and position.x = 0.5, and a very small xMotion (or one that hasn't been added in yet, see answer 1):
(occupying some of block 0 and some of block 1, like so) [.][ ][X]
You want to stop when you hit x = 1, because then you'll be up against your obstruction. But:
x1 = (int)(0.5) = 0
x2 = (int)(0.5 + 1) + 1 = 2
so your loop:
for (int i = x1; i < x2; i++)
checks block 0 (which is empty, then checks block 1 (which is empty) and then stops (because i == x2) and never checks block 2. So the block you care about never gets its collision check. I think if you change it to
for (int i = x1; i <= x2; i++)
then everything will work better at detecting an oncoming collision. You should probably do something similar for your future CollidesTop function.
P.S. Try these answers one at a time, of course. :)
I have a physics simulation and it allows you to place area constraints, so that the bodies within will not exit that area. However if an atom goes past one of the "walls" of the area constraint it blows up the physics simulation. Why does it do this?
Update method:
if (!atom.IsStatic)
{
Vector2 force = Vector2.Zero;
bool x = false, y = false;
if (atom.Position.X - atom.Radius < _min.X)
{
force = new Vector2(-(_min.X - atom.Position.X), 0);
if (atom.Velocity.X < 0)
x = true;
}
if (atom.Position.X + atom.Radius > _max.X)
{
force = new Vector2(atom.Position.X - _max.X, 0);
if (atom.Velocity.X > 0)
x = true;
}
if (atom.Position.Y - atom.Radius < _min.Y)
{
force = new Vector2(0, -(_min.Y - atom.Position.Y));
if (atom.Velocity.Y < 0)
y = true;
}
if (atom.Position.Y + atom.Radius > _max.Y)
{
force = new Vector2(0, atom.Position.Y - _max.Y);
if (atom.Velocity.Y > 0)
y = true;
}
atom.ReverseVelocityDirection(x, y);
if (!atom.IsStatic)
{
atom.Position += force;
}
}
I see you're doing calculation with a constant time step T. When modeling collisions though on every step you should use time step equal to minimal time before any of atoms reach any obstacle.
Make time step variable, and atoms will never "tunnel" obstacles.
P.S. There are a lot of optimizations in collision detection, please read gamedev papers for information on those.
P.S. A bug?
force = new Vector2(-(_min.X - atom.Position.X), 0);
Force is created separately for X and Y reflection. What happens when the atom gets into a corner? Only second force will be applied.
P.P.S: Use epsilon
One more important note: if you use floating point, the error is accumulated, and you should use eps:
abs(atom.Position.Y + atom.Radium - _max.Y) < eps
where eps is a number much smaller than normal sizes in your task, e.g. 0.000001.
You seem to have solved the problem already, but I notice that "force" seems to be wrong. It moves the atom away from the boundary, even if it's on the wrong side. Suppose an atom has shot past _max.X:
if (atom.Position.X + atom.Radius > _max.X)
{
force = new Vector2(atom.Position.X - _max.X, 0);
...
}
Now "force" will be in the +x direction, and the atom's distance from the wall will double with every iteration. Boom!
Wouldn't you know, after about half an hour of mindless hacking at it, i thought of simply not applying the position correction. That fixed it like a charm. For any interested, here's the updated code:
if (!atom.IsStatic)
{
if (atom.Position.X - atom.Radius < _min.X && atom.Velocity.X < 0)
{
atom.ReverseVelocityDirection(true, false);
}
if (atom.Position.X + atom.Radius > _max.X && atom.Velocity.X > 0)
{
atom.ReverseVelocityDirection(true, false);
}
if (atom.Position.Y - atom.Radius < _min.Y && atom.Velocity.Y < 0)
{
atom.ReverseVelocityDirection(false, true);
}
if (atom.Position.Y + atom.Radius > _max.Y && atom.Velocity.Y > 0)
{
atom.ReverseVelocityDirection(false, true);
}
}