WWDC22学生奖学金获奖作品分析- PlaygroundAppAR

本次分析的获奖作品名叫PlayfroundApp,获奖者GitHub名为Oscar Fridh,是一位三次获得WWDC Swift Student Challenge(2019,2021,2022)。下面将会通过创新性和代码可行性来对这个作品进行分析。

创新性分析

这个作品是通过展示奥斯卡和杰西卡两个人不同的视角,其中奥斯卡具有正常视力,而杰西卡则患有近视,以探索和比较两种视角下的世界。用户可以帮助杰西卡选择合适的眼镜,使其能够像奥斯卡一样清晰地看到周围的世界。作者希望通过这个作品能够帮助人们更好地理解和认识不同人群之间的差异,包括视力这一方面,从而在世界上产生更多的同情和善意。
这个作品的出发点和其内核是十分的好的。而且整个作品的流畅和故事性安排得也十分合理。下面我们来欣赏一下这个作品吧!

image
image
image
image
image

视频展示



(由于视频大小有限所以压缩后观感并不是很好,感兴趣的可以自行去下面的GitHub中下载该作品体验。)
从视频中我们可以看见通过滑动滚动条来改变我们看见的清晰度。从而了解近视和不近视的视角。第二关则是通过阐述近视的原理,我们需要通过滑动滚动条将视力调到正常范围;最后一关则是帮杰西卡挑选适合他的眼镜。选择完后整个游戏介绍。
爱和善意是这个世界上不可或缺的东西,我们提倡和传播它们。这个作品的出发点是十分棒的!
科技的出现是为了让世界变得更好而不是更糟糕。

代码可行性(主要代码分析)

通过视频我们可以了解到整个游戏大部分的场景都是在AR下进行的。可以用AR Kit和Reality Kit这两个框架来完成。
使用RealityKit框架实现高性能3D仿真和渲染。RealityKit利用ARKit框架提供的信息,将虚拟对象无缝地集成到现实世界中。RealityKit更注重于AR之间的交互。

在Reality Kit中我们可以发现所有的组件都可以看作一个Entity,这里我们可以类比为SCNNode。它承载着我们的物体,我们的交互大部分都是通过它来实现。
同时我们也可以自定义自己的Entity类型如下图所示:

下面我们通过一个简单的AR交互Demo来了解一下RealityKit的基本使用吧! :

视频展示

主要代码展示

import SwiftUI
import RealityKit
import Combine
import ARKit

//MARK: -翻牌游戏(没有加配对功能)
struct ARGameView: View {
    var body: some View {
        GameARView()
    }
}


struct GameARView: UIViewRepresentable {
    func makeUIView(context: Context) -> ARView {
        
        
        //创建指引
        let arView = ARView(frame: .zero, cameraMode: .ar, automaticallyConfigureSession: true)
        //创建识别的锚点,有平面,Image等
        let arAnchor = AnchorEntity(plane: .horizontal)
        arView.scene.addAnchor(arAnchor)
        
        //卡片的数组
        var cards: [Entity] = []
        
        var cancellable: AnyCancellable? = nil
        //方块的大小
        let boxSize: Float = 0.7
        let occlusionBoxMesh = MeshResource.generateBox(size: boxSize)
        //创建贴图(这里和SceneKit 的贴图方法类似)
        let occlusionBox = ModelEntity(mesh: occlusionBoxMesh, materials: [OcclusionMaterial()])
        
        for _ in 1...16 {
            //mesh形状
            let box = MeshResource.generateBox(width: 0.04, height: 0.02, depth: 0.04)
            //贴图颜色
            let metalMatirial = SimpleMaterial(color: .gray, isMetallic: true)
            
            //装载模型的容器
            let model = ModelEntity(mesh: box, materials: [metalMatirial])
            
            //将模型添加到数组里面
            model.generateCollisionShapes(recursive: true)
            cards.append(model)
        }
        
        //通过遍历数组中的元素位置和元素进行卡片位置的设置
        for (index, card) in cards.enumerated() {
            //print("index:\(index),cards:\(cards)")
            let x = Float(index % 4)
            let z = Float(index / 4)
            
            card.position = [x * 0.1, 0, z * 0.1]
            arAnchor.addChild(card)
        }
        
        //隐藏Model,翻转显示
        occlusionBox.position.y = -boxSize/2
        arAnchor.addChild(occlusionBox)
        
        //Entity中添加Model 将模型添加到原来的卡片的上面
        cancellable =  ModelEntity.loadModelAsync(named: "01")
            .append(ModelEntity.loadModelAsync(named: "02"))
            .append(ModelEntity.loadModelAsync(named: "03"))
            .append(ModelEntity.loadModelAsync(named: "04"))
            .append(ModelEntity.loadModelAsync(named: "05"))
            .append(ModelEntity.loadModelAsync(named: "06"))
            .append(ModelEntity.loadModelAsync(named: "07"))
            .append(ModelEntity.loadModelAsync(named: "08"))
            .collect()
            .sink(receiveCompletion: { error in
                print("Error: \(error)")
                cancellable?.cancel()
            }, receiveValue: { entities in
                var objects: [ModelEntity] = []
                for entity in entities {
                    entity.setScale(SIMD3<Float>(0.002, 0.002, 0.002), relativeTo: arAnchor)
                    entity.generateCollisionShapes(recursive: true)
                    for _ in 1...2 {
                        objects.append(entity.clone(recursive: true))
                    }
                }
                objects.shuffle()
                for (index, object) in objects.enumerated() {
                    cards[index].addChild(object)
                    cards[index].transform.rotation = simd_quatf(angle: .pi, axis: [1, 0, 0])
                }
                
                cancellable?.cancel()
            })
        //添加点击事件
        arView.enableTapGesture()
        return arView
        
    }
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
}

extension ARView { 
    
    func enableTapGesture() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(onTap))
        
        self.addGestureRecognizer(tapGestureRecognizer)
    }
    
    //创建点击事件控制卡片的翻转
    @objc func onTap(sender: UITapGestureRecognizer) {
        let tapLocation = sender.location(in: self )
        //发射点击
        if let card = self.entity(at: tapLocation) {
            if card.transform.rotation.angle == .pi {
                var filpDownTransform = card.transform
                filpDownTransform.rotation = simd_quatf(angle: 0, axis: [1, 0, 0])
                
                card.move(to: filpDownTransform, relativeTo: card.parent, duration: 0.25, timingFunction: .easeInOut)
            } else {
                var flipUpTransform = card.transform
                flipUpTransform.rotation = simd_quatf(angle: .pi, axis: [1, 0, 0])
                card.move(to: flipUpTransform, relativeTo: card.parent, duration: 0.25, timingFunction: .easeInOut)
            }
        }
        
    }
    
    
    
    
}


struct ARGameView_Previews: PreviewProvider {
    static var previews: some View {
        ARGameView()
    }
}

本次介绍获奖者GitHub地址:OscarFridh (Oscar Fridh) · GitHub
本次WWDC作品下载地址:GitHub - OscarFridh/WWDC22: My submission for the Swift Student Challenge 2022
本次Demo下载地址:Swift 学习小Demo: 学习Swift时开发的一些小demo - Gitee.com
(Demo名为AR翻牌游戏)

本次参考资料1:https://www.youtube.com/watch?v=mV7SWeMDEfg
本次参考资料2: RealityKit | Apple Developer Documentation

3 个赞