• Главная
  • Контакты
Подписаться:
  • Twitter
  • Facebook
  • RSS
  • VK
  • PushAll
logo

logo

  • Все
    • Положительные
    • Отрицательные
  • За сегодня
    • Положительные
    • Отрицательные
  • За вчера
    • Положительные
    • Отрицательные
  • За 3 дня
    • Положительные
    • Отрицательные
  • За неделю
    • Положительные
    • Отрицательные
  • За месяц
    • Положительные
    • Отрицательные
  • За год
    • Положительные
    • Отрицательные
  • Сортировка
    • По дате (возр)
    • По дате (убыв)
    • По рейтингу (возр)
    • По рейтингу (убыв)
    • По комментам (возр)
    • По комментам (убыв)
    • По просмотрам (возр)
    • По просмотрам (убыв)
Главная
  • Все
    • Положительные
    • Отрицательные
  • За сегодня
    • Положительные
    • Отрицательные
  • За вчера
    • Положительные
    • Отрицательные
  • За 3 дня
    • Положительные
    • Отрицательные
  • За неделю
    • Положительные
    • Отрицательные
  • За месяц
    • Положительные
    • Отрицательные
  • Главная
  • Опыт переноса приложения с Unity3D на iOS sdk и SceneKit

Опыт переноса приложения с Unity3D на iOS sdk и SceneKit +4

24.04.2017 06:14
elder_cat 3 1300 Источник
Разработка под iOS*, Разработка мобильных приложений*, Swift*, Objective C*, Блог компании Everyday Tools
Сегодня делимся опытом наших партнеров — компании Try Sports Now — о том, как с помощью фреймворка SceneKit дать приложению вторую жизнь.

«Случается, что приложение, которое долгое время прозябало в безвестности, вдруг начинает набирать популярность среди пользователей и приносить прибыль. Само собой разумеется, что при таком раскладе целесообразно его развивать и обновлять. Одно плохо: может оказаться, что исходный код продукта настолько морально устарел за время отсутствия спроса, что временные затраты на его обновление сопоставимы с ресурсом, уходящим на разработку нового исходника с нуля. С подобной проблемой мы столкнулись в работе с проектом Human Anatomy 3D. В этой статье мы расскажем, как осуществлялся переход новой версии приложения с Unity3D исходников на нативные, и осветим некоторые проблемы возникшие в процессе.

Приложение было написано несколько лет назад на кроссплатформенном Unity3D. Через какое-то время кроссплатформенность стала неактуальна, а версия для App Store продолжала занимать достаточно много места на устройствах пользователей, что не могло их не разочаровывать. Да и возможности движка Unity3D были избыточны для реализации функционала: нужно было просто отображать 3D-объекты с возможностью базового взаимодействия с ними и анимацией. Ввиду перечисленного, мы решили обновить версию нативными iOS и macOS исходниками, а для реализации модулей работы с 3D-объектами использовать SceneKit. Решение было продиктовано преимущественно тем, что с этим инструментом мы хорошо знакомы.

Краткий обзор SceneKit


SceneKit — высокоуровневый фреймворк, предназначенный для работы с трехмерными объектами. Он включает в себя физический движок, генератор частиц и удобный API. В основе SceneKit лежит граф сцены. Данный подход широко используется в игровых движках (Unity, Cocos…), а также в 3D редакторах (Blender, Lightwave…). Подробнее о графе сцены можно прочитать здесь. Рассмотрим его типичную структуру:



Основным элементом данной структуры является узел (SCNNode), содержащий в себе информацию о положении, углах поворота и масштабе. Положение, вращение и масштаб дочерних узлов определяются относительно родительского узла.

  • SCNGeometry определяет форму объекта и его отображение за счет набора материалов;
  • SCNCamera отвечает за точку, из которой мы видим сцену (аналогично камере при съемке фильма);
  • SCNLight отвечает за освещение; его положение влияет на все объекты сцены.

