Dynamic levels brick breaker - c#

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.

Related

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

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

Tube mesh generation in unity

I've created a procedural mesh script while watching one of Freya Holmér's improvised live course vods, and re purposed the code to create a procedural tube mesh with subdivision and plenty of other niche features.
But, after looking over the code and the lesson, I still cannot for the life of me figure out why sometimes I will get an:
argument out of range exception
...and sometimes I won't depending on the level of subdivision; also, entire faces wont be generated by the script.
TL;DR Problems list:
Argument out of range exception (Depending on level of subdivision).
Entire sides/faces of the tube mesh will not be generated, even when no errors are provided.
These problems are stemming from line 154 in the UpdateTris() method.
Code
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using OpenNexus.ExtraGizmos;
#endif
using OpenNexus.BaseMath;
using OpenNexus.MeshFormat;
namespace OpenNexus.Procedurals
{
[RequireComponent(typeof(MeshFilter))]
public class TubeMesh : MonoBehaviour
{
private MeshFilter filter;
private Mesh mesh;
public List<Vector3> Points = new List<Vector3>()
{
new Vector3(0, 0, 0),
new Vector3(0, 1, 1),
new Vector3(0, 0, 2)
};
[Header("Bezier Data")]
public Precision BezierPrecision = Precision.Fast;
[Header("Tube Data")]
[Range(0.01f, 1f)]public float Radius = 0.01f;
[Range(3, 32)] public int Segments = 8;
[Range(3, 6)] public int Subdivisions = 3;
[Header("Mesh Data")]
public ProceduralMesh2D BaseProceduralMesh = new ProceduralMesh2D();
public List<Vector3> Vertices;
public List<int> Tris;
private void OnEnable()
{
if (!GetComponent<MeshRenderer>()) // Check for and add missing mesh renderer.
gameObject.AddComponent<MeshRenderer>();
if (filter == null) // Check for and assign mesh filter variable.
filter = GetComponent<MeshFilter>();
if (mesh == null) // Check for and instantiate a new mesh object.
mesh = new Mesh();
mesh.name = "TubeMesh"; // Set name to mesh object.
filter.sharedMesh = mesh; // Set MeshFilter's shared mesh variable to new mesh object.
}
private void Update()
{
/*
Data reset
------------------------------
*/
// Reset base mesh data.
BaseProceduralMesh.Vertices = new List<Vertex>();
BaseProceduralMesh.LineIndex = new List<int>();
// Reset mesh data.
Vertices = new List<Vector3>();
Tris = new List<int>();
/*
Data update
------------------------------
*/
// Update base mesh.
UpdateBaseMesh();
// Update mesh data.
UpdateVertices();
UpdateTris();
}
private void LateUpdate() => UpdateMesh();
private BezierPoint GetBezierPoint(int index)
{
float _t = index / (Segments - 1f);
BezierPoint _bp = BezierMath.QuadraticBezier(Points, BezierPrecision, _t);
return _bp;
}
private void UpdateBaseMesh()
{
// Generate base vertices.
for (int i = 0; i < Subdivisions; i++)
{
float _point = i / (float)Subdivisions;
float _radius = _point * Floats.TAU;
Vertex _vertex = new Vertex(VectorThrees.UnitVectorByAngle(_radius) * Radius);
BaseProceduralMesh.Vertices.Add(_vertex);
}
// Generate base LineIndexes.
for (int i = 0; i < BaseProceduralMesh.VertexCount; i++)
{
BaseProceduralMesh.LineIndex.Add(i);
}
BaseProceduralMesh.LineIndex.Add(0);
}
private void UpdateVertices()
{
for (int i = 0; i < Segments; i++)
{
BezierPoint _point = GetBezierPoint(i);
for (int j = 0; j < BaseProceduralMesh.VertexCount; j++)
{
Vertices.Add(_point.LocalToWorldPosition(BaseProceduralMesh.Vertices[j].Point));
}
}
}
private void UpdateTris()
{
for (int s = 0; s < Segments - 1; s++)
{
int _root = s * BaseProceduralMesh.VertexCount;
int _rootNext = (s + 1) * BaseProceduralMesh.VertexCount;
for (int i = 0; i < BaseProceduralMesh.EdgeCount; i+=2)
{
int _lineA = BaseProceduralMesh.LineIndex[i];
int _lineB = BaseProceduralMesh.LineIndex[i + 1];
int _currentA = _root + _lineA;
int _currentB = _root + _lineB;
int _nextA = _rootNext + _lineA;
int _nextB = _rootNext + _lineB;
Tris.Add(_currentA);
Tris.Add(_nextA);
Tris.Add(_nextB);
Tris.Add(_currentA);
Tris.Add(_nextB);
Tris.Add(_currentB);
}
}
}
private void UpdateMesh()
{
mesh.Clear();
mesh.SetVertices(Vertices);
mesh.SetTriangles(Tris, 0);
mesh.RecalculateNormals();
}
#if UNITY_EDITOR
private void OnDrawGizmos()
{
// Draw psudo mesh with gizmos.
/*
Draw segment/edge loops
-------------------------------------
*/
for (int i = 0; i < Segments; i++) // Debug each segment, and what their 2D mesh segment should look like.
{
BezierPoint _point = GetBezierPoint(i);
WireGizmos.DrawWireCircle(_point.Position, _point.Rotation, Radius, Subdivisions);
}
Gizmos.color = Color.red;
for (int i = 0; i < Vertices.Count; i++) // Debug each vertex.
{
Gizmos.DrawSphere(Vertices[i], 0.01f);
Handles.Label(Vertices[i], "\n\nVertex: " + i + "\n\nVertex position:\n" + Vertices[i].ToString());
}
for (int i = 0; i < Tris.Count; i++)
{
Gizmos.DrawLine(Vertices[Tris[i]], Vertices[Tris[(i + 1) % Tris.Count]]);
}
}
#endif
}
}
I've already looked over my code compared to the code in the video, it's fundamentally the same, with the main differences being, how I'm creating the 2D mesh format for the script to work with, and the structure of the code.
After looking back at what they had done compared to mine, I just don't see how I'm running into this issue.
Things I've tried
Change the loop iteration i+=2 -to- i++ (This spits out the exception, but generates the first section of the tube before getting stuck between the second vertex in the next segment, and vert 0).
Every suggestion I try from this question, I will update the "Things I've tried..." list.
Please let me know if I need to clarify anything in this post, and if you wish to see the other classes/structs referenced in this script.
After a day of resting, and the a couple of hours of testing different values and questioning the code. I've found the problem.
The real issue
The nested for loop in the UpdateTris() method was going up by twos assuming I was making a hard edge mesh with pared vertices. This caused the loop to skip over an entire face of the mesh, and causing the
ArgumentOutOfRangeException
...when the Subdivision value was an even number.
My solution
I had to bring it back to the default single iteration (since I was trying to make a smooth lighting mesh), the second issue with the for loop was the iteration limit BaseProceduralMesh.LineCount needing to be subtracted by one since that was causing another
ArgumentOutOfRangeException

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.

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;
}

