Search code examples
c#arrays2dgame-developmentsandbox

2D infinite procedural world generation


I am working on a falling sand game and now I am trying to make my world infinite like minecraft. and to do that I figured that I need to make a world class to store all of the chunks that will be generated in a list, and here is my problem.

All of the cells are taking their positions from an array in a chunk class.. so a cell 'cells[10, 10]' is at 'x = 10, y = 10' (I am actually using a 1d array but it is easier to explain this with a 2d array), so if I want to add another chunk to the world list the cells will end up in the same positions. and to offset them I guess I have to make the array larger or just offset thier positions when drawing them?

I am sure this is not how I am supposed to do it and I have looked everywhere. I still don't understand how I am supposed to do this.

Here is the chunk class code:

public class Chunk {
        int Size = 5;

        int Width;
        int Height;

        Cell[] cells;

        Random r = new Random();
        
        int[] direction = new int[2] {0, 1 };
        int result = 0;
        public Chunk(int width, int height) { 
            Width = width;
            Height = height;

            cells = new Cell[Width * Height];
        }

        int GetCellIndex(int x, int y) { return x + y * Width; }
        Cell GetCellInLest(int index) { return cells[index]; }
        Cell GetCell(int x, int y) { return GetCellInLest(GetCellIndex(x, y)); }


        public void CellUpdated(int x, int y) { cells[GetCellIndex(x, y)].Updated = true; }
        public void CellNotUpdated(int x, int y) { cells[GetCellIndex(x, y)].Updated = false; }
        public void ReplaceCell(Cell cell, int x, int y, Cell cell1, int xto, int yto) {

            SetCell(x, y, cell1);
            SetCell(xto, yto, cell);

        }
        public void MoveCell(Cell cell, int x, int y, int xto, int yto) {
            SetCell(x, y, Elements.Air);
            SetCell(xto, yto, cell);

        }

        bool IsInBounds(int x, int y) { return x < Width && x > 0 && y < Height && y > 0; }
        bool IsEmpty(int x, int y) { return IsInBounds(x, y) && GetCell(x, y).Type == CellType.Empty; }


        public void SetCell(int x, int y, Cell cell) { cells[GetCellIndex(x, y)] = cell; }

        public void Update() {

            for (int j = Height - 1; j >= 0; j--) {
                for (int i = 0; i < Width; i++) {
                    CellNotUpdated(i, j);
                }
            }

            for (int j = Height - 1; j >= 0; j--) {
                for (int i = 0; i < Width; i++) {
                    var cell = GetCell(i, j);

                    Rules(cell, i, j);
                }
            }
        }