Как видно из структуры, корневым объектом является SCNScene, содержащий в себе ссылку на корневой узел. Именно относительно этого узла и строится вся структура сцены.

Теперь посмотрим, как это использовалось в нашем проекте. В первую очередь был создан объект сцены (SCNScene), и в него была загружена готовая сцена с моделью. Для хранения сцен и текстур использовалась рекомендуемая папка .scnassets. так как в этом случае Xcode оптимизирует их для достижения максимальной производительности на устройстве. Сцена мы импортировали из редактора в формате COLLADA (*.dae). Вот пример подгрузки сцены из .scnassets:

Objective-C:

SCNScene *scene = [SCNScene sceneNamed:@"путь до файла со сценой"];

Swift:

let scene = SCNScene(named: "путь до файла со сценой")

В результате к корневому узлу сцены (rootNode) будет добавлена вся иерархия объектов загруженной сцены. После этого мы решили, что неплохо бы найти нашу модель на сцене и добавить ей материал с текстурой, которая представляет собой изображение и хранится в той же .scnassets:

Objective-C iOS:
SCNNode *meshNode = [scene.rootNode childNodeWithName:@"имя узла с моделью" recursively:true];
meshNode.geometry.materials.firstObject.diffuse.contents = [UIImage imageNamed:@"имя файла с текстурой"];

Swift macOS:

        let meshNode: SCNNode? = mydae.rootNode.childNode(withName: "имя узла с моделью", recursively: true)
        meshNode?.geometry?.materials.first?.diffuse.contents = NSImage(named: "имя файла с текстурой")

Как можно заметить, поиск нужного узла осуществляется просто по имени. Параметр recursively определяет, нужно ли искать дальше, вглубь графа сцены, или достаточно ограничиться дочерними узлами текущего. Отыскав нужный нам узел, мы берем первый материал, который лежит на нем, и назначаем ему выбранную текстуру.

Далее, нужно ввести камеру. Для этого создадим и добавим в иерархию сцены 2 узла: cameraNode, который и будет являться камерой, и cameraRoot, к которому камера будет прикреплена и относительно которого будет перемещаться:

Objective-C iOS:

SCNNode *cameraNode = [SCNNode node];
cameraNode.camera = [SCNCamera camera];
SCNNode * cameraRoot = [SCNNode node];
cameraNode.position = SCNVector3Make(0, 0, 3);
[cameraRoot addChildNode:cameraNode];
[scene.rootNode addChildNode:cameraRoot];


Swift macOS:
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        let cameraRoot = SCNNode()
        cameraNode.position = SCNVector3Make(0, 0, 3)
        cameraRoot.addChildNode(cameraNode)
        scene.rootNode.addChildNode(cameraRoot)

Это сделано для удобства работы с камерой, так как в этом случае все перемещения будут осуществляться относительно неподвижной точки, а это удобнее для тех случаев, когда, например, камера должна облететь вокруг объекта.

Для отображения сцены используется элемент интерфейса SceneKit View, который добавляется на экран приложения:


Так как в рамках нашего проекта нет необходимости в специфическом освещении, мы не будем добавлять на сцену источники света. Вместо этого воспользуемся освещением по умолчанию. За него отвечает параметр autoenablesDefaultLighting у SCNView. Остаётся только указать компоненту SCNView нашу сцену:

_sceneView.scene = scene;

В SceneKit есть встроенный механизм управления камерой, за который отвечает параметр allowsCameraControl. Однако в нашем случае его пришлось отключить, так как необходимо было предусмотреть ограничение амплитуды вращения камеры вокруг объекта.

Пример работы с объектами


В качестве примера работы с объектами сцены рассмотрим реализацию зума и вращения камеры вокруг нашей модели.

