Search code examples
iossprite-kitskspritenodeskscene

SpriteKit: suggestions for rounding corners of unconventional grid?


The goal is to round the corners of an unconventional grid similar to the following:

https://s-media-cache-ak0.pinimg.com/564x/50/bc/e0/50bce0cb908913ebc2cf630d635331ef.jpg

https://s-media-cache-ak0.pinimg.com/564x/7e/29/ee/7e29ee80e957ec22bbba630ccefbfaa2.jpg

Instead of a grid with four corners like a conventional grid, these grids have multiple corners in need of rounding.

The brute force approach would be to identify tiles with corners exposed then round those corners either with a different background image or by clipping the corners in code.

Is there a cleaner approach?

The grid is rendered for an iOS app in a SpriteKit SKScene.


Solution

  • What we did was lay out the tiles then call this function to round the nodes of exposed tiles.

    // Rounds corners of exposed tiles. UIKit inverts coordinates so top is bottom and vice-versa.
    fileprivate func roundTileCorners() {
        // Get all tiles
        var tiles = [TileClass]()
        tileLayer.enumerateChildNodes(withName: ".//*") { node, stop in
            if node is TileClass {
                tiles.append(node as! TileClass)
            }
        }
    
        // Round corners for each exposed tile
        for t in tiles {
            // Convert tile's position to root coordinates
            let convertedPos = convert(t.position, from: t.parent!)
    
            // Set neighbor positions
            var leftNeighborPos = convertedPos
            leftNeighborPos.x -= tileWidth
            var rightNeighborPos = convertedPos
            rightNeighborPos.x += tileWidth
            var topNeighborPos = convertedPos
            topNeighborPos.y += tileHeight
            var bottomNeighborPos = convertedPos
            bottomNeighborPos.y -= tileHeight
    
            // Set default value for rounding
            var cornersToRound : UIRectCorner?
    
            // No neighbor below & to left? Round bottom left.
            if !isTileAtPoint(point: bottomNeighborPos) && !isTileAtPoint(point: leftNeighborPos) {
                cornersToRound = cornersToRound?.union(.topLeft) ?? .topLeft
            }
    
            // No neighbor below & to right? Round bottom right.
            if !isTileAtPoint(point: bottomNeighborPos) && !isTileAtPoint(point: rightNeighborPos) {
                cornersToRound = cornersToRound?.union(.topRight) ?? .topRight
            }
    
            // No neightbor above & to left? Round top left.
            if !isTileAtPoint(point: topNeighborPos) && !isTileAtPoint(point: leftNeighborPos) {
                cornersToRound = cornersToRound?.union(.bottomLeft) ?? .bottomLeft
            }
    
            // No neighbor above & to right? Round top right.
            if !isTileAtPoint(point: topNeighborPos) && !isTileAtPoint(point: rightNeighborPos) {
                cornersToRound = cornersToRound?.union(.bottomRight) ?? .bottomRight
            }
    
            // Any corners to round?
            if cornersToRound != nil {
                t.roundCorners(cornersToRound: cornersToRound!)
            }
        }
    }
    
    // Returns true if a tile exists at <point>. Assumes <point> is in root node's coordinates.
    fileprivate func isTileAtPoint(point: CGPoint) -> Bool {
        return nodes(at: point).contains(where: {$0 is BoardTileNode })
    }