還未入行或剛入行的小夥伴或許都有一個疑問,一個遊戲伺服器到底有哪些東西,我需要關心什麼技術或者什麼知識。

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

這裡我大概講一下一般伺服器都會遇到的幾個大的模組以及模組中特別需要注意什麼,概覽的順序以搭建遊戲伺服器過程為參考。

1。遊戲核心邏輯(Demo的建立)

一個遊戲核心肯定是他最最最本身的玩法。

比如:RPG遊戲的

移動

戰鬥,戰鬥

無外乎就是

技能

BUFF

(介紹一個2D尋路的演算法,大家可以去看看:

JPSPlusGoalBounding

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

回合制遊戲,就只關心房間內,牌桌上,邏輯是什麼。

德州撲克,我們就關心下注、過牌、獎池等,整個伺服器就是一個狀態機,你完全可以想象伺服器就是中間那個美女荷官,他說現在該幹什麼就幹什麼。

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

夢幻西遊等回合制,我們就關心戰鬥公式,至於表現,根本不用關心(不過有些回合制,比如

陰陽師

,他分兩種情況,第一種:

後端計算,前端演繹

,這意味著,如果你作弊,是完全可以跳過演繹動畫的。第二種:同步戰鬥,一場戰鬥可能會動態加入其它玩家或被其它玩家干擾)。

回合制還有兩種細分:

第一種是大家一起操作完,然後等伺服器計算完畢,客戶端在根據戰報演繹,可以舉例的遊戲就是,石器時代,夢幻西遊等。

第二種是每個單位輪流操作且當前操作的結果會決定下一次的操作分配或者對局結果,比如,陰陽師

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

如果對陰陽師熟悉的朋友,可以思考一下陰陽師中“封魔之時”玩法,也就是回合制集體打大BOSS是如何實現的。

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

回到RPG遊戲中,其實RPG的思路很簡單,

我們把回合制遊戲加速到很快,快到極限,就是即時戰鬥遊戲

假設回合制遊戲,我們伺服器1秒做一次輪訓,每秒處理如玩家的輸入、輸入的結果、遊戲結果的判定等等。那麼RPG遊戲就是30-60毫秒做一次這個運算。

還是拿“走路”來說,玩家用滑鼠右鍵點選地圖上的某個位置,伺服器作尋路和校驗(或者客戶端做尋路,伺服器簡單校驗),通過後,把這段路徑加入到該玩家的身上存起來。伺服器每30-60毫秒取出下一個目的座標,透過角色速度計算出行走距離(或透過時間差來估算是否到達)。如果角色的座標發生了變化,則檢測是否進入他人視野,如果是,則把自己的進入廣播給周圍玩家且把周圍玩家的資訊傳送給自己。

戰鬥同理。

我們可以總結出來的是,如果伺服器每幀時間(輪詢間隔)過大,則表現出來的粒度就會很大,這一點可以參考顯示器的重新整理頻率,這個頻率越高,人眼就越分辨不出來,如果頻率低了,我們就會看清楚螢幕重新整理的過程,甚至還需要眨眼補幀。如果伺服器每幀時間過小,就會造成伺服器CPU不必要的浪費,所以這個頻率需要根據經驗來取一個合適的值。

最後再說一句,遊戲的核心邏輯一定是非常簡單的,甚至都要不到幾百行程式碼,利用Console(控制檯)程式就可以輕鬆完成遊戲demo的編寫。

整個遊戲伺服器其實跟前端一樣,說白了就是一個計時器在不停的跑,根據各種條件修改資料和狀態。

給大家看一下撲克的邏輯:

public

final

class

Pokers

{

private

static

final

byte

CARD_COUNT

=

52

private

static

final

byte

CARD_NUM_COUNT

=

13

private

static

final

byte

CARD_SUIT_COUNT

=

4

private

static

final

byte

[]

STANDARD_POKER

=

new

byte

CARD_COUNT

];

// 花色

/** 黑桃 */

public

static

final

byte

CARD_SUIT_SPADE

=

1

/** 紅心 */

public

static

final

byte

CARD_SUIT_HEART

=

2

/** 方塊 */

public

static

final

byte

CARD_SUIT_DIAMOND

=

3

/** 梅花 */

public

static

final

byte

CARD_SUIT_CLUB

=

4

// 編號

/** J */

public

static

final

byte

CARD_NUM_JACK

=

11

/** Q */

public

static

final

byte

CARD_NUM_QUEEN

=

12

/** K */

public

static

final

byte

CARD_NUM_KING

=

13

/** A */

public

static

final

byte

CARD_NUM_ACE

=

14

/** 鬼 */

public

static

final

byte

CARD_NUM_JOKER

=

15

static

{

for

byte

i

=

0

i

<

CARD_SUIT_COUNT

++

i

{

for

byte

j

=

0

j

<

CARD_NUM_COUNT

++

j

{

STANDARD_POKER

CARD_NUM_COUNT

*

i

+

j

=

createCard

((

byte

j

+

2

),

byte

i

+

1

));

}

}

}

