* 인벤토리, 장착

 

- 슬롯 관련(장착 슬롯, 인벤토리)

 

* Inventory Gui 부모 객체에 따라 크기 조절되도록 구현
* Inventory Gui 상수 값(CommonConstant)에 따라 자동으로 레이아웃을 만들어서 슬롯을 배치하도록 구현
* Inventory와 Backpack 연결 후 서버, 클라이언트 정보 갱신
* Inventory 정보를 서버, 클라이언트 동기화 되도록 구현

* EquipSlots Gui 부모 객체에 따라 크기 조절되도록 구현
* EquipSlots Gui 상수 값(CommonConstant)에 따라 자동으로 레이아웃을 만들어서 슬롯을 배치하도록 구현
* EquipSlots 정보를 서버, 클라이언트 동기화 되도록 구현
* Inventory, EquipSlots Gui 단축키 바인딩
* 아이템 장착시 리플리케이션 순서 문제 해결
  => 이벤트 도중에 EquipSlots 갱신 패킷을 보냈더니 해당 Tool이 갱신되지 않은 상황
  => 이벤트를 바인딩하여 처리하지 않고 리플레케이션이 완료되면 패킷을 보내 처리하도록 한다.
* Unequip 로직 Equip로직과 비슷한 시퀀스로 변경
* 방어구 장착 시 메시 데이터 본에 붙여서 같이 시뮬레이션 되도록 작업(Tool 하위 구조 설계)

* Tooltip Gui 부모 객체크기에 따라 조절되도록 구현
* Tooltip에서 해당 Tool의 ToolGameData기반으로 출력
* Tooltip에서 슬롯과 Tool 종류에 따라 적절한 버튼이 나올 수 있도록 구현
* Tooltip에서 버튼에 따라 적절한 요청할 수 있도록 버튼에 요청 함수 바인딩
* Tooltip에서 클라이언트에서 Tool 장착 요청 처리
* Tooltip에서 클라이언트에서 Tool 장착해제 요청 처리
* Tooltip에서 클라이언트에서 Tool 선택 요청 처리
* Inventory, EquipSlot 닫을 때 Tooltip도 닫히도록 구현

 


- 공통

 

* EquipSlots, Inventory, PlayerStatisic 등을 위한 서버와 클라이언트에 저장소 확보와 저장소 구조 설계
* 서버 클라이언트 패킷 구현부 분리

 

 

- 영상

 

 

 

- Github

 

https://github.com/HwangJeongIn/ProjectR

 

GitHub - HwangJeongIn/ProjectR: Roblox Battle Royal

Roblox Battle Royal. Contribute to HwangJeongIn/ProjectR development by creating an account on GitHub.

github.com

 

* Lua의 메모리 관리 : 가비지 컬렉터(Garbage Collector)

 

Lua에서는 자동 메모리 관리를 지원한다. Lua는 가비지 컬렉션을 통해 자동으로 메모리를 관리한다. 따라서 메모리를 수동으로 관리하는 관리 과정에서 나타날 수 있는 허상포인터, 메모리 누수에 대해서 자유롭다.

 

다른 가비지 컬렉터과 다르게 Lua의 가비지 컬렉터은 사이클에 대해 안전하기 때문에 순환하는 자료 구조를 구현할 떄 따로 신경쓰지 않아도 된다. 

 

 

- 특정 상황에서의 가비지 컬렉션(Garbage Collection)

 

가비지 컬렉터은 내부 로직에 의해 가비지라고 간주한 메모리만 수집하기 때문에 예상한 것과 다르게 동작할 수 있다.

 

예를 들어 Stack을 구현할 때 예전에 Pop 되었던 값들이 남아 있을 수 있고, 전역 변수의 경우 사용되지 않는 경우라고 해도 삭제되지 않는다. 이 경우 개발자가 명시적으로 nil을 할당하여 해결할 수 있다.

 

프로그램에서 컬렉션을 사용하는 경우 컬렉션에 들어있는 객체는 수집되지 않는다. 예를 들어보면 일반적인 테이블에서는 키나 값이 객체일 경우 strong reference이기 때문에 가비지 컬렉터가 수집하지 못한다. 

 

 

