How should I program my mechanic design for a puzzle game with Unity 2020.3.14f? - c#

I'm coding a puzzle game where you can slide the tiles horizontally or vertically in a 3x3 gridmap.
If you select a tile, when you press up or down arrow key, the column this selected tile is in moves vertically upwards or downwards by 1 unit. Same applies to horizontal movements.
This will cause the blocks to go over the 3x3 boundary. This is why I have another rule that: when a block is over the 3x3 boundary, it is teleported to the vacant position, filling the grid map. So, for example: the horizontal order of blocks could be (1, 2, 3). After sliding this row of blocks to the left by 1 grid, the order becomes (3, 1, 2). Do it again? It is (2, 3, 1). Here's a screenshot of what the level looks like:
I thought it was a really simple logic to code but it has proven me wrong. It is actually fairly tricky.
I initially assigned each block an order number exactly identical to that of the keypad. So, bottom left block would be 1, then 2 on the right, and 3 on the bottom right... Whenever I pressed number key 1 on keypad and pressed up arrow, I hard-coded it and set the vertical order of blocks (1, 4, 7) to (7, 1, 4).
It doesn't work at all because if I don't reset the position back to normal, and start to change another given row or column, the layout of the map becomes messed up. This is because even if I changed the physical position of the blocks, their assigned order is not changed, which means that if the blocks that are going to be moved are not in their normal position, they can overlap onto other blocks.
Anyways, here is an example of the designed mechanic:
I. Normal position:
II. Slided row (1, 2, 3) right by 1 unit
III. Slided column (2, 5, 8) downwards by 1 unit
Can someone please give me some advice? It doesn't have to be in actual code. I just need some directions to go for... I'm out of ideas now.

