[遊戲伺服器從入門到關門]1.組建遊戲伺服器概覽
還未入行或剛入行的小夥伴或許都有一個疑問,一個遊戲伺服器到底有哪些東西,我需要關心什麼技術或者什麼知識。
這裡我大概講一下一般伺服器都會遇到的幾個大的模組以及模組中特別需要注意什麼,概覽的順序以搭建遊戲伺服器過程為參考。
1。遊戲核心邏輯(Demo的建立)
一個遊戲核心肯定是他最最最本身的玩法。
比如:RPG遊戲的
移動
和
戰鬥,戰鬥
無外乎就是
技能
和
BUFF
(介紹一個2D尋路的演算法,大家可以去看看:
JPSPlusGoalBounding
)
回合制遊戲,就只關心房間內,牌桌上,邏輯是什麼。
德州撲克,我們就關心下注、過牌、獎池等,整個伺服器就是一個狀態機,你完全可以想象伺服器就是中間那個美女荷官,他說現在該幹什麼就幹什麼。
夢幻西遊等回合制,我們就關心戰鬥公式,至於表現,根本不用關心(不過有些回合制,比如
陰陽師
,他分兩種情況,第一種:
後端計算,前端演繹
,這意味著,如果你作弊,是完全可以跳過演繹動畫的。第二種:同步戰鬥,一場戰鬥可能會動態加入其它玩家或被其它玩家干擾)。
回合制還有兩種細分:
第一種是大家一起操作完,然後等伺服器計算完畢,客戶端在根據戰報演繹,可以舉例的遊戲就是,石器時代,夢幻西遊等。
第二種是每個單位輪流操作且當前操作的結果會決定下一次的操作分配或者對局結果,比如,陰陽師
如果對陰陽師熟悉的朋友,可以思考一下陰陽師中“封魔之時”玩法,也就是回合制集體打大BOSS是如何實現的。
回到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遊戲就是兩大塊,
場景
和
互動
。(這裡留給大家一個問題:
為什麼有些遊戲中的交易系統只允許玩家在相同地圖中完成?
)
回合制房間遊戲,如果是單程序應用,則用一個執行緒組來管理所有房間就行了。
執行緒具體的東西我會開一篇具體講,此篇點到為止。我手上目前只有一個德州是畫過相關圖的,所以我也只能貼一下德州相關的,僅供參考。
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轉化為可讀檔案,放進記憶體。
配置檔案:比如,人物等級經驗,怪物資料,獎勵資料等等。
配置檔案是需要可重載入的。(這就是為什麼有時候玩著玩著遊戲,一些獎勵突然變了)
配置檔案給大家一個參考。
當然,如果您公司財大氣粗,完全可以做一個配置中心中臺,運營人員可以隨時修改資料,同步到遊戲中。
7。其他執行緒分佈(防業務阻塞)
其他執行緒,防阻塞其他業務,比如第三方登入,充值,資料儲存等都會單開執行緒來工作。
登入特別說明一下,單開執行緒是保證登入的高併發。
8。資料持久化(如何儲存遊戲資料)
遊戲中如果是RPG的話,基本不會用到持久層框架(比如Mybatis),因為我們只會簡單讀取資料和儲存。RPG遊戲必須是低延遲,玩家登入的時候,從資料庫拉出資料到記憶體中基本就再也不會動資料庫了(有時候為了登入效率,玩家儲存的結構會做成LRU快取)。
遊戲每隔一段時候就會儲存部分使用者,關閉伺服器前會儲存所有使用者一次。
設計到複雜查詢的遊戲大概是強互動遊戲,比如SLG,種菜遊戲,這些遊戲更像是WEB應用。
9。介面暴露(提供可操控介面)
遊戲肯定會提供一些API供第三方查詢,比如當前BOSS的狀態,場景上玩家數量等等。
10。審計(日誌記錄)
遊戲會在不同地方埋點來統計一些資料,比如任務完成情況,遊戲活動參與統計,升級日誌,包裹流水等等。
11。重載入(熱更新)
遊戲伺服器對於重啟是低容忍的,特別是長連線遊戲,你一重啟就斷線,玩家肯定會罵孃的。所以我們必須實現熱更新。熱更新的內容我也會單開一篇講。
12。分散式戰場(跨服)
現在玩家不在拘泥於單服的戰鬥,他們希望跟其他伺服器的玩家戰鬥,所以我們必須得把遊戲的戰鬥邏輯單獨抽取出來,形成一個單獨的伺服器型別,叫做戰鬥伺服器。
咳咳~寫這個系列之前還在想,東西是不是太少了,一下就寫完了,但是……從今天這篇看來,內容實在是太多了,可能還有很多遺漏的。
比如說,遊戲伺服器裡面的一些具體邏輯,包裹,幫會,技能,活動等等。
有些東西寫著寫著就發現寫不完,乾脆就決定單開一篇,但是我發現所有內容都可以單開一篇,好絕望,感覺是個無底洞~
算了,今天就到這兒,後面幾個分類的解釋都是稍微有點水了,沒辦法,深究起來內容太多了。
要是哪位朋友想要了解某方面的,可以留言,有必要的話……我單開一篇來講吧。