Conway’s Game of Life

《Betway体育平台》是一款零玩家游戏,最初是为了二维空间而设计的. 在《Betway体育平台》中,有一个包含许多细胞的网格,这些细胞要么是活的,要么是死的. 游戏是独立进行的,并遵循四个重要规则. 如果一个活细胞的相邻细胞少于两个,它就会死亡. 如果一个活细胞有两到三个邻居,它就会活下去. 如果一个死亡的细胞恰好有三个邻居,它就会复活. 最后,如果一个活细胞有超过三个邻居,它就会死亡. These are the basic rules of the Game of Life. 你可以通过以下链接阅读更多关于这款游戏的内容:

Creating the Project

对于这个项目,我们将首先创建一个ARKit Swift Xcode项目. 一旦你点击增强现实应用选择任何名称和地点, 记住要选择Scenekit作为图形框架.

Create a AR App template

Creating the Cell

首先,让我们创建一个名为CellOfLife的Swift文件.斯威夫特. 对于这个类,我们希望用一个方框来表示单元格, a color that the 盒子 will be, and if the 细胞 is alive or not.

进口SceneKit

class CellOfLife: SCNNode {
    // A default alive color the 细胞s will use
    private let aliveColor = UIColor.白色.withAlphaComponent(0.75)
    // The 盒子 that will represent the 细胞
    private var 盒子Node: SCNNode
    // A color that can be set and the 盒子 will use
    public var color: UIColor? {
        didSet {
            自我.盒子Node.几何?.firstMaterial?.扩散.contents = color ?? aliveColor
        }
        
    }
    // If the 细胞 is dead the 盒子 will be hidden
    public var isAlive: Bool {
        didSet {
            盒子Node.isHidden = !isAlive
        }
    }
    
    // Creates a 细胞 with a SCNBox
    init(isAlive alive: Bool, 节点Width: CGFloat, 节点Height: CGFloat) {
        let 盒子 = SCNBox(宽度:节点Width,高度:节点Height,长度:节点Width, chamferRadius: 0)
        // Set the firstMaterial to the aliveColor
        盒子.firstMaterial?.扩散.contents = aliveColor
        盒子Node = SCNNode(几何: 盒子)
        isAlive =活着
        超级.init ()
        addChildNode(盒子Node)
        盒子Node.isHidden = !isAlive
    }
    
    需要初始化?(coder aDecoder: NSCoder) {
        fatalError("init(coder:)没有被实现")
    }
}

Creating the Cube

既然已经创建了单元格,现在就可以创建多维数据集了! 让我们创建一个新的Swift文件,并命名为CubeOfLife.斯威夫特. 对于这个类,我们将需要一个三个痴呆症数组的细胞,我们刚刚创建和一个只是,如果他们是活着或不是. 首先,让我们添加这个类所需的变量.

进口SceneKit

class CubeOfLife: SCNNode {
    var 生活: [[[Bool]]] = []
    var 细胞sOfLife: [[[CellOfLife]]] = []
    var大小:Int
    var zSize: Int
    var width: CGFloat
    var height: CGFloat
    var isBuilt = false

    init(n: Int,宽度:CGFloat,高度:CGFloat, withAliveCells 细胞s: [float3])? = nil, nHeight: Int = 5) {
        自我.大小= n
        自我.zSize = nHeight
        自我.宽度=宽度
        自我.身高=身高
        超级.init ()
        setupLife(withAliveCells: 细胞s)
    }

// ...

    需要初始化?(coder aDecoder: NSCoder) {
        fatalError("init(coder:)没有被实现")
    }
}

SetupLife Function

对于SetupLife函数,我们希望随机生成一个立方体或接收活细胞位置.

private setupLife(withAliveCells 细胞Locations: [float3])? = nil) {
        对于x in (0 ..< size) {
            var 飞机: [[Bool]] = []
            对于y in (0 ..< size) {
                var 行: [Bool] = []
                对于z in (0 ..< zSize) {
                    if let 细胞s = 细胞Locations {
                        // Center the Location
                        let 数 = 细胞s.filter { Int(0美元.x) + Int(size / 2) == x &&
                                                    Int(0美元.y) + Int(size / 2) == y &&
                                                    Int(0美元.z + 1) == z }
                        行.追加(!数.isEmpty)
                        
                    其他}{
                        / /随机!
                        行.追加(Bool.随机())
                    }
                }
                飞机.追加(行)
            }
            生活.追加(飞机)
        }
    }

Quick Helper Functions

对于从数组中检索单元格,我们不想让索引出界! 因此,我们将创建一个辅助函数来获取值并确保我们没有试图获取数组中不存在的值.