/**

* 創造一張牌

*

* @param number

* @param color

* @return

*/

public

static

byte

createCard

byte

number

byte

color

{

return

byte

((

number

|

color

<<

4

);

}

/**

* 創造一副新牌(除去大小王)

*

* @return

*/

public

static

byte

[]

createPoker

()

{

byte

[]

template

=

new

byte

CARD_COUNT

];

System

arraycopy

STANDARD_POKER

0

template

0

STANDARD_POKER

length

);

return

template

}

/**

* 建立一副新牌

*

* @return

*/

public

static

byte

[]

createPokerWithJoker

()

{

byte

[]

template

=

new

byte

CARD_COUNT

+

2

];

System

arraycopy

STANDARD_POKER

0

template

0

STANDARD_POKER

length

);

template

52

=

createCard

CARD_NUM_JOKER

CARD_SUIT_SPADE

);

template

53

=

createCard

CARD_NUM_JOKER

CARD_SUIT_HEART

);

return

template

}

/**

* 洗牌

*

* @param cards

*/

public

static

void

shuffle

byte

[]

cards

{

for

int

oldIndex

=

0

oldIndex

<

cards

length

++

oldIndex

{

int

newIndex

=

RandomUtil

nextInt

0

cards

length

-

1

);

if

newIndex

==

oldIndex

{

continue

}

byte

tempCard

=

cards

oldIndex

];

cards

oldIndex

=

cards

newIndex

];

cards

newIndex

=

tempCard

}

}

/**

* 獲取牌號碼

*

* @param value

* @return

*/

public

static

byte

getCardNumber

byte

value

{

return

byte

value

&

15

);

}

/**

* 獲取牌花色

*

* @param value

* @return

*/

public

static

byte

getCardColor

byte

value

{

return

byte

value

>>

4

);

}

/*

* 是否是A

*/

public

static

boolean

isAce

byte

card

{

return

getCardNumber

card

==

CARD_NUM_ACE

}

/**

* 是否是鬼

*

* @param card

* @return

*/

public

static

boolean

isJoker

byte

card

{

return

isBigJoker

card

||

isSmallJoker

card

);

}

/**

* 是否是大鬼

*

* @param card

* @return

*/

public

static

boolean

isBigJoker

byte

card

{

return

getCardNumber

card

==

CARD_NUM_JOKER

&&

getCardColor

card

==

CARD_SUIT_HEART

);

}

/**

* 是否是小鬼

*

* @param card

* @return

*/

public

static

boolean

isSmallJoker

byte

card

{

return

getCardNumber

card

==

CARD_NUM_JOKER

&&

getCardColor

card

==

CARD_SUIT_SPADE

);

}

/**

* 是否是相同花色

*

* @param card

* @param anotherCard

* @return

*/

public

static

boolean

isSameSuit

byte

card

byte

anotherCard

