테이블

 

Ruby언어의 Hash, Python언어의 Dictionary, Java의 Map과 같은 자료형이다.

다만, 루아의 테이블은 복합 자료형이면서도 중요한 역활을 한다. key-value가 아닌 배열이나 객체 프로그래밍을 할 때 필요하기 때문이다. 


우선 테이블은 익명성이라는 특징을 가진다.

> cool_table = {}
> print(cool_table)
table: 0x8268920

 

테이블 그 자체의 출력 값은 테이블 요소 값의 관계로써 별 의미가 없다.

 

 

 

> cool_table["name"] = "짱구"
> cool_table["age"] = "다섯살"
> print(cool_table["name"].."는 몇십년째 "..cool_table["age"].."이야.")
짱구는 몇십년째 다섯살이야.

 

> print(cool_table)
table: 0x8268920

 

> print(cool_table["poo"])
nil


선언과 호출은 간단하다. 마치 key-value꼴로 쓴다. key에 들어갈 자료형은 무엇이든지 상관없다만 nil을 key로 쓸 수 없다.

후에 cool_table을 다시 print()로 찍어보았지만 요소와는 별 관계가 없음을 알 수 있다.

또한 없는 key를 호출할 경우 쓰레기값인 nil을 돌려받는다.

 

 

 

> cool_table = {["name"]="짱구", ["age"]="다섯살"}


테이블은 이렇게도 만들 수 있다.

전체적으로 {}로 감싸며 key는 []로 감싼 형태이다.

 

 

 

> cool_table = {}
> cool_table["age"] = "다섯살"
> cool_desk = {}
> cool_desk.age = "다섯살"

> =(cool_table == cool_desk)
false


만드는 방법이 다를 뿐, cool_table과 cool_desk는 서로 같은 key-value를 가진 테이블이지만

참조부분이 다르므로 결과적으로 false다.

 


 

'게임개발공부 > LUA공부' 카테고리의 다른 글

조건문  (0) 2014.01.13
문자열 정리  (0) 2014.01.13
연산자.  (0) 2014.01.13
루아의 변수 선언과 규칙.  (0) 2014.01.13
루아스크립트 콘솔에서 실행하기  (0) 2014.01.13
Posted by JJOREG

문자열

 

> print("Hello!")
Hello!
> print('Hello!')
Hello!

> print([[Hello!]])

Hello!

> =[[동해물과 백두산이 마르고 닳도록
>> 하느님이 보우하사 우리나라만세
>> 무궁화 삼천리 화려강산
>> 대한사람 대한으로 길이보전하세.]]
동해물과 백두산이 마르고 닳도록
하느님이 보우하사 우리나라만세
무궁화 삼천리 화려강산
대한사람 대한으로 길이보전하세.

 

큰 따옴표, 작은 따옴표, 중괄호로 감싸면 문자열이 된다.

긴 문자열은 [[로 시작해서 끝날 때 ]]로 닫는다.

 

 

 

