iPhone向けBox2Dの画面は縦向き固定なので、これを横向き固定に変更します。ちょっとハマリそうだなとは思っていましたが、予想通りハマリました。

ipbox2d_yoko0

横向きで起動するだけなら、plistにUISupportedInterfaceOrientationsを追加して向きを横に限定すればいいのですが……。

ipbox2d_yoko1

これだけだと縦画面のまんま横向きになります。

ipbox2d_yoko2

Appleのガイドラインなどを見ると、この後UIViewControllerにShouldRotate~といったメソッドを書くように説明されているのですが、このプロジェクトにはビューコントローラがありません。

結局、こんなになったりこんなになったりと、試行錯誤が続き……

ipbox2d_yoko3ipbox2d_yoko4

数時間かかってようやくこの記事の先頭の画像にたどり着いたわけです。画面を回すだけのことでこんなに苦労するのはイヤですねぇ。

でも、これでようやくiPhoneでまともな2Dゲームが作れる環境が手に入ったようです。後はC++で書けるわけですからね(ちなみにOpenGL ESはObjective-CではなくC言語の文法で使います)。

 

では、途中経過は省いて、どうやればいいのかだけを説明します。


■Transformプロパティで強制横回転

色々とネットで検索し、Viewのtransformプロパティを使って強制横回転する技を発見。

http://touch.hogelab.net/blog/?p=84

 

アプリ起動時に呼ばれるapplicationDidFinishLaunchingメソッドの6~9行目の部分ですね。90度回転させ、さらに原点位置も(高さの半分-幅の半分=80ピクセル)だけずらします。これでタッチの座標も正しく伝わります。

ついでに最初にテスト一覧(testEntriesView)が表示されないようコメントアウトし、いきなり最初のテストが表示されるようにします。

★Box2DAppDelegate.mm

- (void)applicationDidFinishLaunching:(UIApplication *)application {
     [application setStatusBarHidden:true];

     glView.animationInterval = 1.0 / 60.0;

     CGAffineTransform transform =
     CGAffineTransformMakeRotation(M_PI * 90 / 180.0f);
     transform = CGAffineTransformTranslate(transform, 80.0, 80.0);
     glView.transform = transform;
     [glView setDelegate:self];
     [glView startAnimation];
     [glView selectTestEntry:0];

     //[glView removeFromSuperview];
     //glView.animationInterval = 1.0 / 60.0;
     //
     //testEntriesView=[[TestEntriesViewController alloc] initWithStyle:UITableViewStylePlain];
     //[testEntriesView setDelegate:self];
     //[glView setDelegate:self];
     //
     //[window addSubview:[testEntriesView view]];
}

これで横画面の中でBox2DViewだけが90度回転した状態になります。

 

■Box2Dビューのサイズを調整する

回転させてもBox2DViewのサイズは320×480のままなので、これをさらに480×320に直します。これが結構苦労しました。

まずBox2DViewのインスタンス作成時に呼ばれるinitWitdhCoderメソッドの中で、「レイヤー」というもののサイズを480×320にします(7~11行目)。ここはコードを変更する代わりに、Interface Builderで強制的にビューを480×320固定にしてもいけるかもしれません。

★Box2DView.mm

//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {

    if ((self = [superinitWithCoder:coder])) {
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
        //サイズを横向きにする
        CGRectframe = self.frame;
        frame.origin = CGPointZero;
        frame.size = CGSizeMake(frame.size.height, frame.size.width);
        eaglLayer.frame = frame;//★ここまで横対応

        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionarydictionaryWithObjectsAndKeys:
                                        [NSNumbernumberWithBool:NO],
                                        kEAGLDrawablePropertyRetainedBacking,
                                        kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];

        context = [[EAGLContextalloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

        if (!context || ![EAGLContextsetCurrentContext:context]) {
               [selfrelease];
               returnnil;
        }

        animationInterval = 1.0 / 60.0;
        sceneScale=10.0f;
        positionOffset=CGPointMake(0, 0);
        lastWorldTouch=CGPointMake(0, 0);

        [[UIAccelerometersharedAccelerometer] setUpdateInterval:(1.0 / kAccelerometerFrequency)];
        [[UIAccelerometersharedAccelerometer] setDelegate:self];
    }

    return self;
}

ただし、これだけだと縦横比(アスペクト比)がおかしくなります。320×480の画像データをムリヤリ480×320に変形したような状態になってしまうのです。困った困った。

で、しばらく探した結果、それを設定している部分を発見しました。同じBox2DView.mmのdrawViewメソッド内でglOrthof関数を呼び出しているところです(14~15行目)。

480÷320=1.5なので、1.5fを掛けるほうを最初の2つの引数に変更します。これで比率が正しくなります。

- (void)drawView {

if (doubleClickValidCountdown]] > 0) doubleClickValidCountdown--;

    [EAGLContextsetCurrentContext:context];

    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glViewport(0, 0, backingWidth, backingHeight);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    //縦横比の変更//★横対応
    //glOrthof(-sceneScale, sceneScale, -sceneScale*1.5f, sceneScale*1.5f, -1.0f, 1.0f);
    glOrthof(-sceneScale*1.5f, sceneScale*1.5f, -sceneScale, sceneScale, -1.0f, 1.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(positionOffset.x, positionOffset.y,0);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glEnableClientState(GL_VERTEX_ARRAY);

    test->Step(&settings);

    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

最後にタッチした場所と描画される場所を合わせるために、screenSpaceToWorldSpaceメソッドでscreenLocationから引く値を逆にします(3~4行目)。この関数はtouchesBeginなどのタッチ関連関数から呼び出されています。

-(CGPoint) screenSpaceToWorldSpace:(CGPoint) screenLocation
{
     screenLocation.x-=240;//160;//★横対応
     screenLocation.y-=160;//240;
     screenLocation.x/=160;
     screenLocation.y/=160;
     screenLocation.x*=sceneScale;
     screenLocation.y*=-sceneScale;

     screenLocation.x-=positionOffset.x;
     screenLocation.y-=positionOffset.y;
     return screenLocation;
}

これで横画面でちゃんと動くようになります。

screenLocationを160で割ってsceneScaleを掛けている部分がちょっと気になっています。これはピクセル単位からBox2Dが使うメートル単位に変換しているのかもしれません。だとするとゲームに大いに影響してきます。これは後で調べてみましょう。

とりあえず今回は時間切れということで、マリオ風ゲームの移植は次回に持ち越します。

C言語からC++に直すのはたいした手間ではないですが、長さの単位に関するところと、Box2D 2.0と2.1の違いがちょっと面倒なことになるかもしれません。Box2Dは置き換えちゃったほうが楽かな?

 

OpenGL ES関連については、たまたま仕事の都合で購入していた『実践iPad/iPhoneゲームプログラミング(沼田哲史著、秀和システム刊)』を参考にしました。

著者の沼田氏は前に紹介したKarakuri Frameworkを作った方です。