As pointed out your images are not quite accurate ^^
Anyway, there might be more efficient and extendable ways but here is what I would do as a first iteration - plain straight forward:
Have a grid component which holds a 3x3 grid and handles all the shift operations
Additionally I will also route all movements through this Grid component in order to easily move tiles together - entire rows or columns - and keep things clean
I hope the comments are clear enough
public class Grid : MonoBehaviour
{
// To make things simple for the demo I simply have 9 "GridElement" objects and place them later based on their index
[SerializeField]
private GridElement[] elements = new GridElement[9];
// stores the current grid state
private readonly GridElement[,] _grid = new GridElement[3, 3];
private void Awake()
{
// go through the grid and assign initial elements to their positions and initialize them
for (var column = 0; column < 3; column++)
{
for (var row = 0; row < 3; row++)
{
_grid[column, row] = elements[row * 3 + column];
_grid[column, row].Initialize(this);
}
}
RefreshIndices();
}
// Shifts the given column one step up with wrap around
// => top element becomes new bottom
public void ShiftColumnUp(int column)
{
var temp = _grid[column, 2];
_grid[column, 2] = _grid[column, 1];
_grid[column, 1] = _grid[column, 0];
_grid[column, 0] = temp;
RefreshIndices();
}
// Shifts the given column one step down with wrap around
// => bottom element becomes new top
public void ShiftColumnDown(int column)
{
var temp = _grid[column, 0];
_grid[column, 0] = _grid[column, 1];
_grid[column, 1] = _grid[column, 2];
_grid[column, 2] = temp;
RefreshIndices();
}
// Shifts the given row one step right with wrap around
// => right element becomes new left
public void ShiftRowRight(int row)
{
var temp = _grid[2, row];
_grid[2, row] = _grid[1, row];
_grid[1, row] = _grid[0, row];
_grid[0, row] = temp;
RefreshIndices();
}
// Shifts the given row one step left with wrap around
// => left element becomes new right
public void ShiftRowLeft(int row)
{
var temp = _grid[0, row];
_grid[0, row] = _grid[1, row];
_grid[1, row] = _grid[2, row];
_grid[2, row] = temp;
RefreshIndices();
}
// Iterates through all grid elements and updates their current row and column indices
// and applies according positions
public void RefreshIndices()
{
for (var column = 0; column < 3; column++)
{
for (var row = 0; row < 3; row++)
{
_grid[column, row].UpdateIndices(row, column);
_grid[column, row].transform.position = new Vector3(column - 1, 0, row - 1);
}
}
}
// Called while dragging an element
// Moves the entire row according to given delta (+/- 1)
public void MoveRow(int targetRow, float delta)
{
for (var column = 0; column < 3; column++)
{
for (var row = 0; row < 3; row++)
{
_grid[column, row].transform.position = new Vector3(column - 1 + (row == targetRow ? delta : 0), 0, row - 1);
}
}
}
// Called while dragging an element
// Moves the entire column according to given delta (+/- 1)
public void MoveColumn(int targetColumn, float delta)
{
for (var column = 0; column < 3; column++)
{
for (var row = 0; row < 3; row++)
{
_grid[column, row].transform.position = new Vector3(column - 1, 0, row - 1 + (column == targetColumn ? delta : 0));
}
}
}
}
And then accordingly have a GridElement component on each grid element to handle the dragging and route the movement through the Grid
public class GridElement : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
{
// on indices within the grid so we can forward thm to method calls later
private int _currentRow;
private int _currentColumn;
// a mathematical XZ plane we will use for the dragging input
// you could as well just use physics raycasts
// but for now just wanted to keep it simple
private static Plane _dragPlane = new Plane(Vector3.up, 1f);
// reference to the grid to forward invoke methods
private Grid _grid;
// the world position where the current draggin was started
private Vector3 _startDragPoint;
// camera used to convert screenspace mouse position to ray
[SerializeField]
private Camera _camera;
public void Initialize(Grid grid)
{
// assign the camera
if(!_camera)_camera = Camera.main;
// store the grid reference to later forward the input calls
_grid = grid;
}
// plain set the indices to the new values
public void UpdateIndices(int currentRow, int currentColumn)
{
_currentRow = currentRow;
_currentColumn = currentColumn;
}
// called by the EventSystem when starting to drag this object
public void OnBeginDrag(PointerEventData eventData)
{
// get a ray for the current mouse position
var ray = _camera.ScreenPointToRay(eventData.position);
// shoot a raycast against the mathemtical XZ plane
// You could as well use Physics.Raycast and get the exact hit point on the collider etc
// but this should be close enough especially in top-down views
if (_dragPlane.Raycast(ray, out var distance))
{
// store the world space position of the cursor hit point
_startDragPoint = ray.GetPoint(distance);
}
}
// Called by the EventSystem while dragging this object
public void OnDrag(PointerEventData eventData)
{
var ray = _camera.ScreenPointToRay(eventData.position);
if (_dragPlane.Raycast(ray, out var distance))
{
// get the dragged delta against the start position
var currentDragPoint = ray.GetPoint(distance);
var delta = currentDragPoint - _startDragPoint;
// we either only drag vertically or horizontally
if (Mathf.Abs(delta.x) > Mathf.Abs(delta.z))
{
// clamp the delta between -1 and 1
delta.x = Mathf.Clamp(delta.x, -1f, 1f);
// and tell the grid to move this entire row
_grid.MoveRow(_currentRow, delta.x);
}
else
{
delta.z = Mathf.Clamp(delta.z, -1f, 1f);
// accordingly tell the grid to move this entire column
_grid.MoveColumn(_currentColumn,delta.z);
}
}
}
// Called by the EventSystem when stop dragging this object
public void OnEndDrag(PointerEventData eventData)
{
var ray = _camera.ScreenPointToRay(eventData.position);
if (_dragPlane.Raycast(ray, out var distance))
{
// as before get the final delta
var currentDragPoint = ray.GetPoint(distance);
var delta = currentDragPoint - _startDragPoint;
// Check against a threashold - if simply went with more then the half of one step
// and shift the grid into the according direction
if (delta.x > 0.5f)
{
_grid.ShiftRowRight(_currentRow);
}else if (delta.x < -0.5f)
{
_grid.ShiftRowLeft(_currentRow);
}
else if (delta.z > 0.5f)
{
_grid.ShiftColumnUp(_currentColumn);
}
else if(delta.z < -0.5f)
{
_grid.ShiftColumnDown(_currentColumn);
}
else
{
// if no direction matched at all just make sure to reset the positions
_grid.RefreshIndices();
}
}
}
}
Finally in order to make this setup work you will need
an EventSystem component anywhere in your scene
a PhysicsRaycaster component on your Camera
Little demo

Related

Ui button doesn't move correctly