Create 3 dimensional grid two elements, one element in one row but in random columns

I've created a 3 dimensional grid. I have two separate objects filling the spaces of this grid. I want to have one of the objects in one row but on randomly selected columns.
Has anyone done this before or can anyone point me in the right direction?
I'm using Unity and C#. Thank you.
Vector3 towerSize = new Vector3(3, 3, 3);
//create grid tower
for (int x = 0; x < towerSize.x; x++)
{
for (int z = 0; z < towerSize.z; z++)
{
for (int y = 0; y < towerSize.y; y++)
{
//spawn tiles and space them
GameObject obj = (GameObject)Instantiate(tiles);
obj.transform.position = new Vector3(x * 1.2f, y * 1.2f, z * 1.2f);
//add them all to a List
allTiles.Add(obj);
obj.name = "tile " + allTiles.Count;
}
}
}
There is the code for the grid. I tried to have two objects in a singular List move to those tiles but the random column objects get in the same columns when I do that with this code:
for (int i = 0; i < allCubes.Count; i++)
{
allCubes[i].transform.position = Vector3.MoveTowards(
allCubes[i].transform.position,
allTiles[i].transform.position, 10 * Time.deltaTime);
}
Then thought put the two types of cubes in separate Lists themselves. Which ended up being even more messy. haha Does posting that code help?
I know this is an extremely old question of mine. It was a project that got canceled. I randomly came across it and out of curiosity I decided to try to complete this particular issue I was having such trouble doing back then. And I did.
public Vector3 towerSize = new Vector3(3, 3, 3);
public GameObject tiles;
public GameObject randomTile;
//public variables for debugging purposes.
//no real need to be seen in inspector in final. cleaner too if they're hidden
public int randomSelectedTile;
public List<GameObject> allTiles;
void Start()
{
//create grid tower
for (int x = 0; x < towerSize.x; x++)
{
for (int z = 0; z < towerSize.z; z++)
{
for (int y = 0; y < towerSize.y; y++)
{
//spawn cubes and space them
GameObject obj = (GameObject)Instantiate(tiles);
obj.transform.position = new Vector3(x * 1.2f, y * 1.2f, z * 1.2f);
//add them all to a List
allTiles.Add(obj);
obj.name = "tile " + allTiles.Count;
}
}
}
//select a random cube in the list
randomSelectedTile = Random.Range(0, allTiles.Count);
//get the cube object to delete
GameObject deleteObj = allTiles.ElementAt(randomSelectedTile);
//spawn the random cube at the position of the cube we will delete
GameObject rndObj = (GameObject)Instantiate(randomTile);
rndObj.transform.position = deleteObj.transform.position;
//remove the element at that location
allTiles.RemoveAt(randomSelectedTile);
//insert the random cube at that element's location
allTiles.Insert(randomSelectedTile, rndObj);
//destroy the unwanted cube
Destroy(deleteObj);
}
It's nice to see how you've improved over time. Just in case anyone else would benefit from the solution. Again, I apologize for bringing it back up.

Categories