サイトのトップへ戻る

libGDX ドキュメント 日本語訳

サイト内検索

基本的なネットワーク処理

LibGDX アプリケーションでのネットワーク処理について見ていきましょう。 LibGDX でのネットワーク処理はソケット通信のみサポートしており、比較的プリミティブなものです。 ですが大抵の場合はこれで十分です。 今回は、とても簡単なソケットベースのチャットアプリケーションを実装しましょう。 コードにはたくさんコメントを記載しているので、実際の説明はかなり割愛しています。 何か足りない点があった場合は、コメントを残してください。できる限り善処します。

それでは、早速コードを見てみましょう:

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net.Protocol;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.net.ServerSocket;
import com.badlogic.gdx.net.ServerSocketHints;
import com.badlogic.gdx.net.Socket;
import com.badlogic.gdx.net.SocketHints;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextArea;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

public class Networking implements ApplicationListener {
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private Skin skin;
    private Stage stage;
    private Label labelDetails;
    private Label labelMessage;
    private TextButton button;
    private TextArea textIPAddress;
    private TextArea textMessage;
    
    // Pick a resolution that is 16:9 but not unreadibly small
    public final static float VIRTUAL_SCREEN_HEIGHT = 960;
    public final static float VIRTUAL_SCREEN_WIDTH = 540;
    
    
    @Override
    public void create() {        
        camera = new OrthographicCamera(Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
        batch = new SpriteBatch();
        
        // Load our UI skin from file.  Once again, I used the files included in the tests.
        // Make sure default.fnt, default.png, uiskin.[atlas/json/png] are all added to your assets
        skin = new Skin(Gdx.files.internal("data/uiskin.json"));
        stage = new Stage();
        // Wire the stage to receive input, as we are using Scene2d in this example
        Gdx.input.setInputProcessor(stage);

        
        // The following code loops through the available network interfaces
        // Keep in mind, there can be multiple interfaces per device, for example
        // one per NIC, one per active wireless and the loopback
        // In this case we only care about IPv4 address ( x.x.x.x format )
        List<String> addresses = new ArrayList<String>();
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            for(NetworkInterface ni : Collections.list(interfaces)){
                for(InetAddress address : Collections.list(ni.getInetAddresses()))
                {
                    if(address instanceof Inet4Address){
                        addresses.add(address.getHostAddress());
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        
        // Print the contents of our array to a string.  Yeah, should have used StringBuilder
        String ipAddress = new String("");
        for(String str:addresses)
        {
            ipAddress = ipAddress + str + " \n ";
        }
        
        // Now setupt our scene UI
        
        // Vertical group groups contents vertically.  I suppose that was probably pretty obvious
        VerticalGroup vg = new VerticalGroup().space(3).pad(5).fill();//.space(2).pad(5).fill();//.space(3).reverse().fill();
        // Set the bounds of the group to the entire virtual display
        vg.setBounds(0, 0, VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT);
        
        // Create our controls
        labelDetails = new Label(ipAddress,skin);
        labelMessage = new Label("Hello world",skin);
        button = new TextButton("Send message",skin);
        textIPAddress = new TextArea("",skin);
        textMessage = new TextArea("",skin);

        // Add them to scene
        vg.addActor(labelDetails);
        vg.addActor(labelMessage);
        vg.addActor(textIPAddress);
        vg.addActor(textMessage);
        vg.addActor(button);
        
        // Add scene to stage
        stage.addActor(vg);
        
        // Setup a viewport to map screen to a 480x640 virtual resolution
        // As otherwise this is way too tiny on my 1080p android phone.
        stage.setViewport(VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT,false);
        stage.getCamera().position.set(VIRTUAL_SCREEN_WIDTH/2,VIRTUAL_SCREEN_HEIGHT/2,0);
        
        // Now we create a thread that will listen for incoming socket connections
        new Thread(new Runnable(){

            @Override
            public void run() {
                ServerSocketHints serverSocketHint = new ServerSocketHints();
                // 0 means no timeout.  Probably not the greatest idea in production!
                serverSocketHint.acceptTimeout = 0;
                
                // Create the socket server using TCP protocol and listening on 9021
                // Only one app can listen to a port at a time, keep in mind many ports are reserved
                // especially in the lower numbers ( like 21, 80, etc )
                ServerSocket serverSocket = Gdx.net.newServerSocket(Protocol.TCP, 9021, serverSocketHint);
                
                // Loop forever
                while(true){
                    // Create a socket
                    Socket socket = serverSocket.accept(null);
                    
                    // Read data from the socket into a BufferedReader
                    BufferedReader buffer = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
                    
                    try {
                        // Read to the next newline ( \n ) and display that text on labelMessage
                        labelMessage.setText(buffer.readLine());    
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start(); // And, start the thread running
        
        // Wire up a click listener to our button
        button.addListener(new ClickListener(){
            @Override 
            public void clicked(InputEvent event, float x, float y){
                
                // When the button is clicked, get the message text or create a default string value
                String textToSend = new String();
                if(textMessage.getText().length() == 0)
                    textToSend = "Doesn't say much but likes clicking buttons  \n  ";
                else
                    textToSend = textMessage.getText() + ("  \n  "); // Brute for a newline so readline gets a line
                
                SocketHints socketHints = new SocketHints();
                // Socket will time our in 4 seconds
                socketHints.connectTimeout = 4000;
                //create the socket and connect to the server entered in the text box ( x.x.x.x format ) on port 9021
                Socket socket = Gdx.net.newClientSocket(Protocol.TCP, textIPAddress.getText(), 9021, socketHints);
                try {
                    // write our entered message to the stream
                    socket.getOutputStream().write(textToSend.getBytes());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void dispose() {
        batch.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        stage.draw();
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

このコードを実行すると、以下が表示されます:

image

上二つの値は、アプリが動作している装置のIP v4 アドレスです。 これらの値を、チャットしたい他装置上のアプリケーションで入力する必要があります。 このアドレスでインターネット上のあなたのマシンを一意に識別し、さらにコード上でポート(9021)を指定します。 ポートとは、マンションにある郵便受けのようなものと考えてください。 建物の住所がIPアドレスにあたり、ポートはアパート番号のようなものになります。 ポートは 1 から 65,536まで値になりますが、いくつかは既に予約されています。 予約済みポートのほとんどは100以上の値なので、アプリケーション用のポート番号を選ぶ際にはそのアドレスが専用ポート(HTTP 通信用の80番などのような)かどうかを調べるか、単に大きな値をランダムで取得するようにしてください。 あなたの装置は複数のアドレスを持つことができます。装置のネットワークアダプタごとに1つ、特にVMWareのような仮想化ソフトを実行している場合はさらにアドレスが増えます。 それから127.0.0.1という値がありますが、これはループバックアダプターとして知られる特別なアドレスで、マシン自身を表すアドレスです。

現在“Hello World”と表示されている場所が、追加されていくメッセージが表示される場所です。

最初のテキストボックスには、メッセージを送りたい装置のIPアドレスを入力します。 今回の例では、実際にあなたにメッセージを送ることができます。自分の装置のIPアドレスを入力するだけです。 最後に、二つ目のテキストボックスには送信するメッセージ文を入力します。 もちろん、Send Message ボタンを押してメッセージを送信します。

前述の例で一つ気づいたかもしれませんが、コードをおかしな解像度で実行しています。 これはなぜでしょうか?理由はいくつかあります。 一つ目に、私がテストした携帯(HTC製)のネイティブ解像度が 1920x1080で、既定のScene2D Skin/Fontはこの解像度の場合だと、とっっっっっっっても小さすぎるのです。 もちろんより適切なサイズのskinを新しく作成することもできますが、面倒くさいです。 二つ目の理由は、今回使用した解像度はしっかりとしたサイズで1080p ( 16:9 )と同じアスペクト比なので、16:9画面で表示された時に正しく拡大縮小されるのです。 iPad の環境だとひどく見えづらくなるでしょう。 多分そのうち、複数の装置で解像度を制御する具体的な投稿をする予定です。

もう一つ注意すべき点があります。この例は現時点ではiOS上で動作しません。 Scene2D のTextField ウィジットは、iOSの画面コントローラー上では動作しません。 LibGDX 投稿フォーラム上に修正があります。 この修正はiOS 固有のハック/回避作なので、メインのtrunkに組み込むつもりはありません。 最後に、LibGDX に内蔵されているネットワーク処理は非常に単純なもので、ソケットプログラミングだけに限られています。 より堅牢なネットワーク処理をサポートするには、 kryonetをチェックアウトしてください。 kryonetはLibGDX でもサポートされており、HTML5を除く全てのLibGDX プラットフォーム上で動作すると報告されています。 kryonetを作成したネイサン・スイート 氏はLibGDX のコード投稿者でもあるので、とくに驚くことはないでしょう。