Начнем с реализации приближения/отдаления, которая, по сути, сводится к перемещению узла камеры по оси Z относительно узла объекта-родителя. Все перемещения, вращения, изменения масштаба в SceneKit осуществляются аналогично тому, как это делается в CoreGraphics. Это связано с тем, что SceneKit использует СoreGraphics для реализации анимации:

Objective-C iOS:

- (void)zoomCamera:(CGFloat) zoom{
    CGFloat speedZ = 0,245;
    if (zoom< 1)
        zoom= speedZ;
    else if (zoom> 1)
        zoom= -speedZ;
    if (zoom< 0){
        if (cameraNode.position.z < 2.5)
            return;
    }
    else{
        if (cameraNode.position.z > 6)
            return;
    }
    cameraNode.transform = SCNMatrix4Translate(cameraNode.transform, 0, 0, zoom);
}

Swift macOS:

var allZoom: CGFloat = 0
    func zoomCamera(zoom: CGFloat) {
        
        if allScrollX < 1 {
            let cam = ViewController.cameraRoot.childNodes[0]
            allZoom = zoom
            
            let speedZ = CGFloat(7)
            
            if allZoom < -speedZ {
                allZoom = -speedZ
            } else if allZoom > speedZ {
                allZoom = speedZ
            }
            
            if  allZoom < 0 {
                if cam.position.z < 3 {
                    return
                }
            } else {
                if cam.position.z > 5 {
                    return
                }
            }
            cam.transform = SCNMatrix4Translate(cam.transform, 0, 0, allZoom * 0.035)
        }
        
    }

В данной функции мы определяем, что именно происходит — приближение или отдаление — и, если текущее положение камеры укладывается в установленное ограничение, осуществляем перемещение камеры посредством SCNMatrix4Translate, которая в качестве первого параметра принимает transform — относительно него и будет происходить перемещение. Остальные параметры — это смещения по осям X, Y, Z соответственно. При реализации зума на macOS следует учитывать, что скорость скроллинга Apple Mouse и TouchPad выше стандартных показателей: это может стать причиной нарушения ограничения крайних границ зума.

Реализация вращения камеры вокруг модели во многом аналогична реализации приближения, однако в случае с вращением мы, конечно, имеем дело сразу с 2 осями: ось Y для облета вокруг модели и ось X для возможности просмотра модели сверху/снизу:

Objective-C iOS:

- (void)rotateCamera:(CGPoint) rotation{
    allScrollX = rotation.x / 2;
    for (SCNNode *item in _sceneView.scene.rootNode.childNodes) {
        if (item != cameraRoot && item != cameraNode)
            item.transform = SCNMatrix4Rotate(item.transform, rotation.x * M_PI/180.0, 0, 0.1, 0);
    }
    if (cameraRoot.eulerAngles.x* 180.0/M_PI > 45 && rotation.y < 0){
        return;
    }
    if (cameraRoot.eulerAngles.x* 180.0/M_PI < -45 && rotation.y > 0){
        return;
    }
    cameraRoot.transform = SCNMatrix4Rotate(cameraRoot.transform, rotation.y * M_PI/180.0, -0.1, 0, 0);
}

Swift macOS:

func anglCamera(x: CGFloat, y: CGFloat) {
        
        if allZoom < 1 {
            
            allScrollX = x / 2
            
            let cam = ViewController.cameraRoot
            
            let rootNode = ViewController.sceneRoot
            
            let dX = x * CGFloat(Double.pi / 180)
            
            for item in rootNode.childNodes {
                if item.name != "camera" {
                    item.transform = SCNMatrix4Rotate(item.transform, -dX, 0, 0.1, 0)
                }
            }
            
            let angle = cam.eulerAngles
            let dY = y * CGFloat(Double.pi / 180)
            
            if  dY < 0 {
                if angle.x < -1 {
                    return
                }
            } else {
                if angle.x > 1 {
                    return
                }
            }
            
            cam.transform = SCNMatrix4Rotate(cam.transform, dY, 0.1, 0, 0)
            
        }
        
    }

