Networking/Server Travel2020. 2. 20. 17:17

로비맵에 현제 접속 중인 모든 클라이언트들에게 동시에 게임맵으로 전환(Server Travel)하고 로비맵에서 선택한 캐릭터를 게임맵에서 스폰하는 예

 

로그인 버튼을 누르면 서버측(GameMode)에서 로그인 순서대로 플레이어에게 순번을 부여하면 클라이언트 측의 SaveGame이나 GameInstance 에 저장했다가, 2명이 모두 로그인하면 서버측(GameMode)에서는 Server Travel 명령을 실행하여 모두 클라이언트가 ThirdPersonExampleMap 으로 전환하도록 한다.

Server Travel 후에 실행되는 ThirdPersonGameMode의 OnRestartPlayer 함수에서 SaveGame 이나 GameInstance 로부터 플레이어의 순번을 참조하여 해당 번호의 캐릭터를 스폰하여 3인칭 맵 위에 표시한다

 

 

언리얼 에디터에서 3인칭 프로젝트 템플릿을 사용하여 프로젝트 생성

 

 

2개의 캐릭터 생성

  • ThirdPersonCharacter : 디폴트로 존재하는 캐릭터
  • BP_Blue : ThirdPersonCharacter를 복제하여 BodyColor 만 변경한 캐릭터

 

복제된 캐릭터의 BodyColor 를 변경하기 위해 아래의 Construction Script 참고

 

2개의 레벨 생성(아무것도 없는 Empty 레벨, 3인칭 템플릿 레벨)

  • LobbyMap : 아무것도 없는 로비용 Empty 레벨
  • ThirdPersonExampleMap  : 3인칭 프로젝트 템플릿에 포함된 게임용 레벨
  • 게임맵을 더블클릭하여 에디터에서 열고 월드아웃라이너에서 디폴트로 포함된 Network Player Start를 삭제한다
  • 언리얼 에디터 > 모드 > 배치 >검색창에 Player Start 를 검색하여 드래그하여 레벨에 필요한 플레이어 수만큼 배치한다

 

2개의 GameMode 생성

  • GM_Lobby : Use Seamless Travel 속성 체크
  • 로비맵의 GameMode에디터에서 Default Pawn Class 항목에 None 설정
  • ThirdPersonGameMode (디폴트 생성됨) : Use Seamless Travel 속성 체크
  • 게임맵의 GameMode에디터에서 Default Pawn Class 항목에 Pawn 설정

 

2개의 PlayerController 생성

  • BP_LobbyPlayerController (로비맵에서 사용될 PlayerController)
  • BP_ThirdPersonPlayerController (게임맵에서 사용될 PlayerController)
  • 프로젝트 세팅 항목에서 Editor Startup Map, Game Default MapLobbyMap으로 설정한다

 

SaveGame 블루프린트 클래스 생성(SG_PlayerNumber)

  • Player Number 정수 변수 선언

 

GameInstance 클래스 생성(BP_GameInstance)

  • 여기에서는 GameInstance에 Player Number 정수 값을 저장하여 레벨전환 후에 다시 참조할 것이므로 Player Number 라는 정수변수 1개만 선언한다
  • 프로젝트 세팅 > 맵 & 모드 > Game Instance 섹션의 Game Instance Class항목에 등록한다

 

플레이 설정

  • 언리얼 에디터 > 플레이 버튼 우측 역삼각형 > 고급세팅 > Multiplayer Options > 단일 프로세스 항목 체크해제, 에디터 멀티플레이어 모드 항목에 Play As Client 설정
  • 언리얼 에디터 > 플레이 버튼 우측 역삼각형 > 플레이어 수: 2,    독립형 게임 선택
  • 위와 같이 설정한 후에는 [플레이] 버튼만 누르면 위의 설정대로 실행된다
  • 게임이 실행되면 2개의 윈도우에 각 플레이어의 뷰가 표시되고 키보드와 마우스로 각 캐릭터를 제어할 수 있다
  • ALT + TAB 키를 누르면 제어대상 윈도우를 변경할 수 있다
  • 게임 윈도우의 종료는 ALT + TAB 키를 눌렀을 때 보여지는 섬네일 윈도우의 X 버튼을 누르면 된다
  • 한대의 컴퓨터에서 테스트하면 캐릭터가 각 플레이어마다 동일하게 스폰되므로 2대의 컴퓨터에서 테스트해야 한다
  • 패키징하여 네트워크 환경에서 서버와 클라이언트를 실행하는 방법은 여기를 참조하세요

 

WG_Login 위젯생성 (로비맵의 화면에 표시될 로그인버튼 한개만 필요)

 

 

로비맵에서 사용될 PlayerController

 

 

LobbyPlayerController의 멤버변수 Player Number변수복제 노티파이 함수의 내용

 

 

로비맵의 게임모드에서 플레이어 2명이 로그인하면 Server Travel 명령을 실행하여 게임맵(ThirdPersonExampleMap)으로 모든 접속된 클라이언트들을 전환한다

 

 

로비용 PlayerController 에서 Player Number 변수를 선언하고 변수복제(Rep Notify)를 설정한다

플레이어가 로그인 버튼을 누른면 서버측에서 로그인 순번을 지정하여 Player Number 변수에 저장하므로 Player Number 변수는 변수복제를 사용하여 클라이언트에게 복제되도록 하고 노티파이 함수(On Rep Player Number)도 실행되도록 한다. 변수가 복제되면 SaveGame 을 이용하여 Player Number값을 디스크에 저장한다.

이 값은 나중에 게임맵으로 전환될 때 게임맵의 게임모드에서 읽어들여서 해당 번호의 캐릭터를 스폰하는데 사용한다

 

 

위와 동일하지만 SaveGame이 아닌 GameInstance에 Player Number 를 저장하는 경우

 

 

게임맵의 GameMode에서는 우선 Choose Player Start 를 오버라이드하여 Player Start 위치에 캐릭터가 생성되도록 준비한다

 

 

게임맵의 GameMode에서 각 플레이어에게 캐릭터를 스폰하여 표시해줄 책임이 있으므로 아래처럼 다양한 방법으로 이전에 선택된 캐릭터를 생성한다. 여기서는 OnRestartPlayer 이벤트 함수를 오버라이드 한 예이다. 이 외에도 Spawn Default Pawn at Transform 이나 Spawn Default Pawn For 를 오버라이드하면 더 간편하게 스폰할 수 있다. 이 페이지 하단에 이들 2개 함수의 사용 예를 추가했다

위에서 사용된 Load Player Number 함수는 게임맵의 PlayerController에 정의된 커스텀 이벤트 함수이며 이 함수는 내부에서 다시 GameMode의 RequestPossess 함수를 호출할 때 자신의 Player Number를 파라미터로 전달한다. Player Number를 받은 GameMode 에서는 해당 번호의 폰을 스폰하여 Possess함수를 호출하는 내용이다

 

 

