Симс-стиль стен, часть 2: Заливка


Продолжением этого поста. Я перетащил все это к единству, и вообще прибрались немного, но глядя на код, код для заливки грязно и повторяющиеся. Вот вам наглядно, как это работает:

enter image description here

Вот код, который достигает этого, с небольшими комментариями:

    protected WallSegment readSegment(PackedPoint p, bool isX) => (isX ? map.wallsX : map.wallsZ)[p];

    protected void writeSegment(PackedPoint p, bool isX, WallSegment value) => (isX ? map.wallsX : map.wallsZ)[p] = value;

    protected struct WallPosition
    {
        public WallPosition(PackedPoint pp, bool isX, bool isFront)
        {
            this.pp = pp;
            this.isX = isX;
            this.isFront = isFront;
        }

        public PackedPoint pp;
        public bool isX, isFront;
        public int x { get { return pp.x; } set { pp.x = value; } }
        public int y { get { return pp.y; } set { pp.y = value; } }
        public int z { get { return pp.z; } set { pp.z = value; } }
    }

    private void floodFill()
    {
        Assert.isTrue(curPos.HasValue);
        WallPosition p = curPos.Value;
        WallSegment old = readSegment(p.pp, p.isX);
        Debug.Assert(old.exists);
        ushort mOld = (p.isFront ? old.front : old.back);
        ushort mNew = map.selectedMaterial;
        Debug.Assert(mOld != 0 && mNew != 0);

        // if the clicked segment is already the destination material, don't do anything
        if(mOld == mNew) 
            return;

        Queue<WallPosition> q = new Queue<WallPosition>();
        q.Enqueue(p);
        while(q.Count > 0)
        {
            p = q.Dequeue();
            old = readSegment(p.pp, p.isX);

            // this covers both the case where we've already painted it and the case where it's a different
            // material that we don't want to flood fill
            if((p.isFront ? old.front : old.back) != mOld)
                continue;

            // write the new segment data
            WallSegment @new = p.isFront ? new WallSegment(mNew, old.back) : new WallSegment(old.front, mNew);
            writeSegment(p.pp, p.isX, @new);

            // check if any of the 4 directions from this wall exist and are not blocked by intervening tiles
            // if so, enqueue those new positions. Note this means we will visit painted segments up to 4 times,
            // but since they will already have been painted, we won't do this part again
            WallPosition next = p; 
            if(goLeft(ref next)) 
                q.Enqueue(next);
            next = p; 
            if(goRight(ref next)) 
                q.Enqueue(next);
            next = p; 
            if(goUp(ref next)) 
                q.Enqueue(next);
            next = p; 
            if(goDown(ref next))
                q.Enqueue(next);
        }

        afterUpdate("Flood Fill Walls");
    }

    private bool goLeft(ref WallPosition p)
    {
        MapWalls wallsX = map.wallsX;
        MapWalls wallsZ = map.wallsZ;
        if(p.isX)
        {
            if(p.isFront) // decreasing X
            {
                if(wallsZ.exists(p.x,     p.y, p.z    )) {               p.isX = false;                    return true; }
                if(wallsX.exists(p.x - 1, p.y, p.z    )) { p.x--;                                          return true; }
                if(wallsZ.exists(p.x,     p.y, p.z - 1)) {        p.z--; p.isX = false; p.isFront = false; return true; }
            }
            else       // increasing X
            {
                if(wallsZ.exists(p.x + 1, p.y, p.z - 1)) { p.x++; p.z--; p.isX = false;                    return true; }
                if(wallsX.exists(p.x + 1, p.y, p.z    )) { p.x++;                                          return true; }
                if(wallsZ.exists(p.x + 1, p.y, p.z    )) { p.x++;        p.isX = false; p.isFront = true;  return true; }
            }
        }
        else
        {
            if(p.isFront) // increasing Z
            {
                if(wallsX.exists(p.x    , p.y, p.z + 1)) {        p.z++; p.isX = true;  p.isFront = false; return true; }
                if(wallsZ.exists(p.x    , p.y, p.z + 1)) {        p.z++;                                   return true; }
                if(wallsX.exists(p.x - 1, p.y, p.z + 1)) { p.x--; p.z++; p.isX = true;                     return true; }
            }
            else        // decreasing Z
            {
                if(wallsX.exists(p.x - 1, p.y, p.z    )) { p.x--;        p.isX = true;  p.isFront = true;  return true; }
                if(wallsZ.exists(p.x    , p.y, p.z - 1)) {        p.z--;                                   return true; }
                if(wallsX.exists(p.x    , p.y, p.z    )) {               p.isX = true;                     return true; }
            }
        }
        return false;
    }

    private bool goRight(ref WallPosition p)
    {
        MapWalls wallsX = map.wallsX;
        MapWalls wallsZ = map.wallsZ;
        if(p.isX)
        {
            if(!p.isFront) // decreasing X
            {
                if(wallsZ.exists(p.x    , p.y, p.z - 1)) {        p.z--; p.isX = false; p.isFront = true;  return true; }
                if(wallsX.exists(p.x - 1, p.y, p.z    )) { p.x--;                                          return true; }
                if(wallsZ.exists(p.x    , p.y, p.z    )) {               p.isX = false;                    return true; }
            }
            else          // increasing X
            {
                if(wallsZ.exists(p.x + 1, p.y, p.z    )) { p.x++;        p.isX = false; p.isFront = false; return true; }
                if(wallsX.exists(p.x + 1, p.y, p.z    )) { p.x++;                                          return true; }
                if(wallsZ.exists(p.x + 1, p.y, p.z - 1)) { p.x++; p.z--; p.isX = false;                    return true; }
            }
        }
        else
        {
            if(!p.isFront) // increasing Z
            {
                if(wallsX.exists(p.x - 1, p.y, p.z + 1)) { p.x--; p.z++; p.isX = true;                     return true; }
                if(wallsZ.exists(p.x,     p.y, p.z + 1)) {        p.z++;                                   return true; }
                if(wallsX.exists(p.x,     p.y, p.z + 1)) {        p.z++; p.isX = true;  p.isFront = true;  return true; }
            }
            else          // decreasing Z
            {
                if(wallsX.exists(p.x    , p.y, p.z    )) {               p.isX = true;                     return true; }
                if(wallsZ.exists(p.x    , p.y, p.z - 1)) {        p.z--;                                   return true; }
                if(wallsX.exists(p.x - 1, p.y, p.z    )) { p.x--;        p.isX = true;  p.isFront = false; return true; }
            }
        }
        return false;
    }

    private bool goUp(ref WallPosition p)
    {
        int x = p.x, y = p.y, z = p.z;
        MapWalls wallsX = map.wallsX, wallsZ = map.wallsZ;
        MapTiles tiles = map.tiles;
        bool wallAbove, tileAbove;
        if(p.isX)
        {
            wallAbove = wallsX.exists(x, y + 1, z);
            tileAbove = p.isFront ? tiles.exists(x, y + 1, z) : tiles.exists(x, y + 1, z - 1);
        }
        else
        {
            wallAbove = wallsZ.exists(x, y + 1, z);
            tileAbove = p.isFront ? tiles.exists(x, y + 1, z) : tiles.exists(x - 1, y + 1, z);
        }
        if(wallAbove && !tileAbove)
        {
            p.y++; 
            return true;
        } 
        return false;
    }

    private bool goDown(ref WallPosition p)
    {
        int x = p.x, y = p.y, z = p.z;
        MapWalls wallsX = map.wallsX, wallsZ = map.wallsZ;
        MapTiles tiles = map.tiles;
        bool wallBelow, tileBelow;
        if(p.isX)
        {
            wallBelow = wallsX.exists(x, y - 1, z);
            tileBelow = p.isFront ? tiles.exists(x, y, z) : tiles.exists(x, y, z - 1);
        }
        else
        {
            wallBelow = wallsZ.exists(x, y - 1, z);
            tileBelow = p.isFront ? tiles.exists(x, y, z) : tiles.exists(x - 1, y, z);
        }
        if(wallBelow && !tileBelow)
        {
            p.y--; 
            return true;
        } 
        return false;
    }

