Digital Ocean 응용 프로그램에 설치된 Godot 서버 + 클라이언트 게임
32821 단어 digitaloceangamedevgodot
이 게임은 온라인 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 버전을 정상적으로 작동하지 못하게 했다. 이것은 내가 원하는 게임이다.
Reference
이 문제에 관하여(Digital Ocean 응용 프로그램에 설치된 Godot 서버 + 클라이언트 게임), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/amireldor/a-godot-server-client-game-deployed-on-digitalocean-apps-1n3c텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)