게임맵에서 사용되는 PlayerController의 전체 내용

 

 

게임맵 GameMode 의 변수 섹션에 폰 클래스 배열을 생성한다

 

 

Server Travel 실행 후 로드되는 게임맵에서 폰을 스폰하는 3가지 방법

  • Server Travel 으로 이동된 맵의 GameMode::OnPostLogin 은 실행되지 않으므로 OnRestartPlayer를 오버로드한다
  • OnRestartPlayer를 오버로드할 때는 스폰 후에 Possess함수를 호출해야 한다
  • OnRestartPlayer를 사용하지 않으려면 Spawn Default Pawn at Transform 이나 Spawn Default Pawn For 를 오버라이드하면 된다. 2개의 함수 모두 OnRestartPlayer보다 간편하고 Possess를 호출할 필요가 없다.
  • OnRestartPlayer 실행 후에 Spawn Default Pawn at Transform 이나 Spawn Default Pawn For 가 자동으로 호출된다. 그러므로 이들 3개 중 한개의 함수만 오버로드하여 폰을 스폰해주면 된다

 

OnRestartPlayer 를 오버로드하여 게임맵에 스폰하는 예

 

게임맵의 GameMode에서 Spawn Default Pawn at Transform 오버로드(GameInstance이용하는 버전)

Spawn Default Pawn at Transform 은 OnRestartPlayer 이후에 자동으로 호출되며 Possess 함수는 사용할 필요 없다

 

위와 동일한 함수이며 SaveGame을 이용하는 버전

 

Spawn Default Pawn For 를 오버로드하여 폰을 생성하는 예

Spawn Default Pawn For는 OnRestartPlayer 이후에 자동으로 호출되며 Possess 함수는 사용할 필요 없다

 

Posted by cwisky
Networking/Insert Game Data2018. 12. 26. 22:21

VaRest 플러그인을 이용하여 게임 데이터를 웹서버로 전달하고 MySQL 에 저장하는 예


무료 PHP 웹호스팅 준비

awardspace.com 사이트에 회원가입하고 무로 PHP 계정을 얻는다

awardspace.com 사이트의 Hosting Tools > Database Manager / MySQL Database를 클릭하여 데이터베이스를 생성한다

Hosting Tools > Database Manager / phpMyAdmin4를 선택하여 테이블(game_history)을 생성한다

game_history 테이블에 컬럼 5개를 생성하고 아래처럼 각 컬럼의 자료형을 지정한다



위에서 생성한 game_history 테이블의 game_start_date, game_end_date 컬럼의 자료형인 datetime은 날짜와 시간을 동시에 저장할 수 있으며 그 데이터 포맷은 '2018-12-03 12:31:45' 와 같이 년-월-일 시:분:초 를 표현한다


위에서 생성한 game_history 테이블에 게임 데이터를 저장하는 기능을 할 PHP를 작성한다


insert_game_data.php

<?php

  $game_title = $_REQUEST['game_title'];

  $player_id =  $_REQUEST['player_id'];

  $score =       $_REQUEST['score'];

  $start_time = $_REQUEST['game_start_date'];

  $end_time =  $_REQUEST['game_end_date'];

  

  $host='fdb24.awardspace.net';

  $user='2915954_user';

  $pass='2915954Database';

  $db='2915954_user';


  $arr = array();

  

  $conn = mysqli_connect($host,$user,$pass,$db);

  if ($conn->connect_error) {

    $arr["connection"] = false;

    return;

  }

  if($conn) {

    $arr["connection"] = true;

  }

 

  $sql = "INSERT INTO game_history (game_title, player_id, score, game_start_date, game_end_date) ".

          "VALUES ('$game_title', '$player_id', $score, '$start_time','$end_time')";


  $inserted = mysqli_query($conn, $sql);


  if ($inserted) {

    $arr['inserted'] = true;

  } else {

    $arr['inserted'] = false;

  }

  mysqli_close($conn);

  $json = json_encode($arr);

  echo $json;

?>


언리얼 런처를 실행하여 마켓플레이스를 클릭하고 VaRest 플러그인을 찾아 언리얼 엔진에 설치한다

언리얼 엔진의 기본 프로젝트 템플릿을 이용하여 프로젝트를 생성한다


언리얼 에디터의 블루프린트 버튼을 누르고 레벨블루프린트를 열고 다음과 같이 시간 문자열을 생성하여 리턴하는 함수를 생성한다


GetDateTime함수(MySQL의 datetime 형 데이터를 생성하는 기능)



레벨블루프린트에 다음과 같이 VaRest 플러그인을 이용하여 서버측 스크립트(insert_game_data.php)에 데이터를 전달하는 기능을 작성한다(웹서버에 요청을 전달하는 기능)

Posted by cwisky
Networking/VaRest Plugin2018. 12. 22. 17:59

http 요청 및 응답처리를 위한 VaRest 플러그인 사용법


언리얼 엔진 4.11 ~ 현재 최신버전(4.21)까지 지원하는 플러그인


설치 및 테스트


언리얼 마켓플레이스에서 VaRest 를 검색하고 엔진에 설치하기를 누른다

언리얼 엔진을 이용하여 프로젝트를 생성한다

편집 > 플러그인 > VaRest 활성화 체크 > 화면 우측하단 [지금 재시작] 버튼 누름

언리얼 에디터가 다시 실행되면 블루프린트 입력 가능한 곳에 아래의 내용을 입력한다