Я смотрю меньше за стиль кода/интервал/предложения брекеты (мне нравится то, как я выровнял вещи, так как это помогает с отладкой и пятнистости), и на алгоритмическом предложения.

Я в основном беспокоюсь, потому что я рассматриваю добавление диагональных стенах. Что вдруг Блум влево/вправо логики от 24 случаях (2 стены массивы * 3 направлениях краска может пойти * 2 для передней и задней * 2 для левого и правого) в 112 случаях (4 стены массивы * 7 направлениям краска может пойти * 2 для передней и задней * 2 для левого и правого), в которой точке это может быть проще для создания кода с текстового шаблона или что-то.


Редактировать:, чтобы помочь сосредоточиться на алгоритме вместо форматирования, я управляю авто-формат на нем. Я не собираюсь менять форматирование в моей версии, но если вы найдете его легче читать таким образом, здесь вы идете. Я ищу обратную связь о том, как упростить или улучшить фактический код, а не мнения о том, что стиль/дистанционирование разные люди предпочитают.

    protected WallSegment readSegment(PackedPoint p, bool isX) => (isX ? map.wallsX : map.wallsZ)[p];

    protected void writeSegment(PackedPoint p, bool isX, WallSegment value) =>
        (isX ? map.wallsX : map.wallsZ)[p] = value;

    protected struct WallPosition
    {
        public WallPosition(PackedPoint pp, bool isX, bool isFront)
        {
            this.pp = pp;
            this.isX = isX;
            this.isFront = isFront;
        }

        public PackedPoint pp;
        public bool isX, isFront;

        public int x
        {
            get { return pp.x; }
            set { pp.x = value; }
        }

        public int y
        {
            get { return pp.y; }
            set { pp.y = value; }
        }

        public int z
        {
            get { return pp.z; }
            set { pp.z = value; }
        }
    }

    private void floodFill()
    {
        Assert.isTrue(curPos.HasValue);
        WallPosition startPosition = curPos.Value;
        WallSegment oldSegment = readSegment(startPosition.pp, startPosition.isX);
        Debug.Assert(oldSegment.exists);
        ushort oldMaterial = startPosition.isFront ? oldSegment.front : oldSegment.back;
        ushort newMaterial = map.selectedMaterial;
        Debug.Assert(oldMaterial != 0 && newMaterial != 0);

        // if the clicked segment is already the destination material, don't do anything
        if(oldMaterial == newMaterial)
        {
            return;
        }

        Queue<WallPosition> q = new Queue<WallPosition>();
        q.Enqueue(startPosition);
        while(q.Count > 0)
        {
            WallPosition p = q.Dequeue();
            oldSegment = readSegment(startPosition.pp, startPosition.isX);

            // this covers both the case where we've already painted it and the case where it's a different
            // material that we don't want to flood fill
            if((startPosition.isFront ? oldSegment.front : oldSegment.back) != oldMaterial)
            {
                continue;
            }

            // write the new segment data
            WallSegment @new = startPosition.isFront
                ? new WallSegment(newMaterial, oldSegment.back)
                : new WallSegment(oldSegment.front, newMaterial);
            writeSegment(startPosition.pp, startPosition.isX, @new);

            // check if any of the 4 directions from this wall exist and are not blocked by intervening tiles
            // if so, enqueue those new positions. Note this means we will visit painted segments up to 4 times,
            // but since they will already have been painted, we won't do this part again
            WallPosition next = startPosition;
            if(goLeft(ref next))
            {
                q.Enqueue(next);
            }

            next = startPosition;
            if(goRight(ref next))
            {
                q.Enqueue(next);
            }

            next = startPosition;
            if(goUp(ref next))
            {
                q.Enqueue(next);
            }

            next = startPosition;
            if(goDown(ref next))
            {
                q.Enqueue(next);
            }
        }

        afterUpdate("Flood Fill Walls");
    }

    protected bool goLeft(ref WallPosition p)
    {
        MapWalls wallsX = map.wallsX;
        MapWalls wallsZ = map.wallsZ;
        if(p.isX)
        {
            if(p.isFront) // decreasing X
            {
                if(wallsZ.exists(p.x, p.y, p.z))
                {
                    p.isX = false;
                    return true;
                }

                if(wallsX.exists(p.x - 1, p.y, p.z))
                {
                    p.x--;
                    return true;
                }

                if(wallsZ.exists(p.x, p.y, p.z - 1))
                {
                    p.z--;
                    p.isX = false;
                    p.isFront = false;
                    return true;
                }
            }
            else // increasing X
            {
                if(wallsZ.exists(p.x + 1, p.y, p.z - 1))
                {
                    p.x++;
                    p.z--;
                    p.isX = false;
                    return true;
                }

                if(wallsX.exists(p.x + 1, p.y, p.z))
                {
                    p.x++;
                    return true;
                }

                if(wallsZ.exists(p.x + 1, p.y, p.z))
                {
                    p.x++;
                    p.isX = false;
                    p.isFront = true;
                    return true;
                }
            }
        }
        else
        {
            if(p.isFront) // increasing Z
            {
                if(wallsX.exists(p.x, p.y, p.z + 1))
                {
                    p.z++;
                    p.isX = true;
                    p.isFront = false;
                    return true;
                }

                if(wallsZ.exists(p.x, p.y, p.z + 1))
                {
                    p.z++;
                    return true;
                }

                if(wallsX.exists(p.x - 1, p.y, p.z + 1))
                {
                    p.x--;
                    p.z++;
                    p.isX = true;
                    return true;
                }
            }
            else // decreasing Z
            {
                if(wallsX.exists(p.x - 1, p.y, p.z))
                {
                    p.x--;
                    p.isX = true;
                    p.isFront = true;
                    return true;
                }

                if(wallsZ.exists(p.x, p.y, p.z - 1))
                {
                    p.z--;
                    return true;
                }

                if(wallsX.exists(p.x, p.y, p.z))
                {
                    p.isX = true;
                    return true;
                }
            }
        }

        return false;
    }

    protected bool goRight(ref WallPosition p)
    {
        MapWalls wallsX = map.wallsX;
        MapWalls wallsZ = map.wallsZ;
        if(p.isX)
        {
            if(!p.isFront) // decreasing X
            {
                if(wallsZ.exists(p.x, p.y, p.z - 1))
                {
                    p.z--;
                    p.isX = false;
                    p.isFront = true;
                    return true;
                }

                if(wallsX.exists(p.x - 1, p.y, p.z))
                {
                    p.x--;
                    return true;
                }

                if(wallsZ.exists(p.x, p.y, p.z))
                {
                    p.isX = false;
                    return true;
                }
            }
            else // increasing X
            {
                if(wallsZ.exists(p.x + 1, p.y, p.z))
                {
                    p.x++;
                    p.isX = false;
                    p.isFront = false;
                    return true;
                }

                if(wallsX.exists(p.x + 1, p.y, p.z))
                {
                    p.x++;
                    return true;
                }

                if(wallsZ.exists(p.x + 1, p.y, p.z - 1))
                {
                    p.x++;
                    p.z--;
                    p.isX = false;
                    return true;
                }
            }
        }
        else
        {
            if(!p.isFront) // increasing Z
            {
                if(wallsX.exists(p.x - 1, p.y, p.z + 1))
                {
                    p.x--;
                    p.z++;
                    p.isX = true;
                    return true;
                }

                if(wallsZ.exists(p.x, p.y, p.z + 1))
                {
                    p.z++;
                    return true;
                }

                if(wallsX.exists(p.x, p.y, p.z + 1))
                {
                    p.z++;
                    p.isX = true;
                    p.isFront = true;
                    return true;
                }
            }
            else // decreasing Z
            {
                if(wallsX.exists(p.x, p.y, p.z))
                {
                    p.isX = true;
                    return true;
                }

                if(wallsZ.exists(p.x, p.y, p.z - 1))
                {
                    p.z--;
                    return true;
                }

                if(wallsX.exists(p.x - 1, p.y, p.z))
                {
                    p.x--;
                    p.isX = true;
                    p.isFront = false;
                    return true;
                }
            }
        }

        return false;
    }

    protected bool goUp(ref WallPosition p)
    {
        int x = p.x, y = p.y, z = p.z;
        MapWalls wallsX = map.wallsX, wallsZ = map.wallsZ;
        MapTiles tiles = map.tiles;
        bool wallAbove, tileAbove;
        if(p.isX)
        {
            wallAbove = wallsX.exists(x, y + 1, z);
            tileAbove = p.isFront ? tiles.exists(x, y + 1, z) : tiles.exists(x, y + 1, z - 1);
        }
        else
        {
            wallAbove = wallsZ.exists(x, y + 1, z);
            tileAbove = p.isFront ? tiles.exists(x, y + 1, z) : tiles.exists(x - 1, y + 1, z);
        }

        if(wallAbove && !tileAbove)
        {
            p.y++;
            return true;
        }

        return false;
    }

    protected bool goDown(ref WallPosition p)
    {
        int x = p.x, y = p.y, z = p.z;
        MapWalls wallsX = map.wallsX, wallsZ = map.wallsZ;
        MapTiles tiles = map.tiles;
        bool wallBelow, tileBelow;
        if(p.isX)
        {
            wallBelow = wallsX.exists(x, y - 1, z);
            tileBelow = p.isFront ? tiles.exists(x, y, z) : tiles.exists(x, y, z - 1);
        }
        else
        {
            wallBelow = wallsZ.exists(x, y - 1, z);
            tileBelow = p.isFront ? tiles.exists(x, y, z) : tiles.exists(x - 1, y, z);
        }

        if(wallBelow && !tileBelow)
        {
            p.y--;
            return true;
        }

        return false;
    }