{

return

((

card

&

(~

anotherCard

))

>>

4

==

0

}

/**

* 是否是相同編號

*

* @param card

* @param anotherCard

* @return

*/

public

static

boolean

isSameNumber

byte

card

byte

anotherCard

{

return

((

card

&

(~

anotherCard

))

&

15

==

0

}

/**

* 用long儲存一副牌(只支援8張牌)

*

* @param cards

* @return

*/

public

static

long

cardsToLong

byte

[]

cards

{

if

cards

length

>

Long

BYTES

{

throw

new

IllegalArgumentException

“length <=”

+

Long

BYTES

);

}

long

longOfCards

=

0

for

int

i

=

0

i

<

cards

length

i

++)

{

longOfCards

|=

Byte

toUnsignedLong

cards

i

]))

<<

i

<<

3

);

}

return

longOfCards

}

/**

* 將long轉化為一副牌(只支援8張牌)

*

* @param value

* @return

*/

public

static

byte

[]

longToCards

long

value

{

int

pos

=

0

byte

[]

temp

=

new

byte

Long

BYTES

];

while

((

temp

pos

=

byte

((

value

>>

pos

<<

3

))

&

0XFFL

))

!=

0

{

++

pos

}

byte

[]

result

=

new

byte

pos

];

System

arraycopy

temp

0

result

0

result

length

);

return

result

}

public

static

String

toString

byte

card

{

byte

number

=

getCardNumber

card

);

byte

color

=

getCardColor

card

);

String

suit

=

null

switch

color

{

case

CARD_SUIT_CLUB

suit

=

“梅花”

break

case

CARD_SUIT_HEART

suit

=

“紅桃”

break

case

CARD_SUIT_DIAMOND

suit

=

“方塊”

break

case

CARD_SUIT_SPADE

suit

=

“黑桃”

break

}

String

num

switch

number

{

case

CARD_NUM_JACK

num

=

“J”

break

case

CARD_NUM_QUEEN

num

=

“Q”

break

case

CARD_NUM_KING

num

=

“K”

break

case

CARD_NUM_ACE

num

=

“A”

break

default

num

=

number

+

“”

break

}

return

num

+

“[”

+

suit

+

“]”

}

}

撲克邏輯為基礎,加上德州的演算法,德州的Demo就差不多了

public

class

TexasPokerCalculator

{

// 高牌

private

static

final

long

HIGH_CARD

=

TexasPokerRank

HIGH_CARD

ordinal

()

*

0x10000000000L

// 一對

private

static

final

long

ONE_PAIR

=

TexasPokerRank

ONE_PAIR

ordinal

()

*

0x10000000000L

// 兩對

private

static

final

long

TWO_PAIR

=

TexasPokerRank

TWO_PAIR

ordinal

()

*

0x10000000000L

// 三條

private

static

final

long

THREE_OF_A_KIND

=

TexasPokerRank

THREE_OF_A_KIND

ordinal

()

*

0x10000000000L

// 順子

private

static

final

long

STRAIGHT

=

TexasPokerRank

STRAIGHT

ordinal

()

*

0x10000000000L

// 同花

private

static

final

long

FLUSH

=

TexasPokerRank

FLUSH

ordinal

()

*

0x10000000000L

// 葫蘆

private

static

final

long

FULL_HOUSE

=

TexasPokerRank

FULL_HOUSE

ordinal

()

*

0x10000000000L

// 四條

private

static

final

long

FOUR_OF_A_KIND

=

TexasPokerRank

FOUR_OF_A_KIND

ordinal

()

*

0x10000000000L

// 同花順

private

static

final

long

STRAIGHT_FLUSH

=

TexasPokerRank

STRAIGHT_FLUSH

ordinal

()

*

0x10000000000L

// 皇家同花順

private

static

final

long

ROYAL_FLUSH

=

TexasPokerRank

ROYAL_FLUSH

ordinal

()

*

0x10000000000L

// 原始資料

private

byte

[]

cards

private

byte

[]

numbers

private

byte

[]

colors

// 對子數量

private

byte

pairCount

=

0

// 最大三條點數

private

byte

maxthreeOfAKindNumber

=

0

// 最大四條點數

private

byte

maxFourOfAKindNumber

=

0

// 順子最大點數

private

byte

maxNumberOfStraight

=

0

// 同花花色

private

byte

flush

=

0

// 牌型

private

long

rank

// 牌型評估值

private

long

evaluator

/**

* 篩選某人最大牌型

*

* @param tableCards

* @param handCards

* @return

*/

public

static

TexasPokerCalculator

calBestCards

byte

[]

tableCards

byte

[]

handCards

{

List

<

Byte

>

sum

=

new

ArrayList

<>();

for

byte

handCard

handCards

{

sum

add

handCard

);

}

for

byte

tableCard

tableCards

{

sum

add

tableCard

);

}