VaRest 플러그인 작동 테스트 (https://alyamkin.com/ 사이트에 대한 GET 방식 요청)



게임을 실행하고 키보드에서 T 키를 누르면 아래와 같이 게임화면에 http 응답문자열이 표시된다




PHP 무료 호스팅 서비스 이용하기

언리얼 프로젝트에서 접속할 웹서버가 필요할 때 PHP 무료계정을 이용하면 빠르고 쉽게 웹서버를 구축할 수 있다


무료 PHP 계정으로 유명한 infinityfree.net 은 다른 곳에 비해서 더 좋은 프로그래밍 환경을 제공하고 있지만 언리얼 엔진에서 접속하면 자바스크립트가 지원되지 않는 클라이언트라는 오류 메시지가 표시되면서 PHP가 실행되지 않는 문제가 있어서 아쉽지만 사용할 수가 없다


아래 예제에서 사용한 웹사이트 무료계정

awardspace.com


PHP에서 단순한 문자열을 응답하는 경우

<?php

  echo "Hello";

?>



여러개의 문자열을 쉼표로 구분하여 한개의 문자열로 응답하는 경우

<?php

  echo "Hello, World, Bye";

?>

위와 같은 경우에는 블루프린트에서 수신된 응답 문자열을 Parse into Array 노드를 이용하여 배열을 생성하고 ForEachLoop노드를 이용하여 모든 문자열을 화면에 표시할 수 있다



PHP 에서 간단한 JSON 문자열을 응답으로 출력하는 경우

<?php

  header('Content-type: application/json; charset:utf-8');

  $arr = array();

  $arr["login"] = true;

  $arr["error"] = false;

  $json = json_encode($arr);

  echo urldecode($json);

?>




POST 방식 요청으로 웹서버에 파라미터 전송하기


블루프린트에서 POST 요청을 전달하기 위해서는 위의 블루프린트에서도 생성한 적이 있는 RequestObject 객체의 Set String Field 노드를 이용하여 파라미터 이름, 값을 웹서버로 전송할 수 있다.



웹서버측에서 파라미터를 수신하는 예

<?php

  $ParamValue = $_REQUEST["Param Name"];

  echo ParamValue;

?>



PHP에서 JSON 문자열을 생성하여 클라이언트에게 응답하는 예

<?php

  header('Content-type: application/json; charset=UTF-8');

  $arr = array();

  $arr["login"] = true;

  $arr["error"] = false;

  $jsonStr = json_encode($arr);

  echo $jsonStr;

?>



PHP에서 MySQL 데이터베이스에 접속하는 예 ( http://unrealengine.epizy.com/mysql_test.php )


<?php


  $host='sql113.epizy.com';

  $user='epiz_23187906';

  $pass='HQpyNNHkYp';

  $db='epiz_23187906_User';


  $conn = mysqli_connect($host,$user,$pass,$db);

  if ($conn->connect_error) {

    die("Connection failed: " . $conn->connect_error);

  }

  if($conn) {

    echo "Connection successful"; 

    echo "<br>";

  }

 

  $select_query = "SELECT * FROM Player ";


  $result_set = mysqli_query($conn, $select_query);


  if (mysqli_num_rows($result_set) > 0) {

    while($row = mysqli_fetch_assoc($result_set)) {

        echo "Player ID: " . $row["PlayerID"]. " - Email: " . $row["Email"]. " - Game: " . $row["Game"]. "<br>";

    }

  } else {

    echo "0 results";

  }

  mysqli_close($conn);

?>



로그인 결과를 JSON 문자열로 응답하는 PHP(http://unreal.mywebcommunity.org/unreal_login.php?id=smith&pwd=smithpwd)

<?php

header('Content-type: application/json; charset=UTF-8');

  $id = $_REQUEST["id"];

  $pwd = $_REQUEST["pwd"];


  $result = "";

  

  // DB 코드 시작

  $host='fdb24.awardspace.net';

  $user='2915954_user';

  $pass='2915954Database';

  $db='2915954_user';


  $arr = array();

  

  $conn = mysqli_connect($host,$user,$pass,$db);

  if ($conn->connect_error) {

$arr["error"] = true;

        echo json_encode($arr);

return;

  }

  if($conn) {

    $arr["connection"] = true;

  }

 

  $select_query = "SELECT * FROM Member WHERE memid='$id' AND mempwd='$pwd' ";


  $result_set = mysqli_query($conn, $select_query);


  if (mysqli_num_rows($result_set) > 0) {

$arr["login"] = true;

  } else {

$arr["login"] = false;

  }

  mysqli_close($conn);

  $json = json_encode($arr);

  echo $json;

?>




VaRest 플러그인을 이용하여 GET 방식으로 위의 PHP에 로그인 요청하는 예

 - 서버측에서 JSON 문자열을 송신하여 언리얼 측에서 JSON 오브젝트를 수신





VaRest 플러그인을 이용하여 POST 방식으로 위의 PHP에 로그인 요청하는 예




localhost:3000/login 요청으로 실행되는 Node.js 웹서버 코드


var express = require('express');
var app = express();
var qs = require('querystring');

app.get('/login', function(req, res){
console.log("id=%s, pass=%s", req.query.id, req.query.pass);
login(req.query.id, req.query.pass, res);
});

app.post('/login', function(req, res){
var body = '';

req.on('data', function (data) {
body += data;
if (body.length > 1e6)
req.connection.destroy();
});

req.on('end', function () {
var post = qs.parse(body);
console.log("수신된 파라미터:"+post.id+", "+post.pass);
login(post.id, post.pass, res);
});
});

app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});


function login(empID, empName, res)
{
const oracledb = require('oracledb');
oracledb.getConnection(
{
user : "hr",
password : "hr",
connectString : "127.0.0.1/XEPDB1"
},
function(err, connection)
{
if(err){
console.error(err.message);
return;
}
console.log("오라클 접속 성공");
connection.execute(
"SELECT * FROM employees WHERE employee_id=:a AND first_name=:b",
[empID, empName],
{
maxRows:1
},
function(err, result)
{
var jsonObj = {};

if(err) {
console.error(err.message);
connection.close();
jsonObj.result = "Login Failed!";
} else{
//console.log(result.metaData); // Column names etc
console.log(result.rows);
if(result.rows.length>0) {
console.log("검색 성공")
jsonObj.result = "Login OK";
}else{
console.log("검색 실패")
jsonObj.result = "Login Failed!";
}
connection.close();
}
res.end(JSON.stringify(jsonObj));
}
); // end of execute()
}
); // end of getConnection()
}


Posted by cwisky
Networking/HTTP2018. 12. 20. 20:56

원문 : 언리얼 HTTP 요청 참조

https://wiki.unrealengine.com/UE4.10_How_To_Make_HTTP_GET_Request_in_C%2B%2B


위의 내용을 참조하여 UE4.20에서 테스트해본 내용이며 설명대로 설정하고 코딩한 것을 테스트한 결과 언리얼에서 웹서버에 접속하고 응답을 수신하여 언리얼 화면에 표시할 수 있었다


C++프로젝트 생성

Visual Studio 의 Games/프로젝트이름/Source/프로젝트이름/프로젝트이름.Build.cs 파일을 더블클릭하여 연다


소스코드에서 아래 내용을 찾는다

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });


위의 코드에 HTTP 관련 모듈이름을 추가한다

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore"

,"Http", "Json", "JsonUtilities" });


/Games/프로젝트명/Config/DefaultEngine.ini 파일을 열고 아래의 내용을 추가한다

[HTTP]

HttpTimeout=300

HttpConnectionTimeout=-1

HttpReceiveTimeout=-1

HttpSendTimeout=-1

HttpMaxConnectionsPerServer=16

bEnableHttp=true

bUseNullHttp=false

HttpDelayTime=0



Actor기반의 C++ 클래스를 생성

언리얼 에디터 Content Browser에서 마우스 우측 > C++ 클래스 생성 > Actor > 액터의 이름을 HttpActor 등으로 입력


Visual Studio에 위에서 생성한 클래스의 *.h, *.cpp 파일의 디폴트 코드가 열리면 헤더파일과 소스파일을 다음과 같이 편집한다. 굵게 표시한 코드는 디폴트 코드에 추가된 것이다


헤더파일 편집

// Fill out your copyright notice in the Description page of Project Settings.