Однако, если вы пытаетесь следить за тем, что происходит на самом деле с goLeft и goRightоднако, я настоятельно рекомендую колонке посмотреть в исходный код, после изменения каждой переменной представлены в столбце. Каждый столбец код в тесте выравнивается, так что каждый из 24 случаев на одной линии и выложил вроде как это будет на графике или таблице. Это помогает в пятнистости и понимание различий между каждой из 24 случаев (каждый из 24 случаев очень похож).



Комментарии
2 ответа


Мне нравится то, как я выровнял вещи, так как это помогает с отладкой и пятнистости

Я не обязательно отражают это мнение.

Вы используете много очень коротких имен переменных параметров, которые делают код трудно понять. Помните, что их имена выбраны IntelliSense и показывается пользователю. Если все, что он видит p или pp это не большая помощь.

Вы также можете использовать те же имена для разных вещей, и это не согласуется:

PackedPoint p
PackedPoint pp
WallPosition p

Это не очень профессионально. Однако вы постоянно использовать путая имена во всех частях код:


WallSegment old = readSegment(p.pp, p.isX);    
ushort mOld = (p.isFront ? old.front : old.back);
ushort mNew = map.selectedMaterial;

Вы спрашиваете об алгоритме, но как кто-то должен понимать его, когда он впервые должен ответить на такие вопросы как:


  • old что?

  • mOld & mNew - какого черта m?


