Digital Ocean 응용 프로그램에 설치된 Godot 서버 + 클라이언트 게임

Digital Ocean 응용 프로그램에서 Godot 클라이언트/서버 게임을 구축하는 방법을 알려 드리겠습니다.응, 지금은 게임이 아니라 실험인 것 같지만, 나는 이것이 공유할 만하다고 생각한다. 왜냐하면 인터넷상의 Godot 서버 자원이 매우 희소한 것 같고, 이런 서버를 배치하는 것은 말할 것도 없기 때문이다.
이 게임은 온라인 1인 지속 우주 게임이 될 것이다.서버는 여러 명의 유저가 같은 서버에서 노는 것을 지원해야 하며, 모든 사람이 그들의 샌드박스에 있어야 한다.
처음에 Python과 Phaser3를 선택하여 개발을 진행했을 때, 나의 합작 파트너는 나에게 왜 기존의 게임 엔진을 사용하지 않느냐고 겸손하게 물었다. 나는 그럴듯한 답이 하나도 없었다.인터넷에 올라온 명언을 떠올려보자. "엔진이 아니라 게임을 만들어라."나는 적어도 게임 엔진으로 이런 게임을 위해 개념 검증을 하기로 결정했다.고도에 들어가다.왜 단결하지 않습니까?참조further down this article.

Godot "universal" 서버/클라이언트 공간 게임


Godot's High-Level multiplayer networking API를 사용하여 서버와 클라이언트 간의 동기화 상태, 피어 간에 양방향 명령을 실행하는 등 엔진에서 작업을 처리할 수 있습니다.클라이언트는 "항성 시스템 데이터 얻기"또는 "우주선을 행성 X로 운전하기"를 보낼 수 있으며, 서버는 무작위'우주용 공격'사건이나 게임과 관련된 다른 데이터와 논리를 보고할 수 있습니다.
게임의 요구 사항 중 하나는 브라우저에서 실행되기 때문에 저는 GDScript 코드에서 WebSocketMultiplayerPeer 클래스를 선택했습니다.

우리 일을 연결합시다


우리는 개발자입니다. 저는 말을 적게 하고 코드를 많이 말할 것입니다.
func _ready():
    var peer: WebSocketMultiplayerPeer
    if OS.has_feature("Server") or "--server" in OS.get_cmdline_args():
        peer = WebSocketServer.new()
        peer.active_relay = false  # see note below
        peer.listen(4321, [], true)
    else:
        var server_url = 'ws://localhost:4321'
        if OS.has_feature('release'):
            server_url = "wss://this.will.be.your.digitalocean.apps.thing.soon/server"
        peer = WebSocketClient.new()
        peer.connect_to_url(server_url, [], true, [])

    if peer == null:
        OS.exit_code = 1
        print("Failed initializing peer :( so can't network things")
        get_tree().quit()

    get_tree().network_peer = peer
    if get_tree().is_network_server():
        print("I am the mighty server")
        var ok
        ok = get_tree().connect("network_peer_connected", self, "_peer_connected")
        assert(ok == OK)
        ok = get_tree().connect("network_peer_disconnected", self, "_peer_disconnected")
        assert(ok == OK)
    else:
        print("I am an unworthy client")
        var ok
        ok = get_tree().connect("connected_to_server", self, "_connected_to_server")
        assert(ok == OK)
        ok = get_tree().connect("connection_failed", self, "_connection_failed")
        assert(ok == OK)