> = 'I'm so happy to see you.'
stdin:1: '<eof>' expected near 'm'
> ="I'm so happy to see you."
I'm so happy to see you.
> =[[I'm so happy to see you.]]
I'm so happy to see you.

대신에 문자열로 쓴 기호가 문자열 내에 중복되서는 안된다.

 

 

 

> ="I Like..\nA\tP\tP\tL\tE"
I Like..
A    P    P    L    E

 

서식 문자열도 동일하게 쓸 수 있다.

 

 

 

> name = "철수"
> kim = "김"
> print(kim + name)
stdin:1: attempt to perform arithmetic on global 'kim' (a string value)
stack traceback:
    stdin:1: in main chunk
    [C]: ?

> print(kim..name)
김철수

> kcs = kim..name
> print(kcs)
김철수

 

문자열을 서로 합칠 때는 + 가 아닌 .. 를 사용한다.

문자열 + 정수의 경우에도 ..를 사용할 수 있다.

 

 

 

> ="What is it?"
What is it?
> = "It's "..type("What is it?").."."
It's string.

 

데이터 타입을 알아보기 위해서 type() 함수를 쓴다. 파이썬의 dir()과 같은 역활이다.

 

 

 

-- 제일 나쁘고 느림
local
s = '' for i=1,10000 do s = s .. math.random() .. ',' end io.stdout:write(s)

-- 빠름 for i=1,10000 do io.stdout:write(tostring(math.random()), ',') end -- 셋중에 제일 빠르지만 더 많은 메모리를 필요료함 local t = {} for i=1,10000 do t[i] = tostring(math.random()) end io.stdout:write(table.concat(t,','), ',')

물론 문자열도 불변의 데이터 배열이므로 생성과정에서 자원낭비가 발생할 수 있다.

세 코드중 첫번째 코드가 가장 나쁜 형태로써 제일 느리다.

 

해석을 덧붙이자면 math.random() 메서드로 임의의 정수를 뽑아 문자열 s와 ","로 결합하게 된다.

이것을 10000을 반복하게 되는데 첫번째는 별 생각없이 .. 를 썼다.

두번째의 경우 정수 데이터를 tostring()이란 함수를 이용해 문자열로 바꾼 후에 s와 ","를 결합했다.

 

아마 동적 언어의 특성상 서로 다른 자료형의 데이터 연산이 이루어질 때 '타입 검사'를 하는데 결국 10000번씩이나 타입검사를 시도하면서 성능을 저하 한다는 뜻인 것같다.

두번째 코드는 tostring()을 사용해줌으로써 동적인 '타입검사'를 과감히 pass시켜 정적(?)인 흉내를 내준 셈이다.

 

 

 

 

 


 

문자열 라이브러리

더 많은 라이브러리들은 이 문서를 참고하도록 하자. 여기서는 필자에게 필요한 것들만 정리한다.

 

바꾸기 - gsub()

> =string.gsub("안녕 철수야", "철수", "짱구")
안녕 짱구야    1

 

소문자 - lower()

> =string.lower("KUKUDAS")
kukudas

 

대문자 - upper()

> =string.upper("kukudas")
KUKUDAS

 

반복 - rep()

> =string.rep("Oh~ Yes!\n", 4)
Oh~ Yes!
Oh~ Yes!
Oh~ Yes!
Oh~ Yes!

 

뒤집기 - reverse()

> =string.reverse("kukudas")
sadukuk

조각썰기(슬라이징, 서브, 부분 추출) - sub()

> =string.sub("kukudas", 4)
udas
> =string.sub("kukudas", 0, 4)
kuku


매치(정규식) - match()

> =string.match("TikTak 234 Koko", "%d+ ")
234 
> return string.match("TikTak 234 Koko", "%a+")
TikTak
> return string.match("TikTak 234 Koko", "(%a+) %d+ (%a+)")
TikTak    Koko
> return string.match("TikTak 234 Koko", "(%a+)%d+(%a+)")
nil

아뿔사. 정규식 언어가 다르잖아.

 

포매팅 - format()

> =string.format("%s", "HAHA")
HAHA

 

길이 - len()

> =string.len("ABCDE")
5
> =string.len(123)
3

 

 

 

 

논리(불, 부울)

> =(1 == 1)
true
> =(2 == 1)
false
> a = "A"
> =(a == "A")
true

 

별 달리 중요한 함수는 없으니 연산자만 훌어보고 넘어가자.

 

 

 

 

 

테이블

Ruby언어의 Hash, Python언어의 Dictionary, Java의 Map과 같은 자료형이다.

다만, 루아의 테이블은 복합 자료형이면서도 중요한 역활을 한다. key-value가 아닌 배열이나 객체 프로그래밍을 할 때 필요하기 때문이다. 


우선 테이블은 익명성이라는 특징을 가진다.

> cool_table = {}
> print(cool_table)
table: 0x8268920

 

테이블 그 자체의 출력 값은 테이블 요소 값의 관계로써 별 의미가 없다.

 

 

 

> cool_table["name"] = "짱구"
> cool_table["age"] = "다섯살"
> print(cool_table["name"].."는 몇십년째 "..cool_table["age"].."이야.")
짱구는 몇십년째 다섯살이야.

 

> print(cool_table)
table: 0x8268920

 

> print(cool_table["poo"])
nil


선언과 호출은 간단하다. 마치 key-value꼴로 쓴다. key에 들어갈 자료형은 무엇이든지 상관없다만 nil을 key로 쓸 수 없다.

후에 cool_table을 다시 print()로 찍어보았지만 요소와는 별 관계가 없음을 알 수 있다.

또한 없는 key를 호출할 경우 쓰레기값인 nil을 돌려받는다.

 

 

 

> cool_table = {["name"]="짱구", ["age"]="다섯살"}


테이블은 이렇게도 만들 수 있다.

전체적으로 {}로 감싸며 key는 []로 감싼 형태이다.

 

 

 

> cool_table = {}
> cool_table["age"] = "다섯살"
> cool_desk = {}
> cool_desk.age = "다섯살"

> =(cool_table == cool_desk)
false


만드는 방법이 다를 뿐, cool_table과 cool_desk는 서로 같은 key-value를 가진 테이블이지만

참조부분이 다르므로 결과적으로 false다.

 

 

 

 

배열, 테이블과 같은

제곧내. 배열도 테이블이다. key-value형식이 아닌 우리가 흔히 하는 요소가 한줄로 좌라락 배치된 모습이다.

 

> array = {1, 2, 3, "A", "B", "C"}
> print(array)
table: 0x8269410

 

배열이 곧 테이블이니라~

 

 

 

> array = {1, 2, 3, "A", "B", "C"}

> =array[1]
1
> =array[2]
2
> =array[3]
3
> =array[4]
A
> =array[5]
B
> =array[6]
C


테이블로 흉내낸 배열의 첫번째 요소의 위치 값은 1이다.

사실 알고보면 key가 자동으로 생성되었다고 봐야한다. 아래 코드를 줄인 게 이 코드이다.

 

> array = {[1]=1, [2]=2, [3]=3, [4]="A", [5]="B", [6]="C"}

 

그냥 척 봐도 key 없이 쓰는 게 훨씬 편해보인다. 어쨌든 배열이라는 것은 결국 테이블이라는 것이다.

 

 

 

> array = {[1]=1, [2]=2, [3]=3, [4]="A", [5]="B", [6]="C"}
> =len(array)
stdin:1: attempt to call global 'len' (a nil value)
stack traceback:
    stdin:1: in main chunk
    [C]: ?

 

> =#(array)
6

 

> array[#array+1] = "a"
> =array[#array]
a

> =array[1], array[2], array[3], array[4], array[5], array[6], array[7], array[8]
1    2    3    A    B    C    a    nil

 

> =table.concat(array, ":")
1:2:3:A:B:C:a


테이블의 길이가 알고 싶다면 테이블 앞에 #을 쓰면 된다. len은 string에 속한 클래스다.

 

#연산자는 배열의 뒤 꽁무니에 요소를 첨가할 때 써도 좋다.

맨 마지막 array[8]은 존재하지 않는 요소이므로 nil이다.

 

concat를 통해 테이블을 하나의 문자열로 엮어낼 수 있다. 파이썬에 str.join() 같은 역활이다.

 


 

'게임개발공부 > LUA공부' 카테고리의 다른 글

조건문  (0) 2014.01.13
테이블  (0) 2014.01.13
연산자.  (0) 2014.01.13
루아의 변수 선언과 규칙.  (0) 2014.01.13
루아스크립트 콘솔에서 실행하기  (0) 2014.01.13
Posted by JJOREG

연산자의 종류 ------------------------------------------------------------------------------------------------

 

연산지는 두 개의 값들로 어떤 연산을 수행할 때 사용하는 특별한 기호이다. 산술 연산지들은 산술 결과를 낳으며

관계 연산자는 bool 결괴{true 또는 false)를 낳는다.

 

산술연산자의 종류 ------------------------------------------------------------------------------------------------

• 덧셈예 a + b c
• 뺑셈. 예 a - b = c
• 곱셈. 예 a • b c
• 나늦셈. 예 a / b = c

 

[역주 버전 5.0.x 에는 나머지 연산자가 없는데,버전 51 에서는 정수 형식이 추가될 것이며,

그에 따라 (정수 지원 여부와 상관없이 개발자가 실수로 빼먹 었다는 이야기도 있지만)

정수 나누기의 나머지를 계산하는 나머지 연산자도 추기될 것이다 나머지 연산자의 기호는 %가될 것이 거의 확실하다

 

관계연산자의 종류 ------------------------------------------------------------------------------------------------

관계 연산자는 두 값의 상대적인 관계를 판정하는 연산자이 다. 다음은 루아가 지원히는
표준적인 관계 연산자들을 보여주는 예이다.
if a b then 상등
print("a 는 b 와 같다. ")
end
if a b then 부등
print("a 는 b 와 같지 않다. " )
end
if a < b then 미만
print("a 는 b 보다 작다 . ")
end
if a > b then 초과
print("a 는 b 보다 크다 . ")
end
if a <= b then -- 01하
print( " a 는 b 보다 작거나 같다 " )
end
if a >= b then -- 01상
print("a 는 b 보다 크거나 같다 " )
end


관계 연산자로 테이블들을 비교할 때에는 주의를 기울여야 한다. 테이블이 배정된 변수는 테이블을 실제로 담고 있는 것이 아니라 단지 테이블을 가리키는 것일 뿐이다

(C 의 포인터나 C++ 의 참조 변수와 비슷하다)

따라서 테이블 변수들을 비교하는 것이 실제로 테이블의 내용이 같은지를 비교하는 것은 아니다 다음 예를 보자

 

tableA = {1. 2 , 3}
tableB = {1 , 2 , 3}


if tableA == tableB then
    print(" 두 테이블이 같음. ")
else
    print(" 두 테이블이 같지 않응. ")
end

 

두 변수가 각자 다른 태이블(내용은 같더라도)을 가리키고 있기 때문에 “두 테이블이 같지 않음”이 출력된다


tableA = {1 , 2 , 3}
tableB = tableA
if tableA == tableB then
    print(" 두 테이블이 같음. ")
else
    print(" 두 테이블이 같지 않음. ")
end


그러나 이 두 번째 예제는 tabelB 가 tableA 와 같은 테이블을 가리키는 것이므로 “두 테이블이 같음”을출력한다, 한편, 테이블 안의 값들은 일반적인 방식으로 비교할수 있다

if tableA[2] == tableB[2] then
    print(" 두 값이 같음 ")
end


첫 번째 예제 이후에 이것을실행한다고하더라도두값이 같다는출력이 나온다

 

8) [역주 이 책에서는 다루지 않지만, 메타테이블(metatable) 이라는 것을 이용하면 테이블 변수 자체를 비교할 때에도 개별요소들을 비교하는 식으로 두 태이블의 상등을 판정하게 만들수 있다 루아의 메타테이블은 C++ 의 연산자 중복적재 메커니즘과바슷한것이다.

 

논리 연산자의 종류 ------------------------------------------------------------------------------------------------

 


논리 연산자는주어진두인수(피연산자)들에 대해논리합, 논리곱같은논리 연산을수행한다.
루아에서 논리 연산자들은 모두 소문자로 이루어져 있다.
논리곱을 뜻하는 and 연산지는 첫 번째 인수가 false 이면 첫 번째 인수의 값을 몰려주고,
그렇지 않으면 두 번째 인수의 값을돌려준다.

 

a = 5
b = 10
c = 20

if (a < 10) and (b < 20) then
    print( " 두 번째 인수의 값인 true 를 돌려준다 . ")
end

if (a > c) and (b < 20) then
     Print (" 첫 번째 인수의 값인 false 를 돌려준다 . ")
end

 

논리합을뭇하는 or 연산자는 and 의 반대이다. or 은 첫 번째 인수가 false 가아니면 첫번째 인수를 돌려주고, 그렇지 않으면 두 번째 인수를 돌려준다.


a = 5
b = 10
c = 20
if (a < 10) or (b < 20) then
     print ( " 첫 번째 인수의 값인 true 를 톨려준다 . ")
end
if (a > c) or (b < 20) then
     print(" 두 번째 인수의 값인 true 를 돌려준다 . ")
end
if (a > c) or (b < 5) then
     print(" 두 번째 인수의 값인 false 를 돌려준다. ")
end

 

부정을 못하는 not 연산자는 앞의 두 연산자들과 달리 항상 부울 값, 즉 true 아니면 false 를돌려준다 루아에서 

논리 연산자들이 false로 간주하는값은 false 와 nil 뿐이다.

그 외의 모든 값은 true 로 간주한다. not 연산자는 주어진 인수의 부울 값을 부정한 결과를 돌려준다.

'게임개발공부 > LUA공부' 카테고리의 다른 글

테이블  (0) 2014.01.13
문자열 정리  (0) 2014.01.13
루아의 변수 선언과 규칙.  (0) 2014.01.13
루아스크립트 콘솔에서 실행하기  (0) 2014.01.13
루아에서 변수의 종류  (0) 2014.01.13
Posted by JJOREG

다른 언어와마찬가지로루아스크립트는 여러 가지 식별지(변수이름과함수이름)들로 이루어진다.

식별자는영문자와숫자,

밑줄로구성되나,

첫글자가숫자이어서는안된다

그리고 Start 처럼 밑줄로 시작하고 그 다음 글자가 대문자인 식별자도 피해야 한다.

그런 형태의 식별자들은 루아 자체를 위해 예약된 것이기 때문이다.

 

루아 예약어들 -----------------------------------------------------------------------

 

루아의 예약어들
and         

local
break         

nil
do         

not
else

or
elseif

repeat
end

return
false

then
for

true
function

until
while
In

if

 

일반적인 변수 명 규칙 -----------------------------------------------------------------------

 

• 상수는 대문자와 밑줄만 사용한다. 예를 들면 MYJONSTANT
• 변수는소문자로시작한다 예를들면 myVariable
• 전역 변수는 소문자 g 로 시작한다 예를 들면 gMyGlobal
• 함수는대문자로시작한다 예를들변 MyFunction()

 

주석 -----------------------------------------------------------------------

 

물론 변수의 이름이야 권장하는 것이다. 굳이 따를것은 없지만 개발환경상에서 다른 동료들을 생각한다면 최대한 지켜주는 것이 옳다.

 

주석은 대시 두 개(--)로 시작해서 그 줄의 끝까지이다. 그리고 대괄호 두 개를 이용해서
여러줄에걸친주석을지정히는것도가능하다. 다음에두가지 예가모두나와있다.


-- 이 줄 전체가 주석이다 .
myValue 7 -- 이렇게 문장 뒤에도 주석을 넣을 수 있다 .
- - [ [
        function Counting()
        end
- -]]

 

변수의 타입결정(그런거 없다! 변수의 타입은 강자 아니 루아가 결정한다!) -----------------------------------------------------------------------

 

루아에서는 변수를 미리 선언하지 않고도 사용할수 있다〈물론 이런 방식을 좋아하는 사
람도 있고 반대하는 사람들도 있지만). 변수를 미 리 선언해둘 펼요가 없으므로(변수는 제
일 처음 쓰일 때 생성된다) 변수가 실제로 펼요한 곳에 바로 집어넣는 것이 가능하다. 형
식주의자들은 이런 방식이 지저분한 코드를 만들어 낸다고 주장한다. 변수를 명시적으
로 선언하지 않으면 변수를 관리하기 힘들며, 다른 함수에서 같은 이름의 변수를 시용
하는 실수를 저질러서 자료가 깨지는 위험이 았다는 것이다. 아주 틀린 말은 아니다. 루
아가 깐깐하게 점검해주지 않으므로 프로그래머가 변수들을 확실하게 관리해야 한다는
것은 사실이다. 그리고 루아에서는 변수에 형식(type. 문자열 형식 , 수치 형식 등등)을
지정할 필요도 없다(사실 지정할 수도 없다). 변수의 형식은 변수에 배정된 값을 가지고
루아가 판단한다. 이런 방식은 융통성이 좋다는 장점이 있지만, 변수 선언에서와 마찬
가지로 변수를 제대로 관리하지 않으면 골치 아픈 벼그가 생기기 쉽다는 단점도 가지고
있다

 

특수문자 -----------------------------------------------------------------------

 

다양한 수치 선언-----------------------------------------------------------------------

• myNumber = 7
• myNumber = 0.765
• myNumber = 7. 65e8 (7 .65XI08, 즉 765,000,000.)
• myNumber = 7. 65e-2 (7.65XI0", 즉 0765.)

 

테이블 -----------------------------------------------------------------------


루아의 테이블은 루아에서 가장 강력하면서도 햇갈리는 요소이다‘ 테이블은 이후의 장
들에서 좀 더 자세히 설명 , 사용할 것 이 므로, 여기서는 일단 테이블을 배열처럼 사용할
수 있다는 점만 이야기하고 넘어가겠다.
디음을 콘솔에서 시험해 보기 바란다.
Ready> myTable {2 .4. 6.8. 10}
Ready> print(myTable[3])
6
Ready> myTable[6] 12
Ready> print(myTable[6])
12
이 문맥에서 태이블은 일련의 값들을 담는 단순한 배열처럼 쓰인다 테이블의 특정 값
을 얻기 위해 대괄호를 사용하는 방식도 배열과 동일하다, 그러 나 테이블이 곧 배열인
것은 아니다. 테이블은 5 장에서 좀 더 이야기한다

 

테이블의 선언은 일단 { }으로 한다. [] 아니다.
 

지역변수와 전역변수-----------------------------------------------------------------------


기본적으로 모든 루아 변수는 본질적으로 전역 변수이다 즉, 루아 변수는 스크립트 전체에서 접근할 수 있으며 스크립트 실행 이 끝날 때까지 유지된다

(그리고 변수의 값은 스크립트에서 명시적으로바꾸지 않는한계속동일하게유지된다).

이러한안정성은스크립트 프로그래머에게는 편리한 성질 이지만,

수많은 루아 스크립트들과 함수들로 된 게 임을만들때에는혼란의요인이될수있다.
의도적으로 전역 변수로 사용할 변수의 이름은 소문자 g 로 시작하는 것이 좋대“식별자이름과 주석” 절 참고).

그러면 디버깅에 도움이 된다. 또한 가능한 곳에서는 항상 지역변수를 사용하는 것이 좋다.

또한 가능한 곳에서는 항상 지역변수를 사용하는 것이 좋다.

지역 변수는 그 변수가 정의된 스크립트 블록 안에서만 유효하며 그 블록의 실행이 끝
나면 파괴되는 변수이다. 여기서 블록이란 어떤 제어 구조나 함수에만 속하는 코드 범위를말한다,


지역 변수를 선언(또는 정의)할 때에는 local 이라는 키워드를 사용한다

이 때 초기값을 지정하지 않을수도있디〈그러면 nil 값을가지게된다)
local myValue -- nil     을 값으로 하는 지역 변수 ,
local myValue23         초기 값이 3 인 지역 변수.


변수의 범위(변수가유효한영역)는변수선언의 위치에 의해 결정된다. 다음예를보자.
function MyFunction()
    local myX = 7 --01 변수는 힘수 실행이 물나면 파괴된다.
        if myX < 10 then
            local myY "hello world" --01 변수는 이 if 문 블록이 끝나면파괴된다.
            print(myY) -- "hello world" 를 출력
        end
    print(myY) -- myY 는 위의 블록이 끝나면서 파괴되었으므로 nil 을 출력
end


프로젝트가 커지면서 더 많은 스크립트 파일들이 추가됨에 따라,게임 프로젝트의 어딘
가에서 전역 변수를잘못설정, 시용하는위험이 커진다 지역 변수를최대한사용하는
것은그런 위험을피하고코드를좀더 깔끔하게 유지하는데 도웅이 된다

'게임개발공부 > LUA공부' 카테고리의 다른 글

테이블  (0) 2014.01.13
문자열 정리  (0) 2014.01.13
연산자.  (0) 2014.01.13
루아스크립트 콘솔에서 실행하기  (0) 2014.01.13
루아에서 변수의 종류  (0) 2014.01.13
Posted by JJOREG

dofile 명령은주어진루아스크립트파일을즉시 실행한다 명령의 매개변수는실행할
파일의 경로와이름이 다. 경로구분에는역슬래시 하나가아니라두개(\\)를사용해야
함을 주의할 것 이것은 다른 여러 언어와 마찬가지로 루아에서도 역슬래시 문자는 문
자열 안에서 어떤특별한문지{큰따옴표, 줄바꿈등)를표현하기 위해 쓰이기 때문이다.


dofile 을 이용하면 어떠한 스크립트라도 실행할 수 있다(스크립트가 C++ 로 작성된 특
별한 루이글루 함수를 시용하지 않는다고 할 때 1)). 스크립트 실행 도중 어떤 오류가 발생
하면 루아 환경은 해당 오류 메시지를 출력한다. 이는 스크립트를 디버깅할 때 매우 요
긴한기능이다.

'게임개발공부 > LUA공부' 카테고리의 다른 글

테이블  (0) 2014.01.13
문자열 정리  (0) 2014.01.13
연산자.  (0) 2014.01.13
루아의 변수 선언과 규칙.  (0) 2014.01.13
루아에서 변수의 종류  (0) 2014.01.13
Posted by JJOREG

[루아에서의 변수 종류]

NIL              NULL과 같은 의미 (0이 아닌 아무것도 없는 값) 

                   PRINT(A)

 

BOOLEAN    TRUE, FALSE 값 만을 가진 논리 변수

         A = TRUE

         PRINT(TYPE(A))

 

NUMBER     숫자 

        A = 1 

                  PRINT(TYPE(A))


STRING       문자 

        A=HI

        PRINT(TYPE(A))

 

FUCTION     함수

        PRINT(TYPE(PRINT))

 

TABLE        테이블

        A = {1,2,3} / PRINT(TYPE(A))

 

THREAD     스레드,코루틴 프로그램에서 동시에 어떤 일을 수행시킬 때 사용 

                 CR - COROUTINE.CREATE(FUNTION()PRINT("HELLO")END)

                 PRINT(TYPE(CR))


USERDATA  사용자 데이터. 이건 우선 생략


LOCAL       지역변수. 특정 블록 내에서만 유효한 변수를 뜻함 루아는 기본적으로 전역변수다.

       LOCAL NCOUNT = 0

[출처] 2차 : 변수와 Type|작성자 츄츄양


 

'게임개발공부 > LUA공부' 카테고리의 다른 글

테이블  (0) 2014.01.13
문자열 정리  (0) 2014.01.13
연산자.  (0) 2014.01.13
루아의 변수 선언과 규칙.  (0) 2014.01.13
루아스크립트 콘솔에서 실행하기  (0) 2014.01.13
Posted by JJOREG

출처 http://cheucheu.tistory.com/47

 

         1. 컴파일러(Compiler)와 인터프리터(Interpreter)

컴파일을 하기 위하여 입력되는 프로그램을 "원시 프로그램" 이라 하고, 

이 프로그램을 기술한 언어를 "원시 언어" 라 합니다.

번역되어 출력되는 프로그램을 "목적 프로그램" 이라 하고, 

이 프로그램을 기술한 언어를 "목적 언어" 라 합니다.



       컴파일러(Compiler)


컴파일러는 원시 언어로 된 프로그램을 읽어들여서 목적 언어로 된 동일한 프로그램을

출력하여 주는 언어처리기입니다. 기계어로 번역이 쉽게 이루어질 수 있으면서 

시행 시간을 중시하는 경우에 사용합니다.

 

       인터프리터(Interpreter)


인터프리터는 원시 언어로 작성된 명령문들을 한 번에 한 줄씩 번역하고 실행하는 프로그램입니다.

프로그램이 짧고 복잡하지 않고, 대화형 프로그램에서 많이 사용됩니다.

 


컴파일러는 번역, 인터프리터는 통역이라고 이해하면 쉽습니다.

 

 

 

 

 

         3. 차이점

 



컴파일러와 인터프리터의 차이점을 위 표에 간단하게 정리해보았습니다.

 

       컴파일러(Compiler)

 컴파일러는 컴파일 과정이 복잡하고 그 시간이 많이 걸리게 되는데, 한 번 컴파일 하면 그대로 사용이 가능합니다.

전체 실행 시간 면에서 매우 효율적이며, 매번 번역할 필요가 없다는 것이 장점입니다.

하지만 한 줄의 입력 시 때로는 프로그램이 몇 백 개의 기계어로 번역되기 때문에 큰 기억용량을 요구하게 됩니다.

특히 입출력 명령은 입출력 형식을 위한 코드 외에 기계 상태 파악코드로 인해 큰 기억 장치가 요구됩니다.

 

       인터프리터(Interpreter)


인터프리터는 프로그램이 될 때까지 원시 언어의 형태를 유지하기 때문에 기억 장소가 추가로 필요하지 않습니다.

하지만 원시프로그램을 직접 실행하기 때문에 이 실행에 필요한 소프트웨어가 항상 기계 안에 상주하면서

원시 프로그램의 명령들을 받아서 처리하게 됩니다. 반복하여 실행하는 경우에는 그 때마다 원시 프로그램을

해석하여 처리하기 때문에 많은 시간이 걸리기도 합니다.

 

 

 

 

 

         4. 차이점

컴파일러는 실행 시간의 효율을 중하는 프로그래밍 언어에서, 

인터프리터는 사용자의 융통성을 중시하는 프로그래밍 언어에서 주로 사용됩니다.

 

 


 

'게임개발공부 > 무작정퍼오기' 카테고리의 다른 글

explicit 키워드에 대하여  (0) 2014.01.09
함수객체  (0) 2013.12.29
DLL만 로딩해도 컴퓨터가 뻗는경우.  (0) 2013.12.21
volatile (퍼온글)  (0) 2013.12.21
extern "C" 이건 뭔가? (퍼온글)  (0) 2013.12.21
Posted by JJOREG

Command (커맨드) 패턴 -----------------------------------------------------------------------------------------------

  커맨드 패턴이란 말그대로 명령을 내리는 패턴을 의미합니다.

  하지만 하나의 명령을 내리면 그 명령은 일반적으로 다양한 클래스에 관여될수 있습니다.

  그런경우 우리는 일반적으로 조건문과 분기문을 사용하여 처리합니다.

  하지만 지속적으로 명령에 대한 분기가 늘어난다면 늘어난다면?

  계속 코드를 수정하게 되는 경우가 많습니다.

  커맨드 패턴이란 그런경우 클라이언트를 수정하기 보다는 클래스를 분화시켜 매소드를 간략화 시키는 것이 장기적인 안목으로 더 효율적인때 사용하는 패턴입니다.

 

패턴적용 스토리 -----------------------------------------------------------------------------------------------

  이번에 적용되는 스토리는 다음과 같습니다.

  게임을 만들다보면 키의 입력에 의해서 다양한 분기가 생깁니다.

  i키를 누르면 인벤을 열고

  s키를 누르면 스킬을 쓰고.

  a를 누르면 공격을 합니다.

  이러한 키를 손쉽게 관리하고 그리고 그 명령으로 인한 수없이 많이 생길수 있는 요청들을 처리하기 위한 구조를 잡는데 커맨드 패턴은 매우 유용하게 사용될 수 있습니다.

 

uml -----------------------------------------------------------------------------------------------

  

 

 

 

 

  코드를 보면 커맨드 클래스의 하위클래스들은 자신을 통해서 명령을 실제 실행할 객체들을 포함합니다.

  커맨드 객체들은 요청에 따라서 map으로 관리되고 커맨드의 입력에 따라서 플라이웨이트 패턴의 맵에서 꺼내어져 다른 객체를 호출하게 됩니다.

   

 

코드 -----------------------------------------------------------------------------------------------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#include "include.h"
 
///////// KEY
 
class KeyManager
{
private:
    map<string, string>     m_mapKey;
 
private:
    KeyManager() 
    {
        KeySetting();
    }
 
public:
    ~KeyManager() {}
 
private:
    static KeyManager* m_Inst;
 
public:
    static KeyManager* GetInst(void
    {
        if(m_Inst == NULL)
        { m_Inst = new KeyManager; }
        return m_Inst;
    }
 
    static void Destroyinst(void)
    {
        SAFE_DELETE(m_Inst);
    }
 
public:
    void KeySetting(void)
    {
        m_mapKey["i"] = "inven";
        m_mapKey["a"] = "attack";
        m_mapKey["s"] = "skill";
    }
 
    const string& GetKeyValue(const string& _Str)
    {
        return m_mapKey[_Str];
    }
};
 
KeyManager* KeyManager::m_Inst = NULL;
 
///////// CPlayerPorgress
 
class CPlayerPorgress
{
public:
    virtual void Progress() = 0;
};
 
class CInven : public CPlayerPorgress
{
public:
    virtual void Progress()
    {
        cout << "인벤을 엽니다." << endl;
        cout << "인벤에서 쿵착쿵착." << endl;
    }
};
 
class CAttact : public CPlayerPorgress
{
public:
    virtual void Progress()
    {
        cout << "공격 명령" << endl;
        cout << "공격공격!" << endl;
    }
};
 
class CSkill : public CPlayerPorgress
{
public:
    virtual void Progress()
    {
        cout << "스킬을 사용합니다" << endl;
        cout << "파이어볼" << endl;
    }
};
 
///////// PlayerPorgressManager
 
class PlayerPorgressManager
{
private:
    map<string, CPlayerPorgress*>     m_mapProgress;
 
private:
    PlayerPorgressManager(void
    {  
        m_mapProgress["i"] = new CAttact();
        m_mapProgress["a"] = new CInven();
        m_mapProgress["s"] = new CSkill();
    }
 
public:
    ~PlayerPorgressManager(void) { SAFE_DELETE_MAP(m_mapProgress); }
 
private:
    static PlayerPorgressManager* m_Inst;
 
public:
    static PlayerPorgressManager* GetInst(void
    {
        if(m_Inst == NULL)
        { m_Inst = new PlayerPorgressManager; }
        return m_Inst;
    }
 
    static void Destroyinst(void)
    {
        SAFE_DELETE(m_Inst);
    }
 
public:
    CPlayerPorgress* GetProgress(const string& _Str)
    { return m_mapProgress[_Str]; }
};
 
PlayerPorgressManager* PlayerPorgressManager::m_Inst = NULL;
 
///////// CCommand
 
class CCommand
{
public:
    virtual void CommandProgress() = 0;
};
 
class CInvenCCommand : public CCommand
{
private:
    CInven* m_Pinven;
 
public:
    CInvenCCommand() 
    {
        m_Pinven = static_cast<CInven*>(PlayerPorgressManager::GetInst()->GetProgress("i"));
    }
 
public:
    ~CInvenCCommand() { }
 
public:
    virtual void CommandProgress()
    {
        if(m_Pinven)
        { m_Pinven->Progress(); }
    }
};
 
class CAttackCCommand : public CCommand
{
private:
    CAttact* m_Attack;
 
public:
    CAttackCCommand() 
    {
        m_Attack = static_cast<CAttact*>(PlayerPorgressManager::GetInst()->GetProgress("a"));
    }
 
public:
    ~CAttackCCommand() { }
 
public:
    virtual void CommandProgress()
    {
        if(m_Attack)
        { m_Attack->Progress(); }
    }
};
 
class CSkillCCommand : public CCommand
{
private:
    CSkill* m_Skill;
 
public:
    CSkillCCommand () 
    {
        m_Skill = static_cast<CSkill*>(PlayerPorgressManager::GetInst()->GetProgress("s"));
    }
 
public:
    ~CSkillCCommand () { }
 
public:
    virtual void CommandProgress()
    {
        if(m_Skill)
        { m_Skill->Progress(); }
    }
};
 
class CCommandManager
{
private:
    map<string, CCommand*>     m_mapCommand;
 
private:
    CCommandManager() 
    {
        m_mapCommand["i"] = new CAttackCCommand();
        m_mapCommand["a"] = new CInvenCCommand();
        m_mapCommand["s"] = new CSkillCCommand();
    }
 
public:
    ~CCommandManager() { SAFE_DELETE_MAP(m_mapCommand); }
 
private:
    static CCommandManager* m_Inst;
 
public:
    static CCommandManager* GetInst(void
    {
        if(m_Inst == NULL)
        { m_Inst = new CCommandManager; }
        return m_Inst;
    }
 
    static void Destroyinst(void)
    {
        SAFE_DELETE(m_Inst);
    }
 
public:
    CCommand* GetComment(const string& _Str)
    {
        if(m_mapCommand[_Str])
        { return m_mapCommand[_Str]; }
 
        return NULL;
    }
};
 
CCommandManager* CCommandManager::m_Inst = NULL;
 
// Command.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
 
#include "stdafx.h"
#include "command.h"
 
#include <crtdbg.h>
 
int _tmain(int argc, _TCHAR* argv[])
{
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    //_CrtSetBreakAlloc(215);
#endif
 
    KeyManager::GetInst();
    PlayerPorgressManager::GetInst();
    CCommandManager::GetInst();
 
    bool Progresscheck = true;
    CCommand* pCommand;
    string input;
 
    while(Progresscheck)
    {
        cin >> input;
 
        pCommand = CCommandManager::GetInst()->GetComment(input);
 
        if(pCommand == NULL)
        { break; }
 
        pCommand->CommandProgress();
    }
 
 
    KeyManager::Destroyinst();
    PlayerPorgressManager::Destroyinst();
    CCommandManager::Destroyinst();
 
    return 0;
}

 

결과 ----------------------------------------------------------------------------------------------- 

 

Posted by JJOREG

 

 

(스타크래프트2 의 밸런싱 공식이라고 한다.)

 

 

지금까지 너무 기획실패사례만 언급한것 같아서, 공유할만한 성공사례를 찾던중에 "밸런싱 성공사례" 를 찾을수 있었다. 일반적인 상식과는 다른 "역발상" 을 통해서 게임밸런스를 정상화 시킨 이례적인 성공사례인데 밸런싱작업 자체는 일반적인 사람들이 생각하는것과는 매우 다른 방식으로 이루어졌다.

 

 기획자가 밸런싱을 하는데 도통 밸런스가 잘 맞지 않았다. 기획자가 밸런싱경험도 없었고, STR,CON,DEX,INT,MEN 등등의 스탯을 연동하는 전투공식 경험도 전혀 없어서 애로사항이 많았다. 설상가상으로 기획자는 주로 PS3 나 PSP 같은 콘솔게임을 주로 좋아하지, 온라인게임에는 그다지 관심이 없었다. 게임의 밸런싱 상황은 크게 악화되어가고 있었다.

 

 게임내 밸런싱이 엉망으로 되어가는 상황에서 갑자기 운영툴과 컨텐츠작업을 하던 서버 프로그래머가, 자기가 밸런싱을 해볼수 있을것 같다면서 게임내 밸런싱을 자처했다. 원래는 멀티클라이언트를 띄워보면서 디버깅을 하다가 밸런싱 의견을 몇가지 제시하는 정도였는데, 예전부터 MMORPG 의 전투공식쪽에 경험이 약간 있어서 "밸런싱 할줄안다" 라고 말하는것이 아닌가?

 

 사실 처음에는 우려가 많았다. 서버 프로그래머가 밸런싱을 담당하는것은 정말로 어이없는 상황이였지만, 전에 경험도 약간 있었다고 하고, 말하는것을 보니 뭔가 믿는게 있어서 나서는것 같아서 서버 프로그래머에게 밸런싱을 맡기게 되었다.

 

 

 결과부터 말하지면 서버 프로그래머에게 밸런싱을 맡긴것은 최고의 선택이였고, 서버 프로그래머가 밸런싱을 담당한 이후로 게임내 밸런스는 매우 정교하고 안정적으로 변해갔다. 기획자에게 맡겼다면 정말로 힘들었을부분이 서버 프로그래머에게 맡겼기 때문에 정말로 쉽게 해결되었다.

 

 어떻게 서버 프로그래머가 기획자도 힘든 밸런싱을 완벽하게 할수 있었을까? 나는 이상하게 생각했지만 몇가지 비결이 있었다.

 


 

 

1. 운영팀과의 긴밀한 협력

 운영툴을 만드는 서버 프로그래머였기에 운영팀과 긴밀하게 협력하고 있었다. 운영툴에 원하는 데이터를 자동으로 리포팅하는 기능을 추가해서 데이터를 수집하였고, 운영팀과 소통할일이 많았기 때문에 밸런싱을 하는데 필요한 제반정보를 많이 가지고 있었다.

 

2. 기획자보다 더 풍부한 공식경험

 서버 컨텐츠 프로그래밍을 6년이상 해왔던 경험많은 서버 프로그래머였기 때문에 오히려 기획자보다 시스템기획에 대한 지식이 훨씬더 많았다. MMORPG 에서 사용되는 다양한 데미지 공식을 알고 있었고, 오히려 기획자보다 더 다양한 공식을 보여주었다.

 

Damage = Attack - Defense 같은 간단한 공식에서부터 Damage = Attack * (Attack * AttackFactor / Defense * DefenseAttribute ) * ( AttackerLevel / MonsterLevel ) * WeightRatio + BonusDamage + ClassAdjustment + BuffModifier + PassiveModifier; 와 같은 여러가지 복잡한 데미지 공식을 알고 있었다. 여러가지 MMORPG 게임을 개발하면서 얻은 다양하고 복잡한 공식들을 알고 있었는데, 이러한 지식은 웬만한 기획자를 능가하는 것이였다.

 

3. 로그파일분석 능력.

로그파일 분석능력이 기획자보다 뛰어났다. 밸런싱 작업은 방대한 통계분석이 수반되는데, 이러한 로그파일을 분석해서 이러한 통계작업을 해야하는데, 기획자중에서는 로그파일을 파싱해서 원하는 데이터를 추출하고, 가공해서 원하는 통계값을 뽑는것이 힘들다. 하지만 프로그래머에게는 매우 쉬운일이다.

 

 서버 프로그래머이니, 서버에서 로그파일을 가져와서 원하는 밸런싱 데이터모델을 만드는것은 정말로 쉬운일이였다. 만약에 로그파일에 원하는 정보가 없을경우 바로 서버코드에 추가하여 밸런싱에 반드시 필요한 정보를 로그파일을 남기도록 하였다.

 

이러한 작업은 서버 프로그래머가 아닌 기획자는 꿈도 못꾸는 작업들이다.

 

 


 

서버 프로그래머가 밸런싱을 하는 모습을 보고 가장 충격적이였던 부분은 기획자는 상상하기도 힘든 복잡한 문제를 프로그래머라는 능력을 사용해서 손쉽게 해결하는 그 과정이였다.

 

a. 워리어 1레벨부터 100레벨까지의 캐릭터가 STR,DEX,CON,INT,MEN 을 다양하게 찍었을때의 데미지 분포곡선이 어떻게 나타나는가?

 

b. "전설의 레이드몹" 을 66레벨 캐릭터가 공격한다고 할때, 40이상의 데미지를 줄수 있는 아이템은 어떤것들이 있는가?

 

c. 100레벨의 캐릭터가 1레벨 단검부터 100레벨 단검까지 다양한 아이템으로 공격할때 데미지가 어떻게 분포하는가?

 

d. 1레벨부터 100레벨까지의 캐릭터가, 1레벨부터 100레벨까지의 아이템을 차고, 1레벨부터 레이드몹까지 다양한 몬스터를 공격할때 나타나는 데미지 분포 패턴,

 

f. 특정 몬스터에게 60이상의 데미지를 가할수 있는 STR 과 아이템 조합 경우의 수.

 

이러한 문제들은 기획자가 풀기 위해서는 엄청난 노가다를 해야할것이다. 하지만 프로그래머는 쉽게 해낸다. 게임서버에 있는 캐릭터와 아이템테이블 시스템 소스코드를 뜯어내서 모듈화 시킨다음에, 테스트용 프로그램을 만들어, 수치 시뮬레이션을 돌려버린것이다.

 

기획자라면 2~3개월동안 노가다하면서 감으로 맞출 문제를 프로그래머는 간단하게 코딩 몇번으로 답을 찾아낸다. 정말로 놀라운 일이다.

 

 


 

서버 프로그래머가 밸런싱을 하는 모습은 이상하게 보일수도 있지만 결과적으로 말하자면 매우 성공적이였다. 기획자가 밸런싱을 할때는 밸런싱이 그야말로 "물넣고 소금넣고" 의 반복이였다. 어느 특정 스킬이 강하다고 생각되면 하향패치 하고, 유저들의 반응을 보고, 다시 조정하고 혹은 잘 사용되지 않는 스킬이 있으면 상향패치 하고, 유저들의 반응을 보고.

 

 밸런싱작업을 할때마다 유저들의 불만이 터져나오고, 적절한 밸런싱 모델이 찾을때까지 기획자는 이렇게 해보고, 저렇게 해보는 "감으로 숫자맞추기" 의 반복이였다. 손님에게 요리를 해주는데, 싱거우면 소금넣고, 짜면 물넣는 격이였다. 하지만 달리 방법이 없었다. 한방에 정확한 밸런싱모델이 안나오는데 무슨 방법이 있겠는가? 이리저리 쑤셔보는수밖에 없다.

 

 한가지 재미있는 점은 서버 프로그래머는 "게시판" 을 주된 밸런싱데이터로 생각하지 않았다. 유저들의 의견에 휘둘리다 보면 오히려 밸런싱이 쏠릴수 있다면서 자신만의 굳은 밸런싱 모델을 지켜나갔다. 이것이 기획자와 서버 프로그래머의 차이였다.

 

 

 한가지 안타까운점은 서버 프로그래머가 밸런싱자체는 양호한 수준으로 끌어 올렸지만, 이미 때는 늦었다. 유저들이 다 떠나간다음에 제대로된 밸런싱이 나왔지만 소 잃고 외양간 고친격이였다.

 

 애초에 처음부터 서버프로그래머가 밸런싱을 했다면, 이런일은 발생하지 않았을것이다. 지금 생각하면 아쉬움이 많이 남는다.

 

 서버 프로그래머가 밸런싱을 한다면 뭔가 이상하게 생각하는사람들이 많을것이다. 하지만 게임개발에 있어서 절대적인 법칙은 없다. 서버 프로그래머가 기획자보다 밸런싱을 더 잘할수 있다면 서버 프로그래머가 하는것이 더 좋을수 있다.

 

 실제로 나는 서버 프로그래머가 기획자보다 훨씬더 밸런싱을 잘하는것을 경험했고, 그것이 단순한 요행이 아니라, 프로그래머만이 가질수 있는 탄탄한 기반하에 이루어졌다는것을 알수 있었다.

 

 밸런싱은 기획자의 업무라고 굳게 믿는사람이 있다면 한번 생각을 바꾸는것이 어떨까? 비록 게임은 망했지만 내가 언급할수 있는 몇 안되는 "성공사례" 중 하나이다.

 


// 이분 블로그에 들어가보면 다양한 기획성공실패사례가 있는데... 내용을 보면 대체 어떤 정말 기획자복을 못받으신분 같다.

// 나도 한때는 기획자였고 지금 개발을 배우고 있지만 손님에게 요리를 해주는데, 싱거우면 소금넣고, 짜면 물넣는 격이였다. 도대체 어떤 기획자인데 일을 저런식으로 처리하는지 같이 일하면 암걸릴거 같은 기분이 들지도 모르겠다. 아니 기획 신입이라도 저런짓은 안한다. 내가 안했으니까.

// 나도 게임시작 컨셉을 정하기 위한 시장조사에 대해서는 회의적이다. 사람들이 캐쥬얼 게임많이하니 캐쥬얼게임 만들자 MMORPG시장이 현재 성장중이니 우리도 MMORPG를 만들자 라는 소리는 헛소리라고 생각한다. 일단 들어보고 재밌어보이면 찬성해야 한다고 생각하니까.

// 하지만 하나의 컨셉과 게임의 대한 세부내용이 나온상태에서는 절대적인 통계에 의지한 개발작업이 필요하다고 생각한다.

// 밸런스란 무엇인가? 게임시스템에 의해서 생성된 수치들을 통해서만 맞출수 있는거다. 여기에서 수치가 공격력 방어력 하나인가?

// 유저의 위치, 3차원 게임이라면 그에 맞는 거리. 시전시간 DPS 단순히 스킬의 컨셉에 의한 효과만이 아닌 스킬매커니즘에 따른 사용시의 용의함. 이런것들이 정해지고 이 또한 팩터로 맞춰져야 한다. 이런거다. 1분이 걸릴 전투에서 20초동안 주문을 외우고 한방에 상대의 MAXHP의 절반에 달하는 데미지를 주는 스킬이 있다고 치자. 1분이니 두번만 쓰면 무조건 그 유저는 이긴다. 하지만 여기에 캐스팅을 끊을수 있는 기술이 있다면? 혹은 입고있는 장비에 의해서 캐스팅시간이 길어지거나 짧아진다면? 버프에 의해서 공격력이나 방어력 혹은 캐릭터가 이동하면서 캐스팅을 할수 있다면? 스킬은 타겟팅인가 아니면 투사체를 확인하고 회피할수 있나? 등등의 수많은 변수고 단순한 데미지 공식만이 아닌 전체적인 내용적 조율이 필요하다.

// 위의 내용들은 컨트롤적인 내용이니 밸런스적으로 맞추기 힘들다고? 아니다. 움직이는 거리 스킬의 상성등도 결국에는 수치다.

// 게임은 결국 어떠한 값을 저장하고 그 저장된 값을 조작하여 동작하는 것이다.

// 밸런스는 그 값의 통계를 통해서 맞추는거다. 그 이상이하도 아니다. 절묘한 밸런스? 넘사벽의 컨트롤? 수치화 할 수 없다 예상할 수 없다? 한마디로 이야기하면 그렇지 않다.

// 즉 밸런스의 실패란 수치를 오판하는데서 오는 것이다(밸런스를 위한 항목이 모두 준비되어 있다면 언제든 밸런스는 조정이 가능하다 준비해 주는건 기획자와 프로그래머가 항상 협의해야 한다)

 

결론을 말하자면 모든 영향요소는 밸런스에 고려되어야 한다.

그것에 대해서 오판할 수 있다. 

 

예를 들자면 이런거다 인간의 반사신경으로 80% 정도의 적중률을 가질것으로 보이는 논타겟팅 스킬이라 공격력을 일반적인 데미지의 20% 가량 상승시켰다.

하지만 예상보다 스킬은 맞추기 쉬웠고 프로들의 경기에서는 95%가량의 적중률을 보였다.

일반인들도 90%는 적중시킨다.

어라 생각한것보다 데미지 팩터가 10%의 오차가 생겼다.

이에 의해서 약간의 데미지를 하향시켜서 밸런스를 맞춘다.

혹은 속력을 좀 늦춰서 더 피하기 쉽게 만든다. 공격력을 하향시킬까? 아니면 투사체의 속력을 하향시킬까는 혹은 판정을 좀 좁힐까? 이 모든게 밸런싱 담당자의 몫이다.

인간의 반사신경이나 투사체의 속력 유저의 실력 등의 통계가 밸런스에 영향을 미치는 것이다.

하지만 이미고려된 항목이라면 그때는 팩터만 변경해주면 된다. 오판한 수치에 대한 수정이 밸런스를 맞추는 것이다.

 

하지만 애초에 고려되지 않은 사항이라면? 투사체의 속력이나 유저의 실력 일반적인 통계가 항목에 고려되지 않았다면?

거기서부터 이미 밸런스시 고려해야할 항목에서부터 모든 요소(사거리, 데미지 공식, 방어, 유저의 컨트롤(반사신경, 판단력), 시야, 상대와의 조합등등등 고려될 수 있는 모든 요소!)를 감안하지 않은 시점에서부터 이미 기획자의 실책이라고 볼 수 있다.

Posted by JJOREG
CTS 크기 부호 C C++ C# VB
System.Byte 1 없음 Unsigned char Unsigned char byte Byte
System.SByte 1 있음 char signed char sbyte 없음
System.Int16 2 있음 short short short Short
System.UInt16 2 있음 unsigned short unsigned short ushort 없음
System.Int32 4 있음 int int 또는 long int Integer
System.UInt32 4 없음 unsigned int unsigned(long) uint 없음
System.Int64 8 있음 long __int64 long Long
System.UInt64 8 없음 unsigned long unsigned __int64 ulong 없음
System.Char 2 없음 wchar_t wchar_t char Char
System.Single 4 있음 float float float Single
System.Double 8 있음 double double double Double
System.Decimal 16 있음 없음 Decimal decimal Decimal
System.Boolean 1   bool bool bool Boolean
System.String 가변   없음 string string String
System.Object 가변   없음 Object * object Boolean

 

C#에서 Win32 API 사용하기

 

개요

Win32 API를 불러올 때, 함수의 명칭, 인자, 리턴 값을 가지고 불러오게 되어 있다. 하지만, C#에서 타입들이 모두 객체(Object)의 형식이며, 일반적인 C 의 데이터 형과 상이한 모양을 가진다. 이러한 문제들을 해결할 수 있는 것이 PInvoke 기능이다.

 

PInvoke( Platform Invocation Service)는 관리화 코드에서 비관리화 코드를 호출할 방법을 제공한다. 일반적인 용도는 Win32 API의 호출을 위해 사용한다.

 

namespace PinvokeExample

{

using System;

 

             using System.Runtime.InteropServices; // 반드시 입력해야 한다.

 

             public class Win32

             {

                           [DllImport(“user32.dll”)]

                           public static extern int FindWindow(string a, string b);

                          

             }

}

 

위 예제는 FindWindow라는 user32.dll C함수를 사용하는 모습을 보여주고 있다. 실제 FindWindow의 선언은 다음과 같다.

 

             HWND FindWindow(LPCSTR swClassName, LPCSTR swTitle);

 

HWND는 윈도우 핸들을 표현하는 32비트 정수 이므로, int형으로 치환되고 LPCSTR 형은 NULL로 끝나는 문자열을 표현한다. 이때 PInvoke string을 자동으로 LPCSTR로 치환해 주는 역할을 하게 된다.

이 문서에서는 이처럼 Win32 API 함수의 여러 유형들을 어떻게 C#에서 사용 할 것인지에 대하여 알아보자.

 

WIN32 데이터형의 치환

Win32 API에서 일반적으로 사용하고 있는 데이터형은 모두 C#의 데이터 형으로 치환될 수 있다.

 

Win32 API TYPE

C#

BOOL, BOOLEAN

bool

BYTE

byte

CALLBACK

delegate

COLORREF

int

DWORD

int

DWORD_PTR

long

DWORD32

uint

DWORD64

ulong

FLOAT

float

HACCEL

int

HANDLE

int

HBITMAP

int

HBRUSH

int

HCONV

int

(모든 HANDLE 타입) Hxxxx

int

LPARAM

long

LPCSTR

[in] string [out] StringBuilder

LPBOOL

ref bool

이외 LP*

ref 형식

UINT

uint

Uxxxx

unsigned 타입들..

WORD

Short

WPARAM

Uint

 

 

Structure 의 전달

예를 들어 POINT 형의 경우,

typedef struct t_Point {

             int x;

             int y;

} POINT;

 

이것은 기본적으로 다음과 같이 선언될 수 있다.

[순차적]

[StructLayout(LayoutKind.Sequential)]
public struct Point {
      public int x;
      public int y;
}

 

[명시적]

[StructLayout(LayoutKind.Explicit)]
public struct Point {
      [FieldOffset(0)] public int x;
      [FieldOffset(4)] public int y;
}

 

일차적으로 할당되는 메모리 레이아웃이 동일하다면, C#에서 바로 받아 들이 수 있다.

 

// BOOL SetWindowPos(POINT pos); 이런 함수가 있다고 가정하면… ^^

 

[DllImport (“user32.dll”)]

public static extern bool SetWindowPos(Point pos);

 

사용할 함수 이름 바꾸기

여기서 함수의 이름을 바꿔서 사용하고 싶다면 다음과 같이 변경하면 된다.

 

// BOOL SetWindowPos(POINT pos);

 

[DllImport (“user32.dll”, EntryPoint = “SetWindowPos”)]

public static extern bool ShowAt(Point pos);

레퍼런스형 전달하기

 

LPPOINT형은 POINT의 포인터 형이므로 ref Point와 같이 사용 할 수 있다. 실제 사용하는 형식은 다음과 같다.

C 언어의 포인터의 경우 레퍼런스로 사용하려고 하면, ref 키워드를 사용하는 방법이 있다.

// BOOL SetWindowPos(HWND hWnd, LPRECT lpRect);

[DllImport(“user32.dll”)]

public static extern bool SetWindowPos(int hWnd, ref Rect lpRect);

 

Out형 함수 인자 사용하기

MSDN 같은 곳에서 함수의 선언을 살펴보면 다음과 같은 형식의 함수를 볼 수 있을 것이다. 이러한 형식은 레퍼런스 형으로 결과를 함수의 인자에 보내겠다는 말이다. 이러한 형식은 Win32 API에서 많이 쓰이고 있고, 포인터를 사용하므로, 많은 주의를 기울여야 한다.

 

BOOL GetWindowRect(
  HWND hWnd,      // handle to window
  LPRECT lpRect   // window coordinates
);

Parameters

hWnd

[in] Handle to the window.

lpRect

[out] Pointer to a RECT structure that receives the screen coordinates of the upper-left and lower-right corners of the window.

 

여기서 LPRECT는 앞 절에서 설명한 Structure의 전달을 참고하여 치환 될 수 있다.

여기서 lpRect RECT의 포인터이며, GetWindowRect 함수 내에서 이 포인터에 직접 값을 쓰게 되어 있다. 즉 이 포인터는 값을 기록하기 위한 인자이지, 값을 전달하기 위한 인자는 아닌 것이다. 이것은 또 다른 C# 레퍼런스 연산자인 out 키워드를 사용하여 쉽게 해결 할 수 있다.

public static extern bool GetwindowRect(int hWnd, out Rect lpRect);

 

실제 사용하는 모습은 다음과 같다.

public static extern bool GetWindowRect(int hWnd, out Rect lpRect);

public static void UseFunction() {

        Rect _rect; // 값을 대입하지 않아도 된다.

        Win32.GetWindowRect(hwnd, out _rect);

}

 

참고로 ref 키워드는 입력과 출력 둘 다 사용 할 수 있다. 그러나 ref를 사용하는 변수가 값이 설정되어 있다는 가정을 하고 있으므로, 이전에 반드시 어떠한 값을 입력해야 한다.

실제 사용 예는 다음과 같다.

public static extern bool GetWindowRect(int hWnd, ref Rect lpRect);

public static void UseFunction() {

        Rect _rect = new Rect(); // 꼭 값을 대입해야 한다.

       

        _rect.top = 20; _rect.left = 30;

        _rect.bottom = 50; _rect.right = 60;

 

        Win32.GetWindowRect(hwnd, ref _rect);

}

 

여기서 잠깐

대중없이 Rect라는 구조체가 나오는데 이는 API에서 RECT형을 C#으로 바꾸어 사용하는 structure이다. 앞의 예제들은 다음과 같은 선언을 하였다고 가정한다.

[StructLayout(LayoutKind.Explicit)]
public struct Point {
      [FieldOffset(0)] public int top;
[FieldOffset(4)] public int left;
[FieldOffset(8)] public int bottom;
[FieldOffset(12)] public int right;

}

 

 

CALLBACK 함수의 선언

C 언어에서 콜백 함수는 함수 포인터로 존재하게 된다. 이것은 함수 인스턴스의 포인터로, 함수 자체를 전달하게 되는 방식이다. 대표적으로 사용되는 부분은 EnumWindows 함수이다.

// BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam)

이 함수는 현재 열려 있는 모든 윈도우 핸들을 열거하기 위한 함수로 실제 처리하는 부분은 함수 포인터, 즉 콜백함수인 lpEnumFunc에서 처리하게 되어 있다. WNDENUMPROC 타입의 선언은 다음과 같다.

// typedef BOOL (CALLBACK* WNDENUMPROC)(HWND, LPARAM);

public delegate bool Callback(int hWnd, long lParam);

이러한 콜백 함수 역할을 하는 C#의 프리미티브는 delegate이다. CALLBACK delegate로 지환된다.

 

결과적으로 다음과 같이 사용하게 된다.

namespace ada.appshare

{

             public delegate bool Callback(int hwnd, int lParam);

            

             internal class Win32

             {

               

                           internal static extern int EnumWindows(CallBack x, int y);

                           [DllImport("user32.dll")]

 

                public static bool EnumWindowsCallback(int hWnd, int lParam)
                {

                        System.Console.WriteLine(“” + hWnd);

                        return true;

                }

                

             }

 

        public static void Main(String []args)

        {

                Win32.Callback call
= new Win32.Callback(Win32.EnumWindowsCallback);

                Win32.EnumWindows(call, 0);

        }

}

 

 


Posted by JJOREG