├── .circleci └── config.yml ├── .gitattributes ├── .gitignore ├── Dockerfile ├── Golang Game ServerArchitecture.png ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── README.md ├── agent ├── ClientToAgent.go └── session │ ├── kubermanager.go │ ├── room.go │ └── session.go ├── cluster ├── accountbind.yaml ├── agent.yaml ├── deploy.yaml └── pod.yaml ├── data.proto ├── demo.gif ├── game ├── agentToGame.go ├── clientToGame.go ├── data │ ├── PhysicObj.json │ └── data.go ├── entity │ ├── attack.go │ ├── enemy.go │ ├── entity.go │ ├── entity_test.go │ ├── gameManager.go │ ├── player.go │ ├── room.go │ ├── shell.go │ └── treasure.go ├── hmap │ └── hmap.go ├── physic │ ├── physic.go │ └── physic_test.go └── session │ ├── room.go │ └── session.go ├── gameServer.code-workspace ├── gdb_sandbox ├── main ├── main.go ├── msg ├── Message.cs ├── MessageGrpc.cs ├── any.proto ├── github.com │ └── golang │ │ └── protobuf │ │ └── ptypes │ │ └── any │ │ └── any.pb.go ├── message.pb.go ├── message.proto ├── message.zip └── update.sh ├── rpctest ├── client │ └── main.go ├── invokeTest │ └── main.go └── server │ └── main.go ├── service ├── AgentToGame.go ├── ClientToAgent.go └── ClientToGame.go ├── setupEnv.sh ├── storage ├── storage.go └── storage_test.go ├── timeCalibrate └── timeCalibration.go ├── user └── user.go ├── util └── util.go └── uuid └── uuid.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/golang:1.9 6 | working_directory: /go/src/github.com/daniel840829/gameServer 7 | steps: 8 | - checkout 9 | - run: 10 | name: dep install 11 | command: go get github.com/golang/dep/cmd/dep && dep ensure -v 12 | - run: 13 | name: build 14 | command: go build main.go 15 | - setup_remote_docker: 16 | docker_layer_caching: true 17 | - run: 18 | name: Build and Push Image daniel840829/gameplayserver 19 | command: | 20 | TAG=circleci-latest 21 | docker build -t daniel840829/gameplayserver:$TAG . 22 | docker login -u $DOCKER_USER -p $DOCKER_PASS 23 | docker push daniel840829/gameplayserver:$TAG -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | msg/* linguist-vendored -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | *.log 3 | *.vim 4 | .undodir -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest as builder 2 | MAINTAINER daniel840829 "s102033114@gapp.nthu.edu.tw" 3 | COPY . $GOPATH/src/github.com/daniel840829/gameServer 4 | WORKDIR $GOPATH/src/github.com/daniel840829/gameServer 5 | RUN set -x && go get github.com/golang/dep/cmd/dep && dep ensure -v 6 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /gameServer 7 | CMD ["/gameServer"] 8 | EXPOSE 3000 8080 50051 9 | 10 | FROM alpine 11 | COPY --from=builder /gameServer . 12 | COPY ./cluster ./cluster 13 | EXPOSE 3000 8080 50051 14 | # ENTRYPOINT [ "/bin/bash" ] 15 | CMD ["./gameServer"] 16 | 17 | 18 | -------------------------------------------------------------------------------- /Golang Game ServerArchitecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danie1Lin/Distributed-Golang-Game-Server/620f6c91b6e21db9f8eca88916b1130223ff8b81/Golang Game ServerArchitecture.png -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | digest = "1:c7b7c951907bb9f79d231d62878b06e98ce34ba3e90ae91c986315b0137baff7" 7 | name = "github.com/daniel840829/ode" 8 | packages = ["."] 9 | pruneopts = "UT" 10 | revision = "e0dee66213cb59e9957f6a6f6f16d19ee7b241ad" 11 | 12 | [[projects]] 13 | digest = "1:79a12e216d50c72810973304a485eb77c6e43d1493b5c506778ae61908dd99d1" 14 | name = "github.com/gazed/vu" 15 | packages = ["math/lin"] 16 | pruneopts = "UT" 17 | revision = "a2dfbe3ce829aabbb139ff4f8002bbdb78097058" 18 | version = "v0.10.0" 19 | 20 | [[projects]] 21 | digest = "1:2cd7915ab26ede7d95b8749e6b1f933f1c6d5398030684e6505940a10f31cfda" 22 | name = "github.com/ghodss/yaml" 23 | packages = ["."] 24 | pruneopts = "UT" 25 | revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7" 26 | version = "v1.0.0" 27 | 28 | [[projects]] 29 | branch = "master" 30 | digest = "1:e0361634f0b5e1fd3227eaec90a86bc8595cb2526e71e41bcd72b6e474e198ca" 31 | name = "github.com/globalsign/mgo" 32 | packages = [ 33 | ".", 34 | "bson", 35 | "internal/json", 36 | "internal/sasl", 37 | "internal/scram", 38 | ] 39 | pruneopts = "UT" 40 | revision = "1ca0a4f7cbcbe61c005d1bd43fdd8bb8b71df6bc" 41 | 42 | [[projects]] 43 | digest = "1:34e709f36fd4f868fb00dbaf8a6cab4c1ae685832d392874ba9d7c5dec2429d1" 44 | name = "github.com/gogo/protobuf" 45 | packages = [ 46 | "proto", 47 | "sortkeys", 48 | ] 49 | pruneopts = "UT" 50 | revision = "636bf0302bc95575d69441b25a2603156ffdddf1" 51 | version = "v1.1.1" 52 | 53 | [[projects]] 54 | branch = "master" 55 | digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467" 56 | name = "github.com/golang/glog" 57 | packages = ["."] 58 | pruneopts = "UT" 59 | revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" 60 | 61 | [[projects]] 62 | digest = "1:4c0989ca0bcd10799064318923b9bc2db6b4d6338dd75f3f2d86c3511aaaf5cf" 63 | name = "github.com/golang/protobuf" 64 | packages = [ 65 | "proto", 66 | "ptypes", 67 | "ptypes/any", 68 | "ptypes/duration", 69 | "ptypes/timestamp", 70 | ] 71 | pruneopts = "UT" 72 | revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" 73 | version = "v1.2.0" 74 | 75 | [[projects]] 76 | branch = "master" 77 | digest = "1:3ee90c0d94da31b442dde97c99635aaafec68d0b8a3c12ee2075c6bdabeec6bb" 78 | name = "github.com/google/gofuzz" 79 | packages = ["."] 80 | pruneopts = "UT" 81 | revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1" 82 | 83 | [[projects]] 84 | digest = "1:65c4414eeb350c47b8de71110150d0ea8a281835b1f386eacaa3ad7325929c21" 85 | name = "github.com/googleapis/gnostic" 86 | packages = [ 87 | "OpenAPIv2", 88 | "compiler", 89 | "extensions", 90 | ] 91 | pruneopts = "UT" 92 | revision = "7c663266750e7d82587642f65e60bc4083f1f84e" 93 | version = "v0.2.0" 94 | 95 | [[projects]] 96 | branch = "master" 97 | digest = "1:0778dc7fce1b4669a8bfa7ae506ec1f595b6ab0f8989c1c0d22a8ca1144e9972" 98 | name = "github.com/howeyc/gopass" 99 | packages = ["."] 100 | pruneopts = "UT" 101 | revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8" 102 | 103 | [[projects]] 104 | digest = "1:8eb1de8112c9924d59bf1d3e5c26f5eaa2bfc2a5fcbb92dc1c2e4546d695f277" 105 | name = "github.com/imdario/mergo" 106 | packages = ["."] 107 | pruneopts = "UT" 108 | revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4" 109 | version = "v0.3.6" 110 | 111 | [[projects]] 112 | digest = "1:3e551bbb3a7c0ab2a2bf4660e7fcad16db089fdcfbb44b0199e62838038623ea" 113 | name = "github.com/json-iterator/go" 114 | packages = ["."] 115 | pruneopts = "UT" 116 | revision = "1624edc4454b8682399def8740d46db5e4362ba4" 117 | version = "v1.1.5" 118 | 119 | [[projects]] 120 | digest = "1:33422d238f147d247752996a26574ac48dcf472976eda7f5134015f06bf16563" 121 | name = "github.com/modern-go/concurrent" 122 | packages = ["."] 123 | pruneopts = "UT" 124 | revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" 125 | version = "1.0.3" 126 | 127 | [[projects]] 128 | digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855" 129 | name = "github.com/modern-go/reflect2" 130 | packages = ["."] 131 | pruneopts = "UT" 132 | revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" 133 | version = "1.0.1" 134 | 135 | [[projects]] 136 | digest = "1:d867dfa6751c8d7a435821ad3b736310c2ed68945d05b50fb9d23aee0540c8cc" 137 | name = "github.com/sirupsen/logrus" 138 | packages = ["."] 139 | pruneopts = "UT" 140 | revision = "3e01752db0189b9157070a0e1668a620f9a85da2" 141 | version = "v1.0.6" 142 | 143 | [[projects]] 144 | digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2" 145 | name = "github.com/spf13/pflag" 146 | packages = ["."] 147 | pruneopts = "UT" 148 | revision = "298182f68c66c05229eb03ac171abe6e309ee79a" 149 | version = "v1.0.3" 150 | 151 | [[projects]] 152 | branch = "master" 153 | digest = "1:f8710e1ecd1653103b5e9d03e8e20c0779d3463d2af48e7c324863e06a630780" 154 | name = "github.com/zheng-ji/goSnowFlake" 155 | packages = ["."] 156 | pruneopts = "UT" 157 | revision = "fc763800eec9e4efe1dc7ca1cd9cb8831bd13227" 158 | 159 | [[projects]] 160 | branch = "master" 161 | digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8" 162 | name = "golang.org/x/crypto" 163 | packages = ["ssh/terminal"] 164 | pruneopts = "UT" 165 | revision = "0e37d006457bf46f9e6692014ba72ef82c33022c" 166 | 167 | [[projects]] 168 | branch = "master" 169 | digest = "1:deafe4ab271911fec7de5b693d7faae3f38796d9eb8622e2b9e7df42bb3dfea9" 170 | name = "golang.org/x/net" 171 | packages = [ 172 | "context", 173 | "http/httpguts", 174 | "http2", 175 | "http2/hpack", 176 | "idna", 177 | "internal/timeseries", 178 | "trace", 179 | ] 180 | pruneopts = "UT" 181 | revision = "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2" 182 | 183 | [[projects]] 184 | branch = "master" 185 | digest = "1:374fc90fcb026e9a367e3fad29e988e5dd944b68ca3f24a184d77abc5307dda4" 186 | name = "golang.org/x/sys" 187 | packages = [ 188 | "unix", 189 | "windows", 190 | ] 191 | pruneopts = "UT" 192 | revision = "d0be0721c37eeb5299f245a996a483160fc36940" 193 | 194 | [[projects]] 195 | digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" 196 | name = "golang.org/x/text" 197 | packages = [ 198 | "collate", 199 | "collate/build", 200 | "internal/colltab", 201 | "internal/gen", 202 | "internal/tag", 203 | "internal/triegen", 204 | "internal/ucd", 205 | "language", 206 | "secure/bidirule", 207 | "transform", 208 | "unicode/bidi", 209 | "unicode/cldr", 210 | "unicode/norm", 211 | "unicode/rangetable", 212 | ] 213 | pruneopts = "UT" 214 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" 215 | version = "v0.3.0" 216 | 217 | [[projects]] 218 | branch = "master" 219 | digest = "1:c9e7a4b4d47c0ed205d257648b0e5b0440880cb728506e318f8ac7cd36270bc4" 220 | name = "golang.org/x/time" 221 | packages = ["rate"] 222 | pruneopts = "UT" 223 | revision = "fbb02b2291d28baffd63558aa44b4b56f178d650" 224 | 225 | [[projects]] 226 | branch = "master" 227 | digest = "1:9cc1d3de11a1b6994eb01fe3cf81db064470d0fd39a37475f53a396aa5e8622c" 228 | name = "google.golang.org/genproto" 229 | packages = ["googleapis/rpc/status"] 230 | pruneopts = "UT" 231 | revision = "5a2fd4cab2d6d4a18e70c34937662526cd0c4bd1" 232 | 233 | [[projects]] 234 | digest = "1:ab8e92d746fb5c4c18846b0879842ac8e53b3d352449423d0924a11f1020ae1b" 235 | name = "google.golang.org/grpc" 236 | packages = [ 237 | ".", 238 | "balancer", 239 | "balancer/base", 240 | "balancer/roundrobin", 241 | "codes", 242 | "connectivity", 243 | "credentials", 244 | "encoding", 245 | "encoding/proto", 246 | "grpclog", 247 | "internal", 248 | "internal/backoff", 249 | "internal/channelz", 250 | "internal/envconfig", 251 | "internal/grpcrand", 252 | "internal/transport", 253 | "keepalive", 254 | "metadata", 255 | "naming", 256 | "peer", 257 | "resolver", 258 | "resolver/dns", 259 | "resolver/passthrough", 260 | "stats", 261 | "status", 262 | "tap", 263 | ] 264 | pruneopts = "UT" 265 | revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1" 266 | version = "v1.15.0" 267 | 268 | [[projects]] 269 | digest = "1:2d1fbdc6777e5408cabeb02bf336305e724b925ff4546ded0fa8715a7267922a" 270 | name = "gopkg.in/inf.v0" 271 | packages = ["."] 272 | pruneopts = "UT" 273 | revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf" 274 | version = "v0.9.1" 275 | 276 | [[projects]] 277 | digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202" 278 | name = "gopkg.in/yaml.v2" 279 | packages = ["."] 280 | pruneopts = "UT" 281 | revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" 282 | version = "v2.2.1" 283 | 284 | [[projects]] 285 | digest = "1:9e4dd60b5b4fda39e18da7879b26f8f44f47fa7fbc4f465eaeafd929bce54803" 286 | name = "k8s.io/api" 287 | packages = [ 288 | "admissionregistration/v1alpha1", 289 | "admissionregistration/v1beta1", 290 | "apps/v1", 291 | "apps/v1beta1", 292 | "apps/v1beta2", 293 | "authentication/v1", 294 | "authentication/v1beta1", 295 | "authorization/v1", 296 | "authorization/v1beta1", 297 | "autoscaling/v1", 298 | "autoscaling/v2beta1", 299 | "batch/v1", 300 | "batch/v1beta1", 301 | "batch/v2alpha1", 302 | "certificates/v1beta1", 303 | "core/v1", 304 | "events/v1beta1", 305 | "extensions/v1beta1", 306 | "networking/v1", 307 | "policy/v1beta1", 308 | "rbac/v1", 309 | "rbac/v1alpha1", 310 | "rbac/v1beta1", 311 | "scheduling/v1alpha1", 312 | "settings/v1alpha1", 313 | "storage/v1", 314 | "storage/v1alpha1", 315 | "storage/v1beta1", 316 | ] 317 | pruneopts = "UT" 318 | revision = "0f11257a8a25954878633ebdc9841c67d8f83bdb" 319 | version = "kubernetes-1.10.7" 320 | 321 | [[projects]] 322 | digest = "1:bc7920d9796bd371e092d379b27b6b9bd3b50d135260cbdc10512dde51aec449" 323 | name = "k8s.io/apimachinery" 324 | packages = [ 325 | "pkg/api/errors", 326 | "pkg/api/meta", 327 | "pkg/api/resource", 328 | "pkg/apis/meta/v1", 329 | "pkg/apis/meta/v1/unstructured", 330 | "pkg/apis/meta/v1beta1", 331 | "pkg/conversion", 332 | "pkg/conversion/queryparams", 333 | "pkg/fields", 334 | "pkg/labels", 335 | "pkg/runtime", 336 | "pkg/runtime/schema", 337 | "pkg/runtime/serializer", 338 | "pkg/runtime/serializer/json", 339 | "pkg/runtime/serializer/protobuf", 340 | "pkg/runtime/serializer/recognizer", 341 | "pkg/runtime/serializer/streaming", 342 | "pkg/runtime/serializer/versioning", 343 | "pkg/selection", 344 | "pkg/types", 345 | "pkg/util/clock", 346 | "pkg/util/errors", 347 | "pkg/util/framer", 348 | "pkg/util/intstr", 349 | "pkg/util/json", 350 | "pkg/util/net", 351 | "pkg/util/runtime", 352 | "pkg/util/sets", 353 | "pkg/util/validation", 354 | "pkg/util/validation/field", 355 | "pkg/util/wait", 356 | "pkg/util/yaml", 357 | "pkg/version", 358 | "pkg/watch", 359 | "third_party/forked/golang/reflect", 360 | ] 361 | pruneopts = "UT" 362 | revision = "e386b2658ed20923da8cc9250e552f082899a1ee" 363 | version = "kubernetes-1.10.7" 364 | 365 | [[projects]] 366 | digest = "1:0a4e3d4f41939942aa81b5900cd8332def6f529f2f286e521cc54d6d7874dbb8" 367 | name = "k8s.io/client-go" 368 | packages = [ 369 | "discovery", 370 | "kubernetes", 371 | "kubernetes/scheme", 372 | "kubernetes/typed/admissionregistration/v1alpha1", 373 | "kubernetes/typed/admissionregistration/v1beta1", 374 | "kubernetes/typed/apps/v1", 375 | "kubernetes/typed/apps/v1beta1", 376 | "kubernetes/typed/apps/v1beta2", 377 | "kubernetes/typed/authentication/v1", 378 | "kubernetes/typed/authentication/v1beta1", 379 | "kubernetes/typed/authorization/v1", 380 | "kubernetes/typed/authorization/v1beta1", 381 | "kubernetes/typed/autoscaling/v1", 382 | "kubernetes/typed/autoscaling/v2beta1", 383 | "kubernetes/typed/batch/v1", 384 | "kubernetes/typed/batch/v1beta1", 385 | "kubernetes/typed/batch/v2alpha1", 386 | "kubernetes/typed/certificates/v1beta1", 387 | "kubernetes/typed/core/v1", 388 | "kubernetes/typed/events/v1beta1", 389 | "kubernetes/typed/extensions/v1beta1", 390 | "kubernetes/typed/networking/v1", 391 | "kubernetes/typed/policy/v1beta1", 392 | "kubernetes/typed/rbac/v1", 393 | "kubernetes/typed/rbac/v1alpha1", 394 | "kubernetes/typed/rbac/v1beta1", 395 | "kubernetes/typed/scheduling/v1alpha1", 396 | "kubernetes/typed/settings/v1alpha1", 397 | "kubernetes/typed/storage/v1", 398 | "kubernetes/typed/storage/v1alpha1", 399 | "kubernetes/typed/storage/v1beta1", 400 | "pkg/apis/clientauthentication", 401 | "pkg/apis/clientauthentication/v1alpha1", 402 | "pkg/version", 403 | "plugin/pkg/client/auth/exec", 404 | "rest", 405 | "rest/watch", 406 | "tools/auth", 407 | "tools/clientcmd", 408 | "tools/clientcmd/api", 409 | "tools/clientcmd/api/latest", 410 | "tools/clientcmd/api/v1", 411 | "tools/metrics", 412 | "tools/reference", 413 | "transport", 414 | "util/cert", 415 | "util/flowcontrol", 416 | "util/homedir", 417 | "util/integer", 418 | ] 419 | pruneopts = "UT" 420 | revision = "23781f4d6632d88e869066eaebb743857aa1ef9b" 421 | version = "v7.0.0" 422 | 423 | [solve-meta] 424 | analyzer-name = "dep" 425 | analyzer-version = 1 426 | input-imports = [ 427 | "github.com/daniel840829/ode", 428 | "github.com/gazed/vu/math/lin", 429 | "github.com/globalsign/mgo", 430 | "github.com/globalsign/mgo/bson", 431 | "github.com/golang/protobuf/proto", 432 | "github.com/golang/protobuf/ptypes", 433 | "github.com/golang/protobuf/ptypes/any", 434 | "github.com/sirupsen/logrus", 435 | "github.com/zheng-ji/goSnowFlake", 436 | "golang.org/x/net/context", 437 | "google.golang.org/grpc", 438 | "google.golang.org/grpc/codes", 439 | "google.golang.org/grpc/metadata", 440 | "google.golang.org/grpc/status", 441 | "k8s.io/api/core/v1", 442 | "k8s.io/apimachinery/pkg/api/errors", 443 | "k8s.io/apimachinery/pkg/apis/meta/v1", 444 | "k8s.io/apimachinery/pkg/util/yaml", 445 | "k8s.io/client-go/kubernetes", 446 | "k8s.io/client-go/rest", 447 | "k8s.io/client-go/tools/clientcmd", 448 | ] 449 | solver-name = "gps-cdcl" 450 | solver-version = 1 451 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | branch = "master" 30 | name = "github.com/daniel840829/ode" 31 | 32 | [[constraint]] 33 | name = "github.com/gazed/vu" 34 | version = "0.10.0" 35 | 36 | [[constraint]] 37 | branch = "master" 38 | name = "github.com/globalsign/mgo" 39 | 40 | [[constraint]] 41 | name = "github.com/golang/protobuf" 42 | version = "1.2.0" 43 | 44 | [[constraint]] 45 | name = "github.com/sirupsen/logrus" 46 | version = "1.0.6" 47 | 48 | [[constraint]] 49 | branch = "master" 50 | name = "github.com/zheng-ji/goSnowFlake" 51 | 52 | [[constraint]] 53 | branch = "master" 54 | name = "golang.org/x/net" 55 | 56 | [[constraint]] 57 | name = "google.golang.org/grpc" 58 | version = "1.15.0" 59 | 60 | 61 | [prune] 62 | go-tests = true 63 | unused-packages = true 64 | 65 | [[override]] 66 | name = "k8s.io/api" 67 | version = "kubernetes-1.10.7" 68 | 69 | [[override]] 70 | name = "k8s.io/apimachinery" 71 | version = "kubernetes-1.10.7" 72 | 73 | [[override]] 74 | name = "k8s.io/client-go" 75 | version = "7.0.0" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lin Chan Wei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang Distributed Game Server 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/daniel840829/Distributed-Golang-Game-Server)](https://goreportcard.com/report/github.com/daniel840829/Distributed-Golang-Game-Server) 3 | [![CircleCI](https://circleci.com/gh/daniel840829/Distributed-Golang-Game-Server.svg?style=svg)](https://circleci.com/gh/daniel840829/Distributed-Golang-Game-Server) 4 | [![Gitter](https://badges.gitter.im/daniel840829/Distributed-Golang-Game-Server.svg)](https://gitter.im/daniel840829/Distributed-Golang-Game-Server?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 5 | ## Motivation 6 | At first, I just want to learn Golang.I started to think about which is the best way? 7 | Because the concurrency mechanisms of Golang is very powerful, I choose online game to verify if I can use Golang to make a efficient game server.For me, this is the first time I make this such hard project. I have to learn Unity, Golang, C# At a time. I am glad that I still have full passion to this project and I never give up. 8 | ## Tech/framework used 9 | - golang 10 | - gRPC 11 | - Kubernetes 12 | - Protobuf 13 | ## Features 14 | - CrossPlatform - Message packet use protobuf which is light, fast, crossplatform. 15 | - Autoscaling - controller is written with go-client ,you can wirte the strategy to autoscale dedicated game server by your own. 16 | - Lightweight - The image of dedicated game server is less than 40MB. 17 | ## Architecture 18 | ### Agent server : 19 | - match players to join other player's room or create own room 20 | - control the amount of gameplay server and load balancing. when the amountof a gameplay server's connections exceed maxium connections it should have, agent will create a new pod run gameplay server. 21 | ### Gameplay Server : 22 | - After players are matched successfully ,these players will get the gameplay server's ip and token,and player can start to play. 23 | ![](https://github.com/danie1Lin/Distributed-Golang-Game-Server/blob/kubernete-intergration/Golang%20Game%20ServerArchitecture.png?raw=true) 24 | ### Packet Validating 25 | In the branch master, I use ODE to simulate the physics on server.It is the most safe way to keep game fair.However, I found the memory server use is too much for me, Because I don't have money to maintance the server. So I started to design a way to let client validate packet and simulate physics separatly to reduce the heavy load on the server. I just complete the entities can attack each others so far. I will start to design aftewards: 26 | - The validation part preventing form players cheating 27 | - The interface connecting a physics simulator 28 | ## Installation 29 | - Server : 30 | Two Way to run server: 31 | - Run distributed server using Kubernete cluster 32 | 1. Use Kops to install kubernete on AWS 33 | 2. Create cluster 34 | 3. Install Mongodb 35 | 4. edit setupEnv.sh with your setting and bash setupEnv.sh 36 | 4. ```go run main.go --type=agent``` on your local machine (Must on Where you install Kops) 37 | - Run Standalone Server on local machine 38 | 1. Install Mongodb 39 | 3. edit setupEnv.sh( DONT_USE_KUBE = true )with your setting and bash setupEnv.sh 40 | 2. ```go run main.go --type=standalone``` on your local machine 41 | - Client : 42 | - [Download this project](https://github.com/daniel840829/Tank-Online) 43 | - You can run in Unity Editor by open the Prestart.scene as first scene. 44 | - If you want to test with mutliplayer you can try build Andorid apk because it is likely to be builded successfully. 45 | 46 | [![DEMO](https://github.com/danie1Lin/Distributed-Golang-Game-Server/blob/master/demo.gif)](https://www.youtube.com/watch?v=7Q9g6AqXasg "Demo") 47 | ## How to use? 48 | If you want to make your own game by modifying this project, I am pleasured. 49 | You can throught these step to make it work. 50 | ### Modify The msg/message.proto 51 | 1. Change the GameFrame message in proto buff. 52 | 2. Run './update.sh' in 'msg/' 53 | 3. unzip message.zip under 'Asset/gameServer/proto' in the Unity Project. 54 | ### Create Your Game Logic 55 | 1. add your handler to "gameServer/game/session/room.go": func (r *Room) Run() 56 | 2. modify the UpdateFrame fuction to handle packets design by yourself. then,Data Flow to Entity to render the change of entity's properties. 57 | ## The file structure: 58 | - agent 59 | - session 60 | - room.go 61 | - session.go 62 | - kubernetes.go 63 | - game 64 | - room.go 65 | - session.go 66 | - kubernetes.go 67 | - msg Use Protobuf to define package and RPC service interface 68 | - uuid generate different IDs of objects that can be call with reflection 69 | - user 70 | - UserManager 71 | - User 72 | - storage Use mongoDB to storage user infomation 73 | ## Support me 74 | Buy Me A Coffee 75 | -------------------------------------------------------------------------------- /agent/ClientToAgent.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/daniel840829/gameServer/agent/session" 7 | . "github.com/daniel840829/gameServer/msg" 8 | "github.com/daniel840829/gameServer/user" 9 | 10 | //. "github.com/daniel840829/gameServer/uuid" 11 | //"github.com/globalsign/mgo" 12 | //"fmt" 13 | //"github.com/daniel840829/gameServer/storage" 14 | //p "github.com/golang/protobuf/proto" 15 | //"github.com/golang/protobuf/ptypes" 16 | //any "github.com/golang/protobuf/ptypes/any" 17 | //log "github.com/sirupsen/logrus" 18 | 19 | "time" 20 | 21 | "os" 22 | 23 | log "github.com/sirupsen/logrus" 24 | "golang.org/x/net/context" 25 | "google.golang.org/grpc/codes" 26 | "google.golang.org/grpc/metadata" 27 | "google.golang.org/grpc/status" 28 | /* 29 | "io" 30 | "reflect" 31 | "sync" 32 | */) 33 | 34 | /* 35 | type ClientToAgentServer interface { 36 | AquireSessionKey(context.Context, *Empty) (*SessionKey, error) 37 | AquireOtherAgent(context.Context, *Empty) (*ServerInfo, error) 38 | // Login 39 | Login(context.Context, *LoginInput) (*UserInfo, error) 40 | CreateAccount(context.Context, *RegistInput) (*Error, error) 41 | // UserSetting 42 | SetAccount(context.Context, *AttrSetting) (*Success, error) 43 | SetCharacter(context.Context, *AttrSetting) (*Success, error) 44 | // room 45 | AquireGameServer(context.Context, *Empty) (*ServerInfo, error) 46 | CreateRoom(context.Context, *RoomSetting) (*Success, error) 47 | JoinRoom(context.Context, *ID) (*Success, error) 48 | RoomReady(context.Context, *Empty) (*Success, error) 49 | // View 50 | UpdateHome(*Empty, ClientToAgent_UpdateHomeServer) error 51 | UpdateRoomList(*Empty, ClientToAgent_UpdateRoomListServer) error 52 | UpdateUserList(*Empty, ClientToAgent_UpdateUserListServer) error 53 | // rpc UpdateRoomInfo(SessionKey) returns (stream RoomInfoView) {} 54 | Pipe(ClientToAgent_PipeServer) error 55 | }*/ 56 | 57 | type ErrorPipe struct { 58 | toClient chan (*MessageToUser) 59 | } 60 | 61 | func NewAgentRpc() (agent *Agent) { 62 | agent = &Agent{ 63 | ErrorPipe: &ErrorPipe{ 64 | toClient: make(chan (*MessageToUser), 10), 65 | }, 66 | } 67 | return 68 | } 69 | 70 | type Agent struct { 71 | Uuid int64 72 | ErrorPipe *ErrorPipe 73 | GameServer AgentToGameClient 74 | } 75 | 76 | func (a *Agent) Init(ip, agentToGamePort, clientToGamePort string) { 77 | if os.Getenv("DONT_USE_KUBE") == "true" { 78 | session.RoomManager.ConnectGameServer(ip, clientToGamePort, agentToGamePort, "0") 79 | } else { 80 | session.ClusterManager.KubeClientSet() 81 | } 82 | } 83 | 84 | func (a *Agent) AquireSessionKey(c context.Context, e *Empty) (*SessionKey, error) { 85 | id := session.Manager.MakeSession() 86 | return &SessionKey{Value: strconv.FormatInt(id, 10)}, nil 87 | } 88 | func (a *Agent) AquireOtherAgent(c context.Context, e *Empty) (*ServerInfo, error) { 89 | return nil, nil 90 | } 91 | 92 | func (a *Agent) GetSessionCache(c context.Context, e *Empty) (*SessionCache, error) { 93 | s := GetSesionFromContext(c) 94 | 95 | if s == nil { 96 | log.Warn("GetSessionCache Fail") 97 | return &SessionCache{}, status.Errorf(codes.NotFound, "Session Not Found!") 98 | } 99 | 100 | cache := s.GetSessionCache() 101 | return cache, nil 102 | } 103 | 104 | // Login 105 | 106 | func (a *Agent) Login(c context.Context, in *LoginInput) (*UserInfo, error) { 107 | s := GetSesionFromContext(c) 108 | if s == nil { 109 | return &UserInfo{}, status.Errorf(codes.NotFound, "Session Not Found!") 110 | } 111 | s.Lock() 112 | user := s.State.Login(in.UserName, in.Pswd) 113 | if user == nil { 114 | s.Unlock() 115 | return nil, nil 116 | } 117 | s.Unlock() 118 | return user.UserInfo, nil 119 | } 120 | func (a *Agent) CreateAccount(c context.Context, in *RegistInput) (*Error, error) { 121 | errmsg, err := user.Manager.Regist(in) 122 | return errmsg, err 123 | } 124 | 125 | // UserSetting 126 | func (a *Agent) SetAccount(context.Context, *AttrSetting) (*Success, error) { 127 | return nil, nil 128 | } 129 | 130 | func (a *Agent) SetCharacter(c context.Context, setting *CharacterSetting) (*Success, error) { 131 | s := GetSesionFromContext(c) 132 | if s == nil { 133 | return &Success{ 134 | Ok: false, 135 | }, status.Errorf(codes.NotFound, "Session Not Found!") 136 | } 137 | ok := s.State.SettingCharacter(setting) 138 | return &Success{ 139 | Ok: ok, 140 | }, nil 141 | } 142 | 143 | // allocate room 144 | func (a *Agent) AquireGameServer(c context.Context, e *Empty) (*ServerInfo, error) { 145 | s := GetSesionFromContext(c) 146 | log.Debug("Aquiring game server...") 147 | time.Sleep(5 * time.Second) 148 | if s == nil { 149 | return &ServerInfo{}, status.Errorf(codes.NotFound, "Session Not Found!") 150 | } 151 | msg := s.GetMsgChan("ServerInfo") 152 | if msg != nil { 153 | serverInfo := <-msg.DataCh 154 | return serverInfo.(*ServerInfo), nil 155 | } 156 | return &ServerInfo{}, nil 157 | } 158 | 159 | // View 160 | func (a *Agent) UpdateHome(*Empty, ClientToAgent_UpdateHomeServer) error { 161 | return nil 162 | } 163 | 164 | func (a *Agent) UpdateRoomList(e *Empty, stream ClientToAgent_UpdateRoomListServer) error { 165 | s := GetSesionFromContext(stream.Context()) 166 | if s == nil { 167 | return status.Errorf(codes.NotFound, "Session Not Found!") 168 | } 169 | msgChan := s.GetMsgChan("RoomList") 170 | data := msgChan.DataCh 171 | stop := msgChan.StopSignal 172 | for { 173 | select { 174 | case msg := <-data: 175 | stream.Send(msg.(*RoomList)) 176 | case <-stop: 177 | break 178 | } 179 | } 180 | return nil 181 | } 182 | 183 | func (a *Agent) UpdateUserList(*Empty, ClientToAgent_UpdateUserListServer) error { 184 | return nil 185 | } 186 | 187 | // rpc UpdateRoomInfo(SessionKey) returns (stream RoomInfoView) {} 188 | func (a *Agent) Pipe(ClientToAgent_PipeServer) error { 189 | return nil 190 | } 191 | func (a *Agent) CreateRoom(c context.Context, roomSetting *RoomSetting) (*Success, error) { 192 | s := GetSesionFromContext(c) 193 | if s == nil { 194 | return &Success{}, status.Errorf(codes.NotFound, "Session Not Found!") 195 | } 196 | ok := s.State.CreateRoom(roomSetting) 197 | return &Success{ 198 | Ok: ok, 199 | }, nil 200 | } 201 | 202 | func (a *Agent) JoinRoom(c context.Context, id *ID) (*Success, error) { 203 | s := GetSesionFromContext(c) 204 | 205 | if s == nil { 206 | return &Success{ 207 | Ok: false, 208 | }, status.Errorf(codes.NotFound, "Session Not Found!") 209 | } 210 | ok := s.State.EnterRoom(id.Value) 211 | return &Success{ 212 | Ok: ok, 213 | }, nil 214 | } 215 | 216 | func (a *Agent) UpdateRoomContent(e *Empty, stream ClientToAgent_UpdateRoomContentServer) error { 217 | s := GetSesionFromContext(stream.Context()) 218 | if s == nil { 219 | return status.Errorf(codes.NotFound, "Session Not Found!") 220 | } 221 | msgChan := s.GetMsgChan("RoomContent") 222 | data := msgChan.DataCh 223 | stop := msgChan.StopSignal 224 | for { 225 | select { 226 | case msg := <-data: 227 | stream.Send(msg.(*RoomContent)) 228 | case <-stop: 229 | break 230 | } 231 | } 232 | return nil 233 | } 234 | 235 | func (a *Agent) RoomReady(c context.Context, e *Empty) (*Success, error) { 236 | s := GetSesionFromContext(c) 237 | if s == nil { 238 | return &Success{ 239 | Ok: false, 240 | }, status.Errorf(codes.NotFound, "Session Not Found!") 241 | } 242 | 243 | if s.IsReady { 244 | if s.State.CancelReady() { 245 | return &Success{ 246 | Ok: s.IsReady, 247 | }, nil 248 | } 249 | } else { 250 | if s.State.ReadyRoom() { 251 | return &Success{ 252 | Ok: s.IsReady, 253 | }, nil 254 | } 255 | } 256 | return &Success{}, status.Errorf(codes.Internal, "Somethig Wrong") 257 | } 258 | 259 | func GetSesionFromContext(c context.Context) *session.Session { 260 | md, ok := metadata.FromIncomingContext(c) 261 | if !ok { 262 | return nil 263 | } 264 | s := session.Manager.GetSession(md) 265 | return s 266 | } 267 | -------------------------------------------------------------------------------- /agent/session/kubermanager.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "fmt" 5 | "os/signal" 6 | 7 | "k8s.io/api/core/v1" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "k8s.io/apimachinery/pkg/util/yaml" 11 | "k8s.io/client-go/kubernetes" 12 | 13 | //"k8s.io/client-go/rest" 14 | "os" 15 | "path/filepath" 16 | 17 | "strconv" 18 | 19 | "syscall" 20 | 21 | "k8s.io/apimachinery/pkg/api/errors" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/client-go/rest" 24 | "k8s.io/client-go/tools/clientcmd" 25 | ) 26 | 27 | const NAME_SPACE string = "default" 28 | const SERVER_AMOUNT int = 1 29 | const MAX_SERVER_AMOUT int = 20 30 | const MAX_PORT int = 31000 31 | const MIN_PORT int = 30000 32 | 33 | //MAX_ROOM_IN_POD maxium rooms in pod 34 | const MAX_ROOM_IN_POD = 1 35 | 36 | var interrupt chan os.Signal 37 | var PodMod *v1.Pod 38 | 39 | //ClusterManager clustermanager's instance 40 | var ClusterManager *clusterManager 41 | 42 | //ClusterManager manage kubernete's cluster and gameplayServer 43 | type clusterManager struct { 44 | client *kubernetes.Clientset 45 | pods map[string]*v1.Pod 46 | PodAmount int 47 | } 48 | 49 | //KubeClientSet create clientset 50 | func (c *clusterManager) KubeClientSet() { 51 | //config, err := rest.InClusterConfig() 52 | config, err := clientcmd.BuildConfigFromFlags("", filepath.Join(os.Getenv("HOME"), ".kube", "config")) 53 | 54 | if err != nil { 55 | log.Warn(err) 56 | config, err = rest.InClusterConfig() 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | } 61 | clientset, err := kubernetes.NewForConfig(config) 62 | c.client = clientset 63 | //log.Debug(config, clientset) 64 | 65 | //clientset.CoreV1().Services("NAME_SPACE") 66 | ex, err := os.Executable() 67 | if err != nil { 68 | panic(err) 69 | } 70 | exPath := filepath.Dir(ex) 71 | fmt.Println(exPath) 72 | p, _ := os.Getwd() 73 | f, err := os.Open(filepath.Join(p, "cluster/pod.yaml")) 74 | if err != nil { 75 | log.Fatal(ex, "not found:", err) 76 | return 77 | } 78 | 79 | yamlde := yaml.NewYAMLOrJSONDecoder(f, 300) 80 | podInfo := &v1.Pod{} 81 | if err = yamlde.Decode(podInfo); err != nil { 82 | log.Debug("yaml parse fail", err) 83 | } 84 | //log.Debug("pod info :", podInfo) 85 | PodMod = podInfo.DeepCopy() 86 | // id: '' 87 | // agentToGamePort: '' 88 | // clientToGamePort: '' 89 | // clientToAgentPort: '' 90 | if MIN_PORT+SERVER_AMOUNT*2 > MAX_PORT { 91 | panic("Usable port is not enough.") 92 | } 93 | for i := 0; i < SERVER_AMOUNT; i++ { 94 | c.CreatePod() 95 | } 96 | } 97 | 98 | //CreatePod scale gameplay server 99 | func (c *clusterManager) CreatePod() { 100 | if c.PodAmount == MAX_SERVER_AMOUT { 101 | return 102 | } 103 | i := c.PodAmount 104 | podInfo := PodMod.DeepCopy() 105 | podInfo.Name = "gameplayserver-" + strconv.Itoa(i) 106 | podInfo.Labels["type"] = "game" 107 | podInfo.Labels["id"] = strconv.Itoa(i) 108 | podInfo.Labels["agentToGamePort"] = strconv.Itoa(MIN_PORT + i*2) 109 | podInfo.Labels["clientToGamePort"] = strconv.Itoa(MIN_PORT + i*2 + 1) 110 | _, err := c.client.CoreV1().Pods(NAME_SPACE).Create(podInfo.DeepCopy()) 111 | if err != nil { 112 | log.Warn(err) 113 | if errors.IsAlreadyExists(err) { 114 | policy := metav1.DeletePropagationForeground 115 | deleteTime := int64(0) 116 | c.client.CoreV1().Pods(NAME_SPACE).Delete(podInfo.Name, &metav1.DeleteOptions{GracePeriodSeconds: &deleteTime, PropagationPolicy: &policy}) 117 | 118 | _, err := c.client.CoreV1().Pods(NAME_SPACE).Create(podInfo.DeepCopy()) 119 | if err != nil { 120 | log.Fatal("Create Fail", err) 121 | } 122 | } 123 | } 124 | c.pods[podInfo.Labels["id"]], err = c.client.CoreV1().Pods(NAME_SPACE).Get(podInfo.Name, metav1.GetOptions{}) 125 | if err != nil { 126 | log.Warn(err) 127 | } 128 | 129 | nodeName := c.pods[podInfo.Labels["id"]].Spec.NodeName 130 | node, err := c.client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{}) 131 | if err != nil { 132 | log.Fatal(err) 133 | } 134 | extIP := "" 135 | for _, add := range node.Status.Addresses { 136 | if add.Type == v1.NodeExternalIP { 137 | extIP = add.Address 138 | break 139 | } 140 | } 141 | 142 | RoomManager.ConnectGameServer(extIP, podInfo.Labels["clientToGamePort"], podInfo.Labels["agentToGamePort"], podInfo.Labels["id"]) 143 | log.Info("Gameplyer Server created: id: ", podInfo.Labels["id"], " IP: ", extIP, " Client Port: ", podInfo.Labels["clientToGamePort"], " Agent Port: ", podInfo.Labels["agentToGamePort"]) 144 | c.PodAmount++ 145 | } 146 | 147 | func cleanUp() { 148 | <-interrupt 149 | log.Info("Stopping agent") 150 | log.Info("Stopping gameplayer server") 151 | for _, pod := range ClusterManager.pods { 152 | err := ClusterManager.client.CoreV1().Pods(NAME_SPACE).Delete(pod.Name, &metav1.DeleteOptions{}) 153 | if err != nil { 154 | log.Warn(err) 155 | } 156 | } 157 | os.Exit(1) 158 | } 159 | 160 | func init() { 161 | if os.Getenv("DONT_USE_KUBE") == "true" { 162 | return 163 | } 164 | ClusterManager = &clusterManager{ 165 | pods: make(map[string]*v1.Pod), 166 | } 167 | interrupt = make(chan os.Signal) 168 | signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) 169 | go cleanUp() 170 | } 171 | -------------------------------------------------------------------------------- /agent/session/room.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | . "github.com/daniel840829/gameServer/msg" 8 | . "github.com/daniel840829/gameServer/uuid" 9 | "github.com/golang/protobuf/proto" 10 | log "github.com/sirupsen/logrus" 11 | "golang.org/x/net/context" 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | type ChatMessage struct { 16 | SpeakerId int32 17 | SpeakerName string 18 | Content string 19 | } 20 | 21 | type ChatRoom struct { 22 | ReadingBuffer []*ChatMessage 23 | } 24 | 25 | //TODO : Chat function 26 | 27 | var limitReadyTime = 1000 //wait ms to start a room 28 | 29 | var RoomManager *roomManager = &roomManager{ 30 | Rooms: make(map[int64]*Room), 31 | UserIdleRoomListChan: make(map[*MsgChannel]struct{}), 32 | GameServers: make(map[*GameServer]struct{}), 33 | } 34 | 35 | type GameServer struct { 36 | ExtIp string 37 | AgentPort string 38 | ClientPort string 39 | Client AgentToGameClient 40 | Rooms map[*Room]struct{} 41 | id string 42 | } 43 | 44 | func newGameServer(ExtIp, clientToGamePort, agentToGamePort, id string) (gameServer *GameServer) { 45 | conn, err := grpc.Dial(ExtIp+":"+agentToGamePort, grpc.WithInsecure()) 46 | if err != nil { 47 | log.Warn("Agent can't connect to GameServer", err) 48 | return 49 | } 50 | gameServer = &GameServer{ 51 | id: id, 52 | ExtIp: ExtIp, 53 | ClientPort: ":" + clientToGamePort, 54 | AgentPort: ":" + agentToGamePort, 55 | Rooms: make(map[*Room]struct{}, 0), 56 | } 57 | gameServer.Client = NewAgentToGameClient(conn) 58 | log.Debug("client", gameServer.Client) 59 | return 60 | } 61 | 62 | type roomManager struct { 63 | UserIdleRoomListChan map[*MsgChannel]struct{} 64 | Rooms map[int64]*Room 65 | GameServers map[*GameServer]struct{} 66 | sync.RWMutex 67 | RoomList *RoomList 68 | } 69 | 70 | func (rm *roomManager) DeleteRoom(room *Room) { 71 | room.DeleteRoom() 72 | delete(rm.Rooms, room.Uuid) 73 | rm.UpdateRoomList() 74 | } 75 | 76 | func (rm *roomManager) ConnectGameServer(ExtIp, clientToGamePort, agentToGamePort, id string) { 77 | rm.GameServers[newGameServer(ExtIp, clientToGamePort, agentToGamePort, id)] = struct{}{} 78 | log.Debug("Connect Game Server") 79 | } 80 | 81 | func (rm *roomManager) GetGameServer() (game *GameServer) { 82 | 83 | for gs, _ := range rm.GameServers { 84 | if len(gs.Rooms) >= MAX_ROOM_IN_POD { 85 | continue 86 | } 87 | game = gs 88 | break 89 | } 90 | if game == nil { 91 | ClusterManager.CreatePod() 92 | game = rm.GetGameServer() 93 | } 94 | return 95 | } 96 | func (rm *roomManager) UpdateRoomList() { 97 | roomList := &RoomList{ 98 | Item: make([]*RoomReview, 0), 99 | } 100 | for _, room := range rm.Rooms { 101 | roomList.Item = append(roomList.Item, room.GetReview()) 102 | } 103 | for c, _ := range rm.UserIdleRoomListChan { 104 | c.DataCh <- roomList 105 | } 106 | log.Debug("Update room list", roomList) 107 | } 108 | 109 | func (rm *roomManager) AddIdleUserMsgChan(m *MsgChannel) { 110 | if _, ok := rm.UserIdleRoomListChan[m]; ok { 111 | return 112 | } 113 | rm.UserIdleRoomListChan[m] = struct{}{} 114 | } 115 | 116 | func (rm *roomManager) RemoveIdleUserMsgChan(m *MsgChannel) { 117 | if _, ok := rm.UserIdleRoomListChan[m]; ok { 118 | delete(rm.UserIdleRoomListChan, m) 119 | } 120 | } 121 | 122 | func (rm *roomManager) Run() { 123 | 124 | } 125 | 126 | func (rm *roomManager) LeaveRoom() { 127 | 128 | } 129 | 130 | func (rm *roomManager) CreateRoom(master *Session, setting *RoomSetting) *Room { 131 | id, _ := Uid.NewId(ROOM_ID) 132 | room := NewRoom(master, id, setting) 133 | rm.Rooms[id] = room 134 | rm.UpdateRoomList() 135 | return room 136 | } 137 | 138 | func NewRoom(master *Session, roomId int64, setting *RoomSetting) *Room { 139 | room := &Room{ 140 | Master: master, 141 | Client: make(map[*Session]struct{}), 142 | Uuid: roomId, 143 | Name: setting.Name, 144 | GameType: setting.GameType, 145 | MaxPlyer: setting.MaxPlayer, 146 | PlayerInRoom: 1, 147 | Review: &RoomReview{ 148 | Uuid: roomId, 149 | }, 150 | } 151 | room.UpdateReview() 152 | RoomManager.UpdateRoomList() 153 | room.UpdateRoomContent() 154 | return room 155 | } 156 | 157 | type Room struct { 158 | Name string 159 | GameType string 160 | Master *Session 161 | Client map[*Session]struct{} 162 | Uuid int64 163 | IsFull bool 164 | IsCreatOnGameServer bool 165 | MaxPlyer int32 166 | PlayerInRoom int32 167 | Review *RoomReview 168 | GameServer *GameServer 169 | sync.RWMutex 170 | } 171 | 172 | func (r *Room) GetReview() (Review *RoomReview) { 173 | r.RLock() 174 | Review = proto.Clone(r.Review).(*RoomReview) 175 | r.RUnlock() 176 | return 177 | } 178 | 179 | func (r *Room) UpdateReview() { 180 | r.Lock() 181 | r.Review.Name = r.Name 182 | r.Review.GameType = r.GameType 183 | r.Review.InRoomPlayer = r.PlayerInRoom 184 | r.Review.MaxPlayer = r.MaxPlyer 185 | r.Unlock() 186 | RoomManager.UpdateRoomList() 187 | } 188 | 189 | func (r *Room) EnterRoom(client *Session) bool { 190 | if _, ok := r.Client[client]; ok { 191 | return false 192 | } 193 | if r.IsFull { 194 | return false 195 | } 196 | r.Client[client] = struct{}{} 197 | r.PlayerInRoom += 1 198 | if r.PlayerInRoom == r.MaxPlyer { 199 | r.IsFull = true 200 | } 201 | r.UpdateReview() 202 | r.UpdateRoomContent() 203 | return true 204 | } 205 | 206 | func (r *Room) KickOut(master *Session, client *Session) bool { 207 | if master != r.Master { 208 | return false 209 | } 210 | if _, ok := r.Client[client]; ok { 211 | client.Room = nil 212 | delete(r.Client, client) 213 | r.PlayerInRoom -= 1 214 | r.IsFull = false 215 | r.UpdateReview() 216 | r.UpdateRoomContent() 217 | return true 218 | } 219 | return false 220 | } 221 | 222 | func (r *Room) DeleteRoom() bool { 223 | if r.IsCreatOnGameServer { 224 | 225 | _, err := r.GameServer.Client.DeletGameRoom(context.Background(), &RoomInfo{Uuid: r.Uuid}) 226 | if err != nil { 227 | log.Warn(err) 228 | } 229 | } 230 | if r.Master != nil { 231 | r.Master.Room = nil 232 | } 233 | for s, _ := range r.Client { 234 | s.Room = nil 235 | } 236 | return true 237 | } 238 | 239 | func (r *Room) LeaveRoom(s *Session) bool { 240 | if s == r.Master { 241 | s.Room = nil 242 | r.Master = nil 243 | r.PlayerInRoom -= 1 244 | r.IsFull = false 245 | r.UpdateReview() 246 | r.UpdateRoomContent() 247 | 248 | } else { 249 | if _, ok := r.Client[s]; ok { 250 | s.Room = nil 251 | delete(r.Client, s) 252 | r.PlayerInRoom -= 1 253 | r.IsFull = false 254 | r.UpdateReview() 255 | r.UpdateRoomContent() 256 | 257 | } else { 258 | return false 259 | } 260 | } 261 | if len(r.Client) == 0 && r.Master == nil { 262 | RoomManager.DeleteRoom(r) 263 | } 264 | return true 265 | } 266 | 267 | func (r *Room) UpdateRoomContent() { 268 | content := &RoomContent{ 269 | Uuid: r.Uuid, 270 | Players: make(map[string]*PlayerInfo), 271 | } 272 | if r.Master != nil { 273 | pInfo := r.Master.GetPlayerInfo() 274 | content.Players[pInfo.UserName] = pInfo 275 | } 276 | for s, _ := range r.Client { 277 | pInfo := s.GetPlayerInfo() 278 | content.Players[pInfo.UserName] = pInfo 279 | } 280 | 281 | if r.Master != nil { 282 | ch := r.Master.GetMsgChan("RoomContent") 283 | ch.DataCh <- content 284 | } 285 | for s, _ := range r.Client { 286 | ch := s.GetMsgChan("RoomContent") 287 | ch.DataCh <- content 288 | } 289 | } 290 | 291 | func (r *Room) CheckReady() bool { 292 | r.UpdateRoomContent() 293 | if !r.Master.IsReady { 294 | return false 295 | } 296 | for s, _ := range r.Client { 297 | if !s.IsReady { 298 | return false 299 | } 300 | } 301 | r.CreateRoomOnGameServer() 302 | return true 303 | //Start Game 304 | } 305 | 306 | func (r *Room) CreateRoomOnGameServer() { 307 | if !r.IsCreatOnGameServer { 308 | gameCreation := &GameCreation{ 309 | PlayerSessions: make([]*SessionInfo, 0), 310 | RoomInfo: &RoomInfo{Uuid: r.Uuid}, 311 | } 312 | gameCreation.RoomInfo.GameType = r.GameType 313 | r.Master.SetState(int32(SessionInfo_ConnectingGame)) 314 | gameCreation.PlayerSessions = append(gameCreation.PlayerSessions, r.Master.GetSessionInfo()) 315 | for s, _ := range r.Client { 316 | s.SetState(int32(SessionInfo_ConnectingGame)) 317 | gameCreation.PlayerSessions = append(gameCreation.PlayerSessions, s.GetSessionInfo()) 318 | } 319 | gs := RoomManager.GetGameServer() 320 | gs.Rooms[r] = struct{}{} 321 | pem := make(chan *PemKey) 322 | var getKey func(sig chan *PemKey) 323 | getKey = func(sig chan *PemKey) { 324 | key, err := gs.Client.AquireGameRoom(context.Background(), gameCreation) 325 | if err != nil { 326 | log.Warn("GameServer has some issue: ", err) 327 | time.Sleep(2 * time.Second) 328 | go getKey(sig) 329 | return 330 | } 331 | sig <- key 332 | } 333 | go getKey(pem) 334 | key := <-pem 335 | c := r.Master.GetMsgChan("ServerInfo") 336 | serverInfo := &ServerInfo{ 337 | Addr: gs.ExtIp, 338 | Port: gs.ClientPort, 339 | PublicKey: key.SSL, 340 | } 341 | c.DataCh <- serverInfo 342 | r.Master.ServerInfo = serverInfo 343 | for s, _ := range r.Client { 344 | s.ServerInfo = serverInfo 345 | c = s.GetMsgChan("ServerInfo") 346 | c.DataCh <- serverInfo 347 | } 348 | r.GameServer = gs 349 | r.IsCreatOnGameServer = true 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /agent/session/session.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | //"github.com/daniel840829/gameServer/entity" 5 | . "github.com/daniel840829/gameServer/msg" 6 | "github.com/daniel840829/gameServer/user" 7 | . "github.com/daniel840829/gameServer/uuid" 8 | log "github.com/sirupsen/logrus" 9 | "google.golang.org/grpc/metadata" 10 | "strconv" 11 | "sync" 12 | ) 13 | 14 | type MsgChannel struct { 15 | DataCh chan (interface{}) 16 | StopSignal chan (struct{}) 17 | } 18 | 19 | func (m *MsgChannel) Close() { 20 | select { 21 | case <-m.StopSignal: 22 | return 23 | default: 24 | close(m.StopSignal) 25 | } 26 | } 27 | 28 | func NewMsgChannel(bufferNumber int32) *MsgChannel { 29 | return &MsgChannel{ 30 | DataCh: make(chan (interface{}), bufferNumber), 31 | StopSignal: make(chan (struct{}), 1), 32 | } 33 | } 34 | 35 | func NewMsgChannelManager() *MsgChannelManager { 36 | return &MsgChannelManager{ 37 | make(map[string]*MsgChannel), 38 | } 39 | } 40 | 41 | type MsgChannelManager struct { 42 | c map[string]*MsgChannel 43 | } 44 | 45 | func (m *MsgChannelManager) AddMsgChan(name string, bufferNumber int32) bool { 46 | if _, ok := m.c[name]; ok { 47 | return false 48 | } 49 | m.c[name] = NewMsgChannel(bufferNumber) 50 | return true 51 | } 52 | 53 | func (m *MsgChannelManager) GetMsgChan(name string) *MsgChannel { 54 | return m.c[name] 55 | } 56 | 57 | func (m *MsgChannelManager) CloseMsgChan(name string) { 58 | if ch, ok := m.c[name]; ok { 59 | ch.Close() 60 | delete(m.c, name) 61 | } 62 | } 63 | 64 | type sessionManager struct { 65 | Sessions map[int64]*Session 66 | UserNameMapSession map[string]int64 67 | sync.RWMutex 68 | } 69 | 70 | func (sm *sessionManager) CleanSession(id int64) { 71 | if s, ok := sm.Sessions[id]; ok { 72 | s.Room.LeaveRoom(s) 73 | } 74 | } 75 | 76 | func (sm *sessionManager) MakeSession() int64 { 77 | s := NewSession() 78 | sm.Lock() 79 | sm.Sessions[s.Info.Uuid] = s 80 | sm.Unlock() 81 | return s.Info.Uuid 82 | } 83 | 84 | func (sm *sessionManager) GetSession(md metadata.MD) *Session { 85 | mdid := md.Get("session-id") 86 | if len(mdid) == 0 { 87 | return nil 88 | } 89 | id, err := strconv.ParseInt(mdid[0], 10, 64) 90 | if err != nil { 91 | return nil 92 | } 93 | s, ok := sm.Sessions[id] 94 | if !ok { 95 | return nil 96 | } 97 | s.RLock() 98 | if s.User != nil { 99 | uname := md.Get("uname") 100 | if len(uname) == 0 { 101 | s.RUnlock() 102 | return nil 103 | } else if s.User.UserInfo.UserName != uname[0] { 104 | s.RUnlock() 105 | return nil 106 | } 107 | } else { 108 | uname := md.Get("uname") 109 | if len(uname) != 0 { 110 | return nil 111 | } 112 | } 113 | s.RUnlock() 114 | return s 115 | } 116 | func NewSession() *Session { 117 | s := &Session{ 118 | Info: &SessionInfo{}, 119 | MsgChannelManager: NewMsgChannelManager(), 120 | PlayerInfo: &PlayerInfo{}, 121 | } 122 | for i := int32(SessionInfo_NoSession); i <= int32(SessionInfo_GameServerWaitReconnect); i++ { 123 | ss := SessionStateFactory.makeSessionState(s, SessionInfo_SessionState(i)) 124 | s.States = append(s.States, ss) 125 | } 126 | s.SetState(0) 127 | s.State.CreateSession() 128 | return s 129 | } 130 | 131 | type Session struct { 132 | Info *SessionInfo 133 | State SessionState 134 | SessionKey int64 135 | User *user.User 136 | States []SessionState 137 | Room *Room 138 | *MsgChannelManager 139 | sync.RWMutex 140 | PlayerInfo *PlayerInfo 141 | TeamNo int32 142 | IsReady bool 143 | ServerInfo *ServerInfo 144 | } 145 | 146 | func (s *Session) GetPlayerInfo() *PlayerInfo { 147 | if s.User == nil { 148 | return nil 149 | } 150 | s.PlayerInfo.UserName = s.User.UserInfo.UserName 151 | s.PlayerInfo.UserId = s.User.UserInfo.Uuid 152 | if s.User.UserInfo.UsedCharacter == int64(0) { 153 | for id, c := range s.User.UserInfo.OwnCharacter { 154 | s.User.UserInfo.UsedCharacter = id 155 | s.PlayerInfo.Character = c 156 | break 157 | } 158 | } else { 159 | s.PlayerInfo.Character = s.User.UserInfo.OwnCharacter[s.User.UserInfo.UsedCharacter] 160 | } 161 | s.PlayerInfo.TeamNo = s.TeamNo 162 | s.PlayerInfo.IsReady = s.IsReady 163 | return s.PlayerInfo 164 | } 165 | 166 | func (s *Session) SetState(state_index int32) { 167 | s.State = s.States[state_index] 168 | } 169 | 170 | func (s *Session) GetSessionInfo() *SessionInfo { 171 | info := &UserInfo{} 172 | if s.User != nil { 173 | info = s.User.GetInfo() 174 | } 175 | return &SessionInfo{ 176 | Uuid: s.Info.Uuid, 177 | UserInfo: info, 178 | State: s.State.GetStateCode(), 179 | } 180 | } 181 | 182 | func (s *Session) GetSessionCache() *SessionCache { 183 | return &SessionCache{ 184 | GameServerInfo: s.ServerInfo, 185 | SessionInfo: s.GetSessionInfo(), 186 | } 187 | } 188 | 189 | type SessionState interface { 190 | SetSession(s *Session) bool 191 | SetStateCode(SessionInfo_SessionState) 192 | GetStateCode() SessionInfo_SessionState 193 | CreateSession() int64 194 | Login(uname string, pswd string) *user.User 195 | Logout() bool 196 | Regist(uname string, pswd string, info ...string) bool 197 | CreateRoom(setting *RoomSetting) bool 198 | EnterRoom(roomId int64) bool 199 | DeleteRoom() bool 200 | ReadyRoom() bool 201 | LeaveRoom() bool 202 | StartRoom() bool 203 | SettingCharacter(*CharacterSetting) bool 204 | SettingRoom() bool 205 | CancelReady() bool 206 | EndRoom() bool 207 | String() string 208 | Lock() 209 | Unlock() 210 | } 211 | 212 | func (sb *SessionStateBase) SetSession(s *Session) bool { 213 | if sb.Session != nil { 214 | return false 215 | } 216 | sb.Session = s 217 | return true 218 | } 219 | 220 | func (sb *SessionStateBase) String() string { 221 | return SessionInfo_SessionState_name[int32(sb.StateCode)] 222 | } 223 | 224 | func (sb *SessionStateBase) SetStateCode(code SessionInfo_SessionState) { 225 | sb.StateCode = code 226 | } 227 | func (sb *SessionStateBase) GetStateCode() SessionInfo_SessionState { 228 | return sb.StateCode 229 | } 230 | func (sb *SessionStateBase) CreateSession() int64 { 231 | return 0 232 | } 233 | func (sb *SessionStateBase) Login(uname string, pswd string) *user.User { 234 | return nil 235 | } 236 | func (sb *SessionStateBase) Logout() bool { 237 | return false 238 | } 239 | func (sb *SessionStateBase) Regist(uname string, pswd string, info ...string) bool { 240 | return false 241 | } 242 | func (sb *SessionStateBase) CreateRoom(setting *RoomSetting) bool { 243 | return false 244 | } 245 | func (sb *SessionStateBase) EnterRoom(roomId int64) bool { 246 | return false 247 | } 248 | func (sb *SessionStateBase) DeleteRoom() bool { 249 | return false 250 | } 251 | func (sb *SessionStateBase) ReadyRoom() bool { 252 | return false 253 | } 254 | func (sb *SessionStateBase) LeaveRoom() bool { 255 | return false 256 | } 257 | func (sb *SessionStateBase) StartRoom() bool { 258 | return false 259 | } 260 | func (sb *SessionStateBase) SettingCharacter(*CharacterSetting) bool { 261 | return false 262 | } 263 | func (sb *SessionStateBase) SettingRoom() bool { 264 | return false 265 | } 266 | func (sb *SessionStateBase) EndRoom() bool { 267 | return false 268 | } 269 | 270 | func (sb *SessionStateBase) CancelReady() bool { 271 | return false 272 | } 273 | 274 | type SessionStateBase struct { 275 | StateCode SessionInfo_SessionState 276 | Session *Session 277 | sync.RWMutex 278 | } 279 | 280 | type NoSessionState struct { 281 | SessionStateBase 282 | } 283 | 284 | func (ss *NoSessionState) CreateSession() int64 { 285 | //TODO 286 | s := ss.Session 287 | s.Lock() 288 | s.Info.Uuid, _ = Uid.NewId(SESSION_ID) 289 | uuid := s.Info.Uuid 290 | ss.Session.SetState(int32(ss.StateCode) + 1) 291 | s.Unlock() 292 | return uuid 293 | } 294 | 295 | type GuestSessionState struct { 296 | SessionStateBase 297 | } 298 | 299 | func (ss *GuestSessionState) Regist(uname string, pswd string, info ...string) bool { 300 | //TODO 301 | in := &RegistInput{UserName: uname, Pswd: pswd} 302 | _, err := user.Manager.Regist(in) 303 | if err != nil { 304 | return false 305 | } 306 | return true 307 | } 308 | 309 | func (ss *GuestSessionState) Login(uname string, pswd string) *user.User { 310 | //TODO 311 | 312 | in := &LoginInput{UserName: uname, Pswd: pswd} 313 | userInfo, err := user.Manager.Login(in) 314 | if err != nil { 315 | log.Warn(err) 316 | } 317 | if userInfo == nil { 318 | return nil 319 | } 320 | user := user.Manager.UserOnline[userInfo.Uuid] 321 | ss.Session.User = user 322 | ss.Session.SetState(int32(ss.StateCode) + 1) 323 | log.Info("user state:", ss.Session.State) 324 | ss.Session.AddMsgChan("RoomList", 10) 325 | RoomManager.AddIdleUserMsgChan(ss.Session.GetMsgChan("RoomList")) 326 | RoomManager.UpdateRoomList() 327 | if id, ok := Manager.UserNameMapSession[uname]; ok { 328 | Manager.CleanSession(id) 329 | } 330 | Manager.UserNameMapSession[uname] = ss.Session.Info.Uuid 331 | return user 332 | } 333 | 334 | type UserIdleSessionState struct { 335 | SessionStateBase 336 | } 337 | 338 | func (ss *UserIdleSessionState) CreateRoom(roomSetting *RoomSetting) bool { 339 | //TODO 340 | ss.Session.Info.Capacity = SessionInfo_RoomMaster 341 | ss.Session.AddMsgChan("RoomContent", 10) 342 | room := RoomManager.CreateRoom(ss.Session, roomSetting) 343 | ss.Session.Room = room 344 | ss.Session.SetState(int32(ss.StateCode) + 1) 345 | RoomManager.RemoveIdleUserMsgChan(ss.Session.GetMsgChan("RoomList")) 346 | ss.Session.CloseMsgChan("RoomList") 347 | ss.Session.AddMsgChan("ServerInfo", 1) 348 | return true 349 | } 350 | 351 | func (ss *UserIdleSessionState) EnterRoom(roomId int64) bool { 352 | room := RoomManager.Rooms[roomId] 353 | if room != nil { 354 | ss.Session.AddMsgChan("RoomContent", 2) 355 | if room.EnterRoom(ss.Session) { 356 | ss.Session.Room = room 357 | ss.Session.SetState(int32(ss.StateCode) + 1) 358 | RoomManager.RemoveIdleUserMsgChan(ss.Session.GetMsgChan("RoomList")) 359 | ss.Session.CloseMsgChan("RoomList") 360 | ss.Session.AddMsgChan("ServerInfo", 1) 361 | return true 362 | } else { 363 | ss.Session.CloseMsgChan("RoomContent") 364 | } 365 | } 366 | return false 367 | } 368 | 369 | type UserInRoomSessionState struct { 370 | SessionStateBase 371 | } 372 | 373 | func (ss *UserInRoomSessionState) DeleteRoom() bool { 374 | return false 375 | } 376 | 377 | func (ss *UserInRoomSessionState) ReadyRoom() bool { 378 | ss.Session.IsReady = true 379 | ss.Session.Room.CheckReady() 380 | return true 381 | } 382 | 383 | func (ss *UserInRoomSessionState) LeaveRoom() bool { 384 | ss.Session.IsReady = false 385 | ss.Session.Room = nil 386 | ss.Session.SetState(int32(ss.StateCode) - 1) 387 | return false 388 | } 389 | 390 | func (ss *UserInRoomSessionState) SettingCharacter(setting *CharacterSetting) bool { 391 | if ss.Session.User.SetCharacter(setting) { 392 | ss.Session.Room.UpdateRoomContent() 393 | return true 394 | } 395 | return false 396 | 397 | } 398 | func (ss *UserInRoomSessionState) CancelReady() bool { 399 | ss.Session.IsReady = false 400 | ss.Session.Room.CheckReady() 401 | return true 402 | } 403 | 404 | type ConnectingGameSessionState struct { 405 | SessionStateBase 406 | } 407 | 408 | func (ss *ConnectingGameSessionState) EndRoom() bool { 409 | return false 410 | } 411 | 412 | type sessionStateFactory struct { 413 | } 414 | 415 | func (sf *sessionStateFactory) makeSessionState(session *Session, state_code SessionInfo_SessionState) SessionState { 416 | var s SessionState 417 | switch state_code { 418 | case SessionInfo_NoSession: 419 | s = &NoSessionState{} 420 | case SessionInfo_Guest: 421 | s = &GuestSessionState{} 422 | case SessionInfo_UserIdle: 423 | s = &UserIdleSessionState{} 424 | case SessionInfo_UserInRoom: 425 | s = &UserInRoomSessionState{} 426 | case SessionInfo_ConnectingGame: 427 | s = &ConnectingGameSessionState{} 428 | default: 429 | s = &SessionStateBase{} 430 | } 431 | s.Lock() 432 | s.SetSession(session) 433 | s.SetStateCode(state_code) 434 | s.Unlock() 435 | return s 436 | } 437 | 438 | var Manager *sessionManager 439 | 440 | var SessionStateFactory *sessionStateFactory 441 | 442 | func init() { 443 | Manager = &sessionManager{ 444 | Sessions: make(map[int64]*Session), 445 | UserNameMapSession: make(map[string]int64), 446 | } 447 | SessionStateFactory = &sessionStateFactory{} 448 | } 449 | -------------------------------------------------------------------------------- /cluster/accountbind.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: fabric8-rbac 5 | subjects: 6 | - kind: ServiceAccount 7 | # Reference to upper's `metadata.name` 8 | name: default 9 | # Reference to upper's `metadata.namespace` 10 | namespace: default 11 | roleRef: 12 | kind: ClusterRole 13 | name: cluster-admin 14 | apiGroup: rbac.authorization.k8s.io 15 | -------------------------------------------------------------------------------- /cluster/agent.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: agent 5 | labels: 6 | app: gameserver 7 | type: agent 8 | id: '' 9 | agentToGamePort: '' 10 | clientToGamePort: '8080' 11 | clientToAgentPort: '50051' 12 | spec: 13 | hostNetwork: true 14 | restartPolicy: Never 15 | containers: 16 | - name: game-server 17 | image: daniel840829/gameplayserver:circleci-latest 18 | imagePullPolicy: Always 19 | env: 20 | - name: CLIENT_TO_GAME_PORT 21 | valueFrom: 22 | fieldRef: 23 | fieldPath: metadata.labels['clientToGamePort'] 24 | - name: AGENT_TO_GAME_PORT 25 | valueFrom: 26 | fieldRef: 27 | fieldPath: metadata.labels['agentToGamePort'] 28 | - name: SERVER_TYPE 29 | valueFrom: 30 | fieldRef: 31 | fieldPath: metadata.labels['type'] 32 | - name: ID 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: metadata.labels['id'] 36 | - name: CLIENT_TO_AGENT_PORT 37 | valueFrom: 38 | fieldRef: 39 | fieldPath: metadata.labels['clientToAgentPort'] 40 | - name: MGO_PASS 41 | valueFrom: 42 | secretKeyRef: 43 | name: agent-secret 44 | key: mgoPass 45 | - name: MGO_USER 46 | valueFrom: 47 | secretKeyRef: 48 | name: agent-secret 49 | key: mgoUser 50 | - name: MGO_ADDR 51 | valueFrom: 52 | secretKeyRef: 53 | name: agent-secret 54 | key: mgoAddr 55 | -------------------------------------------------------------------------------- /cluster/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: hello-deployment 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app: gameserver 10 | type: game 11 | template: 12 | metadata: 13 | labels: 14 | app: gameserver 15 | type: game 16 | id: 0 17 | spec: 18 | hostNetwork: true 19 | containers: 20 | - name: gameplayserver 21 | image: daniel840829/gameplayserver:v3 22 | ports: 23 | - name: client-port 24 | containerPort: 50051 25 | - name: agent-port 26 | containerPort: 3000 27 | -------------------------------------------------------------------------------- /cluster/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: gameplayserver- 5 | labels: 6 | app: gameserver 7 | type: game 8 | id: '' 9 | agentToGamePort: '' 10 | clientToGamePort: '' 11 | clientToAgentPort: '' 12 | spec: 13 | hostNetwork: true 14 | imagePullPolicy: Always 15 | containers: 16 | - name: game-server 17 | image: daniel840829/gameplayserver:circleci-latest 18 | env: 19 | - name: CLIENT_TO_GAME_PORT 20 | valueFrom: 21 | fieldRef: 22 | fieldPath: metadata.labels['clientToGamePort'] 23 | - name: AGENT_TO_GAME_PORT 24 | valueFrom: 25 | fieldRef: 26 | fieldPath: metadata.labels['agentToGamePort'] 27 | - name: SERVER_TYPE 28 | valueFrom: 29 | fieldRef: 30 | fieldPath: metadata.labels['type'] 31 | - name: ID 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.labels['id'] 35 | - name: CLIENT_TO_AGENT_PORT 36 | valueFrom: 37 | fieldRef: 38 | fieldPath: metadata.labels['clientToAgentPort'] 39 | -------------------------------------------------------------------------------- /data.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package remoteProxy 3 | 4 | 5 | 6 | message Invoker { 7 | string Func = 1; 8 | int64 id = 2; 9 | string objName = 3; 10 | repeated Data Arg = 4; 11 | } 12 | 13 | message Data { 14 | oneof data{ 15 | string String = 1; 16 | int32 Int32 = 2; 17 | int64 Int64 = 3; 18 | double Double = 4; 19 | float Float =5; 20 | } 21 | } 22 | 23 | message CallableObj { 24 | string Name = 1; 25 | } 26 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danie1Lin/Distributed-Golang-Game-Server/620f6c91b6e21db9f8eca88916b1130223ff8b81/demo.gif -------------------------------------------------------------------------------- /game/agentToGame.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/daniel840829/gameServer/game/session" 5 | . "github.com/daniel840829/gameServer/msg" 6 | log "github.com/sirupsen/logrus" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/metadata" 10 | "google.golang.org/grpc/status" 11 | // io" 12 | "time" 13 | ) 14 | 15 | /* 16 | type AgentToGameServer interface { 17 | // SessionManager 18 | AquireGameRoom(context.Context, *GameCreation) (*PemKey, error) 19 | } 20 | */ 21 | 22 | type ATGServer struct { 23 | } 24 | 25 | type CTGServer struct { 26 | } 27 | 28 | func (g *CTGServer) TimeCalibrate(c context.Context, e *Empty) (*TimeStamp, error) { 29 | log.Debug("Calibration") 30 | return &TimeStamp{Value: int64(time.Now().UnixNano() / 1000000)}, nil 31 | } 32 | 33 | func (g *ATGServer) AquireGameRoom(c context.Context, gc *GameCreation) (*PemKey, error) { 34 | err := session.RoomManager.CreateGame(gc) 35 | return &PemKey{SSL: "HI"}, err 36 | } 37 | 38 | func (g *ATGServer) DeletGameRoom(c context.Context, info *RoomInfo) (*Success, error) { 39 | err := session.RoomManager.DeleteRoom(info) 40 | if err != nil { 41 | return &Success{}, err 42 | } 43 | return &Success{Ok: true}, nil 44 | } 45 | 46 | func (g *CTGServer) PlayerInput(stream ClientToGame_PlayerInputServer) error { 47 | log.Debug("input") 48 | session := GetSesionFromContext(stream.Context()) 49 | if session == nil { 50 | return status.Errorf(codes.NotFound, "Session Not Found!") 51 | } 52 | LOOP2: 53 | for { 54 | select { 55 | default: 56 | input, err := stream.Recv() 57 | if HandleRPCError(session, err) { 58 | break LOOP2 59 | } 60 | session.State.HandleInput(input) 61 | } 62 | } 63 | 64 | return nil 65 | } 66 | func (g *CTGServer) UpdateGameFrame(e *Empty, stream ClientToGame_UpdateGameFrameServer) error { 67 | 68 | log.Debug("Frame") 69 | session := GetSesionFromContext(stream.Context()) 70 | if session == nil { 71 | return status.Errorf(codes.NotFound, "Session Not Found!") 72 | } 73 | msgch := session.GetMsgChan("GameFrame") 74 | if msgch == nil { 75 | return status.Errorf(codes.Internal, "GameFrame MsgChan Not Found!") 76 | } 77 | session.State.StartGame() 78 | LOOP: 79 | for { 80 | select { 81 | case <-msgch.StopSignal: 82 | break LOOP 83 | case msg := <-msgch.DataCh: 84 | err := stream.Send(msg.(*GameFrame)) 85 | if HandleRPCError(session, err) { 86 | break LOOP 87 | } 88 | } 89 | } 90 | 91 | return nil 92 | } 93 | func (g *CTGServer) Pipe(ClientToGame_PipeServer) error { 94 | return nil 95 | } 96 | 97 | func GetSesionFromContext(c context.Context) *session.Session { 98 | md, ok := metadata.FromIncomingContext(c) 99 | if !ok { 100 | return nil 101 | } 102 | s := session.Manager.GetSession(md) 103 | return s 104 | } 105 | 106 | func HandleRPCError(s *session.Session, e error) (IfEndStream bool) { 107 | if e == nil { 108 | return false 109 | } 110 | st, _ := status.FromError(e) 111 | log.Warn(st.Message()) 112 | switch st.Code() { 113 | case codes.Canceled: 114 | IfEndStream = ReconnectError(s) 115 | case codes.Unavailable: 116 | IfEndStream = ReconnectError(s) 117 | default: 118 | IfEndStream = RecordError(s, e) 119 | } 120 | return 121 | } 122 | 123 | func IgnoreError(s *session.Session) (IfEndStream bool) { 124 | return true 125 | } 126 | 127 | func RecordError(s *session.Session, e error) (IfEndStream bool) { 128 | log.Warn("RPCError:", e) 129 | EndConnection(s) 130 | return true 131 | } 132 | func ReconnectError(s *session.Session) (IfEndStream bool) { 133 | log.Warn("Wait to reconnect") 134 | s.State.WaitReconnect() 135 | return true 136 | } 137 | 138 | func EndConnection(s *session.Session) (IfEndStream bool) { 139 | s.State.EndConnection() 140 | return true 141 | } 142 | -------------------------------------------------------------------------------- /game/clientToGame.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | /* 4 | type ClientToGameServer interface { 5 | // roomManager 6 | EnterRoom(context.Context, *Empty) (*Success, error) 7 | LeaveRoom(context.Context, *Empty) (*Success, error) 8 | // entityManager 9 | PlayerInput(ClientToGame_PlayerInputServer) error 10 | // View 11 | UpdateGameFrame(*Empty, ClientToGame_UpdateGameFrameServer) error 12 | Pipe(ClientToGame_PipeServer) error 13 | } 14 | */ 15 | -------------------------------------------------------------------------------- /game/data/PhysicObj.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tank":{ 3 | "Name":"Tank", 4 | "Type":"Player", 5 | "Shape":"Box", 6 | "Lens":[1.8, 2, 1.8], 7 | "Mass":1.0 8 | }, 9 | "Shell":{ 10 | "Name":"Shell", 11 | "Type":"Skill", 12 | "Shape":"Capsule", 13 | "Lens":[0.15,0.55], 14 | "Mass":0.30 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /game/data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "encoding/json" 5 | log "github.com/sirupsen/logrus" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | type Obj struct { 11 | Name string 12 | Type string 13 | Shape string 14 | Lens []float64 15 | Mass float64 16 | } 17 | 18 | type Objs map[string]Obj 19 | 20 | func ReadObjData() Objs { 21 | dir := os.Getenv("GOPATH") 22 | raw, err := ioutil.ReadFile(dir + "/src/github.com/daniel840829/gameServer/data/PhysicObj.json") 23 | if err != nil { 24 | log.Fatal(err.Error()) 25 | os.Exit(1) 26 | } 27 | 28 | var c Objs 29 | json.Unmarshal(raw, &c) 30 | return c 31 | } 32 | 33 | var ObjData Objs 34 | 35 | func init() { 36 | log.SetFormatter(&log.TextFormatter{}) 37 | log.SetOutput(os.Stdout) 38 | log.SetLevel(log.DebugLevel) 39 | log.Info("{data}[init] Loading Data...") 40 | ObjData = ReadObjData() 41 | log.Info("{data}[init] ObjData: ", ObjData) 42 | } 43 | -------------------------------------------------------------------------------- /game/entity/attack.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | . "github.com/daniel840829/gameServer/msg" 5 | ) 6 | 7 | type AttackBehavier interface { 8 | Attack(FromPos Position, Value int64) 9 | AutoAttack(targetId int64) 10 | } 11 | 12 | type AttackBase struct { 13 | AutoAttackable bool 14 | CoolDownLeftTime int32 15 | MaxValue float64 16 | MaxCombo int32 17 | } 18 | 19 | func (atk *AttackBase) Attack(FromPos Position, Value float64) { 20 | 21 | } 22 | 23 | func (atk *AttackBase) AutoAttack(targetId int64) { 24 | 25 | } 26 | 27 | func NewShootBehavier(autoAttackable bool, coolDownLeftTime int32, maxValue float64, maxCombo int32) *ShootBehavier { 28 | return &ShootBehavier{ 29 | AttackBase: AttackBase{ 30 | AutoAttackable: autoAttackable, 31 | CoolDownLeftTime: coolDownLeftTime, 32 | MaxValue: maxValue, 33 | MaxCombo: maxCombo, 34 | }, 35 | } 36 | } 37 | 38 | type ShootBehavier struct { 39 | AttackBase 40 | } 41 | 42 | func (atk *ShootBehavier) Attack(FromPos Position, Value float64) { 43 | 44 | } 45 | 46 | func (atk *ShootBehavier) AutoAttack(targetId int64) { 47 | 48 | } 49 | -------------------------------------------------------------------------------- /game/entity/enemy.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | . "github.com/daniel840829/gameServer/msg" 5 | "github.com/daniel840829/gameServer/physic" 6 | "github.com/daniel840829/ode" 7 | "github.com/gazed/vu/math/lin" 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | type Enemy struct { 12 | Player 13 | CD int64 14 | } 15 | 16 | func (e *Enemy) PhysicUpdate() { 17 | e.Player.PhysicUpdate() 18 | } 19 | 20 | func (e *Enemy) Tick() { 21 | e.FindTargetAndAttack(30.0) 22 | } 23 | 24 | func (e *Enemy) FindTargetAndAttack(searchRadius float64) { 25 | e.CD += 1 26 | //var targetId int64 27 | var targetPos ode.Vector3 28 | var targetDis float64 = searchRadius 29 | var isFindTarget bool = false 30 | var isReadyToAttack bool = false 31 | //Loop obj in AOE 32 | for id, pos := range e.Obj.AOEObjs { 33 | //Check if it is real player 34 | if _, ok := e.Room.EntityOfUser[id]; ok { 35 | dis := physic.V3_OdeToLin(e.Obj.CBody.PosRelPoint(pos)).Len() 36 | if targetDis > dis && dis > 1 { 37 | targetDis = dis 38 | //targetId = id 39 | targetPos = pos 40 | isFindTarget = true 41 | if targetDis < 20 && e.CD > 100 { 42 | isReadyToAttack = true 43 | } 44 | } 45 | } 46 | } 47 | if isFindTarget { 48 | directionV3 := lin.NewV3().Sub(physic.V3_OdeToLin(targetPos), physic.V3_OdeToLin(e.Obj.CBody.Position())) 49 | targetQ := physic.Q_OdeToLin(physic.DirectionV3ToQuaternion(directionV3)) 50 | NowQ := physic.Q_OdeToLin(e.Obj.CBody.Quaternion()) 51 | if isReadyToAttack { 52 | e.Obj.CBody.SetQuaternion(physic.Q_LinToOde(targetQ)) 53 | e.Shoot(&CallFuncInfo{Value: 15}) 54 | e.CD = 0 55 | } else { 56 | MoveToQ := lin.NewQ().Nlerp(NowQ, targetQ, 0.3) 57 | log.Debugf("[Enemy]{Tick} m%v,targetQ:%v,NowQ:%v,MoveToQ:%v", targetQ, NowQ, MoveToQ) 58 | e.Obj.CBody.SetQuaternion(physic.Q_LinToOde(MoveToQ)) 59 | input := &Input{ 60 | V_Movement: float32(targetDis / 25.0), 61 | } 62 | e.Move(input) 63 | 64 | } 65 | } else { 66 | input := &Input{} 67 | e.Move(input) 68 | } 69 | e.Obj.ClearAOE() 70 | } 71 | -------------------------------------------------------------------------------- /game/entity/entity.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "fmt" 5 | . "github.com/daniel840829/gameServer/msg" 6 | "github.com/daniel840829/gameServer/physic" 7 | "github.com/golang/protobuf/proto" 8 | log "github.com/sirupsen/logrus" 9 | "os" 10 | "sync" 11 | ) 12 | 13 | func init() { 14 | // Log as JSON instead of the default ASCII formatter. 15 | log.SetFormatter(&log.TextFormatter{}) 16 | 17 | // Output to stdout instead of the default stderr 18 | // Can be any io.Writer, see below for File example 19 | log.SetOutput(os.Stdout) 20 | 21 | // Only log the warning severity or above. 22 | log.SetLevel(log.DebugLevel) 23 | } 24 | 25 | type Entity struct { 26 | sync.RWMutex 27 | EntityInfo *Character 28 | TypeName string 29 | Health float32 30 | Alive bool 31 | I IEntity 32 | GM *GameManager 33 | Room *Room 34 | World *physic.World 35 | Obj *physic.Obj 36 | Skill map[string]AttackBehavier 37 | } 38 | type IEntity interface { 39 | IGameBehavier 40 | Hit(int32) 41 | GetInfo() *Character 42 | Init(*GameManager, *Room, *Character) 43 | Move(in *Input) 44 | GetTransform() *TransForm 45 | Harm(blood float32) 46 | } 47 | 48 | func (e *Entity) GetInfo() *Character { 49 | e.RLock() 50 | entityInfo := proto.Clone(e.EntityInfo).(*Character) 51 | e.RUnlock() 52 | return entityInfo 53 | } 54 | func (e *Entity) Hit(damage int32) { 55 | fmt.Println("-", damage) 56 | } 57 | 58 | func (e *Entity) Harm(blood float32) { 59 | e.Lock() 60 | e.Health -= blood 61 | if e.Health <= 0 { 62 | //Dead 63 | e.Alive = false 64 | e.Unlock() 65 | e.Destroy() 66 | return 67 | } 68 | f := &CallFuncInfo{ 69 | Func: "Health", 70 | Value: e.Health, 71 | TargetId: e.EntityInfo.Uuid, 72 | } 73 | e.Room.SendFuncToAll(f) 74 | e.Unlock() 75 | } 76 | 77 | func (e *Entity) Init(gm *GameManager, room *Room, entityInfo *Character) { 78 | e.GM = gm 79 | e.EntityInfo = entityInfo 80 | e.Room = room 81 | e.World = room.World 82 | var ok bool 83 | e.Obj, ok = room.World.Objs.Get(entityInfo.Uuid) 84 | if !ok { 85 | log.Fatal("[entity]{init} Get obj ", entityInfo.Uuid, " is not found. ") 86 | } 87 | //call All client create enitity at some point 88 | e.costumeInit() 89 | } 90 | 91 | func (e *Entity) costumeInit() { 92 | log.Warn("Please define your costumeInit") 93 | } 94 | func (e *Entity) Tick() { 95 | } 96 | func (e *Entity) Destroy() { 97 | e.Lock() 98 | e.GM.DestroyEntity(e.EntityInfo.Uuid) 99 | e.Room.DestroyEntity(e.EntityInfo.Uuid) 100 | e.World.DeleteObj(e.EntityInfo.Uuid) 101 | e.Obj.Destroy() 102 | e.Obj = nil 103 | e.Unlock() 104 | e = nil 105 | } 106 | 107 | func (e *Entity) Run() { 108 | } 109 | func (e *Entity) PhysicUpdate() { 110 | } 111 | 112 | func (e *Entity) GetTransform() *TransForm { 113 | return &TransForm{} 114 | } 115 | func (e *Entity) Move(in *Input) { 116 | turnSpeed := e.EntityInfo.Ability.TSPD 117 | moveSpeed := e.EntityInfo.Ability.SPD 118 | moveValue := in.V_Movement 119 | turnValue := in.H_Movement 120 | e.Room.World.Move(e.EntityInfo.Uuid, float64(moveValue*moveSpeed), float64(turnValue*turnSpeed)) 121 | } 122 | 123 | /* 124 | type EntityInfo struct { 125 | //mathod's name map to Mathod's info 126 | MethodMap map[string]EntityMathod 127 | Type reflect.Type 128 | } 129 | 130 | type EntityMathod struct { 131 | Func reflect.Value 132 | Type reflect.Type 133 | Args int 134 | } 135 | type EntityManager struct { 136 | EntityTypeMap map[string]EntityInfo 137 | EntityIdMap map[uuid.UUID]reflect.Value 138 | } 139 | 140 | var eManager *EntityManager = &EntityManager{ 141 | EntityTypeMap: make(map[string]EntityInfo), 142 | EntityIdMap: make(map[uuid.UUID]reflect.Value), 143 | } 144 | 145 | func (em *EntityManager) Call(entityTypeName string, id uuid.UUID, fName string, args ...reflect.Value) { 146 | e, ok := em.EntityIdMap[id] 147 | eInfo, ok := em.EntityTypeMap[entityTypeName] 148 | if !ok { 149 | panic("Id not found") 150 | } 151 | 152 | f := eInfo.MethodMap[fName] 153 | fmt.Println("f:", f) 154 | in := make([]reflect.Value, f.Args) 155 | in[0] = e 156 | for i := 1; i < f.Args; i++ { 157 | in[i] = args[i-1] 158 | } 159 | f.Func.Call(in) 160 | } 161 | 162 | func (em *EntityManager) CreateEnitity(entityTypeName string, isClient bool) (id uuid.UUID) { 163 | entityInfo, ok := em.EntityTypeMap[entityTypeName] 164 | if !ok { 165 | fmt.Println(entityTypeName, "is not regist.") 166 | } 167 | vEntityPtr := reflect.New(entityInfo.Type) 168 | //check uuid repeat 169 | err := error(nil) 170 | id, err = uuid.NewV4() 171 | fmt.Println(id, err) 172 | for _, ok := em.EntityIdMap[id]; ok; { 173 | id, _ = uuid.NewV4() 174 | fmt.Println(id, err) 175 | } 176 | em.EntityIdMap[id] = vEntityPtr 177 | vEntityPtr.Elem().FieldByName("Id").Set(reflect.ValueOf(id)) 178 | vEntityPtr.Elem().FieldByName("TypeName").Set(reflect.ValueOf(vEntityPtr.Type().Elem().Name())) 179 | em.Call(entityTypeName, id, "Init") 180 | return 181 | } 182 | 183 | func RegisterEnitity(iEntity IEntity) { 184 | rEntity := reflect.ValueOf(iEntity) 185 | tEntity := rEntity.Type() 186 | entityName := tEntity.Elem().Name() 187 | rEntity.Elem().FieldByName("TypeName").Set(reflect.ValueOf(entityName)) 188 | fmt.Println("t:", tEntity, "v:", rEntity, "m:", rEntity.NumMethod()) 189 | entityInfo := &EntityInfo{MethodMap: make(map[string]EntityMathod)} 190 | entityInfo.Type = tEntity.Elem() 191 | for i := 0; i < rEntity.NumMethod(); i++ { 192 | m := tEntity.Method(i) 193 | em := EntityMathod{m.Func, m.Type, m.Type.NumIn()} 194 | entityInfo.MethodMap[tEntity.Method(i).Name] = em 195 | } 196 | fmt.Println(entityInfo) 197 | eManager.EntityTypeMap[entityName] = *entityInfo 198 | fmt.Println(eManager) 199 | } 200 | 201 | */ 202 | -------------------------------------------------------------------------------- /game/entity/entity_test.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestRegisterAndCall(*testing.T) { 9 | RegisterEnitity(&Player{}) 10 | id := eManager.CreateEnitity("Player", true) 11 | eManager.Call("Player", id, "Say", reflect.ValueOf("Yo")) 12 | id = eManager.CreateEnitity("Player", true) 13 | eManager.Call("Player", id, "Say", reflect.ValueOf("HI")) 14 | id = eManager.CreateEnitity("Player", true) 15 | eManager.Call("Player", id, "Say", reflect.ValueOf("HI")) 16 | } 17 | -------------------------------------------------------------------------------- /game/entity/gameManager.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | . "github.com/daniel840829/gameServer/msg" 5 | "github.com/daniel840829/gameServer/service" 6 | . "github.com/daniel840829/gameServer/uuid" 7 | "github.com/golang/protobuf/ptypes" 8 | any "github.com/golang/protobuf/ptypes/any" 9 | log "github.com/sirupsen/logrus" 10 | "reflect" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | type GameManager struct { 16 | ////basic information 17 | Uuid int64 18 | ////Reflection 19 | thisType reflect.Type 20 | ReflectFunc sync.Map 21 | ////child component 22 | //rpcCallableObject map[int64]reflect.Value 23 | //time calibration 24 | 25 | //room 26 | rl sync.RWMutex 27 | TypeMapRoom map[string]reflect.Type 28 | IdMapRoom map[int64]IRoom 29 | //entity 30 | el sync.RWMutex 31 | TypeMapEntity map[string]reflect.Type 32 | IdMapEntity map[int64]IEntity 33 | UserIdMapEntityId map[int64]int64 34 | UserIdMapRoomId sync.Map 35 | //Buffer 36 | 37 | ////rpc channel 38 | SendFuncToClient map[int64](chan *CallFuncInfo) 39 | RecvFuncFromClient chan *CallFuncInfo 40 | PosToClient map[int64](chan *Position) 41 | InputFromClient chan *Input 42 | ErrToClient map[int64](chan *Error) 43 | ErrFromClient chan *Error 44 | } 45 | 46 | func (gm *GameManager) Init(rpc *service.Rpc) { 47 | gm.Uuid, _ = Uid.NewId(GM_ID) 48 | gm.IdMapRoom = make(map[int64]IRoom) 49 | gm.TypeMapRoom = make(map[string]reflect.Type) 50 | gm.TypeMapEntity = make(map[string]reflect.Type) 51 | gm.IdMapEntity = make(map[int64]IEntity) 52 | gm.UserIdMapEntityId = make(map[int64]int64) 53 | gm.UserIdMapRoomId = sync.Map{} 54 | 55 | //gm.rpcCallableObject = make(map[int64]reflect.Value) 56 | gm.SendFuncToClient = rpc.SendFuncToClient 57 | gm.RecvFuncFromClient = rpc.RecvFuncFromClient 58 | gm.PosToClient = rpc.PosToClient 59 | gm.InputFromClient = rpc.InputFromClient 60 | gm.ErrFromClient = rpc.ErrFromClient 61 | gm.ErrToClient = rpc.ErrToClient 62 | gm.thisType = reflect.TypeOf(gm) 63 | gm.ReflectFunc = sync.Map{} 64 | for i := 0; i < gm.thisType.NumMethod(); i++ { 65 | f := gm.thisType.Method(i) 66 | gm.ReflectFunc.Store(f.Name, f) 67 | } 68 | } 69 | 70 | func (gm *GameManager) Run() { 71 | for { 72 | select { 73 | case f := <-gm.RecvFuncFromClient: 74 | log.Debug("[GM Call]", f) 75 | go gm.Call(f) 76 | case err := <-gm.ErrFromClient: 77 | gm.ErrorHandle(err) 78 | case input := <-gm.InputFromClient: 79 | gm.SyncPos(input) 80 | } 81 | } 82 | } 83 | 84 | func (gm *GameManager) Calibrate(f *CallFuncInfo) { 85 | f_send := &CallFuncInfo{} 86 | //client time 87 | f_send.TargetId = f.TimeStamp 88 | //recieve time 89 | f_send.FromId = int64(time.Now().UnixNano() / 1000000) 90 | f_send.Func = "Calibrate" 91 | gm.SendFuncToClient[f.FromId] <- f_send 92 | } 93 | 94 | func (gm *GameManager) RegistRoom(roomTypeName string, iRoom IRoom) { 95 | if _, ok := gm.TypeMapRoom[roomTypeName]; ok { 96 | log.Fatal(roomTypeName, "is already registed.") 97 | } 98 | vRoom := reflect.ValueOf(iRoom) 99 | gm.TypeMapRoom[roomTypeName] = vRoom.Type().Elem() 100 | //TODO : Record method info to speed up reflection invoke. 101 | } 102 | 103 | func (gm *GameManager) DestroyEntity(entityId int64) { 104 | delete(gm.IdMapEntity, entityId) 105 | } 106 | 107 | func (gm *GameManager) Call(f *CallFuncInfo) { 108 | log.Debug("Function INFO :", f) 109 | /** 110 | m, ok := gm.ReflectFunc.Load(f.Func) 111 | method := m.(reflect.Method) 112 | **/ 113 | method, ok := gm.thisType.MethodByName(f.Func) 114 | if !ok { 115 | log.Debug("[GM]{Call}gm does not have ", f.Func, " method ") 116 | return 117 | } 118 | param := make([]reflect.Value, 0) 119 | param = append(param, reflect.ValueOf(gm)) 120 | param = append(param, reflect.ValueOf(f)) 121 | method.Func.Call(param) 122 | /* 123 | targetType, _ := Uid.ParseId(f.TargetId) 124 | switch targetType { 125 | case ENTITY_ID: 126 | case EQUIP_ID: 127 | case GM_ID: 128 | case ROOM_ID: 129 | default: 130 | if targetType == "" { 131 | log.Warn(f.TargetId, " is not existed !") 132 | return 133 | } 134 | log.Warn(targetType, " is not callable!") 135 | } 136 | */ 137 | } 138 | 139 | func (gm *GameManager) UserDisconnect(f *CallFuncInfo) { 140 | userId := f.TargetId 141 | log.Debug("[GM]{UserDisconnect}", userId) 142 | v, ok := gm.UserIdMapRoomId.Load(userId) 143 | if !ok { 144 | return 145 | } 146 | roomId := v.(int64) 147 | room := gm.IdMapRoom[roomId] 148 | room.UserDisconnect(userId) 149 | } 150 | 151 | func (gm *GameManager) Entity(f *CallFuncInfo) { 152 | F := &CallFuncInfo{} 153 | ptypes.UnmarshalAny(f.Param[0], F) 154 | e, ok := gm.IdMapEntity[f.TargetId] 155 | if !ok { 156 | log.Warn("No Such Entity id", f.TargetId) 157 | return 158 | } 159 | entity := reflect.ValueOf(e) 160 | method, ok := entity.Type().MethodByName(F.Func) 161 | if !ok { 162 | log.Warn("entity does not have ", F.Func, " method ") 163 | return 164 | } 165 | param := make([]reflect.Value, 0) 166 | param = append(param, entity) 167 | param = append(param, reflect.ValueOf(F)) 168 | method.Func.Call(param) 169 | log.Debug("[gm]{Entity}call function", f) 170 | } 171 | 172 | func (gm *GameManager) ErrorHandle(err *Error) { 173 | log.Warn("Something Wrong", err) 174 | } 175 | 176 | func (gm *GameManager) SyncPos(input *Input) { 177 | //Deal with value 178 | entity, ok := gm.IdMapEntity[gm.UserIdMapEntityId[input.UserId]] 179 | if !ok { 180 | log.Warn("No Such Entity id", input.UserId) 181 | return 182 | } 183 | //TODO 184 | //if timestamp is too far from 185 | 186 | entity.Move(input) 187 | } 188 | 189 | func (gm *GameManager) CreateNewRoom(f *CallFuncInfo) { 190 | roomInfo := &RoomInfo{} 191 | err := ptypes.UnmarshalAny(f.Param[0], roomInfo) 192 | if err != nil { 193 | log.Warn("[*any Unmarshal Error]", f.Param[0]) 194 | return 195 | } 196 | tRoom, ok := gm.TypeMapRoom[roomInfo.GameType] 197 | if !ok { 198 | log.Warn(roomInfo.GameType, " is not registed yet. ") 199 | return 200 | } 201 | room, ok := reflect.New(tRoom).Interface().(IRoom) 202 | if !ok { 203 | log.Warn("Something Wrong with RegisterRoom") 204 | return 205 | } 206 | id, err := Uid.NewId(ROOM_ID) 207 | if err != nil { 208 | log.Fatal("Id generator error:", err) 209 | return 210 | } 211 | roomInfo.Uuid = id 212 | room.Init(gm, roomInfo) 213 | gm.rl.Lock() 214 | gm.IdMapRoom[id] = room 215 | gm.rl.Unlock() 216 | gm.getAllRoomInfo(f.FromId) 217 | } 218 | 219 | func (gm *GameManager) RegistEnitity(EntityTypeName string, iEntity IEntity) { 220 | if _, ok := gm.TypeMapEntity[EntityTypeName]; ok { 221 | log.Fatal(EntityTypeName, "is already registed.") 222 | } 223 | vEntity := reflect.ValueOf(iEntity) 224 | gm.TypeMapEntity[EntityTypeName] = vEntity.Type().Elem() 225 | 226 | } 227 | 228 | func (gm *GameManager) CreatePlayer(room *Room, entityType string, userInfo *UserInfo) IEntity { 229 | tEntity, ok := gm.TypeMapEntity[entityType] 230 | if !ok { 231 | log.Warn(entityType, " is not registed yet. ") 232 | return nil 233 | } 234 | entity, ok := reflect.New(tEntity).Interface().(IEntity) 235 | if !ok { 236 | log.Warn("Something Wrong with RegistEnitity") 237 | return nil 238 | } 239 | entityInfo := userInfo.OwnCharacter[userInfo.UsedCharacter] 240 | entity.Init(gm, room, entityInfo) 241 | 242 | gm.rl.Lock() 243 | gm.IdMapEntity[entityInfo.Uuid] = entity 244 | gm.UserIdMapEntityId[userInfo.Uuid] = entityInfo.Uuid 245 | gm.rl.Unlock() 246 | return entity 247 | } 248 | 249 | func (gm *GameManager) CreateEntity(room *Room, entityInfo *Character, entityType string) IEntity { 250 | tEntity, ok := gm.TypeMapEntity[entityType] 251 | if !ok { 252 | log.Warn(entityType, " is not registed yet. ") 253 | return nil 254 | } 255 | entity, ok := reflect.New(tEntity).Interface().(IEntity) 256 | if !ok { 257 | log.Warn("Something Wrong with RegistEnitity") 258 | return nil 259 | } 260 | entity.Init(gm, room, entityInfo) 261 | gm.rl.Lock() 262 | gm.IdMapEntity[entityInfo.Uuid] = entity 263 | gm.rl.Unlock() 264 | return entity 265 | 266 | } 267 | 268 | //Not Done Yet 269 | func (gm *GameManager) GetRoomStatus(f *CallFuncInfo) { 270 | //leftSecond := gm.IdMapRoom[f.TargetId].GetStatus() 271 | gm.SendFuncToClient[f.FromId] <- &CallFuncInfo{} 272 | } 273 | 274 | func (gm *GameManager) EnterRoom(f *CallFuncInfo) { 275 | log.Debug("{GM}[EnterRoom]Excute") 276 | if ok := gm.IdMapRoom[f.TargetId].EnterRoom(f.FromId); ok { 277 | gm.enterRoom(f.FromId, gm.IdMapRoom[f.TargetId].GetInfo()) 278 | gm.UserIdMapRoomId.Store(f.FromId, f.TargetId) 279 | } 280 | } 281 | 282 | func (gm *GameManager) GetAllRoomInfo(f *CallFuncInfo) { 283 | gm.getAllRoomInfo(f.TargetId) 284 | } 285 | 286 | func (gm *GameManager) LeaveRoom(f *CallFuncInfo) { 287 | id := gm.IdMapRoom[f.TargetId].LeaveRoom(f.FromId) 288 | f_send := &CallFuncInfo{ 289 | Func: "LeaveRoom", 290 | TargetId: id, 291 | } 292 | gm.SendFuncToClient[f.FromId] <- f_send 293 | } 294 | 295 | func (gm *GameManager) GetLoginData(f *CallFuncInfo) { 296 | gm.getLoginData(f.FromId) 297 | } 298 | 299 | func (gm *GameManager) ReadyRoom(f *CallFuncInfo) { 300 | log.Info("{GM}[ReadyRoom]User [", f.FromId, "] is ready in [", f.TargetId, "] Room") 301 | gm.IdMapRoom[f.TargetId].Ready(f.FromId) 302 | } 303 | 304 | ////Send Rpc command to client function 305 | func (gm *GameManager) getLoginData(userId int64) { 306 | // 307 | //b := &BasicType{} 308 | 309 | //param, _ := ptypes.MarshalAny(roomInfo) 310 | 311 | } 312 | 313 | func (gm *GameManager) enterRoom(userId int64, roomInfo *RoomInfo) { 314 | params := make([]*any.Any, 0) 315 | param, _ := ptypes.MarshalAny(roomInfo) 316 | params = append(params, param) 317 | f := &CallFuncInfo{} 318 | f.Func = "EnterRoom" 319 | f.Param = params 320 | gm.SendFuncToClient[userId] <- f 321 | } 322 | func (gm *GameManager) getAllRoomInfo(userId int64) { 323 | params := make([]*any.Any, 0) 324 | for _, room := range gm.IdMapRoom { 325 | roomInfo := room.GetInfo() 326 | param, _ := ptypes.MarshalAny(roomInfo) 327 | params = append(params, param) 328 | } 329 | gm.SendFuncToClient[userId] <- &CallFuncInfo{ 330 | Func: "GetAllRoomInfo", 331 | Param: params, 332 | } 333 | } 334 | 335 | func (gm *GameManager) getMyRoom(userId int64, roomInfo *RoomInfo) { 336 | param, _ := ptypes.MarshalAny(roomInfo) 337 | gm.SendFuncToClient[userId] <- &CallFuncInfo{ 338 | Func: "GetMyRoom", 339 | Param: append(make([]*any.Any, 0), param), 340 | } 341 | } 342 | 343 | func init() { 344 | 345 | } 346 | -------------------------------------------------------------------------------- /game/entity/player.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | . "github.com/daniel840829/gameServer/msg" 5 | "github.com/daniel840829/gameServer/physic" 6 | . "github.com/daniel840829/gameServer/uuid" 7 | "github.com/gazed/vu/math/lin" 8 | log "github.com/sirupsen/logrus" 9 | "math" 10 | ) 11 | 12 | type Player struct { 13 | Entity 14 | } 15 | 16 | func (e *Player) Init(gm *GameManager, room *Room, entityInfo *Character) { 17 | e.Entity.Init(gm, room, entityInfo) 18 | e.Health = e.EntityInfo.MaxHealth 19 | log.Info("player's costumeInit") 20 | } 21 | 22 | func (e *Player) Shoot(f *CallFuncInfo) { 23 | log.Debug("[entity]{Shoot}", f) 24 | //create shell 25 | p, q := e.World.GetTransform(e.EntityInfo.Uuid) 26 | muzzle := lin.NewV3().MultQ(lin.NewV3S(0, 1.1, 1.8), q) 27 | p.Add(p, muzzle) 28 | id, _ := Uid.NewId(ENTITY_ID) 29 | e.World.CreateEntity("Shell", id, *p, *q) 30 | entityInfo := &Character{} 31 | entityInfo.Uuid = id 32 | entityInfo.CharacterType = "Shell" 33 | log.Debug("{Player}[Shoot]", entityInfo) 34 | e.World.Move(id, float64(f.Value/10), 0.0) 35 | entity := e.GM.CreateEntity(e.Room, entityInfo, "Shell") 36 | e.Room.CreateShell(entity, entityInfo, physic.V3_LinToMsg(p), physic.Q_LinToMsg(q)) 37 | //set Velocity 38 | } 39 | 40 | func (e *Player) PhysicUpdate() { 41 | q := e.Obj.CBody.Quaternion() 42 | rot := e.Obj.CBody.AngularVel() 43 | q[1] = 0.0 44 | q[2] = 0.0 45 | len := math.Sqrt(q[0]*q[0] + q[3]*q[3]) 46 | q[0] /= len 47 | q[3] /= len 48 | rot[0] = 0.0 49 | rot[1] = 0.0 50 | e.Obj.CBody.SetQuaternion(q) 51 | e.Obj.CBody.SetAngularVelocity(rot) 52 | e.Obj.SyncAOEPos() 53 | } 54 | -------------------------------------------------------------------------------- /game/entity/room.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | . "github.com/daniel840829/gameServer/msg" 5 | "github.com/daniel840829/gameServer/physic" 6 | "github.com/daniel840829/gameServer/user" 7 | . "github.com/daniel840829/gameServer/uuid" 8 | "github.com/gazed/vu/math/lin" 9 | "github.com/golang/protobuf/proto" 10 | "github.com/golang/protobuf/ptypes" 11 | any "github.com/golang/protobuf/ptypes/any" 12 | log "github.com/sirupsen/logrus" 13 | "math/rand" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | const ( 19 | FRAME_INTERVAL = 60 * time.Millisecond 20 | PHYSIC_UPDATE_INTERVAL = 10 * time.Millisecond 21 | ) 22 | 23 | type IGameBehavier interface { 24 | Tick() 25 | PhysicUpdate() 26 | Destroy() 27 | Run() 28 | } 29 | 30 | type IRoom interface { 31 | IGameBehavier 32 | Init(*GameManager, *RoomInfo) 33 | //CreateEnitity() 34 | UserDisconnect(userId int64) 35 | UserReconnect(userId int64) 36 | GetInfo() *RoomInfo 37 | EnterRoom(int64) bool 38 | LeaveRoom(int64) int64 39 | Ready(int64) bool 40 | GetUserInRoom() []int64 41 | } 42 | 43 | type Room struct { 44 | RoomInfo *RoomInfo 45 | sync.RWMutex 46 | EntityInRoom map[int64]IEntity 47 | GM *GameManager 48 | EntityOfUser map[int64]int64 //map[id]id 49 | World *physic.World 50 | PosChans map[int64](chan *Position) 51 | FuncChans map[int64](chan *CallFuncInfo) 52 | roomEndChan chan (struct{}) 53 | } 54 | 55 | func (r *Room) UserDisconnect(userId int64) { 56 | entity, ok := r.EntityInRoom[r.GM.UserIdMapEntityId[userId]] 57 | if !ok { 58 | log.Info("Entity of User[", userId, "] is not created yet.") 59 | 60 | } else { 61 | 62 | entity.Destroy() 63 | } 64 | r.LeaveRoom(userId) 65 | if len(r.GetInfo().UserInRoom) == 0 { 66 | r.Destroy() 67 | } 68 | } 69 | 70 | func (r *Room) UserReconnect(userId int64) { 71 | //TODO 72 | } 73 | 74 | func (r *Room) Init(gm *GameManager, roomInfo *RoomInfo) { 75 | r.World = &physic.World{} 76 | r.roomEndChan = make(chan (struct{})) 77 | r.GM = gm 78 | r.EntityInRoom = make(map[int64]IEntity) 79 | r.EntityOfUser = make(map[int64]int64) 80 | r.PosChans = make(map[int64](chan *Position), 0) 81 | r.FuncChans = make(map[int64](chan *CallFuncInfo), 0) 82 | r.Lock() 83 | r.RoomInfo = roomInfo 84 | r.World.Init(r.RoomInfo.Uuid) 85 | r.RoomInfo.ReadyUser = make(map[int64]bool) 86 | r.RoomInfo.UserInRoom = make(map[int64]*UserInfo) 87 | r.Unlock() 88 | log.Info("[", roomInfo.Uuid, "]Room is Create: ", roomInfo) 89 | go r.Run() 90 | } 91 | 92 | func (r *Room) Tick() { 93 | //Syncpostion 94 | //callfuncinfo 95 | //Game Logic 96 | r.GetAllTransform() 97 | for _, entity := range r.EntityInRoom { 98 | entity.Tick() 99 | } 100 | } 101 | func (r *Room) Destroy() { 102 | log.Debug("[Room]{Destroy}") 103 | r.Lock() 104 | r.roomEndChan <- struct{}{} 105 | r.World.Destroy() 106 | r.Unlock() 107 | r = nil 108 | } 109 | 110 | func (r *Room) DestroyEntity(id int64) { 111 | r.Lock() 112 | delete(r.EntityInRoom, id) 113 | if _, ok := r.EntityOfUser[id]; ok { 114 | delete(r.EntityOfUser, id) 115 | } 116 | f := &CallFuncInfo{} 117 | f.Func = "DestroyEntity" 118 | f.TargetId = id 119 | r.SendFuncToAll(f) 120 | r.Unlock() 121 | } 122 | func (r *Room) GetInfo() *RoomInfo { 123 | r.RLock() 124 | roomInfo, _ := proto.Clone(r.RoomInfo).(*RoomInfo) 125 | r.RUnlock() 126 | return roomInfo 127 | } 128 | 129 | func (r *Room) Ready(userId int64) bool { 130 | log.Debug("{Room}[Ready]:", userId, " is ready ") 131 | r.Lock() 132 | r.RoomInfo.ReadyUser[userId] = true 133 | r.Unlock() 134 | return true 135 | } 136 | func (r *Room) EnterRoom(userId int64) bool { 137 | r.Lock() 138 | if _, ok := r.RoomInfo.UserInRoom[userId]; ok { 139 | r.Unlock() 140 | log.Debug("User", userId, " is already in room") 141 | return false 142 | } 143 | r.PosChans[userId] = r.GM.PosToClient[userId] 144 | r.FuncChans[userId] = r.GM.SendFuncToClient[userId] 145 | r.RoomInfo.UserInRoom[userId] = user.Manager.GetUserInfo(userId) 146 | r.RoomInfo.ReadyUser[userId] = false 147 | log.Debug("{Room}[EnterRoom]", r.RoomInfo.UserInRoom) 148 | r.Unlock() 149 | return true 150 | } 151 | 152 | func (r *Room) LeaveRoom(userId int64) int64 { 153 | r.Lock() 154 | log.Debug("[room]{LeaveRoom}") 155 | delete(r.RoomInfo.UserInRoom, userId) 156 | delete(r.RoomInfo.ReadyUser, userId) 157 | delete(r.FuncChans, userId) 158 | delete(r.PosChans, userId) 159 | r.Unlock() 160 | return r.RoomInfo.Uuid 161 | } 162 | func (r *Room) Run() { 163 | //Read Info 164 | allReady := false 165 | for !allReady { 166 | r.RLock() 167 | for _, ready := range r.RoomInfo.ReadyUser { 168 | if !ready { 169 | allReady = false 170 | break 171 | } else { 172 | allReady = true 173 | } 174 | } 175 | r.RUnlock() 176 | <-time.After(time.Millisecond) 177 | } 178 | log.Debug("{room}[Run]:start") 179 | r.createPlayers() 180 | r.CreateEnemies() 181 | r.start() 182 | physicUpdate := time.NewTicker(PHYSIC_UPDATE_INTERVAL) 183 | frameUpdate := time.NewTicker(FRAME_INTERVAL) 184 | for { 185 | select { 186 | case <-frameUpdate.C: 187 | r.Tick() 188 | case <-physicUpdate.C: 189 | r.PhysicUpdate() 190 | case <-r.roomEndChan: 191 | return 192 | } 193 | } 194 | } 195 | 196 | func (r *Room) GetUserInRoom() (ids []int64) { 197 | r.RLock() 198 | for id, _ := range r.RoomInfo.UserInRoom { 199 | ids = append(ids, id) 200 | } 201 | r.RUnlock() 202 | return 203 | } 204 | 205 | func (r *Room) GetAllTransform() { 206 | //t1 := time.Now().UnixNano() 207 | pos := r.World.GetAllTransform() 208 | //log.Debug("[room]{GetAllTransform}GetAllTransform time:", (time.Now().UnixNano() - t1)) 209 | pos.TimeStamp = int64(time.Now().UnixNano() / 1000000) 210 | go func() { 211 | for _, posChan := range r.PosChans { 212 | posChan <- pos 213 | } 214 | }() 215 | } 216 | 217 | func (r *Room) SendFuncToAll(f *CallFuncInfo) { 218 | 219 | go func() { 220 | for _, funcChan := range r.FuncChans { 221 | funcChan <- f 222 | } 223 | }() 224 | } 225 | 226 | func (r *Room) CreateEnemies() { 227 | log.Debug("[Room]{CreateEnemies}") 228 | for i := 0; i < 1; i++ { 229 | r.CreateEnemy() 230 | } 231 | } 232 | 233 | func (r *Room) CreateEnemy() { 234 | r.Lock() 235 | log.Debug("[Room]{CreateEnemy}") 236 | entityInfo := &Character{ 237 | MaxHealth: 100.0, 238 | CharacterType: "Enemy", 239 | Color: &Color{ 240 | B: 255.0, 241 | R: 0.0, 242 | G: 0.0, 243 | }, 244 | Ability: &Ability{ 245 | SPD: 0.5, 246 | TSPD: 1.0, 247 | }, 248 | } 249 | entityInfo.Uuid, _ = Uid.NewId(ENTITY_ID) 250 | q := physic.EulerToQuaternion(0.0, 0.0, 0.0) 251 | p := lin.NewV3S((rand.Float64()*64 - 32), (rand.Float64()*64 - 32), 1.0) 252 | r.World.CreateEntity("Tank", entityInfo.Uuid, *p, *q) 253 | entity := r.GM.CreateEntity(r, entityInfo, "Enemy") 254 | if entity == nil { 255 | return 256 | } 257 | r.createEntity(entity, p, q) 258 | r.Unlock() 259 | } 260 | func (r *Room) createPlayers() { 261 | r.RLock() 262 | for id, userInfo := range r.RoomInfo.UserInRoom { 263 | log.Debug("[RoomInfo.UserInRoom]", id, userInfo) 264 | if userInfo.UsedCharacter == int64(0) { 265 | for id, _ := range userInfo.OwnCharacter { 266 | userInfo.UsedCharacter = id 267 | break 268 | } 269 | } 270 | entityInfo := userInfo.OwnCharacter[userInfo.UsedCharacter] 271 | q := physic.EulerToQuaternion(0.0, 0.0, 0.0) 272 | p := lin.NewV3S((rand.Float64()*64 - 32), (rand.Float64()*64 - 32), 1.0) 273 | r.World.CreateEntity("Tank", entityInfo.Uuid, *p, *q) 274 | entity := r.GM.CreatePlayer(r, "Player", userInfo) 275 | if entity == nil { 276 | return 277 | } 278 | r.EntityOfUser[entityInfo.Uuid] = userInfo.Uuid 279 | r.createEntity(entity, p, q) 280 | } 281 | r.RUnlock() 282 | } 283 | 284 | func (r *Room) createEntity(iEntity IEntity, position *lin.V3, quaternion *lin.Q) { 285 | entityInfo := iEntity.GetInfo() 286 | //r.Lock() 287 | r.EntityInRoom[entityInfo.Uuid] = iEntity 288 | 289 | f := &CallFuncInfo{} 290 | f.Func = "CreateEntity" 291 | f.FromPos = &TransForm{Position: physic.V3_LinToMsg(position), Rotation: physic.Q_LinToMsg(quaternion)} 292 | params := make([]*any.Any, 0) 293 | param, _ := ptypes.MarshalAny(entityInfo) 294 | params = append(params, param) 295 | f.Param = params 296 | r.SendFuncToAll(f) //r.Unlock() 297 | } 298 | 299 | //TODO : These two function should be combined 300 | func (r *Room) CreateShell(iEntity IEntity, entityInfo *Character, p *Vector3, q *Quaternion) { 301 | //send msg to all 302 | r.EntityInRoom[entityInfo.Uuid] = iEntity 303 | 304 | f := &CallFuncInfo{} 305 | f.Func = "CreateShell" 306 | f.FromPos = &TransForm{Position: p, Rotation: q} 307 | param, _ := ptypes.MarshalAny(entityInfo) 308 | params := make([]*any.Any, 0) 309 | params = append(params, param) 310 | f.Param = params 311 | r.SendFuncToAll(f) 312 | } 313 | 314 | func (r *Room) start() { 315 | f := &CallFuncInfo{} 316 | f.Func = "StartRoom" 317 | r.RLock() 318 | for id, _ := range r.RoomInfo.UserInRoom { 319 | r.GM.SendFuncToClient[id] <- f 320 | } 321 | r.RUnlock() 322 | } 323 | func (r *Room) PhysicUpdate() { 324 | //r.Lock() 325 | r.World.PhysicUpdate() 326 | for _, entity := range r.EntityInRoom { 327 | entity.PhysicUpdate() 328 | } 329 | //r.Lock() 330 | } 331 | -------------------------------------------------------------------------------- /game/entity/shell.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/daniel840829/gameServer/physic" 5 | "github.com/daniel840829/ode" 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | const ( 10 | ExplosionRadius = 5.0 11 | ) 12 | 13 | type Shell struct { 14 | Entity 15 | State int 16 | //0 : not explosion 17 | //1 : explode 18 | } 19 | 20 | func (s *Shell) PhysicUpdate() { 21 | objData := s.Obj.GetData() 22 | //log.Debug("[Shell]CollideTimes", objData.CollideTimes) 23 | switch s.State { 24 | case 0: 25 | if objData.CollideTimes > 0 { 26 | s.Obj.ResetCollide() 27 | //Send explosion 28 | shellGeom := s.Obj.CGeom 29 | q := shellGeom.Quaternion() 30 | p := shellGeom.Position() 31 | shellGeom.Destroy() 32 | s.Obj.CBody.Destroy() 33 | CGeom := s.World.Space.NewSphere(ExplosionRadius) 34 | CGeom.SetData(s.Obj) 35 | CGeom.SetCategoryBits(0) 36 | CGeom.SetCollideBits(physic.SetBitExcept(physic.SetAllBits(), physic.Skill_Bit, physic.Terrain_Bit)) 37 | s.Obj.CGeom = CGeom 38 | body := s.World.World.NewBody() 39 | body.SetKinematic(true) 40 | body.SetPosition(p) 41 | body.SetQuaternion(q) 42 | body.SetLinearVelocity(ode.NewVector3(0.0, 0.0, 0.0)) 43 | body.SetAngularVelocity(ode.NewVector3(0.0, 0.0, 0.0)) 44 | body.SetGravityEnabled(false) 45 | s.Obj.CBody = body 46 | s.Obj.CGeom.SetBody(body) 47 | s.State = 1 48 | } 49 | case 1: 50 | log.Debug("[Shell]{explode}") 51 | f := func(obj *physic.Obj, times int64) bool { 52 | log.Debug(obj.GetData(), " is damage ", times) 53 | //id := obj.GetData().Uuid 54 | id := obj.GetData().Uuid 55 | entity, ok := s.Room.EntityInRoom[id] 56 | if !ok { 57 | log.Debug("Something Unkown harm") 58 | return true 59 | } 60 | entity.Harm(10) 61 | return true 62 | } 63 | s.Obj.LoopCollideObj(f) 64 | s.Obj.ResetCollide() 65 | s.State = 2 66 | default: 67 | s.Destroy() 68 | break 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /game/entity/treasure.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type Treasure struct { 4 | Entity 5 | lifeTime int64 6 | } 7 | 8 | func (t *Treasure) Tick() { 9 | t.Disapear() 10 | } 11 | 12 | func (t *Treasure) Disapear() { 13 | t.lifeTime++ 14 | if t.lifeTime > 1000 { 15 | t.Destroy() 16 | } 17 | } 18 | 19 | func (t *Treasure) BeCollected() { 20 | 21 | } 22 | 23 | func (t *Treasure) BeAttracted() { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /game/hmap/hmap.go: -------------------------------------------------------------------------------- 1 | // Command go-heightmap provides a basic Square Diamond heightmap / terrain generator. 2 | package hmap 3 | 4 | import ( 5 | "flag" 6 | "image" 7 | "image/color" 8 | "image/png" 9 | "math/rand" 10 | "os" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | optSize := flag.Int("size", 1024, "Image size, must be power of two.") 16 | optSamples := flag.Int("samples", 128, "Number of samples. Lower is more rough terrain. Must be power of two.") 17 | optBlur := flag.Int("blur", 2, "Blur/Smooth size. Somewhere between 1 and 5.") 18 | optScale := flag.Float64("scale", 1.0, "Scale. Most lileky 1.0") 19 | optOut := flag.String("o", "", "Output png filename.") 20 | flag.Parse() 21 | 22 | if *optOut == "" { 23 | flag.Usage() 24 | os.Exit(1) 25 | } 26 | 27 | h := NewHeightmap(*optSize) 28 | h.generate(*optSamples, *optScale) 29 | h.normalize() 30 | h.blur(*optBlur) 31 | h.png(*optOut) 32 | } 33 | 34 | // Heightmap generates a heightmap based on the Square Diamond algorithm. 35 | type Heightmap struct { 36 | random *rand.Rand 37 | points []float64 38 | width, height int 39 | } 40 | 41 | type generator interface { 42 | Generate() 43 | } 44 | 45 | // NewHeightmap initializes a new Heightmap using the specified size. 46 | func NewHeightmap(size int) *Heightmap { 47 | h := &Heightmap{} 48 | h.random = rand.New(rand.NewSource(time.Now().Unix())) 49 | h.points = make([]float64, size*size) 50 | h.width = size 51 | h.height = size 52 | 53 | // init/randomize 54 | for x := 0; x < h.width; x++ { 55 | for y := 0; y < h.height; y++ { 56 | h.set(x, y, h.frand()) 57 | } 58 | } 59 | return h 60 | } 61 | 62 | func (h *Heightmap) png(fname string) { 63 | rect := image.Rect(0, 0, h.width, h.height) 64 | img := image.NewGray16(rect) 65 | 66 | for x := 0; x < h.width; x++ { 67 | for y := 0; y < h.height; y++ { 68 | val := h.get(x, y) 69 | col := color.Gray16{uint16(val * 0xffff)} 70 | img.Set(x, y, col) 71 | } 72 | } 73 | 74 | f, err := os.OpenFile(fname, os.O_CREATE|os.O_WRONLY, 0644) 75 | if err != nil { 76 | panic(err) 77 | } 78 | defer f.Close() 79 | 80 | err = png.Encode(f, img) 81 | if err != nil { 82 | panic(err) 83 | } 84 | } 85 | 86 | func (h *Heightmap) normalize() { 87 | var min = 1.0 88 | var max = 0.0 89 | 90 | for i := 0; i < h.width*h.height; i++ { 91 | if h.points[i] < min { 92 | min = h.points[i] 93 | } 94 | if h.points[i] > max { 95 | max = h.points[i] 96 | } 97 | } 98 | rat := max - min 99 | for i := 0; i < h.width*h.height; i++ { 100 | h.points[i] = (h.points[i] - min) / rat 101 | } 102 | } 103 | 104 | func (h *Heightmap) blur(size int) { 105 | for x := 0; x < h.width; x++ { 106 | for y := 0; y < h.height; y++ { 107 | count := 0 108 | total := 0.0 109 | 110 | for x0 := x - size; x0 <= x+size; x0++ { 111 | for y0 := y - size; y0 <= y+size; y0++ { 112 | total += h.get(x0, y0) 113 | count++ 114 | } 115 | } 116 | if count > 0 { 117 | h.set(x, y, total/float64(count)) 118 | } 119 | } 120 | } 121 | } 122 | 123 | func (h *Heightmap) frand() float64 { 124 | return (h.random.Float64() * 2.0) - 1.0 125 | } 126 | 127 | func (h *Heightmap) get(x, y int) float64 { 128 | return h.points[(x&(h.width-1))+((y&(h.height-1))*h.width)] 129 | } 130 | 131 | func (h *Heightmap) set(x, y int, val float64) { 132 | h.points[(x&(h.width-1))+((y&(h.height-1))*h.width)] = val 133 | } 134 | 135 | func (h *Heightmap) generate(samples int, scale float64) { 136 | for samples > 0 { 137 | h.squarediamond(samples, scale) 138 | samples /= 2 139 | scale /= 2.0 140 | } 141 | } 142 | 143 | func (h *Heightmap) squarediamond(step int, scale float64) { 144 | half := step / 2 145 | for y := half; y < h.height+half; y += step { 146 | for x := half; x < h.width+half; x += step { 147 | h.square(x, y, step, h.frand()*scale) 148 | } 149 | } 150 | for y := 0; y < h.height; y += step { 151 | for x := 0; x < h.width; x += step { 152 | h.diamond(x+half, y, step, h.frand()*scale) 153 | h.diamond(x, y+half, step, h.frand()*scale) 154 | } 155 | } 156 | } 157 | 158 | func (h *Heightmap) square(x, y, size int, val float64) { 159 | half := size / 2 160 | a := h.get(x-half, y-half) 161 | b := h.get(x+half, y-half) 162 | c := h.get(x-half, y+half) 163 | d := h.get(x+half, y+half) 164 | h.set(x, y, ((a+b+c+d)/4.0)+val) 165 | } 166 | 167 | func (h *Heightmap) diamond(x, y, size int, val float64) { 168 | half := size / 2 169 | a := h.get(x-half, y) 170 | b := h.get(x+half, y) 171 | c := h.get(x, y-half) 172 | d := h.get(x, y+half) 173 | h.set(x, y, ((a+b+c+d)/4.0)+val) 174 | } 175 | -------------------------------------------------------------------------------- /game/physic/physic.go: -------------------------------------------------------------------------------- 1 | package physic 2 | 3 | // #include 4 | import "C" 5 | 6 | import ( 7 | "github.com/daniel840829/gameServer/data" 8 | . "github.com/daniel840829/gameServer/msg" 9 | "github.com/daniel840829/ode" 10 | "github.com/gazed/vu/math/lin" 11 | "github.com/golang/protobuf/proto" 12 | log "github.com/sirupsen/logrus" 13 | "math" 14 | "strconv" 15 | "sync" 16 | ) 17 | 18 | // ObjCategory 19 | const ( 20 | Player_Bit = iota 21 | Enemy_Bit 22 | Skill_Bit 23 | Terrain_Bit 24 | Specified_Bit 25 | AOE_Bit 26 | TREASURE_Bit 27 | TeamA_Bit 28 | TeamB_Bit 29 | TeamC_Bit 30 | TeamD_Bit 31 | ) 32 | 33 | const ( 34 | No_Team_Mode = iota 35 | Team_Mode 36 | ) 37 | 38 | func SetBits(b int, bits ...uint) int { 39 | for _, bit := range bits { 40 | b |= 1 << bit 41 | } 42 | return b 43 | } 44 | 45 | func SetBitExcept(b int, bits ...uint) int { 46 | for _, bit := range bits { 47 | b &= ^(1 << bit) 48 | } 49 | return b 50 | } 51 | 52 | func SetAllBits() int { 53 | return 65535 54 | } 55 | 56 | type World struct { 57 | sync.RWMutex 58 | World ode.World 59 | Space ode.Space 60 | Floor ode.Plane 61 | CtGrp ode.JointGroup 62 | Objs Objs 63 | Cb func(data interface{}, obj1, obj2 ode.Geom) 64 | } 65 | 66 | type WorldData struct { 67 | Cb func(data interface{}, obj1, obj2 ode.Geom) 68 | CtGrp ode.JointGroup 69 | } 70 | 71 | func (w *World) Init(roomId int64) { 72 | w.World = ode.NewWorld() 73 | ctGrp := ode.NewJointGroup(100000) 74 | var cb CollideCallback 75 | cb = GetCollideHandler(w) 76 | w.Cb = cb 77 | w.World.SetData(&WorldData{Cb: GetCollideHandler(w), CtGrp: ctGrp}) 78 | w.Space = ode.NilSpace().NewHashSpace() 79 | w.CreateTerrain() 80 | w.Space.SetSublevel(0) 81 | w.World.SetGravity(ode.V3(0, 0, -9.8)) 82 | w.CtGrp = ctGrp 83 | } 84 | 85 | func (w *World) Destroy() { 86 | w.Lock() 87 | w.World.Destroy() 88 | w.CtGrp.Destroy() 89 | w.Cb = nil 90 | w.Unlock() 91 | w = nil 92 | } 93 | 94 | func (w *World) CreateTerrain() { 95 | plane := w.Space.NewPlane(ode.V4(0, 0, 1, 0)) 96 | plane.SetCategoryBits(SetBits(0, Terrain_Bit)) 97 | plane.SetCollideBits(SetBitExcept(SetAllBits(), AOE_Bit)) 98 | obj := &Obj{ 99 | CGeom: plane, 100 | Data: &ObjData{Name: "Floor", Type: "Terrain"}, 101 | CollideObjs: make(map[*Obj]int64), 102 | } 103 | obj.AddGeom(plane) 104 | wallVector := [][]float64{ 105 | {1, 0, 0, -40}, 106 | {-1, 0, 0, -40}, 107 | {0, 1, 0, -40}, 108 | {0, -1, 0, -40}, 109 | } 110 | for i := 0; i < 4; i++ { 111 | v4 := wallVector[i] 112 | wall := w.Space.NewPlane(ode.V4(v4...)) 113 | wall.SetCategoryBits(SetBits(0, Terrain_Bit)) 114 | wall.SetCollideBits(SetBitExcept(SetAllBits(), AOE_Bit)) 115 | obj := &Obj{ 116 | CGeom: wall, 117 | Data: &ObjData{Name: "Wall" + strconv.Itoa(i), Type: "Terrain"}, 118 | CollideObjs: make(map[*Obj]int64), 119 | } 120 | obj.AddGeom(wall) 121 | } 122 | 123 | } 124 | 125 | type CollideCallback func(data interface{}, obj1, obj2 ode.Geom) 126 | 127 | func GetCollideHandler(w *World) func(data interface{}, obj1, obj2 ode.Geom) { 128 | var cb func(data interface{}, obj1, obj2 ode.Geom) 129 | cb = func(data interface{}, obj1, obj2 ode.Geom) { 130 | body1, body2 := obj1.Body(), obj2.Body() 131 | var world ode.World 132 | //log.Debug("GetCollideHandler", body1, " and ", body2) 133 | if body1 == 0 && body2 == 0 { 134 | return 135 | } else if body1 == 0 { 136 | world = body2.World() 137 | } else { 138 | world = body1.World() 139 | } 140 | if (obj1.IsSpace()) || (obj2.IsSpace()) { 141 | spaceCallback := world.Data().(*WorldData).Cb 142 | obj1.Collide2(obj2, data, spaceCallback) 143 | // if need to traverses through all spaces and sub-spaces 144 | if obj1.IsSpace() { 145 | obj1.ToSpace().Collide(data, spaceCallback) 146 | } 147 | if obj2.IsSpace() { 148 | obj2.ToSpace().Collide(data, spaceCallback) 149 | } 150 | } else { 151 | cat1, cat2 := obj1.CategoryBits(), obj2.CategoryBits() 152 | col1, col2 := obj1.CollideBits(), obj2.CollideBits() 153 | if ((cat1 & col2) | (cat2 & col1)) != 0 { 154 | if cat1 == SetBits(0, AOE_Bit) || cat2 == SetBits(0, AOE_Bit) { 155 | cts := obj1.Collide(obj2, 1, 0) 156 | if len(cts) > 0 { 157 | d1, d2 := obj1.Data().(*Obj), obj2.Data().(*Obj) 158 | if d1 == d2 { 159 | return 160 | } else { 161 | var p ode.Vector3 162 | if cat1&SetBits(0, AOE_Bit) != 0 { 163 | p = body2.Position() 164 | d1.InAOE(d2.GetData().Uuid, p) 165 | } else { 166 | p = body1.Position() 167 | d2.InAOE(d1.GetData().Uuid, p) 168 | } 169 | } 170 | } 171 | } else { 172 | ctGrp := world.Data().(*WorldData).CtGrp 173 | //log.Debug("Body1:", body1.Data(), "\n\rBody2:", body2.Data()) 174 | contact := ode.NewContact() 175 | contact.Surface.Mode = 0 176 | contact.Surface.Mu = 0.1 177 | contact.Surface.Mu2 = 0 178 | cts := obj1.Collide(obj2, 1, 0) 179 | if len(cts) > 0 { 180 | d1, d2 := obj1.Data().(*Obj), obj2.Data().(*Obj) 181 | if d1 == d2 { 182 | log.Info("CollideHandle right") 183 | } else { 184 | d1.Collide(d2) 185 | d2.Collide(d1) 186 | contact.Geom = cts[0] 187 | ct := world.NewContactJoint(ctGrp, contact) 188 | ct.Attach(body1, body2) 189 | 190 | } 191 | } 192 | } 193 | } 194 | } 195 | } 196 | return cb 197 | } 198 | func (w *World) PhysicUpdate() { 199 | w.Lock() 200 | w.Space.Collide(0, w.Cb) 201 | w.World.Step(0.01) 202 | w.CtGrp.Empty() 203 | w.Unlock() 204 | } 205 | 206 | func (w *World) GetAllTransform() (pos *Position) { 207 | pos = &Position{} 208 | pos.PosMap = make(map[int64]*TransForm) 209 | f := func(key interface{}, value interface{}) bool { 210 | k := key.(int64) 211 | v := value.(*Obj).CBody 212 | p := V3_OdeToMsg(v.Position()) 213 | q := Q_OdeToMsg(v.Quaternion()) 214 | t := &TransForm{proto.Clone(p).(*Vector3), proto.Clone(q).(*Quaternion)} 215 | pos.PosMap[k] = t 216 | return true 217 | } 218 | w.Lock() 219 | w.Objs.Range(f) 220 | w.Unlock() 221 | return 222 | } 223 | 224 | func (w *World) GetTransform(id int64) (p *lin.V3, q *lin.Q) { 225 | w.Lock() 226 | obj, ok := w.Objs.Get(id) 227 | body := obj.CBody 228 | if !ok { 229 | w.Unlock() 230 | log.Warn("[physic]{GetTransform}id is missed", id) 231 | return 232 | } 233 | p = V3_OdeToLin(body.Position()) 234 | q = Q_OdeToLin(body.Quaternion()) 235 | w.Unlock() 236 | return 237 | } 238 | 239 | func (w *World) AddObj(id int64, obj *Obj) { 240 | log.Debug("{physic}[Addbody] Id:", id) 241 | w.Objs.Store(id, obj) 242 | } 243 | 244 | func (w *World) DeleteObj(id int64) { 245 | w.Objs.Delete(id) 246 | } 247 | func (w *World) CreateEntity(objName string, id int64, pos lin.V3, rot lin.Q) { 248 | 249 | obj := data.ObjData[objName] 250 | body := w.World.NewBody() 251 | mass := ode.NewMass() 252 | switch obj.Shape { 253 | case "Box": 254 | mass.SetBoxTotal(obj.Mass, ode.NewVector3(obj.Lens[0], obj.Lens[1], obj.Lens[2])) 255 | box := w.Space.NewBox(ode.NewVector3(obj.Lens[0], obj.Lens[1], obj.Lens[2])) 256 | box.SetCategoryBits(SetBits(0, Player_Bit)) 257 | box.SetCollideBits(SetBitExcept(SetAllBits(), AOE_Bit)) 258 | box.SetBody(body) 259 | object := &Obj{ 260 | CBody: body, 261 | CGeom: box, 262 | Data: &ObjData{Uuid: id, Name: objName, Type: obj.Type}, 263 | CollideObjs: make(map[*Obj]int64), 264 | } 265 | object.CreateAOE(w.Space, 100.0) 266 | w.Lock() 267 | object.AddGeom(box) 268 | object.AddBody(body) 269 | w.AddObj(id, object) 270 | //joint := w.World.NewPlane2DJoint(ode.JointGroup(0)) 271 | //joint.Attach(body, ode.Body(0)) 272 | case "Capsule": 273 | mass.SetCapsuleTotal(obj.Mass, 1, obj.Lens[0], obj.Lens[1]) 274 | capsule := w.Space.NewCapsule(obj.Lens[0], obj.Lens[1]) 275 | capsule.SetCategoryBits(SetBits(0, Skill_Bit)) 276 | capsule.SetCollideBits(SetBits(0, Terrain_Bit, Player_Bit, Enemy_Bit)) 277 | capsule.SetBody(body) 278 | object := &Obj{ 279 | CBody: body, 280 | CGeom: capsule, 281 | Data: &ObjData{Uuid: id, Name: objName, Type: obj.Type}, 282 | CollideObjs: make(map[*Obj]int64), 283 | } 284 | w.Lock() 285 | object.AddGeom(capsule) 286 | object.AddBody(body) 287 | w.AddObj(id, object) 288 | default: 289 | log.Debug("[World]{CreateEntity} No ", objName) 290 | return 291 | } 292 | body.SetPosition(V3_LinToOde(&pos)) 293 | body.SetQuaternion(Q_LinToOde(&rot)) 294 | w.Unlock() 295 | } 296 | 297 | func (w *World) SetTranform(id int64, t *TransForm) { 298 | q := t.Rotation 299 | v3 := t.Position 300 | obj, ok := w.Objs.Get(id) 301 | body := obj.CBody 302 | if !ok { 303 | log.Warn("{physic}[SetTranform]Not Found:", id) 304 | } 305 | body.SetPosition(V3_MsgToOde(v3)) 306 | body.SetQuaternion(Q_MsgToOde(q)) 307 | } 308 | 309 | func (w *World) Move(id int64, v float64, omega float64) { 310 | w.Lock() 311 | obj, ok := w.Objs.Get(id) 312 | if !ok { 313 | log.Warn("{physic}[Move] No such body ", id) 314 | } 315 | body := obj.CBody 316 | body.SetAngularVelocity(ode.NewVector3(0, 0, -3*omega)) 317 | body.SetLinearVelocity(body.VectorToWorld(ode.NewVector3(0, 10*v, body.LinearVelocity()[2]))) 318 | //log.Debug("{physic}[Move] vel:", body.LinearVelocity(), "ang vel:", body.AngularVel()) 319 | w.Unlock() 320 | //log.Debug("{physic}[Move] position: ", body.Position(), " Rotation: ", body.Quaternion()) 321 | } 322 | 323 | type Objs struct { 324 | sync.Map 325 | } 326 | 327 | func (g *Objs) Get(id int64) (*Obj, bool) { 328 | v, ok := g.Load(id) 329 | obj := v.(*Obj) 330 | return obj, ok 331 | } 332 | 333 | type ObjData struct { 334 | Uuid int64 335 | Type string 336 | Name string 337 | CollideTimes int64 338 | } 339 | 340 | type Obj struct { 341 | sync.Mutex 342 | CBody ode.Body 343 | CGeom ode.Geom 344 | Space ode.Space 345 | AOE ode.Geom 346 | OtherBodys []ode.Body 347 | OtherGeoms []ode.Geom 348 | Data *ObjData 349 | CollideObjs map[*Obj]int64 350 | AOEObjs map[int64]ode.Vector3 351 | } 352 | 353 | func (obj *Obj) CreateAOE(space ode.Space, radius float64) { 354 | obj.AOEObjs = make(map[int64]ode.Vector3) 355 | s := space.NewSphere(radius) 356 | s.SetCategoryBits(SetBits(0, AOE_Bit)) 357 | s.SetCollideBits(SetBits(0, Player_Bit)) 358 | s.SetData(obj) 359 | obj.AOE = s 360 | } 361 | 362 | func (obj *Obj) SyncAOEPos() { 363 | obj.AOE.SetPosition(obj.CBody.Position()) 364 | obj.AOE.SetQuaternion(obj.CBody.Quaternion()) 365 | } 366 | 367 | func (obj *Obj) InAOE(entityId int64, p ode.Vector3) { 368 | if _, ok := obj.AOEObjs[entityId]; ok { 369 | return 370 | } 371 | obj.AOEObjs[entityId] = p 372 | } 373 | 374 | func (obj *Obj) ClearAOE() { 375 | obj.AOEObjs = make(map[int64]ode.Vector3) 376 | } 377 | func (obj *Obj) AddGeom(geom ode.Geom) { 378 | obj.OtherGeoms = append(obj.OtherGeoms, geom) 379 | geom.SetData(obj) 380 | } 381 | 382 | func (obj *Obj) AddBody(body ode.Body) { 383 | obj.OtherBodys = append(obj.OtherBodys, body) 384 | } 385 | 386 | func (obj *Obj) Collide(obj2 *Obj) { 387 | obj.Data.CollideTimes += 1 388 | obj.CollideObjs[obj2] += 1 389 | } 390 | 391 | func (obj *Obj) ResetCollide() { 392 | obj.Data.CollideTimes = 0 393 | obj.CollideObjs = make(map[*Obj]int64) 394 | } 395 | 396 | func (obj *Obj) LoopCollideObj(f func(obj *Obj, times int64) bool) { 397 | for obj, times := range obj.CollideObjs { 398 | if !f(obj, times) { 399 | break 400 | } 401 | } 402 | } 403 | func (obj *Obj) GetData() *ObjData { 404 | return obj.Data 405 | } 406 | 407 | func (obj *Obj) SetData(objData ObjData) { 408 | obj.Data = &objData 409 | } 410 | 411 | func (obj *Obj) Destroy() { 412 | obj.Lock() 413 | log.Debug("[obj]{Destroy} Space:", obj.Space) 414 | if obj.Space != nil { 415 | obj.Space.Destroy() 416 | } else { 417 | obj.CGeom.Destroy() 418 | obj.CBody.Destroy() 419 | } 420 | obj.Unlock() 421 | } 422 | 423 | var ( 424 | roomIdMapCtGrp map[int64]ode.JointGroup 425 | tempCtGrp ode.JointGroup 426 | ) 427 | 428 | func init() { 429 | log.Debug("ode Init") 430 | ode.Init(0, ode.AllAFlag) 431 | } 432 | 433 | func EulerToQuaternion(yaw, pitch, roll float64) *lin.Q { 434 | p := pitch * math.Pi / 180 / 2.0 435 | ya := yaw * math.Pi / 180 / 2.0 436 | r := roll * math.Pi / 180 / 2.0 437 | 438 | sinp := math.Sin(p) 439 | siny := math.Sin(ya) 440 | sinr := math.Sin(r) 441 | cosp := math.Cos(p) 442 | cosy := math.Cos(ya) 443 | cosr := math.Cos(r) 444 | 445 | x := sinr*cosp*cosy - cosr*sinp*siny 446 | y := cosr*sinp*cosy + sinr*cosp*siny 447 | z := cosr*cosp*siny - sinr*sinp*cosy 448 | w := cosr*cosp*cosy + sinr*sinp*siny 449 | q := lin.NewQ() 450 | q.SetS(x, y, z, w) 451 | 452 | return q 453 | } 454 | 455 | func Q_LinToOde(q *lin.Q) ode.Quaternion { 456 | x, y, z, w := q.GetS() 457 | return ode.NewQuaternion(w, x, y, z) 458 | } 459 | 460 | func V3_LinToOde(v3 *lin.V3) ode.Vector3 { 461 | x, y, z := v3.GetS() 462 | return ode.NewVector3(x, y, z) 463 | } 464 | 465 | func Q_OdeToLin(q ode.Quaternion) *lin.Q { 466 | x := q[1] 467 | y := q[2] 468 | z := q[3] 469 | w := q[0] 470 | result := lin.NewQ() 471 | result.SetS(x, y, z, w) 472 | return result 473 | } 474 | func V3_OdeToLin(q ode.Vector3) *lin.V3 { 475 | x := q[0] 476 | y := q[1] 477 | z := q[2] 478 | result := lin.NewV3() 479 | result.SetS(x, y, z) 480 | return result 481 | } 482 | 483 | func V3_OdeToMsg(v3 ode.Vector3) (msg *Vector3) { 484 | msg = &Vector3{v3[0], v3[1], v3[2]} 485 | return 486 | } 487 | 488 | func Q_OdeToMsg(q ode.Quaternion) (msg *Quaternion) { 489 | msg = &Quaternion{q[1], q[2], q[3], q[0]} 490 | return 491 | } 492 | 493 | func V3_MsgToOde(msg *Vector3) (v3 ode.Vector3) { 494 | v3 = ode.NewVector3(msg.X, msg.Y, msg.Z) 495 | return 496 | } 497 | func Q_MsgToOde(msg *Quaternion) (q ode.Quaternion) { 498 | q = ode.NewQuaternion(msg.W, msg.X, msg.Y, msg.Z) 499 | return 500 | } 501 | 502 | func Q_LinToMsg(q *lin.Q) (msg *Quaternion) { 503 | msg = &Quaternion{} 504 | msg.X, msg.Y, msg.Z, msg.W = q.GetS() 505 | return 506 | } 507 | 508 | func V3_LinToMsg(p *lin.V3) (msg *Vector3) { 509 | msg = &Vector3{} 510 | msg.X, msg.Y, msg.Z = p.GetS() 511 | return 512 | } 513 | 514 | func DirectionV3ToQuaternion(v3 *lin.V3) ode.Quaternion { 515 | angle := -1 * math.Atan2(v3.X, v3.Y) 516 | targetQ := lin.NewQ().SetS(0, 0, math.Sin(angle/2), math.Cos(angle/2)) 517 | return Q_LinToOde(targetQ) 518 | } 519 | -------------------------------------------------------------------------------- /game/physic/physic_test.go: -------------------------------------------------------------------------------- 1 | package physic 2 | 3 | import ( 4 | //"github.com/gazed/vu/math/lin" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestWorld(t *testing.T) { 10 | fmt.Printf("Bits:%b", SetBitExcept(SetAllBits(), Skill_Bit, Terrain_Bit)) 11 | // 12 | } 13 | -------------------------------------------------------------------------------- /game/session/room.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "errors" 5 | . "github.com/daniel840829/gameServer/msg" 6 | . "github.com/daniel840829/gameServer/uuid" 7 | "github.com/golang/protobuf/proto" 8 | log "github.com/sirupsen/logrus" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type ChatMessage struct { 14 | SpeakerId int32 15 | SpeakerName string 16 | Content string 17 | } 18 | 19 | type ChatRoom struct { 20 | ReadingBuffer []*ChatMessage 21 | } 22 | 23 | //TODO : Chat function 24 | 25 | var limitReadyTime = 1000 //wait ms to start a room 26 | 27 | var RoomManager *roomManager = &roomManager{ 28 | Rooms: make(map[int64]*Room), 29 | UserIdleRoomListChan: make(map[*MsgChannel]struct{}), 30 | } 31 | 32 | type roomManager struct { 33 | UserIdleRoomListChan map[*MsgChannel]struct{} 34 | Rooms map[int64]*Room 35 | sync.RWMutex 36 | RoomList *RoomList 37 | } 38 | 39 | func (rm *roomManager) CreateGame(gameCreation *GameCreation) error { 40 | switch gameCreation.RoomInfo.GameType { 41 | default: 42 | if _, ok := rm.Rooms[gameCreation.RoomInfo.Uuid]; ok { 43 | return errors.New("Room already exist") 44 | } 45 | room := NewRoom(gameCreation.RoomInfo.Uuid) 46 | for _, sessionInfo := range gameCreation.PlayerSessions { 47 | //sessionInfo.Uuid 48 | session := Manager.CreateSessionFromAgent(sessionInfo) 49 | log.Debug("[CreateGame]", session) 50 | session.InputPool = room.GetMsgChan("Input") 51 | room.Client[session] = struct{}{} 52 | session.Room = room 53 | 54 | } 55 | rm.Rooms[gameCreation.RoomInfo.Uuid] = room 56 | go room.Run() 57 | return nil 58 | } 59 | } 60 | 61 | func (rm *roomManager) DeleteRoom(info *RoomInfo) error { 62 | room, ok := rm.Rooms[info.Uuid] 63 | if !ok { 64 | log.Debug(rm.Rooms) 65 | return errors.New("no this room") 66 | } 67 | room.end <- struct{}{} 68 | delete(rm.Rooms, info.Uuid) 69 | log.Debug("Game Rooms After delete", rm.Rooms) 70 | return nil 71 | } 72 | 73 | func (rm *roomManager) LeaveRoom() { 74 | 75 | } 76 | 77 | func (rm *roomManager) CreateRoom(master *Session, setting *RoomSetting) *Room { 78 | id, _ := Uid.NewId(ROOM_ID) 79 | room := NewRoom(id) 80 | rm.Rooms[id] = room 81 | return room 82 | } 83 | 84 | func NewRoom(roomId int64) *Room { 85 | room := &Room{ 86 | Client: make(map[*Session]struct{}), 87 | Uuid: roomId, 88 | GameStart: make(chan (struct{}), 1), 89 | MsgChannelManager: NewMsgChannelManager(), 90 | end: make(chan struct{}, 10), 91 | } 92 | room.AddMsgChan("Input", 200) 93 | return room 94 | } 95 | 96 | type Room struct { 97 | Name string 98 | GameType string 99 | Master *Session 100 | Client map[*Session]struct{} 101 | Uuid int64 102 | GameFrame *GameFrame 103 | *MsgChannelManager 104 | sync.RWMutex 105 | GameStart chan (struct{}) 106 | end chan (struct{}) 107 | } 108 | 109 | func (r *Room) GenerateStartFrame() { 110 | r.GameFrame.EntityStates = make(map[int64]*EntityState) 111 | r.GameFrame.Interaction = make([]*Interaction, 0) 112 | r.GameFrame.Characters = make(map[int64]*Character) 113 | for s, _ := range r.Client { 114 | 115 | c := s.Info.UserInfo.OwnCharacter[s.Info.UserInfo.UsedCharacter] 116 | r.GameFrame.EntityStates[s.Info.UserInfo.UsedCharacter] = NewEntityState(s.Info.UserInfo.UsedCharacter, "Tank", c) 117 | r.GameFrame.Characters[s.Info.UserInfo.UsedCharacter] = c 118 | log.Debug(c, s.Info.UserInfo.UsedCharacter) 119 | } 120 | r.SyncGameFrame() 121 | //r.GameFrame.Characters = make(map[int64]*Character) 122 | } 123 | 124 | func NewEntityState(id int64, prefabName string, c *Character) *EntityState { 125 | es := &EntityState{ 126 | Health: c.MaxHealth, 127 | Uuid: id, 128 | PrefabName: prefabName, 129 | Transform: &Transform{ 130 | Position: &Vector3{0, 0, 0}, 131 | Rotation: &Quaternion{0, 0, 0, 1}, 132 | }, 133 | Animation: &Animation{}, 134 | Speed: &Vector3{}, 135 | } 136 | return es 137 | } 138 | 139 | func (r *Room) Run() { 140 | //Syncpos 141 | inputPool := r.GetMsgChan("Input") 142 | for _, _ = range r.Client { 143 | <-r.GameStart 144 | } 145 | r.GameFrame = &GameFrame{} 146 | r.GameFrame.RunnigNo = 0 147 | r.GameFrame.TimeStamp = GetTimeStamp() 148 | r.GenerateStartFrame() 149 | r.GameFrame.RunnigNo += 1 150 | update := time.NewTicker(time.Millisecond * 30) 151 | END: 152 | for { 153 | select { 154 | case <-update.C: 155 | //log.Debug("GameFrame: ", r.GameFrame) 156 | r.UpdateFrame() 157 | case msg := <-inputPool.DataCh: 158 | input := msg.(*Input) 159 | r.HandleEntityState(input) 160 | r.HandleNewEntity(input) 161 | r.HandleInteraction(input) 162 | r.HandleEntityDestory(input) 163 | case <-r.end: 164 | break END 165 | } 166 | } 167 | 168 | log.Debug("End Gameing") 169 | for s, _ := range r.Client { 170 | s.Room = nil 171 | } 172 | r.Master.Room = nil 173 | } 174 | 175 | func (r *Room) UpdateFrame() { 176 | r.SyncGameFrame() 177 | r.GameFrame.RunnigNo += 1 178 | r.GameFrame.TimeStamp = GetTimeStamp() 179 | r.GameFrame.Interaction = make([]*Interaction, 0) 180 | } 181 | 182 | func (r *Room) HandleEntityDestory(input *Input) { 183 | if len(input.DestroyEntity) > 0 { 184 | //r.UpdateFrame() 185 | for _, id := range input.DestroyEntity { 186 | //log.Debug("[Destroy Entity]", r.GameFrame.EntityStates) 187 | delete(r.GameFrame.Characters, id) 188 | delete(r.GameFrame.EntityStates, id) 189 | } 190 | } 191 | } 192 | 193 | func (r *Room) HandleInteraction(input *Input) { 194 | /* 195 | if r.GameFrame.TimeStamp > input.TimeStamp { 196 | return 197 | } 198 | */ 199 | for _, in := range input.Interaction { 200 | r.GameFrame.Interaction = append(r.GameFrame.Interaction, in) 201 | } 202 | } 203 | 204 | func (r *Room) HandleEntityState(input *Input) { 205 | for _, in := range input.EntityStates { 206 | r.GameFrame.EntityStates[in.Uuid] = in 207 | } 208 | } 209 | 210 | func (r *Room) HandleNewEntity(input *Input) { 211 | for id, in := range input.NewEntityCharacters { 212 | r.GameFrame.Characters[id] = in 213 | } 214 | } 215 | 216 | func (r *Room) SyncGameFrame() { 217 | gf := proto.Clone(r.GameFrame).(*GameFrame) 218 | for s, _ := range r.Client { 219 | msg := s.GetMsgChan("GameFrame") 220 | msg.DataCh <- gf 221 | } 222 | } 223 | 224 | func (r *Room) WaitPlayerReconnect(s *Session) { 225 | //Do nothing 226 | } 227 | 228 | func (r *Room) PlayerLeave(s *Session) { 229 | //TODO: Check player number 230 | delete(r.Client, s) 231 | //if there is no player 232 | if len(r.Client) == 0 { 233 | r.end <- struct{}{} 234 | } 235 | } 236 | 237 | func GetTimeStamp() int64 { 238 | return int64(time.Now().UnixNano() / 1000000) 239 | } 240 | -------------------------------------------------------------------------------- /game/session/session.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | //"github.com/daniel840829/gameServer2/entity" 5 | . "github.com/daniel840829/gameServer/msg" 6 | "github.com/daniel840829/gameServer/user" 7 | //. "github.com/daniel840829/gameServer/uuid" 8 | log "github.com/sirupsen/logrus" 9 | "google.golang.org/grpc/metadata" 10 | "strconv" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | type MsgChannel struct { 16 | DataCh chan (interface{}) 17 | StopSignal chan (struct{}) 18 | } 19 | 20 | func (m *MsgChannel) Close() { 21 | select { 22 | case <-m.StopSignal: 23 | return 24 | default: 25 | close(m.StopSignal) 26 | } 27 | } 28 | 29 | func NewMsgChannel(bufferNumber int32) *MsgChannel { 30 | return &MsgChannel{ 31 | DataCh: make(chan (interface{}), bufferNumber), 32 | StopSignal: make(chan (struct{}), 1), 33 | } 34 | } 35 | 36 | func NewMsgChannelManager() *MsgChannelManager { 37 | return &MsgChannelManager{ 38 | make(map[string]*MsgChannel), 39 | } 40 | } 41 | 42 | type MsgChannelManager struct { 43 | c map[string]*MsgChannel 44 | } 45 | 46 | func (m *MsgChannelManager) AddMsgChan(name string, bufferNumber int32) bool { 47 | if _, ok := m.c[name]; ok { 48 | return false 49 | } 50 | m.c[name] = NewMsgChannel(bufferNumber) 51 | return true 52 | } 53 | 54 | func (m *MsgChannelManager) GetMsgChan(name string) *MsgChannel { 55 | return m.c[name] 56 | } 57 | 58 | func (m *MsgChannelManager) CloseMsgChan(name string) { 59 | if ch, ok := m.c[name]; ok { 60 | ch.Close() 61 | delete(m.c, name) 62 | } 63 | } 64 | 65 | type sessionManager struct { 66 | Sessions map[int64]*Session 67 | sync.RWMutex 68 | } 69 | 70 | func (sm *sessionManager) MakeSession(info *SessionInfo) int64 { 71 | s := NewSession(info) 72 | sm.Lock() 73 | sm.Sessions[s.Info.Uuid] = s 74 | sm.Unlock() 75 | return s.Info.Uuid 76 | } 77 | 78 | func (sm *sessionManager) CreateSessionFromAgent(sessionInfo *SessionInfo) *Session { 79 | characterInfo := sessionInfo.UserInfo.OwnCharacter[sessionInfo.UserInfo.UsedCharacter] 80 | log.Debug(characterInfo) 81 | s := sm.Sessions[sm.MakeSession(sessionInfo)] 82 | return s 83 | } 84 | 85 | func (sm *sessionManager) GetSession(md metadata.MD) *Session { 86 | mdid := md.Get("session-id") 87 | if len(mdid) == 0 { 88 | return nil 89 | } 90 | id, err := strconv.ParseInt(mdid[0], 10, 64) 91 | if err != nil { 92 | return nil 93 | } 94 | s, ok := sm.Sessions[id] 95 | if !ok { 96 | return s 97 | } 98 | s.RLock() 99 | if s.User != nil { 100 | uname := md.Get("uname") 101 | if len(uname) == 0 { 102 | s.RUnlock() 103 | return nil 104 | } else if s.User.UserInfo.UserName != uname[0] { 105 | s.RUnlock() 106 | return nil 107 | } 108 | } 109 | s.RUnlock() 110 | return s 111 | } 112 | 113 | func NewSession(info *SessionInfo) *Session { 114 | s := &Session{ 115 | Info: info, 116 | MsgChannelManager: NewMsgChannelManager(), 117 | PlayerInfo: &PlayerInfo{}, 118 | } 119 | for i := int32(SessionInfo_NoSession); i <= int32(SessionInfo_GameServerWaitReconnect); i++ { 120 | ss := SessionStateFactory.makeSessionState(s, SessionInfo_SessionState(i)) 121 | s.States = append(s.States, ss) 122 | } 123 | s.SetState(int32(SessionInfo_OnStart)) 124 | s.State.CreateSession() 125 | s.AddMsgChan("GameFrame", 5) 126 | return s 127 | } 128 | 129 | type Session struct { 130 | Info *SessionInfo 131 | State SessionState 132 | SessionKey int64 133 | User *user.User 134 | States []SessionState 135 | Room *Room 136 | sync.RWMutex 137 | PlayerInfo *PlayerInfo 138 | TeamNo int32 139 | InputPool *MsgChannel 140 | InputStamp int64 141 | *MsgChannelManager 142 | } 143 | 144 | func (s *Session) GetPlayerInfo() *PlayerInfo { 145 | if s.User == nil { 146 | return nil 147 | } 148 | s.PlayerInfo.UserName = s.User.UserInfo.UserName 149 | s.PlayerInfo.UserId = s.User.UserInfo.Uuid 150 | if s.User.UserInfo.UsedCharacter == int64(0) { 151 | for id, c := range s.User.UserInfo.OwnCharacter { 152 | s.User.UserInfo.UsedCharacter = id 153 | s.PlayerInfo.Character = c 154 | break 155 | } 156 | } else { 157 | s.PlayerInfo.Character = s.User.UserInfo.OwnCharacter[s.User.UserInfo.UsedCharacter] 158 | } 159 | s.PlayerInfo.TeamNo = s.TeamNo 160 | return s.PlayerInfo 161 | } 162 | 163 | func (s *Session) SetState(state_index int32) { 164 | s.State = s.States[state_index] 165 | } 166 | 167 | type SessionState interface { 168 | SetSession(s *Session) bool 169 | SetStateCode(SessionInfo_SessionState) 170 | GetStateCode() SessionInfo_SessionState 171 | CreateSession() int64 172 | Login(uname string, pswd string) *user.User 173 | Logout() bool 174 | Regist(uname string, pswd string, info ...string) bool 175 | CreateRoom(setting *RoomSetting) bool 176 | EnterRoom(roomId int64) bool 177 | DeleteRoom() bool 178 | ReadyRoom() bool 179 | LeaveRoom() bool 180 | StartRoom() bool 181 | SettingCharacter(*CharacterSetting) bool 182 | SettingRoom() bool 183 | CancelReady() bool 184 | EndRoom() bool 185 | String() string 186 | Lock() 187 | HandleInput(input *Input) 188 | Unlock() 189 | StartGame() 190 | Reconnect() 191 | EndConnection() 192 | WaitReconnect() 193 | End() 194 | } 195 | 196 | func (sb *SessionStateBase) SetSession(s *Session) bool { 197 | if sb.Session != nil { 198 | return false 199 | } 200 | sb.Session = s 201 | return true 202 | } 203 | 204 | func (sb *SessionStateBase) End() { 205 | log.Debug("origin") 206 | } 207 | 208 | func (sb *SessionStateBase) String() string { 209 | return SessionInfo_SessionState_name[int32(sb.StateCode)] 210 | } 211 | 212 | func (sb *SessionStateBase) StartGame() { 213 | //TODO 214 | } 215 | 216 | func (sb *SessionStateBase) Reconnect() { 217 | 218 | } 219 | 220 | func (sb *SessionStateBase) WaitReconnect() { 221 | 222 | } 223 | func (sb *SessionStateBase) EndConnection() { 224 | 225 | } 226 | 227 | func (sb *SessionStateBase) SetStateCode(code SessionInfo_SessionState) { 228 | sb.StateCode = code 229 | } 230 | func (sb *SessionStateBase) GetStateCode() SessionInfo_SessionState { 231 | return sb.StateCode 232 | } 233 | func (sb *SessionStateBase) CreateSession() int64 { 234 | return 0 235 | } 236 | 237 | func (sb *SessionStateBase) HandleInput(input *Input) { 238 | } 239 | 240 | func (sb *SessionStateBase) Login(uname string, pswd string) *user.User { 241 | return nil 242 | } 243 | func (sb *SessionStateBase) Logout() bool { 244 | return false 245 | } 246 | func (sb *SessionStateBase) Regist(uname string, pswd string, info ...string) bool { 247 | return false 248 | } 249 | func (sb *SessionStateBase) CreateRoom(setting *RoomSetting) bool { 250 | return false 251 | } 252 | func (sb *SessionStateBase) EnterRoom(roomId int64) bool { 253 | return false 254 | } 255 | func (sb *SessionStateBase) DeleteRoom() bool { 256 | return false 257 | } 258 | func (sb *SessionStateBase) ReadyRoom() bool { 259 | return false 260 | } 261 | func (sb *SessionStateBase) LeaveRoom() bool { 262 | return false 263 | } 264 | func (sb *SessionStateBase) StartRoom() bool { 265 | return false 266 | } 267 | func (sb *SessionStateBase) SettingCharacter(*CharacterSetting) bool { 268 | return false 269 | } 270 | func (sb *SessionStateBase) SettingRoom() bool { 271 | return false 272 | } 273 | func (sb *SessionStateBase) EndRoom() bool { 274 | return false 275 | } 276 | 277 | func (sb *SessionStateBase) CancelReady() bool { 278 | return false 279 | } 280 | 281 | type SessionStateBase struct { 282 | StateCode SessionInfo_SessionState 283 | Session *Session 284 | sync.RWMutex 285 | } 286 | 287 | type SessionStateGameOnStart struct { 288 | SessionStateBase 289 | } 290 | 291 | func (ssgos *SessionStateGameOnStart) StartGame() { 292 | //firsttimeGetGameframe 293 | ssgos.Session.Room.GameStart <- struct{}{} 294 | ssgos.Session.SetState(int32(SessionInfo_Playing)) 295 | log.Debug("state code :", int32(ssgos.Session.State.GetStateCode())) 296 | } 297 | 298 | type SessionStatePlaying struct { 299 | SessionStateBase 300 | } 301 | 302 | func (sb *SessionStatePlaying) HandleInput(input *Input) { 303 | if sb.Session.InputStamp > input.TimeStamp { 304 | log.Debug("input sequence is disorder") 305 | } 306 | sb.Session.InputPool.DataCh <- input 307 | } 308 | 309 | func (sb *SessionStatePlaying) WaitReconnect() { 310 | sb.Session.SetState(int32(SessionInfo_GameServerWaitReconnect)) 311 | } 312 | 313 | func (sb *SessionStatePlaying) EndConnection() { 314 | sb.Session.SetState(int32(SessionInfo_GameOver)) 315 | log.Debug("state code :", int32(sb.Session.State.GetStateCode())) 316 | sb.Session.State.End() 317 | } 318 | 319 | type SessionStateWaitingReconnection struct { 320 | SessionStateBase 321 | } 322 | 323 | func (sswr *SessionStateWaitingReconnection) Waiting() { 324 | go func() { 325 | c := time.After(10 * time.Second) 326 | END: 327 | for { 328 | select { 329 | case <-c: 330 | break END 331 | } 332 | } 333 | 334 | log.Warn("Exceed waiting time, end connection!") 335 | sswr.EndConnection() 336 | }() 337 | } 338 | 339 | func (sswr *SessionStateWaitingReconnection) EndConnection() { 340 | sswr.Session.SetState(int32(SessionInfo_GameOver)) 341 | log.Debug("state code :", int32(sswr.Session.State.GetStateCode())) 342 | sswr.Session.State.End() 343 | } 344 | 345 | func (sswr *SessionStateWaitingReconnection) StartGame() { 346 | sswr.Reconnect() 347 | log.Debug("state code :", int32(sswr.Session.State.GetStateCode())) 348 | } 349 | 350 | func (sswr *SessionStateWaitingReconnection) Reconnect() { 351 | sswr.Session.SetState(int32(SessionInfo_Playing)) 352 | log.Debug("state code :", int32(sswr.Session.State.GetStateCode())) 353 | sswr.Session.Room.Client[sswr.Session] = struct{}{} 354 | } 355 | 356 | type SessionStateGameOver struct { 357 | SessionStateBase 358 | } 359 | 360 | func (ssgo *SessionStateGameOver) End() { 361 | ssgo.Session.Lock() 362 | if ssgo.Session.Room == nil { 363 | ssgo.Session.Unlock() 364 | return 365 | } 366 | ssgo.Session.Room.PlayerLeave(ssgo.Session) 367 | log.Debug("ssgo.Session.Room", ssgo.Session.Room) 368 | ssgo.Session.Room = nil 369 | ssgo.Session.Unlock() 370 | } 371 | 372 | type sessionStateFactory struct { 373 | } 374 | 375 | func (sf *sessionStateFactory) makeSessionState(session *Session, state_code SessionInfo_SessionState) SessionState { 376 | var s SessionState 377 | switch state_code { 378 | case SessionInfo_OnStart: 379 | s = &SessionStateGameOnStart{} 380 | case SessionInfo_Playing: 381 | s = &SessionStatePlaying{} 382 | case SessionInfo_GameOver: 383 | s = &SessionStateGameOver{} 384 | case SessionInfo_GameServerWaitReconnect: 385 | s = &SessionStateWaitingReconnection{} 386 | default: 387 | s = &SessionStateBase{} 388 | } 389 | s.Lock() 390 | s.SetSession(session) 391 | s.SetStateCode(state_code) 392 | s.Unlock() 393 | return s 394 | } 395 | 396 | var Manager *sessionManager 397 | 398 | var SessionStateFactory *sessionStateFactory 399 | 400 | func init() { 401 | Manager = &sessionManager{ 402 | Sessions: make(map[int64]*Session), 403 | } 404 | SessionStateFactory = &sessionStateFactory{} 405 | } 406 | -------------------------------------------------------------------------------- /gameServer.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /gdb_sandbox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danie1Lin/Distributed-Golang-Game-Server/620f6c91b6e21db9f8eca88916b1130223ff8b81/gdb_sandbox -------------------------------------------------------------------------------- /main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danie1Lin/Distributed-Golang-Game-Server/620f6c91b6e21db9f8eca88916b1130223ff8b81/main -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | 6 | //"github.com/daniel840829/gameServer/entity" 7 | "runtime/pprof" 8 | 9 | "github.com/daniel840829/gameServer/agent" 10 | "github.com/daniel840829/gameServer/game" 11 | "github.com/daniel840829/gameServer/msg" 12 | "google.golang.org/grpc" 13 | 14 | //"google.golang.org/grpc/grpclog" 15 | "flag" 16 | "fmt" 17 | "os" 18 | 19 | log "github.com/sirupsen/logrus" 20 | ) 21 | 22 | func init() { 23 | log.SetFormatter(&log.TextFormatter{}) 24 | log.SetOutput(os.Stdout) 25 | log.SetLevel(log.DebugLevel) 26 | 27 | } 28 | 29 | var ( 30 | serverType *string 31 | configFile *string 32 | AgentPort *string 33 | AgentToGamePort *string 34 | ClientToGamePort *string 35 | cpuprofile *string 36 | ) 37 | 38 | func main() { 39 | 40 | serverType = flag.String("type", "game", "choose server Type") 41 | configFile = flag.String("config", "", "config file's path") 42 | AgentPort = flag.String("agentPort", "50051", "ClientToAgent Port") 43 | AgentToGamePort = flag.String("agentToGamePort", "3000", "AgentToGame Port") 44 | ClientToGamePort = flag.String("clientToGamePort", "8080", "ClientToGame Port") 45 | cpuprofile = flag.String("cpuprofile", "./cpu.prof", "write cpu profile to file,set blank to close profile function") 46 | log.Debug("config :", "type :", serverType) 47 | flag.Parse() 48 | //SERVER_TYPE ID CLIENT_TO_AGENT_PORT CLIENT_TO_GAME_PORT AGENT_TO_GAME_PORT 49 | ReadEnv(serverType, "SERVER_TYPE") 50 | ReadEnv(AgentToGamePort, "AGENT_TO_GAME_PORT") 51 | ReadEnv(ClientToGamePort, "CLIENT_TO_GAME_PORT") 52 | ReadEnv(AgentPort, "CLIENT_TO_AGENT_PORT") 53 | 54 | if *cpuprofile != "" { 55 | f, err := os.Create(*cpuprofile) 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | pprof.StartCPUProfile(f) 60 | defer pprof.StopCPUProfile() 61 | } 62 | if *serverType == "agent" { 63 | RunAgent() 64 | } else if *serverType == "game" { 65 | go RunGame() 66 | RunAgentToGame() 67 | } else { 68 | go RunGame() 69 | go RunAgentToGame() 70 | RunAgent() 71 | } 72 | 73 | /* 74 | //初始化gameManager 75 | rpc := service.NewRpc() 76 | gm := &entity.GameManager{} 77 | gm.Init(rpc) 78 | gm.RegistRoom("room", &entity.Room{}) 79 | gm.RegistEnitity("Player", &entity.Player{}) 80 | gm.RegistEnitity("Shell", &entity.Shell{}) 81 | gm.RegistEnitity("Enemy", &entity.Enemy{}) 82 | go gm.Run() 83 | // 注册HelloService 84 | */ 85 | } 86 | 87 | //ReadEnv if use kubernete Read para from env 88 | func ReadEnv(para *string, envName string) { 89 | v := os.Getenv(envName) 90 | if v != "" { 91 | log.Info(envName, " change from ", *para, " to ", v) 92 | *para = v 93 | } 94 | } 95 | 96 | func RunAgent() { 97 | listen, err := net.Listen("tcp", ":"+*AgentPort) 98 | if err != nil { 99 | fmt.Println("AgentServer failed to listen: %v", err) 100 | } 101 | agentRpc := agent.NewAgentRpc() 102 | s := grpc.NewServer() 103 | msg.RegisterClientToAgentServer(s, agentRpc) 104 | fmt.Println("AgentServer Listen on " + *AgentPort) 105 | agentRpc.Init("127.0.0.1", *AgentToGamePort, *ClientToGamePort) 106 | s.Serve(listen) 107 | } 108 | 109 | func RunGame() { 110 | listen, err := net.Listen("tcp", ":"+*ClientToGamePort) 111 | if err != nil { 112 | fmt.Println("GameServer failed to listen: %v", err) 113 | } 114 | s := grpc.NewServer() 115 | msg.RegisterClientToGameServer(s, &game.CTGServer{}) 116 | fmt.Println("GameServer Listen on " + *ClientToGamePort) 117 | s.Serve(listen) 118 | } 119 | 120 | func RunAgentToGame() { 121 | listen, err := net.Listen("tcp", ":"+*AgentToGamePort) 122 | if err != nil { 123 | fmt.Println("AgentToGameServer failed to listen: %v", err) 124 | } 125 | s := grpc.NewServer() 126 | msg.RegisterAgentToGameServer(s, &game.ATGServer{}) 127 | fmt.Println("AgentToGameServer Listen on " + *AgentToGamePort) 128 | s.Serve(listen) 129 | } 130 | -------------------------------------------------------------------------------- /msg/any.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "github.com/golang/protobuf/ptypes/any"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "AnyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | 42 | // `Any` contains an arbitrary serialized protocol buffer message along with a 43 | // URL that describes the type of the serialized message. 44 | // 45 | // Protobuf library provides support to pack/unpack Any values in the form 46 | // of utility functions or additional generated methods of the Any type. 47 | // 48 | // Example 1: Pack and unpack a message in C++. 49 | // 50 | // Foo foo = ...; 51 | // Any any; 52 | // any.PackFrom(foo); 53 | // ... 54 | // if (any.UnpackTo(&foo)) { 55 | // ... 56 | // } 57 | // 58 | // Example 2: Pack and unpack a message in Java. 59 | // 60 | // Foo foo = ...; 61 | // Any any = Any.pack(foo); 62 | // ... 63 | // if (any.is(Foo.class)) { 64 | // foo = any.unpack(Foo.class); 65 | // } 66 | // 67 | // Example 3: Pack and unpack a message in Python. 68 | // 69 | // foo = Foo(...) 70 | // any = Any() 71 | // any.Pack(foo) 72 | // ... 73 | // if any.Is(Foo.DESCRIPTOR): 74 | // any.Unpack(foo) 75 | // ... 76 | // 77 | // Example 4: Pack and unpack a message in Go 78 | // 79 | // foo := &pb.Foo{...} 80 | // any, err := ptypes.MarshalAny(foo) 81 | // ... 82 | // foo := &pb.Foo{} 83 | // if err := ptypes.UnmarshalAny(any, foo); err != nil { 84 | // ... 85 | // } 86 | // 87 | // The pack methods provided by protobuf library will by default use 88 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack 89 | // methods only use the fully qualified type name after the last '/' 90 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type 91 | // name "y.z". 92 | // 93 | // 94 | // JSON 95 | // 96 | // The JSON representation of an `Any` value uses the regular 97 | // representation of the deserialized, embedded message, with an 98 | // additional field `@type` which contains the type URL. Example: 99 | // 100 | // package google.profile; 101 | // message Person { 102 | // string first_name = 1; 103 | // string last_name = 2; 104 | // } 105 | // 106 | // { 107 | // "@type": "type.googleapis.com/google.profile.Person", 108 | // "firstName": , 109 | // "lastName": 110 | // } 111 | // 112 | // If the embedded message type is well-known and has a custom JSON 113 | // representation, that representation will be embedded adding a field 114 | // `value` which holds the custom JSON in addition to the `@type` 115 | // field. Example (for message [google.protobuf.Duration][]): 116 | // 117 | // { 118 | // "@type": "type.googleapis.com/google.protobuf.Duration", 119 | // "value": "1.212s" 120 | // } 121 | // 122 | message Any { 123 | // A URL/resource name whose content describes the type of the 124 | // serialized protocol buffer message. 125 | // 126 | // For URLs which use the scheme `http`, `https`, or no scheme, the 127 | // following restrictions and interpretations apply: 128 | // 129 | // * If no scheme is provided, `https` is assumed. 130 | // * The last segment of the URL's path must represent the fully 131 | // qualified name of the type (as in `path/google.protobuf.Duration`). 132 | // The name should be in a canonical form (e.g., leading "." is 133 | // not accepted). 134 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][] 135 | // value in binary format, or produce an error. 136 | // * Applications are allowed to cache lookup results based on the 137 | // URL, or have them precompiled into a binary to avoid any 138 | // lookup. Therefore, binary compatibility needs to be preserved 139 | // on changes to types. (Use versioned type names to manage 140 | // breaking changes.) 141 | // 142 | // Schemes other than `http`, `https` (or the empty scheme) might be 143 | // used with implementation specific semantics. 144 | // 145 | string type_url = 1; 146 | 147 | // Must be a valid serialized protocol buffer of the above specified type. 148 | bytes value = 2; 149 | } 150 | -------------------------------------------------------------------------------- /msg/github.com/golang/protobuf/ptypes/any/any.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: any.proto 3 | 4 | /* 5 | Package any is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | any.proto 9 | message.proto 10 | 11 | It has these top-level messages: 12 | Any 13 | Vector3 14 | Rotation 15 | Callin 16 | Reply 17 | */ 18 | package any 19 | 20 | import proto "github.com/golang/protobuf/proto" 21 | import fmt "fmt" 22 | import math "math" 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ = proto.Marshal 26 | var _ = fmt.Errorf 27 | var _ = math.Inf 28 | 29 | // This is a compile-time assertion to ensure that this generated file 30 | // is compatible with the proto package it is being compiled against. 31 | // A compilation error at this line likely means your copy of the 32 | // proto package needs to be updated. 33 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 34 | 35 | // `Any` contains an arbitrary serialized protocol buffer message along with a 36 | // URL that describes the type of the serialized message. 37 | // 38 | // Protobuf library provides support to pack/unpack Any values in the form 39 | // of utility functions or additional generated methods of the Any type. 40 | // 41 | // Example 1: Pack and unpack a message in C++. 42 | // 43 | // Foo foo = ...; 44 | // Any any; 45 | // any.PackFrom(foo); 46 | // ... 47 | // if (any.UnpackTo(&foo)) { 48 | // ... 49 | // } 50 | // 51 | // Example 2: Pack and unpack a message in Java. 52 | // 53 | // Foo foo = ...; 54 | // Any any = Any.pack(foo); 55 | // ... 56 | // if (any.is(Foo.class)) { 57 | // foo = any.unpack(Foo.class); 58 | // } 59 | // 60 | // Example 3: Pack and unpack a message in Python. 61 | // 62 | // foo = Foo(...) 63 | // any = Any() 64 | // any.Pack(foo) 65 | // ... 66 | // if any.Is(Foo.DESCRIPTOR): 67 | // any.Unpack(foo) 68 | // ... 69 | // 70 | // Example 4: Pack and unpack a message in Go 71 | // 72 | // foo := &pb.Foo{...} 73 | // any, err := ptypes.MarshalAny(foo) 74 | // ... 75 | // foo := &pb.Foo{} 76 | // if err := ptypes.UnmarshalAny(any, foo); err != nil { 77 | // ... 78 | // } 79 | // 80 | // The pack methods provided by protobuf library will by default use 81 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack 82 | // methods only use the fully qualified type name after the last '/' 83 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type 84 | // name "y.z". 85 | // 86 | // 87 | // JSON 88 | // 89 | // The JSON representation of an `Any` value uses the regular 90 | // representation of the deserialized, embedded message, with an 91 | // additional field `@type` which contains the type URL. Example: 92 | // 93 | // package google.profile; 94 | // message Person { 95 | // string first_name = 1; 96 | // string last_name = 2; 97 | // } 98 | // 99 | // { 100 | // "@type": "type.googleapis.com/google.profile.Person", 101 | // "firstName": , 102 | // "lastName": 103 | // } 104 | // 105 | // If the embedded message type is well-known and has a custom JSON 106 | // representation, that representation will be embedded adding a field 107 | // `value` which holds the custom JSON in addition to the `@type` 108 | // field. Example (for message [google.protobuf.Duration][]): 109 | // 110 | // { 111 | // "@type": "type.googleapis.com/google.protobuf.Duration", 112 | // "value": "1.212s" 113 | // } 114 | // 115 | type Any struct { 116 | // A URL/resource name whose content describes the type of the 117 | // serialized protocol buffer message. 118 | // 119 | // For URLs which use the scheme `http`, `https`, or no scheme, the 120 | // following restrictions and interpretations apply: 121 | // 122 | // * If no scheme is provided, `https` is assumed. 123 | // * The last segment of the URL's path must represent the fully 124 | // qualified name of the type (as in `path/google.protobuf.Duration`). 125 | // The name should be in a canonical form (e.g., leading "." is 126 | // not accepted). 127 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][] 128 | // value in binary format, or produce an error. 129 | // * Applications are allowed to cache lookup results based on the 130 | // URL, or have them precompiled into a binary to avoid any 131 | // lookup. Therefore, binary compatibility needs to be preserved 132 | // on changes to types. (Use versioned type names to manage 133 | // breaking changes.) 134 | // 135 | // Schemes other than `http`, `https` (or the empty scheme) might be 136 | // used with implementation specific semantics. 137 | // 138 | TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"` 139 | // Must be a valid serialized protocol buffer of the above specified type. 140 | Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` 141 | } 142 | 143 | func (m *Any) Reset() { *m = Any{} } 144 | func (m *Any) String() string { return proto.CompactTextString(m) } 145 | func (*Any) ProtoMessage() {} 146 | func (*Any) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 147 | func (*Any) XXX_WellKnownType() string { return "Any" } 148 | 149 | func (m *Any) GetTypeUrl() string { 150 | if m != nil { 151 | return m.TypeUrl 152 | } 153 | return "" 154 | } 155 | 156 | func (m *Any) GetValue() []byte { 157 | if m != nil { 158 | return m.Value 159 | } 160 | return nil 161 | } 162 | 163 | func init() { 164 | proto.RegisterType((*Any)(nil), "google.protobuf.Any") 165 | } 166 | 167 | func init() { proto.RegisterFile("any.proto", fileDescriptor0) } 168 | 169 | var fileDescriptor0 = []byte{ 170 | // 182 bytes of a gzipped FileDescriptorProto 171 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0xcc, 0xab, 0xd4, 172 | 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4f, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0x85, 0xf0, 0x92, 173 | 0x4a, 0xd3, 0x94, 0xcc, 0xb8, 0x98, 0x1d, 0xf3, 0x2a, 0x85, 0x24, 0xb9, 0x38, 0x4a, 0x2a, 0x0b, 174 | 0x52, 0xe3, 0x4b, 0x8b, 0x72, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xd8, 0x41, 0xfc, 0xd0, 175 | 0xa2, 0x1c, 0x21, 0x11, 0x2e, 0xd6, 0xb2, 0xc4, 0x9c, 0xd2, 0x54, 0x09, 0x26, 0x05, 0x46, 0x0d, 176 | 0x9e, 0x20, 0x08, 0xc7, 0x29, 0x9f, 0x4b, 0x38, 0x39, 0x3f, 0x57, 0x0f, 0xcd, 0x38, 0x27, 0x0e, 177 | 0xc7, 0xbc, 0xca, 0x00, 0x10, 0x27, 0x80, 0x31, 0x4a, 0x35, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 178 | 0x2f, 0x39, 0x3f, 0x57, 0x3f, 0x3d, 0x3f, 0x27, 0x31, 0x2f, 0x5d, 0x1f, 0xa6, 0x4e, 0xbf, 0x00, 179 | 0x64, 0x7a, 0xb1, 0x7e, 0x62, 0x5e, 0xe5, 0x22, 0x26, 0x66, 0xf7, 0x00, 0xa7, 0x55, 0x4c, 0x72, 180 | 0xee, 0x10, 0xa3, 0x02, 0xa0, 0x4a, 0xf4, 0xc2, 0x53, 0x73, 0x72, 0xbc, 0xf3, 0xf2, 0xcb, 0xf3, 181 | 0x42, 0x40, 0x4a, 0x93, 0xd8, 0xc0, 0x7a, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x3a, 182 | 0x7a, 0xed, 0xcd, 0x00, 0x00, 0x00, 183 | } 184 | -------------------------------------------------------------------------------- /msg/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package msg; 4 | 5 | //import "any.proto"; 6 | 7 | message Input{ 8 | map EntityStates = 1; 9 | repeated Interaction Interaction = 2; 10 | map NewEntityCharacters = 3; 11 | repeated int64 DestroyEntity = 4; 12 | int64 TimeStamp = 6; 13 | } 14 | 15 | 16 | 17 | message Interaction{ 18 | int64 FromEntityId = 6; 19 | int64 ToEntityId = 1; 20 | string Type = 2;//Force Harm Torque Use 21 | Vector3 Direction = 3;//if is scalar then only X has value 22 | Vector3 ApplyPoint = 4; 23 | bool IsRebouncable = 5; 24 | } 25 | 26 | message EntityState { 27 | int64 Uuid =1; 28 | Transform Transform = 2; 29 | Vector3 Speed = 3; 30 | Animation Animation = 8; 31 | string PrefabName = 5;//Obj Player Obstacle 32 | float Health = 6; 33 | } 34 | 35 | message GameFrame { 36 | map EntityStates = 1; 37 | repeated Interaction Interaction = 2; 38 | map Characters = 3; 39 | repeated int64 DestroyEntity = 6; 40 | int64 TimeStamp = 4; 41 | int64 RunnigNo = 5; 42 | } 43 | 44 | message Animation{ 45 | string Name = 1; 46 | float Value = 2; 47 | } 48 | 49 | message Transform { 50 | Vector3 Position= 1; 51 | Quaternion Rotation = 2; 52 | } 53 | 54 | message SessionCache { 55 | ServerInfo GameServerInfo = 1; 56 | SessionInfo SessionInfo = 2; 57 | 58 | } 59 | 60 | message Vector3{ 61 | float X = 1; 62 | float Y = 2; 63 | float Z = 3; 64 | } 65 | 66 | message Quaternion{ 67 | float X = 1; 68 | float Y= 2; 69 | float Z = 3; 70 | float W = 4; 71 | } 72 | 73 | message Position{ 74 | map PosMap = 1; 75 | int64 TimeStamp = 2; 76 | } 77 | 78 | message Error{ 79 | string ErrType = 1; 80 | string ErrMsg = 2; 81 | string RunnigNo = 3; 82 | int64 FromId = 4; 83 | } 84 | 85 | message TimeStamp { 86 | int64 Value = 1; 87 | } 88 | 89 | service ClientToGame{ 90 | //roomManager 91 | //entityManager 92 | rpc PlayerInput(stream Input) returns (Empty){} 93 | //View 94 | rpc UpdateGameFrame(Empty) returns (stream GameFrame) {} 95 | rpc Pipe(stream LogMessage) returns (stream MessageToUser) {} 96 | rpc TimeCalibrate(Empty) returns (TimeStamp) {} 97 | } 98 | 99 | service AgentToGame{ 100 | //SessionManager 101 | rpc AquireGameRoom(GameCreation) returns (PemKey) {} 102 | rpc DeletGameRoom(RoomInfo) returns (Success){} 103 | } 104 | 105 | service ClientToAgent{ 106 | rpc AquireSessionKey(Empty) returns (SessionKey) {} 107 | rpc AquireOtherAgent(Empty) returns (ServerInfo) {} 108 | rpc GetSessionCache(Empty) returns (SessionCache) {} 109 | //Login 110 | rpc Login(LoginInput) returns(UserInfo){} 111 | rpc CreateAccount(RegistInput) returns(Error){} 112 | //UserSetting 113 | rpc SetAccount(AttrSetting) returns (Success) {} 114 | rpc SetCharacter(CharacterSetting) returns (Success) {} 115 | //room 116 | rpc AquireGameServer(Empty) returns (ServerInfo){} 117 | rpc CreateRoom(RoomSetting) returns (Success){} 118 | rpc JoinRoom(ID) returns (Success){} 119 | rpc RoomReady(Empty) returns (Success) {} 120 | 121 | //Team 122 | /* 123 | rpc InviteTeam() returns (){} 124 | rpc ReplyInvite() returns (){} 125 | rpc AddFriend(FriendRequest) returns (Success) {} 126 | rpc AcceptFriend(FriendRequest)returns(Success) {} 127 | rpc SearchUser(SearchKeyWord) returns (SearchResult) {} 128 | */ 129 | //View 130 | rpc UpdateRoomContent(Empty) returns (stream RoomContent) {} 131 | rpc UpdateHome(Empty) returns (stream HomeView) {} 132 | rpc UpdateRoomList(Empty) returns (stream RoomList) {} 133 | rpc UpdateUserList(Empty) returns (stream UserList) {} 134 | //rpc UpdateRoomInfo(SessionKey) returns (stream RoomInfoView) {} 135 | rpc Pipe(stream LogMessage) returns (stream MessageToUser) {} 136 | } 137 | 138 | message ID { 139 | int64 Value = 1; 140 | } 141 | 142 | message MessageToUser { 143 | enum Type { 144 | ToView = 0; 145 | ToDebugLog = 2; 146 | } 147 | Type MsgType = 1; 148 | string Context = 2; 149 | } 150 | 151 | message LogMessage { 152 | enum Level { 153 | Debug = 0; 154 | Info = 1; 155 | Warn = 2; 156 | Fatal = 3; 157 | } 158 | Level LogLevel = 1; 159 | string Context = 2; 160 | } 161 | 162 | message FriendRequest{ 163 | int64 UserId = 1; 164 | string UserName = 2; 165 | } 166 | 167 | message SearchKeyWord { 168 | string Value = 1; 169 | } 170 | 171 | message SearchResult { 172 | repeated UserInfo List = 1; 173 | } 174 | 175 | message UserList { 176 | repeated UserInfo userInfos = 1; 177 | } 178 | 179 | message GameCreation { 180 | RoomInfo RoomInfo = 1; 181 | repeated SessionInfo PlayerSessions = 2; 182 | int64 MasterSessionId = 3; 183 | } 184 | 185 | 186 | message PemKey{ 187 | string TLS = 1; 188 | string SSL = 2; 189 | } 190 | 191 | message Empty{} 192 | 193 | 194 | message RoomPrepareView{ 195 | 196 | } 197 | 198 | 199 | 200 | 201 | 202 | message EntityInfo{ 203 | int64 Uuid = 1; 204 | int32 TeamNo = 4; 205 | Transform Transform = 2; 206 | int64 CharacterId = 3; 207 | Skill ActiveSkill = 5; 208 | string Motion = 6; 209 | } 210 | 211 | message Skill { 212 | bool Active = 1; 213 | string Name = 2; 214 | float Value = 3; 215 | } 216 | 217 | message HomeView { 218 | 219 | } 220 | 221 | message RoomSetting { 222 | int32 MaxPlayer = 1; 223 | string GameType = 2; 224 | string Name = 3; 225 | } 226 | 227 | 228 | message RoomList { 229 | repeated RoomReview item = 1; 230 | } 231 | 232 | message RoomInfo{ 233 | int64 Uuid = 1; 234 | string Name = 2; 235 | string GameType = 3; 236 | int64 OwnerUuid = 4; 237 | map UserInRoom = 5; 238 | map ReadyUser = 6; 239 | enum RoomStatus { 240 | Preparing = 0; 241 | OnPlaying = 1; 242 | Ending = 2; 243 | } 244 | RoomStatus Status = 7; 245 | int64 LeftMilliSecond = 8; 246 | } 247 | 248 | message RoomReview { 249 | int64 Uuid = 1; 250 | string Name = 2; 251 | string GameType = 3; 252 | int32 MaxPlayer = 4; 253 | int32 InRoomPlayer = 5; 254 | } 255 | 256 | message RoomContent { 257 | int64 Uuid = 1; 258 | map Players = 2; 259 | } 260 | 261 | message PlayerInfo { 262 | int64 CharacterCode = 1; 263 | int32 TeamNo = 2; 264 | Character Character = 4; 265 | int64 UserId = 5; 266 | string UserName = 6; 267 | bool IsReady = 7; 268 | } 269 | 270 | //character 即是沒有實體之腳色 271 | //entity 則藉由character來初始化 272 | message Character { 273 | int64 Uuid = 1; 274 | string CharacterType = 2; 275 | string Name = 3; 276 | Color Color =4; 277 | int32 Level = 5; 278 | int32 Exp = 6; 279 | float MaxHealth = 10; 280 | //基本能力值 281 | Ability Ability = 7; 282 | repeated Equipment Equipments = 8; 283 | //戰鬥時添加的狀態 284 | map Attr = 9; 285 | } 286 | 287 | message SessionInfo{ 288 | enum SessionState { 289 | NoSession = 0; 290 | Guest = 1; 291 | UserIdle = 2; 292 | UserInRoom = 3; 293 | ConnectingGame = 4; 294 | AgentServerWaitReconnect = 5; 295 | //in game server 296 | OnStart = 6; 297 | Playing = 7; 298 | GameOver = 8; 299 | GameServerWaitReconnect = 9; 300 | } 301 | enum SessionCapacity { 302 | GM = 0; 303 | RoomMaster = 1; 304 | RoomClient = 2; 305 | None = 3; 306 | } 307 | SessionState State = 1; 308 | SessionCapacity Capacity = 2; 309 | int64 Uuid = 5; 310 | SessionKey Key = 3; 311 | UserInfo UserInfo = 4; 312 | } 313 | 314 | message Success { 315 | bool ok = 1; 316 | } 317 | 318 | message AttrSetting { 319 | string Method = 1; 320 | string Key = 2; 321 | string Value = 3; 322 | } 323 | 324 | message SessionKey { 325 | string Value = 1; 326 | } 327 | 328 | message ServerInfo { 329 | enum Type { 330 | GameServer = 0; 331 | AgentServer = 1; 332 | } 333 | Type ServerType = 1; 334 | string PublicKey = 2; 335 | string Port = 3; 336 | string Addr = 4; 337 | SessionKey SessionKey = 5; 338 | int64 MaxConn = 6; 339 | int64 NowConn = 7; 340 | string GameTerrianName = 8; 341 | } 342 | 343 | message LoginInput { 344 | string UserName = 1; 345 | string Pswd = 2; 346 | } 347 | 348 | message RegistInput { 349 | string UserName = 1; 350 | string Pswd = 2; 351 | string Email = 3; 352 | } 353 | 354 | message UserInfo{ 355 | string UserName = 1; 356 | int64 Uuid = 2; 357 | map OwnCharacter = 3; 358 | int64 UsedCharacter = 4; 359 | } 360 | 361 | message UserState{ 362 | enum UserStatus { 363 | OnCreating = 0; 364 | Login = 1; 365 | Offline = 2; 366 | OnPlaying = 3; 367 | } 368 | UserStatus State = 1; 369 | } 370 | 371 | message CharacterSetting{ 372 | int64 Uuid = 1; 373 | Color Color = 2; 374 | repeated Equipment Equipments = 8; 375 | } 376 | 377 | 378 | 379 | 380 | message Color { 381 | int32 R = 1; 382 | int32 G = 2; 383 | int32 B = 3; 384 | } 385 | 386 | message Equipment{ 387 | string Name = 1; 388 | string Type =2 ; 389 | int64 Uuid = 3 ; 390 | repeated Color Colors = 4; 391 | Ability Ability =5; 392 | int32 CD =6;//Cool down time tick 393 | int32 Usable =7; 394 | int32 Inventory =8; 395 | } 396 | 397 | message Ability{ 398 | int32 ATK = 1; 399 | int32 DEF = 2; 400 | float SPD = 3; 401 | float TSPD = 6; 402 | int32 MP = 4; 403 | int32 MAKT = 5; 404 | } 405 | -------------------------------------------------------------------------------- /msg/message.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danie1Lin/Distributed-Golang-Game-Server/620f6c91b6e21db9f8eca88916b1130223ff8b81/msg/message.zip -------------------------------------------------------------------------------- /msg/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | protoc -I. --csharp_out . --grpc_out . message.proto --plugin=protoc-gen-grpc=/usr/sbin/grpc_csharp_plugin 3 | protoc --go_out=plugins=grpc:./ message.proto 4 | zip -r message.zip Message* message.proto 5 | 6 | -------------------------------------------------------------------------------- /rpctest/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pb "github.com/daniel840829/gameServer/proto" // 引入proto包 5 | 6 | "fmt" 7 | log "github.com/sirupsen/logrus" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | "os" 11 | ) 12 | 13 | func init() { 14 | // Log as JSON instead of the default ASCII formatter. 15 | log.SetFormatter(&log.TextFormatter{}) 16 | 17 | // Output to stdout instead of the default stderr 18 | // Can be any io.Writer, see below for File example 19 | log.SetOutput(os.Stdout) 20 | 21 | // Only log the warning severity or above. 22 | log.SetLevel(log.DebugLevel) 23 | } 24 | 25 | const ( 26 | // Address gRPC服务地址 27 | Address = "35.185.75.79:8080" 28 | ) 29 | 30 | func main() { 31 | // 连接 32 | conn, err := grpc.Dial(Address, grpc.WithInsecure()) 33 | 34 | if err != nil { 35 | fmt.Println(err) 36 | } 37 | 38 | defer conn.Close() 39 | 40 | // 初始化客户端 41 | c := pb.NewPacketClient(conn) 42 | 43 | // 调用方法 44 | reqBody := &pb.Pos{Id: "test", Vector3: &pb.Vector3{1.2, 1.3, 1.4}, Rotation: &pb.Rotation{}} 45 | r, err := c.SyncPostion(context.Background(), reqBody) 46 | if err != nil { 47 | fmt.Println(err) 48 | } 49 | log.Debug(r) 50 | } 51 | -------------------------------------------------------------------------------- /rpctest/invokeTest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type MyStruct struct { 9 | name string 10 | } 11 | 12 | type A interface { 13 | HI() 14 | } 15 | 16 | type B struct { 17 | Name string 18 | } 19 | 20 | func (b B) HI() { 21 | 22 | } 23 | 24 | func (s *MyStruct) Test(b B) string { 25 | return b.Name 26 | } 27 | 28 | func (this *MyStruct) GetName(str string) string { 29 | this.name = str 30 | return this.name 31 | } 32 | 33 | func main() { 34 | 35 | // 备注: reflect.Indirect -> 如果是指针则返回 Elem() 36 | // 首先,reflect包有两个数据类型我们必须知道,一个是Type,一个是Value。 37 | // Type就是定义的类型的一个数据类型,Value是值的类型 38 | 39 | // 对象 40 | s := "this is string" 41 | 42 | // 获取对象类型 (string) 43 | fmt.Println(reflect.TypeOf(s)) 44 | 45 | // 获取对象值 (this is string) 46 | fmt.Println(reflect.ValueOf(s)) 47 | 48 | // 对象 49 | var x float64 = 3.4 50 | 51 | // 获取对象值 () 52 | fmt.Println(reflect.ValueOf(x)) 53 | 54 | // 对象 55 | a := &MyStruct{name: "nljb"} 56 | 57 | // 返回对象的方法的数量 (1) 58 | fmt.Println(reflect.TypeOf(a).NumMethod()) 59 | 60 | // 遍历对象中的方法 61 | for m := 0; m < reflect.TypeOf(a).NumMethod(); m++ { 62 | method := reflect.TypeOf(a).Method(m) 63 | fmt.Println(method.Type) // func(*main.MyStruct) string 64 | fmt.Println(method.Name) // GetName 65 | fmt.Println(method.Type.NumIn()) // 参数个数 66 | fmt.Println(method.Type.In(1)) // 参数类型 67 | } 68 | 69 | // 获取对象值 (<*main.MyStruct Value>) 70 | fmt.Println(reflect.ValueOf(a)) 71 | 72 | // 获取对象名称 73 | fmt.Println(reflect.Indirect(reflect.ValueOf(a)).Type().Name()) 74 | 75 | // 参数 76 | i := "Hello" 77 | v := make([]reflect.Value, 0) 78 | v = append(v, reflect.ValueOf(i)) 79 | 80 | // 通过对象值中的方法名称调用方法 ([nljb]) (返回数组因为Go支持多值返回) 81 | fmt.Println("invoke: ", reflect.ValueOf(a).MethodByName("GetName").Call(v)) 82 | var c A 83 | c = B{"Daniel"} 84 | fmt.Println(c) 85 | v = make([]reflect.Value, 0) 86 | v = append(v, reflect.ValueOf(i)) 87 | fmt.Println("invoke2: ", reflect.ValueOf(a).MethodByName("Test").Call(v)) 88 | 89 | // 通过对值中的子对象名称获取值 (nljb) 90 | fmt.Println(reflect.Indirect(reflect.ValueOf(a)).FieldByName("name")) 91 | 92 | // 是否可以改变这个值 (false) 93 | fmt.Println(reflect.Indirect(reflect.ValueOf(a)).FieldByName("name").CanSet()) 94 | 95 | // 是否可以改变这个值 (true) 96 | fmt.Println(reflect.Indirect(reflect.ValueOf(&(a.name))).CanSet()) 97 | 98 | // 不可改变 (false) 99 | fmt.Println(reflect.Indirect(reflect.ValueOf(s)).CanSet()) 100 | 101 | // 可以改变 102 | // reflect.Indirect(reflect.ValueOf(&s)).SetString("jbnl") 103 | fmt.Println(reflect.Indirect(reflect.ValueOf(&s)).CanSet()) 104 | 105 | } 106 | -------------------------------------------------------------------------------- /rpctest/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | 6 | pb "github.com/daniel840829/gameServer/proto" // 引入编译生成的包 7 | 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | //"google.golang.org/grpc/grpclog" 11 | "fmt" 12 | 13 | log "github.com/sirupsen/logrus" 14 | "os" 15 | ) 16 | 17 | func init() { 18 | // Log as JSON instead of the default ASCII formatter. 19 | log.SetFormatter(&log.TextFormatter{}) 20 | 21 | // Output to stdout instead of the default stderr 22 | // Can be any io.Writer, see below for File example 23 | log.SetOutput(os.Stdout) 24 | 25 | // Only log the warning severity or above. 26 | log.SetLevel(log.DebugLevel) 27 | } 28 | 29 | const ( 30 | // Address gRPC服务地址 31 | Address = ":8080" 32 | ) 33 | 34 | type rpcService struct{} 35 | 36 | var rpc *rpcService = &rpcService{} 37 | 38 | func (r *rpcService) SyncPostion(ctx context.Context, in *pb.Pos) (*pb.PosReply, error) { 39 | log.Debug(ctx) 40 | log.Debug(in) 41 | return new(pb.PosReply), nil 42 | } 43 | 44 | func (r *rpcService) CallServer(ctx context.Context, in *pb.Callin) (*pb.Reply, error) { 45 | log.Debug(in) 46 | return new(pb.Reply), nil 47 | } 48 | func (r *rpcService) CallClient(in *pb.ClientStart, stream pb.Packet_CallClientServer) error { 49 | log.Debug(in) 50 | return nil 51 | } 52 | 53 | func main() { 54 | listen, err := net.Listen("tcp", Address) 55 | if err != nil { 56 | fmt.Println("failed to listen: %v", err) 57 | } 58 | 59 | // 实例化grpc Server 60 | s := grpc.NewServer() 61 | 62 | // 注册HelloService 63 | pb.RegisterPacketServer(s, &rpcService{}) 64 | 65 | fmt.Println("Listen on " + Address) 66 | 67 | s.Serve(listen) 68 | } 69 | -------------------------------------------------------------------------------- /service/AgentToGame.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | /* Server 4 | type AgentToGameServer interface { 5 | // SessionManager 6 | CreateSession(context.Context, *SessionInfo) (*Success, error) 7 | GetGameServerInfo(context.Context, *SessionKey) (*ServerInfo, error) 8 | GetRoomList(context.Context, *SessionKey) (*RoomList, error) 9 | } 10 | */ 11 | 12 | type ATGServer struct { 13 | } 14 | 15 | func (s *ATGServer) CreateSession(context.Context, *SessionInfo) (*Success, error) { 16 | return nil, nil 17 | } 18 | func (s *ATGServer) GetGameServerInfo(context.Context, *SessionKey) (*ServerInfo, error) { 19 | return nil, nil 20 | } 21 | func (s *ATGServer) GetRoomList(context.Context, *SessionKey) (*RoomList, error) { 22 | return nil, nil 23 | } 24 | 25 | /*Client 26 | type AgentToGameClient interface { 27 | // SessionManager 28 | CreateSession(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (*Success, error) 29 | GetGameServerInfo(ctx context.Context, in *SessionKey, opts ...grpc.CallOption) (*ServerInfo, error) 30 | GetRoomList(ctx context.Context, in *SessionKey, opts ...grpc.CallOption) (*RoomList, error) 31 | } 32 | */ 33 | 34 | type ATGClient struct { 35 | } 36 | 37 | func (c *ATGClient) CreateSession(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (*Success, error) { 38 | return nil, nil 39 | } 40 | func (c *ATGClient) GetGameServerInfo(ctx context.Context, in *SessionKey, opts ...grpc.CallOption) (*ServerInfo, error) { 41 | return nil, nil 42 | } 43 | func (c *ATGClient) GetRoomList(ctx context.Context, in *SessionKey, opts ...grpc.CallOption) (*RoomList, error) { 44 | return nil, nil 45 | } 46 | -------------------------------------------------------------------------------- /service/ClientToAgent.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | . "github.com/daniel840829/gameServer/msg" 5 | "github.com/daniel840829/gameServer/session" 6 | "golang.org/x/net/context" 7 | "google.golang.org/grpc/metadata" 8 | "strconv" 9 | ) 10 | 11 | /* 12 | type ClientToAgentServer interface { 13 | AquireSessionKey(context.Context, *Empty) (*SessionKey, error) 14 | AquireOtherAgent(context.Context, *Empty) (*ServerInfo, error) 15 | // Login 16 | Login(context.Context, *LoginInput) (*UserInfo, error) 17 | CreateAccount(context.Context, *RegistInput) (*Error, error) 18 | // UserSetting 19 | SetAccount(context.Context, *AttrSetting) (*Success, error) 20 | SetCharacter(context.Context, *AttrSetting) (*Success, error) 21 | // room 22 | AquireGameServer(context.Context, *Empty) (*ServerInfo, error) 23 | CreateRoom(context.Context, *RoomSetting) (*Success, error) 24 | JoinRoom(context.Context, *ID) (*Success, error) 25 | RoomReady(context.Context, *Empty) (*Success, error) 26 | // View 27 | UpdateHome(*Empty, ClientToAgent_UpdateHomeServer) error 28 | UpdateRoomList(*Empty, ClientToAgent_UpdateRoomListServer) error 29 | UpdateUserList(*Empty, ClientToAgent_UpdateUserListServer) error 30 | // rpc UpdateRoomInfo(SessionKey) returns (stream RoomInfoView) {} 31 | Pipe(ClientToAgent_PipeServer) error 32 | }*/ 33 | 34 | func NewAgentRpc() (agent *Agent) { 35 | agent = &Agent{} 36 | return 37 | } 38 | 39 | type Agent struct { 40 | Uuid int64 41 | } 42 | 43 | func (a *Agent) Init() { 44 | 45 | } 46 | 47 | func (a *Agent) AquireSessionKey(c context.Context, e *Empty) (*SessionKey, error) { 48 | id := session.Manager.MakeSession() 49 | return &SessionKey{Value: strconv.FormatInt(id, 10)}, nil 50 | } 51 | func (a *Agent) AquireOtherAgent(c context.Context, e *Empty) (*ServerInfo, error) { 52 | return nil, nil 53 | } 54 | 55 | // Login 56 | 57 | func (a *Agent) Login(c context.Context, in *LoginInput) (*UserInfo, error) { 58 | s := GetSesionFromContext(c) 59 | s.Lock() 60 | user := s.State.Login(in.UserName, in.Pswd) 61 | s.Unlock() 62 | return user.UserInfo, nil 63 | } 64 | func (a *Agent) CreateAccount(context.Context, *RegistInput) (*Error, error) { 65 | return nil, nil 66 | } 67 | 68 | // UserSetting 69 | func (a *Agent) SetAccount(context.Context, *AttrSetting) (*Success, error) { 70 | return nil, nil 71 | } 72 | func (a *Agent) SetCharacter(context.Context, *AttrSetting) (*Success, error) { 73 | return nil, nil 74 | } 75 | 76 | // allocate room 77 | func (a *Agent) AquireGameServer(context.Context, *Empty) (*ServerInfo, error) { 78 | return nil, nil 79 | } 80 | 81 | // View 82 | func (a *Agent) UpdateHome(*Empty, ClientToAgent_UpdateHomeServer) error { 83 | return nil 84 | } 85 | func (a *Agent) UpdateRoomList(*Empty, ClientToAgent_UpdateRoomListServer) error { 86 | return nil 87 | } 88 | func (a *Agent) UpdateUserList(*Empty, ClientToAgent_UpdateUserListServer) error { 89 | return nil 90 | } 91 | 92 | // rpc UpdateRoomInfo(SessionKey) returns (stream RoomInfoView) {} 93 | func (a *Agent) Pipe(ClientToAgent_PipeServer) error { 94 | return nil 95 | } 96 | func (a *Agent) CreateRoom(context.Context, *RoomSetting) (*Success, error) { 97 | return nil, nil 98 | } 99 | func (a *Agent) JoinRoom(context.Context, *ID) (*Success, error) { 100 | return nil, nil 101 | } 102 | func (a *Agent) RoomReady(context.Context, *Empty) (*Success, error) { 103 | return nil, nil 104 | } 105 | 106 | func GetSesionFromContext(c context.Context) *Session { 107 | md, ok := metadata.FromIncomingContext(c) 108 | if !ok { 109 | return nil 110 | } 111 | s := session.Manager.GetSession(md) 112 | 113 | return s 114 | } 115 | -------------------------------------------------------------------------------- /service/ClientToGame.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | /* 4 | type ClientToGameServer interface { 5 | // roomManager 6 | EnterRoom(context.Context, *ServerInfo) (*Success, error) 7 | LeaveRoom(context.Context, *Empty) (*Success, error) 8 | // entityManager 9 | PlayerInput(ClientToGame_PlayerInputServer) error 10 | // View 11 | UpdateRoomPrepareView(*Empty, ClientToGame_UpdateRoomPrepareViewServer) error 12 | UpdateGameFrame(*Empty, ClientToGame_UpdateGameFrameServer) error 13 | } 14 | */ 15 | 16 | type CTGServer struct { 17 | } 18 | 19 | func (s *CTGServer) EnterRoom(context.Context, *ServerInfo) (*Success, error) { 20 | return nil, nil 21 | } 22 | 23 | func (s *CTGServer) LeaveRoom(context.Context, *Empty) (*Success, error) { 24 | return nil, nil 25 | } 26 | 27 | // entityManager 28 | func (s *CTGServer) PlayerInput(ClientToGame_PlayerInputServer) error { 29 | return nil 30 | } 31 | 32 | // View 33 | func (s *CTGServer) UpdateRoomPrepareView(*Empty, ClientToGame_UpdateRoomPrepareViewServer) error { 34 | return nil 35 | } 36 | func (s *CTGServer) UpdateGameFrame(*Empty, ClientToGame_UpdateGameFrameServer) error { 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /setupEnv.sh: -------------------------------------------------------------------------------- 1 | # Set up these env var to connect mongodb 2 | export MOG_ADDR=0.0.0.0:27071 3 | export MGO_USER=username 4 | export MGO_PASS=1234 5 | export DONT_USE_KUBE=false -------------------------------------------------------------------------------- /storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/globalsign/mgo" 9 | //"github.com/globalsign/mgo/bson" 10 | //"os" 11 | //"regexp" 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | const ( 16 | MGO_DB_NAME = "gameServer" 17 | UserInfo_COLLECTION = "UserInfo" 18 | RegistInput_COLLECTION = "RegistInput" 19 | ) 20 | 21 | var ( 22 | _MGO_ADDR string = "" 23 | _MGO_USER string = "" 24 | _MGO_PASS string = "" 25 | ) 26 | 27 | var MgoDb *MongoDb = &MongoDb{} 28 | 29 | func init() { 30 | MgoDb.Init(MGO_DB_NAME, UserInfo_COLLECTION, RegistInput_COLLECTION) 31 | } 32 | 33 | type Db interface { 34 | Init(string, ...string) 35 | Save(string, interface{}) bool 36 | Update(string, interface{}, interface{}) bool 37 | Delete(string, interface{}) bool 38 | Find(string, interface{}) *mgo.Iter 39 | } 40 | 41 | type MongoDb struct { 42 | Session *mgo.Session 43 | Collections map[string]*mgo.Collection 44 | } 45 | 46 | type FileStore struct { 47 | FileName string 48 | Path string 49 | } 50 | 51 | type MgoError struct { 52 | Op string 53 | Err error 54 | } 55 | 56 | func (e *MgoError) Error() string { 57 | return e.Op + ":" + e.Err.Error() 58 | } 59 | 60 | func ReadMgoSettingFromEnv() { 61 | _MGO_PASS = os.Getenv("MGO_PASS") 62 | _MGO_USER = os.Getenv("MGO_USER") 63 | _MGO_ADDR = os.Getenv("MGO_ADDR") 64 | } 65 | 66 | func (m *MongoDb) Init(dbName string, collectionNames ...string) { 67 | ReadMgoSettingFromEnv() 68 | err := error(nil) 69 | defer func() { 70 | if r := recover(); r != nil { 71 | err := fmt.Errorf("%v", r) 72 | fmt.Printf("%T \n\r", err) 73 | a := &MgoError{"mgo:", errors.New("MgoInit")} 74 | log.Warn("", a.Error()) 75 | //panic("Please open mangodb server") 76 | } 77 | }() 78 | m.Collections = make(map[string]*mgo.Collection) 79 | 80 | m.Session, err = mgo.Dial(_MGO_ADDR) 81 | if err != nil { 82 | fmt.Println("mongodb connecting error :", err) 83 | } 84 | if err := m.Session.DB(dbName).Login(_MGO_USER, _MGO_PASS); err != nil { 85 | log.Fatal("Mgo connect error:", err, "/n Do you set env MGO_USER and MGO_PASS? or Do you create a User?") 86 | } 87 | for _, collectionName := range collectionNames { 88 | m.Collections[collectionName] = m.Session.DB(dbName).C(collectionName) 89 | fmt.Println(collectionName, " is register.") 90 | } 91 | } 92 | 93 | func (m *MongoDb) Save(collectionName string, data interface{}) bool { 94 | if _, ok := m.Collections[collectionName]; !ok { 95 | return false 96 | } 97 | m.Collections[collectionName].Insert(data) 98 | return true 99 | } 100 | 101 | func (m *MongoDb) Find(collectionName string, query interface{}) *mgo.Iter { 102 | if _, ok := m.Collections[collectionName]; !ok { 103 | log.Warn("No such Collection", collectionName) 104 | return nil 105 | } 106 | iter := m.Collections[collectionName].Find(query).Iter() 107 | return iter 108 | } 109 | 110 | func (m *MongoDb) Update(collectionName string, query interface{}, data interface{}) bool { 111 | if _, ok := m.Collections[collectionName]; !ok { 112 | return false 113 | } 114 | err := m.Collections[collectionName].Update(query, data) 115 | if err != nil { 116 | fmt.Println("db update error:", err) 117 | return false 118 | } 119 | return true 120 | 121 | } 122 | 123 | func (m *MongoDb) Delete(collectionName string, query interface{}) bool { 124 | if _, ok := m.Collections[collectionName]; !ok { 125 | return false 126 | } 127 | changelog, err := m.Collections[collectionName].RemoveAll(query) 128 | fmt.Println(changelog, err) 129 | if err != nil { 130 | return false 131 | } 132 | return true 133 | } 134 | -------------------------------------------------------------------------------- /storage/storage_test.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "fmt" 5 | "github.com/daniel840829/gameServer/service" 6 | //"github.com/globalsign/mgo" 7 | "github.com/globalsign/mgo/bson" 8 | "testing" 9 | ) 10 | 11 | func TestMgo(t *testing.T) { 12 | m := &MongoDb{} 13 | m.Init("Testing_OnlineGame", "UserInfo", "Character") 14 | m.Save("UserInfo", &service.UserInfo{Uuid: "123", UserName: "Daniel"}) 15 | iter := m.Find("UserInfo", bson.M{"uuid": "123"}) 16 | r := &service.UserInfo{} 17 | for iter.Next(r) { 18 | fmt.Println(r) 19 | } 20 | if err := iter.Close(); err != nil { 21 | return 22 | } 23 | m.Update("UserInfo", bson.M{"uuid": "123"}, bson.M{"$set": bson.M{"uuid": "456"}}) 24 | m.Delete("UserInfo", bson.M{"uuid": "456"}) 25 | } 26 | -------------------------------------------------------------------------------- /timeCalibrate/timeCalibration.go: -------------------------------------------------------------------------------- 1 | package timeCalibration 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "runtime" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type TimeCalibration struct { 11 | RunningNoMapUserId sync.Map 12 | Proccess sync.Map 13 | } 14 | 15 | func (tc *TimeCalibration) FromClientTimeDelay(userId int64, t int64) { 16 | 17 | } 18 | 19 | func (tc *TimeCalibration) ToClientTimeDelay(userId int64, t int64) { 20 | 21 | } 22 | 23 | type Proccess struct { 24 | RunningNo int64 25 | RpcFuncName string 26 | StageTime []*StageInfo 27 | } 28 | 29 | type StageInfo struct { 30 | Fn string 31 | } 32 | 33 | func (p *Proccess) String() { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | . "github.com/daniel840829/gameServer/msg" 5 | "github.com/daniel840829/gameServer/storage" 6 | . "github.com/daniel840829/gameServer/uuid" 7 | "github.com/globalsign/mgo/bson" 8 | "github.com/golang/protobuf/proto" 9 | log "github.com/sirupsen/logrus" 10 | "math/rand" 11 | "sync" 12 | ) 13 | 14 | func NewUserManager(db storage.Db) *UserManager { 15 | return &UserManager{ 16 | UserOnline: make(map[int64]*User), 17 | Db: db, 18 | } 19 | } 20 | 21 | type UserManager struct { 22 | sync.RWMutex 23 | UserOnline map[int64]*User 24 | Db storage.Db 25 | } 26 | 27 | func (um *UserManager) Login(in *LoginInput) (*UserInfo, error) { 28 | iter := um.Db.Find(storage.RegistInput_COLLECTION, bson.M{"username": in.UserName}) 29 | userPassword := &RegistInput{} 30 | userInfo := &UserInfo{} 31 | if iter.Next(userPassword) { 32 | if userPassword.Pswd == in.Pswd { 33 | UserInfoIter := um.Db.Find(storage.UserInfo_COLLECTION, bson.M{"username": in.UserName}) 34 | if UserInfoIter.Next(userInfo) { 35 | user := &User{} 36 | user.Login(userInfo) 37 | um.Lock() 38 | um.UserOnline[userInfo.Uuid] = user 39 | um.Unlock() 40 | log.Debug("[User][Login]", userInfo) 41 | return userInfo, nil 42 | } 43 | } 44 | } 45 | return nil, nil 46 | } 47 | 48 | func (um *UserManager) Logout(userId int64) { 49 | um.Lock() 50 | user := um.UserOnline[userId] 51 | _ = user 52 | delete(um.UserOnline, userId) 53 | user = nil 54 | um.Unlock() 55 | } 56 | 57 | func (um *UserManager) Regist(in *RegistInput) (*Error, error) { 58 | if iter := um.Db.Find(storage.RegistInput_COLLECTION, bson.M{"username": in.UserName}); iter.Next(&RegistInput{}) { 59 | return &Error{ErrMsg: "Username exists"}, nil 60 | } 61 | userInfo := NewUserInfo(in.UserName) 62 | um.Db.Save(storage.UserInfo_COLLECTION, userInfo) 63 | um.Db.Save(storage.RegistInput_COLLECTION, in) 64 | log.Debug(userInfo) 65 | return &Error{}, nil 66 | } 67 | 68 | func (um *UserManager) GetUserInfo(id int64) *UserInfo { 69 | um.RLock() 70 | userInfo := um.UserOnline[id].GetInfo() 71 | um.RUnlock() 72 | return userInfo 73 | } 74 | 75 | type User struct { 76 | UserInfo *UserInfo 77 | sync.RWMutex 78 | } 79 | 80 | func (u *User) Login(userInfo *UserInfo) { 81 | u.Lock() 82 | u.UserInfo = userInfo 83 | u.Unlock() 84 | } 85 | 86 | func (u *User) GetInfo() *UserInfo { 87 | u.RLock() 88 | userInfo, _ := proto.Clone(u.UserInfo).(*UserInfo) 89 | u.RUnlock() 90 | return userInfo 91 | } 92 | 93 | func (u *User) SetCharacter(setting *CharacterSetting) bool { 94 | if c, ok := u.UserInfo.OwnCharacter[setting.Uuid]; ok { 95 | c.Equipments = setting.Equipments 96 | c.Color = setting.Color 97 | u.UserInfo.UsedCharacter = setting.Uuid 98 | log.Debug(u.UserInfo) 99 | Manager.Db.Update(storage.UserInfo_COLLECTION, bson.M{"username": u.UserInfo.UserName}, u.UserInfo) 100 | var userInfo *UserInfo 101 | iter := Manager.Db.Find(storage.UserInfo_COLLECTION, bson.M{"username": u.UserInfo.UserName}) 102 | if iter.Next(userInfo) { 103 | log.Debug("[Userinfo Update]", userInfo) 104 | } 105 | return true 106 | } else { 107 | return false 108 | } 109 | } 110 | func NewCharacter() (c *Character) { 111 | c = &Character{} 112 | uuid, _ := Uid.NewId(CHA_ID) 113 | c.Uuid = uuid 114 | c.Color = &Color{int32(rand.Intn(256)), int32(rand.Intn(256)), int32(rand.Intn(256))} 115 | c.CharacterType = "Player" 116 | c.MaxHealth = 100.0 117 | c.Ability = &Ability{} 118 | c.Ability.SPD = 1.0 119 | c.Ability.TSPD = 1.0 120 | return 121 | } 122 | 123 | func NewUserInfo(userName string) (u *UserInfo) { 124 | uuid, _ := Uid.NewId(USER_ID) 125 | u = &UserInfo{OwnCharacter: make(map[int64]*Character)} 126 | u.Uuid = uuid 127 | u.UserName = userName 128 | c := NewCharacter() 129 | u.OwnCharacter[c.Uuid] = c 130 | u.UsedCharacter = c.Uuid 131 | return 132 | } 133 | 134 | var Manager *UserManager 135 | 136 | func init() { 137 | Manager = NewUserManager(storage.MgoDb) 138 | } 139 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | ) 6 | -------------------------------------------------------------------------------- /uuid/uuid.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "github.com/zheng-ji/goSnowFlake" 6 | ) 7 | 8 | const ( 9 | GM_ID = "GameManager" 10 | ROOM_ID = "Room" 11 | USER_ID = "User" 12 | ENTITY_ID = "Entity" 13 | EQUIP_ID = "Equipment" 14 | CHA_ID = "Character" 15 | SESSION_ID = "Session" 16 | ) 17 | 18 | type UID struct { 19 | Gen map[string]*goSnowFlake.IdWorker 20 | WorkersName map[int64]string 21 | } 22 | 23 | func (u *UID) RegisterWorker(workers ...string) { 24 | u.Gen = make(map[string]*goSnowFlake.IdWorker) 25 | u.WorkersName = make(map[int64]string) 26 | err := error(nil) 27 | for idx, worker := range workers { 28 | u.Gen[worker], err = goSnowFlake.NewIdWorker(int64(idx + 1)) 29 | u.WorkersName[int64(idx+1)] = worker 30 | if err != nil { 31 | log.Fatal(err) 32 | break 33 | } 34 | } 35 | } 36 | 37 | func (u *UID) NewId(worker string) (id int64, err error) { 38 | id, err = u.Gen[worker].NextId() 39 | return 40 | } 41 | 42 | func (u *UID) ParseId(id int64) (worker string, ts int64) { 43 | _, ts, workerId, _ := goSnowFlake.ParseId(id) 44 | worker, ok := u.WorkersName[workerId] 45 | if !ok { 46 | worker = "" 47 | } 48 | return 49 | } 50 | 51 | var Uid *UID = &UID{} 52 | 53 | func init() { 54 | Uid.RegisterWorker(GM_ID, ROOM_ID, USER_ID, CHA_ID, ENTITY_ID, EQUIP_ID, SESSION_ID) 55 | } 56 | --------------------------------------------------------------------------------