I have a code for a crafting system that checks if the inventory has the ingredients needed to craft an item and adds a button to craft it. The problem is when I want to position my button it goes way off the canvas. I have seen some people saying that it has something to do with rect transform. I've been stuck with it for over an hour. Any help is appreciated.
I have tried
removing the setparent() function,
using anchoredPosition,
using localPosition
My code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Crafting : MonoBehaviour
{
public List<recipe> recipes = new List<recipe>();
public GameObject base_item, parent;
List<GameObject> items = new List<GameObject>();
public int y = 75;
public int x = -45;
public Inv inv;
private void Start()
{
inv = GetComponent<Inv>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
checkitems();
Debug.Log("y = " + y + " x = " + (x - 40));
}
}
public void checkitems()
{
for (int i = 0; i < recipes.Count; i++)
{
recipe r = recipes[i];
for (int x = 0; x < r.ingredients.Count; x++)
{
if (!inv.hasitem(r.ingredients[x])){
return;
}
}
showitem(r.result);
}
}
public void onClick(int _slot)
{
recipe r = recipes[_slot];
for (int i = 0; i < r.ingredients.Count; i++)
{
inv.removeitem(inv.getitem(r.ingredients[i]));
}
inv.additem(inv.getFirstAvailable(), r.result, r.stack);
}
public void showitem(string name)
{
GameObject obj = Instantiate(base_item);
if (items.Count != 0)
{
if (((items.Count) % 3) != 0)
{
Debug.Log("first thing");
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
x = x + 40;
Debug.Log("x + 40");
}
else if (((items.Count + 1) % 3) == 0)
{
Debug.Log("second thing");
x = -45;
Debug.Log("x + 40");
y = y + 40;
Debug.Log(" y + 40");
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
}
}else
{
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
x = x + 40;
Debug.Log("x + 40");
}
}
}
Blue circle where it spawns. Red circle where I want it to be
Seems you are confusing a bunch of terms for being the issue of your problem. Firstly I want to address the red X over your scroll bar. Whenever this occurs, it means that your RectTransform of this UI object has been dragged from its positive vertices to negative or vice versa, causing it to almost invert. I would correct this but it is not the reason your objects are not childing correctly.
Generally, with UI objects, I would never use LocalPosition, just AnchoredPosition. LocalPosition is a field from Transform which I believe RectTransform inherits from. As RectTransforms have a lot of modifications to their position from pivots, anchors, and anchored positions, the LocalPosition will most likely need to recalculate data to properly move the object, whereas AnchoredPosition has already done these calculations.
I believe the issue with your current code is how you are using SetParent. There is a second parameter of SetParent which governs whether the object keeps the same position based in world space after being childed. As you are not passing in a new bool for this parameter, it is defaulting to true. As you want your objects to be childed to the parent but not keep their world space positions, you would want to pass in false.
In your case, as it looks as if you want to set objects in a grid-like pattern childed to this ScrollRect, I would attach a GridLayoutGroup to the Content of your scroll and child the new objects to this object. You can set the max columns of this grid and spacing to give the same layout you are attempting to achieve in code.
To summarize, I would remove all the hand placement you are doing in code with LocalPosition and AnchorPosition and just attach a GridLayoutGroup. To fix the current positioning of your objects relative to the parent, change all lines of obj.transform.SetParent(parent.transform); to obj.transform.SetParent(parent.transform, false);. If you want to keep changing position locally in code instead of a layout element, use SetParent first, and use AnchoredPosition instead of LocalPosition as the SetParent with false passed in will override the position you set.

Dynamic levels brick breaker