// 從所有牌中任意選取五張的集合

List

<

List

<

Byte

>>

cnm

=

MathUtil

CNM

sum

5

);

// 所有組合中牌型最大的一種

long

maxValue

=

0

TexasPokerCalculator

maxEval

=

null

for

List

<

Byte

>

list

cnm

{

TexasPokerCalculator

temp

=

new

TexasPokerCalculator

CollectionUtil

toByteArray

list

));

if

temp

getValue

()

>

maxValue

{

maxEval

=

temp

maxValue

=

temp

getValue

();

}

}

return

maxEval

}

public

TexasPokerCalculator

byte

[]

txCards

{

if

txCards

length

!=

2

&&

txCards

length

!=

5

{

throw

new

IllegalArgumentException

“the length must to be two or five”

);

}

numbers

=

new

byte

txCards

length

];

colors

=

new

byte

txCards

length

];

byte

[]

tempCards

=

new

byte

txCards

length

];

System

arraycopy

txCards

0

tempCards

0

txCards

length

);

for

int

i

=

0

i

<

tempCards

length

++

i

{

byte

card

=

tempCards

i

];

numbers

i

=

Pokers

getCardNumber

card

);

colors

i

=

Pokers

getCardColor

card

);

}

// 統計

statistics

();

// 為同花順時,不去匹配其他牌型

if

isStraightFlush

())

{

if

maxNumberOfStraight

==

Pokers

CARD_NUM_ACE

{

rank

=

ROYAL_FLUSH

}

}

else

{

// 匹配牌型

compareRank

();

}

// 將卡牌從大到小排序

for

int

i

=

0

i

<

numbers

length

++

i

{

for

int

j

=

i

+

1

j

<

numbers

length

++

j

{

if

numbers

j

>

numbers

i

])

{

byte

temp

=

numbers

i

];

numbers

i

=

numbers

j

];

numbers

j

=

temp

temp

=

colors

i

];

colors

i

=

colors

j

];

colors

j

=

temp

temp

=

tempCards

i

];

tempCards

i

=

tempCards

j

];

tempCards

j

=

temp

}

}

}

// 如果順子是A2345,將A放至末位

if

maxNumberOfStraight

==

5

{

byte

[]

newCards

=

new

byte

tempCards

length

];

for

int

k

=

0

k

<

newCards

length

k

++)

{

if

k

==

newCards

length

-

1

{

newCards

k

=

tempCards

0

];

}

else

{

newCards

k

=

tempCards

k

+

1

];

}

}

cards

=

newCards

}

else

{

cards

=

tempCards

}

// 評估數值

evaluator

=

evaluator

rank

cards

);

}

/**

* 解析評估數值獲得牌型

*

* @param evaluator

* @param cardCount

* @return

*/

public

static

TexasPokerRank

getCardRank

long

evaluator

int

cardCount

{

return

EnumUtil

byOrdinal

TexasPokerRank

class

int

evaluator

>>

cardCount

<<

3

)));

}

// 評估數值

public

static

long

evaluator

long

rank

byte

[]