peer.active_relay = false 명령은 서버가 각 피어 포인트의 연결을 다른 모든 피어 포인트에 브로드캐스트하지 않도록 합니다(감사합니다 LordDaniel09.
저의 모든 OK 단언을 용서해 주십시오. 저는 고도에 대해 초보입니다. 저는 제가 무엇을 하고 있는지 모르겠습니다. 컴파일러가 저에게 이렇게 하라고 경고했습니다.
이 코드는 일부 기능이나 명령행 매개 변수에 따라 서버를 초기화하거나 서버에 연결합니다.서버를 실행하려면 프로젝트 폴더에서 다음을 실행하십시오.
godot-server
너는 server build의 고도가 필요하다.이것이 바로 우리가 최종적으로 Digital Ocean 응용 프로그램에서 Dockerfile을 사용하여 배치할 내용이다.
실행 클라이언트는 실행 godot 하거나 편집기에서 게임을 실행합니다.분명히 나도 HTML5 버전을 원한다. 그래서 나는 이 게임을 일찌감치 내보냈고 이 모든 웹소켓의 미친 짓을 확보했다.

새 게임 만들기 또는 기존 게임 추가


클라이언트에서 우리가 서버에 연결할 때, 우리는 로컬에서 게임 장면을 만들고 서버(대등 id1)에 게임123을 만들거나 이미 실행된 게임에 가입하도록 요구합니다.인증, 유일한 게임 ID와 게임 -> 유저 관련은 여전히 제가 고려하는 문제입니다.More on that soon .
서버의 차원 구조 (곧 유사한 노드를 만들 것) 가 두 개의 대등한 노드에 대해 같도록 로컬 장면이 필요합니다.이것은 고도가 고급 다인 마술을 할 때 기쁘게 한다. 적어도 나는 이렇게 이해한다.
func _connected_to_server():
    print("Connected to server!")
    # TODO: Should explicitly ask to make a new game or join
    var game_id = 123  # get from somewhere
    var game = load("res://Game.tscn").instance()
    game.set_name(str(game_id))
    $Games.add_child(game)
    rpc_id(1, "_join_game_or_new", 123)
계층 구조는 다음과 같습니다.

서버에서:
remote func _join_game_or_new(game_id: int):
    var peer_id = get_tree().get_rpc_sender_id()
    print("%d wants to join game %d" % [peer_id, game_id])
    # TODO: make sure peer allowed to join this game

    var game_node_path = "./Games/%d" % game_id
    if has_node(game_node_path):
        pass # What now?
    else:
        print("Creating game %d" % game_id)
        var game = load("res://Game.tscn").instance()
        game.set_name(game_id)
        $Games.add_child(game)
따라서 이 점에서 클라이언트와 서버는 차원 구조를'동의'했고 게임은 시작할 수 있다(또는 계속, 우리는 볼 수 있다).Game 장면은 Solar System 장면을 포함한다.미래에 우리는 Galaxy 장면이나 Planet 또는 우리가 필요로 할 수 있는 다른 스크린이 있을 것이다.Solar System 장면에서 우리는 재미있는 즐거움을 누리기 시작했다.그 장면을 봅시다_ready().
func _ready():
    if is_network_master():
        print("Randomizing a solar system")
        var rng = RandomNumberGenerator.new()
        rng.randomize()
        # Following is very specific mock-ish logic code just for the sake of having something on the screen
        var radius = 200
        for _index in range(rng.randi_range(2, 10)):
            var planet = load('res://Planet.tscn').instance()
            planet.set_planet_kind(rng.randi()%20 + 1)
            var pos = Vector2(radius, 0)
            pos = pos.rotated(rng.randf_range(0, 2*PI))
            planet.position = pos
            # this is stupid:
            planet_angle_inc.append(rng.randf_range(min_angle_inc, max_angle_inc))
            radius += 230
            $Planets.add_child(planet)
    else: 
        call_deferred('rpc_id', 1, "get_planets")
클라이언트의 경우 언제든지 GUI를 제외하고는 어떠한 방식으로도 네트워크가 연결되지 않는 노드의 네트워크 마스터가 아닙니다. 저는 get_planets 호출call_deferred 을 사용합니다.get_planets의 실현에서 보듯이 클라이언트는 장면이 존재해야 하기 때문에 _ready() 이 호출을 진행할 때 실행되었을 것입니다.그래, 이것이 바로 내가 자신에게 말한 것이다. 왜냐하면call_지연이 없기 때문이다. 나는'Failed to get path from RPC'오류가 있다.
remote func get_planets():
    var peer_id = get_tree().get_rpc_sender_id()
    print("Sending planets to peer %d" % peer_id)

    # not sure if this is the best way to serialize things
    # If you have Godot magic I'm unaware of please let me know
    var serialized = {"planets": []}

    var planets = $Planets.get_children()

    for index in range(len(planets)):
        serialized["planets"].append(
            {
                "position": [planets[index].position.x, planets[index].position.y],
                "angle_inc": planet_angle_inc[index],
                "kind": planets[index].get_planet_kind(),
                "rotation": planets[index].get_rotation(),
                "rotation_speed": planets[index].rotation_speed,
            }
        )

    rpc_id(peer_id, "set_planets", serialized)
행성은 항성을 둘러싸고 회전한다.POC의 일부로서 클라이언트와 서버 간에 일치하는 상태를 확보하고 싶습니다. 클라이언트를 새로 고치거나 다시 시작할 때도 마찬가지입니다.예를 들어 만약에 행성이'3시'를 막 넘었다면 나는 그 후에 잠시 다시 연결할 것이다. 나는 이 행성이 새로운 무작위 위치에 있는 것이 아니라'4시'에 더 가까워지는 것을 볼 수 있다.
주의해야 할 것은 Solar System 장면이 서버에서 한 번 초기화되었기 때문에 나는 그것이 게임의 생명 주기 내에 활발하게 유지되도록 하려고 한다.이것은 전체 우주 시뮬레이션이 백그라운드에서 계속 운행되어야 할 것이다.그러나 클라이언트는 은하계의 시스템을 통과하기 때문에 새로운 Solar System 장면을 삭제하고 만들 것입니다.클라이언트는 우주의 모든 비밀을 알 필요가 없다. 이것은 자원에 대한 낭비이다. (모바일 브라우저를 생각해 봐.)
remote func set_planets(serialized):
    for serial in serialized["planets"]:
        var planet = load("res://Planet.tscn").instance()
        planet.set_planet_kind(serial["kind"])
        var pos = Vector2(serial["position"][0], serial["position"][1])
        planet.position = pos
        planet.rotate(serial["rotation"])
        planet.rotation_speed = serial["rotation_speed"]
        planet_angle_inc.append(serial["angle_inc"])
        $Planets.add_child(planet)

    first_sync_done = true
이때 서버와 클라이언트는 같은 상태를 가져야 한다.가장 묘한 것은 우리가 같은 엔진에서 게임을 실행하기 때문에 천체의 시뮬레이션은 동기화되고 행성의 위치를 끊임없이 업데이트할 필요가 없기 때문이다.
func _physics_process(delta):
    var planets = $Planets.get_children()
    for index in range(len(planets)):
        var planet = planets[index]
        var pos = planet.position
        pos = planet.position.rotated(planet_angle_inc[index] * delta)
        planet.position = pos
단, 나는 10초마다 클라이언트에서 서버에 추가 get_planets 호출을 추가하여 우리가 진정으로 동기화할 수 있도록 할 것이다.

서버 및 정적 HTML5 웹 사이트를 DigitalOcean 애플리케이션에 배포


따라서 Digital Ocean 응용 프로그램 PaaS에서 이 기능을 실행할 수 있는 유일한 선택은 Dockerfile입니다.실제로는 두 개의 Dockerfiles입니다.하나는 메인 서버이고, 다른 하나는 게임을 구축하는 HTML5 버전이며, 이를 플랫폼의 정적 사이트에 위탁 관리한다.
DockerHub를 검색할 때, 나는 barichello/godot-ci 리셋 프로토콜을 발견했다. 그것은 나의 취약한 Docerfile 버전의 절호의 대체품이다.기본적으로 제 Dockerfile과 Repo는 다운로드 godot-headless 의 복사본을 만들었습니다. 예를 들어 HTML5 구축과 같은 것들을 구축할 수 있습니다.
우리가 말한 Dockerfile.html5부터 시작합시다. 왜냐하면 그것은 좀 더 쉽기 때문입니다.
FROM barichello/godot-ci

WORKDIR /game
COPY . ./src/

RUN mkdir build && godot --path ./src/ --export HTML5 /game/build/index.html && rm -r src/
우리는 프로젝트를 복사해서 내용을 구축 폴더에 구축한 다음 코드를 삭제하여 이미지를 더욱 간소화합니다.이 구축은 독립적이어서 HTML5에 대해서도 코드가 전혀 필요하지 않다.
게임 서버의 경우 다운로드godot-server가 필요합니다. 따라서 Dockerfile은 다음과 같이 보입니다.
FROM barichello/godot-ci

RUN wget https://downloads.tuxfamily.org/godotengine/3.2.3/Godot_v3.2.3-stable_linux_server.64.zip && \
    unzip Godot_v3.2.3-stable_linux_server.64.zip && \
    mv ./Godot_v3.2.3-stable_linux_server.64 /opt/godot-server

WORKDIR /game

COPY . ./src
RUN godot --path ./src --export-pack Linux /game/game.pck && rm -r ./src

EXPOSE 4321
CMD [ "/opt/godot-server", "--main-pack", "/game/game.pck" ]
구축 경로가 정확하고 Digital Ocean 응용 프로그램을 위해 정적 사이트 폴더를 선택하기 위해 시도와 오류를 했지만 결국 성공했습니다.

Digital Ocean의 디테일을 보여드릴게요.


서버를 배치하는 것은 상당히 간단하다.정적 웹 사이트에 대해 나는 정적 구축에 Dockerfile 경로를 제공하기 위해 doctl 응용 프로그램의 규범을 편집해야 한다.
이러한 명령은 다음과 유사합니다.
doctl apps list
# take note of your app id
doctl apps spec get YOUR_APP_ID > do-apps-spec.yaml
# ...or any other filename, not sure how DO want you to call it
그리고 정적 사이트의 부분을 편집하고 Dockerfile_path 및 현재 필요한 output_dir 를 추가합니다.
domains:
  - domain: this.is.stil.unpublished.com
    type: PRIMARY
name: your-repo-name
region: fra
services:
  - dockerfile_path: Dockerfile
    github:
      branch: live
      deploy_on_push: true
      repo: me/repo-name
    http_port: 4321
    instance_count: 1
    instance_size_slug: basic-xxs
    name: nice-server
    routes:
      - path: /server
static_sites:
  - envs:
      - key: SERVER_URL
        scope: BUILD_TIME
        value: wss://the-digital-ocean-link-for-the-server/server
    github:
      branch: live
      deploy_on_push: true
      repo: me/repo-name
    name: web
    routes:
      - path: /web
    dockerfile_path: Dockerfile.html5
    output_dir: /game/build
응용 프로그램의 사양을 업데이트하려면 다음과 같이 하십시오.
doctl apps update $APPS_ID --spec do-apps-spec.yaml
내 생각에는 그렇다!
이것은 좀 고통스러웠지만, 결국 나는 시합에서 이겼다.내가 브라우저를 새로 고쳤을 때, 나는 행성의 상태를 얻었다. 왜냐하면 그것들은 나의 작은 지구적인 우주의 서버에 있고, 내가 주동적으로 그것에 연결되지 않았을 때, 그것은 줄곧 돌고 있기 때문이다.

번호를 맞추다.


내가 보여줄게.
농담
나는'복제 및 실행'저장소로서의 전체 코드가 군더더기라고 생각한다.나는 너에게 약간의 생각과 단편을 주었다. 만약 네가 이 방면에서 실천을 많이 한다면, 너는 더욱 잘 독학할 것이다.

인증 및 게임 ID


나는 외부 auth 서비스, 예를 들어Auth0을 사용하여 나를 위해 귀여운 jwt를 만드는 경향이 있다.그리고 나는 Godot이 이 JWT를 검증하는 방법이 필요하다.
클라이언트가 서버에 연결될 때 나는 C# 또는 C++ 모듈을 사용하여 건의here에 따라 JWT 검증을 처리하거나 상대방이 우주의 비밀을 알기 전에 어떤 구름 함수를 사용하여 영패를 검증할 수 있다.

왜 단결하지 않습니까?


내가 고도를 선택한 데는 두 가지 이유가 있다.
첫째, 이것은 기원된 것이다. 만약에 내가 이 게임을 어딘가에 놓을 수 있다면'Godot 제작'이라는 플래카드가 이 두 프로젝트에 모두 좋을 것이라고 생각한다.
그 다음으로 현재 Unity의 네트워크 스택을 다시 작성하고 있습니다.사용되지 않는 API를 사용하고 싶지 않습니다.누군가는 내가 Mirror 를 사용하여 유니티를 실현할 수 있다고 말할 수도 있다. 그들은 옳다. 그러나 몇 달 전에 내가 HTML5를 사용하려고 시도했을 때 나는 HTML5 버전을 정상적으로 작동하지 못하게 했다. 이것은 내가 원하는 게임이다.

좋은 웹페이지 즐겨찾기