大年初一不免俗的要來:
圖:偉大的背景姊 Mii
這是我們的畢業製作,變秘危機,
5.1號開始在新一代展出~
工商時間結束,好了拉回來XD,
今天要記錄的是如何用wifi將電腦跟手機連在一起,
之後還會寫GPS的使用,主要是因為實習需要做AR導航應用所以才稍有研究,
那麼我們就廢話不多說開始吧:
---
這邊不討論unity的手機平台怎麼輸出apk,有需求的請拿香拜一拜大神應該就有了(?),
首先就從unity怎麼連線開始說吧。
unity要連線的方法有很多種,我使用的方法是一種叫做masterServer的東西,
主要概念是一方用區網的ip架server(伺服器端),
並給一些資訊供client(客戶端)做辨識進而連線,
比起藍芽還需要手動配對來說,masterserver可以不到一秒的時間就連上線,
並可隨時中斷server端以及client端的程式,亦可隨時重新連接,
去掉必須要有wifi的限制,其實還挺方便的。
要連線的話雙方(客戶&伺服器端)都得加入一個Network View元件,
你可以開一個空的gameobject來放,放好NV元件後,
接著就可以將script放進來了,至於你是server還是client端就看你的程式怎麼寫,
我這邊會寫兩個,一個是server的script,一個是client的script,
使用JavaScript,C#的使用者不好意思,請自己轉換囉
。
先從server端的script開始吧:
function OnGUI() {
if (GUILayout.Button ("Start Server")) {
Network.InitializeServer(1, 25002, !Network.HavePublicAddress());
MasterServer.RegisterHost("YourPCRoom", "PhantasyNan", "alpha 1.0");
}
GUI.Label(new Rect(Screen.width* 3 /4,10,100,25), "Connections: " +Network.connections.Length);
}
就這樣(好少!?),
來解釋一下吧,加了NV元件之後:
function OnGUI()
↑在遊戲畫面上(OnGUI是繪圖用的function)
if (GUILayout.Button ("Start Server")) {
↑畫了一個按鈕叫做Start Server,若這個按鈕被點擊
Network.InitializeServer(1, 25002, !Network.HavePublicAddress());
↑就架server,InitializeServer底下三個參數分別代表:
【可多少人連,連線的通道,是否用NAT連線】
人數你可以自己訂,port也可以自己打(應該),
第三個是防範用,若沒有公用IP (ex:沒wifi的時候),則轉NAT。
MasterServer.RegisterHost("YourPCRoom", "PhantasyNan", "alpha 1.0");
↑給定一個專有名稱供Client辨識用,RegisterHost底下三個參數分別代表:
【通關密語(?),遊戲名稱,註解】
這邊的通關密語官方文件稱呼為遊戲類型,只是我都叫他通關密語XD,
因為這個參數到時候是要給client辨識的,必須是個獨特的名稱,
可別因為你做的遊戲是動作遊戲,結果都打act啊啊...!
第二個跟第三個則是會顯示在client上的資訊,要打什麼皆可自訂。
GUI.Label(new Rect(Screen.width* 3 /4,10,100,25), "Connections: " +Network.connections.Length);
↑Network底下的connections底下的Length這個參數,會回傳有多少人連到你的server端,
故這一行是將多少人連進來的資訊顯示在螢幕上。
這樣Server端就做好前置作業了,只要執行,按下按鈕,
就會架好一個server了~ (記得開啟wifi)
不得不說前陣子我用flash做行動裝置跟電腦連,
比這個困難多了... ...
unity在後面幫你寫了很多東西,因此可以很輕鬆就製作連線的功能。
---
接著要談client端的script:
var showDetail:boolean;
function Update(){
MasterServer.RequestHostList("YourPCRoom");
}
function OnGUI() {
if (GUILayout.Button("Connect to your Computer")){
showDetail = !showDetail;
}
var data : HostData[] = MasterServer.PollHostList();
for (var element in data)
{
if(showDetail)
{
GUILayout.BeginHorizontal();
var name = element.gameName + " " + element.connectedPlayers + " / " + element.playerLimit;
GUILayout.Label(name);
GUILayout.Space(5);
var hostInfo;
hostInfo = "[";
for (var host in element.ip)
hostInfo = hostInfo + host + ":" + element.port + " ";
hostInfo = hostInfo + "]";
GUILayout.Space(5);
GUILayout.Label(element.comment);
GUILayout.Space(5);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Connect"))
{
Network.Connect(element);
}
GUILayout.EndHorizontal();
}
}
}
好了,就這樣(不對),
怎麼比起之前的長了不少?其實不多,裡面有一大串都在做排版XD,
就像網路遊戲的房間那樣,將可以連的裝置分行排好,做成清單而已。
來解釋一下吧,加了NV元件之後:
var showDetail:boolean;
↑新增一個布林參數,當作詳細資訊的開關鈕。
function Update(){
↑每個frame更新一次的function
MasterServer.RequestHostList("YourPCRoom");
↑拿著通關密語的IC卡在網域中跑啊跑,看哪個電子門可以被開(?)
RequestHostList裡面的參數就是剛剛server端打的通關密語,
在client端得預先寫好你有的通關密語。
function OnGUI() {
↑在遊戲畫面上(OnGUI是繪圖用的function)
if (GUILayout.Button("Connect to your Computer")){
↑畫了一個按鈕叫做Connect to your Computer,若這個按鈕被點擊
showDetail = !showDetail;
↑將showDetail這個開關切換(開>關,關>開)。
var data : HostData[] = MasterServer.PollHostList();
↑每個frame檢查一次有幾個server端可連線,並將這些資訊放入data這個參數中。
(OnGUI是每個frame更新一次,因為要繪圖)
for (var element in data)
↑使用迴圈,將data用element替代。
if(showDetail)
↑若這個按鈕被打開,顯示以下資訊。
GUILayout.BeginHorizontal();
↑開始繪圖,這可以將繪製的東西都放在同個群組下。
var name = element.gameName + " " + element.connectedPlayers + " / " + element.playerLimit;
GUILayout.Label(name);
GUILayout.Space(5);
var hostInfo;
hostInfo = "[";
for (var host in element.ip)
hostInfo = hostInfo + host + ":" + element.port + " ";
hostInfo = hostInfo + "]";
GUILayout.Space(5);
GUILayout.Label(element.comment);
GUILayout.Space(5);
GUILayout.FlexibleSpace();
↑這邊都在排版(欸),並將可連線的server排好。
if (GUILayout.Button("Connect"))
↑畫了一個按鈕叫做Connect,若這個按鈕被點擊
Network.Connect(element);
↑連到那個server
GUILayout.EndHorizontal();
↑關閉繪圖。
這樣client端也做好前置作業了~
希望打了這麼多客官們不要頭昏腦脹才好,
總之先放個影片壓壓驚:
依舊是工商時間,
這是我們的畢業製作,變秘危機,
5.1號開始在新一代展出~ 來捧個場嘛
,
---
回來正題,從剛剛到現在,我們完成了:
「Server架設,Client連接」
雙方的橋梁已經做好了,那再來要做的就是將要互相傳送的資料送出去,
橋做好了就是給人過的嘛(?),因此接著要寫該怎麼將資料互通有無。
傳輸資料的方法似乎也有不少,
雖然都是橋,但作法材料跟樣貌都有不同,
而我這邊使用的作法是RPC,
希望這樣講會比較好理解。
RPC不僅限於Client到Server,Server到Client也是可以同步處理的,
關於RPC的使用我使用範例來講會比較清楚。
*假設我要將某些資料從Client端送到Server端:
我想要每個frame就更新一次,所以我放在update,基本上要何時呼叫是隨意的,
以下紅字為client增加的script:
var cWeapon:int;
var sWeapon:int;
var create:boolean;
function Update(){
MasterServer.RequestHostList("YourPCRoom");
if(create)GetComponent.<NetworkView>().RPC("Attack", RPCMode.All, cWeapon);
}
function OnGUI() {
if (GUILayout.Button("Connect to your Computer")){
showDetail = !showDetail;
}
var data : HostData[] = MasterServer.PollHostList();
for (var element in data)
{
if(showDetail)
{
GUILayout.BeginHorizontal();
var name = element.gameName + " " + element.connectedPlayers + " / " + element.playerLimit;
GUILayout.Label(name);
GUILayout.Space(5);
var hostInfo;
hostInfo = "[";
for (var host in element.ip)
hostInfo = hostInfo + host + ":" + element.port + " ";
hostInfo = hostInfo + "]";
GUILayout.Space(5);
GUILayout.Label(element.comment);
GUILayout.Space(5);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Connect"))
{
Network.Connect(element);
create = true;
}
GUILayout.EndHorizontal();
}
}
}
@RPC
function Attack(W:int){
sWeapon = W;
}
@script RequireComponent(NetworkView)
只有增加不到十行,有沒有很少XD
假設我client端為手機,server端是電腦,
我希望在手機上選擇武器時,電腦可以隨時更換我選到的武器,
於是我在client的script創建了兩個參數,一個是sWeapon,一個是cWeapon,
分別代表server跟client的武器值,
也許0代表空手,1代表手槍,2代表小刀... ...之類的。
那麼來解釋一下剛剛新增的代表什麼意思:
var cWeapon:int;
var sWeapon:int;
var create:boolean;
if(create)GetComponent.<NetworkView>().RPC("Attack", RPCMode.All, cWeapon);
↑獲取一開始新增的NetworkView元件底下的RPC,
底下三個參數分別代表:
【呼叫的function,要送到那些地方,給function的其他參數】
第一個就好像步驟,第二個是送給誰,第三個則是我會丟入步驟的材料,
所以這串的白話意思就是:
---
「我將【材料】丟到【步驟】裡,一起打包送到指定的地方。」
---
【步驟】我寫在script的最下方,等等就會講到;
送的對象(RPCMode)可單獨送給Server,或是All(包括自己),或是Others(除了自己),
剛剛不是有建立橋樑麼?如果沒有橋梁當然就只能送給自己囉...;
而【材料】可以很多個,你寫越多,該套【步驟】就會有越多個【材料】可以拿來運算。
create = true;
↑當前面說的橋梁建好之後才能開始送貨,如果你沒有做這個開關的話,
程式一執行就會不停地送貨,不停地墜河,不停地報錯,
這點要注意一下XD。
@RPC
↑在寫【步驟】之前會先有RPC的標記,這樣程式才看得懂那串是送貨用的function。
function Attack(W:int){
↑這是我的【步驟】,叫做Attack,W則是會存放呼叫時丟入的【材料】,
若丟入的【材料】越多,括弧裡面的參數也要隨之增多才行。
sWeapon = W;
↑我的【步驟】是將sWeapon的數值換成W這個【材料】。
@script RequireComponent(NetworkView)
↑告知這裡是RPC尾端,並跟unity說我這包需要用到Network View才可以讀取。
呼,
寫到這裡,總算是將貨物跑過流程送出去了,
最後的問題就是server端該怎麼收貨了。
---
server端的script(紅字為增加的script):
function OnGUI() {
if (GUILayout.Button ("Start Server")) {
Network.InitializeServer(1, 25002, !Network.HavePublicAddress());
MasterServer.RegisterHost("YourPCRoom", "PhantasyNan", "alpha 1.0");
}
GUI.Label(new Rect(Screen.width* 3 /4,10,100,25), "Connections: " +Network.connections.Length);
}
@RPC
function Attack(W:int){
sWeapon = W;
}
@script RequireComponent(NetworkView)
Server端輕鬆很多,只要記得創建自己需要的以及【步驟】內有的參數(不然會報錯),
以及複寫一次【步驟】以供核對,
當貨通過橋送來的時候,
收件的那方就會拿對方的【步驟】單跟自己的【步驟】單核對一下,
然後將附上的【材料】套入【步驟】跑一次就算收件完成。
依照慣例,
解釋一下剛剛新增的分別代表什麼意思:
var sWeapon:int;
↑新增一個整數。
@RPC
↑宣告我這邊之下是RPC用的function喔~
function Attack(W:int){
↑這是我的【步驟】,叫做Attack,W則是會存放貨送過來時附上的【材料】,
若寄件者丟入的【材料】越多,收貨時得到的【材料】越多。
sWeapon = W;
↑我的【步驟】是將sWeapon的數值換成W這個【材料】,
因此Server端就會將自己的sWeapon換成送來的【材料】W,
而Client那邊也是有一個sWeapon,那其實沒什麼用,只是因為沒假設的話就會報錯。
@script RequireComponent(NetworkView)
↑告知這裡是RPC尾端,並跟unity說我這包需要用到Network View才可以讀取。
額外要提的注意事項是,
用電腦當server的時候,輸出PC執行檔打開,會跳出防火牆的允許訊息,
允許之後雖然client可以連上,但是關掉之後再開就不能使用了,
因為再次打開時就不會跳允許的視窗了
,會被自動擋掉,
關於這點我還不確定有什麼好方法,
不過暫時關掉防火牆可以立即解決這個窘境X"D,
有空試試有沒有別的比較安全的作法,找到會在更新這篇文。
---
好了,打了一個早上終於結束了!!
基本上這份教學就到這裡為止,
以下是使用這樣的方法製作的遊戲 - 的遊玩影片,
完成度不高,只有兩關,
基本上這只是我自己做著玩的而已:
【音樂節奏遊戲】
再來是當時做好這個功能時的測試影片:
【功能測試】
最後覆蓋一張圖當結尾:
啊!我是小草!很榮幸參與這個動畫!
有這樣壯烈的出場機會真是非常感謝!!
圖:霸氣的 S 動畫大大
這是我們的畢業製作,變秘危機,
5.1號開始在新一代展出~ 來嘛來嘛
,
---
下台一鞠躬。
有哪裡有問題或是錯誤的請提出,歡迎討論,謝謝。