C언어에서는 bool 타입이 따로 존재하지 않는다. 0이면 false, 그렇지 않으면 true이다. 심지어는 float도 피연산자로 참여할 수 있다. 그래도 어쨌든 논리연산자의 결과값은 0 아니면 1이 되는 1byte 정수형을 리턴한다.
예를 들어
int a = 10;
float b = 3.14;
printf("%d, %d\n", a && b, sizeof(a && b));
위의 코드에서 출력은 1, 1 이다.
그런데 파이썬은 조금 더 변태같다.
a = 10
b = 20
print(a and b, a or b)
결과는 20, 10이다. 어째써? and와 or는 다음과 같이 구현되어 있다.
and의 경우 a가 참이면 b를 리턴한다. 결과적으로 a and b가 참인지 거짓인지는 b에 달린 상황이다. 만약 a가 거짓이면 그냥 a를 리턴한다. a가 거짓인 줄 알고 리턴하니까 a and b도 거짓이 된다. 이 때 b는 체크하지 않는다.
or의 경우 a 가 참이면 그냥 a를 리턴한다. a가 참이니까 a or b 도 참이다. 이 때 b는 체크하지 않는다. 만약 a가 거짓이면 b를 리턴한다. a or b가 참인지 거짓인지는 b에 달렸다.
한 번 더 정리하자면 a and b 에서 a 가 거짓이면 b를 체크하지 않고 a를 리턴한다. a or b에서 a 가 참이면 b를 체크하지 않고 a를 리턴한다. a and b에서 a가 참이면 b를 리턴하고, a or b에서는 a가 거짓이면 b를 리턴한다.
C언어의 논리연산자는 정수형 입력을 받아서 정수형 출력을 한다. 파이썬의 논리연산자는 입력 타입이 정해져 있지 않다. 그래서 입력 타입이 부울이 아닌 경우 엉뚱한 결과가 발생한다.
뭐 당연하겠지만 또 웃기는 것은 True의 타입은 <class 'bool'>인데 산술연산에 참여할 때는 정수 1처럼 여겨진다는 것이다.
print(True + True)
결과는 2이다.
또 한 가지 팁이라면 파이썬에서는 None을 거짓으로 취급한다. 반대로 리스트나 기타 오브젝트는 참으로 취급한다. 아래 예제에서
a = [1, 2, 3]
print(a or None)
print(None and True)
결과는 [1, 2, 3], 그리고 None이다.
그런데 비어있는 리스트, 문자열, 딕셔너리, 셋, 튜플 따위는 거짓으로 취급한다.
print( [] or dict() or set() or tuple() or '' or 10 )
결과는 10이다. 10 앞에 나열한 모든 것들은 전부 거짓으로 취급된다.
여기 또 다른 함정이 있다. 예를 들어 10은 bool로 형변환하면 True가 되긴 하지만 그렇다고 1이 True인 것은 아니다.
a = 10
print(a is True)
print(a == True)
결과는 둘 다 False이다. a는 정수 10일 뿐, bool도 아니고 True도 아니다.
if문을 쓰지 않고 a가 참인지 체크하려면 간단히 bool로 형변환해보면 된다.
print(bool(a))
결과는 당연히 True이다.
마지막으로 아래 식을 확인해보자.
print(type(a and True))
print(type(True and a))
결과는 <class 'bool'>, <class 'int'> 이다. and 연산의 리턴 타입을 보면서 파이썬의 함수는 리턴 타입이 정해져있지 않다는 사실이 새삼 현실로 다가온다.
파이썬의 이러한 특성을 이용하면 변태같은 코딩이 가능하다.
if a < b : func() #이 문장은
a < b and func() #요렇게 바꿀 수 있다.
if not (a < b) : func()
a < b or func()
조건문 대신 and와 or를 쓸 수 있다. 영 and와 or가 어색하다면 and 대신 then, or 대신 otherwise로 읽어보면 조금 더 자연스럽다. 어셈블리어를 해봤으면 매우 자연스러울 수 있다, 점프처럼 생각하면 되니까.
여기에 보태서 None이 거짓으로 인식되는 점을 이용해 None 체크를 할 수 있다.
if a is not None: b = a
else: b = somethingDefault #이 두 문장은
b = a or somethingDefault #요 한 문장으로 바꿀 수 있다.
a를 b에 대입하는데 만약 None이면 미리 정해준 기본값을 대입하도록 한 것이다. 같은 원리로 비어있는 리스트를 체크할 수도 있다.
myList = []
x = min(myList or [-1])
x = min(myList) #에러 발생
만약 myList == [] 즉 비어있는 리스트라면 에러가 발생한다. 이걸 try 로 잡아서 어쩌구 해도 되지만 어차피 비어있을 경우 x에 기본값 -1을 넣을 작정이라면 위와 같이 or를 이용할 수 있다.
당연히 논리연산자의 피연산자는 bool로 묵시적 형변환이 일어나는 줄 알았고, 당연히 출력은 무조건 bool인줄 알았는데 입력 타입을 그대로 살려서 출력한다니 좀 어이없긴 하다. 파이썬의 철학이 단 하나의 목적을 위한 단 하나의 아름다운 코드 아닌가? 이런 변태같은 부분은 파이썬의 기본 철학을 정면으로 부정하는 것이다. 통일성과 간편함을 동시에 추구하는 것이 그렇게 쉬운 일은 아닌 셈이다.
파이썬의 이런 점이 마음에 들지 않는다, 혹은 헷갈려서 공부 못하겠다 싶으면 정수나 기타 이상한 타입은 일절 논리 연산에 참여시키지 말아야 한다. 반드시 bool(a) 또는 a != 0 과 같은 코드를 써서 명시적으로 bool로 변환해주는 것이 좋다. C를 제외한 대부분의 언어, 그러니까 Java와 C#에서는 당연히 이렇게 한다. 논리연산에는 bool만 참여할 수 있기 때문에 반드시 명시적 형변환이 필요하다. 차라리 이게 나은 것 같은데.
11bit studio는 This War Of Mine 으로 화제가 되어 그 동안 다양한 게임들을 내놓았다. 대형 게임은 아닐지라도 특유의 레트로 감성과 충실한 시나리오로 매나이를 형성하고 있다.
프로스트 펑크는 그 동안의 게임 중 This War Of Mine과 가장 닮은 게임이라고 할 수 있다. 똑같이 혹한의 설정, 그리고 서바이벌 상황에서 무너지는 인간의 존엄성을 주제로 하고 있다.
게임의 주된 컨텐츠는 경영과 심시티이다. 주변의 자원을 채취하고, 건물을 건설하고, 테크트리를 개발하는 등 게임 구조는 매우 평범하다. 다만 혹한에 싸우는 작은 인간 사회라는 설정 속에서 생존을 위해 인간의 존엄성과 양심을 버리도록 선택을 강요한다. 여기에 충분한 연출로 몰입감을 더해서 단순한 매니악한 스크립트 위주의 게임성을 벗어나 대중적인 입맛도 만족시키고 있다.
중요한 건 난이도이다. 난이도가 너무 쉬우면 특유의 긴장감을 느끼기 어렵지만 난이도가 너무 높으면 게임의 분위기를 느끼기보다 시스템을 분석하고 파고들기에 열중하여 그 역시 몰입을 방해한다. 또한 게임에서 다 이기려고 하면 안 된다. 사상자 한 명도 없이 게임을 끝내려고 하는 노력은 안 하는 것이 좋다. 좀 고생도 하고 이래저래 난관이 와도 어떻게든 게임을 붙잡고 진행을 해보는 것이 게임을 제대로 즐기는 방법이다. 공포영화를 보는데 아무도 안 죽는 건 말이 안 된다. 사람이 죽는 긴장감과 스트레스가 싫으면 애초에 공포영화를 선택해서는 안 된다. 이 게임은 사람들이 죽어나가는 상황에서 어떻게든 발버둥쳐야 하는 스트레스와 긴장을 동반하고 있다. 그리고 그 긴장을 즐기는 것이 이 게임의 목적이다.
또 재미난 건 일반적인 심시티와는 달리 직교좌표가 아닌 극좌표계를 채용하여 가운데를 중심으로 동그랗게 건물을 쌓아나가야 한다. 극좌표계 그 자체로도 나름의 재미가 있다.
경영 시뮬레이션 + 심시티 + 생존게임에 적절한 연출이 합쳐진 수작임에 틀림없다.
Unity에서 동영상을 불러오려면 보통 VideoPlayer를 쓰거나 OpenCV의 VideoCapture를 많이 쓰게 된다. 여기서는 https://forum.unity.com/threads/how-to-extract-frames-from-a-video.853687/ 여기 링크를 참고하여 VideoPlayer를 사용하는 방법을 설명한다.
VideoPlayer는 unity 컴포넌트라서 new VideoPlayer() 식으로 할당하면 안 되고 씬에서 생성한 다음에 넣어줘야 하는데 스크립트에서만 동작시키고 싶으면 Start()와 같은 곳에서 다음과 같이 쓰면 된다.
gameObject가 필요하니까 MonoBehavior 클래스를 상속받아야 한다. 어차피 유니티 모듈이니까 아예 유니티와 상관없이 동작은 안 되는 것이다. 위의 코드는 그냥 예제라서 로컬로 선언했지만 멤버로 선언하는게 맞으르 것이다.
받아오자마자 Stop()부터 하고 APIOnly 모드로 설정한다. 이는 화면에 직접 플레이하는 일은 안 하고 내부적으로만 동작하겠다는 것이다.
preparedCompleted 는 동영상의 헤더를 다 읽어와서 준비가 되면 호출된다. frameReady는 프레임 한 장을 grab 하는 일이 완료되면 불러와진다. 매 프레임마다 불러와지는 것이다.
이제 아래와 같이 주소값을 넣어주고 Prepare를 호출한다.
여기서 path를 잘 넣어줘야 한다. 아래와 같은 구문을 참고해보자.
준비가 완료되었을 때 호출될 함수는 다음과 같다.
이렇게 포즈를 걸면 0번째 프레임을 불러온다.
중간에 frameIndex == 0 블럭이랑 saveImage 블럭은 내가 OpenCVForUnity를 활용하기 위해 작성한 것이니 신경쓰지 말자. 하여튼 중요한 것은 textureToCopy가 들어왔다는 것이고, 이것을 이용하여 본인 원하는 작업을 하면 된다.
맨 마지막에 frameLimit는 내가 직접 선언한 것이니 신경쓰지 말고, 중요한 것은 vp.frame에 그 다음으로 읽고 싶은 프레임 번호를 넣는 것이다. vp.frame은 단순한 멤버 변수가 아니고 property이기 때문에 값을 집어넣는 순간 다음 프레임을 읽어오는 동작을 수행한다.
ReadComplete도 내가 직접 만든 것이다. 하여튼 여기에서 다 읽어왔을 때의 동작을 수행하면 된다.
이 방법의 문제점은 Prepare에 실패했을 때 아무런 피드백이 없다는 것이다. Prepare()를 호출하고 얼마간 지났는데도 Prepared 이벤트 함수로 들어오지 않는다면 뭔가 조치를 취할 수 있도록 해야 한다. 우선 아래와 같은 무식한 방법이 있긴 하다.
100 frame이니까 약 3초 정도 기다려보는 것이다. while을 빠져나오고 나면 isPrepared를 다시 검사해서 준비가 되서 빠져나온 건지 아니면 count가 다 되서 빠져나온 건지 확인해보면 된다.
또한 일부 Unity 플러그인에서 레코딩한 파일은 몇 프레임 읽다가 실패한다. 상당수의 비디오 레코더들이 표준 포맷을 지키지 않고 파일을 쓴다. 여타 다른 비디오 플레이어들은 어떻게든 잘못된 포맷 형태를 무시하고 동영상을 플레이해주는데, VideoPlayer는 그들만큼 정성스럽지가 못하다.
유니티에서 공식적으로 지원한다고 밝힌 확장자는 아래와 같다.
https://docs.unity3d.com/Manual/VideoSources-FileCompatibility.html
그런데 여기에 함정이 있다. mp4의 경우, h.264는 잘 불러와지는데 h.265는 따로 코덱을 깔아야 한다. 나는 대충 깔아서 해봤는데 잘 안 되고 신통치 않다;; 같은 avi, mp4 확장자라도 코덱은 천차만별이라 신경 쓰이는 게 한두가지가 아니다. 게다가 윈도우가 아닌 안드로이드로 넘어가면 골치아픔은 두 배가 된다.
중3 때 처음 음정을 배울 때 음정에 대해서 이렇게 배웠다.
*숫자를 세릴 때는 자기 자신부터 세린다.
*계이름 사이에 반음(미파, 시도)이 한 없으면 장, 있으면 단, 2개면 감
*감 - 단 - 장 - 증 순이며 조표가 붙어서 간격이 넓어지면 오른쪽으로, 좁아지면 왼쪽으로
뭐든지 이해를 못 할 때는 외우는 게 좋고 외우나 이해하나 결과적으로는 같게 되는 경우가 많다지만 그냥 이해하면 쉬운 걸 굳이 어렵게 외울 때가 있는데, 바로 이런 경우라 하겠다.
음정이란 음 사이의 간격을 나타내는 말이다. 우리가 거리를 잴 때, 25cm, 3m 등으로 말하듯이 음악에서 음과 음 사이의 거리를 말할 때 쓰는 용어가 음정이다.
음악에서 모든 음은 반음 간격으로 이루어져 있으며 1옥타브 내에 12개의 음이 존재한다. 그러므로 그냥 음과 음 사이의 거리를 반음의 개수로 3반음 5반음 이런 식으로 나타내면 그냥 편하지 않느냐고, 공학적인 입장에서는 설명할 수 있지만, 실제로 음악은 그렇지 않다. 음악의 음은 음계 위에서 이루어지며 음 과의 거리도 음계 위에서 설명해야 한다. 예를 들어 자연 장음계에서 미와 솔 사이에는 파, 파# 이렇게 두 개의 음이 있으므로 미부터 솔까지 가려면 3칸 움직어야 하니까 미 솔은 3칸 이라고 하고 솔과 시 사이에는 솔#, 라, 라# 이렇게 3개의 음이 있으므로 솔부터 시까지 가려면 4칸 움직여야 하니까 4칸! 이래버리면 편할 것 같지만... 이렇게 해봐야 원래 음정을 얘기하는 취지가 무색해지고 음악적이지가 않다. 장음계 위해서는 자기 자신과의 거리가 1도이고 미와 솔은 미 파 솔 순으로 진행되므로 3도, 솔과 시 는 솔 라 시 순으로 진행되므로 역시 3도이다. 그런데 같은 3도라고 해도 완전히 똑같은 3도는 아니기 때문에 별명을 붙여서 미솔은 단3도, 솔시는 장3도라고 부르게 된 것이다.
어쨌든 처음 음계의 개념을 이해할 때는 음악적으로 접근하기보다 그냥 공학적으로 접근해버리는 것이 더 이해가 빠를 수도 있다.
다음과 같이 외워보자.
자기 자신과의 거리 = 완전1도
반음 1칸 거리 = 단2도 = 증1도 (미파, 시도)
반음 2칸 거리 = 장2도 = 감3도 (도레, 레미, 파솔, 솔라, 라시)
반음 3칸 거리 = 단3도 = 증2도 (레파, 미솔, 라도, 시레)
반음 4칸 거리 = 장3도 = 감4도 (도미, 파라, 솔시)
반음 5칸 거리 = 완전4도 = 증3도 (도파, 레솔, 미라, 솔도, 라레, 시미)
반음 6칸 거리 = 증4도 = 감5도 (파시)
반음 7칸 거리 = 완전 5도 = 감6도 (도솔, 레라, 미시, 파도, 솔레, 라미)
이런 식이다. 음 사이의 거리에 대해 각각의 별칭이 붙었다고 생각하면 편하다.
그럼 왜 단, 장, 완전 이런 용어들을 붙였을까? 그건 그냥 그 음 간격이 주는 음악적 느낌으로부터 왔다고 해야 될 것이다.
같은 3도 화음이라도 도미, 솔시와 같은 장3도는 밝은 느낌을 주는데 반해 미솔, 라도 등 단3도는 어두운 느낌을 준다. 한 가지 힌트로서 장음정이나 완전음정은 도부터 시작하는 음이 존재한다. 예를 들어 도레 : 장2도, 도미 : 장3도, 도파 : 완전4도, 도솔 : 완전5도, 도라 : 장6도, 도시 : 장7도 이다. 만약에 도부터 출발해서 단3도를 만들려면 도레#이 되어야 하는데 반음이 섞여 있으므로 어두운 느낌이 난다. 음정을 최초에 어떤 놈이 만들었는지 알 수는 없지만 분명 내가 생각한대로 도를 기준으로 나열하여 장음과 완전음정을 정하고, 나머지를 단음정이라고 했을 것으로 강력히 추측된다. 완전 내 뇌피셜 ㅋㅋㅋ 가끔 보면 뇌피셜을 진짜 어디서 확인한 사실인 양 우선 얘기하고 보는 사람들 있는데 나도 그 축에 낀다 헤헤헤 내가 맞다고 생각한 거니까 맞는 거다, 그게 나 같은 인간의 사고 체계.
완전4도, 완전 5도 완전 8도는 배음과 관련이 있다. 주파수를 2배로 올렸을 때 한 옥타브 높은 음이 나며 1.5배 올렸을 때 완전 5도 높은 음이 생긴다. 1.5배음과 2배음 사이의 음정은 완전 4도이다. 예로부터 이들 사이의 화음이 가장 아름답다 하였고, 심지어는 complete하다고 여겼다. 겨우 음악의 음정 따위에 완전이라는 거룩한 명칭을 붙인 서양인들의 세계관이 간접적으로 느껴지는 부분이다.
다른 자료가 없으면 내가 엄청 열심히 설명할테지만 널리고 널린 게 음정 자료니까 요까지만.
2013년인가 나왔던 국산 애니메이션인데, 스팀에서 3300원에 구매할 수 있다.
https://store.steampowered.com/app/468060/PADAK/
횟집에 잡혀들어온 고등어가 어항을 탈출해서 바다로 간다는 이야기인데, 매우 현실적인 묘사가 특징이다. 벌써 이렇게 한 줄만 봐도 내용을 다 설명하고 남은 느낌이다.
회라는 요리, 생으로 살을 썰어 먹는다는 그로테스크함, 생선이란 동물이 가지고 있는 기괴함이랄까, 그리고 약육강식의 현실이 주는 잔혹함, 그런 것들을 적극적으로 차용해서 단순한 탈출 동화를 매우 극적으로 느껴지게 만든다.
주인공인 우리 파닥이는 매우 단순한 캐릭터에 멍청함까지 가지고 있다. 어항에 들어오자마자 나갈꺼야! 만 외치며 하루 종일 유리벽에 머리를 들이받는다. 실제 고등어야 그럴 수 있지만 영화 속 주인공이 저래도 되나 싶다. 게다가 바다라는 야생에서 컸음에도 불구하고 양식장에서 자란 물고기들보다 야성이 없고 순한 것도 공감할 수 없게 만드는 부분이다.
이야기의 핵심이 되는 것은 오히려 고등어가 아니라 넙치. 유일하게 입체적인 캐릭터를 가졌고, 나름 스토리가 있는 인생을 살았다. 그리고 그게 이 영화의 유일한 스토리라고 할 수 있을 것이다.
더빙은 모두 전문 성우들이 맡았다. 인간의 경우는 자연스러운 일상톤으로 연기했고 물고기들은 약간의 극화톤이 느껴진다. 전체적으로 훌륭한 편이다. 성우의 연기를 항상 애니메이션이나 게임으로만 접하다보니 뭔가 말투가 과장되어 있고 억지로 목을 눌러 목소리를 변형시킨 티가 나서 영 거북했는데, 그나마 여기서는 자연스럽다.
본격적인 뮤지컬 형식은 아니지만 몇 곡의 노래가 들어있고 이 때는 2D 애니메이션이 나온다. 나름 볼만한 요소, 들을만한 요소이다. 성우들이 직접 노래를 불렀다.
"밥 먹는 데 울고 지랄이야, 밥맛 떨어지게" - 같은 수족관 물고기의 꼬리를 뜯어먹으면서 하는 말
선도 악도 없는 잔인한 약육강식의 세계, 그 속에서 최상위포식자인 우리는 인간이다. 그래서 평소에 물고기의 입장으로 생각하지 않았는데, 과연 물고기들은 어떨까? 잔인한 묘사를 통해 그 모습을 있는 그대로 보여주면서 약간의 위트를 섞어서 너무 거부감이 들지 않도록 배려했다. 그 잔인하고 (물고기 입장에서)현실적인 묘사라는 것은 나름 충격적으로 다가오는 면이 있으나, 그것은 그저 작품의 분위기만 전달할 뿐, 전체적인 영화구조나 스토리에 큰 영향이 없으며 충격, 그걸로 그냥 끝이라는 게 문제다. 먹고 먹히는 현실에 충분히 몰입하는 경험은 좋지만 어항과 바다를 대비시켜 그 의미를 부여하는 것에는 미흡하다. 대사는 전체적으로 임팩트가 부족하고, 특히 고등어가 바다로 가는 희망을 얘기할 때는 좀 루즈하다. 무엇보다 역시 내용이 좀 없다.
횟감이 되는 물고기의 입장에서 횟집에서 벌어지는 일을 물고기 입장에서 잔인한 톤으로 그려본다는 것, 벌써부터 뻔하지 않나.
영화를 보면서 떠오른 건 안도현의 '스며드는 것'. 영화보다 한 편의 시가 더 나은 것 같다.
'저녁이야, 불 끄고 잘 시간이야'