#pragma once


#include "CoreMinimal.h"

#include "GameFramework/Actor.h"

#include "Runtime/Online/HTTP/Public/Http.h"

#include "MyHttpActor.generated.h"


UCLASS()

class CPP_HTTP_API AMyHttpActor : public AActor

{

GENERATED_BODY()

public:

FHttpModule* Http;


UFUNCTION()

void MyHttpCall(); // http 요청에 사용할 함수


// http GET 요청 직후 호출되는 콜백함수

void OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);

// 생성자 함수를 약간 변경한다

AMyHttpActor(const class FObjectInitializer& ObjectInitializer);


protected:

// Called when the game starts or when spawned

virtual void BeginPlay() override;


public:

// Called every frame

virtual void Tick(float DeltaTime) override;

};



소스파일 편집

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyHttpActor.h"



// Sets default values

AMyHttpActor::AMyHttpActor(const class FObjectInitializer& ObjectInitializer)

: Super(ObjectInitializer)

{

  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.

PrimaryActorTick.bCanEverTick = true;

Http = &FHttpModule::Get();

}


// Called when the game starts or when spawned

void AMyHttpActor::BeginPlay()

{

MyHttpCall();

Super::BeginPlay();

}


void AMyHttpActor::MyHttpCall()

{

TSharedRef<IHttpRequest> Request = Http->CreateRequest();

Request->OnProcessRequestComplete().BindUObject(this, &AMyHttpActor::OnResponseReceived);

//This is the url on which to process the request

Request->SetURL("http://unreal.mywebcommunity.org/getInt.php");

Request->SetVerb("GET");

Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent");

Request->SetHeader("Content-Type", TEXT("application/json"));

Request->ProcessRequest();

}


void AMyHttpActor::OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)

{


//Create a pointer to hold the json serialized data

TSharedPtr<FJsonObject> JsonObject;


//Create a reader pointer to read the json data

TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());


//Deserialize the json data given Reader and the actual object to deserialize

if (FJsonSerializer::Deserialize(Reader, JsonObject))

{

//Get the value of the json object by field name

int32 recievedInt = JsonObject->GetIntegerField("customInt");


//Output it to the engine

GEngine->AddOnScreenDebugMessage(1, 2.0f, FColor::Green, FString::FromInt(recievedInt));

}

}


// Called every frame

void AMyHttpActor::Tick(float DeltaTime)

{

Super::Tick(DeltaTime);


}



위와 같이 작성된 클래스를 Visual Studio에서 저장하고 언리얼 에디터에서 [컴파일] 버튼을 누른다

컴파일에 성공하면 Content Broswer에서 위의 클래스를 드래그하여 레벨에 배치하면 언리얼 에디터에서 작업은 끝이다


위의 언리얼 프로그램이 접속할 대상은 HTTP 프로토콜을 사용하여 연결되는 웹서버이므로 웹서버에서 위의 접속에 응답할 서버 스크립트가 필요하다. 웹서버에서 실행되는 스크립트는 프로그래밍 언어와 상관없다


getInt.php (위의 언리얼 프로그램에서 접속할 웹서버의 스크립트)

<?php
	//Create a variable to be used in 
	$theVar = array('customInt' => 5);

	//Set the headers
	header('Content-Type: application/json');

	//Encode the variable, and save the encoded string
	$encoded = json_encode($theVar);

	//Output it
	echo $encoded; 

?>



위의 예제를 약간 변경하여 언리얼 프로젝트에서 웹서버에 로그인하도록 연결한 예

로그인 기능을 담당하는 C++ 클래스에서 로그인 결과를 블루프린트에 통보하기 위해 C++에 함수의 원형을 선언하고  블루프린트에서 구현하였다. C++에 선언되어 있기 때문에 C++에서 호출이 가능하며 실행은 블루프린트에 정의된 함수가 실행된다


MyHttpActor.h (블루프린트에서 구현할 함수의 원형을 선언한다)

// Fill out your copyright notice in the Description page of Project Settings.


#pragma once


#include "CoreMinimal.h"

#include "GameFramework/Actor.h"

#include "Runtime/Online/HTTP/Public/Http.h"

#include "MyHttpActor.generated.h"


UCLASS(BlueprintType, Blueprintable)

class CPP_HTTP_API AMyHttpActor : public AActor

{

GENERATED_BODY()

public:

FHttpModule* Http;


UFUNCTION(BlueprintCallable, Category="HTTP")

void MyHttpCall(FString url); // http 요청에 사용할 함수


// http GET 요청 직후 호출되는 콜백함수

void OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);

//블루프린트에서 구현할 함수 선언

//이 방법을 사용하면 블루프린트에서 구현된 이벤트함수를 C++에서 호출할 수 있다

UFUNCTION(BlueprintImplementableEvent, Category="HTTP")

void PrintOnScreen(bool bLogin); // 정수 파라미터라면 int32형을 사용해야 한다


// 생성자 함수를 약간 변경한다

AMyHttpActor(const class FObjectInitializer& ObjectInitializer);


protected:

// Called when the game starts or when spawned

virtual void BeginPlay() override;


public:

// Called every frame

virtual void Tick(float DeltaTime) override;

};



MyHttpActor.cpp (블루프린트에서 구현한 함수를 C++에서 호출)

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyHttpActor.h"

#include "../Public/MyHttpActor.h"


// Sets default values

AMyHttpActor::AMyHttpActor(const class FObjectInitializer& ObjectInitializer)

: Super(ObjectInitializer)

{

  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.

PrimaryActorTick.bCanEverTick = true;

Http = &FHttpModule::Get();

}


// Called when the game starts or when spawned

void AMyHttpActor::BeginPlay()

{

Super::BeginPlay();

}


void AMyHttpActor::MyHttpCall(FString url)

{

TSharedRef<IHttpRequest> Request = Http->CreateRequest();

Request->OnProcessRequestComplete().BindUObject(this, &AMyHttpActor::OnResponseReceived);

//This is the url on which to process the request


Request->SetURL(url);


Request->SetVerb("GET");

Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent");

Request->SetHeader("Content-Type", TEXT("application/json"));

Request->ProcessRequest();

}


void AMyHttpActor::OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)

{

//웹서버로부터 응답된 내용을 화면에 디버그 문자열로 출력해본다

//GEngine->AddOnScreenDebugMessage(1, 2.0f, FColor::Green, Response->GetContentAsString());

//Create a pointer to hold the json serialized data

TSharedPtr<FJsonObject> JsonObject;


//Create a reader pointer to read the json data

TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());


//Deserialize the json data given Reader and the actual object to deserialize

if (FJsonSerializer::Deserialize(Reader, JsonObject))

{

bool login = JsonObject->GetBoolField("login");

//이 클래스에서 선언하고 블루프린트에서 구현된 함수 호출

PrintOnScreen(login);

}

}


// Called every frame

