[lua]모딩 일단 따라하기 - 1

https://gall.dcinside.com/mgallery/board/view/?id=factorio&no=23583


1에서 불갑옷예제 프로토타입을 만들고 한글이름을 붙이고 모드 패키징 하는것까지 알려줬음.

내일 쓰려고 준비했는데 준비하고보니 얼마안걸려서(내일까지 반나절넘게 남았다)

그냥 씀.


위키의 불갑옷예제에는 입고 걸어댕기면 바닥에 불이 붙는 효과가 있는데,

https://wiki.factorio.com/Tutorial:Modding_tutorial/Gangsir#The_control_scripting

이 코드 해설을 좀 하겠음.

따라하는건 그냥 복붙해서 control.lua파일을 하나 만들면 되니까 쉽다.

도대체 왜 이거시 이런 효과가 나는지 이해하는게 중요하니까 설명을 하겠어.



control.lua 내용을 작성하려면,

https://lua-api.factorio.com/latest/

이 홈페이지를 사전마냥 코딩 내내 켜놓고 봐야한다.

검색기능이 없기 때문에 구글에 캐시된 내용을 검색하는 식으로 필요한 내용을 찾아가야함.


---------------------------

--control.lua


script.on_event(defines.events.on_player_changed_position,

function(event)

local player = game.get_player(event.player_index) -- get the player that moved

-- if they're wearing our armor

if player.character and player.get_inventory(defines.inventory.character_armor).get_item_count("fire-armor") >= 1 then

-- create the fire where they're standing

player.surface.create_entity{name="fire-flame", position=player.position, force="neutral"}

end

end

)

---------------------------



◆ --control.lua

--는 lua의 코멘트 시작 표현임. 뒤에 적어둔건 걍 메모끄적여둔거다.



◆ script.on_event(☆1,☆2,☆3)

script는 팩토리오의 전역변수임. https://lua-api.factorio.com/latest/LuaBootstrap.html

.on_event는 그것의 메소드 함수. https://lua-api.factorio.com/latest/LuaBootstrap.html#LuaBootstrap.on_event

이 함수 설명을 읽어 봐야 인수로 어떤 값을 넘겨야하는지 알 수 있다.


예제에는 이 함수의 인수로

첫째 인수 ☆1 : defines.eve...

