ARKit從入門到精通(10)-ARKit讓飛機繞著你飛起來

  • 廢話不多說,先看效果
    • 由于是晚上,筆者選擇的是一個臺燈
      • 其實是會一直圍著你轉(zhuǎn)圈的,只不過筆者不好意思暴露家里的場景,所以請讀者朋友們見諒~
1101.gif

<h2 id="1.1">1.1-ARKit物體圍繞相機旋轉(zhuǎn)流程介紹</h2>

  • 1.點擊屏幕添加物體,已經(jīng)在第三小節(jié)ARKit從入門到精通(3)-ARKit自定義實現(xiàn)中介紹

  • 2.實現(xiàn)物體的圍繞相機旋轉(zhuǎn)(這里主要會用到SceneKit框架中內(nèi)容)

    • 注意:繞相機旋轉(zhuǎn)的關鍵點在于:在相機的位置創(chuàng)建一個空節(jié)點,然后將臺燈添加到這個空節(jié)點,最后讓這個空節(jié)點自身旋轉(zhuǎn),就可以實現(xiàn)臺燈圍繞相機旋轉(zhuǎn)
      • 1.為什么要在相機的位置創(chuàng)建一個空節(jié)點呢?因為你不可能讓相機也旋轉(zhuǎn)
      • 2.為什么不直接讓臺燈旋轉(zhuǎn)呢? 這樣的話只能實現(xiàn)臺燈的自轉(zhuǎn),而不能實現(xiàn)公轉(zhuǎn)
  • 核心代碼介紹

#pragma mark- 點擊屏幕添加飛機
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.planeNode removeFromParentNode];
    
    //1.使用場景加載scn文件(scn格式文件是一個基于3D建模的文件,使用3DMax軟件可以創(chuàng)建,這里系統(tǒng)有一個默認的3D飛機)--------在右側(cè)我添加了許多3D模型,只需要替換文件名即可
    SCNScene *scene = [SCNScene sceneNamed:@"Models.scnassets/lamp/lamp.scn"];
    //2.獲取臺燈節(jié)點(一個場景會有多個節(jié)點,此處我們只寫,飛機節(jié)點則默認是場景子節(jié)點的第一個)
    //所有的場景有且只有一個根節(jié)點,其他所有節(jié)點都是根節(jié)點的子節(jié)點
    
    SCNNode *shipNode = scene.rootNode.childNodes[0];
    
    self.planeNode = shipNode;
    
    //臺燈比較大,適當縮放一下并且調(diào)整位置讓其在屏幕中間
    shipNode.scale = SCNVector3Make(0.5, 0.5, 0.5);
    shipNode.position = SCNVector3Make(0, -15,-15);
    ;
    //一個臺燈的3D建模不是一氣呵成的,可能會有很多個子節(jié)點拼接,所以里面的子節(jié)點也要一起改,否則上面的修改會無效
    for (SCNNode *node in shipNode.childNodes) {
        node.scale = SCNVector3Make(0.5, 0.5, 0.5);
        node.position = SCNVector3Make(0, -15,-15);
        
    }
    
    
    self.planeNode.position = SCNVector3Make(0, 0, -20);
    
    //3.繞相機旋轉(zhuǎn)
    //繞相機旋轉(zhuǎn)的關鍵點在于:在相機的位置創(chuàng)建一個空節(jié)點,然后將臺燈添加到這個空節(jié)點,最后讓這個空節(jié)點自身旋轉(zhuǎn),就可以實現(xiàn)臺燈圍繞相機旋轉(zhuǎn)
    //1.為什么要在相機的位置創(chuàng)建一個空節(jié)點呢?因為你不可能讓相機也旋轉(zhuǎn)
    //2.為什么不直接讓臺燈旋轉(zhuǎn)呢? 這樣的話只能實現(xiàn)臺燈的自轉(zhuǎn),而不能實現(xiàn)公轉(zhuǎn)
    SCNNode *node1 = [[SCNNode alloc] init];
    
    //空節(jié)點位置與相機節(jié)點位置一致
    node1.position = self.arSCNView.scene.rootNode.position;
    
    //將空節(jié)點添加到相機的根節(jié)點
    [self.arSCNView.scene.rootNode addChildNode:node1];
    
    
    // !!!將臺燈節(jié)點作為空節(jié)點的子節(jié)點,如果不這樣,那么你將看到的是臺燈自己在轉(zhuǎn),而不是圍著你轉(zhuǎn)
    [node1 addChildNode:self.planeNode];
    
    
    //旋轉(zhuǎn)核心動畫
    CABasicAnimation *moonRotationAnimation = [CABasicAnimation animationWithKeyPath:@"rotation"];
    
    //旋轉(zhuǎn)周期
    moonRotationAnimation.duration = 30;
    
    //圍繞Y軸旋轉(zhuǎn)360度  (不明白ARKit坐標系的可以看筆者之前的文章)
    moonRotationAnimation.toValue = [NSValue valueWithSCNVector4:SCNVector4Make(0, 1, 0, M_PI * 2)];
    //無限旋轉(zhuǎn)  重復次數(shù)為無窮大
    moonRotationAnimation.repeatCount = FLT_MAX;
    
    //開始旋轉(zhuǎn)  ?。。。呵杏涍@里是讓空節(jié)點旋轉(zhuǎn),而不是臺燈節(jié)點。  理由同上
    [node1 addAnimation:moonRotationAnimation forKey:@"moon rotation around earth"];
    
    
    
}

