VR+全景播放器+头控讲解-01-知识储备
VR+全景播放器+头控讲解-02-创建球体
VR+全景播放器+头控讲解-03-渲染视频
VR+全景播放器+头控讲解-04-滑动手势
VR+全景播放器+头控讲解-05-伸缩画面
VR+全景播放器+头控讲解-06-头控实现
VR+全景播放器+头控讲解-07-分屏技术
学习目标
掌握头控部分布局
如何检测头控按钮被选中
如何实现悬停动画
在UIView上面布局我们可以使用UIButton UIView UIImageView等,但是是在3D场景中,我们不能使用UIView,我们要使用平面几何当视图使用,下面具体介绍一下
分析
提示几点
- 头控根节点可以放在根节点上中心或者球体中心都是可以的,但是考虑到后期我们要进行视频滤波,所以最好放到场景节点上
- 低头菜单出现,抬头菜单消失,这个要根据重力感应在X轴旋转决定的
- 当抬头时菜单总是出现在下方,一旦出现不会跟随照相机转动,当重力感应变化时,我们让头控节点的绕着y轴旋转
实现步骤
第一步.创建菜单节点
+(SCNNode*)createBackgroundNode{
/// 创建一个背景色
SCNNode* bgNode = [SCNNode node];
bgNode.position = SCNVector3Make(0, CONTROL_DISTANCE/2, -CONTROL_DISTANCE);
bgNode.geometry.firstMaterial.cullMode = SCNCullModeBack;
bgNode.rotation = SCNVector4Make(1, 0, 0, atan(1/2.0));
return bgNode;
}
+(SCNNode*)createShadowNode{
SCNNode *shadowNode = [SCNNode node];
shadowNode.geometry= [SCNPlane planeWithWidth:MENU_MAX_LENGTH height:BUTTON_WIDHT+BUTTON_WIDHT/2.0];
shadowNode.geometry.firstMaterial.diffuse.contents = BACKGROUND_MENU_SHOW_NAME;
shadowNode.position = SCNVector3Make(0, 0, 0);
shadowNode.geometry.firstMaterial.cullMode = SCNCullModeBack;
return shadowNode;
}
+(SCNNode*)createPlayNode{
SCNNode* playNode = [SCNNode node];
playNode.geometry = [SCNPlane planeWithWidth:BUTTON_WIDHT*0.8 height:BUTTON_WIDHT*0.8];
playNode.geometry.firstMaterial.diffuse.contents = ICON_PLAY_NAME;
playNode.position = SCNVector3Make(-20, 0, 2);
playNode.geometry.firstMaterial.cullMode = SCNCullModeBack;
return playNode;
}
+(SCNNode*)createPreviousNode{
/// 第六步-创建播放上一个视频的节点
SCNNode* previousNode = [SCNNode node];
previousNode.geometry = [SCNPlane planeWithWidth:BUTTON_WIDHT*0.8 height:BUTTON_WIDHT*0.8];
previousNode.geometry.firstMaterial.diffuse.contents = ICON_PREVIOUS_NAME;
previousNode.position = SCNVector3Make(-60, 0, 2);
previousNode.geometry.firstMaterial.cullMode = SCNCullModeBack;
return previousNode;
}
+(SCNNode*)createAfterNode{
SCNNode *nextNode = [SCNNode node];
nextNode.geometry = [SCNPlane planeWithWidth:BUTTON_WIDHT*0.8 height:BUTTON_WIDHT*0.8];
nextNode.geometry.firstMaterial.diffuse.contents = ICON_AFTER_NAME;
nextNode.position = SCNVector3Make(20, 0, 2);
nextNode.geometry.firstMaterial.cullMode = SCNCullModeBack;
return nextNode;
}
+(SCNNode*)createMoreNode{
SCNNode* moreNode = [SCNNode node];
moreNode.geometry = [SCNPlane planeWithWidth:BUTTON_WIDHT*0.8 height:BUTTON_WIDHT*0.8];
moreNode.geometry.firstMaterial.diffuse.contents = ICON_MENU_HIDDEN_NAME;
moreNode.position = SCNVector3Make(60, 0, 2);
moreNode.geometry.firstMaterial.cullMode = SCNCullModeBack;
return moreNode;
}
+(SCNNode*)createHighVoiceNode{
SCNNode* voiceHighNode = [SCNNode node];
voiceHighNode.geometry = [SCNPlane planeWithWidth:BUTTON_WIDHT*0.8 height:BUTTON_WIDHT*0.8];
voiceHighNode.geometry.firstMaterial.diffuse.contents = ICON_VOICE_HIGH_NAME;
voiceHighNode.geometry.firstMaterial.cullMode = SCNCullModeBack;
return voiceHighNode;
}
+(SCNNode*)createLowVoiceNode{
SCNNode* voiceLowNode = [SCNNode node];
voiceLowNode.geometry = [SCNPlane planeWithWidth:BUTTON_WIDHT*0.8 height:BUTTON_WIDHT*0.8];
voiceLowNode.geometry.firstMaterial.diffuse.contents = ICON_VOICE_LOW_NAME;
voiceLowNode.geometry.firstMaterial.cullMode = SCNCullModeBack;
return voiceLowNode;
}
创建的方法基本一致,调节一下位置即可!
第二步 添加到先把功能按钮节点添加到头控背景节点上,然后将背景节点添加到头控根节点上去
[self.scene.rootNode addChildNode:self.controlNode];
[self.controlNode addChildNode:self.bgNode];
[self.bgNode addChildNode:self.shadowNode];
[self.bgNode addChildNode:self.playOrPauseNode];
[self.bgNode addChildNode:self.proviousNode];
[self.bgNode addChildNode:self.AfterNode];
[self.bgNode addChildNode:self.showMoreNode];
[self.bgNode addChildNode:self.lowVoiceNode];
[self.bgNode addChildNode:self.highVoiceNode];
[self.scene.rootNode addChildNode:self.eyeNode];
第三步 添加点控节点,将其放在添加到照相机节点上去,这样照相机转动的时候,它就能跟着转动,效果就是一直在屏幕中央
[self.eyeNode addChildNode:self.dotNode];
第四步 抬头小时低头出现
-(void)controlEyeNodeInVR:(SCNVector3)vector{
// 向上抬头 并且local
if ( vector.x>0.8 &&self.bgNode.hidden==false){
self.bgNode.hidden = true;
self.dotNode.hidden = true;
}else if ( vector.x<0.8 &&self.bgNode.hidden){
self.bgNode.hidden = false;
self.dotNode.hidden = false;
SCNVector3 eulerAngles = self.controlNode.eulerAngles;
// eulerAngles.y = -vector.z;
eulerAngles.z = vector.z;
self.controlNode.eulerAngles = eulerAngles;
}
}
第五步 如何检测点控射线和头控按钮相交
思路:
先将按钮转换到照相机节点上,点控射线是否和按钮区域相交,就是相当于头控坐标 x在范围 [-button.width/2,button.width/2]内, y在 [-button.height/2.button.heigth/2.0]内
/// 下面就举一个例子,其它的都是类似的
SCNVector3 proviousPosition = [self.bgNode convertPosition:self.proviousNode.position toNode:self.self.eyeNode];
if (proviousPosition.x > -BUTTON_WIDHT/2 && proviousPosition.x < BUTTON_WIDHT/2 && proviousPosition.y > -BUTTON_WIDHT/2 && proviousPosition.y < BUTTON_WIDHT/2 ){
if (!self.controlDotIn.inPreviousNode&&!self.proviousNode.hidden){
// 第一次进入 还没有离开
// 开始执行悬停动画
[self startAnimation];
}
_controlDotIn.inPreviousNode = YES;
return;
}else if(_controlDotIn.inPreviousNode){
// 检测到离开按钮
return;
}
悬停动画的实现
第一步 创建动画行为
-(void)createAnimation{
/// 创建动画节点
self.animationNode = [SCNNode node];
self.animationNode.geometry = [SCNPlane planeWithWidth:CONTROL_DISTANCE/5.0 height:CONTROL_DISTANCE/5.0];
self.animationNode.hidden = true;
// 控制点上增加动画节点
[self.dotNode addChildNode:self.animationNode];
__weak XJRenderView* weakSelf = self;
self.animationAction = [SCNAction customActionWithDuration:3 actionBlock:^(SCNNode * _Nonnull node, CGFloat elapsedTime) {
int time = (int) (elapsedTime *(weakSelf.gif.count-1)/3.0);
node.geometry.firstMaterial.diffuse.contents = weakSelf.gif[time];
if(time == weakSelf.gif.count-1){
[weakSelf animationDidStop];
}
}];
}
-(NSArray *)gif{
if(!_gif){
NSMutableArray *gif= [NSMutableArray arrayWithCapacity:GIF_LENGTH];
for(int i = 0 ; i< GIF_LENGTH ;i++)
{
NSString *name = [NSString stringWithFormat:GIF_PREFIX_NAME_FORMAT,i];
[gif addObject:name];
}
_gif = gif;
}
return _gif;
}
第二步 在执行的节点上执行动画行为
-(void)startAnimation{
[self.animationNode runAction:self.animationAction];
}
本节讲解完毕
SceneKit 中文教程