둘째 인수 ☆2 : function(eve...

두 개의 인수가 쓰였다.

세번째 인수 ☆3 자리는 옵션(optional, 안넣어도 상관없음)이라 안넣은듯함.


이 함수는 이벤트를 등록해주는 함수다. ☆1이벤트가 발생하면 ☆2가 자동으로 실행되게끔 만들어준다.

☆3는 필터인데, 이벤트를 일으킨 주체를 가려받는 용도로 사용하고, 여기선 안쓰임.

한가지의 이벤트에 발생시킬 함수를 여러개 등록해주려면 이걸 중복해서 사용하면 절대 안되고 다른 방법이 있다.

이 on_event함수를 같은 이벤트 종류에 그대로 중복해서 쓰면 전에 등록된건 지워지기 때문.

그건 다음에 기회되면 알려줌.



◆ defines.events.on_player_changed_position

defines들은 내부적으로는 숫자 값인데, 인지하기 편하도록 이렇게 defines테이블의 키값의 이름으로 표현되어 있음.

defines는 다른 프로그래밍 언어의 상수 치환 같은 기능을 한다고 보면 됨.

숫자를 넣어도 동작하긴 하는데 그럼 뭔지 모르잖아.

이 표시는 이벤트를 나타냄. 아래 내용을 찾아서 참고할 수 있도록 키값을 적어둔거임.

https://lua-api.factorio.com/latest/events.html#on_player_changed_position

그리고 이 이벤트 정보를 보면 이벤트가 발생했을 때, player_index라는 키값을 가진다고 설명이 되어있어.


그니까 플레이어가 움직이면 이 이벤트가 발생한다는 뜻이지.


그럼 다음 script.on_event의 두번째 인수를 보자.



◆ function(eve... 으로 시작해서 몇줄을 적힌 다음에 )괄호 앞의 ... end로 끝나는 블럭

lua의 함수블럭 표현방식인데, 이렇게 표현하는건 익명함수 표현방법이다.

함수를 선언했는데 함수가 어디 안가고 바로 script.on_event의 두번째 인수자리로 return되기 때문에 따로 함수이름도 필요없는 것.



◆ function(event)

이 함수는 인수를 한개를 받을 건데, 첫째 인수는 함수내부에서 event라는 local변수로 사용될거다. 라고 나타낸거.

이벤트 발생시 실행되는 함수는 죄다 약속이라도 한듯 인수 이름을 event로 사용한다.



◆ local player = game.get_player(event.player_index) 코멘트 빼고.

코멘트 -- 는 구분이 가지. 이 뒤에 있는건 실행이 되지않는 메모내용임.

local은 지역변수 선언을 나타내고, 해당 블럭 안에서만 player라는 값을 액세스 할 수 있게 될 거라는 내용임.

그리고 player에 값을 대입하기 시작함.


변수 선언시, 아무것도 안붙이면 상위 지역변수를 조작하거나, 전역변수를 만드는 일이된다.

근데 팩토리오에서는 절대로 무표기 전역변수를 만들면 안된다.

그 값은 save를 못하게 되고,

save를 못하기 때문에 멀티플레이어에서는 "참가자를 위해 save를 했는데 참가자랑 너의 맵 상태가 일치가 안되서" desync가 일어나서 참가자나 너는 서버에서 쫒겨난다.

무표기 전역변수는 혼자서 lua 코드 테스트 할 때나 쓰는거임.

팩토리오 전역변수 선언은 팩토리오 전역변수인 global이라는 테이블 안에 키값으로써 입력해주면 안전하게 save가능한 값이 된다.

전역변수 관련 : https://lua-api.factorio.com/latest/Global.html


game은 팩토리오 전역변수임. https://lua-api.factorio.com/latest/LuaGameScript.html


.get_player(☆1)는 game의 메소드 함수. https://lua-api.factorio.com/latest/LuaGameScript.html#LuaGameScript.get_player

함수 설명을 보면 인수를 뭘 던져넣어야하고, 어떤 값을 return하는지 설명이 나온다.

링크 타서 보면 The player index or name 을 인수로 받아서 LuaPlayer클래스를 return한다는 내용이 있다.

짧게 쓰면 game.players[event.player_index] 라고 쓰라고 부연설명도 있네. 친절해라.


그리고 .get_player의 인수로 던져진게, event.player_index이다.

여기서 event는 윗줄에 function(event)라고 해서 받은 첫번째 인수를 event라고 부르라고 했기 때문에 event인 것이고 미리 등록된 변수명같은건 아니다.


그리고 on_player_changed_position 이벤트의 설명에, 이벤트가 발생하면 player_index라는 키값을 던져준다고 했기 때문에,

함수 내부로 들어온 event는 테이블값이 되고, 키값으로 player_index를 가지고 있게 된다.

json 형식으로 표현하면 이런거임.

event = {

player_index = 숫자값

}

이 player_index를 읽는거임.



◆◆ if player.character and player.get_inventory(defines.inventory.character_armor).get_item_count("fire-armor") >= 1 then


존나기네. 좀 짜르자. if ... then 은 if문을 시작하는 구문이고,


◆ player.character

위에 local player로 정의한 player는 game.get_player가 LuaPlayer클래스를 return 했기 때문에 아마도 LuaPlayer가 들어가 있을 것이다.

그럼 여기를 참고해야한다.

https://lua-api.factorio.com/latest/LuaPlayer.html


LuaPlayer에서 character라는 키값을 호출하면 뭐가 나오냐...

https://lua-api.factorio.com/latest/LuaPlayer.html#LuaPlayer.character

플레이어가 소유한 캐릭터를 return한다고 되어있네. 캐릭터의 원형 클래스는 LuaEntity임.


player.character 라고 쓰고 그다음에 바로 and 로 가는데, 비교문 조합하는 거임.

player.character가 nil(값 없음)이면 false, 아무튼 뭔가가 return되어서 존재한다면 true로 인식되는 것임.


◆ player.get_inventory(defines.inventory.character_armor).get_item_count("fire-armor")

그리고 아 이거 존나 기네.


위에 적어놓은 LuaPlayer 클래스의 설명페이지에 가보면 get_inventory 메소드 함수가 있음.


문자열 검색으로 찾아보면 이렇게 LuaControl에서 상속되었다고 나와있는데,

이런식으로 상속으로 정의된 메소드나 키가 많으니 좀 주의깊에 볼 필요가 있다.


아무튼간에 누르면 설명이 나옴.

https://lua-api.factorio.com/latest/LuaControl.html#LuaControl.get_inventory


파라메터로 defines.inventory 종류를 던지라고 나오지. 그러면 LuaInventory를 return한다고 나와있다.

그럼

player.get_inventory(defines.inventory.character_armor)

여기까지 실행되면 LuaInventory임. 그것도 캐릭터의 아머슬롯 인벤토리를 나타내겠네.

그리고 그 뒤에 .(점)이 붙고 뭐가 또 호출되었으니 그놈은 LuaInventory소속임.


그니까 .(점) 바로 뒤에 있는 get_item_count함수는 LuaInventory소속임.

https://lua-api.factorio.com/latest/LuaInventory.html#LuaInventory.get_item_count


item을 문자열로 받고, 갯수를 숫자로 return한다고 적혀있다.

"fire-armor" : 프로토타입으로 열심히 만들었던 그 아이템의 이름이다. 프로토타입 만들면서 만든사람이 지어준거지.

그래서 이게 다 실행되면 최종적으로 갯수값이 나오는거임.


◆◆


그럼 on_player_changed_position 이벤트를 일으킨 주체인 플레이어가 멀쩡한 캐릭터를 조작중이면,(캐릭터 없는 에디터 상태같은게 아니라면)

player.character 는 어떤 값이 나오고 and로 합쳐질 예정이기 때문에 boolean값인데 true일테고,

그 캐릭터의 갑옷 슬롯에 fire-armor가 들어있으면 아이템 갯수값은 1이 될테니

if ... then 구문은

if true and 1 >= 1 then이 되는거야.

만약 그렇지 않다면 이 if문의 내용은 실행되지 않고 다음으로 넘어가겠지.


◆ player.surface.create_entity{name="fire-flame", position=player.position, force="neutral"}


이건 자주쓰이는건데,

LuaPlayer는 surface라는 키값을 가지고 있어.(LuaControl로부터 상속됨) 그 플레이어가 서있는 지면(surface)의 LuaSurface를 return하지.

https://lua-api.factorio.com/latest/LuaControl.html#LuaControl.surface

https://lua-api.factorio.com/latest/LuaSurface.html


create_entity{...}는 LuaSurface클래스의 메소드 함수인데,

https://lua-api.factorio.com/latest/LuaSurface.html#LuaSurface.create_entity

함수이름{...} 이렇게 중괄호{}로 된건 받는 인수가 하나이고 그 인수는 그 자리에서 만들어진 table이라는 뜻임.

함수이름(...) 이렇게 괄호()로 된건 받는 인수가 그 자리에서 만들어진 테이블이 아니거나, 인수가 필요없는 함수거나, 인수가 여러개 라는 뜻.

함수이름{...} 이 표현은 함수이름({...}) 표현의 약식표현임. 둘이 같아. 유일한 인수가 table이라는 거지.


local sometable = {...}

surface.create_entity(sometable)

이렇게 쓰든가


surface.create_entity{...}

이렇게 쓰든가

surface.create_entity({...}) 이렇게 쓰든가 맘대로...


보면 3개의 키값을 가진 테이블 1개가 create_entity의 인수로 들어가는 모양인데,

{

name = "fire-flame",

position = player.position,

force = "neutral",

}

함수 설명 페이지 보면 각각 뭔지 대충 알 수 있을테니 넘김.

lua 테이블 표현에서 마지막 키값의 끝에 ,콤마를 넣어도 문법상 문제없다. 줄 단위로 복붙하기 편하라고 그렇게 한듯.


◆◆ 마지막 블럭닫는구문

end

end

)