I'm trying to create a brick breaker game with dynamically loading levels. I'd like to stay in one scene the entire game, and then dynamically load different levels by changing the brick positions (e.g. one level in where the bricks are in the shape of a circle, another level where the bricks are in the shape of a square, etc.).
I've imagined the screen as a grid of which each cell either has a brick or doesn't, and place them using for loops. My trouble is dynamically loading the data. Right now I have the grid data in terms of arrays. I've half attempted to upload 1 json file, but didn't succeed.
I'm not sure how to go about this problem. Do I make individual json files for each level? Can json files even have jagged arrays? How would I extract the data as an array? Is there a way of doing this with playerprefs?
Any help would be appreciated
public class BrickGrid : MonoBehaviour {
string filename = "data.json";
string jsonString;
string path;
public Transform brickPrefab;
[System.Serializable]
public class Bricks {
public string[] rows;
}
void Start() {
LoadGridData();
InitGrid();
}
void LoadGridData() {
path = Application.streamingAssetsPath + "/" + filename;
if (File.Exists(path)) {
jsonString = File.ReadAllText(path);
BrickPattern rows = JsonUtility.FromJson<BrickPattern>(jsonString);
}
}
void InitGrid() {
int[] row1 = { 0, 0, 1, 1 };
int[] row2 = { 1, 1, 0, 1 };
int[] row3 = { 0, 0, 0, 1 };
int[][] rows = new int[][] {row1, row2, row3};
Vector2 brickPosition = new Vector3(-2.25f, 4f, 0);
for (int i = 0; i < rows.Length; i++) {
int[] individualRow = rows[i];
for (int j = 0; j < individualRow.Length; j++){
if (individualRow[j] == 1){
// instantiate
Instantiate(brickPrefab, brickPosition, Quaternion.identity);
}
else if (individualRow[j] == 0) {
continue;
}
// inrease x position
brickPosition.x = brickPosition.x + 1.5f;
}
// increase y position and reset x position
brickPosition.x = -2.25f;
brickPosition.y = brickPosition.y - 1.5f;
}
}
}
yes you can create a prefab for every level, just design them under one GameObject and create a Prefab from that GameObject. Now when you change the level, instantiate the correct prefab.
I think that would be the easiest way.
You could also try many other ways.
Hardcode the locations for each bricks on each levels and use those information when you "dynamically" load a new level.
Store the position values somewhere else (file, database, etc.).
Hope this helps.

move along a grid in Unity

I can set four different movement directions for my player
Vector2.up => (0,1)
Vector2.down => (0,-1)
Vector2.left => (-1,0)
Vector2.right => (1,0)
and I have a two dimensional array that contains Cell objects
public class Cell
{
public Cell(GameObject cellObject, bool isObstacle)
{
CellObject = cellObject;
IsObstacle = isObstacle;
}
public GameObject CellObject { get; set; }
public bool IsObstacle { get; set; }
}
My array is initialized by the size of the map.
private const int MAP_SIZE = 10;
private Cell[,] mapCells = new Cell[MAP_SIZE, MAP_SIZE];
I fill this array by using two loops, this will give me 100 cells.
for (int x = 0; x < MAP_SIZE; x++)
{
for (int y = 0; y < MAP_SIZE; y++)
{
Vector3 newCellPosition = new Vector3(x, 0, y);
GameObject newCellObject = Instantiate(cell, newCellPosition, cell.transform.rotation);
bool isObstacle = false; // TEST
Cell newCell = new Cell(newCellObject, isObstacle);
mapCells[x, y] = newCell;
}
}
When moving the player I want to return the Cell he has to move to. The movementDirection parameter will set the row and the column to search for.
If there is an obstacle cell the player should just move to this obstacle and stop.
public Cell GetTargetCell(Vector2 movementDirection)
{
Cell targetCell = null;
// get the path
// get the closest obstacle
return targetCell;
}
Is there an elegant way to calculate the correct target cell by a 2D direction?
I think the most elegant way of doing it is by using two separate for loops and the ?: operator.
//returns cell
Cell GetTargetCell(Vector2 dir)
{
if(dir.y == 0) //if we're going horizontal
{
//HorizontalMovement
for(int i = 0; i < ((dir.x==1)?mapSize-PLAYER_X:PLAYER_X); i++)
{
if(mapCells[(int)dir.x*i,PLAYER_Y].IsObstacle()) //if we encounter an obstacle
return mapCells[(int)dir.x*i,PLAYER_Y]; //return cell that's an obstacle
}
//if we didn't encounter an obstacle
return mapCells[(dir.x == 1)?mapSize:0,PLAYER_Y]; //return the cell
}
else if(dir.x == 0)
{
//VerticalMovement
//copy paste the previous for loop and edit the parameters, I'm too lazy :P
}
else
{
//NoMovement
Debug.Log("Please enter a valid Direction");
return mapCells[0,0];
}
}
Replace the PLAYER_X and PLAYER_Y values, with the x, and y values of the cell the player is currently in. I didn't check if the code contains any errors, but I think it should work.

Map possible paths for amount of moves C#

