SceneKit自学之路(终)

有阵子没玩这个了。

其实这玩意儿学了我工作也用不上,之前本来想学来玩玩ARKit,结果手机太旧了不支持(手动捂脸)。本着有始有终,花了两天时间留下最后一个相对完整的Demo,SceneKit的学习也暂告一段落。如果有什么不懂的,也可以留言讨论。
LittlerJumper:下载地址:https://github.com/anjohnlv/LittleJumper


我把它称为粗暴版跳一跳,Demo很粗暴也很简陋。

为了方便初学者学习SceneKit,整个Demo,SceneKit内容纯代码完成。且没有任何框架设计,所有的代码都在ViewController中,加上注释一共接近400行。

注释还是很详细了。应该都能看懂并理解。

游戏截图:


Little Jumper

简单总结一下:

  • 创建工程。
    和之前的Demo一样,我直接新建的Single View App。原因就不多重复。


    Single View App
  • 初始化场景。
    SceneKit中所有的物体、行为都要在SCNScene中,而SCNScene需要在SCNView中。
    在Demo中,包括SCNViewSCNScenefloorcameralight等,这些一开始就要准备好的元素。我为了体现游戏的操作过程,把这些初始化都放在了自己身上懒加载。
    Demo里的注释很详细,我挑一些说
    想要目标始终在视线范围内,我们得在“小人”跳走后让镜头跟随。可是如果一直让镜头跟随小人,会让整个游戏看起来特别晃。所以我让相机跟随站台,每成功跳一次,将相机移动观察新站台。

-(void)moveCameraToCurrentPlatform {
    SCNVector3 position = self.platform.presentationNode.position;
    position.x += 20;
    position.y += 30;
    position.z += 20;
    SCNAction *move = [SCNAction moveTo:position duration:0.5];
    [self.camera runAction:move];
    [self createNextPlatform];
}

x,y,z值是目测的,由于只是个Demo,所以细节方面没有太讲究。
如果这是这么写,你会发现,跳着跳着就看不见了。原因是你的光源始终照在远处,离光源远了,自然就看不到了。在这里,我是直接让光源始终跟随镜头。实现方法是在初始化相机之后,直接将光源设为相机的子节点。

-(SCNNode *)camera {
    if (!_camera) {
        _camera = ({
            SCNNode *node = [SCNNode node];
            node.camera = [SCNCamera camera];
            node.camera.zFar = 200.f;
            node.camera.zNear = .1f;
            [self.scene.rootNode addChildNode:node];
            node.eulerAngles = SCNVector3Make(-0.7, 0.6, 0);
            node;
        });
        [_camera addChildNode:self.light];
    }
    return _camera;
}
  • 事件
    这个Demo其实很简单。整个游戏过程梳理下来:

初始化->点击屏幕蓄力->释放跳跃->判断成功->移动相机->生成下一个跳台->下一次跳跃->判断失败->游戏结束

蓄力的过程用到了长按手势,对,就和写App里的长按一样。SCNView是基于UIView的,可以直接将手势加在上面。设置longPressGesture.minimumPressDuration = 0;保证短按也能监听到。这里有一个知识点是自定义SCNAction的使用。

-(void)updateStrengthStatus {
    SCNAction *action = [SCNAction customActionWithDuration:kMaxPressDuration actionBlock:^(SCNNode * node, CGFloat elapsedTime) {
        CGFloat percentage = elapsedTime/kMaxPressDuration;
        self.jumper.geometry.firstMaterial.diffuse.contents = [UIColor colorWithRed:1 green:1-percentage blue:1-percentage alpha:1];
    }];
    [self.jumper runAction:action];
}

很简单地实现了颜色的渐变动画。力量越大,颜色越红。在释放跳跃的瞬间,取消Action即可。

[self.jumper removeAllActions];

跳跃的过程得先提后面生成新跳台。Demo里新跳台的生成,是范围内随机大小,随机颜色、随机方向、随机距离。所以跳跃的时候,需要判断“小人”和目标跳台的方向。我们要保证方向向量上单位力量为恒定的,这样当通过时间来增加力量时才有意义。
这个是数学知识,已知三角形斜边长度和两边直角边比,求直角边长度。这个不多说。

移动相机上文已经提到就不再复述,接下来着重说明一下自己生成下一个站台的方法:

-(void)createNextPlatform {
    self.nextPlatform = ({
        SCNNode *node = [SCNNode node];
        node.geometry = ({
            //随机大小
            int radius = (arc4random() % kMinPlatformRadius) + (kMaxPlatformRadius-kMinPlatformRadius);
            SCNCylinder *cylinder = [SCNCylinder cylinderWithRadius:radius height:2];
            //随机颜色
            cylinder.firstMaterial.diffuse.contents = ({
                CGFloat r = ((arc4random() % 255)+0.0)/255;
                CGFloat g = ((arc4random() % 255)+0.0)/255;
                CGFloat b = ((arc4random() % 255)+0.0)/255;
                UIColor *color = [UIColor colorWithRed:r green:g blue:b alpha:1];
                color;
            });
            cylinder;
        });
        node.physicsBody = ({
            SCNPhysicsBody *body = [SCNPhysicsBody dynamicBody];
            body.restitution = 1;
            body.friction = 1;
            body.damping = 0;
            body.allowsResting = YES;
            body.categoryBitMask = CollisionDetectionMaskPlatform;
            body.collisionBitMask = CollisionDetectionMaskJumper|CollisionDetectionMaskFloor|CollisionDetectionMaskOldPlatform|CollisionDetectionMaskPlatform;
            body.contactTestBitMask = CollisionDetectionMaskJumper;
            body;
        });
        //随机位置
        node.position = ({
            SCNVector3 position = self.platform.presentationNode.position;
            int xDistance = (arc4random() % (kMaxPlatformRadius*3-1))+1;
            position.z -= ({
                double lastRadius = ((SCNCylinder *)self.platform.geometry).radius;
                double radius = ((SCNCylinder *)node.geometry).radius;
                double maxDistance = sqrt(pow(kMaxPlatformRadius*3, 2)-pow(xDistance, 2));
                double minDistance = (xDistance>lastRadius+radius)?xDistance:sqrt(pow(lastRadius+radius, 2)-pow(xDistance, 2));
                double zDistance = (((double) rand() / RAND_MAX) * (maxDistance-minDistance)) + minDistance;
                zDistance;
            });
            position.x -= xDistance;
            position.y += 5;
            position;
        });
        [self.scene.rootNode addChildNode:node];
        node;
    });
}

为了直观地看出每一步做了什么,Demo里我尽量采用语法糖来包裹所有节点。
如上文所说,新站台生成,是范围内的随即大小、随机颜色、随机方向、随机距离。随机大小和随机颜色很好理解。随机的方向和随机的距离的实现,是在x-z平面,首先在范围内,随机一个x坐标,然后根据最大距离和两元相切的最小距离,计算了一个z坐标的区间,再取z的随机坐标。以此达到随机方向和随机距离的效果。

Demo中得跳跃、碰撞等效果,均是使用的模拟的物理效果。使用很简单,说起来又是很多知识点。想了解更多可以点击这里
在Demo我分别监听了小人与站台,以及小人和地板的碰撞。

- (void)physicsWorld:(SCNPhysicsWorld *)world didBeginContact:(SCNPhysicsContact *)contact{
    SCNPhysicsBody *bodyA = contact.nodeA.physicsBody;
    SCNPhysicsBody *bodyB = contact.nodeB.physicsBody;
    if (bodyA.categoryBitMask==CollisionDetectionMaskJumper) {
        if (bodyB.categoryBitMask==CollisionDetectionMaskFloor) {
            bodyB.contactTestBitMask = CollisionDetectionMaskNone;
            [self performSelectorOnMainThread:@selector(gameDidOver) withObject:nil waitUntilDone:NO];
        }else if (bodyB.categoryBitMask==CollisionDetectionMaskPlatform) {
            //这里有个小bug,我在第一次收到碰撞后进行如下配置,按理说不应该收到碰撞回调了。可实际上还是会来。于是我直接将跳过的台子的categoryBitMask改为CollisionDetectionMaskOldPlatform,保证每个台子只会收到一次。上面的掉落又没有这个bug。
            //bodyB.contactTestBitMask = CollisionDetectionMaskNone;
            bodyB.categoryBitMask = CollisionDetectionMaskOldPlatform;
            [self jumpCompleted];
        }
    }
}

判断小人与地板碰撞,则游戏结束。
小人与新站台碰撞,则移动相机并生成下一个站台。
这里要注意的是,需要判断识别第一次碰撞。

最后,游戏结束。弹出的界面是UIView实现的。SceneKit就是一个framework,可以和其他UIKit之类的完全无缝衔接。

以上,加注释400行代码,粗暴版跳一跳完成。收工!


我觉得学习一门语言,主要是学习他的框架、他的流程,精益求精者会去关注他的实现原理。而学习Api,只是表面工作。其实在写这个Demo的时候,还遇到了一些未解的迷之bug。比如注释里提到的contactTestBitMask取消了仍然会收到通知,比如加大重力后出现的无法平静的小人等等。

随意啦随意啦。反正SceneKit告一段落,撒花。
有什么bug的话欢迎斧正。有什么疑问的话也欢迎留言讨论。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容

  • 目前网上关于SceneKit的教程还是略少。对于完全没有任何游戏开发经验的人(我)来说还是有一定挑战。记下此篇,加...
    anjohnlv阅读 3,009评论 1 14
  • SceneKit 是一个OC 框架,开始之前,先熟悉一下SceneKit 的三维坐标系: 很清楚的看到,Scene...
    淘气CC阅读 5,578评论 0 5
  • 在你可以称为「青春」的时间里,哪件事让你感到幸运? 一个人,一件事,或者看到了一本书,有没有哪件事哪个人在你称为青...
    达达令阅读 502评论 0 1
  • 一、将问题员工转化为正能量员工的工具,要不要? 1.1当员工叙述问题之后,你就说“我听懂了,我明白了”,你问您一个...
    669c5bc6dd72阅读 562评论 0 0
  • 朋友講起佢隻貓識得屙喺屋企廁所嘅地板,唔駛用貓砂兜同貓砂,一屙就沖落水渠,得咗。聽完我望一望屎霸同奧利奧,諗起馬雲...
    海苦麻阅读 291评论 0 0