iOSで空を飛べなかったお話(Babylon.jsとiOSについてのメモ)



先日こんなものを作りました。

・Granage | HollowCradle
http://hollowcradle.cf/granage/index.html


僕みたいなHTML,CSS,Javascriptをちょろっと触ったことがある人間でも、2日ほどで作ることが出来ました。
感動です。

「空を飛びたいなら飛べる世界を作ればいいじゃない」

っていう単純過ぎる動機ですが、それでできちゃうんだから技術の進歩ってすごい。



しかし、その感動はすぐに消し飛ぶことに。

プログラミングにおいて、動作環境が異なれば動かなくなることなんて日常茶飯事。

自分が持っているAndroidスマートフォンとLinuxMint入りのPCで動作を確認しており、

「Javascriptだし、多少の挙動の違いはあれどまぁ動くじゃろ」

という、何処からその根拠が出てきたのか不思議な思い込みから、日本で(何故か)異常なまでに多いiOSでのテストを放置していました。

で、ふと友人に空を飛ぶ感動をおすそ分けしようと試してもらったところ、画面が真っ白に。

「あ...これは...」と焦りつつも色々試すことに。


いじっていったコードはこちら。
(AndroidとLinuxMintでは動作確認済み)

Javascript
if (BABYLON.Engine.isSupported()) {
    const canvas = document.getElementById('canvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    let speed = 5;

    let is_mobile = false;
    let ua = navigator.userAgent;

    if (ua.indexOf('Android') > 0 || ua.indexOf('iPhone') > 0 || ua.indexOf('iPod') > 0 && ua.indexOf('Mobile') > 0 ) {
        is_mobile = true;
    } else {
        is_mobile = false;
    }
 
    function create_scene(engine) {
        let scene = new BABYLON.Scene(engine);

        let camera;
        if (is_mobile) {
            camera = new BABYLON.DeviceOrientationCamera('DCamera', new BABYLON.Vector3(0, 100, -400), scene);
        }
        else {
            camera = new BABYLON.TouchCamera('Camera', new BABYLON.Vector3(100, 100, 100), scene);
        }

        camera.attachControl(canvas, true);

        let light = new BABYLON.PointLight('Omni', new BABYLON.Vector3(5000, 2600, -5000), scene);

        let lens_flare_system = new BABYLON.LensFlareSystem('LensFrareSystem', light, scene);
        let flare0 = new BABYLON.LensFlare(0.2, 0, new BABYLON.Color3(1, 1, 1), 'img/lens5.png', lens_flare_system);
        let flare1 = new BABYLON.LensFlare(0.5, 0.2, new BABYLON.Color3(0.5, 0.5, 1), 'img/lens4.png', lens_flare_system);
        let flare2 = new BABYLON.LensFlare(0.2, 0.1, new BABYLON.Color3(1, 1, 1), 'img/lens4.png', lens_flare_system);
        let flare3 = new BABYLON.LensFlare(0.4, 0.4, new BABYLON.Color3(1, 0.5, 1), 'img/Flare.png', lens_flare_system);
        let flare4 = new BABYLON.LensFlare(0.1, 0.6, new BABYLON.Color3(1, 1, 1), 'img/lens5.png', lens_flare_system);
        let flare5 = new BABYLON.LensFlare(0.3, 0.8, new BABYLON.Color3(1, 1, 1), 'img/lens4.png', lens_flare_system);

        for (let i = 0; i < 10; i++) {
            create_sky_sphere(`skysphere${i}`, scene);
        }

        let skybox0 = BABYLON.Mesh.CreateSphere('skybox0', 16, 4000.0, scene);
        skybox0.rotation.y = Math.PI * 1.6;
        let skybox0_material = new BABYLON.StandardMaterial('skybox0', scene);
        skybox0_material.backFaceCulling = false;
        skybox0_material.reflectionTexture = new BABYLON.CubeTexture('img/TropicalSunnyDay/TropicalSunnyDay', scene);
        skybox0_material.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
        skybox0_material.disableLighting = true;
        skybox0.material = skybox0_material;

        let skybox1 = BABYLON.Mesh.CreateSphere('skybox1', 16, 10000.0, scene);
        let skybox1_material = new BABYLON.StandardMaterial('skybox1', scene);
        skybox1_material.backFaceCulling = false;
        skybox1_material.reflectionTexture = new BABYLON.CubeTexture('img/cloudylightrays/CloudyLightRays', scene);
        skybox1_material.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
        skybox1_material.disableLighting = true;
        skybox1.material = skybox1_material;

        let ground_texture = new BABYLON.Texture('img/Ground.jpg', scene);
        ground_texture.vScale = ground_texture.uScale = 2.0;
        let ground_material = new BABYLON.StandardMaterial('groundMaterial', scene);
        ground_material.diffuseTexture = ground_texture;
        let ground = BABYLON.Mesh.CreateGround('ground', 1028, 1028, 32, scene, false);
        ground.position.y = -1;
        ground.material = ground_material;

        let camera_euler = new BABYLON.Vector3();

        if (is_mobile) {
            scene.registerBeforeRender(function () {
                camera_euler = camera.rotationQuaternion.toEulerAngles();
                camera.position.x += speed * Math.sin(camera_euler.y);
                camera.position.y -= speed * Math.sin(camera_euler.x);
                camera.position.z += speed * Math.cos(camera_euler.y);
            });
        }else {
            scene.registerBeforeRender(function () {
                camera.position.x += speed * Math.sin(camera.rotation.y);
                camera.position.y -= speed * Math.sin(camera.rotation.x);
                camera.position.z += speed * Math.cos(camera.rotation.y);
            });
        }

        return scene;
    }

    function create_sky_sphere(name, scene) {
        let skysphere = BABYLON.Mesh.CreateSphere(name, 10, Math.floor(Math.random() * 300), scene);
        skysphere.position = new BABYLON.Vector3(1200 - Math.floor(Math.random() * 2400), 1200 - Math.floor(Math.random() * 2400), 1200 - Math.floor(Math.random() * 2400));
        let skysphere_material = new BABYLON.StandardMaterial(name, scene);
        skysphere_material.diffuseColor = new BABYLON.Color3(Math.random(), Math.random(), Math.random());
        skysphere_material.alpha = 0.5;
        skysphere.material = skysphere_material;
    }

    function init() {
        let engine = new BABYLON.Engine(canvas, true);
        window.addEventListener('resize', function () {
            engine.resize();
        })

        let scene = create_scene(engine);

        engine.runRenderLoop(function () {
            scene.render();
        });

        // scene.debugLayer.show();
    }

    init();
}


結論からいうと、動きませんでした。
厳密に言えば、動いたのだけど、飛ぶことは出来なかったといったところ。


今回色々いじっていく中で確認できたことは以下。

Babylon.jsそのものは動く
まぁ、これは流石に。


DeviceOrientationCameraは問題なく動いた
このデモを試してもらった後、上のコードをいじって試したけど大丈夫だった。

・A Playground Example of a Device Orientation Camera
http://www.babylonjs-playground.com/#12WBC#81


ただし、デバイス判定によるカメラの分岐設定が出来なかったので、テストの際は

let camera;

if (is_mobile) {
    camera = new BABYLON.DeviceOrientationCamera('DCamera', new BABYLON.Vector3(0, 100, -400), scene);
}
else {
    camera = new BABYLON.TouchCamera('Camera', new BABYLON.Vector3(100, 100, 100), scene);
}

じゃなく、

let camera = new BABYLON.DeviceOrientationCamera('DCamera', new BABYLON.Vector3(0, 100, -400), scene);

として試しました。

これはiOSにおけるJavascriptの挙動の問題なのでしょうか。
にわか過ぎてか、何でここで動かないのかがわかりませぬ。

iOSって(Safariって?)if分の使い方違うの?
OS変えただけでただのif文が動かなくなるって何。
user agentのところは問題ないっぽいのになにゆえ。
それともvarとletの差とかそんなやつなのかな。
全然わからない。

一応、確認しましたがTouchCameraやArcRotateCameraは問題なく動くみたいです。



カメラを移動させようとすると駄目
この部分。

let camera;
    if (is_mobile) {
        scene.registerBeforeRender(function () {
            camera_euler = camera.rotationQuaternion.toEulerAngles();
            camera.position.x += speed * Math.sin(camera_euler.y);
            camera.position.y -= speed * Math.sin(camera_euler.x);
            camera.position.z += speed * Math.cos(camera_euler.y);
        });
    }else {
        scene.registerBeforeRender(function () {
            camera.position.x += speed * Math.sin(camera.rotation.y);
            camera.position.y -= speed * Math.sin(camera.rotation.x);
            camera.position.z += speed * Math.cos(camera.rotation.y);
        });
    }

分岐無しで、

scene.registerBeforeRender(function () {
        camera.position.x += speed * Math.sin(camera.rotation.y);
        camera.position.y -= speed * Math.sin(camera.rotation.x);
        camera.position.z += speed * Math.cos(camera.rotation.y);
    });

としても駄目でした。

ここが通らないと飛べないので、一番悲しいところ。
原因もさっぱりわからない。
if文に問題があるなら上と同じ可能性もあったのに、移動がそもそも無理って。


最後に、動作を確認できたものを残しておきます。
汚いですが、何かの参考になれば。

if (BABYLON.Engine.isSupported()) {
if (BABYLON.Engine.isSupported()) {
    const canvas = document.getElementById('canvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    let speed = 5;

    let is_mobile = false;
    // let is_ios = false;
    let ua = navigator.userAgent;

    if (ua.indexOf('Android') > 0 || ua.indexOf('iPhone') > 0 || ua.indexOf('iPod') > 0 && ua.indexOf('Mobile') > 0 ) {
        is_mobile = true;
    } else {
        is_mobile = false;
    }

    // console.log(ua);

    function create_scene(engine) {
        let scene = new BABYLON.Scene(engine);

//      ※動かない
//        let camera;
//        if (is_mobile) {
//            camera = new BABYLON.DeviceOrientationCamera('DCamera', new BABYLON.Vector3(0, 100, -400), scene);
//        }
//        else {
//            camera = new BABYLON.TouchCamera('Camera', new BABYLON.Vector3(100, 100, 100), scene);
//        }

        camera = new BABYLON.DeviceOrientationCamera('DCamera', new BABYLON.Vector3(0, 100, -400), scene);
        camera.attachControl(canvas, true);

        let light = new BABYLON.PointLight('Omni', new BABYLON.Vector3(5000, 2600, -5000), scene);

        let lens_flare_system = new BABYLON.LensFlareSystem('LensFrareSystem', light, scene);
        let flare0 = new BABYLON.LensFlare(0.2, 0, new BABYLON.Color3(1, 1, 1), 'img/lens5.png', lens_flare_system);
        let flare1 = new BABYLON.LensFlare(0.5, 0.2, new BABYLON.Color3(0.5, 0.5, 1), 'img/lens4.png', lens_flare_system);
        let flare2 = new BABYLON.LensFlare(0.2, 0.1, new BABYLON.Color3(1, 1, 1), 'img/lens4.png', lens_flare_system);
        let flare3 = new BABYLON.LensFlare(0.4, 0.4, new BABYLON.Color3(1, 0.5, 1), 'img/Flare.png', lens_flare_system);
        let flare4 = new BABYLON.LensFlare(0.1, 0.6, new BABYLON.Color3(1, 1, 1), 'img/lens5.png', lens_flare_system);
        let flare5 = new BABYLON.LensFlare(0.3, 0.8, new BABYLON.Color3(1, 1, 1), 'img/lens4.png', lens_flare_system);

        for (let i = 0; i < 10; i++) {
            create_sky_sphere(`skysphere${i}`, scene);
        }

        let skybox0 = BABYLON.Mesh.CreateSphere('skybox0', 16, 4000.0, scene);
        skybox0.rotation.y = Math.PI * 1.6;
        let skybox0_material = new BABYLON.StandardMaterial('skybox0', scene);
        skybox0_material.backFaceCulling = false;
        skybox0_material.reflectionTexture = new BABYLON.CubeTexture('img/TropicalSunnyDay/TropicalSunnyDay', scene);
        skybox0_material.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
        skybox0_material.disableLighting = true;
        skybox0.material = skybox0_material;

        let skybox1 = BABYLON.Mesh.CreateSphere('skybox1', 16, 10000.0, scene);
        let skybox1_material = new BABYLON.StandardMaterial('skybox1', scene);
        skybox1_material.backFaceCulling = false;
        skybox1_material.reflectionTexture = new BABYLON.CubeTexture('img/cloudylightrays/CloudyLightRays', scene);
        skybox1_material.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
        skybox1_material.disableLighting = true;
        skybox1.material = skybox1_material;

        let ground_texture = new BABYLON.Texture('img/Ground.jpg', scene);
        ground_texture.vScale = ground_texture.uScale = 2.0;
        let ground_material = new BABYLON.StandardMaterial('groundMaterial', scene);
        ground_material.diffuseTexture = ground_texture;
        let ground = BABYLON.Mesh.CreateGround('ground', 1028, 1028, 32, scene, false);
        ground.position.y = -1;
        ground.material = ground_material;

        let camera_euler = new BABYLON.Vector3();

//      ※動かない
//        if (is_mobile) {
//            scene.registerBeforeRender(function () {
//                camera_euler = camera.rotationQuaternion.toEulerAngles();
//                camera.position.x += speed * Math.sin(camera_euler.y);
//                camera.position.y -= speed * Math.sin(camera_euler.x);
//                camera.position.z += speed * Math.cos(camera_euler.y);
//            });
//        }else {
//            scene.registerBeforeRender(function () {
//                camera.position.x += speed * Math.sin(camera.rotation.y);
//                camera.position.y -= speed * Math.sin(camera.rotation.x);
//                camera.position.z += speed * Math.cos(camera.rotation.y);
//            });
//        }

        return scene;
    }

    function create_sky_sphere(name, scene) {
        let skysphere = BABYLON.Mesh.CreateSphere(name, 10, Math.floor(Math.random() * 300), scene);
        skysphere.position = new BABYLON.Vector3(1200 - Math.floor(Math.random() * 2400), 1200 - Math.floor(Math.random() * 2400), 1200 - Math.floor(Math.random() * 2400));
        let skysphere_material = new BABYLON.StandardMaterial(name, scene);
        skysphere_material.diffuseColor = new BABYLON.Color3(Math.random(), Math.random(), Math.random());
        skysphere_material.alpha = 0.5;
        skysphere.material = skysphere_material;
    }

    function init() {
        let engine = new BABYLON.Engine(canvas, true);
        window.addEventListener('resize', function () {
            engine.resize();
        })

        let scene = create_scene(engine);

        engine.runRenderLoop(function () {
            scene.render();
        });

        // scene.debugLayer.show();
    }

    init();
}



誰でも空を飛べるようにしたいので、もしBabylon.jsとiOSの組み合わせを試した方でわかる方いらっしゃいましたら教えてくださいませ。

よろしくお願いします。

コメント