I am re-creating the game of cluedo and I want to map the possible paths that the player can move after dice roll.
I have mapped the grid by drawing pictureboxes and naming them to their mapped location.
Here is my code so far for the possible paths:
int Roll;
private void RollDice()
{
ResetTiles();
JimRandom Random = new JimRandom();
//Roll DIce 1
int dice1 = Random.Next(1, 7);
//Roll DIce 2
int dice2 = Random.Next(1, 7);
Roll = dice1 + dice2;
//Set Dice images
pbDice1.BackgroundImage = Roller[dice1 - 1].Picture;
pbDice2.BackgroundImage = Roller[dice2 - 1].Picture;
btnRoll.Enabled = false;
Test(Roll);
//Show available moves
Control[] lCurrent = PnlBoard.Controls.Find("pnl" + CurrentPlauer, true);
Panel Current = null;
System.Drawing.Point CurrentLoc = new System.Drawing.Point(0, 0);
foreach (Control c in lCurrent)
{
Current = c as Panel;
CurrentLoc = new System.Drawing.Point(c.Location.X, c.Location.Y);
}
//Dynamic map
List<string> possiblities = new List<string>();
int currentRow = CurrentLoc.Y / tileWidth;
int currentCol = CurrentLoc.X / tileHeight;
//Find all possible start blocks
string down = String.Format("Col={0:00}-Row={1:00}", currentCol, currentRow + 1);
string up = String.Format("Col={0:00}-Row={1:00}", currentCol, currentRow - 1);
string left = String.Format("Col={0:00}-Row={1:00}", currentCol - 1, currentRow);
string right = String.Format("Col={0:00}-Row={1:00}", currentCol + 1, currentRow);
List<string> startBlocks = new List<string>();
//See if down is available
Control[] LPossible = PnlBoard.Controls.Find(down, true);
if (LPossible.Length > 0)
{
startBlocks.Add(down);
}
//See if Up is available
LPossible = PnlBoard.Controls.Find(up, true);
if (LPossible.Length > 0)
{
startBlocks.Add(up);
}
//See if left is available
LPossible = PnlBoard.Controls.Find(left, true);
if (LPossible.Length > 0)
{
startBlocks.Add(left);
}
//See if right is available
LPossible = PnlBoard.Controls.Find(right, true);
if (LPossible.Length > 0)
{
startBlocks.Add(right);
}
//possiblilities 1
foreach (string s in startBlocks)
{
Control[] lStarBlock = PnlBoard.Controls.Find(s, true);
PictureBox startBlock = lStarBlock[0] as PictureBox;
int sRow = startBlock.Location.Y / tileWidth;
int sCol = startBlock.Location.X / tileHeight;
//Rows
for (int row = sRow; row < sRow + Roll; row++)
{
//Columns
for (int col = sCol; col < sCol + Roll; col++)
{
possiblities.Add(String.Format("Col={0:00}-Row={1:00}", col, row));
}
}
}
//Show possible moves
foreach (string p in possiblities)
{
LPossible = PnlBoard.Controls.Find(p, true);
if (LPossible.Length > 0)
{
PictureBox active = LPossible[0] as PictureBox;
active.Image = Cluedo.Properties.Resources.TileActive;
System.Threading.Thread.Sleep(1);
Application.DoEvents();
}
//else
//{
// break;
//}
}
}
There's a lot of things I would do different here. This is more of a Code Review post, but there's a solution to your problem at the end, and perhaps the rest can help you to improve the overall state of your code.
Randomness
You're creating a new random generator instance for every method call:
JimRandom Random = new JimRandom();
This often results in the same values being generated if the method is called in rapid succession. Perhaps that's why you're using a cryptographic RNG instead of a PRNG? A PRNG should be sufficient for a game like this, as long as you reuse it.
Using the right types
You're determining the current player location with the following code:
//Show available moves
Control[] lCurrent = PnlBoard.Controls.Find("pnl" + CurrentPlauer, true);
Panel Current = null;
System.Drawing.Point CurrentLoc = new System.Drawing.Point(0, 0);
foreach (Control c in lCurrent)
{
Current = c as Panel;
CurrentLoc = new System.Drawing.Point(c.Location.X, c.Location.Y);
}
It looks like CurrentPlauer is a string. Creating a Player class that stores the name and current location of a player would make things much easier:
Point currentLocation = currentPlayer.Location;
Splitting game logic from UI code
You're checking for passable tiles by doing string lookups against controls:
string down = String.Format("Col={0:00}-Row={1:00}", currentCol, currentRow + 1);
// ...
Control[] LPossible = PnlBoard.Controls.Find(down, true);
if (LPossible.Length > 0)
{
startBlocks.Add(down);
}
Normally a 2D array is used for tile maps like these, possible encapsulated in a Tilemap or Map class. This makes working with tiles more natural, as you can work in tile coordinates directly instead of having to translate between UI and tile coordinates. It also breaks up the code more cleanly into a game-logic and a UI part (the code in your post is impossible to test without UI):
// TileMap class:
public bool IsPassable(int x, int y)
{
if (x < 0 || x >= Width || y < 0 || y >= Height)
return false;
return tiles[x][y] != Tile.Wall; // enum Tile { Wall, Ballroom, DiningRoom, Hall, ... }
}
// When called from your Board code:
if (map.IsPassable(currentLocation.X, currentLocation.Y + 1))
startBlocks.Add(new Point(currentLocation.X, currentLocation.Y + 1));
Reducing repetition
As for checking all direct neighboring tiles, there's no need to repeat the same code 4 times:
// Let's make a utility function:
public static IEnumerable<Point> GetNeighboringPositions(Point position)
{
yield return new Point(position.X - 1, position.Y);
yield return new Point(position.X, position.Y - 1);
yield return new Point(position.X + 1, position.Y);
yield return new Point(position.X, position.Y + 1);
}
// In the Board code:
foreach (Point neighboringPosition in GetNeighboringPositions(currentPosition))
{
if (map.IsPassable(neighboringPosition.X, neighboringPosition.Y))
startBlocks.Add(neighboringPosition);
}
Determining valid moves
Finally, we get to the code that determines which tiles the current player can move to:
//possiblilities 1
foreach (string s in startBlocks)
{
Control[] lStarBlock = PnlBoard.Controls.Find(s, true);
PictureBox startBlock = lStarBlock[0] as PictureBox;
int sRow = startBlock.Location.Y / tileWidth;
int sCol = startBlock.Location.X / tileHeight;
//Rows
for (int row = sRow; row < sRow + Roll; row++)
{
//Columns
for (int col = sCol; col < sCol + Roll; col++)
{
possiblities.Add(String.Format("Col={0:00}-Row={1:00}", col, row));
}
}
}
What this does is checking a rectangular area, using a starting position as its top-left corner. It's doing so for up to 4 neighboring positions, so the rectangles will partially overlap each other. That's just not going to work. If the map didn't have any obstacles, something like this, combined with a Manhattan distance check, could work (if you don't forget to look to the left and upwards too). Or better, some fancy looping that checks a diamond-shaped area.
However, you've got walls to deal with, so you'll need a different approach. The player's current position is at distance 0. Its direct neighbors are at distance 1. Their neighbors are at distance 2 - except those tiles that are at a lower distance (the tiles that have already been covered). Any neighbours of tiles at distance 2 are either at distance 3, or have already been covered. Of course, wall tiles must be skipped.
So you need to keep track of what tiles have already been covered and what neighboring tiles you still need to check, until you run out of movement points. Let's wrap that up into a reusable method:
public List<Point> GetReachableTiles(Point currentPosition, int maxDistance)
{
List<Point> coveredTiles = new List<Point> { currentPosition };
List<Point> boundaryTiles = new List<Point> { currentPosition };
for (int distance = 0; distance < maxDistance; distance++)
{
List<Point> nextBoundaryTiles = new List<Point>();
foreach (Point position in boundaryTiles)
{
foreach (Point pos in GetNeighboringPositions(position))
{
// You may also want to check against other player positions, if players can block each other:
if (!coveredTiles.Contains(pos) && !boundaryTiles.Contains(pos) && map.IsPassable(pos.X, pos.Y))
{
// We found a passable tile:
coveredTiles.Add(pos);
// And we want to check its neighbors in the next 'distance' iteration, too:
nextBoundaryTiles.Add(pos);
}
}
}
// The next 'distance' iteration should check the neighbors of the current boundary tiles:
boundaryTiles = nextBoundaryTiles;
}
return coveredTiles;
}