Queue<WallPosition> q = new Queue<WallPosition>();


  • q чего?

и так далее...

Вы не можете ожидать кого-либо, чтобы улучшить свой алгоритм без их понимания сначала и код не помогает с этим. Наоборот. Похоже, она была спрятана.

Я знаю, что это легко создать рабочее решение, потому что в момент написания вы все это понимаете... другие люди-нет.

Все мы видим какие-то письма. Если вы не используете сильные имена переменных, они не имеют смысла ни для кого, кроме вас... хотя через пару недель вы будете там, где мы сейчас, интересно, что там творится, и вы поймете, о чем мы говорим ;-)

6
ответ дан 27 марта 2018 в 02:03 Источник Поделиться

Я не слишком хорошо знакомы с графическими алгоритмами, так что я оставлю комментарий для других. Это в основном обзор стиль, потому что вы делаете много ошибок, что име сделает его более трудно поддерживать этот код.


public PackedPoint pp;
public bool isX, isFront;

Не делайте ваши поля public. Общей Конвенции является то, что поле-это private внедрение деталь; все, что должно быть подвержено все остальное имущество, которое может контролировать запись.



if(goLeft(ref next)) 
q.Enqueue(next);

У вас есть хорошие вмятины, но, пожалуйста, используйте скобки. Брекеты никогда не необязательно в мою книгу; я видел много ошибок/путаница (т. е. новый программист не понимает, что вы можете иметь только одно заявление без скобок) вокруг них.



