[GameMaker: Studio] 端末の向きとレイアウト (補足あり)

*シンプルバージョンの記事はこちら

gms_device_orientation_sample_2

GameMaker: Studioで作成したゲームは、実行時に一番最初に呼ばれるroomで設定されている画面サイズがそのゲームの画面サイズとして固定されます。
他のroomの設定で異なる画面サイズを指定したとしても、一番最初に呼ばれたroomのサイズのままになるという仕様になっています。

例えば、room0は800×450、room1は450×800の場合、実行時に画面は800×450となりますが、room1に切り替えても画面は450×800にはならずに800×450のままになります。

しかし、端末の向きによってレイアウトを変更したい場合にはこの仕様に悩まされることになります。

単に画面サイズを変更するだけであればwindow_set_size命令で変えることはできますが、これだけではそのサイズに収まるように自動的に拡大/縮小されるだけです。
gms_device_orientation_sample_5

レイアウトそのものを変更したい場合は、window_set_sizeに加え、surface_resizeview_visible[n]を組み合わせると上手くいきます。

基本的な描画は画面外に行います。この例ではapplication_surfaceの表示領域の外に描いていますが、動き回るオブジェクトがある場合はviewを外れてportに重なってしまうこともあるので、独立した描画専用のsurfaceを作成した方が安全です。
gms_device_orientation_sample_1

これらの描画領域を、端末が縦の場合と横の場合でそれぞれviewに設定しておき、端末の向きが変更されたらwindow_set_sizesurface_resizeを行って各view_visibleを切り替えます。

以下は実際のコード例です。

[script - p_set_view] viewを定義
{
    var n = argument0;
    var e = argument1;
    var o = argument2;
    var vx = argument3;
    var vy = argument4;
    var vw = argument5;
    var vh = argument6;
    var px = argument7;
    var py = argument8;
    var pw = argument9;
    var ph = argument10;

    view_visible[n] = e;
    view_xview[n] = vx;
    view_yview[n] = vy;
    view_wview[n] = vw;
    view_hview[n] = vh;
    view_xport[n] = px;
    view_yport[n] = py;
    view_wport[n] = pw;
    view_hport[n] = ph;
    view_angle[n] = 0;
    view_hborder[n] = 0;
    view_vborder[n] = 0;
    view_object[n] = noone;
    view_hspeed[n] = 0;
    view_vspeed[n] = 0;
    view_surface_id[n] = application_surface;

    if(o != noone) return instance_create(vx, vy, o);
    return noone;
}

[script - p_change_device_orientation] レイアウト切り替え
{
    var o = argument0;

    if(o < 0) o = display_get_orientation();
    if(o == global.system_device_orientation) return o;

    global.system_device_orientation = o;
    var n = 0;
    switch(o) {
    case display_landscape:
        view_visible[1] = true;
        view_visible[2] = false;
        view_visible[3] = false;
        view_visible[4] = true;
        view_visible[5] = false;
        view_visible[6] = true;
        view_visible[7] = false;
        break;
    case display_landscape_flipped:
        view_visible[1] = false;
        view_visible[2] = true;
        view_visible[3] = false;
        view_visible[4] = false;
        view_visible[5] = true;
        view_visible[6] = false;
        view_visible[7] = true;
        break;
    case display_portrait:
        view_visible[1] = false;
        view_visible[2] = false;
        view_visible[3] = true;
        view_visible[4] = false;
        view_visible[5] = false;
        view_visible[6] = false;
        view_visible[7] = false;
        n = 3;
        break;
    case display_portrait_flipped:
        view_visible[1] = false;
        view_visible[2] = false;
        view_visible[3] = true;
        view_visible[4] = false;
        view_visible[5] = false;
        view_visible[6] = false;
        view_visible[7] = false;
        n = 3;
        break;
    }
    window_set_size(view_wview[n], view_hview[n]);
    surface_resize(application_surface, view_wview[n], view_hview[n]);
    return o;
}

[object - create - code] view登録
p_set_view(0, true,  o_bg,          0, 1024, 702, 450,   0,    0, 702, 450);
p_set_view(1, true,  o_image_a,     0, 1024, 288, 450, 414,    0, 288, 450);
p_set_view(2, false, noone,         0, 1024, 288, 450,   0,    0, 288, 450);
p_set_view(3, false, noone,         0, 1024, 288, 450,   0,    0, 702, 450);
p_set_view(4, true,  o_image_b,   512, 1024, 414, 150,   0,    0, 414, 150);
p_set_view(5, false, noone,       512, 1024, 414, 150, 288,  300, 414, 150);
p_set_view(6, true,  o_image_c,  1024, 1024, 414, 300,   0,  150, 414, 300);
p_set_view(7, false, noone,      1024, 1024, 414, 300, 288,    0, 414, 300);
view_enabled = true;
global.system_device_orientation = -1;
p_change_device_orientation(-1);

view 0 = 背景(常時表示)

landscape
view 1 = ブロックA・右配置
view 4 = ブロックB・左上配置
view 6 = ブロックC・左下配置
gms_device_orientation_sample_2

landscape_flipped
view 2 = ブロックA・左配置
view 5 = ブロックB・右下配置
view 7 = ブロックC・右上配置
gms_device_orientation_sample_3

portrait/portrait_flipped
view 3 = ブロックA
gms_device_orientation_sample_4

surface_resizeportの値に注意してください。
通常、Global Game SettingsKeep aspect ratioを有効にしていると思いますが、この場合GM:Sは強制的に初期roomと同じアスペクト比を維持しようとするので、port側で画面サイズにフィッティングさせています。

ゲーム中のメインループ(step beginイベント等)でp_change_device_orientation(-1)を呼び出すと、端末の向きが変更されていたら自動的にレイアウトが切り替わります。
引数をdisplay_landscape等で指定すると強制的にレイアウトを切り替えることもできます。

 


*補足

room設定の背景クリア機能を使うと画面外からのコピーが透過されないため背景用のviewを設定していますが、別surfaceを使って自前でクリアしてから描画するのであれば背景用のviewは必要ありません。
surfaceならviewも使わずに直接draw_surface_extで貼り付けることもできます。

viewの場合、view設定の最大値で自動的にウィンドウサイズが変更されるようで、window_set_sizeも不要かもしれません。
ただし、すべてのプラットフォームで上手くいくかは未調査です。