* Weak Table

 

- 가비지 수집을 허용하는 Weak Table

 

Weak Table은 객체를 수집할 수 있도록 하기 위한 테이블이다. Weak Table은 모든 객체에 대한 참조를 weak reference로 수행한다. weak reference는 객체에 대한 참조로 가비지 컬렉터가 고려하지 않는 참조이기 때문에 가비지 컬렉터의 가비지 수집을 방지하지 않는다. 따라서 Weak Table에 존재하는 객체는 가비지 컬렉터가 수집할 수 있다.

 

Weak Table은 키, 값, 키와 값 을 weak reference로 가질 수 있기 떄문에 3종류 존재하며 키나 값이 수집되면 해당 항목이 테이블에서 사라진다.

 

 

- Weak Table의 사용 방법

 

테이블의 필드인 __mode를 통해 Weak Table로 설정할 수 있다. 

    a = {}
    b = {}
    setmetatable(a, b)
    b.__mode = "k"         -- now `a' has weak keys
    key = {}               -- creates first key
    a[key] = 1
    key = {}               -- creates second key
    a[key] = 2
    collectgarbage()       -- forces a garbage collection cycle
    for k, v in pairs(a) do print(v) end
      --> 2

 

객체 타입에만 적용되며 값 타입(정수형 등)은 적용되지 않는다. 문자열 또한 프로그래머 입장에서는 값 타입이기 때문에 동작하지 않는다.

 

* Rojo로 포팅

 

- 특징

 

  • VS Code에서 Rojo 플로그인을 설치하여 Roblox를 연결해서 사용한다.
  • default.project.json를 통해서 로블록스의 폴더와 VS Code의 폴더를 연결시킬 수 있다. 이 정보를 기반으로 VS Code의 코드가 변경되면 로블록스를 갱신할 수 있다.
  • VS Code(서버)에서 에디팅하여 Roblox(클라이언트)에 반영한다. // 양방향 갱신이 가능하긴 하지만 크래시가 발생하는 버그가 있다.
  • 간단한 것들은 .model.json 파일에 클래스의 이름과 이름을 정의하여 추가할 수 있다.
  • Script : .server.lua / LocalScript : .client.lua / ModuleScript : .lua or .json 로 매칭된다.
  • 폴더가 아닌 스크립트 하위에 존재하는 스크립트를 만들기 위해서는 상위 스크립트를 폴더로 만들고 Script / LocalScript / ModuleScript에 따라 init.server.lua / init.client.lua / init.lua을 폴더 내부에 작성하면 된다. 그러면 로블록스에서 각 스크립트로 인식할 수 있다.
  • 폴더에 여러 가지 정보를 넣는 것도 가능한데, init.meta.json을 통해 해당 폴더가 적절한 클래스로 변경하거나 속성 값들을 설정할 수 있다.

 

 

- 완료

 

  • Rojo로 빌드된 프로젝트로 바로 작업할 수 있도록 모든 환경을 포팅
  • 폴리곤 데이터의 집합체(맵, 무기) 등은 파일로 추출하여 Rojo에서 그대로 사용
  • 서버 클라이언트 통신에 쓰이는 RemoteEvents, RemoteValues를 폴더로 정리 < 간단한 값이기 때문에 Rojo의 .model.json 을 사용하여 간단히 정의
  • Gui 구조 정리, 부모 Gui에 따라서 적절하게 크기 변경
  • 스트립트 별로 따로 관리하고 필요에 따라 스크립트를 복제하여 각 객체에 부착 / 가능하면 객체(도구, 무기 등)은 스크립트를 포함하지 않도록
  • 슬롯(인벤토리, 장착 슬롯) 기능을 위한 컨테이너 TArray, TList 작성

 

https://github.com/HwangJeongIn/ProjectR

 

GitHub - HwangJeongIn/ProjectR: Roblox Battle Royal

Roblox Battle Royal. Contribute to HwangJeongIn/ProjectR development by creating an account on GitHub.

github.com

 

 

- 참고 자료

 

https://rojo.space/docs/v0.5/sync-details/

 

Sync Details | Rojo

This page aims to describe how Rojo turns files on the filesystem into Roblox objects.

rojo.space

 

* 중간 완료된 작업(기록용)

 

- 입력

* 클라이언트에 KeyBinder 추가, 특정 키에 대해서 기능을 바인딩할 수 있도록 인터페이스 제공


- 게임 데이터

* 클라이언트 Gui에 모든 정보를 패킷을 통해 받으면 네트워크 성능이 안좋다고 판단하여 어쩔 수 없이 클라이언트에도 일부 게임 데이터 공개, CommonGameDataManager를 공용, ServerGameDataManager를 서버 전용으로 사용


* 클라이언트에서 적절하게 데이터를 읽어서 Gui로 띄울 수 있도록 기본적인 정보는 클라이언트에서 가지고 있도록 결정


* CommonGlobalStorage에서 공용 정보(방어구, 무기 장착 정보 / 인벤토리 정보 / 플레이어 능력치 합산 정보) 제공

  기본적으로 서버의 ServerGlobalStorage에서 갱신된 것을 클라이언트로 통보하는 형식으로 설계


* ClientGlobalStorage에서 QuickSlot 등록, 해제, 퀵슬롯간 스왑 기능 제공
  클라이언트 전용으로 서버는 퀵슬롯을 모르고 클라이언트에서 쿽슬롯에 있는 아이템 사용 요청만 받는다.

 


- UI

* 월드 객체 툴팁 UI(빌보드), 마우스에 타겟팅되는 도구(무기 방어구)의 정보를 띄워주는 기능 구현
* 일반 툴팁 UI 작성 완료
* EquipSlot, Inventroy UI 작성 완료

 

* 로블록스 클라이언트 서버 모델

 

- 로블록스 게임 접속

 

여러 기기에서 게임에 시작할 때 로블록스 컴퓨터에 접속하게 된다. 기기는 클라이언트 로블록스 컴퓨터는 서버다. 

 

 

- 서버 => 클라이언트

 

게임 플레이하는 동안 서버는 지속적으로 클라이언트를 업데이트 한다. 

예를 들어 서버용 스크립트인 Script가 시간을 바꾸면 서버는 클라이언트에게 통보해준다.  

 

 

- 클라이언트 => 서버

 

클라이언트에서 입력이나 제어(채팅 등)가 발생했을 때 클라이언트는 서버에게 업데이트 요청을 보내고 서버는 업데이트를 수행하고 모든 플레이어에게 통보해준다.

 

@ 예시1 : 이동 입력 처리

 

클라이언트에서 입력을 처리하는 과정은 다음과 같다.

  • 게임을 시작하면 로블록스 플레이어 스크립트에는 PlayerModule이라는 것이 생성된다.
  • PlayerModule 내부에 ControlModule이 있고 이 모듈에서 입력 처리도 담당한다.
  • ControlModule 내부에는 Keyboard가 있는데 Keyboard에서 입력을 받아서 MoveVector를 계산하여 보관해둔다.
  • ControlModule의 OnRenderStepped 함수에서 Keyboard에서 미리 계산해 두었던 MoveVector를 사용하여 LocalPlayer의 Move함수를 호출한다.
  • Player.Move는 코드가 가려져있지만 아마 서버에게 MoveVector로 시뮬레이션을 요청하는 코드가 들어가 있을 것이다. 

 

 

@ 예시2 : 채팅

 

  • 게임을 시작하면 로블록스 플레이어 스크립트에는 ChatScript라는 것이 생성된다.
  • ChatScript 내부에 ChatMain가 있고 ChatMain은 채팅 바가 포커스를 잃을 때 chatBarFocusLost라는 함수로 바인딩하여 처리하고 있다.
  • 채팅 바가 포커스를 잃을 때 엔터키가 눌러진 상황이라면 채팅 바에 있는 메시지를 가공한다.
  • 가공된 메시지는 MessageSender에서 MessageSender의 SendMessage를 호출한다.
  • SendMessage에서 ReplicatedStorage에 존재하는 SayMessageRequest 원격 이벤트(RemoteEvent(RPC와 비슷))를 통해서 메시지와 채널을 넣어서 서버로 요청한다.
  • SayMessageRequest 원격 이벤트를 바인딩하고 있는 서버는 해당 정보를 기반으로 채팅을 처리해준다.

 

 

- 클라이언트 코드와 서버 코드 작성

 

@ 클라이언트

클라이언트에서는 플레이어 입력을 추적해야하고, 정보를 특정 플레이어(본인 LocalPlayer)에게 Gui를 통해 보여줘야 한다. 클라이언트 코드는 LocalScript에서 수행된다. 당연히 서버와 관련된 ServerScriptService와 같은 곳에는 LocalScript를 수행할 수 없다.

 

@ 서버

서버에서는 게임 로직, 데이터 저장(DB), 각종 업데이트, 생성 등을 수행하고 모든 클라이언트에게 통보해야한다. 서버 코드는 Script에서 수행된다. 클라이언트와 관련된 PlayerGui와 같은 곳에서 사용할 수 없다.

 

 

- RemoteFunction, RemoteEvent

 

RemoteFunction, RemoteEvent는 각 서버와 클라이언트에서 클라이언트와 서버로 통신하기 위한 수단이다. 서버와 클라이언트에서 모두 접근할 수 있는 ReplicatedStorage에 넣고 해당 이벤트나 함수가 들어왔을 때 어떻게 처리할 것인지 적절하게 함수를 바인딩하면 된다. 다만 클라이언트에서 서버로 올라온 이벤트나 함수는 믿을 수 없기 때문에 한번 검증 후 사용한다. 예를 들어 플레이어가 스킬 사용을 서버에게 요청하면 해당 플레이어가 스킬을 사용할 수 있는 상태(플레이어 상태, 착용한 도구 상태, 소모 자원 상태 등)인지 서버에 있는 데이터 기반으로 검증 후 허가 해야한다.

 

 

 

 

* 데미지 로직 수정

 

공격 가능 도구 관련 코드

-- 로컬 변수 정의, 바인드 --------------------------------------------------------------------------------------------

local ServerStorage = game:GetService("ServerStorage")
local ServerModuleFacade = require(ServerStorage:WaitForChild("ServerModuleFacade"))
local Utility = ServerModuleFacade.Utility
local Debug = ServerModuleFacade.Debug
local ServerConstant = ServerModuleFacade.ServerConstant
local ServerGlobalStorage = ServerModuleFacade.ServerGlobalStorage

local GameDataType = ServerModuleFacade.ServerEnum.GameDataType

local ToolBase = Utility.DeepCopy(require(ServerModuleFacade.ToolModule:WaitForChild("ToolBase")))

local Tool = script.Parent
local Anim1 = Tool.anim1
local isAttacking = false

ToolBase:InitializeAll(GameDataType.Tool, Tool)

-- 함수 정의 ------------------------------------------------------------------------------------------------------

function CanAttack(otherPart)
	
	if not otherPart then
		Debug.Assert(false, "대상이 존재하지 않습니다.")
		return false
	end
	
	local otherModel = otherPart.Parent
	if not otherModel then
		Debug.Assert(false, "모델이 존재하지 않습니다.")
		return false
	end
	
	if not Tool then
		Debug.Assert(false, "도구가 없습니다.")
		return false
	end
	
	local toolParent = Tool.Parent
    
    -- 자기자신인 경우
	if toolParent == otherModel then
		return false
	end
    
	local toolParentClassName = toolParent.ClassName
	if not toolParentClassName 
		or toolParentClassName == "Workspace" 		-- 필드에 존재
		or toolParentClassName == "Backpack" then	-- 가방에 존재
		Debug.Assert(false, "비정상입니다.")
		return false
	end
	
	return true
end

function CalcDamage(attackerCharacter, attackeeCharacter)
	
	local attackerSTR = 0
	local attackeeDEF = 0
	
	-- ==== 캐릭터 계산 ====
	local attackerCharacterGameData = ServerGlobalStorage:GetGameData(attackerCharacter, GameDataType.Character)
	local attackeeCharacterGameData = ServerGlobalStorage:GetGameData(attackeeCharacter, GameDataType.Character)
	
	-- 캐릭터 공격 데이터
	if not attackerCharacterGameData then
		Debug.Assert(false, "캐릭터 정보가 없습니다." .. attackerCharacter.Name)
        	return 0
	else
		attackerSTR += attackerCharacterGameData.STR
	end
	
	-- 캐릭터 방어 데이터
	if not attackeeCharacterGameData then
		Debug.Assert(false, "캐릭터 정보가 없습니다." .. attackeeCharacter.Name)
        	return 0
	else
		attackeeDEF += attackeeCharacterGameData.DEF
	end
	
	-- ==== 도구 계산 =====
	local attackerToolGameData = ServerGlobalStorage:GetGameData(attackerCharacter, GameDataType.Tool)
	local attackeeToolGameData = ServerGlobalStorage:GetGameData(attackeeCharacter, GameDataType.Tool)
	
	-- 도구 공격 데이터
	if attackerToolGameData and attackerToolGameData.STR then
		attackerSTR += attackerToolGameData.STR
	end
	-- 도구 방어 데이터
	if attackeeToolGameData and attackeeToolGameData.DEF then
		attackeeDEF += attackeeToolGameData.DEF
	end
	
	local finalDamage = ServerConstant.DefaultAttackPoint + (attackerSTR * ServerConstant.DefaultSTRFactor) - attackeeDEF
	finalDamage = math.clamp(finalDamage, 0, 100)
	
	return finalDamage
end

function AttackCharacter(attackerCharacter, attackeeCharacter)

	local damage = CalcDamage(attackerCharacter, attackeeCharacter)
	Debug.Log("Damage : ".. tostring(damage))
	if damage == 0 then
		return
	end

	local attackeeCharacterHumanoid = attackeeCharacter:FindFirstChild("Humanoid")
	if not attackeeCharacterHumanoid then
		Debug.Assert(false, "Humanoid가 없습니다.")
		return
	end

	attackeeCharacterHumanoid:TakeDamage(damage)
	
end

function Attack(attackeePart)
	if CanAttack(attackeePart) == false then
		--Debug.Assert(false, "공격할 수 없습니다.")
		return
	end
	
	
	local attackeePlayer = game.Players:GetPlayerFromCharacter(attackeePart.Parent)
	--print(attackeePlayer)
	--print(attackeePart)
	--print(attackeePart.Parent)
	if not attackeePlayer then
		-- 추가해야한다.
		Debug.Log("플레이어가 아닙니다.")
	else
		local attackerCharacter = Tool.Parent
		local attackeeCharacter = attackeePart.Parent
		AttackCharacter(attackerCharacter, attackeeCharacter)
	end
end

function onTouched(otherPart)
	if isAttacking == false then return end
	isAttacking = false
	
	Attack(otherPart)
end

function onActivated()
	isAttacking = true
	local humanoid = Tool.Parent:FindFirstChild("Humanoid")
	local anim1Track = humanoid:LoadAnimation(Anim1)
	anim1Track:Play()

	anim1Track.Stopped:Connect(function() isAttacking = false end)
end


-- 이벤트 바인드
Tool.Activated:Connect(onActivated)
Tool.Attacker.Touched:Connect(onTouched)

 

객체 기본 코드 추가

function ObjectBase:Initialize(objectGameDataType, objectRoot)
	
	if not objectRoot then
		Debug.Assert(false, "입력이 비정상입니다. GameDataType => " .. tostring(objectGameDataType))
		return	
	end

	if not objectGameDataType or type(objectGameDataType) ~= "number" then
		Debug.Assert(false, "입력이 비정상입니다.")
		return	
	end


	local objectGameDataKey = objectRoot:FindFirstChild("Key")
	if not objectGameDataKey then
		Debug.Assert(false, "객체에 키 태그가 존재하지 않습니다.")
		return
	end

	objectGameDataKey = objectGameDataKey.Value

	local objectGameData = GameDataManager[objectGameDataType]:Get(objectGameDataKey)
	if not objectGameData then
		Debug.Assert(false, "데이터가 존재하지 않습니다.")
		return
	end
	
	local interalData = {
		root = objectRoot,
		gameDataType = objectGameDataType,
		gameDataKey = objectGameDataKey,
		gameData = objectGameData,
	}
	
	self.Root = function()
		Debug.Assert(interalData.root, "ObjectRoot가 존재하지 않습니다. 초기화 해주세요.")
		return interalData.root
	end

	self.GetGameDataType = function()
		Debug.Assert(interalData.root, "ObjectRoot가 존재하지 않습니다. 초기화 해주세요.")
		Debug.Assert(interalData.gameDataType, "GameDataType이 존재하지 않습니다. 초기화 해주세요.")
		return interalData.gameDataType
	end

	self.GetGameDataKey = function()
		Debug.Assert(interalData.root, "ObjectRoot가 존재하지 않습니다. 초기화 해주세요.")
		Debug.Assert(interalData.gameDataType, "GameDataType이 존재하지 않습니다. 초기화 해주세요.")
		Debug.Assert(interalData.gameDataKey, "GameDataKey가 존재하지 않습니다. 초기화 해주세요.")
		return interalData.gameDataKey
	end

	self.GetGameData = function()
		Debug.Assert(interalData.root, "ObjectRoot가 존재하지 않습니다. 초기화 해주세요.")
		Debug.Assert(interalData.gameDataType, "GameDataType이 존재하지 않습니다. 초기화 해주세요.")
		Debug.Assert(interalData.gameData, "GameData가 존재하지 않습니다. 초기화 해주세요.")
		return interalData.gameData
	end
end

 

 

* 기타 수정

 

- 서버

* 서버 전역 저장소 기능 추가 및 정리, 캐릭터마다 저장한 게임 데이터를 관리할 수 있도록 수정 완료
// static private 변수와 효과가 비슷한 내부 local 변수를 사용 , 무조건 싱클톤으로만 사용해야됨

- 객체

* ObjectBase 기능 추가, 공용으로 사용할 수 있는 기능들 추가(Root, GetGameData 등), 함수를 통해서만 접근가능하도록 설계

- 도구

* 도구와 캐릭터의 게임 데이터를 통해 공격력, 방어력 반영
* 도구와 기타 정보들을 서버에서 로드해서 생성하여 플레이어에게 제공, 정적인 도구라면 그냥 서버 스토리지에서 찾아서 제공

- 기타

* Debug.Assert, Log에 콜스택 찍히도록 수정
* Debug 모듈에 Print 함수 추가
* 각 게임 데이터에서 게임 데이터 타입 받아올 수 있도록 수정

 

 

* 구조 정리, 모듈 추가

 

 

  • 코드에서 쉽게 접근할 수 있도록 Facade 추가
  • Debug 코드 추가 - Assert, Log 함수
  • ServerConstant 추가 - 서버 전용 상수
  • ServerGlobalStorage 추가 - 모든 스크립트에서 전역 변수로 사용할 수 있는 공간 // 멀티스레드(Parallel)을 사용할 경우 동기화에 주의해야한다.
  • ObjectBase 설계 - 액션 바인딩 지원
  • ToolModule 추가, InteractorModule 추가, VehicleModule 추가
  • ToolModule 코드정리, Attack에서 Damage 계산 시 GameData 적용, 검증 함수 추가
  • GameData 없는 속성에 접근하려고 할 때 Assert 추가

 

* GameDataManager

 

다음과 같은 원칙을 지키면서 개발하였다.

 

  • 여러 곳에서 참조할 수 있고 이를 기반으로 캐릭터의 여러 스탯들을 결정하기 때문에 변경되면 안된다.
  • 읽기 전용으로 만들기 위해 __newindex를 빈 함수로 설정하여 막고있다.
  • 키에 따라 게임데이터를 반환할 때 게임데이터의 테이블을 직접 반환하는 것이 아니라 메타테이블로 설정하여 반환한다.(앞에서 __newindex를 빈함수로 설정한 효과를 볼 수 있다.)

 

 

- GameData 의 메타테이블 : GameDataBase

 

해당 게임데이터가 존재하는지 확인하고 받아오는 Get함수를 제공하고 있으며, 각 게임데이터를 readonly로 만들기 위해 Initialize와 Insert 메소드를 제공하고 있다.

local ServerStorage = game:GetService("ServerStorage")
local ServerModule = ServerStorage:WaitForChild("ServerModule")
local Utility = require(ServerModule:WaitForChild("Utility"))


function EmptyFunction() end

local GameDataBase = {}
GameDataBase.__index = GameDataBase
GameDataBase.__newindex = EmptyFunction

-- readonly로 만들어준다.
function GameDataBase:Initialize()
	rawset(self, "__index", self)
	rawset(self, "__newindex", EmptyFunction)
end

function GameDataBase:Get(key)
	
	local keyTypeString = type(key)
	
	if keyTypeString ~= "number" then
		Utility.Assert(false, "잘못된 키값입니다.")
		return nil
	end
	
	local value = self[key]
	
	if not value then
		Utility.Assert(false, "존재하지 않는 키값입니다. => ".. tostring(key))
		return nil
	end
	
	return value
end

function GameDataBase:InsertData(key, value)
	
	local keyTypeString = type(key)
	
	if keyTypeString ~= "number" then
		Utility.Assert(false, "정수형 키만 가질 수 있습니다.")
		return
	end
	
	if self[key] ~= nil then
		Utility.Assert(false, "중복 삽입하려고 합니다.")
		return
	end
	
	local valueTypeString = type(value)
	
	if valueTypeString ~= "table" then
		Utility.Assert(false, "잘못된 값을 삽입하려고 합니다.")
		return
	end
	
	value.__index = value
	value.__newindex = EmptyFunction
	
	rawset(self, key, setmetatable({}, value))
	
end


return GameDataBase

 

 

- GameData 테이블

 

지금은 그냥 데이터를 넣고 있지만 추후 xml과 json과 같은 파일들을 읽어서 삽입할 수 있는 기능을 추가하면 좋을 것 같다.

 

이 현재는 두 가지 데이터를 기반으로 플레이어의 공격력, 방어력, 이동속도, 공격 속도 등을 결정하고 있다. 

 

-- ToolGameData

local GameDataBase = require(script.Parent:WaitForChild("GameDataBase"))

local ToolGameData = setmetatable({}, GameDataBase)
ToolGameData:Initialize()

--[[ 기본 	--]] ToolGameData:InsertData(1, {STR = 10, DEF = 10, Move = 10, AttackSpeed = 10, Skill = ""})
--[[ 검 	--]] ToolGameData:InsertData(2, {STR = 10, DEF = 10, Move = 15, AttackSpeed = 30, Skill = ""})
--[[ 도끼	--]] ToolGameData:InsertData(3, {STR = 25, DEF = 5, Move = 1, AttackSpeed = 10, Skill = ""})

return setmetatable({}, ToolGameData)

 

-- CharacterGameData

local GameDataBase = require(script.Parent:WaitForChild("GameDataBase"))

local CharacterGameData = setmetatable({}, GameDataBase)
CharacterGameData:Initialize()


--[[ 기본 	--]] CharacterGameData:InsertData(1, {STR = 10, DEF = 10, Move = 10, AttackSpeed = 10, Skill = ""})
--[[ 성장	--]] CharacterGameData:InsertData(2, {STR = 50, DEF = 50, Move = 50, AttackSpeed = 50, Skill = ""})

return setmetatable({}, CharacterGameData)

 

 

- GameDataManager 테이블

 

외부에서 사용하기 편하도록 이들을 래핑하고 있는 테이블이다.

local GameDataManager = {
	CharacterGameData = require(script:WaitForChild("CharacterGameData")),
	ToolGameData = require(script:WaitForChild("ToolGameData"))
}

return GameDataManager

 

+ Recent posts