So I have been trying to find an answer for this since two days now, i'm still a student and I don't know if couldn't understand the other posts, or if my case is too specific to be solved with anything I found on the internet.
As I said in my title I have a 2D array of class called "piece", anytime I start the game it'll create a 2D array with random rows and columns (to explain quickly the goal is to connect all the pieces to win, you rotate the pieces to make the connections). It was going fine with the OnMouseClick(); function but since I need to use the arrows of my keyboard to archieve this, I'm encountering some troubles.
I've got a function which is generating the puzzle each time I start or reset it (So it's called in Start(); and the Update(); when I'm pressing a "clear" button) which is going like this :
public piece[,] pieces;
public GameObject Cursor;
void GeneratePuzzle()
{
pieces = new piece[width, height];
int[] auxValues = { 0, 0, 0, 0 };
for (int h = 0; h < height; h++)
{
for (int w = 0; w < width; w++)
{
//width restrictions
if (w == 0)
auxValues[3] = 0;
else
auxValues[3] = pieces[w - 1, h].values[1];
if (w == width - 1)
auxValues[1] = 0;
else
auxValues[1] = Random.Range(0, 2);
//heigth resctrictions
if (h == 0)
auxValues[2] = 0;
else
auxValues[2] = pieces[w, h - 1].values[0];
if (h == height - 1)
auxValues[0] = 0;
else
auxValues[0] = Random.Range(0, 2);
//tells piece type
int valueSum = auxValues[0] + auxValues[1] + auxValues[2] + auxValues[3];
if (valueSum == 2 && auxValues[0] != auxValues[2])
valueSum = 5;
go = (GameObject)Instantiate(piecesPrefabs[valueSum], new Vector3(origin.position.x + w, origin.position.y, origin.position.z + h), Quaternion.identity);
go.transform.parent = gameObject.transform;
while (go.GetComponent<piece>().values[0] != auxValues[0] ||
go.GetComponent<piece>().values[1] != auxValues[1] ||
go.GetComponent<piece>().values[2] != auxValues[2] ||
go.GetComponent<piece>().values[3] != auxValues[3])
{
go.GetComponent<piece>().RotatePiece();
}
pieces[w, h] = go.GetComponent<piece>();
instantiatedPieces.Add(go.gameObject);
}
}
}
So far I've been trying something like this to make my cursor move in this array :
public void Update()
{
for (int h = 0; h < height; h++)
{
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
h++;
if (h > height)
h = 0;
}
for (int w = 0; w < width; w++)
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
w++;
if (w > width)
w = 0;
}
// Cursor.transform.position = pieces[w, h].transform.position; ==> I suppose this is where it should be
}
}
}
But I end up being out of range or the cursor is so fast I can't see it going over each piece. So to be clear I'd like to be able to move my cursor over each pieces in this 2D array, by row and column (I suppose this is the way to go), after that I'll need to call the function from the piece class for the specific piece my cursor is over but I think I will be able to find this out.
English is not my native language so sorry and I'll try my best if you need any more informations to help me.
Thank you very much any help will be really appreciated !
Here is an example of my comment:
If you want to move a cursor over the elements of your array in update based off of keypress, there is no need for a for loop.
// Declared outside of your update makes these class variables that
// will live as long as this object exists.
int w = 0; // May be better to change this to cursorXPos
int h = 0; // May be better to name this cursorYPos
public void Update()
{
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
w--;
if (w < 0)
w = width -1;
}
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
w++;
if (w >= width)
w = 0;
}
if (Input.GetKeyDown(KeyCode.UpArrow))
{
h--;
if (h < 0)
h = height -1;
}
else if (Input.GetKeyDown(KeyCode.DownArrow))
{
h++;
if (h >= height)
h = 0;
}
Cursor.transform.position = pieces[w, h].transform.position;
}
I am not sure exactly what you're trying to do with your code but.
I don't get what you're using the following line in update for.
for (int h = 0; h < height; h++)
try removing that line.
try this:
public void Update()
{
for (int h = 0; h < height; h++)
{
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
h++;
if (h > height)
h = 0;
}
}
for (int w = 0; w < width; w++)
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
w++;
if (w > width)
w = 0;
}
// Cursor.transform.position = pieces[w, h].transform.position; ==> I suppose this is where it should be
}
}
or this:
public void Update()
{
for (int h = 0; h < width; h++)
{
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
h++;
if (h > height)
h = 0;
}
}
for (int w = 0; w < height; w++)
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
w++;
if (w > width)
w = 0;
}
// Cursor.transform.position = pieces[w, h].transform.position; ==> I suppose this is where it should be
}
}
with WindowsForms I would use the KeyDown event and increase/decrease h or w and set them to zero or max when reaching an edge.
Related
I am having trouble with Unity Physics.CheckSphere. I have added a mask "Wall" and assigned it to the object.
In my custom editor window I can select this mask and set values as a grid to check for this object. However, when I click the button I don't get any collisions. But when I change the object's mask to something like "TransparentFX" I get the results I expected.
What am I missing here?
Here is the code for the custom editor window:
private void OnGUI()
{
GUILayout.Label("Generate A*", EditorStyles.boldLabel);
showGridTools = EditorGUILayout.BeginFoldoutHeaderGroup(showGridTools, "Grid Tools");
if(showGridTools)
{
this.GridDimensions = EditorGUILayout.Vector3IntField("Grid Dimensions", GridDimensions);
this.ObstacleMask = EditorGUILayout.LayerField("Obstacle Mask", this.ObstacleMask);
if (GUILayout.Button("Generate Grid"))
CreateGrid();
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
And for checking the collisions:
private void CreateGrid()
{
for (int x = 0; x < this.GridDimensions.x; x++)
{
for (int z = 0; z < this.GridDimensions.z; z++)
{
for (int y = 0; y < this.GridDimensions.y; y++)
{
Vector3Int position = new Vector3Int(x, y, z);
bool obstacle = Physics.CheckSphere(position, 0.3f, this.ObstacleMask);
if(obstacle)
Debug.Log($"Position: {position}, Obstacle: {obstacle}");
}
}
}
}
----------- EDIT -----------
I just did this. Might be pretty slow, I don't know, but at least it
works for now.
private void CreateGrid()
{
this.Grid = new Node[this.GridDimensions.x, this.GridDimensions.y, this.GridDimensions.z];
for (int x = 0; x < this.GridDimensions.x; x++)
{
for (int z = 0; z < this.GridDimensions.z; z++)
{
for (int y = 0; y < this.GridDimensions.y; y++)
{
Vector3Int position = new Vector3Int(x, y, z);
var collisions = Physics.OverlapSphere(position, 0.3f);
bool obstacle = collisions.Where(c => c.gameObject.layer == this.ObstacleMask).Count() > 0;
if (obstacle)
Debug.Log($"Position: {position}, Obstacle: {obstacle}");
}
}
}
}
The problem
I am trying to procedurally generate dungeon rooms with random X, Y sizes inside of a radius (r). However, even after I validate that the starting grid (origin of the "room") is not in the same position as other origins after running the separation function there are rooms still building inside of each other.
Solutions I have tried
I tried using math to calculate an optimal radius that will be able to fit the average of all the room sizes * amount of rooms. However, the separation should hypothetically work with any radius (though I want to keep them relatively close in order to keep hallways short).
Code
All my code is based on one tile. This means that all calculations are using one tile, and will remain one tile until the very end, then I scale them up.
private void GenerateRooms(int amount)
{
// init sizes
Vector2[] room_sizes = new Vector2[amount];
for (int i = 0; i < amount; i++)
{
room_sizes[i] = new Vector2(Random.Range(minimum_room_height, maximum_room_height), Random.Range(minimum_room_width, maximum_room_width));
}
float biggest_room = calculations.CalculateBiggest(room_sizes);
Vector2[] room_points = new Vector2[amount];
Vector2[] used_points = new Vector2[amount];
float radius = calculations.CalculateAverage(room_sizes) * amount;
for (int i = 0; i < amount; i++)
{
do {
Vector2 test_point = new Vector2(Random.Range(-radius, radius), Random.Range(-radius, radius));
foreach (Vector2 point in used_points) {
if (test_point == point) {
continue;
} else {
room_points[i] = test_point;
used_points[i] = test_point;
break;
}
}
} while (Vector2.Distance(Vector2.zero, room_points[i]) < radius);
}
for (int i = 0; i < amount; i++)
{
//Vector2 origin = room_points[i];
Vector3 position = calculations.computeSeperate(room_points, room_points[i], biggest_room);
//position = new Vector3(position.x + origin.x, position.y + origin.y, 0);
Vector3Int location = tile_map.WorldToCell(position);
tile_map.SetTile(location, tile);
calculations.scaleUpRooms(position, room_sizes[i].x, room_sizes[i].y, tile_map, tile);
}
}
The above is code for calling all the functions and validating the points. Here are the important functions (calculation functions):
public Vector2 computeSeperate(Vector2[] point_array, Vector2 target_point, float minimum_distance)
{
int neighbor_count = 0;
for (int i = 0; i < point_array.Length; i++)
{
if (point_array[i] != target_point)
{
if (Vector2.Distance(target_point, point_array[i]) < minimum_distance * 2)
{
target_point.x += point_array[i].x - target_point.x;
target_point.y += point_array[i].y - target_point.y;
neighbor_count++;
}
}
}
if (neighbor_count == 0)
{
return target_point;
} else
{
target_point.x /= neighbor_count;
target_point.y /= neighbor_count;
target_point.x *= -1;
target_point.y *= -1;
target_point.Normalize();
return target_point;
}
}
public void scaleUpRooms(Vector2 base_point, float scale_x, float scale_y, Tilemap tile_map, Tile tile) // ex: 5x5
{
List<Vector2> Calculate(Vector2 size)
{
List<Vector2> results = new List<Vector2>();
for (int i = 0; i < size.y; i++)
for (int o = 0; o < size.x; o++)
results.Add(new Vector2(o, i) + (new Vector2(size.x % 2 != 0 ? .5f : 1, size.y % 2 != 0 ? .5f : 1) - (size / 2)));
string st = "";
for (int i = 0; i < results.Count; i++)
st += "\n" + results[i].ToString();
return results;
}
Vector2 desired_scale = new Vector2(scale_x, scale_y);
List<Vector2> Offsets = Calculate(desired_scale);
for (int i = 0; i < Offsets.Count; i++)
{
Vector3 position = base_point + Offsets[i];
Vector3Int location = tile_map.WorldToCell(position);
tile_map.SetTile(location, tile);
}
}
I would like to find an object on a bitmap using its HSV color. I know there already are some topics created about this issue, but they seem not to work on Android (I didn't find System.Drawing.Imaging on Android) or to be less efficient than the algorithm I already created:
public Rect FindObjectOnBitmap(Bitmap bm)
{
bm.GetPixels(pixels, 0, bm.Width, 0, 0, bm.Width, bm.Height);
// Get the matching pixels.
for(int x = 0; x<bm.Width; x++)
for (int y = 0; y < bm.Height; y++)
{
Color color = new Color(pixels[x + bm.Width * y]);
pixelsMatch[x,y] = (color.GetBrightness() > Settings.Tracker.blackFilter && color.GetSaturation() > Settings.Tracker.whiteFilter) && (Angle(Settings.Tracker.hue, color.GetHue()) < Settings.Tracker.sensitivity);
}
Rect objectRect = null;
int maxNbPixel = 0;
// for each matching pixel
for (int x = 0; x < bm.Width; x ++)
for (int y = 0; y < bm.Height; y ++)
if(pixelsMatch[x, y])
{
int nbPixel = 0;
Rect objectRectTemp = new Rect(x, y, x, y);
Queue<Pixel> zone = new Queue<Pixel>();
zone.Enqueue(new Pixel(x, y));
while(zone.Count != 0)
{
Pixel px = zone.Dequeue();
nbPixel++;
// Updates object position
if (px.x < objectRectTemp.Left)
objectRectTemp.Left = px.x;
if (px.x > objectRectTemp.Right)
objectRectTemp.Right = px.x;
if (px.y < objectRectTemp.Bottom)
objectRectTemp.Bottom = px.y;
if (px.y > objectRectTemp.Top)
objectRectTemp.Top = px.y;
// for each nearby pixel
for (int side = 0; side < 4; side++)
{
Pixel sidePx = px.Side(side);
if (sidePx.x >= 0 && sidePx.x < bm.Width && sidePx.y >= 0 && sidePx.y < bm.Height)
{
if (pixelsMatch[sidePx.x, sidePx.y])
{
zone.Enqueue(sidePx);
pixelsMatch[sidePx.x, sidePx.y] = false;
}
}
}
}
// Save the object if it is big enough.
if(nbPixel > maxNbPixel && nbPixel >= 4)
{
maxNbPixel = nbPixel;
objectRect = objectRectTemp;
}
}
return objectRect;
}
// Function comparing hues.
static float Angle(float deg1, float deg2)
{
if (Math.Abs(deg1 - deg2) > 180f)
return (360f - Math.Abs(deg1 - deg2));
else
return Math.Abs(deg1 - deg2);
}
However, I am reading a stream, and this algorithm is also not efficient enough since I get 0.1 fps on a 640x480 image... I get a good fps only if I create the bitmap with a smaller resolution than the real image (for example 40x30). That enables me to detect big or close objects with a bad precision (position and size).
How can I increase the efficiency enough, knowing I am on an ANDROID device...
Thanks in advance.
PS: Sorry for my poor English ^^
This is a function which determines if a point is within the boundary of the image and checks if it overlaps with other circle.
If it returns true then i check the threshold of the circle filled black and later save the point which is filled more than 90% into a list of points.
My program works in 2 steps
1) If it forms a circle with no overlaping.
2) If it is 90% filled.
I am facing a error that if i pass a image with only 1 circle it saves 1408 circles.I have no idea what i am doing wrong.
Below is the button click event
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 50; j++)
{
p.X = j;
p.Y = i;
if (isCircle(p))
{
if (checkThreshold(p) > 90)
{
pts.Insert(0, p);
}
}
}
}
Below given are the functions
private bool isCircle(Point p)
{
double count = 0;
Point curP = new Point();
//Point centre = new Point(24, 20);
int a = 0;
boundary.X = 50;
boundary.Y = 50;
for (int x = (p.X - radius); x <= (p.X - radius); x++)
{
for (int y = (p.Y - radius); y <= (p.Y - radius); y++)
{
if ((x < boundary.X) && (y < boundary.Y) && (x + radius < boundary.X) && (y + radius < boundary.Y))
{
curP.X = 0;
curP.Y = 0;
curP.X = x;
curP.Y = y; //stores new point to be sent in curP
while (a < pts.Count)
{
//point , centre, radius
if (checkOverlap(curP, pts[a], radius) == false) //send point to check if it overlaps or not
{
// MessageBox.Show("yellow");
count = 1;
break;
}
else
{
a++;
}
}
}
if (count == 1)
break;
}
if (count == 1)
break;
}
if (count == 1)
return true;
else return false;
}
Below given is the checkOverlap function
private bool checkOverlap(Point p, Point c, int radii)
{
Point listPoint;
listPoint = p;
//the following if condition checks if the point resides in the list or not
if ((((c.X - radii) < listPoint.X) && (listPoint.X > (c.X - radii))) && (((c.Y - radii) < listPoint.Y) && (listPoint.Y > (c.Y - radii))))
{
if ((((p.X - c.X) * (p.X - c.X)) - ((p.Y - c.Y) * (p.Y - c.Y))) < (radius * radius))
{
return false;
}
return true;
}
else
return true;
}
Not sure if this is THE issue, but your count variable should be an int due to the way you are testing for equality.
I cannot for the life of me work out how to get the block that is supposed to be drawn with every loop through the array of "Arxl" objects to animate across the grid.
Any suggestions would be really appreciated, not looking for someone to complete the code for me. just a fresh set of eyes.
public partial class Game : Form
{
//attributes
private Bitmap _grid;
private Arxl[,] _cartesianGrid;
private int _arxlAmount;
const int ARXL = 4;
public Game()
{
InitializeComponent();
_arxlAmount = (gridPictureBox.Height / ARXL);//in case height/arxl is not an even number?
_cartesianGrid = new Arxl[_arxlAmount, _arxlAmount];
_grid = new Bitmap(gridPictureBox.Width, gridPictureBox.Height);
int x;
int y;
for (x = 0; x < _arxlAmount; x++)
{
for (y = 0; y < _arxlAmount; y++)
{
_cartesianGrid[x, y] = new Arxl();
}
}
SetSeed(_cartesianGrid);
}
private void SetSeed(Arxl[,] cartesianGrid)
{
_cartesianGrid[1, 1].Active = true;
}
private void DrawArxl(Bitmap _grid, Arxl[,] cartesianGrid,int arxlAmount)
{
int x, y;
x=0;
y=0;
Graphics graphics = Graphics.FromImage(_grid);
graphics.Clear(Color.White);
for (x = 1; x < arxlAmount;x++ )
{
for (y = 1; y < arxlAmount; y++)
{
if (cartesianGrid[x, y].Active==true)
{
cartesianGrid[x, y].Area = new Rectangle(x * ARXL, y * ARXL, ARXL, ARXL);
graphics.FillRectangle(Brushes.Black, cartesianGrid[x, y].Area);
}
else if(cartesianGrid[x,y].Active==false)
{
Pen newPen=new Pen(Color.Black);
cartesianGrid[x, y].Area = new Rectangle(x * ARXL, y * ARXL, ARXL, ARXL);
graphics.DrawRectangle(newPen,cartesianGrid[x, y].Area);
newPen.Dispose();
}
}
}
}
private void timer_Tick(object sender, EventArgs e)
{
//GameOfLife(_cartesianGrid, _arxlAmount);
ScrollBlock(_cartesianGrid, _arxlAmount);
DrawArxl(_grid, _cartesianGrid, _arxlAmount);
gridPictureBox.Image = _grid;
}
private void ScrollBlock(Arxl[,] cartesianGrid, int arxlAmount)
{
int x = 0;
int y = 0;
for (x = 0; x < arxlAmount; x++)
{
for (y = 0; y < arxlAmount; y++)
{
if (_cartesianGrid[x, y].Active == true)
{
if (x>=0)
{
if (x == (arxlAmount-1))
{
_cartesianGrid[x, y].Active = false;
_cartesianGrid[1, y].Active = true;
}
else if(x<(arxlAmount-1))
{
_cartesianGrid[x, y].Active = false;
_cartesianGrid[x+1, y].Active = true;
}
}
}
}
}
}
According to a comment in your code, you want to program the life game. It will not work, if you change the cells in place, because you will have to compute the new state from the unchanged old state. Therefore, you will need to have two game boards, one with the current state and one with the new state. Instead of creating new board all the time, it is better to have two boards and to swap them. In addition, there is no point in storing the Rectangles in the board. Therefore, I declare the boards as Boolean matrix.
const int CellSize = 4;
private int _boardSize;
private bool[,] _activeBoard, _inactiveBoard;
Bitmap _grid;
The form constructor is changed like this
public Game()
{
InitializeComponent();
_boardSize = Math.Min(gridPictureBox.Width, gridPictureBox.Height) / CellSize;
_grid = new Bitmap(gridPictureBox.Width, gridPictureBox.Height);
_activeBoard = new bool[_boardSize, _boardSize];
_inactiveBoard = new bool[_boardSize, _boardSize];
SetSeed();
}
We initialize the game like this (as an example)
private void SetSeed()
{
_activeBoard[0, 0] = true;
_activeBoard[7, 4] = true;
DrawGrid();
}
The timer tick does this
ScrollBlock();
DrawGrid();
The logic in ScrollBlock is completely new. We look at the state on the _activeBoard and set the state of _inactiveBoard. Then we swap the two boards.
private void ScrollBlock()
{
for (int x = 0; x < _boardSize; x++) {
for (int y = 0; y < _boardSize; y++) {
if (_activeBoard[x, y]) {
_activeBoard[x, y] = false;
int newX = x + 1;
int newY = y;
if (newX == _boardSize) {
newX = 0;
newY = (newY + 1) % _boardSize;
}
_inactiveBoard[newX, newY] = true;
}
}
}
SwapBoards();
}
The boards are simply swapped like this
private void SwapBoards()
{
bool[,] tmp = _activeBoard;
_activeBoard = _inactiveBoard;
_inactiveBoard = tmp;
}
And finally DrawGrid draws the _activeBoard
private void DrawGrid()
{
Graphics graphics = Graphics.FromImage(_grid);
graphics.Clear(Color.White);
for (int x = 0; x < _boardSize; x++) {
for (int y = 0; y < _boardSize; y++) {
var rect = new Rectangle(x * CellSize, y * CellSize, CellSize, CellSize);
if (_activeBoard[x, y]) {
graphics.FillRectangle(Brushes.Black, rect);
} else {
graphics.DrawRectangle(Pens.Black, rect);
}
}
}
gridPictureBox.Image = _grid;
}
I've spotted your problem.
The problem is that you're updating a cell position (moving it to the right in this particular initial state), but the next iteration in the for loop finds the updated state from the previous iteration, so it updates the cell again, and again, and when the cycle stops, the cell was scrolled over to its initial cell position!, with no repainting in between.
I'm modifying your code to add an UpdateList that will turn on cells that need to be ON after the grid scan has finished to avoid updating the same "active dot" more than once. This should show a moving dot from left to right.
private void ScrollBlock(Arxl[,] cartesianGrid, int arxlAmount) {
int x = 0;
int y = 0;
List<Point> updateList = new List<Point>();
for( x = 0; x < arxlAmount; x++ ) {
for( y = 0; y < arxlAmount; y++ ) {
if( _cartesianGrid[x, y].Active == true ) {
if( x >= 0 ) {
if( x == (arxlAmount - 1) ) {
_cartesianGrid[x, y].Active = false;
//_cartesianGrid[1, y].Active = true;
updateList.Add(new Point(1, y));
} else if( x < (arxlAmount - 1) ) {
_cartesianGrid[x, y].Active = false;
//_cartesianGrid[x + 1, y].Active = true;
updateList.Add(new Point(x + 1, y));
}
}
}
}
}
foreach( var pt in updateList ) {
_cartesianGrid[pt.X, pt.Y].Active = true;
}
}
In your timer try calling gridPictureBox.Invalidate() after you assign the image to the picturebox. This will force the picturebox to redraw itself.