--------------------------------------------------------- SInet SIGame ActiveX コントロール for Win32 Version 1.10 Copyright 2000. Sato-Si --------------------------------------------------------- ※原則として このOCXを使用したソフトウエアは、商用利用、シェアウエアは不可です。 説明: 早い話が ネットワーク プログラミング エンジンです.  ネットワーク対戦ゲームを簡単に作成するための ActiveXコントロールです Win32のActiveXが使える環境なら、どこでも使うことが出来ますが TCP/IPが使用でき、かつ、TCP接続が可能な環境でのみ完全に動作します。 他にDLL等は不要です。 1つだけ、サーバとして動作し、他がクライアントとして、 そのサーバにTCPで接続します。 なお、サーバとクライアントの出来ることは同じです。 つながればサーバやクライアントを意識する必要はありません 変数は全て自動で共有するので、データの送信や受信するという ことを意識せずに、ソフト開発ができます。 また、変数は無制限に作成でき、同じ変数名でも、 一人一人、別々に用意されます。 インストール方法:  登録.bat を実行します(単にOCXを登録するだけです) ファイルリスト: SInet.txt このファイル SInet.ocx ActiveX本体のプログラム。 SInet.tlb タイプライブラリー Visual C++などのウイザードから、利用できます 説明(イメージ付き).doc Word2000のドキュメントです.イメージ付きです. 登録.bat これを実行するとシステムに登録します 登録解除.bat これを実行するとシステムに登録を外します。 Vb2 (フォルダ)Visual Basic 6.0 による、チャットソフトのサンプルが付いています その他:  まだ、バグが残っていると思うので、動作がおかしい場合は お知らせください。 他にも ご意見ご感想なども、募集しています。 また、こんなゲームが出来たよ 等も 是非ください mail@sato-si.com このソフトウエアは、フリーソフトですが、 これを、利用した、ソフトウエアの商用利用、 シェアウエアは不可です。 このソフトウエアは、Viusal C++ 6.0 SP5 + ATLを使用して作成しました 使い方:  Visual Basic 6.0での、使用例 なら、コンポーネントを追加で (メニューのプロジェクト(P)-コンポーネント(O)...で) コントロールの中のリストより、 Sato-Si SInet 1.0 を チェックを付けて、OKを押して閉じます すると、左のコントロールとかの一覧に アイコンが追加されます それが、本体で、SIGameというActiveXです それを、フォームなどに、貼り付けます 標準で、SIGame1 という、名前になります 使い方には、基本方式と、応用方式の方法があります。 ほとんどの場合は、基本方式で十分に足ります。また、 両用の方式を同時に使うこともできます。 Visual Basic風の構文で説明します。 以下の説明で[]で囲まれたものは、C++等から、利用した場合の関数名です ※プロパティ名、メソッド名には、その前に、このActiveXを識別する オブジェクトを付けます。ここでは、objとしました 概念: TCPプロトコルを接続した サーバ(接続待ち側)1つに対して、 クライアント(接続する側)つまり、接続を多数もつ、1対多の接続方式です。 サーバは、単に接続先、通信中継点として利用するだけです。 また、サーバもクライアントを一つ持たせるので、サーバも、 クライアントも、動作中の区別は ありません。 (サーバを直接意識する必要はありませんが、誰か一人だけが、サーバとして 起動する必要があり、また、その人のIPアドレス(サーバのアドレス)や、 使用するポート番号(これはソフトごとに固定、最低4桁以上で、使われてなそうな、 任意の整数を決めます)を他の接続するソフトが、一致させることによって、 通信を可能にします。 接続中のクライアントを識別するには、接続時に、idが、決定します。 (サーバが使用していないidを自動的に割り当てます。) これは、単なる0からはじまる整数値で、最大値が、obj.maxConnectsより1つ 小さい値です。同一のサーバに接続中のクライアントは、で全て 違ったidを 持っています。サーバで起動している人は、id = 0と、なっています。 このActiveXでは、ひとりひとりが、それぞれ、変数を持ちます。この変数は 無制限に増やせて、また、全ての人が、それぞれ持ち、全ての人が、全ての人の 変数を自由に見ることが出来ます。たとえば、id = 1の人の変数 "name"を 見たい場合は、 val = obj.GetVal(1,"name") とすると、valに、値が 帰ってきます。値を、セットしたい場合は、 obj.SetVal 1,"name","さとーし" [ obj.SetVal(1,"name","さとーし"); ]とすると、設定することが出来ます。 変数名は、文字列です。また、値も文字列のみです。変数名には、TAB文字(8), CR(13),LF(10), NULL文字 NUL(0)の文字や、ヌルストリング""以外なら、 いかなる 文字でも可能です。自分の変数は、 obj.SetMyVal "name","さとーし" val = obj.GetMyVal("name")などで、設定でき、こちらは、接続していない時でも 使えます。接続中は、obj.SetMyVal 自分のid, "name", "さとーし" や val = obj.GetVal(自分のid,"name") としても、アクセスできます。他の人は これで、アクセスします。  接続すると、すぐに他の全ての人の変数を受信します。 また、SetMyValで事前に設定していた変数を、全員に送信し、 全員のもっている、自分の変数を更新します(この更新の割り込みは発生しません)  obj.SetValを実行すると、全ての接続している人に、OnSetValの割り込みが 発生します。これによって、合図なども、変数に代入することで可能になります。 ※obj.SetValは、接続中のみ有効です。 ※obj.GetValは、接続中のみ有効です。しかし、自分以外の誰かが切断した直後なら  切断した人の変数を見ることは出来ます。 obj.GetVal(id,"name") & "さん がないくなりました" 接続状態を表す特殊変数があります。変数名は"cnt"です。この変数が変更される 割り込みが入ると、接続してきた、切断された、等がわかります。値は、"1"なら 接続。"0"ならば、切断。 この変数は、読み出しは自由ですが(自分や、任意のidが接続中かどうか分かる) 設定は、"0"のみ可能です。"0"を設定してしまうと、その人が切断されます(自分を含める) さらに、サーバの人を切断すると、全員切断されてしまいます。 obj.GetVal(2,"cnt") = "1" なら、id = 2の人が接続中 obj.GetVal(3,"cnt") = "0" なら、id = 3の人は接続していない obj.SetVal 2,"cnt","0" [ obj.SetVal(2,"cnt","0"); ]で、切断 ("0"以外の値は、無視されます) obj.SetMyVal "cnt","0" [ obj.SetMyVal("cnt","0"); ]で、自分を切断 ("0"以外の値は、無視されます) 自分が接続してきたかどうかは、変更された人のidが、obj.GetIDと等しくて obj.GetMyVal("cnt") = "1"な、場合です。obj.GetMyVal("cnt") = "0"の時は 自分自身が、切断されたことを表します。ただし、このときはすでに切断された あとなので、obj.GetIDは、-1を返します。 基本の方式: プロパティ: obj.serverIP [ obj.GetServerIP(val);, obj.SetServerIP(val); ] (型は文字列) サーバのIPアドレスを設定します。サーバは、"localhost"固定です "tama.co.jp" や、"192.168.1.1"等の、インターネット上の コンピューターを識別する値を入れます。 obj.portNo [ obj.GetPortNo(val);, obj.SetPortNo(val); ] (型はlong) サーバで使用するポート番号 obj.maxConnects [ obj.GetMaxConnects(val); , obj.SetMaxConnects(val); ] (型はshort) サーバが受け付けることが出来る最大の接続個数です また、IDの接続最大値 + 1の値 サーバに接続時中は、代入しても値を変更することは 出来ません。また、サーバの設定値となります。(勝手に変更しても サーバで設定した値に変更されます) メソッド: obj.StartServer [ obj.StartServer(); ] サーバで、開始します。 obj.portNo , obj.maxConnectsの設定を使用します 待ち受けに使用するサーバのポート番号は、obj.portNo 受付を行う最大の許容数は、obj.maxConnectsです。 サーバを起動して待ち受け処理を開始すると、 すぐに関数から戻ります。 正常に起動すると、  OnSetVal(id = 0,fromID = 0, key = "cnt", val = "1") が呼び出され、接続が入ると、  OnSetVal(id が0以外, fromが0以外, key="cnt", val = "1") が、呼び出されます。 なお、失敗したら、  OnSetVal(id = 0, fromID = 0, key = "cnt", val = "0") が、すぐに呼び出されます。(obj.GetMyVal("cnt")="0"なら、起動失敗) (拡張モードでは、それぞれの場合で、別の割り込みが 呼び出されます) obj.Connect [ obj.Connect(); ] サーバに接続を開始します。 obj.serverIP , obj.protNo の設定を使用します。 接続先のコンピューター名をobj.serverIPに、設定します 接続先のコンピューターで使用するポート番号をobj.protNoに 設定します。この値は、obj.StartServer を実行している 待ち受け中のコンピューターで、設定した値です。 接続のための初期化を開始すると、すぐに戻ります 正常に起動すると、OnSetValとOnConnectが呼び出されます  OnSetVal(id が0以外, fromが0以外, key="cnt", val = "1") が、呼び出されます。 なお、失敗したら、  OnSetVal(id = ?, fromID = ?, key = "cnt", val = "0") が、すぐに呼び出されます。(obj.GetMyVal("cnt")="0"なら、接続失敗) (拡張モードでは、それぞれの場合で、別の割り込みが 呼び出されます) obj.Close [ obj.Close(); ] 接続を閉じます。サーバで、動作している場合は、サーバも閉じます ( obj.SetMyVal "cnt","0"でも、同じです ) obj.SetMyVal key,val [ obj.SetMyVal(key,val); ] val = obj.GetMyVal(key) [ val = obj.GetMyVal(key); ] (key及び、valの型は、文字列です) 自分が所有する変数を作成・設定及び変更を行います。 変数は、無制限に作成できます。変数名や、値には、TAB文字(8), 改行文字 CR(13) LF(10) NULL文字 NUL(0)の文字や、 ヌルストリング""以外なら、いかなる 文字でも可能です。 この関数は、接続していないときでも、実行できます。 SetMyVal "name","さとーし" ' 自分の変数"name"で、値を "さとーし"とします val = obj.GetMyVal("name") ' 自分の変数"name"から、valに値を取り出します ただし、変数名が"cnt"の時は、(これは、接続状態を表す特殊変数です) 接続状態を返します。これには、"0"のみ、代入出来ます。 0を代入すると、自分の接続を切断します(obj.Closeと同じです) "0"(文字列)を返すと、未接続。( if obj.GetMyVal("cnt") = "0" then ) "1"(文字列)を返すと、接続中でサーバ稼働中( if obj.GetMyVal("cnt") = "1" then ) obj.SetVal id, key,val [ obj.SetVal(id, key, val); ] val = obj.GetVal(id,key) [ val = obj.GetVal(id, key); ] (id は、shortです key及び、valの型は、文字列です) これは、idで識別される任意の人の任意の変数を設定したり、取得したりします。 これも変数は、無制限に作成できます。変数名や、値には、TAB文字(8), 改行文字 CR(13) LF(10) NULL文字 NUL(0)の文字や、 ヌルストリング""以外なら、いかなる 文字でも可能です。 この関数は、接続していないと使えません。(呼び出しは無視されます) 接続中しか使えませんが、接続状態変数"cnt"だけは、いつでも アクセスする事が、出来ます。 これには、"0"のみ、代入出来ます。 "0"を代入すると、その人の回線を切断します。 "0"(文字列)を返すと、未接続。( if obj.GetVal(id,"cnt") = "0" then ) "1"(文字列)を返すと、接続中でサーバ稼働中( if obj.GetVal(id, "cnt") = "1" then ) idは、接続時にサーバから、もらえます。自分のidは、obj.GetID() で取得できます つまり、val = obj.GetVal(obj.GetID,key) は、val = obj.GetMyVal(key) と同じです。 しかし、GetMyValや、SetMyValは、接続していない状態でも使えます。 val = GetID [ val = GetID(); ] (valは、short) 自分を識別するidを取得します。 接続していない状態では -1 を返します。(これは無効なidです) イベント: OnSetVal(id, fromID, key, val) (idとfromIDはshort, keyとvalは、文字列) 自分が、接続した、切断した、誰かが接続した、切断した、そして 誰かの変数が変更された場合に 呼び出されます。 idは、変更された変数を持っている人を識別するid fromIDは、変数の変更を実行した人を識別するid keyは変数名、valは、変更後の値。 ただし、obj.GetVal(id,key)は、直前の値を返します。 この、サブルーチン(関数)から、抜けると、値が更新されます。 出来る限り、同じ変数へのアクセスは行わない 設計にする方が、良いでしょう(わかりにくいバグのもとになります) key = "cnt"の場合は、特別で、サーバを開始した や 自分がサーバに接続した、自分が、切断した 誰かが、サーバに接続した、誰かがサーバから切断されたを 意味します。 val の値が、"1"ならば、idの人が新たに接続してきた事を意味します。 id = obj.GetID ならば、自分が接続したことを意味します。 それに対して、valの値が"0"ならば、idの人が切断したことを意味します。 このとき、obj.GetIDの戻り値がIDが-1ならば、 自分が切断されたことになります。また、サーバ起動や、接続処理を 失敗した場合も、これになります。 また、GetVal(id,"cnt")や、GetMyVal("cnt")が返す値も直前の物ではなく 現在の状態を表します。 応用の方式: メソッド: val = obj.IsConnecting() 自分が接続しているかどうかを返します obj.GetMyVal("cnt")でも同じ様な事が出来ます val = obj.IsComing(id) [ val = obj.IsComming(id); ] idで識別出来る人が来ているかどうか obj.GetConnects 現在、サーバに接続しているクライアントの個数 (参加人数) obj.CloseAt id [ obj.CloseAt(id); ] obj.SetVal id, "cnt","0"と 同じで、idの人を強制切断します val = obj.IsFail [ val = obj.IsFail(); ] 直前に呼び出した、メソッドの処理が失敗したかどうかを 返します。(しかし、この値もあまりあてになりません(汗)) obj.SendString id,hint,val 個別に idの人へ データを送信します。受け側は OnRecvStringのイベントで受信します hint は、データの識別子(文字列)で自由に設定できます val は、 イベント: OnConnect(id) 自分が接続したときに割り込みが発生します。 idは、自分のIDです OnClose() 自分が切断されたときに割り込みます OnCome(id) 自分以外の誰かが、接続してきたときに割り込みます idは、そのひとを識別するIDです OnLeave(id) 自分以外の誰かが、去ったときに割り込みます idは、その人を識別するIDです OnRecvString(fromID, hint, val) obj.SendString id,hint,val で送られた データを受信します。 fromIDは、送信もとのid hintは obj.SendString の引数のhint valは、obj.SendString の引数のval 現実的な、プログラミングにおける、問題点と、解決方法: フォルダVB2に、サンプルプログラムがありますが、 他にも実際開発していて必要なテクニックを紹介します。 ここでは、このアクティブXにSIGame1とい、 オブジェクト名が割り当てられていると仮定します 値更新の同期の必要性: 例えば、キャラクターの位置を座標(X,Y)で管理しているとします。 キャラクターが移動したら、 SIGame1.SetMyVal "X", x ' X座標を更新 SIGame1.SetMyVal "Y", y ' Y座標を更新 したとして、 OnSetValが呼ばれると、キャラを動かすことにします Moveという、 サブルーチンで動くとします Private Sub SIGame1_OnSetVal(ByVal id As Integer, ByVal fromID As Integer, ByVal key As String, ByVal val As String) If key = "X" or key = "Y" Then ' 座標の変更を検出 Move id, SIGame.GetVal(id, "X"), SIGame.GetVal(id, "Y") ' 移動 End If End Sub しかし、問題が、あります。 どういうことかというと、"X"の変数が更新されたときは まだ、"Y"の値が、更新されていないと言うことです。"X"が更新された だけで、移動をしてしまうと、おかしな事になります。 回答例 キャラクターが移動したら、 SIGame1.SetMyVal "X",x ' X座標を更新 SIGame1.SetMyVal "Y",y ' Y座標を更新 SIGame1.SetMyVal "update", "pos" ' 座標を更新、という、メッセージを発行 それの処理は、 Private Sub SIGame1_OnSetVal(ByVal id As Integer, ByVal fromID As Integer, ByVal key As String, ByVal val As String) If key = "update" Then ' 何かが更新された If val = "pos" Then ' 座標の更新だ! Move id, SIGame.GetVal(id,"X"), SIGame.GetVal(id,"Y") ' 移動 End If End If End Sub このように、値を更新したぞ、という、変数をさらに用意して、その変数に どれが更新されたか等を 設定してやると効率的でしょう。 配列を共有するには: 配列データは、扱えません。が、配列のデータを共有する事は可能です どうするかというと、それぞれが、配列をグローバル変数で保持します。 そして、値の更新をいつも同じサブルーチンで行います 例えば、5人で、配列を共有する場合は以下のようにします。 Dim d(4, 10, 10) As Integer Sub SetMyValD(x As Integer, y As Integer, val As Integer) SIGame1.SetMyVal "dx", x SIGame1.SetMyVal "dy", y SIGame1.SetMyVal "dval", val End Sub Private Sub SIGame1_OnSetVal(ByVal id As Integer, ByVal fromID As Integer, ByVal key As String, ByVal val As String) If key = "dval" Then ' 配列が更新された d(id, SIGame1.GetVal(id,"dx"),SIGame1.GetVal(id,"dy")) = val End If End Sub この場合は、接続していない状態では、設定できません。なぜなら、 接続してない状態では、SIGame1_OnSetValが、呼び出されず、配列dが更新 されないからです。対策としては、接続前に、あらかじめ、別の配列を 用意しておき、接続していないときはそこに、代入するようにします。 そして、接続したら、For文などを使用して、一斉に送信をします。 Dim d_off(10, 10) As Integer Dim d(4, 10, 10) As Integer Sub SetMyValD(x As Integer, y As Integer, val As Integer) If SIGame1.GetMyVal("cnt") = "0" Then ' 特殊変数が0なら、接続してない d_off(x, y) = val Exit Sub ' サブルーチンから抜ける End If SIGame1.SetMyVal "dx", x SIGame1.SetMyVal "dy", y SIGame1.SetMyVal "dval", val End Sub Private Sub SIGame1_OnConnect(int id) ' 応用方式で自分が接続したという割り込み Dim i As Integer, j As Integer For i = 0 To 10 For j = 0 To 10 SIGame1.SetMyVal "dx", i SIGame1.SetMyVal "dy", j SIGame1.SetMyVal "dval", d_off(i, j) Next Next End Sub Private Sub SIGame1_OnSetVal(ByVal id As Integer, ByVal fromID As Integer, ByVal key As String, ByVal val As String) If key = "dval" Then ' 配列が更新された d(id, SIGame1.GetVal(id,"dx"),SIGame1.GetVal(id,"dy")) = val End If End Sub 色々手法や、テクニックは アイデア次第です。 良いアイデアが、あれば、私のも教えてください。^^ 変更内容: 2000/10/05 Ver 1.0 とりあえずこっそり 公開 2000/10/06 Ver 1.0 接続状態を表す特殊変数"cnt"に値を"0"のみ代入可能にする(切断機能)表示中のSIGameの文字を小さくする 2000/10/07 Ver 1.0 OnSetValの割り込みの仕様を変更して、値を設定前に、割り込むようにする。それによって 直前の値を取得出来るようにした 2000/10/09 Ver 1.02 OnSetValの割り込みの仕様を変更したときに生じた バグを修正(自分の変数値は割り込み前になっていなかった) サーバの接続許容数が6を越えると、どのidが接続中かを 正しく検出出来ないバグを修正。(プロトコル自体を少し修正) 2000/10/14 Ver 1.03 OnRecvStringが動作していないバグを修正 値に CR/LF/TABのコードが含まれていても 正常に動作するように変更 2000/10/16 Ver 1.04 異常終了してしまうことがある バグを修正 他システムの安全性を向上させる修正を入れる 2000/11/04 Ver 1.04a バグを取る実用に耐えるレベルにする 2001/03/33 Ver 1.10 内部のエンジンの安定性を強化.メモリの再確保の量を減らす.データ保護を強化「説明(イメージ付き).doc」を追加