- Home /
How to draw walls around rooms using the least number of objects for walls?
I have created a new project to test in before I implement it into my game. Essentially my end game will have a series of rooms. I want to generate all the walls around each room via code. I would really appreciate some help in working this problem out. The current code I have does the follow:
1. Randomly generate a floor tile from 1 to 3
2. Place an coloured object to indicate the floor tile's number
3. Place a along wall on the north, south, east and west sides of the base cube
4. Loop through each tile and work out if the one before (either in x for horizontal, or y for vertical) and place a wall if the tile before is different than the current one.
What I want to do to optimise this all is only use a single object for continuous sides. For example: In the current code, a wall may consist of 3 single wall items in a row one after the other. I want it to just make 1 wall instead to replace these 3 walls.
So basically optimising it this way:
Here is what my current setup looks like:
And here is the code I have for the above current setup:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Walls : MonoBehaviour {
int[,] tiles;
public GameObject wall;
public Transform walls;
public GameObject floor1;
public GameObject floor2;
public GameObject floor3;
public GameObject floor;
public int width = 30;
public int height = 30;
void Start () {
tiles = new int[width,height];
GenerateTiles();
GenerateWalls();
}
void GenerateTiles() {
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
tiles[x,y] = Random.Range(1,4);
}
}
}
void GenerateWalls() {
Vector3 horizontal = new Vector3(0,0,0);
Vector3 vertical = new Vector3(0,90,0);
GameObject northWall = Instantiate(wall,new Vector3(0,-0.5f,(height / 2)),Quaternion.Euler(horizontal),walls);
northWall.transform.localScale += new Vector3(width - 1,0,0);
GameObject southWall = Instantiate(wall,new Vector3(0,-0.5f,-(height / 2)),Quaternion.Euler(horizontal),walls);
southWall.transform.localScale += new Vector3(width - 1,0,0);
GameObject eastWall = Instantiate(wall,new Vector3((width / 2),-0.5f,0),Quaternion.Euler(vertical),walls);
eastWall.transform.localScale += new Vector3(width - 1,0,0);
GameObject westWall = Instantiate(wall,new Vector3(-(width / 2),-0.5f,0),Quaternion.Euler(vertical),walls);
westWall.transform.localScale += new Vector3(width - 1,0,0);
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
if(tiles[x,y] == 1) {
GameObject newFloorTile = Instantiate(floor1,new Vector3(x - (width/2) + 0.5f,-0.5f,y - (height/2) + 0.5f),transform.rotation,floor.transform);
}
if(tiles[x,y] == 2) {
GameObject newFloorTile = Instantiate(floor2,new Vector3(x - (width / 2) + 0.5f,-0.5f,y - (height / 2) + 0.5f),transform.rotation,floor.transform);
}
if(tiles[x,y] == 3) {
GameObject newFloorTile = Instantiate(floor3,new Vector3(x - (width / 2) + 0.5f,-0.5f,y - (height / 2) + 0.5f),transform.rotation,floor.transform);
}
if(y != 0 && y != height) {
if(tiles[x,y-1] != tiles[x,y]) {
GameObject xWall = Instantiate(wall,new Vector3(x - (width / 2) + 0.5f,-0.5f,y - (height / 2)),Quaternion.Euler(horizontal),walls);
}
}
if(x != 0 && x != width) {
if(tiles[x - 1,y] != tiles[x,y]) {
GameObject yWall = Instantiate(wall,new Vector3(x - (width / 2),-0.5f,y - (height / 2) + 0.5f),Quaternion.Euler(vertical),walls);
}
}
}
}
}
}
I'm in drive by mode (quick 'n dirty), so I'm going to avoid C# example code.
I'd consider a schematic, possibly implemented as an array of objects. I personally prefer simple arrays for this, ins$$anonymous$$d of 2D arrays, where row(n) is array + ( n * rowWidth ), but each to their own. For this requirement, I'd consider each "cell" of this structure to represent where a wall $$anonymous$$IGHT go, not where a tile might go.
In this parlance, I'll say walls are horizontal (for the row) and vertical (for the column), viewed from the perspective of a board game.
As walls are formed by your current method, I'd note where a wall is in the array (which, by definition, means those I don't touch have no wall).
Then, I'd run through rows first (columns in a second pass), and check each cell. I'd count the number of contiguous cells with wall structure, then fashion the wall object based on that size. For the rows I'd assume the creation of horizontal walls.
This means you'd collect the incidental situations where walls form long chains without regard to actual color boundaries, so you can end up with walls optimized beyond the color assignments, which may be of benefit.
If the squares are, say, .25 each side, you'd have to consider how you want to deal with margins for the space of the wall to calculate the size of the wall. For example, if the thickness of the wall is .025, and you find 3 cells in sequence with a wall structure, you'd have 3*.25 + 2*.025 in total length, assu$$anonymous$$g you don't $$anonymous$$d the implied alignment of these horizontal walls.
I'd run a separate pass for vertical walls.
This method allows you to place walls using the existing method, then sweep through to "merge" individual wall segments into longer wall segments.
It assumes you prefer to create rectangular objects as walls, rather than create meshes.
If you created meshes, you could theoretically fashion one wall object that turns 90 corners, but the complexity may not be worth it.
Thank you for your reply. The size of the objects isn't a problem. They are set how I want them already. The current code works to generate a single wall where they should be. I do appreciate your comment but it really only reiterates what I am trying to achieve, not really how to calculate it. I can count how many tiles came before hand that needed a wall. I can calculate how long the wall needs to be. The problem is that I can't work out how to offset where that wall should be located accurately.
"The problem is that I can't work out how to offset where that wall should be located accurately." I've hinted to that with "... you'd have 3*.25 + 2*.025 in total length...", but I didn't detail it assu$$anonymous$$g that since you've already placed tiles that wouldn't be an issue. Before I continue, I'd like to point out that I'm not sure if you have a clear sense of scale with respect to physics, if physics is part of your game. This layout is large. If I read this correctly, the playing field is 30 x 30 units, which means with respect to physics that's 30 meters by 30 meters, or some 98 square feet. If a .5 meter sphere were tossed in as a ball, the physics would look very slow - though that $$anonymous$$IGHT be an intent, I don't know. A .5 meter ball is a "beach ball" size, which has a certain "look" with regard to how fast it falls and moves. If objects should react more like baseball sized objects (which, relative to their size, should appear to fall much faster, A baseball would be .074 (just under 3 inches) in diameter. I say this because in most work I'd expect to see some indication of the size of objects such that the code used to create the field would work on any sized objects, not specifically sized objects, especially under the assumption of aligned units (that is, typically, things are created as 1, 2 or 3 units in size, which is meters. Setting that aside, there are some specifics that must be nailed down. I personally prefer to think in terms of a corner, but it appears you like to think about the center of objects for placement. I'll work with center placement. The square has a dimension, say sd. The center of any square would be sd/2, so the dimension is the side of a square. The wall has a width, say sw, which might be a fraction of the square size, perhaps sd/10 or sd/20. A schematic of the field would be indices into a 2D array structure, which I infer from tiles[x,y], such that the 4th tile of the 3rd row is tile[3, 2] if x are columns and y are rows . If I assume that there is no space between tiles, and the wall straddles the join between tiles, and the floor is the xz plane, a tile is located by it's center with tile_center_x = ( sd x ) + ( sd / 2 ), tile_center_z = ( sd y ) + ( sd / 2 ), which implies that the upper left corner of the tile is ulc_x = tile_center_x - ( sd / 2 ), ulc_z = tile_center_z - ( sd / 2 ), and the other corners can be deduced with something like urc_x = ulc_x + sd, urc_z = ulc_z, llc_x = ulc_x, llc_z = ulc_z + sd, etc. A wall starts at a corner, perhaps centered on the line of the tile. A horizontal wall might be placed by finding the upper left corner of the first square and the upper right corner of the last square in a sequence. The wall's 2d placement would then account for the wall's width (sw), itself a rectangular solid. You already have in $$anonymous$$d the wall's height (which I'd suggest be swh = sd / 2, or something expressed in terms of the size of the square, but that's up to you). (continued...)
Answer by DawdleDev · Jul 09, 2018 at 06:39 PM
Here's an idea for an algorithm. While the code is recording a stretch of walls facing in the same direction, record the number of walls being placed in an integer. Once you get to a place where the walls turn, erase all of the walls placed. Then, create a new wall that is the length of each wall multiplied by the number of walls, at a position halfway down the line of walls.