오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...Amazon Web Services Korea
서비스 런칭을 위해 라이온하트와 카카오게임즈가 어떻게 최적 성능의 인스턴스를 선택하고, Windows 운영 체제를 최적화하며, 왜 Amazon Aurora를 기본 데이터베이스로 채택하였는지를 설명합니다. 또한, 출시부터 운영까지의 과정에서 MMORPG가 어떻게 AWS 상에서 설계되고, 게임 서버 성능을 극대할 수 있었는지에 대해 전달해드립니다.
이 발표는 [야생의 땅: 듀랑고]의 지형 배포 시스템과 생태계 시뮬레이션 자동화 시스템에 대한 이야기를 다룹니다. 듀랑고의 각 섬은 크기와 지형, 기후 조건이 다양하고 섬의 개수가 많아서 수동으로 관리하는 것은 사실상 불가능합니다. 몇번의 사내 테스트와 베타 테스트를 거치면서 이러한 문제를 해결해주는 자동화된 도구의 필요성이 절실해졌고, 작년에 NDC에서 발표했던 생태계 시뮬레이터와 Docker, 그리고 아마존 웹서비스(AWS)를 이용하여 수많은 섬들을 자동으로 생성하고 관리하는 자동화 시스템을 구축하게 되었습니다. 그 과정에서 했던 고민들, 기존의 애플리케이션을 "Dockerizing" 했던 경험, AWS의 각 서비스들을 적절히 활용했던 이야기, AWS의 각 지역별 요금이 상이하다는 점을 이용해서 비용을 절감한 사례, 그리고 자동화 시스템의 문제점과 앞으로의 방향에 대해서 이야기 할 계획입니다.
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...Amazon Web Services Korea
서비스 런칭을 위해 라이온하트와 카카오게임즈가 어떻게 최적 성능의 인스턴스를 선택하고, Windows 운영 체제를 최적화하며, 왜 Amazon Aurora를 기본 데이터베이스로 채택하였는지를 설명합니다. 또한, 출시부터 운영까지의 과정에서 MMORPG가 어떻게 AWS 상에서 설계되고, 게임 서버 성능을 극대할 수 있었는지에 대해 전달해드립니다.
이 발표는 [야생의 땅: 듀랑고]의 지형 배포 시스템과 생태계 시뮬레이션 자동화 시스템에 대한 이야기를 다룹니다. 듀랑고의 각 섬은 크기와 지형, 기후 조건이 다양하고 섬의 개수가 많아서 수동으로 관리하는 것은 사실상 불가능합니다. 몇번의 사내 테스트와 베타 테스트를 거치면서 이러한 문제를 해결해주는 자동화된 도구의 필요성이 절실해졌고, 작년에 NDC에서 발표했던 생태계 시뮬레이터와 Docker, 그리고 아마존 웹서비스(AWS)를 이용하여 수많은 섬들을 자동으로 생성하고 관리하는 자동화 시스템을 구축하게 되었습니다. 그 과정에서 했던 고민들, 기존의 애플리케이션을 "Dockerizing" 했던 경험, AWS의 각 서비스들을 적절히 활용했던 이야기, AWS의 각 지역별 요금이 상이하다는 점을 이용해서 비용을 절감한 사례, 그리고 자동화 시스템의 문제점과 앞으로의 방향에 대해서 이야기 할 계획입니다.
2016 아이펀팩토리 Dev Day 발표 자료
강연 제목 : Docker 로 Linux 없이 Linux 환경에서 개발하기
발표자 : 김진욱 CTO
<2016>
- 일시 : 2016년 9월 28 수요일 12:00~14:20
- 장소 : 넥슨 판교 사옥 지하 1층 교육실
1. 넥슨 코리아 데브캣 스튜디오 드래곤하운드(프로젝트DH) 개발팀
전형규(henjeon@nexon.co.kr)
UE4에서 Lua 사용하기
SilvervineUE4Lua
NDC2019
2. • 현재, 드래곤하운드(프로젝트DH) 개발팀 엔지니어링 책임자
발표자 소개
• 참여 프로젝트
• GEARS(2001)
• 마비노기(2004), 마비노기 XBOX360, 마비노기2
• 링토스 세계여행(2014)
• 마비노기 듀얼(2015)
• 주요 관심사: UE4, 육아, 그리고 AI
• 자율 주행 엔지니어를 찾고 있습니다(농담아님).
3. 발표 내용
• UE4 Blueprint Overview
• Blueprint의 장점과 단점 요약
• 관련 작업
• UE4 스크립트 플러그인 몇 가지 소개
• SilvervineUE4Lua 소개
• 개발 동기 및 특징 설명
4. 발표 내용
• 드래곤하운드 사례 분석
• Lua를 적용한 작업 몇 가지를 예로 들어서 설명
• SilvervineUE4Lua 스크립팅 가이드
• 플러그인 사용 방법과 유용한 팁 소개
• SilvervineUE4Lua 설계 리뷰
• 스크립트 플러그인 제작 시 참고할 만한 경험을 공유
5. 발표 자료 URL
• NDC 다시 보기
• http://ndcreplay.nexon.com/#
• 데브캣 스튜디오 슬라이드셰어
• https://www.slideshare.net/devcatpublications
• 올해부터 다른 곳에 올릴 가능성이 있음(구글 프리젠테이션, GitHub, …)
7. UE4 Blueprint
• 노드 기반의 비주얼 스크립팅 시스템
• 엔지니어의 도움 없이 디자이너가 직접 기능을 구현할 수 있다.
http://api.unrealengine.com/images/Engine/Blueprints/GettingStarted/Level_Blueprint_Main.jpg
8. UE4 Blueprint의 장점
• 사용자 관점에서
• 배우기 쉽다.
• 사용법이 간단하다.
• 작업 진행 속도가 빠르다(프로토타이핑에 유리).
• 언어 관점에서
• 데이터의 흐름, 상태를 기술할 때 편하다
• [+] 셰이더(머티리얼) 프로그래밍
• [+] 애니메이션 상태 기계 프로그래밍
9. UE4 Blueprint의 단점
• 복잡도 관리가 어렵다
• 시간이 지나면... 괴물이 된다.
https://blueprintsfromhell.tumblr.com/image/180629779201
10. UE4 Blueprint의 단점
• 공동 작업이 어렵다.
• 바이너리 형식이라서 ‘브랜치 후 머지’ 방식을 쓸 수 없다.
• 배타적 체크아웃으로 인한 작업 병목이 심하다.
15. UE4 ScriptPlugin
• Epic의 순정 코드
• Engine/Plugins/ScriptPlugin
• 거의 모든 UE4 스크립트 플러그인의 레퍼런스로 사용됨
• UHT(Unreal Header Tool)로 Lua 바인딩 코드를 생성하는 코드가 아주 유용함
• 바로 쓸 수 있는 상태는 아님
• 2014년 이후 거의 변경되지 않음
• 문서도 없음
17. Unreal.js https://github.com/ncsoft/Unreal.js/
• Javascript를 통합
• NCSOFT에서 개발한 오픈 소스
• NDC2017 발표 참고(https://www.slideshare.net/crocuis/unrealjs-ue4-75499471)
https://github.com/ncsoft/Unreal.js/blob/master/doc/images/UnrealJs_JavascriptConsole.gif
18. UE4 Python Editor Script Plugin
• Epic의 순정 코드
• Engine/Plugins/Experimental/PythonScriptPlugin
• Python을 스크립트 언어로 사용 가능
• 문서화됨: http://api.unrealengine.com/KOR/Engine/Editor/ScriptingAndAutomation/Python/
• 주의: 에디터 플러그인
• 런타임 플러그인이 아니라서 게임 플레이 로직에 사용할 수 없음
• 개조하면 될 것 같기도 하다.
19. MonoUE https://mono-ue.github.io/
• C#을 지원!!
• 오픈 소스(https://github.com/mono-ue)
• 꿈 같다… 너무 좋다…
• 하지만…
• 개발 속도가 다소 늦다(1인 개발)
• 충분히 검증되지 않음
https://mono-ue.github.io/code.png
24. SUE4Lua 개발 동기
• Blueprint 지옥에서 탈출하고 싶었다
• 팀 규모가 커지자 문제가 심각해짐
• 한 여름 우유보다 잘 상하는 우리 팀의 Blueprint 코드들
• 체크아웃 전쟁… 끝없는 노드 리프레시… 선 정리…
• C++로 탈출을 시도했으나 만족스럽지 않았다
• 몇 개월에 걸쳐 Blueprint 코드를 C++로 포팅함(일명 BP2CPP)
• BP2CPP 과정에서 오류가 빈번하게 발생
• BP2CPP 작업자가 스트레스를 많이 받음
• 구현 속도 및 작업 난이도 증가
25. 목표
• Blueprint 복잡도를 낮춘다.
• Blueprint를 안 쓸 수는 없다: 빠른 작업 이터레이션에 필수적
• Blueprint 사용을 최소화
• 협업이 쉬워야 한다.
• 공동 작업이 가능해야 한다.
• 브랜치, 머지, 코드 리뷰가 쉬워야 한다. 즉, git과 같은 버전 관리가 가능해야 한다.
• 디버깅이 쉬워야 한다.
26. 달성 방법
• 텍스트 스크립트 언어를 도입
• 복잡한 Blueprint 그래프를 텍스트 스크립트로 표현
• 스크립트를 소스코드로 취급하고 UE4 에디터에서 편집하지 않는다.
• 스크립트 언어로 Lua를 선택
• 따.. 딱히 Lua를 좋아해서 선택한 것은 아님(강타입 언어를 선호함)
• 이전 프로젝트(마비노기 듀얼)에서 Lua를 사용했기 때문에 별 고민 없이 선택
• 충분한 시간과 자원이 있었다면 C#을 선택했을 것
27. 자체 제작을 결심한 이유
• 다른 플러그인들은 스크립트 자유도가 너무 높다
• 스크립트로 파생 타입을 정의할 수 있으면 복잡도 관리가 어려울 것으로 판단
• 객체의 정의는 가급적 C++로 제한
• 문서화 및 코드 품질이 중요
• 영어까지는 괜찮은데 중국어를 할 줄 모름
• UE4 코딩 컨벤션을 지켰으면 함
28. SilvervineUE4Lua(SUE4Lua)
• 자체 개발한 UE4 Lua 통합 플러그인
• 2018년 6월 부터 개발
• 2019년 4월에 오픈 소스화(https://github.com/devcat-studio/SilvervineUE4Lua)
• ‘드래곤하운드’ 개발에 사용 중
29. SUE4Lua 특징
• 스크립트를 소스코드와 동등하게 취급
• Lua 파일을 uasset으로 저장하지 않는다.
• 프로젝트 Source 폴더에 스크립트를 둔다.
• Visual Studio나 Visual Studio Code로 편집한다.
30. SUE4Lua 특징
• Visual Studio Code로 디버깅
• VSCodeLuaDebug 사용(https://github.com/devcat-studio/VSCodeLuaDebug)
• 디버그 로그 리다이렉션
• 브레이크 포인트 및 데이터 검사 지원
35. 사례: AI 커스터마이징
• AI 커스터마이징 데이터를 자체 텍스트 형식에서 Lua 테이블로 변경
• 수동으로 작성한 텍스트 라인 파서 코드를 제거할 수 있었다.
• 문법 오류 검사 및 문법 강조(Syntax Highlight) 기능을 공짜로 얻게 됨
{-- DecisionMaker
Class = "Normal",
Description = "기본피격반응",
bRandomSelection = false,
Weight = 2.0,
Evaluators =
{
{
bGenerateOrganStateActions = true,
},
{
bGenerateLegLimpHelper = true,
},
{
bGenerateStakeResponses = true,
},
{
bGenerateHitActionEffects = true,
Args = {
-- 함포 사격을 받으면 넘어짐
bArtilleryHitFall = true,
},
},
},-- end of evaluators
},
36. 사례: 지스타2018 전투 미션 제작
• 5분 정도의 멀티 플레이 전투 데모
• Lua로 미션 스크립트를 구현함
• 약 3000라인 / 작성자 6명 / 358 커밋
내부 리뷰 자료 일부
37. ScenarioMission_GSTAR2018.lua 일부
--[[
Phase: 인트로 컷씬
]]
local function SpawnFlyingDragon_IntroCutScene(self)
if 0 < #self.LuaContext.TargetDragons then return end
SUE4Lua.Log("염화룡 등장")
if UDhFieldNetworkFunctionLibrary.IsServer(self) then
-- 염화룡 스폰
local SpawnSpots = {
self.LuaContext.WorldActors['M9Spawn’]
}
for _, Spot in pairs(SpawnSpots) do
local SpawnTM = Spot:GetActorTransform()
SpawnTM.Scale3D = UE4.Vector.new(4, 4, 4)
local Monster = SpawnFireDragonMiddle(self, SpawnTM, false, Spot, "")
table.insert(self.LuaContext.TargetDragons, Monster)
Monster:GetController():GetAIWrapperComponent():ToggleEnableTargeting(0)
end
38. ScenarioMission_GSTAR2018.lua 일부
--[[
Phase: 관문 보여주기 컷씬
]]
function Mission_GSTAR2018.EnterPhase_GateShowCutScene(self)
SUE4Lua.Log("EnterPhase_GateShowCutScene: "..self.LuaContext.Phases[self.LuaContext.Phase].DebugName)
SetGateClosed(self)
RefreshContextPlayers(self)
TeleportActorsToSpots(self, self.LuaContext.LocalPlayers, self.LuaContext.StartPosList_Gate)
if UDhFieldNetworkFunctionLibrary.IsClient(self) then
-- Town
self.LuaContext.WorldActors['GSTAR_Town']:SetActiveTown(false)
self.LuaContext.WorldActors['GSTAR_Town']:SetActiveTownMesh(false)
self.LuaContext.WorldActors['GSTAR_Town']:SetActiveInteriorProps(false)
self.LuaContext.WorldActors['GSTAR_Town']:SetActiveExteriorProps(false)
self.LuaContext.IsTownMove = false
StopLocalPlayer(self)
SetEnablePlayerInput(self, false)
self.LuaContext.WidgetGSTARMain:HideNPCTracker()
end
39. 사례: 지스타2018 전투 미션 제작
• 엔지니어, 디자이너 모두 Lua를 사용
• 엔지니어는 VS2017이나 VS Code를 사용
• 디자이너는 VS Code를 사용
• 저장소는 git을 사용
ScenarioMission_GSTAR2018.lua 커밋 로그 일부
• 텍스트 스크립트의 효율성을 확인함
• Blueprint로 제작했다면… 체크아웃 병목이 심했을 것
• C++로 제작했다면… 작업 진행 속도가 느렸을 것
• 덕분에 일정에 늦지 않게 작업을 완료할 수 있었다.
40. 사례: UMG 위젯 구현
• 복잡한 위젯 함수 구현을 Lua로 옮김
• SUE4Lua의 디스패치 기능을 사용
• UMG Blueprint는 이벤트 정의와 애니메이션 처리만 담당
• C++은 함수를 정의만 해둠
void UDhTownItemDetailWidgetCpp::ToggleSubStatExpand(FName StatName)
{
SUE4LUA_DISPATCH(GetLuaBridge(), StatName);
}
41. function TownStatGaugeWidget:ToggleExpand(Params)
if not self:IsValidWidget() then
return
end
self.bSubStatExpanded = not self.bSubStatExpanded
if self.bSubStatExpanded then
self.IMG_ExpandShrink:SetBrushFromTexture(self.ShrinkImageTexture)
self.IMG_ExpandShrink:SetColorAndOpacity(self.SubStatExpandColor)
self.BDR_SubStat:SetVisibility(ESlateVisibility.Visible)
UGameplayStatics.PlaySound2D(self:GetWorld(), self.OnExpandSound)
else
self.IMG_ExpandShrink:SetBrushFromTexture(self.ExpandImageTexture)
self.IMG_ExpandShrink:SetColorAndOpacity(self.SubStatShrinkColor)
self.BDR_SubStat:SetVisibility(ESlateVisibility.Collapsed)
UGameplayStatics.PlaySound2D(self:GetWorld(), self.OnShrinkSound)
end
end 코드 변환 샘플
42. local function RefreshItemState(self)
self.TXT_ItemState:SetText("")
self.TXT_Remark:SetText("")
self.TXT_Equipped:SetText("")
if self.TargetItem.EquippingCharacter then
local ShortEquippedText, _LongEquippedText = UDhTownFunctionLibrary.G
self.TXT_Equipped:SetText(ShortEquippedText)
self.TXT_Equipped:SetVisibility(ESlateVisibility.SelfHitTestInvisible
else
self.TXT_Equipped:SetVisibility(ESlateVisibility.Collapsed)
end
local ShortLevelRequirementText, LongLevelRequirementText = UDhTownFunctio
self.TXT_ItemState:SetText(ShortLevelRequirementText)
if self.TargetItem:NeedsMoreLevel(UDhSyncedPlayer.Get(self).CurrentCharact
self.TXT_Remark:SetText(LongLevelRequirementText)
self.TXT_ItemState:SetColorAndOpacity(self.NeedMoreLevelStateColor)
else
self.TXT_ItemState:SetColorAndOpacity(self.NotNeedMoreLevelStateColor
end
if self.TargetItem:IsBroken() then
self.BDR_Main:SetBrushColor(self.BrokenBorderColor)
local ShortBrokenText, LongBrokenText, RepairText = UDhTownFunctionLi
self.TXT_ItemState:SetText(ShortBrokenText..".")
self.TXT_ItemState:SetColorAndOpacity(self.BrokenStateColor)
self.TXT_Remark:SetText(LongBrokenText)
if self.ItemViewPurpose == EDhItemViewPurpose2.Equipment then
self.TXT_RightClick:SetText(RepairText)
self:PlayAnimation(self.Anim_ClickBlink)
end
end
end
코드 변환 샘플
43. void UDhTownTooltipItemWidgetCpp::RefreshItemSet()
{
if (!IsValidWidget())
{
return;
}
FDhCommonSheetTownModifierRowData ModifierRowData;
if (UDhCommonSheetContainerEx::Get()->FindModifierBySetID(TargetItem->SetID, ModifierRowData))
{
SetItemTextBlock->SetVisibility(ESlateVisibility::HitTestInvisible);
SetItemTextBlock->SetText(UDhTownFunctionLibrary::GetModifierFullName2(ModifierRowData));
if (TargetItem->IsBroken())
{
SetItemTextBlock->SetColorAndOpacity(BrokenItemSetColor);
}
else
{
SetItemTextBlock->SetColorAndOpacity(SetItemTextColor);
}
}
else
{
SetItemTextBlock->SetVisibility(ESlateVisibility::Collapsed);
}
}
local function RefreshItemSet(self)
local bModifierFound, ModifierRowData =
UDhSheetManager:GetDhSheetManager().CommonSheetEx:FindModifi
if bModifierFound then
self.TXT_SetItem:SetVisibility(ESlateVisibility.HitTest
self.TXT_SetItem:SetText(UDhTownFunctionLibrary.GetModi
if self.TargetItem:IsBroken() then
self.TXT_SetItem:SetColorAndOpacity(self.BrokenIte
else
self.TXT_SetItem:SetColorAndOpacity(self.SetItemTe
end
else
self.TXT_SetItem:SetVisibility(ESlateVisibility.Collaps
end
end 코드 변환 샘플
44. 사례: UMG 위젯 구현
• Lua로 변경한 결과
• 위젯 코드 변경점을 쉽게 알 수 있었다.
• 컴파일 과정 없이 로직을 바로 변경할 수 있어서 작업 속도가 빨라졌다.
47. Lua 코딩 컨벤션
• UE4 코딩 스타일을 따른다.
• UE4 코딩 스타일을 좋아하지 않지만 여러 스타일이 섞이는 것보다 낫다고 판단
function Sedan:OnMoveRight(Params)
--SUE4Lua.Log("Sedan:OnMoveRight() was called.")
local AxisValue = Params.AxisValue
-- Steering inupt
self.VehicleMovement:SetSteeringInput(AxisValue)
end
49. SUE4Lua 팁: 기본 라이브러리의 활용
• 기본적인 Blueprint 라이브러리를 연결해 둠
• KismetSystemLibrary, 각종 수학 라이브러리, …
• ‘Find in Files’로 키워드를 검색하면 설명과 함께 샘플 코드를 찾을 수 있다.
50. SUE4Lua 팁: Main.lua
• VM의 시작 파일을 Main.lua로 설정하는 것을 추천
• 이렇게 하면 Main.lua가 가장 먼저 실행된다.
• 추가로 필요한 파일들을 Main.lua에서 ExecuteFile()로 실행하자.
• 자주 사용하는 타입을 전역변수로 설정해두면 편리하다.
-- Engine 타입 선언
ECollisionChannel = SUE4Lua.GetEnumTable("ECollisionChannel")
ECollisionResponse = SUE4Lua.GetEnumTable("ECollisionResponse")
ECollisionEnabled = SUE4Lua.GetEnumTable("ECollisionEnabled")
UObject = UE4.FindClass("Object")
UGameplayStatics = UE4.FindClass("GameplayStatics")
UWidgetBlueprintLibrary = UE4.FindClass("WidgetBlueprintLibrary")
UWidgetLayoutLibrary = UE4.FindClass("WidgetLayoutLibrary")
ALevelSequenceActor = UE4.FindClass("LevelSequenceActor")
SUE4Lua.ExecuteFile("SUE4Lua/Tests/AllTests.lua")
51. SUE4Lua 팁: Lua Hot Reloading
• SUE4Lua.OnFileModified() 를 활용
• 파일 변경이 감지되면 SUE4Lua.OnFileModified() 함수가 호출된다(개발 빌드 전용)
• 지정된 파일이 변경되면 다시 실행해서 핫 리로딩을 흉내 낼 수 있다.
• 디스패치 핸들러 파일은 플러그인에서 자동으로 다시 실행해 준다.
local AdditionalExecuteFilenames = {
"Game/Utility.lua",
"Game/UI.lua",
}
-- 개발전용: 파일 변경 처리
SUE4Lua.OnFileModifiled = function (Filename)
for _, ExecutedFilename in pairs(AdditionalExecuteFilenames) do
if Filename:lower() == ExecutedFilename:lower() then
SUE4Lua.Log("Re-Execute", Filename)
SUE4Lua.ExecuteFile(Filename)
break
end
end
end
52. SUE4Lua 팁: LuaContext
• Lua 상태를 저장하는 변수 이름은 ‘LuaContext’을 사용
• Blueprint나 C++ 클래스에 ‘LuaContext’라는 이름의 LuaValue를 추가하고,
• Lua 구현을 위해 필요한 변수들을 저 곳에 저장해 두면 좋다.
• Lua 값과 나머지 값을 격리할 수 있어서 코드 유지 보수에 유리하다.
self.LuaContext = {
ColumnMax = Params.Column,
CurCol = 0,
CurRow = 0,
}
53. SUE4Lua 팁: 대소문자 구별
• 함수, 속성 이름의 대소문자를 구별할 수 없다
• UProperty와 UFunction이 내부적으로 FName 해시 테이블에 저장되기 때문
• Lua에서 외부 속성, 함수에 접근할 때 주의할 것
-- self.IsInCar = State 와 결과가 같다
self.isincar = State
54. SUE4Lua 팁: 이벤트 핸들러의 디스패치
• 디스플레이 이름과 실제 이름이 다르므로 주의할 것
• 예를 들어, Tick 이벤트의 실제 이름은 ReceiveTick
• 이벤트를 추가하면 자동 생성되는 이름도 있다.
• 디스패치 노드의 FunctionName을 활용하면 좋다.
실제 이름은 InpActEvt_ResetVR_K2Node_InputActionEvent_0와 InpActEvt_ResetVR_K2Node_InputActionEvent_1
55. SUE4Lua 팁: Hard Reference
• SUE4Lua는 Lua 안에 UObject에 대한 강참조를 만들지 않는다
• 어셋 참조가 필요하다면 외부 클래스에 변수를 만들어서 저장하거나,
• 해당 객체를 반환하는 Blueprint 함수를 만들자.
-- Print vehicle speed
self:DrawText({
Text = Sedan.SpeedDisplayString,
TextColor = UE4.LinearColor.new(1.0, 1.0, 1.0, 1.0),
ScreenX = HUDXRatio * 805,
ScreenY = HUDYRatio * 455,
Font = self.Font,
Scale = HUDYRatio * 1.4,
})
57. 지원하는 Lua Version
• Lua 5.3.4 사용
• Lua 5.1.5도 사용 가능할 것 같다. 다만 pairs()가 동작하지 않을 것이다.
• Lua JIT는 지원하지 않는다.
• 플러그인 내부에 Lua 소스코드가 포함됨
• 직접 Lua 코드를 구해서 연결할 필요 없다.
• luasocket 및 op_halt 패치가 적용되어 있다.
• https://github.com/devcat-studio/lua-5.3.4-op_halt
58. Lua 소스코드 관리
• 파일 로더 클래스를 세분화
• LocalFileLoader : git에 저장된 Lua 소스코드를 사용
• BundleFileLoader: 빌드머신에서 배포한 Lua 파일 번들(zip)을 사용
• 사용할 로더는 실행 환경마다 다름
• 엔지니어, 디자이너: LocalFileLoader
• 나머지 개발자: BundleFileLoader
• 배포 빌드: BundleFileLoader
61. SilvervineUE4LuaCodeGen
• UHT(Unreal Header Tool) 플러그인
• 프로젝트 모듈을 빌드하기 전에 먼저 실행된다.
• 스태틱 바인딩 코드와 함수 디폴트 파라메터 테이블을 생성한다.
1>------ Build started: Project: SUE4LuaSample, Configuration: DebugGame_Editor x64 ------
1>Performing full C++ include scan (building a new target)
1>Using 'git status' to determine working set for adaptive non-unity build (D:devgithubSilvervineUE4Lua).
1>Creating makefile for SUE4LuaSampleEditor (source directory changed)
1>Using Visual Studio 2017 14.16.27023 toolchain (C:Program Files (x86)Microsoft Visual Studio2017...
1>UnrealBuildTool : warning : Missing binary: D:devEpic GamesUE_4.21EngineSource$(ProjectDir)Plugins...
1>Building UnrealHeaderTool...
1>Performing full C++ include scan (building a new target)
1>Using 'git status' to determine working set for adaptive non-unity build (D:devgithubSilvervineUE4Lua).
1>Creating makefile for UnrealHeaderTool (ini files are newer than UBTMakefile)
1>Using Visual Studio 2017 14.16.27023 toolchain (C:Program Files (x86)Microsoft Visual Studio2017...
1>Parsing headers for SUE4LuaSampleEditor
1> Running UnrealHeaderTool "D:devgithubSilvervineUE4LuaSUE4LuaSample.uproject" "D:devgithubSilvervineUE4Lua...
1>Reflection code generated for SUE4LuaSampleEditor in 11.5833797 seconds
1>Target is up to date
1>Deploying SUE4LuaSampleEditor Win64 DebugGame...
1>Total build time: 59.99 seconds (NoActionsToExecute executor: 0.00 seconds)
1>Done building project "SUE4LuaSample.vcxproj".
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
62. SilvervineUE4LuaCodeGen
• 스태틱 바인딩 코드 생성
• IScriptGeneratorPluginInterface를 사용함
• 엔진 및 게임 클래스의 UFunction을 호출하는 함수를 생성
• 엔진 및 게임 클래스, 구조체의 UProperty 접근자 생성
• 두 개의 (거대한) inl 파일을 생성
• SilvervineUE4LuaCodeGen_Engine.g.inl : 약 80k 라인(3.9MB)
• SilvervineUE4LuaCodeGen_Game.g.inl : 게임 마다 다름
65. SilvervineUE4LuaCodeGen
• 간단한 성능 비교
• 평균적으로 스태틱 바인딩 코드 패스가 리플렉션 코드 패스에 비해 빠르다.
• Scripts/Test/PerformanceTest.lua 참고
0 0.01 0.02 0.03 0.04 0.05 0.06
UObject* Getter
UObject* Setter
int32 Getter
int32 Setter
TestArg(…)
TestArg(Args)
Reflection Static Binding
66. SilvervineUE4LuaCodeGen
• 스태틱 바인딩 활성화가 기본값
• 실행 속도가 빠르기 때문에 기본값이 True
• 대신, 빌드 속도가 느리다(10~30초 추가)
• 원한다면 비활성화 할 수 있다(DefaultSilvervineUE4Lua.ini 수정)
[/Script/SilvervineUE4Lua.SUE4LuaSettings]
bEnableStaticBinding = False
67. SilvervineUE4LuaCodeGen
• 디폴트 파라메터 테이블
• 함수 호출 시 생략 가능한 파라메터의 기본값이 저장되어 있는 Lua 테이블을 생성한다.
• 함수 파라메터가 생략되면 Lua 테이블에서 기본값을 찾는다.
• 메타 정보가 에디터 빌드에서만 접근 가능하기 때문에 이와 같은 파일이 필요하다.
• 모듈 의존성을 제거하기 위해 C++이 아닌 Lua 코드로 생성한다.
--SpawnDecalAttached
DefaultParameters.GameplayStatics.SpawnDecalAttached = Class(DefaultParameterClass)
DefaultParameters.GameplayStatics.SpawnDecalAttached.AttachPointName = "None"
DefaultParameters.GameplayStatics.SpawnDecalAttached.LocationType = 0 --EAttachLocation::Type::KeepRelativeOffset
DefaultParameters.GameplayStatics.SpawnDecalAttached.LifeSpan = 0.000000
생성된 코드 샘플
68. SilvervineUE4Lua
• 예외 처리
• VM에 에러 핸들러가 구현되어 있음
• C++에서 Lua 함수를 호출할 때는 항상 lua_pcall() 사용
• Lua xpcall에서 사용 가능(SUE4Lua.EventHandler)
[Log] [Game/Main.lua:10] DispatchHandlerFactory: Sedan_C
[Log] [SUE4Lua/Framework/SUE4LuaBinding.lua:103] Dispatch Handler Registered: Sedan_C
Game/Sedan.lua
[Log] [Game/Sedan.lua:62] Sedan:ReceiveBeginPlay() was called.
[Log] [Game/Main.lua:10] DispatchHandlerFactory: CameraComponent
[Error] [Game/Sedan.lua:23] 'Activatee': is not a member of 'CameraComponent'.
[Error] [Game/Sedan.lua:23] FATAL: attempt to call a nil value (method 'Activatee')
[Log] [Game/Main.lua:10] DispatchHandlerFactory: VehicleHUD_C
[Log] [SUE4Lua/Framework/SUE4LuaBinding.lua:103] Dispatch Handler Registered: VehicleHUD_C
Game/VehicleHUD.lua
[Log] [FSUE4LuaVMContext::Dispose] Disposing '00000153CEEA5608'...
70. SilvervineUE4Lua
• 디버깅
• VSCodeLuaDebug에 크게 의존하고 있다.
• 디버기 설정은 샘플 프로젝트의 Main.lua를 참고(SUE4LuaSample/Scripts/Main.lua)
• 파일 로딩 전에 설치된 브레이크포인트가 작동하지 않는 문제가 있다.
• 개발 빌드는 시작할 때 모든 Lua 파일을 미리 로딩해 둠
71. SilvervineUE4Lua
• Lua 객체를 C++에 저장하기
• LuaValue(USUE4LuaValue)를 통해 모든 Lua 값을 C++에 저장할 수 있다.
• 저장된 값은 LuaValue 파괴 전까지 gc되지 않는다.
• 저장된 값을 C++에서 사용하기
• boolean, integer, number, string 면 해당 값을 C++ 타입으로 반환
• 테이블 필드에도 접근 가능하다.
• 함수면 직접 호출할 수 있다.
• UObject(userdata)는 UObject*로 반환
• UStruct, TArray 등은 (아직) 지원 안함
72. SilvervineUE4Lua
• Lua 스택에 Push 할 수 있는 타입
• C++ 기본 타입: (u)int8/16/32/64, float/double, bool, char*, TChar*(wchar)
• UE4 스트링: FName, FString, FText
• Enum : C++ enum, enum class, UEnum
• UObject: UObject 및 모든 파생 클래스 -> userdata
• UStruct: FVector, FColor 등의 구조체는 미리 정의된 table, 나머지는 userdata
• 그리고 LuaValue: 저장된 Lua 값을 푸시
• TArray 등의 컨테이너 타입은 지원하지 않는다.
• 하지만 함수 파라메터로는 전달할 수 있다.
• UFunction이 UStruct의 파생 클래스이기 때문
73. SilvervineUE4Lua
• Lua에서 UProperty에 접근
• Lua에서 UObject, UStruct의 UProperty를 읽고 쓸 수 있다.
• UE4 리플렉션 시스템 및 스태틱 바인딩을 사용해서 구현
• 배열 및 컨테이너 지원
• 배열(int32[]), 컨테이너(TArray, TMap, TSet) 모두 지원한다.
• pairs()로 원소 순회 가능
74. SilvervineUE4Lua
• 기본 타입이 아닌 UProperty는 프록시 객체를 생성
• UObject, UStruct, 배열, TArray, TMap, TSet, Delegate가 해당
• userdata 형식의 프록시를 생성하고 weak 테이블에 캐싱함
• 프록시 객체는 소유자 UObject에 대한 약참조를 생성
• C++은 Lua 값에 대한 강참조 가능
• Lua는 C++에 대한 약참조만 가능
• Lua에서 강참조가 필요하다면 C++에 저장하면 된다.
75. SilvervineUE4Lua
• 배열과 컨테이너의 원소 반환
• 값을 반환 : 안전하지만 복사 비용이 발생
• 참조를 반환 : 위험하지만 비용 부담이 없다.
• 고정 크기 배열은 참조를 반환해도 안전
• 메모리가 재할당되지 않기 때문
• TArray 등은 참조를 반환하면 위험
• 내부 버퍼가 재할당될 수 있기 때문
• 재할당이 발생하지 않는 경우에만 참조 반환을 쓰자.
76. SilvervineUE4Lua
• Lua에서 클래스 함수 호출
• C++, Blueprint 함수 모두 호출할 수 있다.
• UE4 리플렉션 및 코드젠된 스태틱 바인딩 코드를 사용함
• 파라메터 전달 과정이 약간 복잡한데 이후 슬라이드에서 설명
• 코딩 편의를 위한 추가 기능
• UClass 타입은 편의상 대상 클래스의 UFunction을 바로 호출할 수 있다.
• 이름에 ‘K2_’ 접두사가 붙은 함수는 접두사 없는 이름으로 호출 가능
77. SilvervineUE4Lua
• Lua에서 UFunction이 아닌 클래스 함수 호출
• 예를 들면 UObject::IsA()
• 간단하게 바인딩 함수를 구현할 수 있다
• Public/Bindings/*.h 참고
//
// UObject에 대한 lua 바인딩
//
UCLASS(NotBlueprintType)
class SILVERVINEUE4LUA_API USUE4LuaObjectBinding : public USUE4LuaBinding
{
GENERATED_BODY()
public:
// Begin USUE4LuaBinding Interface
virtual UClass* GetBindingClass() const override;
// End USUE4LuaBinding Interface
UFUNCTION()
static FString LuaGetName(UObject* InSelf);
UFUNCTION()
static UClass* LuaGetClass(UObject* InSelf);
UFUNCTION()
static bool LuaIsA(UObject* InSelf, FName ClassName);
UFUNCTION()
static class UWorld* LuaGetWorld(UObject* InSelf);
};
function VehicleHUD:ReceiveDrawHUD(Params)
--SUE4Lua.Log("VehicleHUD:ReceiveDrawHUD() was called.")
local SizeX = Params.SizeX
local SizeY = Params.SizeY
local OwningPawn = self:GetOwningPawn()
local Sedan = (UE4.IsValid(OwningPawn) and OwningPawn:IsA('Sedan_C’)) …
78. SilvervineUE4Lua
• Lua에서 클래스 함수 호출 시 파라메터 및 반환값 전달
• (거의) 모든 타입을 지원: int32부터 TArray까지
• 파라메터는 테이블로 전달할 수 있다.
• 참조형 파라메터(출력값)도 지원(파라메터가 테이블 값이 갱신된다).
• 반환값 및 출력값은 순서대로 Lua 함수의 반환값으로 전달된다.
UFUNCTION()
int32 TestArgs(int32 InIntArg1, int32 InIntArg2, const int32& InIntArg3, int32& OutIntArg1, int32& OutIntArg2);
local Ret, OutIntArg1, OutIntArg2 = self:TestArgs({
InIntArg1 = 1,
InIntArg2 = 2,
InIntArg3 = 3,
OutIntArg1 = 4,
OutIntArg2 = nil,-- 생략 가능
})
79. SilvervineUE4Lua
• C++에서 Lua 함수 호출
• FSUE4LuaFunction 클래스를 사용하거나,
• 딜리게이트에 Lua 함수를 바인딩해서 호출할 수 있다.
• 딜리게이트 Lua 바인딩 구현이 약간 특이함
• 일단, 딜리게이트에 비어 있는 더미 함수를 바인딩 해둔다.
• ProcessEvent() 함수를 오버라이드한 후 그 안에서 Lua 함수를 호출
• 자세한 사항은 USUE4LuaValue::ProcessEvent() 구현을 참고
80. SilvervineUE4Lua
• Blueprint 디스패치
• 다른 스크립트 플러그인에는 없는 독특한 기능.
• Blueprint 스택을 읽어서 Lua 스택에 바로 푸시한다.
• 때문에 Blueprint 노드 파라메터를 디스패치 노드로 연결하는 작업이 필요없다.
function SomeClass:Func(Params)
local InParams1 = Params.InParam1
local InParams2 = Params.InParam2
local InParams3 = Params.InParam3
-- ...
Params.OutParam1 = ...
Params.OutParam2 = ...
end
81. SilvervineUE4Lua
• 네이티브(C++) 디스패치
• Blueprint 디스패치와 비슷한 기능
• 디스패치라고 부르고 있지만 사실 단순한 LuaFunction 호출이다.
• 리플렉션을 사용하지 않기 때문에 파라메터를 배열로 Lua에 전달한다.
// NativeDispatch()를 쉽게 사용하기 위한 매크로입니다.
//
// 사용 예:
//void UMyClass::Foo(int32 P1, int32 P2)
//{
// SUE4LUA_DISPATCH(LuaBridge, P1, P2);
//}
#define SUE4LUA_DISPATCH(LuaBridge, ...)
if( LuaBridge)
{
LuaBridge->NativeDispatch(this, ANSI_TO_TCHAR(__func__), __VA_ARGS__);
}
82. SilvervineUE4Lua
• 디스패치 핸들러 팩토리
• 클래스의 디스패치에 사용할 Lua 파일(디스패치 핸들러) 이름을 반환하는 함수를 말한다.
• Lua로 구현해서 VM에 설정한다.
SUE4Lua.SetDispatchHandlerFactory(function (CallerClass)
local ClassName = CallerClass:GetName()
SUE4Lua.Log("DispatchHandlerFactory: ", ClassName)
local Filenames = {
Sedan_C = "Game/Sedan.lua",
VehicleHUD_C = "Game/VehicleHUD.lua",
}
return Filenames[ClassName] or ""
end)
83. SilvervineUE4Lua
• 디스패치 핸들러 직접 호출
• 디스패치 핸들러 Lua 함수에서 다른 디스패치 핸들러의 Lua 함수를 바로 부를 수 있다.
• 구조적으로 마음에 들지 않지만 확실히 컨텐츠 구현할 때 유용하다...
• 최소한 함수 네이밍이라도 다르게 해야 할 것 같다(내부 논의 중)
function ClassA:FuncA(Params)
self.ObjectB:FuncB(1, 2, 3)
end
function ClassB:FuncB(P1, P2, P3)
SUE4Lua.Log(P1, P2, P3) -- 1 2 3
end
84. SilvervineUE4Lua
• 코루틴 및 멀티스레드
• 멀티스레드는 지원하지 않는다. 필요하다면 VM을 분리해서 사용.
• 최근에 코루틴을 지원하기 시작했다. 아직 불안정함.
85. SilvervineUE4Lua
• 알고 있는 문제들
• UE4 핫 리로딩 이후에 Lua에서 잘못된 함수를 호출하는 문제
• 딜리게이트에 시그니처가 맞지 않는 함수를 바인딩하면 호출할 때 크래시
• SUE4Lua 오류 메시지가 너무 불친절함
• 이후 작업
• 오류 메시지를 이해하기 쉽게 수정
• REPL(Read-Eval-Print-Loop) 지원
87. SilvervineUE4Lua
• UE4 Blueprint의 단점을 보완하기 위해 Lua를 사용
• Lua와 같은 텍스트 스크립트 언어를 사용하여 Blueprint 복잡도를 낮췄다.
• 텍스트 자료형을 사용하여 협업에 어려움이 없도록 개선했다.
• 실제 업무에 적용하여 좋은 결과를 얻고 있다.
• 플러그인 소스코드 공개
• GitHub에 소스코드를 공개함. 많은 피드백 부탁 드립니다!
• 스크립트 플러그인을 제작할 때 도움이 될 만한 경험을 공유