第四个Demo,看了那么久无聊的,这次我想做点稍微有意思的。来做一个可以做动作的机器人吧。
RobotDemo
这个实例主要是对SceneKit中可视化场景设置和SCNAction进行了学习和使用。
先看效果:
很简单的一个例子,实现了机器人的手部运动,腿部运动,头部运动。移动实现了固定场景和固定任务两种方式。
一共就一百来行代码。接下来记录一下:
1、机器人建模
前文中说过,我没有也不知道去哪找开源的资源。所以所有例子都是用图形和颜色搭建。本例的机器人亦如此。
本例中,我不再和以前的Demo一样,为了理解Scene,把所有流程用代码梳理出来。而是直接通过storyboard+SceneKit Scene File,可视化地完成了所有的界面部分。
storyboard中找到SceneKitView,拖到我们的控制器上,像使用UIView一样给他设置大小位置或约束。摆放按钮就不说了。
接下来创建SceneKit Scene File文件,并在里面构造出我们的机器人。
没玩过3D的人对这个玩意儿开始确实不习惯,多玩玩,基本的操作还是挺简单的。
在右边的属性栏里可以设置位置。(也可以用拖的,但是设置更精确)
设置大小。这个根据你选择的几何不同而不同,比如圆的话是设置半径。
在Diffuse设置颜色。有贴图的话也可以在这设置。
创建子节点的方式则是直接把一个Node,拖到另一个Node里,类似往UIView里放另一个UIView。
还有灯光,相机等等,都可以直接往里面甩。然后根据可视化得进行设置和位置变换。
2、初始化场景
接下来该敲敲代码了。
- (void)viewDidLoad {
[super viewDidLoad];
SCNScene *scene = [SCNScene sceneNamed:@"art.scnassets/showcase.scn"];
self.scnView.scene = scene;
self.scnView.autoenablesDefaultLighting = YES;
self.robot = [scene.rootNode childNodeWithName:@"robot" recursively:NO];
self.camera = [scene.rootNode childNodeWithName:@"camera" recursively:NO];
}
初始化场景,设置默认光源等。这里我们也可以自己拖,但是这个Demo里,光源不是重点,所以从简了。
3、手部运动
这里运动我只实现了最基本的抬手和放下。
-(IBAction)leftHand {
SCNNode *leftHand = [_robot childNodeWithName:@"leftShoulder" recursively:NO];
[self raiseHand:leftHand];
}
-(IBAction)rightHand {
SCNNode *rightHand = [_robot childNodeWithName:@"rightShoulder" recursively:NO];
[self raiseHand:rightHand];
}
分别获取到双手,使用抬手动作
-(void)raiseHand:(SCNNode *)hand {
SCNAction *raiseAction = [SCNAction rotateToX:-2.5 y:0 z:0 duration:0.5];
SCNAction *putAction = [SCNAction rotateToX:0 y:0 z:0 duration:0.5];
SCNAction *sequenceAction = [SCNAction sequence:@[raiseAction,putAction]];
[hand runAction:sequenceAction];
}
首先实现了一个抬起的动作raiseAction
,这个动作要将手臂,沿-x轴,旋转2.5个单位。
复习一下,+ (SCNAction *)rotateToX:(CGFloat)xAngle y:(CGFloat)yAngle z:(CGFloat)zAngle duration:(NSTimeInterval)duration;
Creates an action that rotates the node to absolute angles in each of the three principal axes.
When the action executes, the node’s rotation property animates to the new angle. Calling this method is equivalent to calling rotateToX:y:z:duration:shortestUnitArc: and passing NO
for the shortestUnitArc
parameter.
This action is not reversible; the reverse of this action has the same duration but does not change anything.
将节点旋转到三个主轴上的绝对角度。这里稍微扩散一下,SCNAction中旋转的角度有好几种:这个方法旋转的是绝对角。但是在SceneKit Scene File中对应的旋转方法,旋转的又是欧拉角。这两者不一样,所以不要疑惑为什么明明设置的一样的值为什么动作不一样。
言归正传。SceneKit的坐标系:
我们想做一个从前向上的举手动作,就是要求手沿x轴转动。
我是这么理解这个旋转角度的:从正轴往负轴的方向。当value<0,为逆时针旋转,value>0时,为顺时针旋转。所以,我们可以推断出举手的动作,x<0,y=0,z=0,如果感觉一时没got到也没关系,多试几次就知道了。所以在这里,我们设置举手
SCNAction *raiseAction = [SCNAction rotateToX:-2.5 y:0 z:0 duration:0.5];
同理手放下
SCNAction *putAction = [SCNAction rotateToX:0 y:0 z:0 duration:0.5];
然后把两个动作串联起来
SCNAction *sequenceAction = [SCNAction sequence:@[raiseAction,putAction]];
就是完整的一次举手。接下来就执行就可以了
[hand runAction:sequenceAction];
4、头部运动
同理我们来设置点头和摇头的动作。
点头:
-(IBAction)nod {
SCNNode *head = [_robot childNodeWithName:@"head" recursively:NO];
SCNAction *raiseAction = [SCNAction rotateToX:0.3 y:0 z:0 duration:0.3];
SCNAction *putAction = [SCNAction rotateToX:0 y:0 z:0 duration:0.3];
SCNAction *sequenceAction = [SCNAction sequence:@[raiseAction,putAction]];
SCNAction *repeatAction = [SCNAction repeatAction:sequenceAction count:2];
repeatAction.timingMode = SCNActionTimingModeEaseInEaseOut;
[head runAction:repeatAction];
}
这里新用到了repeatAction
,顾名思义就是重复动作。
timingMode
,是动作执行的一个速度枚举。有四种类型
/*! @enum SCNActionTimingMode
@abstract The modes that an action can use to adjust the apparent timing of the action.
*/
typedef NS_ENUM(NSInteger, SCNActionTimingMode) {
SCNActionTimingModeLinear,//匀速
SCNActionTimingModeEaseIn,//一开始慢,慢慢加快
SCNActionTimingModeEaseOut,//一开始快,逐渐变慢
SCNActionTimingModeEaseInEaseOut//开始慢慢地,通过中间的时候加速,然后再次放缓
} API_AVAILABLE(macos(10.10), ios(8.0));
摇头:
-(IBAction)shakeHead {
SCNNode *head = [_robot childNodeWithName:@"head" recursively:NO];
SCNAction *leftAction = [SCNAction rotateToX:0 y:-0.3 z:0 duration:0.5];
SCNAction *rightAction = [SCNAction rotateToX:0 y:0.3 z:0 duration:0.5];
SCNAction *sequenceAction = [SCNAction sequence:@[leftAction,rightAction]];
SCNAction *repeatAction = [SCNAction repeatAction:sequenceAction count:2];
SCNAction *centerAction = [SCNAction rotateToX:0 y:0 z:0 duration:0.5];
SCNAction *sequenceAction2 = [SCNAction sequence:@[repeatAction,centerAction]];
sequenceAction2.timingMode = SCNActionTimingModeEaseInEaseOut;
[head runAction:sequenceAction2];
}
这里主要是尝试了各种Action的组合,先左右摇两次,再还原。
4、腿部运动
腿部运动分为两个部分,一个是腿部位置变换,一个是机器人位置变换。
腿部不多说了,和上文基本相同。
-(void)takeStep:(SCNNode *)foot{
SCNAction *raiseAction = [SCNAction rotateToX:0.5 y:0 z:0 duration:0.5];
SCNAction *putAction = [SCNAction rotateToX:0 y:0 z:0 duration:0.5];
SCNAction *sequenceAction = [SCNAction sequence:@[raiseAction,putAction]];
SCNAction *repeatAction = [SCNAction repeatActionForever:sequenceAction];
[foot runAction:repeatAction];
}
接下来说机器人位置变换,就是移动。
-(void)move:(SCNNode *)node {
SCNAction *moveAction = [SCNAction moveByX:0 y:0 z:1 duration:0.5];
SCNAction *repeatAction = [SCNAction repeatActionForever:moveAction];
[node runAction:repeatAction];
}
就是另外一个SCNAction,朝着+z轴方向,永远的重复前进。
但是有一个问题,机器人走着走着就走出了镜头。
怎么像第一人称的游戏一样,让人物始终在视角的中心呢?设置除人物外的所有节点朝相反方向移动吗?
并不用,我们让摄像头跟着机器人一起移动就可以了。
机器人移动:
[self move:_robot];
镜头移动:
[self move:_camera];
(-move:
方法在上文)
想停止的时候移除动作即可。
[_robot removeAllActions];
[_camera removeAllActions];
顺便,立正
-(void)standUp:(SCNNode *)foot {
[foot removeAllActions];
SCNAction *standAction = [SCNAction rotateToX:0 y:0 z:0 duration:0.5];
[foot runAction:standAction];
}
以上,全部完成。
记得比较啰嗦,因为本人也在学习阶段,Demo实际上也就100来行。如有不正确的地方,感谢斧正。