        void Rules(Cell cell, int X, int Y) {
            if (cell.Updated == false) {
                cell.Updated = true;
                result = direction[r.Next(direction.Length)];


                if (cell.Type == CellType.MovableSolid) {
                    if (IsEmpty(X, Y + 1)) {
                        MoveCell(cell, X, Y, X, Y + 1);

                    } else if (IsEmpty(X + 1, Y + 1) && IsEmpty(X - 1, Y + 1)) {
                        if (result == 0) {
                            MoveCell(cell, X, Y, X + 1, Y + 1);

                        } else {
                            MoveCell(cell, X, Y, X - 1, Y + 1);
                        }
                    } else if (IsEmpty(X + 1, Y + 1) && IsEmpty(X + 1, Y)) {
                        MoveCell(cell, X, Y, X + 1, Y + 1);

                    } else if (IsEmpty(X - 1, Y + 1) && IsEmpty(X - 1, Y)) {
                        MoveCell(cell, X, Y, X - 1, Y + 1);

                    }

                }

                if (cell.Type == CellType.Liquid) {
                    if (IsEmpty(X, Y + 1)) {
                        MoveCell(cell, X, Y, X, Y + 1);

                    } else if (IsEmpty(X + 1, Y + 1) && IsEmpty(X - 1, Y + 1)) {
                        if (result == 0) {
                            MoveCell(cell, X, Y, X + 1, Y + 1);

                        } else {
                            MoveCell(cell, X, Y, X - 1, Y + 1);

                        }

                    } else if (IsEmpty(X + 1, Y + 1) && IsEmpty(X + 1, Y)) {
                        MoveCell(cell, X, Y, X + 1, Y + 1);

                    } else if (IsEmpty(X - 1, Y + 1) && IsEmpty(X - 1, Y)) {
                        MoveCell(cell, X, Y, X - 1, Y + 1);

                    } else if (IsEmpty(X + 1, Y) && IsEmpty(X - 1, Y)) {
                        if (result == 0) {
                            MoveCell(cell, X, Y, X + 1, Y);

                        } else {
                            MoveCell(cell, X, Y, X - 1, Y);

                        }

                    } else if (IsEmpty(X + 1, Y)) {
                        MoveCell(cell, X, Y, X + 1, Y);

                    } else if (IsEmpty(X - 1, Y)) {
                        MoveCell(cell, X, Y, X - 1, Y);

                    }

                }

                if (cell.Type == CellType.Gas) {
                    if (IsEmpty(X, Y - 1)) {
                        MoveCell(cell, X, Y, X, Y - 1);

                    } else if (IsEmpty(X + 1, Y - 1) && IsEmpty(X - 1, Y - 1)) {
                        if (result == 0) {
                            MoveCell(cell, X, Y, X + 1, Y - 1);

                        } else {
                            MoveCell(cell, X, Y, X - 1, Y - 1);

                        }

                    } else if (IsEmpty(X + 1, Y - 1) && IsEmpty(X + 1, Y)) {
                        MoveCell(cell, X, Y, X + 1, Y + 1);

                    } else if (IsEmpty(X - 1, Y - 1) && IsEmpty(X - 1, Y)) {
                        MoveCell(cell, X, Y, X - 1, Y + 1);

                    } else if (IsEmpty(X + 1, Y) && IsEmpty(X - 1, Y)) {
                        if (result == 0) {
                            MoveCell(cell, X, Y, X + 1, Y);

                        } else {
                            MoveCell(cell, X, Y, X - 1, Y);

                        }

                    } else if (IsEmpty(X + 1, Y)) {
                        MoveCell(cell, X, Y, X + 1, Y);

                    } else if (IsEmpty(X - 1, Y)) {
                        MoveCell(cell, X, Y, X - 1, Y);

                    }
                }
            }
        }

        public void Draw(SpriteBatch sb, Texture2D tex) {
            for (int j = 0; j < Height; j++) {
                for (int i = 0; i < Width; i++) {
                    var cell = GetCell(i, j);

                    if (cell.Type != CellType.Empty) {
                        sb.Draw(tex, new Vector2((i * Size) + (2 * (Width * 2)), (j * Size) ), new Rectangle(0, 0, tex.Width, tex.Height), cell.Color, 0, Vector2.Zero, Size, SpriteEffects.None, 1);
                    }
                }
            }
        }

        public void Paint(int x, int y, Cell cell) {

            if(IsInBounds(x, y)) {
                SetCell(x, y, cell);
            }


        }

    }

Solution

  • Define an origin for the chunk.
    The origin is literally the position the rectangle is located at. I defined it below using Vector2Int, since you seem to be using integers exclusively.

    public class Chunk
    {
        public Vector2Int origin;
        ...
    }
    

    For the simple case where the size of each chunk is the same, you can multiply the (x,y) coordinates by the size to get the origin.

    public void Example()
    {
        var size = Vector2Int.one * 10;
    
        for (int y = 0; y < 2; y++)
        {
            for (int x = 0; x < 2; x++)
            {
                var chunk = new Chunk();
                chunk.origin = new Vector2Int(x, y) * size;
            }
        }
    }
    

    Then when you go to render the cell, add the chunk origin to the cells position.