SMAPI Document 중 Multiplayer 관련 내용을 살펴보자.
아쉽게도 Multiplayer API는 아직 빈약한 편이라고 한다. 추후 더 다양한 기능을 지원할 예정.
반쯤 자면서 쓴거라... 내일 일어나서 다시 읽어보면 머리가 아파올거다🙄 요즘 너무 과로하는 것 같아
게임이 가끔 unique한 64비트 정수인 'multiplayer ID'를 필요로 할 때가 있다. 농장에 동물을 생성하거나 낚시로 물고기를 잡는 등의 상황에서 데이터 sync를 위해 multiplayer ID가 필요하다.
아래처럼 사용하면 된다.
int animalID = this.Helper.Multiplayer.GetNewID(); var animal = new FarmAnimal("CoW", animalID, ownerID);
멀티플레이어 모드에선 한 세션에서 모든 location을 동기화하지 않는다. 각각의 플레이어는 자신이 위치한 곳(농장, 빌딩 등) 만 서버와 동기화한다. 동기화 되고 있는 location의 리스트를 얻을 수 있다.
foreach(GameLocation location in this.Helper.Multiplayer.GetActiveLocations()) { // do stuff }
GetConnectedPlayer(playerID)
와 GetConnectedPlayers()
메서드를 이용하면 현재 서버와 연결되어 있는 유저들의 기본적인 정보를 받아올 수 있다. 플레이어가 서버에 연결한 순간, 특히 게임이 연결을 승인하기도 전이라도 사용할 수 있다. 대부분의 정보는 타겟 사용자가 SMAPI를 사용하고 있을 때 얻을 수 있다. 아래는 접근할 수 있는 필드 목록이다.
field | type | description |
---|---|---|
PlayerID | long | unique한 player ID를 요구하는 메서드나 필드에 전달할 수 있는 고유 multiplayer ID. |
IsHost | bool | 지금 이 플레이어가 서버를 호스팅하고 있으면 True , 그렇지 않으면 False |
IsSplitScreen | bool | 플레이어가 스타듀 밸리를 split-screen 모드로 같은 컴퓨터에서 실행중일때 |
HasSmapi | bool | 플레이어가 SMAPI를 설치한 상태인가? |
Platform | GamePlatform | (SMAPI 필요.) 플레이어의 운영체제가 GamePlatform enum 참조에 포함될 때 해당 값: Linux, Mac, Windows 중 하나 |
GameVersion | ISemanticVersion | (SMAPI 필요.) 설치한 스타듀 밸리의 버전 (예: 1.5.4 ). |
ApiVersion | ISemanticVersion | (SMAPI 필요.) 설치한 SMAPI의 버전 (예: 2.11 ). |
Mods | IEnumerable<IMultiplayerPeerMod> | (SMAPI 필요.) 설치한 mod들의 목록. 목록 요소(mod)들은 name, unique ID, 버전을 담고 있다. 다른 플레이어가 SMAPI를 설치하지 않았다면 이 값은 null 이 된다. 다른 플에이어들이 **SMAPI를 설치하였더라도 mod가 없다면 빈 리스트가 된다. |
ScreenID | int? | IsSplitMode 가 true 일 때, 플레이어의 screen ID. |
GetMod(id) | method returns IMultiplayerPeerMod | (SMAPI 필요.) (사용 가능한 경우) SMAPI와 동일한 대소문자 규칙을 사용하여 해당 모드와 그 모드의 ID를 반환한다. 예를 들어 모드가 설치되어있는지 확인하려면 다음과 같이 한다: if (peer.GetMod("Pathoschild.ContentPatcher") != null) . |
SendMesasge
메서드를 이용하여 (자신을 포함한) 모든 연결된 컴퓨터로 메세지를 보낼 수 있다. 메세지의 목적지는 아주 좁을 수도(컴퓨터 한 대 위의 mod 하나), 넓을 수도(모든 컴퓨터의 모든 mod) 있다. 범위는 자유롭게 지정할 수 있다. 메세지는 mod 그 자신으로 되돌아갈 수 없다. 대신 다른 컴퓨터의 같은 mod로 메세지를 보낼 수 있다.
메시지를 보낼 때 3가지를 분명히 해야한다.
보낼 데이터를 품은 데이터 모델. 숫자나 문자같은 단순 값이나 클래스 인스턴스는 적절한 예다. 커스텀 생성자로 만든 인스턴스의 경우 이것이 기본 생성자를 포함하고 있는지 확인하라.
메시지 타입. 메세지를 보낼 모드가 어떤 메세지를 보낼지 알 수 있도록 하라. 전역적으로 unique할 필요까진 없다. mod는 원래의 mod ID를 체크한다.
누가 메시지를 받아야 하는가 개발자는 player ID, mod ID들을 마음대로 조합할 수 있다. 기본적으로 메세지는 모든 mod와 player에게 전달된다.
예를 들어서:
// broadcast a message to all mods on all computers MyMessage message= new MyMessage(...); // create your own class with the data to send this.Helper.Multiplayer.SendMessage(message, "MyMessageType");
// send a message to a specific mod on all computers MyMessage message = new MyMessage(...); this.Helper.Multiplayer.SendMessage(message, "MyMessageType", modIDs: new[] { this.ModManifest.UniqueID });
helper.Events.Multiplayer.ModMessageReceived
event를 listen하면 메시지를 받을 수 있다. event arguments는 누가 메시지를 보냈는지를 지정하기 때문에 메시지를 알맞는 데이터 모델로 읽을 수 있다.
> public override void Entry(IModHelper helper){
helper.Events.Multiplayer.ModMessageReceived += this.onModMessageReceived;
public void OnModMessageReceived(object sender, ModMessageReceivedEventArgs e)
{
if (e.FromModID == this.ModManifest.UniqueID && e.Type == "ExampleMessageType")
{
MyMessageClass message = e.ReadAs<MyMessageClass>();
// handle message fields here
}
}
Split-screen multiplayer방식으로 플레이하는 경우, 각각의 플레이어로 전체 게임 state를 분할하기 때문에 mod는 플레이어 마다 따로 적용된다. 예를 들어 local player 4명이 있을 때 UpdateTicked
event는 각 tick마다 4번 일어난다. 게임은 그 자신의 상태를 자동으로 (예를 들어 Game1.activeClickableMenu
를 이용하는 식으로) 관리하지만, 여러분의 mod가 자신의 필드에 정보를 저장한다면 이런 상황을 직접 핸들링해줘야 한다.
SMAPI는 이런 상황을 위해 PerScreen<T>
를 준비했다. readonly
필드를 만들어서 사용하면 된다. 이 필드에는 int
부터 시작해서 클래스 인스턴스까지 정말 다양한 값을 자유롭게 넣을 수 있다.
private readonly PerScreen<int> LastPlayerId = new PerScreen<int>(); // defaults to 0 private readonly PerScreen<SButton> LastButtonPressed = new PerScreen<SButton>(createNewState: () => SButton.None); // defaults to the given value
아래 메서드와 프로퍼티를 사용할 수 있다.
member | description |
---|---|
value | 현재 screen의 값을 얻거나 지정. 필요하다면 그 값을 자동으로 생성한다. |
GetActiveValues | 값을 갖고 있는 모든 active screen에 대한 screen ID와 value를 얻는다 |
GetValueForScreen(screenId) SetValueForScreen(screenId, value) | 특정 screen ID에 대한 값을 얻거나 지정한다. 필요하다면 새로 만든다. |
ResetAllScreens | 모든 screen을 참조하기 위해 저장된 값을 초기화한다 |
예를 들어, 이 mod가 간단한 게임을 한다고 생각해보자: 메인 player는 키를 누르고, 다른 player들은 어떤 키를 눌렀는지 추측해야 한다
internal class ModEntry : Mod { private readonly PerScreen<SButton> LastButtonPressed = new PerScreen<SButton>(); private readonly PerScreen<int> Score = new PerScreen<int>(); public override void Entry(IModHelper helper) { helper.Events.Input.ButtonPressed += this.OnButtonPressed; } private void OnButtonPressed(object sender, ButtonPressedEventArgs e) { // main player changes key if (Context.IsMainPlayer) { this.LastButtonPressed.Value = e.Button; this.Monitor.Log("The main player changed the key. Can you guess it?", LogLevel.Info); } // farmhands try to guess the key else { SButton correctButton = this.LastButtonPressed.GetValueForScreen(0); if (correctButton != SButton.None) { if (e.Button == correctButton) { this.Score.Value++; this.LastButtonPressed.SetValueForScreen(0, SButton.None); this.Monitor.Log($"Player #{Context.ScreenId + 1} correctly guessed the key: {e.Button}! Their score is now {this.Score.Value}.", LogLevel.Info); } else this.Monitor.Log($"Player #{Context.ScreenId + 1} guessed incorrectly: {e.Button}.", LogLevel.Debug); } } } }
Tip: 각각의 스크린 필드를 readonly
로 지정해놓으면 좋다.. Value
프로퍼티를 지정하는 대신 전체 필드를 덮어쓰면 모든 플레이어의 데이터가 날아간다. 아마 여러분은 이런걸 원하진 않을 텐데.
오늘은 Method를 간단히 다뤄봤다. 다음 시간에는 SMAPI Multiplayer Event를 살펴보겠다. 그렇게까지 내용이 많진 않은 것 같다. 그 만큼 할 수 있는 일도 생각보다 제한적이다.