void AMyHttpActor::Tick(float DeltaTime)

{

Super::Tick(DeltaTime);

}



위에 정의된 C++ 클래스를 기반으로 블루프린트 액터 클래스를 생성하고 C++에 선언된 PrintOnScreen 함수를 블루프린트에서 구현한다


BP_HttpActor



웹서버에서 로그인을 처리하는 PHP 스크립트

언리얼 프로젝트로부터 id, pwd 파라미터를 받아서 MySQL 데이터베이스를 검색하여 전달된 이용자 정보가 존재하는지 확인한다. 확인된 결과를 JSON 문자열로 표현하여 응답한다


unreal_login.php

<?php

header('Content-type: application/json; charset=UTF-8');

  $id = $_REQUEST["id"];

  $pwd = $_REQUEST["pwd"];


  $result = "";

  

  // DB 코드 시작

  $host='fdb24.awardspace.net';

  $user='2915954_user';

  $pass='2915954Database';

  $db='2915954_user';


  $arr = array();

  

  $conn = mysqli_connect($host,$user,$pass,$db);

  if ($conn->connect_error) {

$arr["error"] = true;

        echo json_encode($arr);

return;

  }

  if($conn) {

    $arr["connection"] = true;

  }

 

  $select_query = "SELECT * FROM Member WHERE memid='$id' AND mempwd='$pwd' ";


  $result_set = mysqli_query($conn, $select_query);


  if (mysqli_num_rows($result_set) > 0) {

$arr["login"] = true;

  } else {

$arr["login"] = false;

  }

  mysqli_close($conn);

  $json = json_encode($arr);

  echo $json;

?>


Posted by cwisky


Advanced Sessions Plugin을 검색하여 포럼 사이트에 접속하면 최시버전을 다운로드할 수 있다

https://forums.unrealengine.com/community/community-content-tools-and-tutorials/41043-advanced-sessions-plugin


압축을 해제하고 AdvancedSessions 폴더를 언리얼 설치폴더의 Engine/Plugins/안에 복사해 넣는다


프로젝트를 새로 생성하거나 기존 프로젝트를 연다


편집 > Plugins 를 선택하고 Advanced Sessions, Advanced Steam Sessions, Online Subsystem Steam 에 각각 체크하고 하단의 [지금 재시작] 버튼을 누른다


언리얼 에디터가 재시작 되면 블루프린트 클래스 안에서 다음과 같은 노드가 사용가능한지 확인한다

Create Advanced Sessions 

Find Sessions Advanced




Create Advanced Session 노드의 사용 예


Find Sessions Advanced 노드의 사용예



Join Session은 일반 Session 의 Join Session과 동일


Posted by cwisky
Networking/Intro(Videos)2018. 12. 7. 11:47

Listen Server

별도의 서버머신이 필요없다

서버에서도 한개의 클라이언트가 실행된다

프로그래밍이 쉽다

소수의 클라이언트가 접속할 경우에 적당


Dedicated Server

전용서버로 클라이언트가 포함되 않음

클라이언트 머신 외에 독립적인 서버머신이 필요하다

해당 게임에 접속자가 많을 경우에 적합하다


리슨서버와 클라이언트를 에디터 외부에서 실행하는 방법

리슨서버 실행

contentExamples.uproject Network_Features?listen -game

conetntExamples.uproject :  프로젝트 이름

Network_Features : 실행할 맵(레벨) 이름

? : 추가적인 옵션

listen : 리슨서버로 실행

-game : 에디터에서 실행하는 것이 아니라 게임을 실행한다


클라이언트 실행

contentExamples.uproject 127.0.0.1 -game

contentExamples.uproject : 프로젝트 이름

127.0.0.1 (IP) : 클라이언트가 접속할 서버의 주소



언리얼 게임 프로젝트를 윈도우 기반으로 패키징하여 실행파일(*.exe)을 콘솔에서 실행하는 방법


네트워크 프로그램을 패키징하여 실행파일(예, Net_TPS.exe)을 실행하는 콘솔명령


1. 완성된 언리얼 프로젝트를 패키징한다

  - 파일 > 프로젝트 패키지 > 윈도우 > 윈도우(64비트) > 실행파일 저장 경로 지정


2. 위와같은 방법으로 프로젝트를 패키징하면 'WindowsNoEditor' 폴더 안에 몇개의 폴더와 실행파일(프로젝트명exe)이 생성된다. 예를들어, 프로젝트명이 Net_TPS 였다면 실행파일은 Net_TPS.exe 이 생성된다


3. CMD를 열고 실행파일(Net_TPS.exe)이 있는 곳으로 이동하여 다음과 같이 서버와 클라이언트를 각각 실행한다


 - Listen 서버 실행 : D:\MyGame>Net_TPS ThirdPersonExampleMap?listen -game<enter>

 - 위의 서버에 연결되는 클라이언트 실행 : D:\MyGame>Net_TPS 127.0.0.1(서버 IP) -game<enter>


위와 같이 게임을 실행하면 방화벽에 접근을 허용할 것인지 묻는 창이 뜰 때 허용하면 된다



Networking

 - 개념


Server/Client

 - Listen Server

 - Dedicated Server

 - 에디터에서 네트워크 프로그램 실행방법(1명, 2명, 화면크기 설정, Simulated Dedicated Server)

 - 에디터 밖에서 Listen 서버/클라이언트 실행 방법

 - 서버실행 : 프로젝트명.uproject 맵이름?listen -game

 - 클라이언트 실행 : 프로젝트명.uproject 127.0.0.1 -game

 - 서버측/클라이언트측 캐릭터 스폰하는 방법

 - 서버/클라이언트 동기화 실험(클라이언트 측에서 키보드를 이용하여 3인칭 캐릭터를 움직여본다)

 - 클라이언트측에서 값이 변경되는 조작은 서버에게 요청하면 서버에서 값을 변경하고 그 값을 클라이언트에게 복제한다


서버에서만 값을 변경해야 하는 이유

 - 클라이언트에서 값을 변경할 수 있다면 클라이언트 머신을 해킹하여 점수나 승패와 관련한 중용한 값을 조작할 수 있다

 - 그러므로 값이 변경되는 부분의 스크립트는 Switch Has Authority 노드를 사용해야 한다

 - 노드의 우측상단 모서리에 서버 모양을 가진 노드(예를 들면, Apply Damage노드)는 서버에서만 호출되어야 하고 클라이언트가 호출할 시에는 아무런 작용을 하지 않는다

 - Receive Draw HUD처럼 노드 우측 상단에 모니터 아이콘을 가진 노드는 서버가 별다른 주의를 기울이지 않는다