txCards

{

long

evaluatorNum

=

rank

for

int

i

=

0

i

<

txCards

length

++

i

{

evaluatorNum

|=

((

long

Pokers

getCardNumber

txCards

i

]))

<<

((

txCards

length

-

i

-

1

<<

3

);

}

return

evaluatorNum

}

/**

* 獲取牌型

*

* @return

*/

public

TexasPokerRank

getRank

()

{

return

EnumUtil

byOrdinal

TexasPokerRank

class

int

this

rank

/

0x10000000000L

));

}

/**

* 獲取牌價值

*

* @return

*/

public

long

getValue

()

{

return

this

evaluator

}

public

byte

[]

getCards

()

{

return

this

cards

}

/** 統計卡牌的點數、花色、對子、三條、四條的數量及順子 */

private

void

statistics

()

{

// 點數容器

byte

[]

cardNumbers

=

new

byte

13

];

// 花色容器

byte

[]

cardColors

=

new

byte

4

];

for

int

i

=

0

i

<

numbers

length

++

i

{

// 統計點數

++

cardNumbers

numbers

i

-

2

];

// 統計花色

++

cardColors

colors

i

-

1

];

if

cardColors

colors

i

-

1

>=

5

{

flush

=

colors

i

];

}

}

for

int

i

=

cardNumbers

length

-

1

i

>=

0

——

i

{

// 給所有對子的點數增加一個區間,用於最後對比牌內大小

if

cardNumbers

i

>

1

{

for

int

j

=

0

j

<

numbers

length

++

j

{

if

numbers

j

==

i

+

2

{

numbers

j

+=

13

}

}

}

// 統計對子

if

cardNumbers

i

==

2

{

++

pairCount

}

// 統計三條

else

if

cardNumbers

i

==

3

&&

cardNumbers

i

>

maxthreeOfAKindNumber

{

maxthreeOfAKindNumber

=

byte

i

+

2

);

}

// 統計四條

else

if

cardNumbers

i

==

4

&&

cardNumbers

i

>

maxFourOfAKindNumber

{

maxFourOfAKindNumber

=

byte

i

+

2

);

}

// 順子

for

int

j

=

i

——

j

{

if

i

<

3

{

break

}

// 滿足A2345特殊情況

if

j

==

-

1

&&

cardNumbers

cardNumbers

length

-

1

>

0

{

maxNumberOfStraight

=

byte

i

+

2

);

break

}

else

if

j

==

-

1

&&

cardNumbers

cardNumbers

length

-

1

<=

0

{

break

}

// 不連續

if

cardNumbers

j

<=

0

{

break

}

// 普通順子

if

j

==

i

-

4

{

maxNumberOfStraight

=

byte

i

+

2

);

break

}

}

}

}

/** 匹配牌型 */

private

void

compareRank

()

{

boolean

hasRank

=

isFourOfAKind

()

||

isFullHouse

()

||

isFlush

()

||

isStraight

()

||

isThreeOfAKind

()

||

isTwoPair

()

||

isOnePair

());

if

(!

hasRank

{

rank

=

HIGH_CARD

}

}

/**

* 一對

*

* @return

*/

private

boolean

isOnePair

()

{

if

pairCount

>

0

{

rank

=

ONE_PAIR

return

true

}

return

false

}

/**

* 兩對

*

* @return

*/

private

boolean

isTwoPair

()

{

if

pairCount

>

1

{

rank

=

TWO_PAIR

return

true

}

return

false

}

/**

* 三條

*

* @return

*/

private

boolean

isThreeOfAKind

()

{

if

maxthreeOfAKindNumber

>

0

{

rank

=

THREE_OF_A_KIND

return

true

}

return

false

}

/**

* 順子

*

* @return

*/

private

boolean

isStraight

()

{

if

maxNumberOfStraight

>

0

{

rank

=

STRAIGHT

return

true

}

return

false

}

/**

* 同花

*

* @return

*/

private

boolean

isFlush