Здесь мы поворачиваем по оси Y саму модель, чтобы поворот по каждой оси выполнялся независимо. Непосредственно вращение реализуется посредством SCNMatrix4Rotate. Первым его параметром является transform, относительно которого будет осуществляться поворот, вторым параметром — угол поворота, а остальные три — это компоненты поворота по оси X, Y и Z соответственно.

Проблемы


С новой iOS-версией приложения проблем не возникало, нам удалось понизить минимальную требуемую версию iOS до 10.0. Сложности начались, когда аналогичное приложение понадобилось реализовать и на macOS. На версиях macOS ниже 10.12 при работе с моделями мы столкнулись с глобальными проблемами:

  • анимации игнорировались;
  • смещались координаты анимаций;
  • возрастала вероятность краша IDE XCode при работе с моделями.

Эти проблемы пока так и не удалось устранить; пришлось на время оставить минимальную требуемую версию macOS 10.12.

Заключение


Перенос приложения с Unity3D на iOS sdk и SceneKit позволил упростить и ускорить разработку интерфейса приложения, реализацию управления жестами. Без лишних усилий интерфейсы iOS и macOS приложений стали максимально привычным пользователю, а размер архивных файлов уменьшился в 2-2.5 раза. Вместе с тем, переход к нативным средствам усложнил работу с моделями: стало невозможным получить доступ к конкретной части единой модели *.dae. Но в целом, если говорить о требованиях к отображению и базовых взаимодействиях, SceneKit позволил без глобальных сложностей интегрировать в приложение трехмерные объекты с анимацией на свежих версиях macOS и iOS, а также реализовать простейшее взаимодействие с камерой».
Поделиться с друзьями
-->

Комментарии (3)


  1. nepster
    24.04.2017 12:44
    #10188366
    +2

    А перенос собственно где? Я виже работу со сценой и камерой всего лишь…


    1. EverydayTools
      25.04.2017 07:35
      #10189590

      Спасибо за Ваш комментарий. В нашей статье мы постарались кратко осветить некоторые стандартные примеры работы со SceneKit, обозначить проблемы/сложности, которые стоит учитывать при взятии в работу подобной задачи, проблемы, которые на этапе планирования разработки были непрогнозируемы и повлияли на конечный результат. Мы придерживаемся позиции, что поверхностное описание процесса написания кода работы с 3D моделями с использованием Scenekit это и есть перенос функционала с Unity3D. Если бы Вы хотели узнать еще о какой-то проблеме детально, сообщите нам — будем признательны и постараемся рассказать об этом в наших следующих статьях.


  1. Nils22
    26.04.2017 10:59
    #10191574

    «стало невозможным получить доступ к конкретной части единой модели *.dae» – что именно тут имеется ввиду? Получить доступ для того, чтобы затем анимировать, установить новый материал или что-то другое?

МЕТКИ

  • Хабы
  • Теги

Разработка под iOS

Разработка мобильных приложений

Swift

Objective C

Блог компании Everyday Tools

objective-c

Swift

scenekit

разработка приложений

разработка под ios

СЕРВИСЫ
  • logo

    CloudLogs.ru - Облачное логирование

    • Храните логи вашего сервиса или приложения в облаке. Удобно просматривайте и анализируйте их.
Все публикации автора
  • Задание для дизайнеров от Google: почему большинство решений не годятся -1

    • 30.06.2017 11:04

    Сперва сторифреймы, потом вайрфреймы +4

    • 16.06.2017 11:07

    Создание простого аудиоредактора +5

    • 07.06.2017 10:53

    Реализация системы доступа в собственном корпоративном мессенджере: часть первая +5

    • 30.05.2017 10:49

    Программирование — это сложно; именно поэтому ему и стоит учиться

    • 19.05.2017 07:14

    Новые инструменты для организации эффективной командной работы +3

    • 11.05.2017 11:52

    Классические и новые пособия по интернет-маркетингу, которые стоит увидеть +5

    • 27.04.2017 08:02

    «Почему вы просто не перепишете это на язык X?» +18

    • 25.04.2017 07:36

    Опыт переноса приложения с Unity3D на iOS sdk и SceneKit +4

    • 24.04.2017 06:14

    Разработка компонента для создания коллажей +7

    • 20.04.2017 08:16

