前回に続いてBluetoothを使ってみる。
前回までの処理をどんどん改造してみます。
とりあえず今回までで終わりの予定。
まずサーバとクライアントの接続を一対多を前提としてみる。
公式サンプルでは一対一前提なのでスレッドを入れ子にして対応する。
ゲームを作るとしてほしい機能としてこんなものがありそう。
◆ サーバの起動(他端末からSCAN可能にしてaccept+new Threadのループ)
◆ クライアントのサーバースキャン(サーバ一覧の名前とアドレスを取得)
◆ クライアントのサーバ接続(Unityでサーバ一覧から選択して実行)
◆ サーバの受付終了(ゲーム開始時)
◆ 端末名の取得と変更(ついでの機能)
また状況に応じてメッセージのやり取りを行うようにする。
一対多用のスレッド修正はこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
... private class ServerControlThread extends Thread { BluetoothServerSocket sSocket; BluetoothSocket socket; boolean isClose = false; ServerControlThread() { try { sSocket = adapter.listenUsingRfcommWithServiceRecord(activity.getPackageName(), uid); } catch (IOException e) { IOExpHandle(e); } } public void run() { try { while (true) { socket = sSocket.accept(); if(isClose)break; ServerThread st = new ServerThread(socket); st.start(); serverThreads.add(st); SendUnity("CNT",socket.getRemoteDevice().getName()); //接続されたデバイスを返す } } catch (IOException e) { IOExpHandle(e); } finally { close(); } } public void close(){ try { isClose=true; if(sSocket!=null)sSocket.close(); } catch (IOException e) { IOExpHandle(e); } } } private abstract class TransThread extends Thread { protected BluetoothSocket socket; protected String msg; //socket message protected void send(String msg) throws IOException { socket.getOutputStream().write((msg+"\n").getBytes()); } protected void loop(boolean isServer) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while ((msg = br.readLine()) != null) { if(isServer)SendMessage(msg); //クライアントから受け取ったメッセージを再配信 SendUnity("MSG",msg); //受け取ったことを返す } } } private class ServerThread extends TransThread { private ServerThread(BluetoothSocket bs) { socket = bs; } public void run() { try {loop(true);} catch (IOException e) { IOExpHandle(e); } } } private class ClientThread extends TransThread { private final BluetoothDevice device; private ClientThread(String address) { device = adapter.getRemoteDevice(address); try { socket = device.createRfcommSocketToServiceRecord(uid); } catch (IOException e) { IOExpHandle(e); } } public void run() { adapter.cancelDiscovery(); //検出中止 try { socket.connect(); loop(false); } catch (IOException e) { IOExpHandle(e); } finally { try { socket.close(); } catch (IOException e) { IOExpHandle(e); } } } } ... |
accept() と readLine() がブロック処理かつループなのでスレッドで処理してます。
BluetoothServerSocketを閉じるところはどうやってもエラーを吐くのでエラーハンドルで無視するくらいしかなさそう。acceptを安全に中止する方法がない。
メッセージ送信の部分はサーバ送信で全てのクライアントに送られるようにする。
1 2 3 4 5 6 |
public void SendMessage(String msg) { try { for(ServerThread st : serverThreads){ st.send(msg); } if (clientThread != null) { clientThread.send(msg); } } catch (IOException e) { IOExpHandle(e); } } |
スレッドの方でサーバがメッセージを受け取った場合はこのメソッドも呼ばれるので全体に配信されることになる。
サーバで何か処理した後に配信する場合にはスレッドからの呼び出しは必要ない。
サーバの受付終了と端末名の取得・変更は簡単にできた。
1 2 3 |
public void CloseServer(){ sct.close(); } public void SetDeviceName(String name){ adapter.setName(name); } public String GetDeviceName(){ return adapter.getName(); } |
レシーバーはほとんど変更してないけど検出時にUnityに返すようにしている。
1 2 3 4 5 6 7 8 9 10 11 |
private final BroadcastReceiver receiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals( action )) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); SendUnity("SCAN", device.getName() + "##" + device.getAddress()); } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals( action )) { context.unregisterReceiver(receiver); } } }; |
実際にUnityに返す処理はこんな感じに変更。
1 2 3 |
private void SendUnity(String mode,String msg){ UnityPlayer.UnitySendMessage(unityObjName,unityFuncName,mode+"::"+msg); } |
Unity側ではメッセージを受け取ったら分解して適当な処理をする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public void BluetoothGetMsg (string msg) { string[] _sp1=new []{"::"}, _sp2=new []{"##"}; StringSplitOptions _op = StringSplitOptions.RemoveEmptyEntries; string mode = msg.Split(_sp1,_op)[0]; string[] msgs = msg.Split(_sp1,_op)[1].Split(_sp2,_op); switch( mode ){ case "SCAN": //called by Client AddServerBtn(msgs[0],msgs[1]); //接続できるサーバを表示 break; case "CNT": //called by Server AddClientBoard(msgs[0]); //接続されたクライアントを表示 break; case "MSG": Message(msgs[0]); //メッセージ処理 break; } } |
これでUnity側からBluetoothを利用できるようになったはず。
とりあえず1つアプリでも作ってみて変更が必要そうなら追記します。