Replication(아래 항목 각 실험)

 - Actor : 서버에서 스폰된 액터가 클라이언트에게도 복제됨(액터 클래스의 변수는 개별적으로 Replicate 설정가능)

 - Variable(RepNotify포함) : 서버에서 변경된 변수의 값이 클라이언트에게 복제됨

 - RepNotify 로 설정된 변수가 변경되면 자동생성된 함수(OnRep_변수명)가 자동으로 실행됨(C++에서는 수동으로 호출해야 함)

 - RepNotify : 변수의 값이 변경되면 비주얼 이펙트를 한번 보여주려는 경우에 유용함(Replication Notify)

 - Function : 서버에서 실행된 함수가 클라이언트에서도 실행됨(일회성 이벤트)

 - 변수복제(변경된 값을 유지할 필요가 있을 때 사용), 함수복제(일회성 효과에 사용)

 - Replicate 변수는 변경하는 Set 노드의 우측 상단에는 2개의 볼이 겹쳐진 아이콘이 표시된다

 - 변수복제는 신뢰성(Reliable) 있음: 클라이언트에 반드시 복제됨

 - 함수복제는 신뢰성을 선택할 수 있음 : 장식성 효과에는 Reliable 설정하지 않는 것이 바람직함


변수복제의 용도

 - 예를 들면, 조명이 켜진 상태로 한동안 유지되어야 할 때

 - RepNotify : 변경된 상태가 한동안 지속되아야 할 필요가 있고 비주얼 이펙트를 출력할 필요가 있을 때


함수복제의 용도

 - 예를 들면, 캐릭터가 특정 영역에 들어가면 폭발하는 비주얼 이펙트는 일회성 효과이므로 함수복제를 이용함


Switch Has Authority(아래 항목 실험)

 - Replicate 설정된 변수를 클라이언트가 직접 변경하면 어떤 현상 발생?

 - Replicate 설정된 변수를 서버에서만 변경하려면?

 - Switch Has Authority는 꼭 필요한가?

 

Function Replication

 - Not Replicated : 요청한 클라이언트를 제외한 모든 클라이언트에게는 복제되지 않음

 - Replicated : 모든 클라이언트에게 복제됨

 - Multicast : 서버에서만 호출되어 모든 클라이언트에게 복제됨

 - Reliable : 선택하면 클라이어트에서도 반드시 실행됨

 - 순수한 장식성 효과라면 Reliable 을 설정할 필요는 없다(성능을 고려)

 - Multicast 는 한 프레임 내에서 여러변 호출되더라도 거절됨

 - Run on Server : 클라이언트가 호출하여 서버에서 함수 실행

 - Run on Owning Client : 서버가 호출하여 액터를 소유한 클라이언트에서 함수 실행