Подписка


ЛУЧШЕЕ

  • Сегодня
  • Вчера
  • Позавчера
01:02

Как молодой девушке уехать на Яндекс.Такси в промзону и пропасть среди гаражей +81

04:57

Перетягивание замороженных активов с ЕС, а также космические дата-центры Илона Маска +17

07:59

Синдром бесконечного окна: почему 1 миллион токенов в LLM не решает ваши проблемы (пока) +16

07:33

Как мы запускали «марсоход» на PostgreSQL: автоматизация кластеров в изолированной среде крупной компании +14

08:00

Дешевых ПК и ноутбуков больше не будет: готовимся к 2026 году +12

06:15

Rust, mmap и 10 миллионов пикселей: делаем производительный Log Viewer для VS Code +12

06:10

SQL HowTo: проверяем и объединяем диапазоны (Advent of Code 2025, Day 5: Cafeteria) +11

05:16

Как я уже 5 лет создаю свою макрос-клавиатуру. И почему не бросил этот проект +10

09:01

Firefox — лучший мобильный браузер +8

07:44

Почему учителя бегут из школ, а дети не хотят учиться — и как я это исправляю +8

07:05

Что скрывается за адвент-календарем: бизнес-логика праздничных окошек +8

05:30

«Взрослый» Autoenrollment сертификатов в Linux +7

07:40

Нобелевская премия-2025: кто главные бенефициары научных открытий ученых-лауреатов? +6

04:55

В поисках портала в иные миры: эксперимент DANSS сужает пространство для гипотез +6

07:11

Сколько на самом деле получали тестировщики в 2025 году +5

08:15

Программист в вакууме +4

07:09

Окно в прошлое: как я путешествовал во времени с помощью ИИ, пока не наткнулся на запретную страницу истории +4

06:43

9 самых частых задач на Python live-coding (и как их правильно решать) +4

05:42

Лучшие практики по настройке конфигураций в Kubernetes +4

05:03

Установка и Настройка FreeIPA с внешним Root CA +4

22:11

Пожалуйста, почините найм +88

11:20

Тайная жизнь оконного стекла: история, технологии и немного олова +39

09:01

В GitHub Actions, пожалуй, худший пакетный менеджер +38

08:00

Станет ли FreeBSD 15.0 новым шагом в развитии свободной ОС +31

16:43

Если вы эксперт 45+ и вдруг решили сменить работу +26

05:00

Минимальный набор практик для микросервиса +26

13:01

Дело Solar Sunrise: кто «ломал Пентагон» в феврале 1998 года? +25

15:16

Ретроспектива 2025: Денежный дождь закончился +23

14:55

Абсурд прогресса. Почему «высокий уровень жизни» не продлевает годы, а крадет их? +22

16:05

Телевизионные передачи 90-х +19

10:28

Я доверил деньги нейросети, чтобы не сидеть у монитора 24/7: результаты эксперимента с алготрейдингом +19

19:36

Ночь, телескоп, ИИ, комета: анализ спектра 3I/ATLAS с собственным Python-pipeline +15

17:25

Мы больше не увидим BSoD на вывесках и табло +11

13:20

Самобеглый чемодан +11

12:45

Как Европа развивает свою open source-экосистему — и какие новые возможности по кооперации появляются у стран БРИКС +10

07:00

[Resource Quota] А что, если ваш финансовый потолок — это не баг, а защита от перегрева? +8

13:30

