수천 개의 격자 사각형으로 구성된 타일 맵을 사용하여 게임을 만들고 있습니다. 현재 각 사각형에는 충돌을 확인하기위한 사각형 충돌체가 있습니다.
그러나 수천 개의 작은 블록으로 인해 충돌이 있는지 모두 확인하는 것은 비효율적입니다. 만약 타일 맵이 미리 이런 식으로 나타날 것이라는 것을 알았다면, 수천 개의 작은 것 대신에 3 개 또는 4 개의 큰 콜 라이더를 사용할 수있었습니다.
인접한 여러 작은 타일을 최대 큰 타일로 결합하기위한 일종의 표준 알고리즘이 있습니까? 그렇다면 누군가가 여기에 설명하거나 그러한 알고리즘에 대한 문헌을 가리킬 수 있습니까?
대안 적으로, 이러한 방식으로 타일 콜 라이더를 전처리하는 것은 완전히 잘못된 접근법 일 수있다. 그렇다면 매우 많은 수의 충돌체의 효율성을 다루는 올바른 방법은 무엇입니까?
답변
이 알고리즘 이 love2d engine ( lua language )에 유용하다는 것을 알았습니다.
https://love2d.org/wiki/TileMerging
-- map_width and map_height are the dimensions of the map
-- is_wall_f checks if a tile is a wall
local rectangles = {} -- Each rectangle covers a grid of wall tiles
for x = 0, map_width - 1 do
local start_y
local end_y
for y = 0, map_height - 1 do
if is_wall_f(x, y) then
if not start_y then
start_y = y
end
end_y = y
elseif start_y then
local overlaps = {}
for _, r in ipairs(rectangles) do
if (r.end_x == x - 1)
and (start_y <= r.start_y)
and (end_y >= r.end_y) then
table.insert(overlaps, r)
end
end
table.sort(
overlaps,
function (a, b)
return a.start_y < b.start_y
end
)
for _, r in ipairs(overlaps) do
if start_y < r.start_y then
local new_rect = {
start_x = x,
start_y = start_y,
end_x = x,
end_y = r.start_y - 1
}
table.insert(rectangles, new_rect)
start_y = r.start_y
end
if start_y == r.start_y then
r.end_x = r.end_x + 1
if end_y == r.end_y then
start_y = nil
end_y = nil
elseif end_y > r.end_y then
start_y = r.end_y + 1
end
end
end
if start_y then
local new_rect = {
start_x = x,
start_y = start_y,
end_x = x,
end_y = end_y
}
table.insert(rectangles, new_rect)
start_y = nil
end_y = nil
end
end
end
if start_y then
local new_rect = {
start_x = x,
start_y = start_y,
end_x = x,
end_y = end_y
}
table.insert(rectangles, new_rect)
start_y = nil
end_y = nil
end
end
Here's how the rectangles would be used for physics.
-- Use contents of rectangles to create physics bodies
-- phys_world is the world, wall_rects is the list of...
-- wall rectangles
for _, r in ipairs(rectangles) do
local start_x = r.start_x * TILE_SIZE
local start_y = r.start_y * TILE_SIZE
local width = (r.end_x - r.start_x + 1) * TILE_SIZE
local height = (r.end_y - r.start_y + 1) * TILE_SIZE
local x = start_x + (width / 2)
local y = start_y + (height / 2)
local body = love.physics.newBody(phys_world, x, y, 0, 0)
local shape = love.physics.newRectangleShape(body, 0, 0,
width, height)
shape:setFriction(0)
table.insert(wall_rects, {body = body, shape = shape})
end
현재 프로젝트에서 love2d 예제를 따르십시오. 빨간색으로 벽이 충돌하는 것을 볼 수 있습니다.
답변
파괴 가능한 지형을 만들려고한다면 Unity 에서이 작업을 수행 한 방식은 세상의 가장자리 블록에만 충돌자를 설정하는 것입니다. 예를 들어, 이것이 당신이 달성하고자하는 것입니다 :
모든 녹색 블록에는 충돌체가 포함되어 있으며 나머지 블록에는 충돌이 없습니다. 그것은 계산에 많은 비용을 절약합니다. 블록을 파괴하면 인접한 블록의 충돌체를 아주 쉽게 활성화 할 수 있습니다. 충돌체 활성화 / 비활성화는 비용이 많이 들고 드물게 수행해야합니다.
따라서 Tile 리소스는 다음과 같습니다.
표준 게임 오브젝트이지만 풀링 가능합니다. 또한 상자 충돌체는 기본적으로 비활성화되어 있습니다. 가장자리 타일 인 경우에만 활성화됩니다.
월드를 정적으로로드하는 경우 타일을 풀링 할 필요가 없습니다. 한 번에 모두로드하고 가장자리에서 거리를 계산하고 필요한 경우 충돌체를 적용 할 수 있습니다.
동적으로로드하는 경우 타일 풀을 사용하는 것이 가장 좋습니다. 여기 내 새로 고침 루프의 편집 된 예가 있습니다. 현재 카메라 뷰를 기반으로 타일을로드합니다.
public void Refresh(Rect view)
{
//Each Tile in the world uses 1 Unity Unit
//Based on the passed in Rect, we calc the start and end X/Y values of the tiles presently on screen
int startx = view.x < 0 ? (int)(view.x + (-view.x % (1)) - 1) : (int)(view.x - (view.x % (1)));
int starty = view.y < 0 ? (int)(view.y + (-view.y % (1)) - 1) : (int)(view.y - (view.y % (1)));
int endx = startx + (int)(view.width);
int endy = starty - (int)(view.height);
int width = endx - startx;
int height = starty - endy;
//Create a disposable hashset to store the tiles that are currently in view
HashSet<Tile> InCurrentView = new HashSet<Tile>();
//Loop through all the visible tiles
for (int i = startx; i <= endx; i += 1)
{
for (int j = starty; j >= endy; j -= 1)
{
int x = i - startx;
int y = starty - j;
if (j > 0 && j < Height)
{
//Get Tile (I wrap my world, that is why I have this mod here)
Tile tile = Blocks[Helper.mod(i, Width), j];
//Add tile to the current view
InCurrentView.Add(tile);
//Load tile if needed
if (!tile.Blank)
{
if (!LoadedTiles.Contains(tile))
{
if (TilePool.AvailableCount > 0)
{
//Grab a tile from the pool
Pool<PoolableGameObject>.Node node = TilePool.Get();
//Disable the collider if we are not at the edge
if (tile.EdgeDistance != 1)
node.Item.GO.GetComponent<BoxCollider2D>().enabled = false;
//Update tile rendering details
node.Item.Set(tile, new Vector2(i, j), DirtSprites[tile.TextureID], tile.Collidable, tile.Blank);
tile.PoolableGameObject = node;
node.Item.Refresh(tile);
//Tile is now loaded, add to LoadedTiles hashset
LoadedTiles.Add(tile);
//if Tile is edge block, then we enable the collider
if (tile.Collidable && tile.EdgeDistance == 1)
node.Item.GO.GetComponent<BoxCollider2D>().enabled = true;
}
}
}
}
}
}
//Get a list of tiles that are no longer in the view
HashSet<Tile> ToRemove = new HashSet<Tile>();
foreach (Tile tile in LoadedTiles)
{
if (!InCurrentView.Contains(tile))
{
ToRemove.Add(tile);
}
}
//Return these tiles to the Pool
//this would be the simplest form of cleanup -- Ideally you would do this based on the distance of the tile from the viewport
foreach (Tile tile in ToRemove)
{
LoadedTiles.Remove(tile);
tile.PoolableGameObject.Item.GO.GetComponent<BoxCollider2D>().enabled = false;
tile.PoolableGameObject.Item.GO.transform.position = new Vector2(Int32.MinValue, Int32.MinValue);
TilePool.Return(tile.PoolableGameObject);
}
LastView = view;
}
이상적으로는 훨씬 더 자세한 게시물을 작성합니다. 그러나 이것은 도움이 될 수 있습니다. 궁금한 점이 있으면 언제든지 문의하거나 문의하십시오.