`

Network Relevancy(연관성)

 - 액터가 특정 시간, 특정 머신에 연관성이 있는가 결정하는 것

 - 연관성이 있는 클라이언트에만 정보를 전달하여 통신량을 줄이고 성능 저하를 막기위한 것

 - 보이지 않거나 다른 공간에 있는 상대방에 대해서 현재 당장 알아야 할 필요는 없다

 - 나중에는 결국 알아야 할 필요가 있을 수 있다

 - 멀리 있는 플레이어에게 잘 보이지도 않는 고비용 이펙트를 굳이 서버로부터 복제하여 보게 할 필요가 없다

 - 일정한 거리 안으로 들어온 플레이어에게만 현재 상황에 해당하는 장면을 볼 수 있게 한다

Posted by cwisky
Networking/Network Cube2017. 12. 28. 11:01

http://unrealengine.tistory.com/category/Networking/Actor%2C%20Function%20Replication

Posted by cwisky
Networking/PlayerState2017. 11. 13. 16:47

언리얼 엔진에서 커스텀 PlayerState 사용하는 예


PlayerState 는 네트워크 게임에서 매우 유용한 클래스이다. 이름에서 그 용도를 짐작할 수 있듯이 게임 플레이어의 모든 상태를 저장하고 다른 클라이언트에게 까지 전달할 수 있는 특징을 가지고 있다


PlayerState 객체는 모든 클라이언트 머신과 서버 머신에서 공히 접근이 가능하며 플레이어 하나에 한개의 PlayerState 가 할당되고 레벨이나 플레이어 폰(캐릭터)가 메모리에서 제거(Destroyed)되더라도 PlayerState 는 계속 존재하며 플레이어의 정보를 유지할 수 있다.


PlayerState 기반의 클래스 블루프린트 생성

커스텀 PlayerState 에 임의의 변수 선언

커스텀 GameMode 에 위에서 생성한 PlayerState 등록

커스텀 GameMode를 월드세팅 / GameMode에 등록

커스텀 GameMode를 Edit > 프로젝트 세팅 > 맵 & 모드 패널의 Default Modes / Default GameMode에 설정

이벤트 그래프에서 GetPlayerState 노드와 형변환 노드를 이용하여 커스텀 PlayerState 참조를 구한다

PlayerState 참조를 이용하여 그 안에서 선언한 변수에 접근하여 값을 변경한다


다음은 마우스로 큐브를 클릭하면 점수를 1점씩 올리며 5점에 도달하면 'You WIN!' 메시지를 화면에 출력하는 예이다



Posted by cwisky
Networking/Summary2017. 11. 12. 17:10

Network Conpendium의 요약정리


자료를 참고하여 정리하는 과정에서 작성자의 오해나 이해가 부족한 점이 있을 수 있음을 알려드립니다


Network Multiplayers 게임관련 주요 클래스


GameInstance

GameMode

GameState

Pawn (and Character, which inherits from Pawn)

PlayerController

PlayerState


게임 데이터를 저장할 때 고려해야 할 사항

 - Pawn은 게임 내에서 죽거나 제거되는 경우가 많으며 그때마다 폰 클래스에 저장한 데이터도 사라진다

 - PlayerController, PlayerState는 새 레벨이 로드되지 않는 한 폰이 제거되어도 계속 존재한다


GameInstance

 - 게임인스턴스는 게임엔진이 시작될 때부터 종료할 때까지 존재한다

 - 서버와 클라이언트에 각각 하나의 게임인스턴스가 있고 서로 통신하지는 않는다

 - 현재 게임세션의 밖에 존재하며 레벨로드에 영향을 받지 않은 상태로 게임을 구성한다

 - 영구적인 정보를 저장할 수 있는 적합한 장소이다


서버에만 존재하는 객체

 - GameMode


서버와 모든 클라이언트에 존재하는 객체

 - GameState, PlayerState, Pawn


서버와 소유한 클라이언트에 존재하는 객체

 - PlayerController 는 클라이언트와 서버에 존재하지만 다른 클라이언트끼리는 공유하지 않는다

 - PlayerController는 클라이언트 서버간의 통신에 관련한 작업을 주로 한다


클라이언트에만 존재하는 객체

 - HUD, UMG Widgets


GameMode 클래스의 오버라드 가능한 함수

 - 게임의 룰을 결정한다(플레이어 수, 최고점수 등)

 - Ready to start Match : 클라이언트 수가 채워지면 true를 리턴하고 아니면 false리턴

 - Event On Post Login : 클라이언트의 PlayerController를 파라미터를 통해 가져온다. PlayerController를 배열에 저장해 놓으면 나중에 유용하다

 - Choose Player Start : 다수개의 Player Start 액터가 레벨상에 존하는 경우에 그 중 아직 점유되지 않은 Player Start를 찾아 리턴한다

 - Start Match, Restart Game, End Match, Has Match Started, Abord Match, Has Match Ended, Is Match in Progress, Event OnSetMatchState, Get Match State 등의 함수 및 이벤트를 오버라이드할 수 있고 수동으로 호출도 가능하다

 - 이 함수들은 Ready to start Match 함수가 true 를 리턴한 후에 대부분 자동으로 호출되지만 수동으로 호출도 가능하다



GameState

 - 클라이언트/서버간의 게임의 현재 상태에 대한 정보교환을 위한 중요한 클래스

 - 멀티 플레이어어 게임에서 중요한 정보인 접속된 플레이어 리스트(PlayerState의 리스트)를 포함한다

 - GameState는 모든 클라이언트에게 Replicated 되므로 모든 클라이언트가 이 객체에 접근할 수 있다

 - 멀티플레이어 게임에서 가장 핵심적인 클래스 중에 하나이다

 - GameMode가 승리를 위한 점수를 가지고 있는 반면, GameState는 현재까지 취득한 점수를 가지고 있다

 - GameState에는 개발자가 임의의 정보(배열이나 구조체 등)를 저장할 수 있다

 - GameMode에 비해 개발자가 다루어야 할 작업은 적은 편이지만 이벤트 그래프에는 모든 클라이언트가 알아야 할 로직을 작성할 수 있다

 - PlayerArray MatchState, ElapsedTime은 replicated 설정되어 있으므로 모든 클라이언트에서 접근할 수 있다

 - GameState에 선언한 변수를 Replicated 설정하고 Switch has Authority 를 사용하여 서버측에서 값을 변경하면 모든 클라이언트에서 확인할 수 있다


PlayerState

 - 모든 접속된 클라이언트는 현재 클라이언트의 정보를 포함하고 있는 한개의 PlayerState 객체를 갖는다

 - PlayerState 객체는 모든 클라이언트에게 Replicated 되므로 어떤 클라이언트에서 다른 클라이언트의 정보를 접할 수가 있다

 - 현재 클라이언트에서 다른 클라이언트의 PlayerState 객체에 접근하는 쉬운 방법은 GameState::getPlayerArray 를 이용하는 것이다

 - PlayerName, Score 등 다른 클라이언트에게 제공해야 하는 다양한 정보(커스텀 변수)를 이 객체에 저장하여 다른 클라이언트에게 전달할 수 있다

 - PlayerPawn이 Destroy 되더라도 PlayerState는 유지된다


Pawn

 - Actor를 기반으로 파생된 클래스이며 플레이어에 의해 조종될 수 있는 액터이다

 - 플레이어는 한번에 한개의 폰을 소유할 수 있으며 쉽게 다른 폰으로 전환할 수 있다(un-possessing, re-possessing)

 - Pawn은 대부분 모든 클라이언트에게 Replicated 된다. Actor Replicated 설정된 상태

 - 폰은 대부분의 경우 사람 형태이지만 동물이나 자동차, 비행기, 배, 블럭 등도 될 수 있다

 - Pawn의 하위 클래스인 Character클래스는 네트워크에서 지원되는 MovementComponent를 포함하고 있어서 위치, 회전 등의 정보가 Replicate 된다


Actor Replication

- 맵에 배치된 액터를 선택하고 [디테일] 뷰 / Replication / 안에 있는 체크박스이다 

- Net Load on Client : 클라이언트에 맵이 로드되면서 동시에 이 항목이 체크된 해당 액터도 맵과 함께 클라이언트에게 보여지게 된다

 - 서버 측에는 위의 항목과 무관하게 보여진다

 - Replicate : 서버에 스폰될 때 클라이언트에게도 Replicate 된다. 

 - 서버측에 스폰하는 것이 아니라 클라이언트에 스폰될 때는 위의 항목설정과 무관하게 액터가 보여진다

 - 클라이언트에서 스폰하는 경우에는 서버측에 Replicate 되지는 않는다

 - 게임의 승패에 영향을 주는 중요한 액터를 생성할 때는 반드시 서버에 스폰하고 클라이언트에게 Replicate하는 것이 보안상 최선책이다

 - 게임의 승패에 영향이 없는 장식성 액터의 출력도 서버에 스폰하여 클라이언트에 Replicate해도 된다

 - 서버에서 Replicate설정된 액터를 삭제하면 모든 Replicate 한 클라이언트에서도 해당 액터가 삭제된다

 - 네트워크 상에서는 클라이언트가 Character를 움직이는 것이 아니라 클라이언트의 입력을 받아서 서버에서 Character를 움직이고 클라이언트에 Replicate 된다



Event replication

https://docs.unrealengine.com/latest/INT/Gameplay/Networking/Blueprints/index.html

 - 서버측에서 발생하는 이벤트만 유효하게 사용될 수 있도록 이벤트 함수 안에서 Switch Has Authority 노드를 사용하여 현재 인스턴스가 서버측에서 실행되는 것인지 확인 할 수 있다


Switch has Authority

 - 현재의 스크립트가 서버측에서 실행되는 클라이언트 로직인지 리모트 클라이언트에서 실행되는 로직인지 확인하여 로직을 분기한다

 - 현재 스크립트를 실행하는 머신이 서버인지 클라인지를 식별하여 실행내용을 다르게 작성할 수 있다

 - 실행 중인 이벤트 함수가 서버측에서 실행 중인지 확인하여 그 결과를 다른 클라이언트에게 복제할 때도 사용할 수 있다

 - 네트워크 게임은 모든 클라이언트의 화면의 내용이 같고 보는 방향만 다르므로 맵은 모두 동일하다고 할 수 있다

 - 현재 실행되고 있는 머신이 서버인지 클라이언트인지를 구분하는 이유는 게임의 승패와 관련한 중요한 변수의 변경은 서버측 머신에서만 하려고 하기 때문이다


Function Replication (Remote Procedure Calls or RPCs for short)

https://docs.unrealengine.com/latest/INT/Gameplay/HowTo/Networking/ReplicateFunction/Blueprints/index.html

https://docs.unrealengine.com/latest/INT/Gameplay/Networking/Blueprints/index.html

 - 로컬 머신에서 호출되어 다른 머신에서 실행된다

 - 클라이언트, 서버간의 데이터 전송방법으로 유용하게 사용된다

 - Reliable : 네트워크 상황과 무관하게 데이터가 반드시 전달된다

 - Unreliable : 네트워크 상황에 따라 전달하려는 데이터가 손실될 수도 있다

 - Multicast : 서버측에서 호출되어 서버에서 실행되고 모든 클라이언트들에게도 포워드된다

 - Run on Server : 클라이언트 측에서 호출하고 서버에서만 실행된다

 - Run on owning Client : 서버측에서 호출하고 소유한(owning client)클라이언트에서만 실행된다

 - PlayerController 에 의해 소유된 폰은 위의 2가지 타입(Run on Server, Run on owning Client)의 함수를 가질 수 있다

 - Run on Server, Run on owning Client 함수의 호출과 선언은 클라이언트의 PlayerController, PlayerState, PlayerController가 소유한 Pawn에서만 해야한다



Multicast Function 테스트

 - Multicast 로 설정된 함수를 서버 윈도우에서 실행하면 서버 화면 뿐만 아니라 다른 모든 클라이언트 화면에서도 그대로 재현된다. 그러나 클라이언트 화면에서 Multicast 함수를 실행하면 현재 클라이언트에서만 효과가 나타난다

 - Multicast 로 설정된 함수는 서버에서 호출되고 서버에서 실행되어 모든 클라이언트에게 포워드되어 실행되기 때문이다


Server Function 테스트

 - 클라이언트가 서버로 데이터를 전송하기 위한 주요 방법이다

 - Ron on Server 로 설정된 함수를 클라이언트에서 호출하면 서버측 화면에서만 효과가 나타나고 현재 클라이언트 화면에는 효과가 나타나지 않는다

 - Run on Server로 설정된 함수는 클라이언트에서 호출되어 서버에서 실행되기 때문이다

 - Run on Server로 설정된 함수를 호출하여 서버에서 특정액터의 변수의 값을 변경하고 그 액터가 Replicates 로 설정된 경우에는 클라이언트가 호출한 로직이 서버에서 실행되고 다른 클라이언트에게 Replicated된다

 - 클라이언트 측에서 호출할 수 있으므로 Run on Server로 설정된 함수 안에서 다시 Multicast 함수를 호출하면 클라이언트가 호출한 로직은 서버에서 실행되고 모든 클라이언트에게 포워드되어 실행되므로 모든 클라이언트에서 효과가 나타난다


Run On Owning Client Function 테스트

 - 서버에서 호출할 수 있고 특정 클라이언트만 선택하여 해당 클라이언트에서만 실행되도록 할 때 사용한다

 - 예를 들어 서버측 스크립트 실행 중 특정 클라이언트의 폰이 트리거에 들어가는 경우 해당 폰을 소유한 클라이언트에게만 이벤트 실행결과를 보여주려는 경우에 사용할 수 있다



Replicating Variables

https://docs.unrealengine.com/latest/INT/Gameplay/HowTo/Networking/ReplicateVariable/index.html

액터 클래스에 선언된 변수가 Replicated 로 설정된 경우, 서버에서 해당 변수의 값을 변경하면 모든 클라이언트 머신에도 변경된 값이 복제된다. 이 때 반드시 서버측에서 값이 변경된 경우에만 클라이언트에 복제가 된다


Actor Replication, Event Replication

https://docs.unrealengine.com/latest/INT/Gameplay/HowTo/Networking/ReplicateActor/Blueprints/index.html

https://docs.unrealengine.com/latest/INT/Gameplay/Networking/Blueprints/index.html

https://docs.unrealengine.com/latest/INT/Gameplay/Networking/Actors/index.html


Testing Multiplayers

https://docs.unrealengine.com/latest/INT/Gameplay/HowTo/Networking/TestMultiplayer/index.html



변수복제 VS 함수복제

변수복제와 함수복제를 위해서는 서버측에서 조작되는 객체가 클라이언트 측에도 있어야 한다. 그러한 이유로 변수복제와 함수복제에 사용할 수 있는 객체는 PlayerController 이거나 Pawn 일 경우가 많다

모든 클라이언트에 존재하는 PlayerController 객체는 서버측에도 생성된다. 그러므로 서버측에서 특정 PlayerController의 변수를 변경하고 그 변수의 값이 클라이언트 측에도 변경되도록 하려면 해당 변수를 Replication 설정해야 한다. 이렇게 하면 특정 PlayerController 의 속성을 변경하면 자동으로 해당 PlayerController 가 존재하는 클라이언트 측에서도 그 변수의 값이 변경된다. 

서버측에서는 모든 클라이언트의 PlayerController를 가지므로 변수복제를 이용하여 모든 클라이언트의 화면에 다른 값을 표시할 수가 있다.

변수복제는 서버측에서 변경된 변수의 값이 클라이언트에서도 변경되도록 할 때 사용한다. 그 반대는 안된다

서버측에서 모든 클라이언트의 PlayerController를 구할 때는 Get All Actors of Class() 함수노드를 사용하면 된다


레벨에 올려진 캐릭터는 모든 클라이언트에서 서로를 볼 수가 있기 때문에 모든 클라이언트의 컴퓨터에는 모든 캐릭터가 기능을 하고 있다고 할 수 있다. 그런데 게임 중에서 특정 캐릭터가 점프를 한다면 모든 클라이언트 화면에서 해당 캐릭터가 점프하는 동작이 보여져야 한다. 이 때는 서버측에서 점프 동작을 수행하고 그 동작이 모든 클라이언트에 존재하는 해당 캐릭터에 그대로 재현되어야 한다. 이러한 일은 함수복제 중에서도 Multicast 설정으로 할 수 있다

서버측에서 특정 클라이언트의 캐릭터에 접근할 때는 PlayerController:: Get Controlled Pawn() 함수노드를 사용하면 된다


Posted by cwisky

Function Replication 을 활용하여 [서버에서 실행], [멀티캐스트] 를 연동하여 사용하는 예


클라이언트에서 함수를 호출하여 서버에서 실행되도록 하고 서버에서 실행 중에 멀티캐스트 함수를 호출하면 클라이언트에서 호출하여 서버를 거쳐 모든 클라이언트에 포워드되는 기능을 구현할 수 있다


아래의 예제는 플로어에 놓인 큐브를 양측 상대방 화면에서 클릭하면 상대방이 있는 방향으로 큐브가 이동하게 하는 예이다. 커스텀 이벤트를 2개 생성하여 Function Replication를 설정할 때 각각 [서버에서 실행], [멀티캐스트]로 설정하였다


[서버에서 실행] 으로 설정하면 클라이언트 측에서 호출하여 서버에서 함수가 실행되도록 할 수 있고, [멀티캐스트]로 설정하면 서버에서 호출하여 서버에서 실행되고 접속된 모든 클라이언트에게로 포워드되어 실행된다


[서버에서 실행] 으로 설정된 함수(커스텀 이벤트) 가 [멀티캐스트]로 설정된 함수를 호출하므로 결과적으로 클라이언트가 호출한 함수는 서버에서 실행되고 멀티캐스트 함수가 실행되어 포워드되므로 모든 클라이언트에서도 실행된다



Posted by cwisky