<h2 id="1.2">1.2-完整代碼</h2>


#import "ARSCNViewViewController.h"

//3D游戲框架
#import <SceneKit/SceneKit.h>
//ARKit框架
#import <ARKit/ARKit.h>

@interface ARSCNViewViewController ()<ARSCNViewDelegate,ARSessionDelegate>

//AR視圖:展示3D界面
@property(nonatomic,strong)ARSCNView *arSCNView;

//AR會話,負責管理相機追蹤配置及3D相機坐標
@property(nonatomic,strong)ARSession *arSession;

//會話追蹤配置:負責追蹤相機的運動
@property(nonatomic,strong)ARSessionConfiguration *arSessionConfiguration;

//飛機3D模型(本小節(jié)加載多個模型)
@property(nonatomic,strong)SCNNode *planeNode;

@end

@implementation ARSCNViewViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    
    // Do any additional setup after loading the view.
}

- (void)back:(UIButton *)btn
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    //1.將AR視圖添加到當前視圖
    [self.view addSubview:self.arSCNView];
    //2.開啟AR會話(此時相機開始工作)
    [self.arSession runWithConfiguration:self.arSessionConfiguration];
    
    
    //添加返回按鈕
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    [btn setTitle:@"返回" forState:UIControlStateNormal];
    btn.frame = CGRectMake(self.view.bounds.size.width/2-50, self.view.bounds.size.height-100, 100, 50);
    btn.backgroundColor = [UIColor greenColor];
    [btn addTarget:self action:@selector(back:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
    
}

#pragma mark- 點擊屏幕添加飛機
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.planeNode removeFromParentNode];
    
    //1.使用場景加載scn文件(scn格式文件是一個基于3D建模的文件,使用3DMax軟件可以創(chuàng)建,這里系統(tǒng)有一個默認的3D飛機)--------在右側(cè)我添加了許多3D模型,只需要替換文件名即可
    SCNScene *scene = [SCNScene sceneNamed:@"Models.scnassets/lamp/lamp.scn"];
    //2.獲取臺燈節(jié)點(一個場景會有多個節(jié)點,此處我們只寫,飛機節(jié)點則默認是場景子節(jié)點的第一個)
    //所有的場景有且只有一個根節(jié)點,其他所有節(jié)點都是根節(jié)點的子節(jié)點
    
    SCNNode *shipNode = scene.rootNode.childNodes[0];
    
    self.planeNode = shipNode;
    
    //臺燈比較大,適當縮放一下并且調(diào)整位置讓其在屏幕中間
    shipNode.scale = SCNVector3Make(0.5, 0.5, 0.5);
    shipNode.position = SCNVector3Make(0, -15,-15);
    ;
    //一個臺燈的3D建模不是一氣呵成的,可能會有很多個子節(jié)點拼接,所以里面的子節(jié)點也要一起改,否則上面的修改會無效
    for (SCNNode *node in shipNode.childNodes) {
        node.scale = SCNVector3Make(0.5, 0.5, 0.5);
        node.position = SCNVector3Make(0, -15,-15);
        
    }
    
    
    self.planeNode.position = SCNVector3Make(0, 0, -20);
    
    //3.繞相機旋轉(zhuǎn)
    //繞相機旋轉(zhuǎn)的關鍵點在于:在相機的位置創(chuàng)建一個空節(jié)點,然后將臺燈添加到這個空節(jié)點,最后讓這個空節(jié)點自身旋轉(zhuǎn),就可以實現(xiàn)臺燈圍繞相機旋轉(zhuǎn)
    //1.為什么要在相機的位置創(chuàng)建一個空節(jié)點呢?因為你不可能讓相機也旋轉(zhuǎn)
    //2.為什么不直接讓臺燈旋轉(zhuǎn)呢? 這樣的話只能實現(xiàn)臺燈的自轉(zhuǎn),而不能實現(xiàn)公轉(zhuǎn)
    SCNNode *node1 = [[SCNNode alloc] init];
    
    //空節(jié)點位置與相機節(jié)點位置一致
    node1.position = self.arSCNView.scene.rootNode.position;
    
    //將空節(jié)點添加到相機的根節(jié)點
    [self.arSCNView.scene.rootNode addChildNode:node1];
    
    
    // !!!將臺燈節(jié)點作為空節(jié)點的子節(jié)點,如果不這樣,那么你將看到的是臺燈自己在轉(zhuǎn),而不是圍著你轉(zhuǎn)
    [node1 addChildNode:self.planeNode];
    
    
    //旋轉(zhuǎn)核心動畫
    CABasicAnimation *moonRotationAnimation = [CABasicAnimation animationWithKeyPath:@"rotation"];
    
    //旋轉(zhuǎn)周期
    moonRotationAnimation.duration = 30;
    
    //圍繞Y軸旋轉(zhuǎn)360度  (不明白ARKit坐標系的可以看筆者之前的文章)
    moonRotationAnimation.toValue = [NSValue valueWithSCNVector4:SCNVector4Make(0, 1, 0, M_PI * 2)];
    //無限旋轉(zhuǎn)  重復次數(shù)為無窮大
    moonRotationAnimation.repeatCount = FLT_MAX;
    
    //開始旋轉(zhuǎn)  ?。。。呵杏涍@里是讓空節(jié)點旋轉(zhuǎn),而不是臺燈節(jié)點。  理由同上
    [node1 addAnimation:moonRotationAnimation forKey:@"moon rotation around earth"];
    
    
    
}

