├── .gitignore ├── README.md ├── client.go ├── gameclient ├── client.go ├── client_js.go └── client_nonjs.go ├── gameserver ├── client.go └── server.go ├── index.html ├── main.go ├── netmsg ├── connect_response.pb.go ├── connect_response.proto ├── disconnect.pb.go ├── disconnect.proto ├── kind.go ├── make.sh ├── update_player.pb.go └── update_player.proto └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | /*.exe 2 | /networkplatformer-go 3 | /*.js 4 | /*.js.map 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Networked Platformer Proof of Concept in Golang 2 | 3 | This is a thrown together remix of the [Ebiten platformer example](https://github.com/hajimehoshi/ebiten/tree/master/examples/platformer). 4 | The difference is that this allows you to host a Websocket server and connect with native or web clients. 5 | 6 | **!!!Warning!!!** 7 | 8 | This has bugs, sub-optimal networking code and poor error handling. This was hastily put together to show that this 9 | is possible with Go. Use this more as a loose guide to getting started! 10 | 11 | **!!!Warning!!!** 12 | 13 | ## Install 14 | 15 | ``` 16 | go get github.com/silbinarywolf/networkplatformer-go 17 | ``` 18 | 19 | ## Requirements 20 | 21 | * Golang 1.10+ 22 | * [Gorilla Websockets](https://github.com/gorilla/websocket) (for native client and server) 23 | * [GopherJS Websockets](https://github.com/gopherjs/websocket) (for JavaScript client) 24 | 25 | ## How to use 26 | 27 | These commands were all tested on Windows, running via Git Bash. 28 | 29 | Build and run server 30 | ``` 31 | go build && ./networkplatformer-go.exe --server 32 | ``` 33 | 34 | Build and run client 35 | ``` 36 | go build && ./networkplatformer-go.exe 37 | ``` 38 | 39 | Build web client (requires GopherJS is installed) 40 | ``` 41 | GOOS=linux gopherjs build 42 | ``` 43 | Then open "index.html" in your browser of choice to run it. (Tested Chrome and Firefox) 44 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/gogo/protobuf/proto" 8 | "github.com/silbinarywolf/networkplatformer-go/gameclient" 9 | "github.com/silbinarywolf/networkplatformer-go/netmsg" 10 | ) 11 | 12 | const ( 13 | maxClients = 256 14 | ) 15 | 16 | var ( 17 | client *Client 18 | isConnected = false 19 | lastWorldUpdateTimer time.Time 20 | ) 21 | 22 | type Client struct { 23 | *gameclient.Client 24 | clientSlots []*Char 25 | } 26 | 27 | func NewClient() *Client { 28 | server := &Client{ 29 | Client: gameclient.NewClient(), 30 | clientSlots: make([]*Char, maxClients), 31 | } 32 | return server 33 | } 34 | 35 | func (c *Client) Update() { 36 | RecvMsgLoop: 37 | for { 38 | select { 39 | case buf := <-c.ChRecv(): 40 | kind := netmsg.Kind(buf[0]) 41 | buf = buf[1:] 42 | switch kind { 43 | case netmsg.MsgConnectResponse: 44 | recvMsg := &netmsg.ConnectResponse{} 45 | err := recvMsg.Unmarshal(buf) 46 | if err != nil { 47 | log.Fatal("marshaling error: ", err) 48 | break 49 | } 50 | 51 | // Receive starting pos from server and add to chars to simulate 52 | you.X = recvMsg.X 53 | you.Y = recvMsg.Y 54 | chars = append(chars, you) 55 | isConnected = true 56 | 57 | // Last time we received an update about the world 58 | lastWorldUpdateTimer = time.Now() 59 | 60 | log.Printf("%s: received login data: %v\n", kind, recvMsg) 61 | case netmsg.MsgUpdatePlayer: 62 | recvMsg := &netmsg.UpdatePlayer{} 63 | err := recvMsg.Unmarshal(buf) 64 | if err != nil { 65 | log.Fatal("marshaling error: ", err) 66 | break 67 | } 68 | clientSlot := recvMsg.GetClientSlot() 69 | char := c.clientSlots[clientSlot] 70 | if char == nil { 71 | // Create char if they don't exist 72 | char = &Char{} 73 | chars = append(chars, char) 74 | c.clientSlots[clientSlot] = char 75 | } 76 | char.X = recvMsg.X 77 | char.Y = recvMsg.Y 78 | char.isKeyLeftPressed = recvMsg.IsKeyLeftPressed 79 | char.isKeyRightPressed = recvMsg.IsKeyRightPressed 80 | case netmsg.MsgDisconnectPlayer: 81 | recvMsg := &netmsg.DisconnectPlayer{} 82 | err := recvMsg.Unmarshal(buf) 83 | if err != nil { 84 | log.Fatal("marshaling error: ", err) 85 | break 86 | } 87 | clientSlot := recvMsg.GetClientSlot() 88 | char := c.clientSlots[clientSlot] 89 | if char == nil { 90 | continue 91 | } 92 | char.RemoveFromSimulation() 93 | c.clientSlots[clientSlot] = nil 94 | default: 95 | log.Printf("Unhandled netmsg kind: %s, with data: %v", kind.String(), buf) 96 | } 97 | case <-c.ChDisconnected(): 98 | isConnected = false 99 | you.RemoveFromSimulation() 100 | 101 | log.Println("Lost connection to server") 102 | default: 103 | // no more messages 104 | break RecvMsgLoop 105 | } 106 | } 107 | 108 | // 109 | if you != nil && isConnected { 110 | elapsed := time.Since(lastWorldUpdateTimer) 111 | if elapsed > 15*time.Millisecond { 112 | lastWorldUpdateTimer = time.Now() 113 | 114 | // Send update data to server 115 | sendMsg := netmsg.UpdatePlayer{ 116 | X: you.X, 117 | Y: you.Y, 118 | IsKeyLeftPressed: you.isKeyLeftPressed, 119 | IsKeyRightPressed: you.isKeyRightPressed, 120 | } 121 | data, err := proto.Marshal(&sendMsg) 122 | if err != nil { 123 | log.Fatal("client update: marshaling error: ", err) 124 | } 125 | 126 | // send update message to server 127 | packetData := make([]byte, 1, len(data)+1) 128 | packetData[0] = netmsg.MsgUpdatePlayer 129 | packetData = append(packetData, data...) 130 | client.SendMessage(packetData) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /gameclient/client.go: -------------------------------------------------------------------------------- 1 | package gameclient 2 | 3 | import "time" 4 | 5 | const ( 6 | // Time allowed to write a message to the peer. 7 | writeWait = 5000 * time.Millisecond 8 | 9 | // Time allowed to read the next pong message from the peer. 10 | pongWait = 60 * time.Second 11 | 12 | // Send pings to peer with this period. Must be less than pongWait. 13 | pingPeriod = (pongWait * 9) / 10 14 | 15 | // Maximum message size allowed from peer. 16 | maxMessageSize = 512 17 | ) 18 | 19 | type clientShared struct { 20 | // Inbound messages from the server. 21 | recv chan []byte 22 | 23 | // Disconnect 24 | disconnect chan bool 25 | } 26 | 27 | func newClientShared() clientShared { 28 | return clientShared{ 29 | recv: make(chan []byte, 256), 30 | disconnect: make(chan bool), 31 | } 32 | } 33 | 34 | func (c *clientShared) ChRecv() chan []byte { return c.recv } 35 | 36 | func (c *clientShared) ChDisconnected() chan bool { return c.disconnect } 37 | -------------------------------------------------------------------------------- /gameclient/client_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package gameclient 4 | 5 | import ( 6 | "net" 7 | 8 | "github.com/gopherjs/websocket" 9 | ) 10 | 11 | type Client struct { 12 | clientShared 13 | 14 | conn net.Conn 15 | 16 | // Outbound messages to the server. 17 | send chan []byte 18 | } 19 | 20 | func NewClient() *Client { 21 | return &Client{ 22 | clientShared: newClientShared(), 23 | conn: nil, 24 | } 25 | } 26 | 27 | func (c *Client) Dial(addr string) error { 28 | conn, err := websocket.Dial("ws://" + addr + "/ws") // Blocks until connection is established. 29 | if err != nil { 30 | // handle error 31 | return err 32 | } 33 | c.conn = conn 34 | return nil 35 | } 36 | 37 | func (c *Client) DialTLS(addr string) error { 38 | conn, err := websocket.Dial("wss://" + addr + "/ws") // Blocks until connection is established. 39 | if err != nil { 40 | // handle error 41 | return err 42 | } 43 | c.conn = conn 44 | return nil 45 | } 46 | 47 | func (c *Client) Listen() error { 48 | err := c.readPump() // this is blocking 49 | c.disconnect <- true 50 | c.conn.Close() 51 | return err 52 | } 53 | 54 | func (c *Client) SendMessage(message []byte) { 55 | // NOTE(Jake): 2018-05-27 56 | // 57 | // This is not blocking, at least for JavaScript impl. of Websocket. 58 | // 59 | // We also don't need to add a write deadline because its non-blocking. 60 | // (this function returns nil) 61 | // 62 | //c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 63 | c.conn.Write(message) 64 | } 65 | 66 | func (c *Client) readPump() error { 67 | conn := c.conn 68 | for { 69 | // NOTE(Jake): 2018-06-20 70 | // 71 | // Disabling this in hopes that it fixes timeout issues 72 | // on the server. JavaScript impl. might not need this 73 | // and might just close automatically after X amount of time. 74 | // 75 | //conn.SetReadDeadline(time.Now().Add(pongWait)) 76 | 77 | // todo(Jake): 2018-05-27 78 | // 79 | // Perhaps profile / figure out how keep allocations here low? 80 | // Maybe this isnt even a big deal? 81 | // 82 | buf := make([]byte, 1024) 83 | size, err := conn.Read(buf) // Blocks until a WebSocket frame is received. 84 | if err != nil { 85 | // handle error 86 | return err 87 | } 88 | buf = buf[:size] 89 | c.recv <- buf 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /gameclient/client_nonjs.go: -------------------------------------------------------------------------------- 1 | // +build darwin freebsd linux windows 2 | // +build !js 3 | // +build !android 4 | // +build !ios 5 | 6 | package gameclient 7 | 8 | import ( 9 | "log" 10 | "time" 11 | 12 | "github.com/gorilla/websocket" 13 | ) 14 | 15 | type Client struct { 16 | clientShared 17 | conn *websocket.Conn 18 | 19 | // Outbound messages to the server. 20 | send chan []byte 21 | } 22 | 23 | func NewClient() *Client { 24 | return &Client{ 25 | clientShared: newClientShared(), 26 | conn: nil, 27 | send: make(chan []byte, 256), 28 | } 29 | } 30 | 31 | func (c *Client) Dial(addr string) error { 32 | conn, _, err := websocket.DefaultDialer.Dial("ws://"+addr+"/ws", nil) 33 | if err != nil { 34 | return err 35 | } 36 | c.conn = conn 37 | return nil 38 | } 39 | 40 | func (c *Client) DialTLS(addr string) error { 41 | conn, _, err := websocket.DefaultDialer.Dial("wss://"+addr+"/ws", nil) 42 | if err != nil { 43 | return err 44 | } 45 | c.conn = conn 46 | return nil 47 | } 48 | 49 | func (c *Client) Listen() { 50 | go c.writePump() 51 | go c.readPump() 52 | } 53 | 54 | func (c *Client) ChRecv() chan []byte { return c.recv } 55 | 56 | func (c *Client) SendMessage(message []byte) { 57 | c.send <- message 58 | } 59 | 60 | // readPump pumps messages from the websocket connection to the hub. 61 | // 62 | // The application runs readPump in a per-connection goroutine. The application 63 | // ensures that there is at most one reader on a connection by executing all 64 | // reads from this goroutine. 65 | func (c *Client) readPump() { 66 | defer func() { 67 | c.conn.Close() 68 | c.disconnect <- true 69 | }() 70 | c.conn.SetReadLimit(maxMessageSize) 71 | c.conn.SetReadDeadline(time.Now().Add(pongWait)) 72 | c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) 73 | for { 74 | _, buf, err := c.conn.ReadMessage() 75 | if err != nil { 76 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 77 | log.Printf("error: %v", err) 78 | } 79 | break 80 | } 81 | c.recv <- buf 82 | } 83 | } 84 | 85 | // writePump pumps messages from the hub to the websocket connection. 86 | // 87 | // A goroutine running writePump is started for each connection. The 88 | // application ensures that there is at most one writer to a connection by 89 | // executing all writes from this goroutine. 90 | func (c *Client) writePump() { 91 | ticker := time.NewTicker(pingPeriod) 92 | defer func() { 93 | ticker.Stop() 94 | c.conn.Close() 95 | c.disconnect <- true 96 | }() 97 | for { 98 | select { 99 | case message, ok := <-c.send: 100 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 101 | if !ok { 102 | // The hub closed the channel. 103 | c.conn.WriteMessage(websocket.CloseMessage, []byte{}) 104 | return 105 | } 106 | 107 | w, err := c.conn.NextWriter(websocket.BinaryMessage) 108 | if err != nil { 109 | return 110 | } 111 | w.Write(message) 112 | 113 | if err := w.Close(); err != nil { 114 | return 115 | } 116 | case <-ticker.C: 117 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 118 | if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { 119 | return 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /gameserver/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gameserver 6 | 7 | import ( 8 | "net/http" 9 | "time" 10 | 11 | "github.com/gorilla/websocket" 12 | ) 13 | 14 | const ( 15 | // Time allowed to write a message to the peer. 16 | writeWait = 1000 * time.Millisecond 17 | 18 | // Time allowed to read the next pong message from the peer. 19 | pongWait = 60 * time.Second 20 | 21 | // Send pings to peer with this period. Must be less than pongWait. 22 | pingPeriod = (pongWait * 9) / 10 23 | 24 | // Maximum message size allowed from peer. 25 | maxMessageSize = 128 26 | ) 27 | 28 | var upgrader = websocket.Upgrader{ 29 | ReadBufferSize: 1024, 30 | WriteBufferSize: 1024, 31 | CheckOrigin: func(r *http.Request) bool { 32 | // todo(Jake): Make sure this URL is validated. 33 | println("Client from URL: ", r.URL.String()) 34 | return true 35 | }, 36 | } 37 | 38 | type Message struct { 39 | client *Client 40 | data []byte 41 | } 42 | 43 | func (message *Message) Client() *Client { return message.client } 44 | func (message *Message) Data() []byte { return message.data } 45 | 46 | // Client is a middleman between the websocket connection and the hub. 47 | type Client struct { 48 | server *Server 49 | 50 | // The websocket connection. 51 | conn *websocket.Conn 52 | 53 | // Buffered channel of outbound messages. 54 | send chan []byte 55 | 56 | // Client slot 57 | clientSlot int32 58 | 59 | // Arbitrary data for user-code use. Store the related player entity, etc. 60 | data interface{} 61 | } 62 | 63 | func (c *Client) SetData(data interface{}) { 64 | c.data = data 65 | } 66 | 67 | func (c *Client) Data() interface{} { 68 | return c.data 69 | } 70 | 71 | func (c *Client) ClientSlot() int32 { 72 | return c.clientSlot 73 | } 74 | 75 | func (c *Client) SendMessage(message []byte) { 76 | c.send <- message 77 | } 78 | 79 | // readPump pumps messages from the websocket connection to the hub. 80 | // 81 | // The application runs readPump in a per-connection goroutine. The application 82 | // ensures that there is at most one reader on a connection by executing all 83 | // reads from this goroutine. 84 | func (c *Client) readPump() { 85 | c.conn.SetReadLimit(maxMessageSize) 86 | c.conn.SetReadDeadline(time.Now().Add(pongWait)) 87 | c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) 88 | for { 89 | _, buf, err := c.conn.ReadMessage() 90 | if err != nil { 91 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 92 | println("websocket.IsUnexpectedCloseError: " + err.Error()) 93 | } 94 | break 95 | } 96 | //message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1)) 97 | c.server.broadcast <- Message{ 98 | client: c, 99 | data: buf, 100 | } 101 | } 102 | c.server.unregister <- c 103 | c.conn.Close() 104 | } 105 | 106 | // writePump pumps messages from the hub to the websocket connection. 107 | // 108 | // A goroutine running writePump is started for each connection. The 109 | // application ensures that there is at most one writer to a connection by 110 | // executing all writes from this goroutine. 111 | func (c *Client) writePump() { 112 | ticker := time.NewTicker(pingPeriod) 113 | defer func() { 114 | ticker.Stop() 115 | c.conn.Close() 116 | }() 117 | for { 118 | select { 119 | case message, ok := <-c.send: 120 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 121 | if !ok { 122 | // The server closed the channel. 123 | c.conn.WriteMessage(websocket.CloseMessage, []byte{}) 124 | return 125 | } 126 | 127 | w, err := c.conn.NextWriter(websocket.BinaryMessage) 128 | if err != nil { 129 | return 130 | } 131 | w.Write(message) 132 | 133 | if err := w.Close(); err != nil { 134 | println("Client disconnected. Err = ", err) 135 | return 136 | } 137 | case <-ticker.C: 138 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 139 | if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { 140 | return 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /gameserver/server.go: -------------------------------------------------------------------------------- 1 | package gameserver 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | const ( 10 | maxClients = 256 11 | ) 12 | 13 | var ( 14 | ErrNoMoreClientSlots = errors.New("No more free client slots.") 15 | ) 16 | 17 | type Server struct { 18 | addr string 19 | 20 | // 21 | clientSlots []bool 22 | 23 | // Registered clients. 24 | clients map[*Client]bool 25 | 26 | // Inbound messages from the clients. 27 | broadcast chan Message 28 | 29 | // Register requests from the clients. 30 | register chan *Client 31 | 32 | // Unregister requests from clients. 33 | unregister chan *Client 34 | } 35 | 36 | // Create new chat server. 37 | func NewServer(pattern string) *Server { 38 | return &Server{ 39 | addr: ":8080", 40 | clientSlots: make([]bool, maxClients), 41 | broadcast: make(chan Message), 42 | register: make(chan *Client), 43 | unregister: make(chan *Client), 44 | clients: make(map[*Client]bool), 45 | } 46 | } 47 | 48 | // Listen and serve. 49 | // It serves client connection and broadcast request. 50 | func (s *Server) Listen() { 51 | http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { 52 | s.serveWs(w, r) 53 | }) 54 | println("Listening server...") 55 | err := http.ListenAndServe(s.addr, nil) 56 | if err != nil { 57 | println("Failed to listen:", err.Error()) 58 | } 59 | } 60 | 61 | func (s *Server) ListenTLS(sslCert string, sslKey string) { 62 | http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { 63 | s.serveWs(w, r) 64 | }) 65 | println("Listening server...") 66 | err := http.ListenAndServeTLS(s.addr, sslCert, sslKey, nil) 67 | if err != nil { 68 | println("Failed to listen:", err.Error()) 69 | } 70 | } 71 | 72 | func (s *Server) ChRegister() chan *Client { return s.register } 73 | 74 | func (s *Server) ChUnregister() chan *Client { return s.unregister } 75 | 76 | func (s *Server) ChBroadcast() chan Message { return s.broadcast } 77 | 78 | func (s *Server) GetMaxClients() int32 { return int32(len(s.clientSlots)) } 79 | 80 | func (s *Server) GetClients() map[*Client]bool { return s.clients } 81 | 82 | func (s *Server) HasClient(c *Client) bool { 83 | _, ok := s.clients[c] 84 | return ok 85 | } 86 | 87 | func (s *Server) RegisterClient(c *Client, data interface{}) { 88 | c.data = data 89 | s.clients[c] = true 90 | } 91 | 92 | func (s *Server) RemoveClient(c *Client) bool { 93 | if _, ok := s.clients[c]; ok { 94 | s.clientSlots[c.clientSlot] = false 95 | close(c.send) 96 | delete(s.clients, c) 97 | return true 98 | } 99 | return false 100 | } 101 | 102 | // serveWs handles websocket requests from the peer. 103 | func (s *Server) serveWs(w http.ResponseWriter, r *http.Request) { 104 | conn, err := upgrader.Upgrade(w, r, nil) 105 | if err != nil { 106 | log.Println(err) 107 | return 108 | } 109 | clientSlot, err := s.getNextFreeClientSlot() 110 | if err != nil { 111 | log.Println(err) 112 | return 113 | } 114 | client := &Client{ 115 | server: s, 116 | conn: conn, 117 | clientSlot: clientSlot, 118 | send: make(chan []byte, 256), 119 | } 120 | s.clientSlots[clientSlot] = true 121 | client.server.register <- client 122 | 123 | // Allow collection of memory referenced by the caller by doing all work in 124 | // new goroutines. 125 | go client.writePump() 126 | go client.readPump() 127 | 128 | println("Client connected!") 129 | } 130 | 131 | func (s *Server) getNextFreeClientSlot() (int32, error) { 132 | maxClients := s.GetMaxClients() 133 | for i := int32(0); i < maxClients; i++ { 134 | if !s.clientSlots[i] { 135 | return i, nil 136 | } 137 | } 138 | return 0, ErrNoMoreClientSlots 139 | } 140 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Ebiten Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "image" 21 | _ "image/png" 22 | "os" 23 | "time" 24 | 25 | "github.com/hajimehoshi/ebiten" 26 | "github.com/hajimehoshi/ebiten/ebitenutil" 27 | rplatformer "github.com/hajimehoshi/ebiten/examples/resources/images/platformer" 28 | ) 29 | 30 | const ( 31 | // Settings 32 | screenWidth = 1024 33 | screenHeight = 512 34 | ) 35 | 36 | var ( 37 | leftSprite *ebiten.Image 38 | rightSprite *ebiten.Image 39 | idleSprite *ebiten.Image 40 | backgroundImage *ebiten.Image 41 | ) 42 | 43 | func init() { 44 | // Preload images 45 | img, _, err := image.Decode(bytes.NewReader(rplatformer.Right_png)) 46 | if err != nil { 47 | panic(err) 48 | } 49 | rightSprite, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault) 50 | 51 | img, _, err = image.Decode(bytes.NewReader(rplatformer.Left_png)) 52 | if err != nil { 53 | panic(err) 54 | } 55 | leftSprite, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault) 56 | 57 | img, _, err = image.Decode(bytes.NewReader(rplatformer.MainChar_png)) 58 | if err != nil { 59 | panic(err) 60 | } 61 | idleSprite, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault) 62 | 63 | img, _, err = image.Decode(bytes.NewReader(rplatformer.Background_png)) 64 | if err != nil { 65 | panic(err) 66 | } 67 | backgroundImage, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault) 68 | } 69 | 70 | type Char struct { 71 | X float64 72 | Y float64 73 | sprite *ebiten.Image 74 | isKeyLeftPressed bool 75 | isKeyRightPressed bool 76 | 77 | // used by server only 78 | lastUpdatedTimer time.Time 79 | } 80 | 81 | func (c *Char) RemoveFromSimulation() { 82 | // Unordered remove 83 | for i, char := range chars { 84 | if char == c { 85 | chars[i] = chars[len(chars)-1] // Replace it with the last one. 86 | chars = chars[:len(chars)-1] // delete last element 87 | return 88 | } 89 | } 90 | } 91 | 92 | var ( 93 | you *Char = &Char{ 94 | X: 50, 95 | Y: 380, 96 | } 97 | chars []*Char = make([]*Char, 0, 256) 98 | ) 99 | 100 | func update(screen *ebiten.Image) error { 101 | // Read/write network information 102 | if server != nil { 103 | server.Update() 104 | } 105 | if client != nil { 106 | client.Update() 107 | } 108 | 109 | // Controls 110 | if you != nil { 111 | you.isKeyLeftPressed = false 112 | you.isKeyRightPressed = false 113 | if ebiten.IsKeyPressed(ebiten.KeyA) || ebiten.IsKeyPressed(ebiten.KeyLeft) { 114 | you.isKeyLeftPressed = true 115 | } else if ebiten.IsKeyPressed(ebiten.KeyD) || ebiten.IsKeyPressed(ebiten.KeyRight) { 116 | you.isKeyRightPressed = true 117 | } 118 | } 119 | 120 | // Simulate 121 | for _, char := range chars { 122 | char.sprite = idleSprite 123 | if char.isKeyLeftPressed { 124 | // Selects preloaded sprite 125 | char.sprite = leftSprite 126 | // Moves character 3px right 127 | char.X -= 3 128 | } else if char.isKeyRightPressed { 129 | // Selects preloaded sprite 130 | char.sprite = rightSprite 131 | // Moves character 3px left 132 | char.X += 3 133 | } 134 | } 135 | 136 | if ebiten.IsRunningSlowly() { 137 | return nil 138 | } 139 | 140 | // Draws Background Image 141 | op := &ebiten.DrawImageOptions{} 142 | op.GeoM.Scale(0.5, 0.5) 143 | screen.DrawImage(backgroundImage, op) 144 | 145 | // Draws selected sprite image 146 | for _, char := range chars { 147 | if char.sprite == nil { 148 | continue 149 | } 150 | op = &ebiten.DrawImageOptions{} 151 | op.GeoM.Scale(0.5, 0.5) 152 | op.GeoM.Translate(char.X, char.Y) 153 | screen.DrawImage(char.sprite, op) 154 | } 155 | 156 | // FPS counter 157 | fps := fmt.Sprintf("FPS: %f", ebiten.CurrentFPS()) 158 | ebitenutil.DebugPrint(screen, fps) 159 | 160 | return nil 161 | } 162 | 163 | func main() { 164 | // Setup network 165 | isServer := false 166 | if len(os.Args) > 1 { 167 | firstArg := os.Args[1] 168 | if firstArg == "--server" { 169 | isServer = true 170 | } 171 | } 172 | if isServer { 173 | server = NewServer() 174 | go server.Listen() 175 | } else { 176 | client = NewClient() 177 | err := client.Dial("localhost:8080") 178 | if err != nil { 179 | panic(err) 180 | } 181 | go client.Listen() 182 | } 183 | 184 | // This is required so the server can run when the window isn't focused. 185 | ebiten.SetRunnableInBackground(true) 186 | 187 | if err := ebiten.Run(update, screenWidth, screenHeight, 1, "Platformer (Ebiten Demo)"); err != nil { 188 | panic(err) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /netmsg/connect_response.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: connect_response.proto 3 | 4 | /* 5 | Package netmsg is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | connect_response.proto 9 | 10 | It has these top-level messages: 11 | ConnectResponse 12 | */ 13 | package netmsg 14 | 15 | import proto "github.com/golang/protobuf/proto" 16 | import fmt "fmt" 17 | import math "math" 18 | 19 | import binary "encoding/binary" 20 | 21 | import io "io" 22 | 23 | // Reference imports to suppress errors if they are not otherwise used. 24 | var _ = proto.Marshal 25 | var _ = fmt.Errorf 26 | var _ = math.Inf 27 | 28 | // This is a compile-time assertion to ensure that this generated file 29 | // is compatible with the proto package it is being compiled against. 30 | // A compilation error at this line likely means your copy of the 31 | // proto package needs to be updated. 32 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 33 | 34 | type ConnectResponse struct { 35 | ClientSlot int32 `protobuf:"varint,1,opt,name=ClientSlot,proto3" json:"ClientSlot,omitempty"` 36 | X float64 `protobuf:"fixed64,2,opt,name=X,proto3" json:"X,omitempty"` 37 | Y float64 `protobuf:"fixed64,3,opt,name=Y,proto3" json:"Y,omitempty"` 38 | } 39 | 40 | func (m *ConnectResponse) Reset() { *m = ConnectResponse{} } 41 | func (m *ConnectResponse) String() string { return proto.CompactTextString(m) } 42 | func (*ConnectResponse) ProtoMessage() {} 43 | func (*ConnectResponse) Descriptor() ([]byte, []int) { return fileDescriptorConnectResponse, []int{0} } 44 | 45 | func (m *ConnectResponse) GetClientSlot() int32 { 46 | if m != nil { 47 | return m.ClientSlot 48 | } 49 | return 0 50 | } 51 | 52 | func (m *ConnectResponse) GetX() float64 { 53 | if m != nil { 54 | return m.X 55 | } 56 | return 0 57 | } 58 | 59 | func (m *ConnectResponse) GetY() float64 { 60 | if m != nil { 61 | return m.Y 62 | } 63 | return 0 64 | } 65 | 66 | func init() { 67 | proto.RegisterType((*ConnectResponse)(nil), "netmsg.ConnectResponse") 68 | } 69 | func (m *ConnectResponse) Marshal() (dAtA []byte, err error) { 70 | size := m.Size() 71 | dAtA = make([]byte, size) 72 | n, err := m.MarshalTo(dAtA) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return dAtA[:n], nil 77 | } 78 | 79 | func (m *ConnectResponse) MarshalTo(dAtA []byte) (int, error) { 80 | var i int 81 | _ = i 82 | var l int 83 | _ = l 84 | if m.ClientSlot != 0 { 85 | dAtA[i] = 0x8 86 | i++ 87 | i = encodeVarintConnectResponse(dAtA, i, uint64(m.ClientSlot)) 88 | } 89 | if m.X != 0 { 90 | dAtA[i] = 0x11 91 | i++ 92 | binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.X)))) 93 | i += 8 94 | } 95 | if m.Y != 0 { 96 | dAtA[i] = 0x19 97 | i++ 98 | binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Y)))) 99 | i += 8 100 | } 101 | return i, nil 102 | } 103 | 104 | func encodeVarintConnectResponse(dAtA []byte, offset int, v uint64) int { 105 | for v >= 1<<7 { 106 | dAtA[offset] = uint8(v&0x7f | 0x80) 107 | v >>= 7 108 | offset++ 109 | } 110 | dAtA[offset] = uint8(v) 111 | return offset + 1 112 | } 113 | func (m *ConnectResponse) Size() (n int) { 114 | var l int 115 | _ = l 116 | if m.ClientSlot != 0 { 117 | n += 1 + sovConnectResponse(uint64(m.ClientSlot)) 118 | } 119 | if m.X != 0 { 120 | n += 9 121 | } 122 | if m.Y != 0 { 123 | n += 9 124 | } 125 | return n 126 | } 127 | 128 | func sovConnectResponse(x uint64) (n int) { 129 | for { 130 | n++ 131 | x >>= 7 132 | if x == 0 { 133 | break 134 | } 135 | } 136 | return n 137 | } 138 | func sozConnectResponse(x uint64) (n int) { 139 | return sovConnectResponse(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 140 | } 141 | func (m *ConnectResponse) Unmarshal(dAtA []byte) error { 142 | l := len(dAtA) 143 | iNdEx := 0 144 | for iNdEx < l { 145 | preIndex := iNdEx 146 | var wire uint64 147 | for shift := uint(0); ; shift += 7 { 148 | if shift >= 64 { 149 | return ErrIntOverflowConnectResponse 150 | } 151 | if iNdEx >= l { 152 | return io.ErrUnexpectedEOF 153 | } 154 | b := dAtA[iNdEx] 155 | iNdEx++ 156 | wire |= (uint64(b) & 0x7F) << shift 157 | if b < 0x80 { 158 | break 159 | } 160 | } 161 | fieldNum := int32(wire >> 3) 162 | wireType := int(wire & 0x7) 163 | if wireType == 4 { 164 | return fmt.Errorf("proto: ConnectResponse: wiretype end group for non-group") 165 | } 166 | if fieldNum <= 0 { 167 | return fmt.Errorf("proto: ConnectResponse: illegal tag %d (wire type %d)", fieldNum, wire) 168 | } 169 | switch fieldNum { 170 | case 1: 171 | if wireType != 0 { 172 | return fmt.Errorf("proto: wrong wireType = %d for field ClientSlot", wireType) 173 | } 174 | m.ClientSlot = 0 175 | for shift := uint(0); ; shift += 7 { 176 | if shift >= 64 { 177 | return ErrIntOverflowConnectResponse 178 | } 179 | if iNdEx >= l { 180 | return io.ErrUnexpectedEOF 181 | } 182 | b := dAtA[iNdEx] 183 | iNdEx++ 184 | m.ClientSlot |= (int32(b) & 0x7F) << shift 185 | if b < 0x80 { 186 | break 187 | } 188 | } 189 | case 2: 190 | if wireType != 1 { 191 | return fmt.Errorf("proto: wrong wireType = %d for field X", wireType) 192 | } 193 | var v uint64 194 | if (iNdEx + 8) > l { 195 | return io.ErrUnexpectedEOF 196 | } 197 | v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) 198 | iNdEx += 8 199 | m.X = float64(math.Float64frombits(v)) 200 | case 3: 201 | if wireType != 1 { 202 | return fmt.Errorf("proto: wrong wireType = %d for field Y", wireType) 203 | } 204 | var v uint64 205 | if (iNdEx + 8) > l { 206 | return io.ErrUnexpectedEOF 207 | } 208 | v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) 209 | iNdEx += 8 210 | m.Y = float64(math.Float64frombits(v)) 211 | default: 212 | iNdEx = preIndex 213 | skippy, err := skipConnectResponse(dAtA[iNdEx:]) 214 | if err != nil { 215 | return err 216 | } 217 | if skippy < 0 { 218 | return ErrInvalidLengthConnectResponse 219 | } 220 | if (iNdEx + skippy) > l { 221 | return io.ErrUnexpectedEOF 222 | } 223 | iNdEx += skippy 224 | } 225 | } 226 | 227 | if iNdEx > l { 228 | return io.ErrUnexpectedEOF 229 | } 230 | return nil 231 | } 232 | func skipConnectResponse(dAtA []byte) (n int, err error) { 233 | l := len(dAtA) 234 | iNdEx := 0 235 | for iNdEx < l { 236 | var wire uint64 237 | for shift := uint(0); ; shift += 7 { 238 | if shift >= 64 { 239 | return 0, ErrIntOverflowConnectResponse 240 | } 241 | if iNdEx >= l { 242 | return 0, io.ErrUnexpectedEOF 243 | } 244 | b := dAtA[iNdEx] 245 | iNdEx++ 246 | wire |= (uint64(b) & 0x7F) << shift 247 | if b < 0x80 { 248 | break 249 | } 250 | } 251 | wireType := int(wire & 0x7) 252 | switch wireType { 253 | case 0: 254 | for shift := uint(0); ; shift += 7 { 255 | if shift >= 64 { 256 | return 0, ErrIntOverflowConnectResponse 257 | } 258 | if iNdEx >= l { 259 | return 0, io.ErrUnexpectedEOF 260 | } 261 | iNdEx++ 262 | if dAtA[iNdEx-1] < 0x80 { 263 | break 264 | } 265 | } 266 | return iNdEx, nil 267 | case 1: 268 | iNdEx += 8 269 | return iNdEx, nil 270 | case 2: 271 | var length int 272 | for shift := uint(0); ; shift += 7 { 273 | if shift >= 64 { 274 | return 0, ErrIntOverflowConnectResponse 275 | } 276 | if iNdEx >= l { 277 | return 0, io.ErrUnexpectedEOF 278 | } 279 | b := dAtA[iNdEx] 280 | iNdEx++ 281 | length |= (int(b) & 0x7F) << shift 282 | if b < 0x80 { 283 | break 284 | } 285 | } 286 | iNdEx += length 287 | if length < 0 { 288 | return 0, ErrInvalidLengthConnectResponse 289 | } 290 | return iNdEx, nil 291 | case 3: 292 | for { 293 | var innerWire uint64 294 | var start int = iNdEx 295 | for shift := uint(0); ; shift += 7 { 296 | if shift >= 64 { 297 | return 0, ErrIntOverflowConnectResponse 298 | } 299 | if iNdEx >= l { 300 | return 0, io.ErrUnexpectedEOF 301 | } 302 | b := dAtA[iNdEx] 303 | iNdEx++ 304 | innerWire |= (uint64(b) & 0x7F) << shift 305 | if b < 0x80 { 306 | break 307 | } 308 | } 309 | innerWireType := int(innerWire & 0x7) 310 | if innerWireType == 4 { 311 | break 312 | } 313 | next, err := skipConnectResponse(dAtA[start:]) 314 | if err != nil { 315 | return 0, err 316 | } 317 | iNdEx = start + next 318 | } 319 | return iNdEx, nil 320 | case 4: 321 | return iNdEx, nil 322 | case 5: 323 | iNdEx += 4 324 | return iNdEx, nil 325 | default: 326 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 327 | } 328 | } 329 | panic("unreachable") 330 | } 331 | 332 | var ( 333 | ErrInvalidLengthConnectResponse = fmt.Errorf("proto: negative length found during unmarshaling") 334 | ErrIntOverflowConnectResponse = fmt.Errorf("proto: integer overflow") 335 | ) 336 | 337 | func init() { proto.RegisterFile("connect_response.proto", fileDescriptorConnectResponse) } 338 | 339 | var fileDescriptorConnectResponse = []byte{ 340 | // 135 bytes of a gzipped FileDescriptorProto 341 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4b, 0xce, 0xcf, 0xcb, 342 | 0x4b, 0x4d, 0x2e, 0x89, 0x2f, 0x4a, 0x2d, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 343 | 0x2f, 0xc9, 0x17, 0x62, 0xcb, 0x4b, 0x2d, 0xc9, 0x2d, 0x4e, 0x57, 0xf2, 0xe5, 0xe2, 0x77, 0x86, 344 | 0xa8, 0x08, 0x82, 0x2a, 0x10, 0x92, 0xe3, 0xe2, 0x72, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0x09, 0xce, 345 | 0xc9, 0x2f, 0x91, 0x60, 0x54, 0x60, 0xd4, 0x60, 0x0d, 0x42, 0x12, 0x11, 0xe2, 0xe1, 0x62, 0x8c, 346 | 0x90, 0x60, 0x52, 0x60, 0xd4, 0x60, 0x0c, 0x62, 0x8c, 0x00, 0xf1, 0x22, 0x25, 0x98, 0x21, 0xbc, 347 | 0x48, 0x27, 0x81, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 348 | 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0xb0, 0x7d, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x03, 349 | 0x94, 0x49, 0x55, 0x89, 0x00, 0x00, 0x00, 350 | } 351 | -------------------------------------------------------------------------------- /netmsg/connect_response.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package netmsg; 3 | 4 | message ConnectResponse { 5 | int32 ClientSlot = 1; 6 | double X = 2; 7 | double Y = 3; 8 | } 9 | -------------------------------------------------------------------------------- /netmsg/disconnect.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: disconnect.proto 3 | 4 | /* 5 | Package netmsg is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | disconnect.proto 9 | 10 | It has these top-level messages: 11 | DisconnectPlayer 12 | */ 13 | package netmsg 14 | 15 | import proto "github.com/golang/protobuf/proto" 16 | import fmt "fmt" 17 | import math "math" 18 | 19 | import io "io" 20 | 21 | // Reference imports to suppress errors if they are not otherwise used. 22 | var _ = proto.Marshal 23 | var _ = fmt.Errorf 24 | var _ = math.Inf 25 | 26 | // This is a compile-time assertion to ensure that this generated file 27 | // is compatible with the proto package it is being compiled against. 28 | // A compilation error at this line likely means your copy of the 29 | // proto package needs to be updated. 30 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 31 | 32 | type DisconnectPlayer struct { 33 | ClientSlot int32 `protobuf:"varint,1,opt,name=ClientSlot,proto3" json:"ClientSlot,omitempty"` 34 | } 35 | 36 | func (m *DisconnectPlayer) Reset() { *m = DisconnectPlayer{} } 37 | func (m *DisconnectPlayer) String() string { return proto.CompactTextString(m) } 38 | func (*DisconnectPlayer) ProtoMessage() {} 39 | func (*DisconnectPlayer) Descriptor() ([]byte, []int) { return fileDescriptorDisconnect, []int{0} } 40 | 41 | func (m *DisconnectPlayer) GetClientSlot() int32 { 42 | if m != nil { 43 | return m.ClientSlot 44 | } 45 | return 0 46 | } 47 | 48 | func init() { 49 | proto.RegisterType((*DisconnectPlayer)(nil), "netmsg.DisconnectPlayer") 50 | } 51 | func (m *DisconnectPlayer) Marshal() (dAtA []byte, err error) { 52 | size := m.Size() 53 | dAtA = make([]byte, size) 54 | n, err := m.MarshalTo(dAtA) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return dAtA[:n], nil 59 | } 60 | 61 | func (m *DisconnectPlayer) MarshalTo(dAtA []byte) (int, error) { 62 | var i int 63 | _ = i 64 | var l int 65 | _ = l 66 | if m.ClientSlot != 0 { 67 | dAtA[i] = 0x8 68 | i++ 69 | i = encodeVarintDisconnect(dAtA, i, uint64(m.ClientSlot)) 70 | } 71 | return i, nil 72 | } 73 | 74 | func encodeVarintDisconnect(dAtA []byte, offset int, v uint64) int { 75 | for v >= 1<<7 { 76 | dAtA[offset] = uint8(v&0x7f | 0x80) 77 | v >>= 7 78 | offset++ 79 | } 80 | dAtA[offset] = uint8(v) 81 | return offset + 1 82 | } 83 | func (m *DisconnectPlayer) Size() (n int) { 84 | var l int 85 | _ = l 86 | if m.ClientSlot != 0 { 87 | n += 1 + sovDisconnect(uint64(m.ClientSlot)) 88 | } 89 | return n 90 | } 91 | 92 | func sovDisconnect(x uint64) (n int) { 93 | for { 94 | n++ 95 | x >>= 7 96 | if x == 0 { 97 | break 98 | } 99 | } 100 | return n 101 | } 102 | func sozDisconnect(x uint64) (n int) { 103 | return sovDisconnect(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 104 | } 105 | func (m *DisconnectPlayer) Unmarshal(dAtA []byte) error { 106 | l := len(dAtA) 107 | iNdEx := 0 108 | for iNdEx < l { 109 | preIndex := iNdEx 110 | var wire uint64 111 | for shift := uint(0); ; shift += 7 { 112 | if shift >= 64 { 113 | return ErrIntOverflowDisconnect 114 | } 115 | if iNdEx >= l { 116 | return io.ErrUnexpectedEOF 117 | } 118 | b := dAtA[iNdEx] 119 | iNdEx++ 120 | wire |= (uint64(b) & 0x7F) << shift 121 | if b < 0x80 { 122 | break 123 | } 124 | } 125 | fieldNum := int32(wire >> 3) 126 | wireType := int(wire & 0x7) 127 | if wireType == 4 { 128 | return fmt.Errorf("proto: DisconnectPlayer: wiretype end group for non-group") 129 | } 130 | if fieldNum <= 0 { 131 | return fmt.Errorf("proto: DisconnectPlayer: illegal tag %d (wire type %d)", fieldNum, wire) 132 | } 133 | switch fieldNum { 134 | case 1: 135 | if wireType != 0 { 136 | return fmt.Errorf("proto: wrong wireType = %d for field ClientSlot", wireType) 137 | } 138 | m.ClientSlot = 0 139 | for shift := uint(0); ; shift += 7 { 140 | if shift >= 64 { 141 | return ErrIntOverflowDisconnect 142 | } 143 | if iNdEx >= l { 144 | return io.ErrUnexpectedEOF 145 | } 146 | b := dAtA[iNdEx] 147 | iNdEx++ 148 | m.ClientSlot |= (int32(b) & 0x7F) << shift 149 | if b < 0x80 { 150 | break 151 | } 152 | } 153 | default: 154 | iNdEx = preIndex 155 | skippy, err := skipDisconnect(dAtA[iNdEx:]) 156 | if err != nil { 157 | return err 158 | } 159 | if skippy < 0 { 160 | return ErrInvalidLengthDisconnect 161 | } 162 | if (iNdEx + skippy) > l { 163 | return io.ErrUnexpectedEOF 164 | } 165 | iNdEx += skippy 166 | } 167 | } 168 | 169 | if iNdEx > l { 170 | return io.ErrUnexpectedEOF 171 | } 172 | return nil 173 | } 174 | func skipDisconnect(dAtA []byte) (n int, err error) { 175 | l := len(dAtA) 176 | iNdEx := 0 177 | for iNdEx < l { 178 | var wire uint64 179 | for shift := uint(0); ; shift += 7 { 180 | if shift >= 64 { 181 | return 0, ErrIntOverflowDisconnect 182 | } 183 | if iNdEx >= l { 184 | return 0, io.ErrUnexpectedEOF 185 | } 186 | b := dAtA[iNdEx] 187 | iNdEx++ 188 | wire |= (uint64(b) & 0x7F) << shift 189 | if b < 0x80 { 190 | break 191 | } 192 | } 193 | wireType := int(wire & 0x7) 194 | switch wireType { 195 | case 0: 196 | for shift := uint(0); ; shift += 7 { 197 | if shift >= 64 { 198 | return 0, ErrIntOverflowDisconnect 199 | } 200 | if iNdEx >= l { 201 | return 0, io.ErrUnexpectedEOF 202 | } 203 | iNdEx++ 204 | if dAtA[iNdEx-1] < 0x80 { 205 | break 206 | } 207 | } 208 | return iNdEx, nil 209 | case 1: 210 | iNdEx += 8 211 | return iNdEx, nil 212 | case 2: 213 | var length int 214 | for shift := uint(0); ; shift += 7 { 215 | if shift >= 64 { 216 | return 0, ErrIntOverflowDisconnect 217 | } 218 | if iNdEx >= l { 219 | return 0, io.ErrUnexpectedEOF 220 | } 221 | b := dAtA[iNdEx] 222 | iNdEx++ 223 | length |= (int(b) & 0x7F) << shift 224 | if b < 0x80 { 225 | break 226 | } 227 | } 228 | iNdEx += length 229 | if length < 0 { 230 | return 0, ErrInvalidLengthDisconnect 231 | } 232 | return iNdEx, nil 233 | case 3: 234 | for { 235 | var innerWire uint64 236 | var start int = iNdEx 237 | for shift := uint(0); ; shift += 7 { 238 | if shift >= 64 { 239 | return 0, ErrIntOverflowDisconnect 240 | } 241 | if iNdEx >= l { 242 | return 0, io.ErrUnexpectedEOF 243 | } 244 | b := dAtA[iNdEx] 245 | iNdEx++ 246 | innerWire |= (uint64(b) & 0x7F) << shift 247 | if b < 0x80 { 248 | break 249 | } 250 | } 251 | innerWireType := int(innerWire & 0x7) 252 | if innerWireType == 4 { 253 | break 254 | } 255 | next, err := skipDisconnect(dAtA[start:]) 256 | if err != nil { 257 | return 0, err 258 | } 259 | iNdEx = start + next 260 | } 261 | return iNdEx, nil 262 | case 4: 263 | return iNdEx, nil 264 | case 5: 265 | iNdEx += 4 266 | return iNdEx, nil 267 | default: 268 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 269 | } 270 | } 271 | panic("unreachable") 272 | } 273 | 274 | var ( 275 | ErrInvalidLengthDisconnect = fmt.Errorf("proto: negative length found during unmarshaling") 276 | ErrIntOverflowDisconnect = fmt.Errorf("proto: integer overflow") 277 | ) 278 | 279 | func init() { proto.RegisterFile("disconnect.proto", fileDescriptorDisconnect) } 280 | 281 | var fileDescriptorDisconnect = []byte{ 282 | // 110 bytes of a gzipped FileDescriptorProto 283 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0xc9, 0x2c, 0x4e, 284 | 0xce, 0xcf, 0xcb, 0x4b, 0x4d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xcb, 0x4b, 285 | 0x2d, 0xc9, 0x2d, 0x4e, 0x57, 0x32, 0xe2, 0x12, 0x70, 0x81, 0xcb, 0x05, 0xe4, 0x24, 0x56, 0xa6, 286 | 0x16, 0x09, 0xc9, 0x71, 0x71, 0x39, 0xe7, 0x64, 0xa6, 0xe6, 0x95, 0x04, 0xe7, 0xe4, 0x97, 0x48, 287 | 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x06, 0x21, 0x89, 0x38, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 288 | 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x33, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x0d, 0x35, 289 | 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xac, 0xa1, 0xcd, 0x84, 0x68, 0x00, 0x00, 0x00, 290 | } 291 | -------------------------------------------------------------------------------- /netmsg/disconnect.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package netmsg; 3 | 4 | message DisconnectPlayer { 5 | int32 ClientSlot = 1; 6 | } 7 | -------------------------------------------------------------------------------- /netmsg/kind.go: -------------------------------------------------------------------------------- 1 | package netmsg 2 | 3 | type Kind byte 4 | 5 | const ( 6 | MsgUnknown Kind = 0 + iota 7 | MsgConnectResponse = 1 8 | MsgUpdatePlayer = 2 9 | MsgDisconnectPlayer = 3 10 | ) 11 | 12 | var kindToString = []string{ 13 | MsgUnknown: "MsgUnknown", 14 | MsgConnectResponse: "MsgConnectResponse", 15 | MsgUpdatePlayer: "MsgUpdatePlayer", 16 | MsgDisconnectPlayer: "MsgDisconnectPlayer", 17 | } 18 | 19 | func (kind Kind) String() string { 20 | kindAsInt := int(kind) 21 | if kindAsInt >= 0 && kindAsInt < len(kindToString) { 22 | return kindToString[kind] 23 | } 24 | return "MsgUnknown" 25 | } 26 | -------------------------------------------------------------------------------- /netmsg/make.sh: -------------------------------------------------------------------------------- 1 | # Build the net messages with protocol buffers 2 | 3 | protoc --gofast_out=. connect_response.proto 4 | protoc --gofast_out=. update_player.proto 5 | protoc --gofast_out=. disconnect.proto 6 | -------------------------------------------------------------------------------- /netmsg/update_player.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: update_player.proto 3 | 4 | /* 5 | Package netmsg is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | update_player.proto 9 | 10 | It has these top-level messages: 11 | UpdatePlayer 12 | */ 13 | package netmsg 14 | 15 | import proto "github.com/golang/protobuf/proto" 16 | import fmt "fmt" 17 | import math "math" 18 | 19 | import binary "encoding/binary" 20 | 21 | import io "io" 22 | 23 | // Reference imports to suppress errors if they are not otherwise used. 24 | var _ = proto.Marshal 25 | var _ = fmt.Errorf 26 | var _ = math.Inf 27 | 28 | // This is a compile-time assertion to ensure that this generated file 29 | // is compatible with the proto package it is being compiled against. 30 | // A compilation error at this line likely means your copy of the 31 | // proto package needs to be updated. 32 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 33 | 34 | type UpdatePlayer struct { 35 | ClientSlot int32 `protobuf:"varint,1,opt,name=ClientSlot,proto3" json:"ClientSlot,omitempty"` 36 | X float64 `protobuf:"fixed64,2,opt,name=X,proto3" json:"X,omitempty"` 37 | Y float64 `protobuf:"fixed64,3,opt,name=Y,proto3" json:"Y,omitempty"` 38 | IsKeyLeftPressed bool `protobuf:"varint,4,opt,name=IsKeyLeftPressed,proto3" json:"IsKeyLeftPressed,omitempty"` 39 | IsKeyRightPressed bool `protobuf:"varint,5,opt,name=IsKeyRightPressed,proto3" json:"IsKeyRightPressed,omitempty"` 40 | } 41 | 42 | func (m *UpdatePlayer) Reset() { *m = UpdatePlayer{} } 43 | func (m *UpdatePlayer) String() string { return proto.CompactTextString(m) } 44 | func (*UpdatePlayer) ProtoMessage() {} 45 | func (*UpdatePlayer) Descriptor() ([]byte, []int) { return fileDescriptorUpdatePlayer, []int{0} } 46 | 47 | func (m *UpdatePlayer) GetClientSlot() int32 { 48 | if m != nil { 49 | return m.ClientSlot 50 | } 51 | return 0 52 | } 53 | 54 | func (m *UpdatePlayer) GetX() float64 { 55 | if m != nil { 56 | return m.X 57 | } 58 | return 0 59 | } 60 | 61 | func (m *UpdatePlayer) GetY() float64 { 62 | if m != nil { 63 | return m.Y 64 | } 65 | return 0 66 | } 67 | 68 | func (m *UpdatePlayer) GetIsKeyLeftPressed() bool { 69 | if m != nil { 70 | return m.IsKeyLeftPressed 71 | } 72 | return false 73 | } 74 | 75 | func (m *UpdatePlayer) GetIsKeyRightPressed() bool { 76 | if m != nil { 77 | return m.IsKeyRightPressed 78 | } 79 | return false 80 | } 81 | 82 | func init() { 83 | proto.RegisterType((*UpdatePlayer)(nil), "netmsg.UpdatePlayer") 84 | } 85 | func (m *UpdatePlayer) Marshal() (dAtA []byte, err error) { 86 | size := m.Size() 87 | dAtA = make([]byte, size) 88 | n, err := m.MarshalTo(dAtA) 89 | if err != nil { 90 | return nil, err 91 | } 92 | return dAtA[:n], nil 93 | } 94 | 95 | func (m *UpdatePlayer) MarshalTo(dAtA []byte) (int, error) { 96 | var i int 97 | _ = i 98 | var l int 99 | _ = l 100 | if m.ClientSlot != 0 { 101 | dAtA[i] = 0x8 102 | i++ 103 | i = encodeVarintUpdatePlayer(dAtA, i, uint64(m.ClientSlot)) 104 | } 105 | if m.X != 0 { 106 | dAtA[i] = 0x11 107 | i++ 108 | binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.X)))) 109 | i += 8 110 | } 111 | if m.Y != 0 { 112 | dAtA[i] = 0x19 113 | i++ 114 | binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Y)))) 115 | i += 8 116 | } 117 | if m.IsKeyLeftPressed { 118 | dAtA[i] = 0x20 119 | i++ 120 | if m.IsKeyLeftPressed { 121 | dAtA[i] = 1 122 | } else { 123 | dAtA[i] = 0 124 | } 125 | i++ 126 | } 127 | if m.IsKeyRightPressed { 128 | dAtA[i] = 0x28 129 | i++ 130 | if m.IsKeyRightPressed { 131 | dAtA[i] = 1 132 | } else { 133 | dAtA[i] = 0 134 | } 135 | i++ 136 | } 137 | return i, nil 138 | } 139 | 140 | func encodeVarintUpdatePlayer(dAtA []byte, offset int, v uint64) int { 141 | for v >= 1<<7 { 142 | dAtA[offset] = uint8(v&0x7f | 0x80) 143 | v >>= 7 144 | offset++ 145 | } 146 | dAtA[offset] = uint8(v) 147 | return offset + 1 148 | } 149 | func (m *UpdatePlayer) Size() (n int) { 150 | var l int 151 | _ = l 152 | if m.ClientSlot != 0 { 153 | n += 1 + sovUpdatePlayer(uint64(m.ClientSlot)) 154 | } 155 | if m.X != 0 { 156 | n += 9 157 | } 158 | if m.Y != 0 { 159 | n += 9 160 | } 161 | if m.IsKeyLeftPressed { 162 | n += 2 163 | } 164 | if m.IsKeyRightPressed { 165 | n += 2 166 | } 167 | return n 168 | } 169 | 170 | func sovUpdatePlayer(x uint64) (n int) { 171 | for { 172 | n++ 173 | x >>= 7 174 | if x == 0 { 175 | break 176 | } 177 | } 178 | return n 179 | } 180 | func sozUpdatePlayer(x uint64) (n int) { 181 | return sovUpdatePlayer(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 182 | } 183 | func (m *UpdatePlayer) Unmarshal(dAtA []byte) error { 184 | l := len(dAtA) 185 | iNdEx := 0 186 | for iNdEx < l { 187 | preIndex := iNdEx 188 | var wire uint64 189 | for shift := uint(0); ; shift += 7 { 190 | if shift >= 64 { 191 | return ErrIntOverflowUpdatePlayer 192 | } 193 | if iNdEx >= l { 194 | return io.ErrUnexpectedEOF 195 | } 196 | b := dAtA[iNdEx] 197 | iNdEx++ 198 | wire |= (uint64(b) & 0x7F) << shift 199 | if b < 0x80 { 200 | break 201 | } 202 | } 203 | fieldNum := int32(wire >> 3) 204 | wireType := int(wire & 0x7) 205 | if wireType == 4 { 206 | return fmt.Errorf("proto: UpdatePlayer: wiretype end group for non-group") 207 | } 208 | if fieldNum <= 0 { 209 | return fmt.Errorf("proto: UpdatePlayer: illegal tag %d (wire type %d)", fieldNum, wire) 210 | } 211 | switch fieldNum { 212 | case 1: 213 | if wireType != 0 { 214 | return fmt.Errorf("proto: wrong wireType = %d for field ClientSlot", wireType) 215 | } 216 | m.ClientSlot = 0 217 | for shift := uint(0); ; shift += 7 { 218 | if shift >= 64 { 219 | return ErrIntOverflowUpdatePlayer 220 | } 221 | if iNdEx >= l { 222 | return io.ErrUnexpectedEOF 223 | } 224 | b := dAtA[iNdEx] 225 | iNdEx++ 226 | m.ClientSlot |= (int32(b) & 0x7F) << shift 227 | if b < 0x80 { 228 | break 229 | } 230 | } 231 | case 2: 232 | if wireType != 1 { 233 | return fmt.Errorf("proto: wrong wireType = %d for field X", wireType) 234 | } 235 | var v uint64 236 | if (iNdEx + 8) > l { 237 | return io.ErrUnexpectedEOF 238 | } 239 | v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) 240 | iNdEx += 8 241 | m.X = float64(math.Float64frombits(v)) 242 | case 3: 243 | if wireType != 1 { 244 | return fmt.Errorf("proto: wrong wireType = %d for field Y", wireType) 245 | } 246 | var v uint64 247 | if (iNdEx + 8) > l { 248 | return io.ErrUnexpectedEOF 249 | } 250 | v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) 251 | iNdEx += 8 252 | m.Y = float64(math.Float64frombits(v)) 253 | case 4: 254 | if wireType != 0 { 255 | return fmt.Errorf("proto: wrong wireType = %d for field IsKeyLeftPressed", wireType) 256 | } 257 | var v int 258 | for shift := uint(0); ; shift += 7 { 259 | if shift >= 64 { 260 | return ErrIntOverflowUpdatePlayer 261 | } 262 | if iNdEx >= l { 263 | return io.ErrUnexpectedEOF 264 | } 265 | b := dAtA[iNdEx] 266 | iNdEx++ 267 | v |= (int(b) & 0x7F) << shift 268 | if b < 0x80 { 269 | break 270 | } 271 | } 272 | m.IsKeyLeftPressed = bool(v != 0) 273 | case 5: 274 | if wireType != 0 { 275 | return fmt.Errorf("proto: wrong wireType = %d for field IsKeyRightPressed", wireType) 276 | } 277 | var v int 278 | for shift := uint(0); ; shift += 7 { 279 | if shift >= 64 { 280 | return ErrIntOverflowUpdatePlayer 281 | } 282 | if iNdEx >= l { 283 | return io.ErrUnexpectedEOF 284 | } 285 | b := dAtA[iNdEx] 286 | iNdEx++ 287 | v |= (int(b) & 0x7F) << shift 288 | if b < 0x80 { 289 | break 290 | } 291 | } 292 | m.IsKeyRightPressed = bool(v != 0) 293 | default: 294 | iNdEx = preIndex 295 | skippy, err := skipUpdatePlayer(dAtA[iNdEx:]) 296 | if err != nil { 297 | return err 298 | } 299 | if skippy < 0 { 300 | return ErrInvalidLengthUpdatePlayer 301 | } 302 | if (iNdEx + skippy) > l { 303 | return io.ErrUnexpectedEOF 304 | } 305 | iNdEx += skippy 306 | } 307 | } 308 | 309 | if iNdEx > l { 310 | return io.ErrUnexpectedEOF 311 | } 312 | return nil 313 | } 314 | func skipUpdatePlayer(dAtA []byte) (n int, err error) { 315 | l := len(dAtA) 316 | iNdEx := 0 317 | for iNdEx < l { 318 | var wire uint64 319 | for shift := uint(0); ; shift += 7 { 320 | if shift >= 64 { 321 | return 0, ErrIntOverflowUpdatePlayer 322 | } 323 | if iNdEx >= l { 324 | return 0, io.ErrUnexpectedEOF 325 | } 326 | b := dAtA[iNdEx] 327 | iNdEx++ 328 | wire |= (uint64(b) & 0x7F) << shift 329 | if b < 0x80 { 330 | break 331 | } 332 | } 333 | wireType := int(wire & 0x7) 334 | switch wireType { 335 | case 0: 336 | for shift := uint(0); ; shift += 7 { 337 | if shift >= 64 { 338 | return 0, ErrIntOverflowUpdatePlayer 339 | } 340 | if iNdEx >= l { 341 | return 0, io.ErrUnexpectedEOF 342 | } 343 | iNdEx++ 344 | if dAtA[iNdEx-1] < 0x80 { 345 | break 346 | } 347 | } 348 | return iNdEx, nil 349 | case 1: 350 | iNdEx += 8 351 | return iNdEx, nil 352 | case 2: 353 | var length int 354 | for shift := uint(0); ; shift += 7 { 355 | if shift >= 64 { 356 | return 0, ErrIntOverflowUpdatePlayer 357 | } 358 | if iNdEx >= l { 359 | return 0, io.ErrUnexpectedEOF 360 | } 361 | b := dAtA[iNdEx] 362 | iNdEx++ 363 | length |= (int(b) & 0x7F) << shift 364 | if b < 0x80 { 365 | break 366 | } 367 | } 368 | iNdEx += length 369 | if length < 0 { 370 | return 0, ErrInvalidLengthUpdatePlayer 371 | } 372 | return iNdEx, nil 373 | case 3: 374 | for { 375 | var innerWire uint64 376 | var start int = iNdEx 377 | for shift := uint(0); ; shift += 7 { 378 | if shift >= 64 { 379 | return 0, ErrIntOverflowUpdatePlayer 380 | } 381 | if iNdEx >= l { 382 | return 0, io.ErrUnexpectedEOF 383 | } 384 | b := dAtA[iNdEx] 385 | iNdEx++ 386 | innerWire |= (uint64(b) & 0x7F) << shift 387 | if b < 0x80 { 388 | break 389 | } 390 | } 391 | innerWireType := int(innerWire & 0x7) 392 | if innerWireType == 4 { 393 | break 394 | } 395 | next, err := skipUpdatePlayer(dAtA[start:]) 396 | if err != nil { 397 | return 0, err 398 | } 399 | iNdEx = start + next 400 | } 401 | return iNdEx, nil 402 | case 4: 403 | return iNdEx, nil 404 | case 5: 405 | iNdEx += 4 406 | return iNdEx, nil 407 | default: 408 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 409 | } 410 | } 411 | panic("unreachable") 412 | } 413 | 414 | var ( 415 | ErrInvalidLengthUpdatePlayer = fmt.Errorf("proto: negative length found during unmarshaling") 416 | ErrIntOverflowUpdatePlayer = fmt.Errorf("proto: integer overflow") 417 | ) 418 | 419 | func init() { proto.RegisterFile("update_player.proto", fileDescriptorUpdatePlayer) } 420 | 421 | var fileDescriptorUpdatePlayer = []byte{ 422 | // 179 bytes of a gzipped FileDescriptorProto 423 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2e, 0x2d, 0x48, 0x49, 424 | 0x2c, 0x49, 0x8d, 0x2f, 0xc8, 0x49, 0xac, 0x4c, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 425 | 0x62, 0xcb, 0x4b, 0x2d, 0xc9, 0x2d, 0x4e, 0x57, 0x5a, 0xc2, 0xc8, 0xc5, 0x13, 0x0a, 0x96, 0x0f, 426 | 0x00, 0x4b, 0x0b, 0xc9, 0x71, 0x71, 0x39, 0xe7, 0x64, 0xa6, 0xe6, 0x95, 0x04, 0xe7, 0xe4, 0x97, 427 | 0x48, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x06, 0x21, 0x89, 0x08, 0xf1, 0x70, 0x31, 0x46, 0x48, 0x30, 428 | 0x29, 0x30, 0x6a, 0x30, 0x06, 0x31, 0x46, 0x80, 0x78, 0x91, 0x12, 0xcc, 0x10, 0x5e, 0xa4, 0x90, 429 | 0x16, 0x97, 0x80, 0x67, 0xb1, 0x77, 0x6a, 0xa5, 0x4f, 0x6a, 0x5a, 0x49, 0x40, 0x51, 0x6a, 0x71, 430 | 0x71, 0x6a, 0x8a, 0x04, 0x8b, 0x02, 0xa3, 0x06, 0x47, 0x10, 0x86, 0xb8, 0x90, 0x0e, 0x97, 0x20, 431 | 0x58, 0x2c, 0x28, 0x33, 0x3d, 0x03, 0xae, 0x98, 0x15, 0xac, 0x18, 0x53, 0xc2, 0x49, 0xe0, 0xc4, 432 | 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf1, 0x58, 0x8e, 0x21, 433 | 0x89, 0x0d, 0xec, 0x0f, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xba, 0x88, 0x27, 0xa6, 0xde, 434 | 0x00, 0x00, 0x00, 435 | } 436 | -------------------------------------------------------------------------------- /netmsg/update_player.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package netmsg; 3 | 4 | message UpdatePlayer { 5 | int32 ClientSlot = 1; 6 | double X = 2; 7 | double Y = 3; 8 | bool IsKeyLeftPressed = 4; 9 | bool IsKeyRightPressed = 5; 10 | } 11 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/gogo/protobuf/proto" 9 | "github.com/silbinarywolf/networkplatformer-go/gameserver" 10 | "github.com/silbinarywolf/networkplatformer-go/netmsg" 11 | ) 12 | 13 | var ( 14 | server *Server 15 | ) 16 | 17 | type Server struct { 18 | *gameserver.Server 19 | } 20 | 21 | func NewServer() *Server { 22 | server := &Server{ 23 | Server: gameserver.NewServer("/abgame"), 24 | } 25 | return server 26 | } 27 | 28 | func (s *Server) Update() { 29 | RecvMsgLoop: 30 | for { 31 | select { 32 | case client := <-s.ChRegister(): 33 | clientSlot := int32(client.ClientSlot()) 34 | 35 | // Create player instance 36 | char := &Char{ 37 | X: float64(rand.Int63n(90) + 130), 38 | Y: float64(380), 39 | } 40 | 41 | // Create client 42 | s.RegisterClient(client, char) 43 | 44 | // Add client to simulation 45 | chars = append(chars, char) 46 | 47 | // Send connection response 48 | { 49 | sendMsg := netmsg.ConnectResponse{ 50 | ClientSlot: clientSlot, 51 | X: char.X, 52 | Y: char.Y, 53 | } 54 | data, err := proto.Marshal(&sendMsg) 55 | if err != nil { 56 | log.Fatal("client connect: marshaling error: ", err) 57 | } 58 | 59 | // Send to connecting player their information 60 | packetData := make([]byte, 1, len(data)+1) 61 | packetData[0] = netmsg.MsgConnectResponse 62 | packetData = append(packetData, data...) 63 | client.SendMessage(packetData) 64 | } 65 | case client := <-s.ChUnregister(): 66 | if s.RemoveClient(client) { 67 | char := client.Data().(*Char) 68 | char.RemoveFromSimulation() 69 | 70 | log.Printf("client #%d disconnected", client.ClientSlot()) 71 | 72 | // Tell clients player disconnected 73 | sendMsg := netmsg.DisconnectPlayer{ 74 | ClientSlot: int32(client.ClientSlot()), 75 | } 76 | data, err := proto.Marshal(&sendMsg) 77 | if err != nil { 78 | log.Fatal("client connect: marshaling error: ", err) 79 | } 80 | 81 | // Send disconnected player 82 | packetData := make([]byte, 1, len(data)+1) 83 | packetData[0] = netmsg.MsgDisconnectPlayer 84 | packetData = append(packetData, data...) 85 | for otherClient := range s.GetClients() { 86 | if otherClient == client { 87 | continue 88 | } 89 | otherClient.SendMessage(packetData) 90 | } 91 | } 92 | case message := <-s.ChBroadcast(): 93 | var ( 94 | client = message.Client() 95 | buf = message.Data() 96 | ) 97 | kind := netmsg.Kind(buf[0]) 98 | buf = buf[1:] 99 | switch kind { 100 | case netmsg.MsgUpdatePlayer: 101 | // Receive update 102 | recvMsg := &netmsg.UpdatePlayer{} 103 | err := recvMsg.Unmarshal(buf) 104 | if err != nil { 105 | log.Fatal("unmarshaling error: ", err) 106 | break 107 | } 108 | char := client.Data().(*Char) 109 | char.X = recvMsg.X 110 | char.Y = recvMsg.Y 111 | char.isKeyLeftPressed = recvMsg.IsKeyLeftPressed 112 | char.isKeyRightPressed = recvMsg.IsKeyRightPressed 113 | default: 114 | log.Printf("Unhandled netmsg kind: %s, with data: %v\n", kind.String(), buf) 115 | } 116 | default: 117 | // no-op 118 | break RecvMsgLoop 119 | } 120 | } 121 | 122 | // Send updates to clients 123 | for client := range s.GetClients() { 124 | char := client.Data().(*Char) 125 | elapsed := time.Since(char.lastUpdatedTimer) 126 | if elapsed > 15*time.Millisecond { 127 | // Reset countdown till next update 128 | char.lastUpdatedTimer = time.Now() 129 | 130 | // Send update to other players 131 | sendMsg := netmsg.UpdatePlayer{ 132 | ClientSlot: int32(client.ClientSlot()), 133 | X: char.X, 134 | Y: char.Y, 135 | IsKeyLeftPressed: char.isKeyLeftPressed, 136 | IsKeyRightPressed: char.isKeyRightPressed, 137 | } 138 | data, err := proto.Marshal(&sendMsg) 139 | if err != nil { 140 | log.Fatal("client update: marshaling error: ", err) 141 | } 142 | packetData := make([]byte, 1, len(data)+1) 143 | packetData[0] = netmsg.MsgUpdatePlayer 144 | packetData = append(packetData, data...) 145 | for otherClient := range s.GetClients() { 146 | if otherClient == client { 147 | continue 148 | } 149 | otherClient.SendMessage(packetData) 150 | } 151 | } 152 | } 153 | } 154 | --------------------------------------------------------------------------------