private func get(_ x: Int, _ y: Int, _ z: Int) -> Bool? {
        if x > 0, y > 0, z > 0, x < size, y < size, z < zSize {
            let value = 生活[x][y][z]
            
            返回值
        }
        返回nil
        
    }
    
    private func get(_ x: Int, _ y: Int, _ z: Int, from: [[[Bool]]]) -> Bool? {
        if x > 0, y > 0, z > 0, x < from.数, y < from.数, z < from.计数{
            let value = from[x][y][z]
            
            返回值
        }
        返回nil
        
    }

Building the Boxes

Now we will create the build function. 对于这个函数,它应该只运行一次,这样它就会触发isBuilt标志.

函数构建(){
        对于x in (0 ..< size) {
            var 飞机: [[CellOfLife]] = []
            对于y in (0 ..< size) {
                var 行: [CellOfLife] = []
                对于z in (0 ..< zSize) {
                    // Get if the 细胞 is alive
                    let isAlive = 生活[x][y][z]
                    // Get the width and height
                    let 节点Width = width / CGFloat(size)
                    let 节点Height = height / CGFloat(size)
                    // Create the basic 细胞
                    让细胞 = CellOfLife(isAlive: isAlive, 节点Width: 节点Width, 节点Height: 节点Height)
                    // Set the postion for the 细胞
                    细胞.位置 = SCNVector3((CGFloat(x) * 节点Width) - width / 2, (CGFloat(y) * 节点Height) - width / 2, CGFloat(z) * 节点Width)
                    // Calculate the distance from the center
                    let 节点1Pos = SCNVector3ToGLKVector3(细胞.位置)
                    let 节点2Pos = SCNVector3ToGLKVector3(SCNVector3(CGFloat(位置.x) + 节点Width / 2, CGFloat(位置.y) + 节点Height / 2, CGFloat(位置.z) + 节点Width / 2))
                    let distance = GLKVector3Distance(节点1Pos, 节点2Pos)
                    // Set the color of the 盒子
                    let color = UIColor(red: CGFloat(255 - (x * 10)) / 255.0, green: CGFloat(255 - (y * 10)) / 255.0, blue: CGFloat(255 - (z * 10)) / 255.0, alpha: CGFloat(1 - distance))
                    细胞.颜色=颜色
                    // Add the 细胞 to the 多维数据集 of 生活
                    addChildNode(细胞)
                    行.追加(细胞)
                }
                
                飞机.追加(行)
            }
            细胞sOfLife.追加(飞机)
        }
        // The 多维数据集 has been built
        isBuilt = true
        // Start the timer
        计时器.scheduled计时器(timeInterval: 0.target: 自我, selector: #selector(tick), userInfo: nil, repeats: true
    }

Updating the Cube

接下来,我们将创建一个简单的函数来遍历该多维数据集并更新所有单元格.

func update () {
        对于x in (0 ..< size) {
            对于y in (0 ..< size) {
                对于z in (0 ..< zSize) {
                    let 细胞 = 细胞sOfLife[x][y][z]
                    let isAlive = 生活[x][y][z]
                    细胞.isAlive = isAlive
                }
            }
        }
    }

Tick for the 计时器

最后,我们需要创建的最后一个函数是每次计时器触发时都会发生的tick方法. 对于这个方法,我们将遍历每个单元格并获得该单元格的所有邻居. We will then get the sum of the neighbors alive. 对于三维的生命游戏,我们的规则如下:

  • If an Alive Cell has 0 – 3 neighbors it will die
  • If an Alive Cell has 4 – 6 neighbors it will live
  • 如果一个死亡细胞有4个邻居,它将成为活的
  • If an Alive Cell has > 6 neighbors it will die
   @objc
    func蜱虫(){
        var 美国: [[[Bool]]] = []
        对于x in (0 ..< size) {
            var 飞机: [[Bool]] = []
            对于y in (0 ..< size) {
                var 行: [Bool] = []
                对于z in (0 ..< zSize) {
                    let neighbors: [Bool?] = [
                        / /底部
                        get(x-1, y-1, z-1),
                        get(x, y-1, z-1),
                        get(x, y, z-1),
                        get(x, y+1, z-1),
                        get(x+1, y+1, z-1),
                        get(x-1, y+1, z-1),
                        get(x+1, y-1, z-1),
                        get(x-1, y, z-1),
                        get(x+1, y, z-1),
                        / /边
                        get(x-1, y-1, z),
                        get(x, y-1, z),
                        get(x, y+1, z),
                        get(x+1, y+1, z),
                        get(x-1, y+1, z),
                        get(x+1, y-1, z),
                        get(x-1, y, z),
                        get(x+1, y, z),
                        / /顶部
                        get(x-1, y-1, z+1),
                        get(x, y-1, z+1),
                        get(x, y, z+1),
                        get(x, y+1, z+1),
                        get(x+1, y+1, z+1),
                        get(x-1, y+1, z+1),
                        get(x+1, y-1, z+1),
                        get(x-1, y, z+1),
                        get(x+1, y, z+1),
                        ]
                    
                    let neighborsSum = neighbors.compactMap { $0 }.地图{$ 0 ? 1 : 0 }.减少(0,+)
                    switch neighborsSum {
                    例0 ... 3:
                        行.追加(假)
                    例4 ... 6:
                        if let isAlive = get(x, y, z) {
                            如果isAlive {
                                行.追加(真正的)
                            其他}{
                                行.追加(neighborsSum == 4)
                            }
                        其他}{
                            行.追加(假)
                        }
                    默认值:
                        行.追加(假)
                    }
                }
                飞机.追加(行)
            }
            美国.追加(飞机)
        }
        生活=美国
        update ()
    }

创造生命!

First we will go into the ViewController.斯威夫特并更改viewDidLoad以加载一个空白场景. 我们还想将世界跟踪的飞机Detection设置为水平.

进口UIKit
进口SceneKit
进口ARKit

// Based of off: http://en.Wikipedia.org/wiki/Conway%27s_Game_of_Life

类ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView!
    private var isSpawned = false
    private var 多维数据集: CubeOfLife?
    
    override func viewDidLoad () {
        超级.viewDidLoad ()
        
        // Set the view's delegate
        sceneView.委托=自我
        
        //显示fps和定时信息等统计信息
        sceneView.showsStatistics = true
        
        // Set the scene to the view
        sceneView.scene = SCNScene()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        超级.viewWillAppear(animated)
        
        // Create a 会话 配置
        let 配置 = ARWorldTrackingConfiguration()
        
        配置.飞机Detection = .水平

        // Run the view's 会话
        sceneView.会话.run(配置)
    }
    
// ...
}