첫째 end는 if블럭을 닫고, 둘째 end는 function(event)를 닫고, 셋째 )괄호는 script.on_event의 인수입력을 닫는다.


이걸로 script.on_event라는 이벤트 등록 함수 하나의 실행이 끝난거임.

얘가 한번 등록해줬으면, 해당 이벤트가 일어날 때마다 몇번이고 계속해서 등록된 함수가 실행이 된다.



이걸로 캐릭터가 움직여서 위치가 변할 때마다, 캐릭터의 위치에 중립세력 소속의 화염 엔티티를 만드는 기능이 되었다.




◆ 프로토타입 참고

참고로 fire-flame 엔티티는

팩토리오 설치폴더/data/base/prototypes/entity/fire.lua의 334번째 줄에 프로토타입이 입력되있는거 같네.

설치폴더/data/base/prototypes/fire-util.lua에 적어둔 함수를 사용하는걸 보면 화염 프로토타입 만드는 과정 자체를 함수화 해둔거 같다.


프로토타입 입력구문 살펴보면, type = "fire" 라고 했으므로 바닐라 data.raw리스트에서는 fire를 찾아가면 됨

https://wiki.factorio.com/Data.raw#fire

링크따라가면 이 화염의 프로토타입의 프로토타입(클래스)은

https://wiki.factorio.com/Prototype/FireFlame


이런식으로 기존에 바닐라나 다른 모드에서 만들어진 프로토타입이 어떤 프로토타입의 프로토타입(클래스=type)...를 참조하고 있는지 추적이 가능함.

이것저것 보면서 내가 원하는 프로토타입을 만드는데 참고할 수 있다.



◆ 암튼 됨.


아무튼,

그래서 이걸로 캐릭터가 움직여서 위치가 변할 때마다, 캐릭터의 위치에 중립세력 소속의 화염 엔티티를 만드는 기능이 되었다.

fire-flame엔티티는 화염속성 틱뎀을 주는 놈인데,

fire-armor만들 때 화염저항 100% 했기 때문에 착용자는 뜨겁지 않아. 기지를 다 불태울 뿐.



다음 예제 예약받는다. 뭐가 만들고싶어? 마법지팡이? 레벨업하는 캐릭터? GUI? 등등...?