#pragma mark -搭建ARKit環(huán)境


//懶加載會話追蹤配置
- (ARSessionConfiguration *)arSessionConfiguration
{
    if (_arSessionConfiguration != nil) {
        return _arSessionConfiguration;
    }
    
    //1.創(chuàng)建世界追蹤會話配置(使用ARWorldTrackingSessionConfiguration效果更加好),需要A9芯片支持
    ARWorldTrackingSessionConfiguration *configuration = [[ARWorldTrackingSessionConfiguration alloc] init];
    //2.設置追蹤方向(追蹤平面,后面會用到)
    configuration.planeDetection = ARPlaneDetectionHorizontal;
    _arSessionConfiguration = configuration;
    //3.自適應燈光(相機從暗到強光快速過渡效果會平緩一些)
    _arSessionConfiguration.lightEstimationEnabled = YES;
    
    return _arSessionConfiguration;
    
}

//懶加載拍攝會話
- (ARSession *)arSession
{
    if(_arSession != nil)
    {
        return _arSession;
    }
    //1.創(chuàng)建會話
    _arSession = [[ARSession alloc] init];
    _arSession.delegate = self;
    //2返回會話
    return _arSession;
}

//創(chuàng)建AR視圖
- (ARSCNView *)arSCNView
{
    if (_arSCNView != nil) {
        return _arSCNView;
    }
    //1.創(chuàng)建AR視圖
    _arSCNView = [[ARSCNView alloc] initWithFrame:self.view.bounds];
    
    //2.設置代理  捕捉到平地會在代理回調(diào)中返回
    _arSCNView.delegate = self;
    
    //2.設置視圖會話
    _arSCNView.session = self.arSession;
    //3.自動刷新燈光(3D游戲用到,此處可忽略)
    _arSCNView.automaticallyUpdatesLighting = YES;
    
    return _arSCNView;
}

#pragma mark -- ARSCNViewDelegate



//添加節(jié)點時候調(diào)用(當開啟平地捕捉模式之后,如果捕捉到平地,ARKit會自動添加一個平地節(jié)點)
- (void)renderer:(id <SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
    
    
}

//刷新時調(diào)用
- (void)renderer:(id <SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
    NSLog(@"刷新中");
}

//更新節(jié)點時調(diào)用
- (void)renderer:(id <SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
    NSLog(@"節(jié)點更新");
    
}

//移除節(jié)點時調(diào)用
- (void)renderer:(id <SCNSceneRenderer>)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
    NSLog(@"節(jié)點移除");
}

#pragma mark -ARSessionDelegate

//會話位置更新(監(jiān)聽相機的移動),此代理方法會調(diào)用非常頻繁,只要相機移動就會調(diào)用,如果相機移動過快,會有一定的誤差,具體的需要強大的算法去優(yōu)化,筆者這里就不深入了
- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
{
    NSLog(@"相機移動");
    
}
- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor*>*)anchors
{
    NSLog(@"添加錨點");
    
}


- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor*>*)anchors
{
    NSLog(@"刷新錨點");
    
}


- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor*>*)anchors
{
    NSLog(@"移除錨點");
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end


<h2 id="1.3">1.3-代碼下載地址</h2>

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容