Новогодний IT-челлендж для разработчиков: сможете расшифровать сообщение? +6

12:40

Эта музыка будет вечной: микросервисы против монолита, camelCase против snake_case и другие неугасающие споры в ИТ +6

12:15

Полиграф: иллюзия точности. Как метод без научного фундамента нарушает права и манипулирует законодательством +6

03:50

Ученые обнаружили скрытые ускорители частиц в околоземной космической плазме +6

08:00

Почему xor eax, eax используется так часто? +66

20:08

Скажи yay -S say +65

16:22

В прошлом квартале я внедрил Microsoft Copilot для 4000 сотрудников +55

19:05

Вот такие пироги… Почему нам врут круговые диаграммы +51

14:07

Инженерное чудо Compaq'а из 90-х… +39

09:01

История дирижаблей. Часть 2: рождение и смерть французского дирижаблестроения +35

13:01

Ассемблер для гоферов. Структура и макросы. Часть 2 +27

12:00

Бунт против IBM, или как хакеры сломали систему и сделали компьютеры персональными +27

07:54

«Прочный как кирпич»: сервер от Nokia, Dataflow-ускорители и не только +22

09:05

Как работают современные браузеры. Часть 2 +18

17:16

Как весь день быть продуктивным: не выгореть и покончить с прокрастинацией. Часть 3 +16

06:15

Ловушки PowerShell: поведение, которое ломает привычные ожидания разработчиков +16

15:01

Open source-стратегии: как работать с партнерами на базе открытых технологий — опыт Александра Нозика, директора SPC +10

14:52

librats: Выпуск версии 0.5.x. Ускорение поиска пиров, алгоритм spider, поддержка JavaScript, Python и многое другое +10

20:25

Сборка высокопроизводительного AI-десктопа +9

11:32

11 полезных фичей Chrome DevTools часть 2 +8

18:36

Арифметика сверточных слоев. Вычисляем размерность изображения с учетом stride, padding и dilation +7

15:45

Обнови ICU в PHP 7.3 intl на Centos 7, если осмелишься +6

12:47

Исследование комет: анализ патентов +5

08:34

Бросаем Event Loop, переходим на Горутины: Go для JS-девелоперов (Часть 1) +5

ОБСУЖДАЕМОЕ

  • Пожалуйста, почините найм +88

    • 138   29000

    Если вы эксперт 45+ и вдруг решили сменить работу +26

    • 124   14000

    Как молодой девушке уехать на Яндекс.Такси в промзону и пропасть среди гаражей +81

    • 116   19000

    Бунт против IBM, или как хакеры сломали систему и сделали компьютеры персональными +27

    • 94   13000

    Абсурд прогресса. Почему «высокий уровень жизни» не продлевает годы, а крадет их? +22

    • 93   10000

    Я доверил деньги нейросети, чтобы не сидеть у монитора 24/7: результаты эксперимента с алготрейдингом +19

    • 89   54000

    Инженерное чудо Compaq'а из 90-х… +39

    • 68   13000

    Мы больше не увидим BSoD на вывесках и табло +11

    • 43   26000

    Ретроспектива 2025: Денежный дождь закончился +23

    • 38   9700

    Эдсгер Дейкстра «О вреде оператора go to» +1

    • 38   8800

    Эта музыка будет вечной: микросервисы против монолита, camelCase против snake_case и другие неугасающие споры в ИТ +6

    • 34   7400

    [Resource Quota] А что, если ваш финансовый потолок — это не баг, а защита от перегрева? +8

    • 33   7400

    Почему xor eax, eax используется так часто? +66

    • 29   18000

    Новогодний IT-челлендж для разработчиков: сможете расшифровать сообщение? +6

    • 25   8900

    Тайная жизнь оконного стекла: история, технологии и немного олова +39

    • 20   13000
  • Главная
  • Контакты
© 2025. Все публикации принадлежат авторам.