2D Per pixel collision detection not working

I would like to say sorry in advance for the amount of code I will post, but I can't seem to get my collision detection to work, the player and the objects pass through each other with no effect when I play test.
I receive 0 warnings or errors, but the Player playerBot object does not seem to interact with any of the level items I have imported from GLEED2D.
One thing I did was to store the values of my item's rectangles and color arrays in lists so they could be all easily iterated through, maybe this is the source of the problem.
If you can spot why my code is not working I will be hugely grateful. I have removed any code that is definitely not relevant, and I am running VS 2010 with GLEED2D 1.3 if that helps.
Thanks again.
// Item Class ImageItem Downcast
public class ImageItem : Item
{
public Texture2D Texture;
}
// Level
Level level;
// Ints
int iNumOfItems = 0;
int iTextureDataListNum = 0;
int iRectangleListNum = 0;
// Lists
List<Color []> itemTextureDataList = new List<Color[]>();
List<Rectangle> itemRectangleList = new List<Rectangle>();
protected override void Initialize()
{
if (filename.Length > 0) level = Level.FromFile(filename, Content);
else level = Level.FromFile("level1.xml", Content);
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
iNumOfItems =+ 1;
}
}
// Creates Player Ship
playerBot = new Player(new Vector2(400f, 240f), new Vector2(0f, 0f));
base.Initialize();
}
protected override void LoadContent()
{
Texture2D pixel = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
pixel.SetData(new[] { Color.White });
spriteBatch = new SpriteBatch(GraphicsDevice);
// Player Bot
playerBot.LoadContent(Content, "Images/Player Bot Sprite Sheet", 40, 40, 4);
// Assigns level textures color data to array
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
ImageItem imageItem = item as ImageItem;
if (imageItem != null)
{
Texture2D texture = imageItem.Texture;
itemTextureDataList[iTextureDataListNum] = new Color[imageItem.Texture.Width * imageItem.Texture.Height];
imageItem.Texture.GetData(itemTextureDataList[iTextureDataListNum]);
iTextureDataListNum++;
}
}
}
// Creates a rectangle for every level texture
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
ImageItem imageItem = item as ImageItem;
if (imageItem != null)
{
itemRectangleList[iRectangleListNum] = new Rectangle((int)imageItem.Position.X, (int)imageItem.Position.Y, imageItem.Texture.Width, imageItem.Texture.Height);
iRectangleListNum++;
}
}
}
spriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void Update(GameTime gameTime)
{
// Player Update
playerBot.Update(gameTime);
((Sprite)playerBot).Update(gameTime);
// Check for player collisons with level
for (int i = 0; i < iNumOfItems - 1; i++)
{
if (IntersectPixels(playerBot.colRectangle, playerBot.textureDataArray, itemRectangleList[i], itemTextureDataList[i]) == true)
{
playerBot.StopMovement();
}
}
base.Update(gameTime);
}
// Level Collision Detection Method
static bool IntersectPixels(Rectangle rectangleA, Color[] dataA, Rectangle rectangleB, Color[] dataB)
{
// Find the bounds of the rectangle intersection
int top = Math.Max(rectangleA.Top, rectangleB.Top);
int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
int left = Math.Max(rectangleA.Left, rectangleB.Left);
int right = Math.Min(rectangleA.Right, rectangleB.Right);
// Check every point within the intersection bounds
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
// Get the color of both pixels at this point
Color colorA = dataA[(x - rectangleA.Left) + (y - rectangleA.Top) * rectangleA.Width];
Color colorB = dataB[(x - rectangleB.Left) + (y - rectangleB.Top) * rectangleB.Width];
// If both pixels are not completely transparent
if (colorA.A != 0 && colorB.A != 0)
{
// Then an intersection has been found
return true;
}
}
}
// No intersection fond
return false;
}
// Sprite Class
public void Update(GameTime gameTime)
{
textureDataArray = new Color[texture.Width * texture.Height];
texture.GetData(textureDataArray);
// Player Class
public void StopMovement()
{
velocity.X *= -1;
velocity.Y *= -1;
}
The first thing I'll say here is, you should aim to be more Object Oriented in your approach. Storing a list of textures and a list of rectangles alongside your list of Items is "bad" technique and going to eventually cause you some massive headaches when it comes to debugging.
So first of all, instead of having a list of Color[] and a list of Rectangle, add one Color[] and one Rectangle to your ImageItem class and work with those instead, or at least create a little class called "CollisionData" that has a Rectangle and a Color[] and store those in a single list.
Secondly, note that there is a Rectangle.Intersect(Rectangle A, Rectangle B) that gets your the rectangle of intersection. So you can tidy up your code a bit by using that.
Your color checking can be simplified to (ColorA.A * ColorB.A != 0) as either being zero will
cause the result to be zero.
Regarding not getting any errors, put a breakpoint at the start of the collision checking loop. Does the application break? If yes, what is the value of iNumItems? (you can hover over it to see the current value at point of breaking). If no, then that section of code isn't being reached. Put another breakpoint a bit further back and a bit further back until it gets hit, then figure out why the code isn't executing.

Categories