()

{

if

flush

>

0

{

rank

=

FLUSH

return

true

}

return

false

}

/**

* 滿堂彩(俘虜、葫蘆)

*

* @return

*/

private

boolean

isFullHouse

()

{

if

maxthreeOfAKindNumber

>

0

&&

pairCount

>

0

{

rank

=

FULL_HOUSE

return

true

}

return

false

}

/**

* 四條

*

* @return

*/

private

boolean

isFourOfAKind

()

{

if

maxFourOfAKindNumber

>

0

{

rank

=

FOUR_OF_A_KIND

return

true

}

return

false

}

/**

* 同花順

*

* @return

*/

private

boolean

isStraightFlush

()

{

if

isStraight

()

&&

isFlush

())

{

rank

=

STRAIGHT_FLUSH

return

true

}

return

false

}

}

2。核心邏輯執行緒模型(分配計算資源)

這一點我不多說,後面會開一篇文章專門講這個。

遊戲一定會根據遊戲的玩法來劃分執行緒,比如網路,場景,互動等。

RPG遊戲就是兩大塊,

場景

互動

。(這裡留給大家一個問題:

為什麼有些遊戲中的交易系統只允許玩家在相同地圖中完成?

回合制房間遊戲,如果是單程序應用,則用一個執行緒組來管理所有房間就行了。

執行緒具體的東西我會開一篇具體講,此篇點到為止。我手上目前只有一個德州是畫過相關圖的,所以我也只能貼一下德州相關的,僅供參考。

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

3。遊戲主迴圈(Main Loop)

主迴圈,說白了就是一個Timer或者Scheduler,定時傳送訊息到執行緒中,執行緒執行相關操作。

比如:每天0點,每個執行緒應該幹什麼事?清理一些玩家資料,排行榜重新整理。

遊戲主迴圈的作用就是與現實的時間關聯起來,讓遊戲不至於一瞬間就計算完了,這個迴圈讓遊戲有一個“過程性”。

4。遊戲外圍搭建(分割槽分服、連服、唯一ID)

分割槽分服

:玩過RPG遊戲的都知道遊戲有區服,但是為什麼遊戲需要分割槽分服呢。這裡我要解釋一下,長連線遊戲就算玩法上不分割槽,在程序上也一定會做類似的事情。因為單臺機器的併發是有上限的。

連服

:連服其實就是程序內的“跨服”,透過一個key來從邏輯上隔離玩家資料。給一個直觀的實現,假設有這個程序內有N個伺服器,他們是一組連服,他們伺服器的編號分別為1,2,3,4。那麼我們存他們的資料都用Map來達到隔離的目的。雖然這些玩家登入在同一臺主機上,但我們用邏輯讓他們互不可見。連服的目的是:

節約資源

唯一ID

:因為分割槽分服了,我們生成的唯一ID就不再考慮網際網路應用中的那種全域性唯一,我們只需要做到該區唯一就行了。這裡貼一個實現。

public

static

long

nextID

int

areaID

Seed

seed

{

Conditions

args

areaID

<=

0x00FFFFFF

&&

areaID

>=

1

“1<=areaID<=0x00FFFFFF”

);

// areaID24+時間29+自增11

return

(((

long

areaID

&

0x00FFFFFF

))

<<

40

|

seed

next

();

}

public

static

class

Seed

{

private

final

transient

AtomicLong

origin

=

new

AtomicLong

(((

TimeUtil

now

()

/

1000

&

0x000000001FFFFFFFL

<<

11

);

public

static

Seed

create

()

{

return

new

Seed

();

}

private

Seed

()

{}

private

long

next

()

{

return

origin

getAndIncrement

();

}

}

僅作參考,areaID為區域ID,Seed為種子,areaID可以用

渠道+區服來拼接

,比如

public

interface

AreaID

{

/** 主要最小 */

byte

MAJOR_MIN

=

1

/** 次要最大 */

int

MINOR_MAX

=

0xFFFF

/** 次要最小 */

int

MINOR_MIN

=

1

int

areaID

();

static

int

areaID

byte

majorID

int

minorID

{

Conditions

args

minorID

>=

MINOR_MIN

&&

minorID

<=

MINOR_MAX

“%s<=minorID<=%s,yours:%s”

MINOR_MIN

MINOR_MAX

minorID

);

Conditions

args

majorID

>=

MAJOR_MIN

“majorID>=%s,yours:%s”

MAJOR_MIN

majorID

);

return

((

majorID

&

0XFF

<<

16

|

((

minorID

&

0xFFFF

));

}

static

int

minorID

AreaID

key

{

return

minorID

key

areaID

());

}

static

byte

majorID

AreaID

key

{

return

majorID

key

areaID

());

}

static

int

minorID

int

key

{

return

key

&

0X00FFFF

}

static

byte

majorID

int

key

{

return

byte

key

>>

16

);

}

}

