简介
SuperstellarGolang
health pointsenergy points
技术栈
central servera front end app
game simulationclient network communicationstatisticsmonitoringGo
JavaScriptwebpackPixieJS
在本文的其余部分中,我将讨论后端部分,而客户端应用程序将留待以后讨论。
游戏状态主控模拟 - 在一个地方,而且只有一个地方
Superstellar
下面是服务器工作方式的总体概述。它同时运行三种不同类型的动作:
simulation
20tt + 1
Gogoroutinechanneltickerssimulations stepsGoCPUgoroutinechannels
与客户端通信
websocketsGorilla web toolkitGolangwebsocketswebsocketGorilla
websockethandlerwebsocketclient
superstellar_websocket_handler.go
handler := func(w http.ResponseWriter, r *http.Request) {
conn, err := s.upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
client := NewClient(conn, … //other attributes)
client.Listen()
}
writingreadinggoroutinego
superstellar_websocket_listen.go
func (c *Client) Listen() {
go c.listenWrite()
c.listenRead()
}
readReadMessage()
superstellar_websocket_listen_loop.go
func (c *Client) listenRead() {
for {
messageType, data, err := c.conn.ReadMessage()
if err != nil {
log.Println(err)
} else if messageType == websocket.BinaryMessage {
// unmarshall and handle the data
}
}
}
goroutinegoroutines20050
当我们建立低级通信机制时,我们需要选择双方都将用来交换游戏消息的协议。 事实证明不是那么明显。
通信-协议必须小巧轻便
JSONGolangJavaScriptGostruct
superstellar_json_structs.go
type Spaceship struct {
Position *types.Vector `json:"position"`
Velocity *types.Vector `json:"velocity"`
Facing *types.Vector `json:"facing"`
AngularVelocity float64 `json:"thrust"`
}
JSONJSON
superstellar_json_marshall.go
bytes, err := json.Marshal(spaceship)
JSONJSON410“2147483647”
protobufProtobufDSLJavaScriptstruct
protobuf
superstellar_spaceship.proto
message Spaceship {
uint32 id = 1;
Point position = 2;
Vector velocity = 3;
double facing = 4;
double angularVelocity = 5;
...
}
protobuf
superstellar_spaceship_to_proto.go
func (s *Spaceship) ToProto() *pb.Spaceship {
return &pb.Spaceship {
Id: s.Id(),
Position: s.Position().ToProto(),
Velocity: s.Velocity().ToProto(),
Facing: s.Facing(),
AngularVelocity: s.AngularVelocity(),
...
}
}
最后序列化为原始字节:
superstellar_proto_marshal.go
bytes, err := proto.Marshal(message)
现在,我们可以简单地通过网络以最小的开销将这些字节发送给客户端。
移动平滑和连接滞后补偿
一开始,我们试图在每个模拟帧上发送整个世界的状态。这样,客户端只会在接收到服务器消息时重新绘制屏幕。然而,这种方法导致了大量的网络流量—我们不得不将游戏中每个对象的细节每秒发送50次给所有的客户端,以使动画流畅。太多的数据了!
JavaScript
Internet
从一个程序包到事件调度程序
Go
$ go build
import cycle not allowed
Go
client joinsleavessends an input messagedispatcher
下面是一个示例,说明我们如何使用事件调度程序来传播模拟更新时间间隔。首先,我们需要创建一个能够监听事件的结构:
superstellar_eventdisp_create.go
type Updater struct {}
func (updater *Updater) HandleTimeTick(*events.TimeTick) {
// do something with the event
}
然后我们需要实例化它,并将它注册到事件调度程序中:
superstellar_eventdisp_time_tick.go
updater := Updater{}
eventDispatcher := events.NewEventDispatcher()
eventDispatcher.RegisterTimeTickListener(updater)
ticker
superstellar_eventdisp_time_tick_loop.go
for range time.Tick(constants.PhysicsFrameDuration) {
event := &events.TimeTick{}
eventDispatcher.FireTimeTick(event)
}
goroutine
Go
Go
结论
Go
我们还了解了在创建实时多人游戏时必须面对的问题。 客户端和服务器之间的通信量可能非常大,必须付出很多努力来降低它。 您也不会忘记不可避免地会出现的滞后和网络问题。
最后值得一提的是,创建一个简单的在线游戏也需要大量的工作,无论是在内部实现方面还是在您想使其变得有趣且可玩时。 我们花了无休止的时间讨论要在游戏中放入哪种武器,资源或其他功能,只是意识到要实际实现需要多少工作。 但是,当您尝试做一些对您来说是全新的事情时,即使您设法制造出最小的东西也能给您带来很多满足感。