if(!p.isFront) // decreasing X
{
if(wallsZ.exists(p.x , p.y, p.z - 1)) { p.z--; p.isX = false; p.isFront = true; return true; }
if(wallsX.exists(p.x - 1, p.y, p.z )) { p.x--; return true; }
if(wallsZ.exists(p.x , p.y, p.z )) { p.isX = false; return true; }
}

Теперь это просто лажа. И он скрывает код на экране; мне понадобилась секунда, чтобы выяснить, что здесь происходило, и она вас тоже, когда вы вернетесь к нему в следующем году. Писать каждый оператор с новой строки Вот так:

if(!p.isFront) // decreasing X
{
if(wallsZ.exists(p.x, p.y, p.z - 1))
{
p.z--;
p.isX = false;
p.isFront = true;
return true;
}
if(wallsX.exists(p.x - 1, p.y, p.z))
{
p.x--;
return true;
}
if(wallsZ.exists(p.x, p.y, p.z))
{
p.isX = false;
return true;
}
}

Это действительно хороший случай для сопоставления, характеристика шаблон c# 7 по. Я не так хорошо знаком с этой функцией, как хотелось бы, но это должно выглядеть примерно так:

switch (p)
{
// syntax might be `case p when wallsZ.exists(p.x, p.y, p.z - 1)`:
case wallsZ.exists(p.x, p.y, p.z - 1):
p.z--;
p.isX = false;
p.isFront = true;
return true;
case wallsX.exists(p.x - 1, p.y, p.z):
p.x--;
return true;
case wallsZ.exists(p.x, p.y, p.z):
p.isX = false;
return true;
}



int x = p.x, y = p.y, z = p.z;

Пишут те, каждый по своей линии. Сначала я думал, что был сложный инициализатор.


Причина есть рекомендации, потому что это позволяет вам снимать свой код и найти, что конкретно происходит в каждой точке не читая посимвольно и построчно. Если, например, я никогда не работал над этим, но исправить баг на некоторых особый случай, связанный с одним из размеров, я бы внимательно читать код, чтобы выяснить, где переменные объявляются и используются. К тому времени я был сделан, я бы знал процесс, но я бы так вовлечены в процесс, я бы иметь трудное время, видя алгоритма.

Если, с другой стороны, вы выполнили несколько стандартных рекомендаций, я смогу прочитать это и понять общий алгоритм и потом смотрите через процесс, чтобы увидеть, где он отклонился от алгоритма. Я бы также быть в состоянии легко пропустить разделы процесса, которые правильно функционируют на основе ожиданий алгоритма. Это может ускорить исправление ошибки от 5 часов до 5 минут.

5
ответ дан 27 марта 2018 в 02:03 Источник Поделиться