5。網路通訊(輸入輸出手段)

略。專開一篇講。

6。靜態配置檔案(移交可變資料)

數值策劃配置Excel,程式把Excel轉化為可讀檔案,放進記憶體。

配置檔案:比如,人物等級經驗,怪物資料,獎勵資料等等。

配置檔案是需要可重載入的。(這就是為什麼有時候玩著玩著遊戲,一些獎勵突然變了)

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

配置檔案給大家一個參考。

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

當然,如果您公司財大氣粗,完全可以做一個配置中心中臺,運營人員可以隨時修改資料,同步到遊戲中。

7。其他執行緒分佈(防業務阻塞)

其他執行緒,防阻塞其他業務,比如第三方登入,充值,資料儲存等都會單開執行緒來工作。

登入特別說明一下,單開執行緒是保證登入的高併發。

8。資料持久化(如何儲存遊戲資料)

遊戲中如果是RPG的話,基本不會用到持久層框架(比如Mybatis),因為我們只會簡單讀取資料和儲存。RPG遊戲必須是低延遲,玩家登入的時候,從資料庫拉出資料到記憶體中基本就再也不會動資料庫了(有時候為了登入效率,玩家儲存的結構會做成LRU快取)。

遊戲每隔一段時候就會儲存部分使用者,關閉伺服器前會儲存所有使用者一次。

設計到複雜查詢的遊戲大概是強互動遊戲,比如SLG,種菜遊戲,這些遊戲更像是WEB應用。

9。介面暴露(提供可操控介面)

遊戲肯定會提供一些API供第三方查詢,比如當前BOSS的狀態,場景上玩家數量等等。

10。審計(日誌記錄)

遊戲會在不同地方埋點來統計一些資料,比如任務完成情況,遊戲活動參與統計,升級日誌,包裹流水等等。

11。重載入(熱更新)

遊戲伺服器對於重啟是低容忍的,特別是長連線遊戲,你一重啟就斷線,玩家肯定會罵孃的。所以我們必須實現熱更新。熱更新的內容我也會單開一篇講。

12。分散式戰場(跨服)

現在玩家不在拘泥於單服的戰鬥,他們希望跟其他伺服器的玩家戰鬥,所以我們必須得把遊戲的戰鬥邏輯單獨抽取出來,形成一個單獨的伺服器型別,叫做戰鬥伺服器。

[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽

咳咳~寫這個系列之前還在想,東西是不是太少了,一下就寫完了,但是……從今天這篇看來,內容實在是太多了,可能還有很多遺漏的。

比如說,遊戲伺服器裡面的一些具體邏輯,包裹,幫會,技能,活動等等。

有些東西寫著寫著就發現寫不完,乾脆就決定單開一篇,但是我發現所有內容都可以單開一篇,好絕望,感覺是個無底洞~

算了,今天就到這兒,後面幾個分類的解釋都是稍微有點水了,沒辦法,深究起來內容太多了。

要是哪位朋友想要了解某方面的,可以留言,有必要的話……我單開一篇來講吧。