• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            牽著老婆滿街逛

            嚴(yán)以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            [轉(zhuǎn)載]Flash為客戶端的多人網(wǎng)絡(luò)游戲的實(shí)現(xiàn)

            來(lái)源:http://outspace.spaces.live.com/

            多人網(wǎng)絡(luò)游戲的實(shí)現(xiàn)

            項(xiàng)目開發(fā)的基本硬件配置
            一臺(tái)普通的pc就可以了,
            安裝好windows 2000和vc6就可以了,
            然后連上網(wǎng),局域網(wǎng)和internet都可以,

            接下去的東西我都簡(jiǎn)化,不去用晦澀的術(shù)語(yǔ),

            既然是網(wǎng)絡(luò),我們就需要網(wǎng)絡(luò)編程接口,
            服務(wù)器端我們用的是winsock 1.1,使用tcp連接方式,

            [tcp和udp]
            tcp可以理解為一條連接兩個(gè)端子的隧道,提供可靠的數(shù)據(jù)傳輸服務(wù),
            只要發(fā)送信息的一方成功的調(diào)用了tcp的發(fā)送函數(shù)發(fā)送一段數(shù)據(jù),
            我們可以認(rèn)為接收方在若干時(shí)間以后一定會(huì)接收到完整正確的數(shù)據(jù),
            不需要去關(guān)心網(wǎng)絡(luò)傳輸上的細(xì)節(jié),
            而udp不保證這一點(diǎn),
            對(duì)于網(wǎng)絡(luò)游戲來(lái)說(shuō),tcp是普遍的選擇。

            [阻塞和非阻塞]
            在通過(guò)socket發(fā)送數(shù)據(jù)時(shí),如果直到數(shù)據(jù)發(fā)送完畢才返回的方式,也就是說(shuō)如果我們使用send( buffer, 100.....)這樣的函數(shù)發(fā)送100個(gè)字節(jié)給別人,我們要等待,直到100個(gè)自己發(fā)送完畢,程序才往下走,這樣就是阻塞的,
            而非阻塞的方式,當(dāng)你調(diào)用send(buffer,100....)以后,立即返回,此時(shí)send函數(shù)告訴你發(fā)送成功,并不意味著數(shù)據(jù)已經(jīng)向目的地發(fā)送完畢,甚至有可能數(shù)據(jù)還沒有開始發(fā)送,只被保留在系統(tǒng)的緩沖里面,等待被發(fā)送,但是你可以認(rèn)為數(shù)據(jù)在若干時(shí)間后,一定會(huì)被目的地完整正確的收到,我們要充分的相信tcp。
            阻塞的方式會(huì)引起系統(tǒng)的停頓,一般網(wǎng)絡(luò)游戲里面使用的都是非阻塞的方式,
            ——————————————————————
            注意,僅僅用flash作為客戶端,
            服務(wù)器端,我們使用vc6,
            我將陸續(xù)的公開服務(wù)器端的源代碼和大家共享,
            并且將講解一些網(wǎng)絡(luò)游戲開發(fā)的原理,
            希望對(duì)此感興趣的朋友能夠使用今后的資源或者理論開發(fā)出完整的網(wǎng)絡(luò)游戲。
            我們從簡(jiǎn)單到復(fù)雜,
            從棋牌類游戲到動(dòng)作類的游戲,
            從2個(gè)人的游戲到10個(gè)人的游戲,
            因?yàn)楣ぷ髅Φ年P(guān)系,我所做的一切僅僅起到拋磚引玉的作用,
            希望大家能夠熱情的討論,為中國(guó)的flash事業(yè)墊上一塊磚,添上一片瓦。

            現(xiàn)在的大型網(wǎng)絡(luò)游戲(mmo game)都是基于server/client體系結(jié)構(gòu)的,
            server端用c(windows下我們使用vc.net+winsock)來(lái)編寫,
            客戶端就無(wú)所謂,
            在這里,我們討論用flash來(lái)作為客戶端的實(shí)現(xiàn),

            實(shí)踐證明,flash的xml socket完全可以勝任網(wǎng)絡(luò)傳輸部分,
            在別的貼子中,我看見有的朋友談?wù)搈sn中的flash game
            他使用msn內(nèi)部的網(wǎng)絡(luò)接口進(jìn)行傳輸,
            這種做法也是可以的,
            我找很久以前對(duì)于2d圖形編程的說(shuō)法,"給我一個(gè)打點(diǎn)函數(shù),我就能創(chuàng)造整個(gè)游戲世界",
            而在網(wǎng)絡(luò)游戲開發(fā)過(guò)程中,"給我一個(gè)發(fā)送函數(shù)和一個(gè)接收函數(shù),我就能創(chuàng)造網(wǎng)絡(luò)游戲世界."

            我們抽象一個(gè)接口,就是網(wǎng)絡(luò)傳輸?shù)慕涌冢?br />對(duì)于使用flash作為客戶端,要進(jìn)行網(wǎng)絡(luò)連接,
            一個(gè)網(wǎng)絡(luò)游戲的客戶端,
            可以簡(jiǎn)單的抽象為下面的流程
            1.與遠(yuǎn)程服務(wù)器建立一條長(zhǎng)連接
            2.用賬號(hào)密碼登陸
            3.循環(huán)
            接收消息
            發(fā)送消息
            4.關(guān)閉

            我們可以直接使用flash 的xml socket,也可以使用類似msn的那種方式,
            這些我們先不管,我們先定義接口,
            Connect( "127.0.0.1", 20000 ); 連接遠(yuǎn)程服務(wù)器,建立一條長(zhǎng)連接
            Send( data, len ); 向服務(wù)器發(fā)送一條消息
            Recv( data, len ); 接收服務(wù)器傳來(lái)的消息


            說(shuō)明一下普通網(wǎng)絡(luò)游戲中windows下面網(wǎng)絡(luò)傳輸?shù)脑恚?br />——————————————————————
            ?
            [有狀態(tài)服務(wù)器和無(wú)狀態(tài)服務(wù)器]
            在c/s體系中,如果server不保存客戶端的狀態(tài),稱之為無(wú)狀態(tài),反之為有狀態(tài),

            在這里要強(qiáng)調(diào)一點(diǎn),
            我們所說(shuō)的服務(wù)器不是一臺(tái)具體的機(jī)器,
            而是指服務(wù)器應(yīng)用程序,
            一臺(tái)具體的機(jī)器或者機(jī)器群組可以運(yùn)行一個(gè)或者多個(gè)服務(wù)器應(yīng)用程序,

            我們的網(wǎng)絡(luò)游戲使用的是有狀態(tài)服務(wù)器,
            保存所有玩家的數(shù)據(jù)和狀態(tài),
            ——————————————————————
            一些有必要了解的理論和開發(fā)工具

            [開發(fā)語(yǔ)言]
            vc6
            我們首先要熟練的掌握一門開發(fā)語(yǔ)言,
            學(xué)習(xí)c++是非常有必要的,
            而vc是windows下面的軟件開發(fā)工具,
            為什么選擇vc,可能與我本身使用vc有關(guān),
            而且網(wǎng)上可以找到許多相關(guān)的資源和源代碼,

            [操作系統(tǒng)]
            我們使用windows2000作為服務(wù)器的運(yùn)行環(huán)境,
            所以我們有必要去了解windows是如何工作的,
            同時(shí)對(duì)它的編程原理應(yīng)該熟練的掌握

            [數(shù)據(jù)結(jié)構(gòu)和算法]
            要寫出好的程序要先具有設(shè)計(jì)出好的數(shù)據(jù)結(jié)構(gòu)和算法的能力,
            好的算法未必是繁瑣的公式和復(fù)雜的代碼,
            我們要找到又好寫有滿足需求的算法,
            有時(shí)候,最笨的方法同時(shí)也是很好的方法,
            很多程序員沉迷于追求精妙的算法而忽略了宏觀上的工程,
            花費(fèi)了大量的精力未必能夠取得好的效果,

            舉個(gè)例子,
            我當(dāng)年進(jìn)入游戲界工作,學(xué)習(xí)老師的代碼,
            發(fā)現(xiàn)有個(gè)函數(shù),要對(duì)畫面中的npc位置進(jìn)行排序,
            確定哪個(gè)先畫,那個(gè)后畫,
            他的方法太“笨”,
            任何人都會(huì)想到的冒泡,
            一個(gè)一個(gè)去比較,沒有任何的優(yōu)化,
            我當(dāng)時(shí)想到的算法就有很多,
            而且有一大堆優(yōu)化策略,
            可是,當(dāng)我花了很長(zhǎng)時(shí)間去實(shí)現(xiàn)我的算法時(shí),
            發(fā)現(xiàn)提升的那么一點(diǎn)效率對(duì)游戲整個(gè)運(yùn)行效率而言幾乎是沒起到什么作用,
            或者說(shuō)雖然算法本身快了幾倍,
            可是那是多余的,老師的算法雖然“笨”,
            可是他只花了幾十行代碼就搞定了,
            他的時(shí)間花在別的更需要的地方,
            這就是他可以獨(dú)自完成一個(gè)游戲,
            而我可以把一個(gè)函數(shù)優(yōu)化100倍也只能打雜的原因

            [tcp/ip的理論]
            推薦數(shù)據(jù)用tcp/ip進(jìn)行網(wǎng)際互連,tcp/ip詳解,
            這是兩套書,共有6卷,
            都是國(guó)外的大師寫的,
            可以說(shuō)是必讀的,
            ——————————————————————
            網(wǎng)絡(luò)傳輸中的“消息”

            [消息]
            消息是個(gè)很常見的術(shù)語(yǔ),
            在windows中,消息機(jī)制是個(gè)十分重要的概念,
            我們?cè)诰W(wǎng)絡(luò)游戲中,也使用了消息這樣的機(jī)制,

            一般我們這么做,
            一個(gè)數(shù)據(jù)塊,頭4個(gè)字節(jié)是消息名,后面接2個(gè)字節(jié)的數(shù)據(jù)長(zhǎng)度,
            再后面就是實(shí)際的數(shù)據(jù)

            為什么使用消息??
            我們來(lái)看看例子,

            在游戲世界,
            一個(gè)玩家想要和別的玩家聊天,
            那么,他輸入好聊天信息,
            客戶端生成一條聊天消息,
            并把聊天的內(nèi)容打包到消息中,
            然后把聊天消息發(fā)送給服務(wù)器,
            請(qǐng)求服務(wù)器把聊天信息發(fā)送給另一個(gè)玩家,

            服務(wù)器接收到一條消息,
            此刻,服務(wù)器并不知道當(dāng)前的數(shù)據(jù)是什么東西,
            對(duì)于服務(wù)器來(lái)講,這段數(shù)據(jù)僅僅來(lái)自于網(wǎng)絡(luò)通訊的底層,
            不加以分析的話,沒有任何的信息,
            因?yàn)槲覀兊耐ㄓ嵤腔谙C(jī)制的,
            我們認(rèn)為服務(wù)器接收到的任何數(shù)據(jù)都是基于消息的數(shù)據(jù)方式組織的,
            4個(gè)字節(jié)消息名,2字節(jié)長(zhǎng)度,這個(gè)是不會(huì)變的,

            通過(guò)消息名,服務(wù)器發(fā)現(xiàn)當(dāng)前數(shù)據(jù)是一條聊天數(shù)據(jù),
            通過(guò)長(zhǎng)度把需要的數(shù)據(jù)還原,校驗(yàn),
            然后把這條消息發(fā)送給另一個(gè)玩家,

            大家注意,消息是變長(zhǎng)的,
            關(guān)于消息的解釋完全在于服務(wù)器和客戶端的應(yīng)用程序,
            可以認(rèn)為與網(wǎng)絡(luò)傳輸?shù)蛯訜o(wú)關(guān),
            比如一條私聊消息可能是這樣的,

            MsgID:4 byte
            Length:2 byte
            TargetPlayerID:2 byte
            String:anybyte < 256

            一條移動(dòng)消息可能是這樣的,
            MsgID:4 byte
            Length:2 byte
            TargetPlayerID:2 byte
            TargetPosition:4 byte (x,y)

            編程者可以自定義消息的內(nèi)容以滿足不同的需求
            ——————————————————————
            隊(duì)列

            [隊(duì)列]
            隊(duì)列是一個(gè)很重要的數(shù)據(jù)結(jié)構(gòu),
            比如說(shuō)消息隊(duì)列,
            服務(wù)器或者客戶端,
            發(fā)送的消息不一定是立即發(fā)送的,
            而是等待一個(gè)適當(dāng)時(shí)間,
            或者系統(tǒng)規(guī)定的時(shí)間間隔以后才發(fā)送,
            這樣就需要?jiǎng)?chuàng)建一個(gè)消息隊(duì)列,以保存發(fā)送的消息,

            消息隊(duì)列的大小可以按照實(shí)際的需求創(chuàng)建,
            隊(duì)列又可能會(huì)滿,
            當(dāng)隊(duì)列滿了,可以直接丟棄消息,
            如果你覺得這樣不妥,
            也可以預(yù)先劃分一個(gè)足夠大的隊(duì)列,

            可以使用一個(gè)系統(tǒng)全局的大的消息隊(duì)列,
            也可以為每個(gè)對(duì)象創(chuàng)建一個(gè)消息隊(duì)列,


            這個(gè)我們的一個(gè)數(shù)據(jù)隊(duì)列的實(shí)現(xiàn),
            開發(fā)工具vc.net,使用了C++的模板,
            關(guān)于隊(duì)列的算法和基礎(chǔ)知識(shí),我就不多說(shuō)了,

            DataBuffer.h

            #ifndef __DATABUFFER_H__
            #define __DATABUFFER_H__

            #include <windows.h>
            #include <assert.h>
            #include "g_assert.h"
            #include <stdio.h>

            #ifndef HAVE_BYTE
            typedef unsigned char byte;
            #endif // HAVE_BYTE

            //數(shù)據(jù)隊(duì)列管理類
            template <const int _max_line, const int _max_size>
            class DataBufferTPL
            {
            public:

            bool Add( byte *data ) // 加入隊(duì)列數(shù)據(jù)
            {
            G_ASSERT_RET( data, false );
            m_ControlStatus = false;

            if( IsFull() )
            {
            //assert( false );
            return false;
            }

            memcpy( m_s_ptr, data, _max_size );

            NextSptr();
            m_NumData++;

            m_ControlStatus = true;
            return true;
            }


            bool Get( byte *data ) // 從隊(duì)列中取出數(shù)據(jù)
            {
            G_ASSERT_RET( data, false );
            m_ControlStatus = false;

            if( IsNull() )
            return false;

            memcpy( data, m_e_ptr, _max_size );

            NextEptr();
            m_NumData--;

            m_ControlStatus = true;
            return true;
            }


            bool CtrlStatus() // 獲取操作成功結(jié)果
            {
            return m_ControlStatus;
            }


            int GetNumber() // 獲得現(xiàn)在的數(shù)據(jù)大小
            {
            return m_NumData;
            }

            public:

            DataBufferTPL()
            {
            m_NumData = 0;
            m_start_ptr = m_DataTeam[0];
            m_end_ptr = m_DataTeam[_max_line-1];
            m_s_ptr = m_start_ptr;
            m_e_ptr = m_start_ptr;
            }
            ~DataBufferTPL()
            {
            m_NumData = 0;
            m_s_ptr = m_start_ptr;
            m_e_ptr = m_start_ptr;
            }

            private:

            bool IsFull() // 是否隊(duì)列滿
            {
            G_ASSERT_RET( m_NumData >=0 && m_NumData <= _max_line, false );
            if( m_NumData == _max_line )
            return true;
            else
            return false;
            }
            bool IsNull() // 是否隊(duì)列空
            {
            G_ASSERT_RET( m_NumData >=0 && m_NumData <= _max_line, false );
            if( m_NumData == 0 )
            return true;
            else
            return false;
            }
            void NextSptr() // 頭位置增加
            {
            assert(m_start_ptr);
            assert(m_end_ptr);
            assert(m_s_ptr);
            assert(m_e_ptr);
            m_s_ptr += _max_size;
            if( m_s_ptr > m_end_ptr )
            m_s_ptr = m_start_ptr;
            }
            void NextEptr() // 尾位置增加
            {
            assert(m_start_ptr);
            assert(m_end_ptr);
            assert(m_s_ptr);
            assert(m_e_ptr);
            m_e_ptr += _max_size;
            if( m_e_ptr > m_end_ptr )
            m_e_ptr = m_start_ptr;
            }

            private:

            byte m_DataTeam[_max_line][_max_size]; //數(shù)據(jù)緩沖
            int m_NumData; //數(shù)據(jù)個(gè)數(shù)
            bool m_ControlStatus; //操作結(jié)果

            byte *m_start_ptr; //起始位置
            byte *m_end_ptr; //結(jié)束位置
            byte *m_s_ptr; //排隊(duì)起始位置
            byte *m_e_ptr; //排隊(duì)結(jié)束位置
            };


            //////////////////////////////////////////////////////////////////////////
            // 放到這里了!

            //ID自動(dòng)補(bǔ)位列表模板,用于自動(dòng)列表,無(wú)間空順序列表。
            template <const int _max_count>
            class IDListTPL
            {
            public:
            // 清除重置
            void Reset()
            {
            for(int i=0;i<_max_count;i++)
            m_dwList[i] = G_ERROR;
            m_counter = 0;
            }

            int MaxSize() const { return _max_count; }
            int Count() const { return m_counter; }
            const DWORD operator[]( int iIndex ) {

            G_ASSERTN( iIndex >= 0 && iIndex < m_counter );

            return m_dwList[ iIndex ];
            }
            bool New( DWORD dwID )
            {
            G_ASSERT_RET( m_counter >= 0 && m_counter < _max_count, false );

            //ID 唯一性,不能存在相同ID
            if ( Find( dwID ) != -1 )
            return false;

            m_dwList[m_counter] = dwID;
            m_counter++;

            return true;
            }
            // 沒有Assert的加入ID功能
            bool Add( DWORD dwID )
            {
            if( m_counter <0 || m_counter >= _max_count )
            return false;

            //ID 唯一性,不能存在相同ID
            if ( Find( dwID ) != -1 )
            return false;

            m_dwList[m_counter] = dwID;
            m_counter++;
            return true;
            }
            bool Del( int iIndex )
            {
            G_ASSERT_RET( iIndex >=0 && iIndex < m_counter, false );

            for(int k=iIndex;k<m_counter-1;k++)
            {
            m_dwList[k] = m_dwList[k+1];
            }

            m_dwList[k] = G_ERROR;
            m_counter--;
            return true;
            }
            int Find( DWORD dwID )
            {
            for(int i=0;i<m_counter;i++)
            {
            if( m_dwList[i] == dwID )
            return i;
            }

            return -1;
            }

            IDListTPL():m_counter(0)
            {
            for(int i=0;i<_max_count;i++)
            m_dwList[i] = G_ERROR;
            }
            virtual ~IDListTPL()
            {}

            private:

            DWORD m_dwList[_max_count];
            int m_counter;

            };

            //////////////////////////////////////////////////////////////////////////


            #endif //__DATABUFFER_H__
            ——————————————————————
            socket

            我們采用winsock作為網(wǎng)絡(luò)部分的編程接口,

            接下去編程者有必要學(xué)習(xí)一下socket的基本知識(shí),
            不過(guò)不懂也沒有關(guān)系,我提供的代碼已經(jīng)把那些麻煩的細(xì)節(jié)或者正確的系統(tǒng)設(shè)置給弄好了,
            編程者只需要按照規(guī)則編寫游戲系統(tǒng)的處理代碼就可以了,

            這些代碼在vc6下編譯通過(guò),
            是通用的網(wǎng)絡(luò)傳輸?shù)讓樱?br />這里是socket部分的代碼,

            我們需要安裝vc6才能夠編譯以下的代碼,
            因?yàn)榻酉氯ノ覀円佑|越來(lái)越多的c++,
            所以,大家還是去看看c++的書吧,

            // socket.h
            #ifndef _socket_h
            #define _socket_h
            #pragma once

            //定義最大連接用戶數(shù)目 ( 最大支持 512 個(gè)客戶連接 )
            #define MAX_CLIENTS 512
            //#define FD_SETSIZE MAX_CLIENTS

            #pragma comment( lib, "wsock32.lib" )

            #include <winsock.h>

            class CSocketCtrl
            {
            void SetDefaultOpt();
            public:
            CSocketCtrl(): m_sockfd(INVALID_SOCKET){}
            BOOL StartUp();
            BOOL ShutDown();
            BOOL IsIPsChange();

            BOOL CanWrite();
            BOOL HasData();
            int Recv( char* pBuffer, int nSize, int nFlag );
            int Send( char* pBuffer, int nSize, int nFlag );
            BOOL Create( UINT uPort );
            BOOL Create(void);
            BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort );
            void Close();

            BOOL Listen( int nBackLog );
            BOOL Accept( CSocketCtrl& sockCtrl );

            BOOL RecvMsg( char *sBuf );
            int SendMsg( char *sBuf,unsigned short stSize );
            SOCKET GetSockfd(){ return m_sockfd; }

            BOOL GetHostName( char szHostName[], int nNameLength );

            protected:
            SOCKET m_sockfd;

            static DWORD m_dwConnectOut;
            static DWORD m_dwReadOut;
            static DWORD m_dwWriteOut;
            static DWORD m_dwAcceptOut;
            static DWORD m_dwReadByte;
            static DWORD m_dwWriteByte;
            };


            #endif

            // socket.cpp

            #include <stdio.h>
            #include "msgdef.h"
            #include "socket.h"
            // 吊線時(shí)間
            #define ALL_TIMEOUT 120000
            DWORD CSocketCtrl::m_dwConnectOut = 60000;
            DWORD CSocketCtrl::m_dwReadOut = ALL_TIMEOUT;
            DWORD CSocketCtrl::m_dwWriteOut = ALL_TIMEOUT;
            DWORD CSocketCtrl::m_dwAcceptOut = ALL_TIMEOUT;
            DWORD CSocketCtrl::m_dwReadByte = 0;
            DWORD CSocketCtrl::m_dwWriteByte = 0;

            // 接收數(shù)據(jù)
            BOOL CSocketCtrl::RecvMsg( char *sBuf )
            {
            if( !HasData() )
            return FALSE;
            MsgHeader header;
            int nbRead = this->Recv( (char*)&header, sizeof( header ), MSG_PEEK );
            if( nbRead == SOCKET_ERROR )
            return FALSE;
            if( nbRead < sizeof( header ) )
            {
            this->Recv( (char*)&header, nbRead, 0 );
            printf( "\ninvalid msg, skip %ld bytes.", nbRead );
            return FALSE;
            }

            if( this->Recv( (char*)sBuf, header.stLength, 0 ) != header.stLength )
            return FALSE;

            return TRUE;
            }

            // 發(fā)送數(shù)據(jù)
            int CSocketCtrl::SendMsg( char *sBuf,unsigned short stSize )
            {
            static char sSendBuf[ 4000 ];
            memcpy( sSendBuf,&stSize,sizeof(short) );
            memcpy( sSendBuf + sizeof(short),sBuf,stSize );

            if( (sizeof(short) + stSize) != this->Send( sSendBuf,stSize+sizeof(short),0 ) )
            return -1;
            return stSize;
            }


            // 啟動(dòng)winsock
            BOOL CSocketCtrl::StartUp()
            {
            WSADATA wsaData;
            WORD wVersionRequested = MAKEWORD( 1, 1 );

            int err = WSAStartup( wVersionRequested, &wsaData );
            if ( err != 0 )
            {
            return FALSE;
            }


            return TRUE;

            }
            // 關(guān)閉winsock
            BOOL CSocketCtrl::ShutDown()
            {
            WSACleanup();
            return TRUE;
            }

            // 得到主機(jī)名
            BOOL CSocketCtrl::GetHostName( char szHostName[], int nNameLength )
            {
            if( gethostname( szHostName, nNameLength ) != SOCKET_ERROR )
            return TRUE;
            return FALSE;
            }

            BOOL CSocketCtrl::IsIPsChange()
            {
            return FALSE;
            static int iIPNum = 0;
            char sHost[300];

            hostent *pHost;
            if( gethostname(sHost,299) != 0 )
            return FALSE;
            pHost = gethostbyname(sHost);
            int i;
            char *psHost;
            i = 0;
            do
            {
            psHost = pHost->h_addr_list[i++];
            if( psHost == 0 )
            break;

            }while(1);
            if( iIPNum != i )
            {
            iIPNum = i;
            return TRUE;
            }
            return FALSE;
            }

            // socket是否可以寫
            BOOL CSocketCtrl::CanWrite()
            {
            int e;

            fd_set set;
            timeval tout;
            tout.tv_sec = 0;
            tout.tv_usec = 0;

            FD_ZERO(&set);
            FD_SET(m_sockfd,&set);
            e=::select(0,NULL,&set,NULL,&tout);
            if(e==SOCKET_ERROR) return FALSE;
            if(e>0) return TRUE;
            return FALSE;
            }

            // socket是否有數(shù)據(jù)
            BOOL CSocketCtrl::HasData()
            {
            int e;
            fd_set set;
            timeval tout;
            tout.tv_sec = 0;
            tout.tv_usec = 0;

            FD_ZERO(&set);
            FD_SET(m_sockfd,&set);
            e=::select(0,&set,NULL,NULL,&tout);
            if(e==SOCKET_ERROR) return FALSE;
            if(e>0) return TRUE;
            return FALSE;
            }

            int CSocketCtrl::Recv( char* pBuffer, int nSize, int nFlag )
            {
            return recv( m_sockfd, pBuffer, nSize, nFlag );
            }

            int CSocketCtrl::Send( char* pBuffer, int nSize, int nFlag )
            {
            return send( m_sockfd, pBuffer, nSize, nFlag );
            }

            BOOL CSocketCtrl::Create( UINT uPort )
            {
            m_sockfd=::socket(PF_INET,SOCK_STREAM,0);
            if(m_sockfd==INVALID_SOCKET) return FALSE;
            SOCKADDR_IN SockAddr;
            memset(&SockAddr,0,sizeof(SockAddr));
            SockAddr.sin_family = AF_INET;
            SockAddr.sin_addr.s_addr = INADDR_ANY;
            SockAddr.sin_port = ::htons( uPort );
            if(!::bind(m_sockfd,(SOCKADDR*)&SockAddr, sizeof(SockAddr)))
            {
            SetDefaultOpt();
            return TRUE;
            }
            Close();
            return FALSE;

            }

            void CSocketCtrl::Close()
            {
            ::closesocket( m_sockfd );
            m_sockfd = INVALID_SOCKET;
            }

            BOOL CSocketCtrl::Connect( LPCTSTR lpszHostAddress, UINT nHostPort )
            {
            if(m_sockfd==INVALID_SOCKET) return FALSE;

            SOCKADDR_IN sockAddr;

            memset(&sockAddr,0,sizeof(sockAddr));
            LPSTR lpszAscii=(LPSTR)lpszHostAddress;
            sockAddr.sin_family=AF_INET;
            sockAddr.sin_addr.s_addr=inet_addr(lpszAscii);
            if(sockAddr.sin_addr.s_addr==INADDR_NONE)
            {
            HOSTENT * lphost;
            lphost = ::gethostbyname(lpszAscii);
            if(lphost!=NULL)
            sockAddr.sin_addr.s_addr = ((IN_ADDR *)lphost->h_addr)->s_addr;
            else return FALSE;
            }
            sockAddr.sin_port = htons((u_short)nHostPort);

            int r=::connect(m_sockfd,(SOCKADDR*)&sockAddr,sizeof(sockAddr));
            if(r!=SOCKET_ERROR) return TRUE;

            int e;
            e=::WSAGetLastError();
            if(e!=WSAEWOULDBLOCK) return FALSE;

            fd_set set;
            timeval tout;
            tout.tv_sec = 0;
            tout.tv_usec = 100000;

            UINT n=0;
            while( n< CSocketCtrl::m_dwConnectOut)
            {
            FD_ZERO(&set);
            FD_SET(m_sockfd,&set);
            e=::select(0,NULL,&set,NULL, &tout);

            if(e==SOCKET_ERROR) return FALSE;
            if(e>0) return TRUE;

            if( IsIPsChange() )
            return FALSE;
            n += 100;
            }

            return FALSE;

            }
            // 設(shè)置監(jiān)聽socket
            BOOL CSocketCtrl::Listen( int nBackLog )
            {
            if( m_sockfd == INVALID_SOCKET ) return FALSE;
            if( !listen( m_sockfd, nBackLog) ) return TRUE;
            return FALSE;
            }

            // 接收一個(gè)新的客戶連接
            BOOL CSocketCtrl::Accept( CSocketCtrl& ms )
            {
            if( m_sockfd == INVALID_SOCKET ) return FALSE;
            if( ms.m_sockfd != INVALID_SOCKET ) return FALSE;

            int e;
            fd_set set;
            timeval tout;
            tout.tv_sec = 0;
            tout.tv_usec = 100000;

            UINT n=0;
            while(n< CSocketCtrl::m_dwAcceptOut)
            {
            //if(stop) return FALSE;
            FD_ZERO(&set);
            FD_SET(m_sockfd,&set);
            e=::select(0,&set,NULL,NULL, &tout);
            if(e==SOCKET_ERROR) return FALSE;
            if(e==1) break;
            n += 100;
            }
            if( n>= CSocketCtrl::m_dwAcceptOut ) return FALSE;

            ms.m_sockfd=accept(m_sockfd,NULL,NULL);
            if(ms.m_sockfd==INVALID_SOCKET) return FALSE;
            ms.SetDefaultOpt();

            return TRUE;
            }

            BOOL CSocketCtrl::Create(void)
            {
            m_sockfd=::socket(PF_INET,SOCK_STREAM,0);
            if(m_sockfd==INVALID_SOCKET) return FALSE;
            SOCKADDR_IN SockAddr;

            memset(&SockAddr,0,sizeof(SockAddr));
            SockAddr.sin_family = AF_INET;
            SockAddr.sin_addr.s_addr = INADDR_ANY;
            SockAddr.sin_port = ::htons(0);
            //if(!::bind(m_sock,(SOCKADDR*)&SockAddr, sizeof(SockAddr)))
            {
            SetDefaultOpt();
            return TRUE;
            }
            Close();
            return FALSE;
            }

            // 設(shè)置正確的socket狀態(tài),
            // 主要是主要是設(shè)置非阻塞異步傳輸模式
            void CSocketCtrl::SetDefaultOpt()
            {
            struct linger ling;
            ling.l_onoff=1;
            ling.l_linger=0;
            setsockopt( m_sockfd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling));
            setsockopt( m_sockfd, SOL_SOCKET, SO_REUSEADDR, 0, 0);
            int bKeepAlive = 1;
            setsockopt( m_sockfd, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(int));
            BOOL bNoDelay = TRUE;
            setsockopt( m_sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&bNoDelay, sizeof(BOOL));
            unsigned long nonblock=1;
            ::ioctlsocket(m_sockfd,FIONBIO,&nonblock);
            }
            ——————————————————————
            今天晚上寫了一些測(cè)試代碼,
            想看看flash究竟能夠承受多大的網(wǎng)絡(luò)數(shù)據(jù)傳輸,

            我在flash登陸到服務(wù)器以后,
            每隔3毫秒就發(fā)送100次100個(gè)字符的串 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" 給flash,
            然后在flash里面接收數(shù)據(jù)的函數(shù)里面統(tǒng)計(jì)數(shù)據(jù),


            var g_nTotalRecvByte = 0;
            var g_time = new Date();
            var g_nStartTime = g_time.getTime();
            var g_nCounter = 0;

            mySocket.onData=function(xmlDoc)
            {
            g_nTotalRecvByte += xmlDoc.length;
            // 每接收超過(guò)1k字節(jié)的數(shù)據(jù),輸出一次信息,
            if( g_nTotalRecvByte-g_nCounter > 1024 )
            {
            g_time = new Date();
            var nPassedTime = g_time.getTime()-g_nStartTime;
            trace( "花費(fèi)時(shí)間:"+nPassedTime+"毫秒" );
            g_nCounter = g_nTotalRecvByte;
            trace( "接收總數(shù):"+g_nTotalRecvByte+"字節(jié)" );
            trace( "接收速率:"+g_nTotalRecvByte*1000/nPassedTime+"字節(jié)/秒" );

            }
            結(jié)果十分令我意外,
            這是截取的一段調(diào)試信息,
            //
            花費(fèi)時(shí)間:6953毫秒
            接收總數(shù):343212字節(jié)
            接收速率:49361.7143678988字節(jié)/秒
            花費(fèi)時(shí)間:7109毫秒
            接收總數(shù):344323字節(jié)
            接收速率:48434.800956534字節(jié)/秒
            花費(fèi)時(shí)間:7109毫秒
            接收總數(shù):345434字節(jié)
            接收速率:48591.0817273878字節(jié)/秒
            。。。
            。。。
            。。。
            。。。
            花費(fèi)時(shí)間:8125毫秒
            接收總數(shù):400984字節(jié)
            接收速率:49351.8769230769字節(jié)/秒
            花費(fèi)時(shí)間:8125毫秒
            接收總數(shù):402095字節(jié)
            接收速率:49488.6153846154字節(jié)/秒
            花費(fèi)時(shí)間:8125毫秒
            接收總數(shù):403206字節(jié)
            接收速率:49625.3538461538字節(jié)/秒

            我檢查了幾遍源程序,沒有發(fā)現(xiàn)邏輯錯(cuò)誤,
            如果程序沒有問(wèn)題的話,
            那么我們得出的結(jié)論是,flash的xml socket每秒可以接收至少40K的數(shù)據(jù),
            這還沒有計(jì)算xmlSocket.onData事件的觸發(fā),調(diào)試代碼、信息輸出占用的時(shí)間。

            比我想象中快了一個(gè)數(shù)量級(jí),
            夠用了,
            flash網(wǎng)絡(luò)游戲我們可以繼續(xù)往下走了
            ——————————————————————
            有朋友問(wèn)到lag的問(wèn)題,
            問(wèn)得很好,不過(guò)也不要過(guò)于擔(dān)心,
            lag的產(chǎn)生有的是因?yàn)榫W(wǎng)絡(luò)延遲,
            有的是因?yàn)榉?wù)器負(fù)載過(guò)大,
            對(duì)于游戲的設(shè)計(jì)者和開發(fā)者來(lái)說(shuō),
            首先要從設(shè)計(jì)的角度來(lái)避免或者減少lag產(chǎn)生的機(jī)會(huì),
            如果lag產(chǎn)生了,
            也不要緊,找到巧妙的辦法騙過(guò)玩家的眼睛,
            這也有很多成熟的方法了,
            比如航行預(yù)測(cè)法,路徑插值等等,
            都可以產(chǎn)生很好的效果,
            還有最后的絕招,就是提高服務(wù)器的配置和網(wǎng)絡(luò)帶寬,

            從我開發(fā)網(wǎng)絡(luò)游戲這段時(shí)間的經(jīng)驗(yàn)來(lái)看,
            我們的服務(wù)器是vc開發(fā)的,
            普通pc跑幾百個(gè)玩家,幾百個(gè)怪物是沒有問(wèn)題的,


            又作了一個(gè)flash發(fā)送的測(cè)試,

            網(wǎng)絡(luò)游戲的特點(diǎn)是,
            出去的信息比較少,
            進(jìn)來(lái)的信息比較多,

            這個(gè)很容易理解,
            人操作游戲的速度是很有限的,
            控制指令的產(chǎn)生也是隨機(jī)的,
            離散的,

            但是多人游戲的話,
            因?yàn)槿硕啵畔⒌牧髁恳簿蛥^(qū)域均勻分布了,

            在昨天接收數(shù)據(jù)的基礎(chǔ)上,
            我略加修改,
            這次,
            我在_root.enterFrame寫了如下代碼,
            _root.onEnterFrame = function()
            {
            var i;
            for( i = 0; i < 10; i++ )
            mySocket.send( ConvertToMsg( "01234567890123456789012345678901234567890123456789" ) );
            return;
            }

            服務(wù)器端要做的是,
            把所有從flash客戶端收到的信息原封不動(dòng)的返回來(lái),

            這樣,我又可以通過(guò)昨天onData里面的統(tǒng)計(jì)算法來(lái)從側(cè)面估算出flash發(fā)送數(shù)據(jù)的能力,
            這里是輸出的數(shù)據(jù)
            //
            花費(fèi)時(shí)間:30531毫秒
            接收總數(shù):200236字節(jié)
            接收速率:6558.44878975468字節(jié)/秒
            花費(fèi)時(shí)間:30937毫秒
            接收總數(shù):201290字節(jié)
            接收速率:6506.44858906811字節(jié)/秒
            花費(fèi)時(shí)間:31140毫秒
            接收總數(shù):202344字節(jié)
            接收速率:6497.88053949904字節(jié)/秒
            花費(fèi)時(shí)間:31547毫秒
            接收總數(shù):203398字節(jié)
            接收速率:6447.45934637208字節(jié)/秒

            可以看出來(lái),發(fā)送+接收同時(shí)做,
            發(fā)送速率至少可以達(dá)到5k byte/s

            有一點(diǎn)要注意,要非常注意,
            不能讓flash的網(wǎng)絡(luò)傳輸滿載,
            所謂滿載就是flash在阻塞運(yùn)算的時(shí)候,
            不斷的有數(shù)據(jù)從網(wǎng)絡(luò)進(jìn)來(lái),
            而flash又無(wú)法在預(yù)計(jì)的時(shí)間內(nèi)處理我這些信息,
            或者flash發(fā)送數(shù)據(jù)過(guò)于頻繁,
            導(dǎo)致服務(wù)器端緩沖溢出導(dǎo)致錯(cuò)誤,

            對(duì)于5k的傳輸速率,
            已經(jīng)足夠了,
            因?yàn)槲乙蚕氩怀鰜?lái)有什么產(chǎn)生這么大的數(shù)據(jù)量,
            而且如果產(chǎn)生了這么大的數(shù)據(jù)量,
            也就意味著服務(wù)器每時(shí)每刻都要處理所有的玩家發(fā)出的海量數(shù)據(jù),
            還要把這些海量數(shù)據(jù)轉(zhuǎn)發(fā)給其他的玩家,
            已經(jīng)引起數(shù)據(jù)爆炸了,
            所以,5k的上傳從設(shè)計(jì)階段就要避免的,
            我想用flash做的網(wǎng)絡(luò)游戲,
            除了動(dòng)作類游戲可能需要恒定1k以內(nèi)的上傳速率,
            其他的200個(gè)字節(jié)/秒以內(nèi)就可以了,
            ——————————————————————
            使用于Flash的消息結(jié)構(gòu)定義

            我們以前討論過(guò),
            通過(guò)消息來(lái)傳遞信息,
            消息的結(jié)構(gòu)是
            struct msg
            {
            short nLength; // 2 byte
            DWORD dwId; // 4 byte

            ....
            data
            }

            但是在為flash開發(fā)的消息中,
            不能采用這種結(jié)構(gòu),

            首先Flash xmlSocket只傳輸字符串,
            從xmlSocket的send,onData函數(shù)可以看出來(lái),
            發(fā)出去的,收進(jìn)來(lái)的都應(yīng)該是字符串,

            而在服務(wù)器端是使用vc,java等高級(jí)語(yǔ)言編寫的,
            消息中使用的是二進(jìn)制數(shù)據(jù)塊,
            顯然,簡(jiǎn)單的使用字符串會(huì)帶來(lái)問(wèn)題,

            所以,我們需要制定一套協(xié)議,
            就是無(wú)論在客戶端還是服務(wù)器端,
            都用統(tǒng)一的字符串消息,
            通過(guò)解析字符串的方式來(lái)傳遞信息,

            我想這就是flash采用xml document來(lái)傳輸結(jié)構(gòu)化信息的理由之一,
            xml document描述了一個(gè)完整的數(shù)據(jù)結(jié)構(gòu),
            而且全部使用的是字符串,
            原來(lái)是這樣,怪不得叫做xml socket,
            本來(lái)socket和xml完全是不同的概念,
            flash偏偏出了個(gè)xml socket,
            一開始令我費(fèi)解,
            現(xiàn)在,漸漸理解其中奧妙。

            ——————————————————————
            Flash Msg結(jié)構(gòu)定義源代碼和相關(guān)函數(shù)

            在服務(wù)器端,我們?yōu)閒lash定義了一種msg結(jié)構(gòu),
            使用語(yǔ)言,vc6
            #define MSGMAXSIZE 512
            // 消息頭
            struct MsgHeader
            {
            short stLength;
            MsgHeader():stLength( 0 ){}

            };
            // 消息
            struct Msg
            {
            MsgHeader header;
            short GetLength(){ return header.stLength; }
            };
            // flash 消息
            struct MsgToFlashublic Msg
            {
            // 一個(gè)足夠大的緩沖,但是不會(huì)被整個(gè)發(fā)送,
            char szString[MSGMAXSIZE];
            // 計(jì)算設(shè)置好內(nèi)容后,內(nèi)部會(huì)計(jì)算將要發(fā)送部分的長(zhǎng)度,
            // 要發(fā)送的長(zhǎng)度=消息頭大小+字符串長(zhǎng)度+1
            void SetString( const char* pszChatString )
            {
            if( strlen( pszChatString ) < MSGMAXSIZE-1 )
            {
            strcpy( szString, pszChatString );
            header.stLength = sizeof( header )+
            (short)strlen( pszChatString )+1;
            }
            }

            };

            在發(fā)往flash的消息中,整個(gè)處理過(guò)后MsgToFlash結(jié)構(gòu)將被發(fā)送,
            實(shí)踐證明,在flash 客戶端的xmlSocket onData事件中,
            接收到了正確的消息,消息的內(nèi)容是MasToFlash的szString字段,
            是一個(gè)字符串,

            比如在服務(wù)器端,
            MsgToFlash msg;
            msg.SetString( "move player0 to 100 100" );
            SendMsg( msg,............. );
            那么,在我們的flash客戶端的onData( xmlDoc )中,
            我們trace( xmlDoc )
            結(jié)果是
            move player0 to 100 100


            然后是flash發(fā)送消息到服務(wù)器,
            我們強(qiáng)調(diào)flash只發(fā)送字符串,
            這個(gè)字符串無(wú)論是否內(nèi)部擁有有效數(shù)據(jù),
            服務(wù)器都應(yīng)該首先把消息收下來(lái),
            那就要保證發(fā)送給服務(wù)器的消息遵循統(tǒng)一的結(jié)構(gòu),
            在flash客戶端中,
            我們定義一個(gè)函數(shù),
            這個(gè)函數(shù)把一個(gè)字符串轉(zhuǎn)化為服務(wù)器可以識(shí)別的消息,

            補(bǔ)充:現(xiàn)在我們約定字符串長(zhǎng)度都不大于97個(gè)字節(jié)長(zhǎng)度,


            var num_table = new array( "0","1","2","3","4","5","6","7","8","9" );
            function ConvertToMsg( str )
            {
            var l = str.length+3;
            var t = "";
            if( l > 10 )
            t = num_table[Math.floor(l/10)]+num_table[Number(l%10)]+str;
            else
            t = num_table[0]+num_table[l]+str;
            return t;
            }

            比如
            var msg = ConvertToMsg( "client login" );
            我們trace( msg );
            看到的是
            15client login

            為什么是這個(gè)結(jié)果呢?
            15是消息的長(zhǎng)度,
            頭兩個(gè)字節(jié)是整個(gè)消息的長(zhǎng)度的asc碼,意思是整個(gè)消息有15個(gè)字節(jié)長(zhǎng),
            然后是信息client login,
            最后是一個(gè)0(c語(yǔ)言中的字符串結(jié)束符)

            當(dāng)服務(wù)器收到15client login,
            他首先把15給分析出來(lái),
            把"15"字符串轉(zhuǎn)化為15的數(shù)字,
            然后,根據(jù)15這個(gè)長(zhǎng)度把后面的client login讀出來(lái),
            這樣,網(wǎng)絡(luò)傳輸?shù)牡讓泳屯瓿闪耍?br />client login的處理就交給邏輯層,
            ——————————————————————
            前陣子我開發(fā)了Match3D,
            一個(gè)可以把三維動(dòng)畫輸出成為swf的工具,
            而且實(shí)現(xiàn)了swf渲染的實(shí)時(shí)三維角色動(dòng)畫,
            這可以說(shuō)是我真正推出的第一個(gè)flash第三方軟件,
            其實(shí)這以前,
            我曾經(jīng)開發(fā)過(guò)幾個(gè)其他的flash第三方軟件,
            都中途停止了,
            因?yàn)椴粚?shí)用或者市場(chǎng)上有更好的同類軟件,

            隨著互聯(lián)網(wǎng)的發(fā)展,
            flash的不斷升級(jí),
            我的flash第三方軟件目光漸漸的從美術(shù)開發(fā)工具轉(zhuǎn)移到網(wǎng)絡(luò)互連,
            web應(yīng)用上面來(lái),
            如今已經(jīng)到了2004版本,
            flash的種種新特性讓我眼前發(fā)光,

            我最近在帝國(guó)的各個(gè)板塊看了很多貼子,
            分析里面潛在的用戶需求,
            總結(jié)了以下的幾個(gè)我認(rèn)為比較有意義的選題,
            可能很片面,

            flash源代碼保護(hù),主要是為了抵御asv之類的軟件進(jìn)行反編譯和萃取
            flash與遠(yuǎn)端數(shù)據(jù)庫(kù)的配合,應(yīng)該出現(xiàn)一個(gè)能夠方便快捷的對(duì)遠(yuǎn)程數(shù)據(jù)庫(kù)進(jìn)行操作的方法或者控件,
            flash網(wǎng)際互連,我認(rèn)為flash網(wǎng)絡(luò)游戲是一塊金子,

            這里我想談?wù)刦lash網(wǎng)絡(luò)游戲,
            我要談的不僅僅是技術(shù),而是一個(gè)概念,
            用flash網(wǎng)絡(luò)游戲,
            我本身并不想把flash游戲做成rpg或者其他劇烈交互性的游戲,
            而是想讓flash實(shí)現(xiàn)那些節(jié)奏緩慢,玩法簡(jiǎn)單的游戲,
            把網(wǎng)絡(luò)的概念帶進(jìn)來(lái),

            你想玩游戲的時(shí)候,登上flash網(wǎng)絡(luò)游戲的網(wǎng)站,
            選擇你想玩的網(wǎng)絡(luò)游戲,
            因?yàn)楝F(xiàn)在幾乎所有上網(wǎng)的電腦都可以播放swf,
            所以,我們幾乎不用下載任何插件,
            輸入你的賬號(hào)和密碼,
            就可以開始玩了,

            我覺得battle.net那種方式很適合flash,
            開房間或者進(jìn)入別人開的房間,
            然后2個(gè)人或者4個(gè)人就可以交戰(zhàn)了,

            這種游戲可以是棋類,這是最基本的,
            用戶很廣泛,
            我腦海中的那種是類似與寵物飼養(yǎng)的,
            就像當(dāng)年的電子寵物,
            每個(gè)玩家都可以到服務(wù)器認(rèn)養(yǎng)寵物,
            然后在線養(yǎng)成寵物,
            還可以邀請(qǐng)別的玩家進(jìn)行寵物比武,
            看誰(shuí)的寵物厲害,

            就這樣簡(jiǎn)簡(jiǎn)單單的模式,
            配合清新可愛的畫面,
            趣味的玩法,
            加入網(wǎng)絡(luò)的要素,
            也許可以取得以想不到的效果,

            今天就說(shuō)到這里吧,
            想法那么多,要實(shí)現(xiàn)的話還有很多路要走,

            希望大家多多支持,積極參與,
            讓我們的想法不僅僅停留于紙上。
            ——————————————————————
            格斗類游戲和休閑類游戲不同,
            最大的差別在于對(duì)響應(yīng)時(shí)間的要求不在同一數(shù)量級(jí)上,

            休閑類游戲允許很大的延遲,
            而動(dòng)作類游戲需要盡可能小的延遲,

            服務(wù)器的作用,
            要起到數(shù)據(jù)轉(zhuǎn)發(fā)和數(shù)據(jù)校驗(yàn)的作用,
            在格斗游戲設(shè)計(jì)上,
            如果采用和mmorpg相同的服務(wù)器結(jié)構(gòu),
            會(huì)產(chǎn)生較大的延遲,
            你可以想象,當(dāng)我打出一招升龍拳,
            然后把這個(gè)信息傳遞給服務(wù)器,
            假設(shè)有100毫秒的延遲,
            服務(wù)器收到以后,
            轉(zhuǎn)發(fā)給其他的人,
            又經(jīng)過(guò)100毫秒的延遲,
            也就是說(shuō),
            你打出一招,對(duì)方要得知需要200毫秒的時(shí)間,
            實(shí)際情況也許更糟,
            這對(duì)于格斗類游戲來(lái)說(shuō)是很大的,
            所以現(xiàn)在市面上幾乎沒有真正意義上的多人格斗類網(wǎng)絡(luò)游戲,

            如果要實(shí)現(xiàn)格斗的感覺,
            盡可能減少延遲,
            有很多現(xiàn)有經(jīng)過(guò)驗(yàn)證的方法,
            比如把服務(wù)器建立在兩個(gè)對(duì)戰(zhàn)玩家的機(jī)器上,
            這樣就省掉了一個(gè)延遲,
            或者每個(gè)玩家機(jī)器上都建一個(gè)服務(wù)器,

            局域網(wǎng)摸使得聯(lián)機(jī)游戲很多采用這種的,
            比如星際爭(zhēng)霸,它的聯(lián)網(wǎng)模式服務(wù)器就是建在玩家主機(jī)上的,

            減少網(wǎng)絡(luò)延遲是必要的,
            但是,一個(gè)平滑爽快的網(wǎng)絡(luò)游戲,
            他的客戶端需要有很多預(yù)測(cè)的策略,
            用來(lái)估計(jì)未來(lái)很短的時(shí)間內(nèi),
            某個(gè)玩家會(huì)做什么,
            這樣,就可以在不等待服務(wù)器返回正確信息的情況下,
            直接估計(jì)出正確的結(jié)果或者接近正確的結(jié)果,
            當(dāng)服務(wù)器返回的結(jié)果和預(yù)測(cè)結(jié)果的誤差在很小范圍內(nèi),
            則預(yù)測(cè)成功,否則,可以強(qiáng)行校正客戶端的狀態(tài),
            這樣做,可以盡量減少網(wǎng)絡(luò)延遲帶來(lái)的損失,
            絕大部分成功的聯(lián)網(wǎng)游戲,
            在這個(gè)策略上,都花了很大的功夫,
            ——————————————————————
            你提到的那個(gè)游戲模式是經(jīng)典模式battle.net,
            也就是戰(zhàn)網(wǎng),
            暗黑,星際,魔獸都有戰(zhàn)網(wǎng),

            你下面提到的是一個(gè)緩沖模式,
            當(dāng)用戶發(fā)出一招升龍拳,
            并不立即向服務(wù)器發(fā)送指令,
            而是等待5ms(不過(guò)在網(wǎng)絡(luò)游戲中5ms設(shè)定也許太樂(lè)觀),
            如果在這期間服務(wù)器下發(fā)了新指令,
            與在緩沖中的升龍拳指令進(jìn)行運(yùn)算,
            用來(lái)得到是否可以發(fā)出升龍拳的評(píng)估,
            這樣做是可以的,
            不過(guò)實(shí)際開發(fā)過(guò)程中,
            這種緩沖統(tǒng)計(jì)的模式,
            邏輯非常的復(fù)雜,
            網(wǎng)絡(luò)游戲畢竟是在網(wǎng)絡(luò)上玩的,
            而且是多人玩的,


            你如果經(jīng)常上戰(zhàn)網(wǎng)打星際的話,
            會(huì)知道它的菜單里面有一個(gè)設(shè)定操作延遲的菜單,
            高延遲的話,
            你的操作會(huì)緩沖到一定的程度然后統(tǒng)一向服務(wù)器發(fā)出,
            低延遲的話,
            會(huì)盡可能的立刻把你的操作向服務(wù)器發(fā)出,

            我的感覺是,
            網(wǎng)絡(luò)格斗類游戲與單機(jī)格斗類游戲是不同的,
            想在網(wǎng)絡(luò)上完美實(shí)現(xiàn)單機(jī)格斗游戲的內(nèi)涵難度極大,
            對(duì)于開發(fā)者來(lái)說(shuō),絕對(duì)是一個(gè)挑戰(zhàn),
            我想,可以積極的考慮如何在游戲設(shè)計(jì)的層面去規(guī)避一些問(wèn)題。
            ——————————————————————
            我的最近一條諺語(yǔ)是西點(diǎn)軍校的軍官對(duì)新兵的訓(xùn)斥,
            。。。。如果一個(gè)愚蠢的方法能夠奏效的話,那他就不是愚蠢的方法。。

            這句話簡(jiǎn)直太妙了,
            我立刻刪除了手中那些“完美的”代碼,
            用最簡(jiǎn)單的方法快速達(dá)到我的目的,
            雖然新寫的代碼張得很丑陋,

            可以說(shuō)說(shuō)題外話,
            最近策劃一直希望能夠?qū)崿F(xiàn)一個(gè)實(shí)時(shí)渲染的怪物能夠時(shí)不時(shí)的眨巴眨巴眼睛

            主流的想法是更換眼部的貼圖,
            也有使用骨骼控制眼部肌肉的方法,
            同時(shí)還有其他“技術(shù)含量”更高的解決方案,
            不過(guò)經(jīng)過(guò)評(píng)估,實(shí)現(xiàn)這兩者難度不大,但是需要耗費(fèi)比較多的資源和制作時(shí)間,更嚴(yán)重點(diǎn)的是程序需要升級(jí)代碼來(lái)支持這個(gè)新的特性,
            后來(lái),在大家集思廣益之下,
            有人提出了高明的辦法,

            把怪物的眼睛鏤空,
            然后在后面放一個(gè)畫有眼睛的矩形,
            這個(gè)矩形的正面是一個(gè)睜大的眼睛,
            而反面是一個(gè)閉上的眼睛
            這樣,眨眼就是這個(gè)矩形正反翻轉(zhuǎn)的過(guò)程,
            不錯(cuò),是個(gè)好方法,讓人眼前一亮,
            大家一致通過(guò),
            在現(xiàn)有技術(shù)下,這個(gè)“愚蠢”的方法解決了這個(gè)“復(fù)雜”的問(wèn)題,

            在有一個(gè)更有趣的例子,
            看似調(diào)侃,其實(shí)折射出人的智慧,
            日本人做游戲是非常講究細(xì)節(jié)的,
            他們最早做三維格斗游戲的時(shí)候遇到一個(gè)問(wèn)題,
            當(dāng)我打出一記重拳,命中的時(shí)候,
            我的拳頭在畫面上嵌入到敵方模型的肉里面去了,
            日本人覺得不能接受,拳頭怎么能打到肉里面去呢,
            應(yīng)該剛剛好碰到敵方的身體才行,
            可是大家想想,想要?jiǎng)倓偤门龅剑?br />談何容易,
            美術(shù)要求嚴(yán)格不說(shuō),程序可能要精確的計(jì)算,
            還不一定能夠滿足要求,
            于是高招出現(xiàn)了,嵌到肉里就嵌到肉里吧,
            我給你來(lái)一個(gè)閃光,把有問(wèn)題的畫面給你全遮住,
            這招可真絕,
            美工,程序,策劃不但偷了懶,把玩家給騙了不說(shuō),
            玩家還覺得這個(gè)游戲真酷,打中了還有閃光的特效,
            ;),實(shí)在是高,

            都一點(diǎn)了,我去睡了,
            ——————————————————————
            關(guān)于flash com server、remoting
            最近研究了一下flash com server
            是個(gè)好東西,
            他提供了集成的flash網(wǎng)絡(luò)服務(wù),

            我認(rèn)為,flash com server最具吸引我的是他的媒體能力,

            案例
            使用flash作為客戶端的圍棋游戲,
            flash實(shí)現(xiàn)這個(gè)游戲的界面和玩法是沒有問(wèn)題的,
            但是計(jì)算圍棋的死活需要大量運(yùn)算,
            這恰恰不是flash的強(qiáng)項(xiàng),
            而且我們是網(wǎng)絡(luò)游戲,
            我們會(huì)把計(jì)算交給flash comm server,
            但是flash comm server的負(fù)擔(dān)也很重,
            他要承擔(dān)視頻,音頻和數(shù)據(jù)流的傳輸,
            而且他是一個(gè)面向web應(yīng)用的服務(wù)器,
            并不適合做大量的計(jì)算,
            所以我們需要一個(gè)專門進(jìn)行運(yùn)算的服務(wù)器,


            remoting是一個(gè)網(wǎng)關(guān),
            用戶可以通過(guò)flash com server+remoting間接的取得應(yīng)用程序服務(wù)器的服務(wù),

            flash客戶端可以僅僅和com server交流,
            不必關(guān)心其他應(yīng)用程序服務(wù)器的存在,

            不過(guò)因?yàn)橐獙?shí)現(xiàn)應(yīng)用程序服務(wù)器,
            就要用到remoting,
            而mm提供的remoting有for java的和for c#的,
            我本人對(duì)java和c#不了解,
            我只會(huì)使用c++,
            所以我設(shè)計(jì)的體系就放棄了remoting,


            現(xiàn)在這個(gè)交互性很強(qiáng)的flash娛樂(lè)平臺(tái)的體系建立起來(lái)了,
            flash comm server提供媒體的服務(wù),
            比如視頻,音頻和數(shù)據(jù)流,
            使用xml socket直連由vc開發(fā)的應(yīng)用服務(wù)器,
            這個(gè)應(yīng)用服務(wù)器提供計(jì)算服務(wù),

            這樣,就把現(xiàn)在網(wǎng)絡(luò)游戲服務(wù)器的大吞吐量的運(yùn)算和flash com server的媒體服務(wù)融合到一起,

            網(wǎng)絡(luò)游戲盈利模式明確是眾所周知的,
            而且運(yùn)營(yíng)模式已經(jīng)比較成熟了,

            我認(rèn)為以flash為客戶端的休閑娛樂(lè)平臺(tái)一定是很有商業(yè)前景的,
            而在此貼中,一個(gè)由flash構(gòu)成似于聯(lián)眾的網(wǎng)絡(luò)游戲平臺(tái)就這樣誕生了,
            他的特點(diǎn)是不需要安裝專門的插件(幾乎所有的上網(wǎng)電腦都有flashplayer),
            圖形表現(xiàn)力強(qiáng),
            客戶端可以實(shí)時(shí)下載,
            這種模式可以統(tǒng)一到現(xiàn)在的網(wǎng)絡(luò)游戲收費(fèi)模式中,
            因?yàn)橹链薴lash的網(wǎng)絡(luò)游戲和普通的網(wǎng)絡(luò)游戲沒有什么區(qū)別了,
            要玩游戲就要買點(diǎn)卡,
            我們還可以提供視頻服務(wù)(這種服務(wù)可能要付更多的錢),
            一邊玩,一邊視頻聊天,

            這種平臺(tái)用來(lái)運(yùn)作那些節(jié)奏稍慢的休閑益智類網(wǎng)絡(luò)游戲再適合不過(guò)了,
            比如紙牌,棋類,
            flash客戶端開發(fā)成本低,
            運(yùn)行期間穩(wěn)定,占用資源少,

            我現(xiàn)在已經(jīng)擁有成熟的技術(shù)和解決方案,
            最近考慮投資作這個(gè)項(xiàng)目,
            如果有對(duì)此感興趣的或者想進(jìn)行商業(yè)合作的朋友,
            發(fā)email到gamemind@sina.com聯(lián)系我
            ?
            ?????????????????????????????????????? 閃之主宰

            posted on 2007-02-05 15:33 楊粼波 閱讀(992) 評(píng)論(0)  編輯 收藏 引用


            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


            久久精品免费大片国产大片| 中文字幕精品无码久久久久久3D日动漫 | 亚洲中文久久精品无码| 久久久久一区二区三区| 久久精品国产久精国产一老狼| 国产亚洲精品美女久久久| 合区精品久久久中文字幕一区 | 久久久久人妻精品一区二区三区 | 精品无码久久久久久久久久| 无码精品久久久久久人妻中字 | 久久久噜噜噜久久中文字幕色伊伊| 久久精品国产91久久麻豆自制| 欧美伊人久久大香线蕉综合| 精品久久久久中文字| 精品久久久久久综合日本| 色婷婷综合久久久久中文一区二区 | 久久久久久久久久久精品尤物 | 婷婷国产天堂久久综合五月| 很黄很污的网站久久mimi色| 国产成人久久精品区一区二区| 亚洲午夜久久久久久久久电影网| 久久精品国产欧美日韩| 色综合久久最新中文字幕| 精品国产91久久久久久久| 久久青青草原精品国产| 亚洲精品乱码久久久久66| 合区精品久久久中文字幕一区 | 久久亚洲中文字幕精品一区| 老司机国内精品久久久久| 狠狠色噜噜狠狠狠狠狠色综合久久| 欧美喷潮久久久XXXXx| 色偷偷久久一区二区三区| 亚洲中文久久精品无码| 亚洲va久久久噜噜噜久久| 国产aⅴ激情无码久久| 国内精品伊人久久久久777| 久久久久久久久66精品片| 7777精品伊人久久久大香线蕉| 亚洲?V乱码久久精品蜜桃| 一本久久综合亚洲鲁鲁五月天亚洲欧美一区二区 | 亚洲综合日韩久久成人AV|