Renderer ARSCNViewDelegate

接下来我们需要使用ARSCNViewDelegate中的渲染方法. 为此,我们想看看传递的锚是否为ARPlaneAnchor,然后我们将构建创建CubeOfLife.

func renderer(_ renderer: SCNSceneRenderer, didAdd 节点: SCNNode, for anchor: ARAnchor) {
        if !isSpawned {
            if let 飞机Anchor = anchor as? ARPlaneAnchor {
                / /创建平面
                let 飞机Width = CGFloat(飞机Anchor.程度上.x)
                let 飞机Height = CGFloat(飞机Anchor.程度上.z)
                
                让平面= SCNPlane(宽度:飞机Width,高度:飞机Height)
                
                let 飞机Node = SCNNode()
                飞机Node.位置 = SCNVector3(飞机Anchor.center.x, 0, 飞机Anchor.center.z)
                飞机Node.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0)
                飞机.firstMaterial?.扩散.contents = UIColor.黑色的.withAlphaComponent(0.75)
                飞机Node.几何 = 飞机
                
                // Create Cube of Life
                立方体= CubeOfLife(n: 10,宽度:飞机Width / 2,高度:飞机Width / 2, nHeight: 10)
                多维数据集?.位置 = 飞机Node.位置
                
                飞机Node.addChildNode(多维数据集!)
                
                节点.addChildNode(飞机Node)
                isSpawned.切换()
            }
        }
    }

TouchesEnded

最后,我们将让用户触摸屏幕生成立方体. 一旦我们在增强现实中生成了平面,我们将覆盖touchesEnded函数来构建立方体.

override func touchesEnded(_ touches: Set, with event: UIEvent?) {
        超级.touchesEnded(touches, with: event)
        如果数据集?.isBuilt ?? 假{
            多维数据集?.蜱虫()
        其他}{
            多维数据集?.build ()
        }
    }

最终结果

一旦你完成了,你应该会得到以下结果:

如果你有任何麻烦或想看看源代码,它是张贴 GitHub

如果你想了解更多关于国际广播电台如何帮助你 IOS开发 请联系 我们的团队 or contact us at gig@korean-business-cards.com.

快速开发人员与激情将咖啡转化为代码. 目前正在使用MetalKit, ARKit和SceneKit进行Objective-C和Swift. Feel free to follow me on GitLab or join my open source agile development team oneleif.

联系