Battlefield 2142

작년 하반기에 가장 많은 시간을 할애해서 플레이하던 게임이 Battlefield 2142입니다. Battlefield 2142는 Battlefield 1942, Battlefield 2의 sequel로 64명까지 동시에 플레이할 수 있는 온라인 FPS입니다. 두 팀이 보다 많은 전략적 지점을 보다 오랫동안 점유하는 것을 목표로 경쟁하는 게임 형식을 취하고 있습니다. 성장 시스템을 가지고 있어서, 게임 내에서의 활동에 따라 점수가 지급되고, 일정 점수에 도달하면 계급이 오르며, 계급이 오를 때마다 특수한 무기를 새로이 사용할 수 있게 됩니다. 이러한 성장 시스템은 랭킹을 통한 명예라는 인센티브와 게임에 직접적으로 도움이 되는 실질적인 인센티브를 조합한 시스템이라고 볼 수 있습니다. 흔히 보는 MMORPG의 시스템을 FPS에 인접시킨 것이라고 볼 수 있겠죠.

Battlefield 2142는 전통적인 클래스 시스템을 가지고 있습니다. 즉, 이 게임에는 Recon, Assault, Engineer, Support라는 4가지의 병과가 있는데, 병과마다 특수한 무기들과 능력이 주어지고, 이에 따른 역할이 주어집니다. 예를 들어 봅시다. Assault 클래스는 라이플과 AR로켓을 장비하고 있어서 대보병전에 뛰어나며, 메디킷을 가지고 있어서 아군 보병을 치료할 수 있습니다. 한편, Engineer 클래스는 대전차무기를 장비하고 있어서 적 전차를 상대하는 역할을 맡게되고 아군 전차를 수리할 수 있습니다. 각 병과마다 역할이 다르기 때문에 고유의 플레이를 가지게되고, 성장을 할 때도 특정 병과에 집중해서 성장시킬 수 있기 때문에 독특한 플레이를 할 수 있습니다.

Battlefield 2142의 중요한 요소들 중 하나는 바로 분대(squad) 시스템입니다. 누구나 분대를 생성해서 분대장(squad leader)이 될 수 있습니다. 물론 누구나 자기가 원하는 분대에 들어갈 수 있습니다. 분대는 6명의 분대원이 최대이므로 제약은 있습니다. 분대를 만들 수 있다는 것만으로는 분대 시스템이 원활하게 동작하지 않겠죠. 서로 다른 병과의 분대원들이 모여서 시너지를 발휘할 수 있다는 것 외에도 분대에 참여하는 것만으로 게임 시스템에 의해 주어지는 것들이 있습니다. 각 병과에는 같은 분대원들에게 특별한 정보를 줄 수 있는 능력이 있습니다. 분대에 의해 발견된 적은 네트워크를 통해 바로 분대원의 레이더에 표시됩니다. 플레이어가 분대에 참여함으로서 얻을 수 있는 다른 중요한 이익은 바로 분대에 속한 다른 플레이어들-분대원들의 (경험치가 아닌) 경험입니다. 분대원끼리는 Voice를 통해서 대화를 나눌 수 있고, 급속한 전장 상황에 빠르게 대처하고 작전을 펼쳐나갈 수 있게됩니다. 물론, 분대장의 통솔 능력이 여기서 빛을 발합니다. 뛰어난 분대장이 이끄는 분대와 경험없는 분대장이 이끄는 분대는 분대원들의 생존 능력에서 뿐만 아니라, 분대원들이 느끼는 재미에 있어서도 커다란 차이가 있습니다. 분대장은 또다른 엄청나게 중요한 역할을 가지고 있는데, 바로 분대장 자신이 Spawn Point라는 사실입니다. 분대원들의 전방에서 멀리 떨어져있는 후방의 전략 지점이 아니라 전방에 있는 분대장 곁에서 바로 Spawn함으로써 빠르고 효율적으로 전투를 펼칠 수 있으며, 분대원들이 좀 더 뭉칠 수 있도록 도와줍니다. 이러한 분대 시스템은 아마도 Battlefield 이전에는 없었거나 또는 완성되지 않았던, 정말 완벽에 가까운 시스템이라고 생각합니다.

출시된 지 시간이 많이 지나서, 플레이어들의 실력 차이가 커지면서 팀사이의 밸런스가 맞지 않는 현상이 자주 발생하는 바람에 최근에는 이 게임에 어느 정도 흥미를 잃었습니다. 하지만, 제가 경험한 Battlefield 2, Battlefield 2142 모두, 제가 플레이해본 게임들 중 최고의 게임들이었습니다. Battlefield의 다음 시리즈를 기대해봅니다.

Battlefield 2142 더 읽기"

Unnecessary Software Complexity

소프트웨어가 해결하려는 문제가 복잡하다면 그 해결책 즉, 소프트웨어도 복잡해진다. 즉, 소프트웨어의 복잡성은 부정적인 효과를 가지지만, 항상 잘못된 것이고 거절할 수 있는 것은 아니다.

따라서, 소프트웨어의 복잡성 문제를 해결하려고 할 때는 불필요한 소프트웨어의 복잡성(Unnecessary Software Complexity)라는 개념을 가지고 얘기해야한다.

한편, 소프트웨어의 복잡성이 문제의 복잡성에 의존하기 때문에, 문제가 달라지면 불필요한 소프트웨어의 복잡성도 달라진다. 다르게 말하면, 문제가 달라지면, 소프트웨어를 평가하는 기준도 달라질 수 있다는 것이다.

이야기를 하나 해보자. 어떤 소프트웨어 팀은 실제로 많은 문제가 있다고 생각해서, 또는 의욕적이어서 많은 문제를 해결하는 소프트웨어를 만든다. 시간이 지나고, 그런 문제를 해결할 필요가 없어졌고, 사람들은 그런 사실을 잊어버렸다고 해보자. 남아있는 소프트웨어는 그 시점에서 불필요한 소프트웨어의 복잡성이 높은 소프트웨어일테고, 그 소프트웨어의 개발자는 비난을 받는다. 이것은 공평하지 못하다. 소프트웨어 엔지니어링에서 얘기하는 것처럼 문제의 합의에 대한 계약서라도 써놓는 것이 이러한 문제를 해결할까? 그렇지는 않다고 생각한다. 사람들은 ‘문제가 변화한다’는 사실에 대한 명확한 인식을 가지고 있어야할 뿐만 아니라 ‘문제를 알고있다’라는 생각에 대해 좀 더 보수적일 필요가 있다.

문제가 언제 변화하냐고? 문제는 항상 변화한다.

Unnecessary Software Complexity 더 읽기"

Software Complexity

생물학자 줄리언 헉슬리(Julian Huxley)는 1912년에 복잡성을 ‘부분들의 이질성’이라고 정의했는데, 이는 일종의 기능적 불가분성을 뜻한 것이었다. 만들어진 신, 리처드 도킨스

어떤 일을 할 때는 항상 그 일에 맞는 ‘생각의 틀’이 필요하다. 최근 4개월 가량 소프트웨어 개발에서 완전히 손을 떼고 있었고, 그 생각의 틀을 조금씩 회복하는 중이다. 그러던 중 읽은 복잡성에 관한 정의는 내가 지금 겪고 있는 소프트웨어 복잡성의 문제를 떠올리게 해주었다.

복잡성에 관한 얘기를 할 때, 소프트웨어의 복잡성은 흔히 생물의 복잡성과 함께 취급되곤 한다. 물론, 생물의 복잡성은 소프트웨어의 복잡성에 비교할 수 없을 정도지만, 소프트웨어의 복잡성에 대한 일반인들의 관용은 생물의 복잡성에 대한 관용보다 높지는 않을 듯하다. 하지만, 소프트웨어 엔지니어 입장에서 복잡한 소프트웨어를 바라보았을 때 ‘손을 댈 수도 없다’는 그 심정은 생물을 다루는 연구자나 중환자의 몸을 다루는 의사의 심정과 크게 다르지 않으리라고 생각한다.

소프트웨어 복잡성의 문제는 다른 과학이나 공학과 마찬가지로, 환원, 특히 기능적 환원으로 해결하는 것이 기본이다. 복잡한 소프트웨어는 하나의 부분을 이해하거나 고치기 위해서는 그 부분과 기능적 불가분의 관계에 있는 여러 부분들을 동시에 이해하고 고쳐야한다. 환원 과정을 통해 프로그래머가 신경써야할 거리를 분리함(separation of concern)으로써, 프로그래머는 한번에 하나의 부분만을 다른 부분과 관계없이 신경쓸 수 있다. 소프트웨어에 있어서는 이러한 과정을 decomposition또는 모듈화(modulization)이라고 부른다. 소프트웨어가 만들어지고 나서 수행하는 decomposition을 특히 리팩토링(refactoring)이라고 부르기도 한다.

생물과 소프트웨어의 또다른 유사성은 바로 계속 변화한다는 것이다. 그리고, 생물이 진화할 수록 그 복잡성이 높아지는 것처럼 (진화의 방향이 항상 복잡도가 높아지는 방향인 것은 아닐테지만, 적어도 현재까지의 생물사에서는 그러했던 것 같다.) 소프트웨어도 성장할될 때마다 복잡성이 높아진다. 생물의 진화는 인위적인 것이 아니므로 복잡성을 ‘해결’할 필요가 없지만, 소프트웨어의 변화에는 (아직은) 인간의 지적인 능력이 필수적이므로, 복잡성은 소프트웨어가 더이상 성장하는 것을 막는 요인이 되고, 심지어는 소프트웨어의 생명을 짧게 만드는 요인이 되기도 한다. 따라서, 소프트웨어가 오랫동안 성장하기 위해서는 복잡성을 일정 수준 이하로 항상 유지시켜야 하고, 이를 위해서는  decomposition에 더욱 많은 시간을 들이고 자주 해야한다.

소프트웨어 복잡성을 해결하는 과정 못지않게 그것을 인지하는 과정도 중요하다. 복잡성을 인지하지 못하면, 해결하려는 노력을 할 수도 없다. 또한, 복잡성을 인지하지 않고 decomposition을 하려는 노력은 무의미하거나 적어도 비용-효율적이지 못할 수 있다.

소프트웨어 복잡성은 소프트웨어를 만드는 과정 뿐만 아니라 소프트웨어의 결함들을 해결하는 과정 또는 그 원인을 분석하는 과정에서도 인지될 수 있다. 그러한 과정에서 프로그래머가 ‘너무 복잡하다’고 느끼는 것은 복잡성의 첫번째 징표다.

  • 결함 해결 과정에서 너무 많은 부분들이 결함 가능성을 안고 있고 어느 부분에 결함이 있는지 가늠하기 어렵다면 그 소프트웨어는 복잡한 것이다.
  • 결함의 원인이 한 부분만의 결함이 아니라 많은 부분들이 서로 영향을 주면서 발생한 결함이라면 그 소프트웨어는 복잡한 것이다.

복잡성을 훌륭하게 해결한 시스템들을 보면 항상 기능적 분화 뿐만 아니라 발생 가능한 결함 자체를 격리한다. 복잡한 시스템들은 그 부분들간에 너무 많은 기능을 서로 의존하고 있을 뿐만 아니라, 설령 기능은 분리되어있다고 하더라도, 발생가능한 결함들은 분리되어있지 않다.

가만히 생각해보면 소프트웨어 디자인이 해결하고자 하는 주요 문제는 결국 복잡성의 해결이다. 성능과 같은 비기능적 요소들은 제약조건에 불과하다. 이를테면, ‘성능 요건을 xxx만큼 만족시키면서 복잡성을 yyy만큼 해결하는 것’의 문제란 것이다. 대규모 소프트웨어를 높은 수준에서 바라보는 사람은 복잡성에 관한 생각이 반드시 그의 ‘생각의 틀’에 담겨있어야한다.

Software Complexity 더 읽기"

Web Crawler

web_crawler_20070223.pdf

올해 초에 신입 팀원들을 대상으로 발표한 자료로, Web Crawler의 기본적인 설계와 구현시 신경써야할 기초적인 사항들을 언급하고 있습니다. 대단한 내용이 있는 것은 아니고 단순히 Mining the Web의 Web Crawler chapter를 정리한 내용입니다.

Web Crawler 개발에 있어서 가장 중요한 것은 무엇이냐고 제게 묻는다면 저는 이 발표자료의 Closing에 적혀있는 한 줄로 대신 답하겠습니다.

Understand status quo of Web

사실 어느 정도 숙련된 개발자라면 Web Crawler의 기초적인 기능들은 대단히 단순하기 때문에 별로 어렵지 않다고 느끼실 겁니다. 저도 물론 처음엔 그렇게 생각했구요. 하지만 현재는 약간 생각이 달라졌습니다. 훌륭한 Web Crawler를 만들 수 있는 능력은 바로 웹의 현재 상태에 관한 지식을 얼마나 가지고 있는가 또는 얻을 수 있는가에 달려있다고 생각합니다. 이 발표자료를 쓸 때보다 이러한 생각이 더욱 강하게 드는 요즈음입니다.

Web Crawler 더 읽기"

재설계

사람들이 미술을 처음 시작하면 잘못된 부분을 다시 그리는 것을 꺼려한다. 일단 그 정도로 그린 것에 만족하며, 다시 손을 대다가 혹시 더 망치지나 않을까 염려한다. 그리하여 방금 그린 것이 그렇게 나쁘지는 않다고, 어쩌면 최선의 작품일지도 모른다고 생각하여 스스로를 설득한다. 폴 그레이엄, 해커와 화가

재설계 더 읽기"

Naming Objects

이 글은 2004년 12월 14일, 네오위즈에서 일하던 시절에, 팀 내부에 공유한 메일입니다. (영어 단어나 어법 오류 등은 수정했습니다.) 현재의 제 생각과는 약간 다른 부분도 있지만,

  • 문제 영역의 언어로 이름짓기
  • 적절한 비유의 사용
  • 최소화 인터페이스

등은 저 뿐만 아니라 많은 사람들이 동의하는 프로그래밍의 원칙들입니다.

이름 짓기

바쁜 일정과 많은 업무로 인해서 팀 내에서는 ‘이름 짓기’가 중요하게 여겨지지 않고 있지만, 저는 이름 짓기에 대해서 강한 의견을 가지고 있는 사람입니다. ;-)

객체와 메서드의 이름을 지을 때는 그것이 하는 역할에 맞게 이름을 짓는 것이 좋습니다. 당연히 이 역할은 객체가 해결하려는 문제의 도메인 안에서, 그 도메인의 언어로 정의됩니다. 따라서, 객체와 메서드의 이름은 문제 도메인의 언어로 기술하는 것이 가장 바람직합니다. (일반화된 이름은 원래 문제 도메인의 이름이 아닌 경우가 있지만, ‘일반화’ 역시 일종의 해결하려는 문제입니다)

예를 들어, 6살짜리 어린이가 가게에서 사탕을 사먹는 문제에 대해서 해결 방법을 기술하려면, 6살짜리 어린이가 이해할 수 있어야 하고, 사탕이라는 단어가 거의 반드시 들어가야겠죠.  또한, 사탕을 사 먹어야 하는데, 다른 도메인의 언어, 즉 음표라든가, 양자역학 같은 단어가 나오면 곤란합니다.

junkmemocheck를 희생양으로 삼아 예를 들어보죠. (junkmemocheck는 쪽지를 너무 많이 보내는 사람을 차단하기 위해 사용자별로 최근에 쪽지를 보낸 수를 세어주는 컴포넌트입니다.)

메서드 이름 짓기

제가 이해한 바로는, junkmemocheck(이하, 줄여서 JMC)에 관련된 시나리오는 다음과 같습니다.

  1. 사용자 A가 쪽지를 보낼 때, A가 쪽지를 보냈음을 JMC에 알려줍니다.
  2. 사용자 A가 미리 정해진 시간 내에, 쪽지를 보낸 수를 얻거나, 너무 많이 보냈다는 정보를 얻어옵니다.

이를 좀 더 단순한 말로 적어보죠.

  1. 특정 사용자가 쪽지를 보냈다.
  2. 특정 사용자가 최근에 쪽지를 보낸 수를 주세요.

메서드 이름으로 바꿔보죠.

쪽지는 Memo라는 용어로 대치하고, ‘미리 정해진 시간 내에’라는 requirement에 대해서 ‘Recently’라는 단어를 도입했습니다.

  1. sentMemo(user)
  2. getCountOfMemoSentRecently(user)

좀 더 줄이면,

  1. sentMemo(user)
  2. getRecentMemoSent(user) 또는 getMemoSent(user)

클래스 이름 짓기

이러한 역할에 비추어보면, junkmemocheck는 (Sent)MemoCounter 같은 이름이 더욱 적당하지 않을까 싶습니다. (사실 ‘정해진 시간 내’라는 제약 조건이 있어서, “Counter”라는 비유가 약간 부적절해 보이므로, 좀 더 나은 비유가 있으면 좋을 듯 하네요.)

MemoCounter
+ increaseCount(user)
+ getRecentCount(user)

일반화된 이름 짓기

나중에 다른 요구사항이 생겨서, 쪽지만이 아니라 다른 곳에도 이 어플리케이션이 사용되기도 결정되었다고 합시다. 우리는 “Counter”라는 비유를 이미 사용했으므로, 이를 그대로 사용하면 될 것 같습니다. 사용자 대신 슬롯(slot)이라는 개념을 사용합니다.

Counter 클래스의 역할(Role)은 다음과 같습니다.

  1. k 슬롯의 카운터를 하나 증가시킨다.
  2. 일정 시간 내에 k 슬롯에 증가된 카운터 값을 가져온다.

이를 바탕으로 다음과 같이 이름을 지을 수 있겠네요.

Counter
+ increaseCount(k)
+ getRecentCount(k)

개선된 디자인

사실 Counter 안에 k라는 슬롯을 찾는 것보다는, k라는 이름의 Counter가 있는 것이 더욱 직관적입니다. Counter의 메서드에 k를 명시하기 보다는, k에 해당하는 Counter 객체가 있는 것이 낫겠죠.

kCounter = CounterCollection.getCounter(k)
kCounter.increaseCount()
kCounter.getRecentCount()

좀 더 단순하게 만들어볼까요? CounterCollection에 Count가 있는 것은 당연하고, Count를 increase하는 것은 당연하므로,

kCounter = CounterCollection.get(k)
kCounter.increase()
kCounter.getRecent()

성능 고려하기

MemoCounter 든, Counter든 역할에 따라 두개의 operation으로 분리했습니다. 하지만, 내부적인 구현에 따르면, 각각이 server로 메시지를 따로 보내야 한다면, 그리고 거의 항상 두 operation이 같이 쓰인다면, 이러한 분리는 비효율적입니다. 한번에 물어볼 수 있는 방법이 필요합니다.

Counter
+increase(k)
+getRecent(k)
+increaseAndGetRecent(k)

가능한 한 원래의 인터페이스가 직관적이므로 유지하는 것이 좋다고 생각하지만, 이를 유지하면서 성능을 개선할 수 있는 더 나은 방법이 있을지도 모르겠습니다.

인터페이스 최소화

거의 항상 단순한 인터페이스가 좋습니다. 필요없는 인터페이스는 사용자를 혼란스럽게 합니다. 예를 들어, increaseAndGet(k)만이 사용되고, 다른 메서드는 사용될 가능성이 별로 없다면,

Counter
+increaseAndGetRecent(k)

만으로도 충분합니다.

결론

저는 메서드 이름부터 새로 짓기 시작했지만, 좀 더 일반적인 방법은,

  1. 문제 해결에 필요한 객체의 이름과 역할을 정합니다. 객체를 정할 때는 문제를 해결하기 위한 비유를 생각해보면 좀 더 쉽게 지을 수 있습니다. 문제는 얼마나 적절한 비유인가인데, 어떤 비유가 객체의 역할 즉, 문제의 해결방법에 얼마나 근접하느냐를 따져야할 것입니다. 객체의 이름은 항상 문제 도메인의 언어나, 비유의 이름으로 짓습니다.
  2. 객체 역할에 따라 메서드를 추가합니다. 하나의 동작에 하나의 메서드를 추가합니다. 메서드 이름 역시 항상 문제 도메인의 언어로 짓습니다.
  3. 이 후에도 얼마든지 성능 고려나 인터페이스 최소화를 할 수 있습니다.

Naming Objects 더 읽기"

Great Scott!

Heroes 에피소드 10에서 웨이트리스를 구하기 위해 시간이동을 하다가 6개월전으로 가버린 히로가 안도에게 도움을 구하기 위해 전화를 하지만, 과거의 히로가 전화를 받죠. 그 때 – 서로 다른 시간에 존재하는 자신을 조우했을 때, 어떤 현상이 일어나는지 모르니까 – 히로가 놀라서 하는 말이 “Great Scott!”입니다.

위키피디아 내용대로라면 Great Scott은  놀람을 표현하는 감탄어구라고 볼 수 있는데, Superman 같은 수퍼히어로물이나 나르니아 연대기 등에서도 사용되었다고 하는군요. 그 기원은 남북전쟁 시대의 유명한 장군의 이름이라고 하는데, 그것이 왜 감탄어구로 자리잡게 되었는지는 알 수가 없군요.

어쨌든 히로 너무 귀엽습니다아.

Great Scott! 더 읽기"

Dynamics of Software Development

Dynamics of Software Development by Jim McCarthy

요즈음 널리 유행하는 방법론들처럼 체계화 되어있는 것은 아니지만, 이 책은 소프트웨어 개발의 여러 부분들에 걸친 조언을 담고 있는 책이다. 그 범위는 소프트웨어 개발에서 어떤 것이 바람직한 리더쉽인가, 어떻게 팀을 만들 것인가, 고객이나 비즈니스 부문 등의 팀 외부와 어떻게 소통할 것인가, 경쟁 시장에서 어떻게 포지셔닝할 것인가,빌드와 출시, 개발 일정과 주기는 어떻게 할 것인가, 사람은 어떻게 뽑을 것인가와 같이 소프트웨어 개발 팀에 관련된 거의 모든 부문을 다루고 있다고 해도 과언이 아니다.

저자는 소프트웨어 개발팀을 생태계와 같은 것으로 보고 있다. 팀원들은 팀장의 지시에만 따르고, 미리 정해진 역할을 수행하는 것이 아니라, 팀장은 팀원에게 권한을 위임해야하며 (지식노동에 있어서 이것은 이미 황금률이다.) 팀원들은 자신의 능력이 다른 팀원들과 조화를 이루도록 역할을 진화시켜 나간다. 이러한 관점은 소프트웨어가 지식 노동의 산출물이기 때문에 무엇보다도 팀원들부터 아이디어가 자유롭게 나오고 그 중에서 최상의 아이디어가 선택되어야한다는 이유에 비롯한다.

애자일 소프트웨어 개발 프랙티스 (이를테면 XP와 같은)에서와 비슷한 주장들을 1995년에 쓰여진 이 책에서 하고 있는 것도 재미있는 점이다. (참고로, C3 프로젝트는 1996년에 시작되었고, Kent Beck이 유명한 Extreme Programming Explained를 내놓은 것은 1997년의 일이다.) 신속하게 개발주기를 반복한다든가, 알려진 상태를 유지하는 것, 고객과의 관계의 강조 등에서 그러한 것들을 엿볼 수 있다. Scrum과 같은 방법론은 1993년에 적용되기 시작했고, Iterative and Incremental Development: A Brief History와 같은 문서를 보면, 1980년대에 이미 점진적인 개발 방법이 소프트웨어 개발계에 떠들썩하게 떠오르고 있음을 할 수 있다.

설령 책이 제시하는 세부적인 방법들에 동의를 하지 않는다고 하더라도 한번이라도 팀에서 소프트웨어 개발을 해본 경험이 있다면, 지난 날의 경험을 회상하며 이 책의 조언에 대해 깊히 생각해보는 것은 매우 유익한 일일 것이다. 다만, 저자는 인문학적인 배경이 있는지 몰라도 문학이나 철학에 기원한 개념들을 자주 빌려쓰고 있을 뿐만 아니라 전체적인 문체도 간명한 편은 아니어서, 읽기가 쉽지 않은 면이 있다. 단지 번역의 탓만은 아닌 듯 보인다. 반대로 이러한 점 때문에, 한문장 한문장을 곱씹어볼 경우 여러가지 경우에 여러 의미로 다가올 수도 있을 것이고, 비단 저자의 조언을 곧이곧대로 받아들이기보다는 풍부한 고민의 원천이 될 수 있는 면도 있을 것이다.

Dynamics of Software Development 더 읽기"

한글 윈도우즈 환경에서 스팀 게임 설치 문제 해결

밸브(Valve) 사의 게임 배포 시스템인 스팀(Steam)에서 게임(Tomb Raider: Anniversary)을 구입한 후 설치 도중 진행률 99%에 멈춰서 더이상 진행되지 않고, 게임도 플레이할 수 없는 문제가 생겼습니다. (SexyDino님도 경험하셨더군요.)

스팀 A/S에 문의해보니 FAQ 답변을 제시해주었고, 모든 방법을 시도해보았으나, 해결에 도움이 되지 않았습니다. 거의 포기하고 있다가, 마지막 방법으로 ‘유니코드를 지원하지 않는 프로그램용 언어’ ([제어판]-[국가 및 언어 옵션]-[고급])의 설정을 영어로 하고 다시 설치를 시도하니까 정상적으로 설치가 되고 이 설정을 원래대로 복구한 후에도 플레이가 가능했습니다.

스팀 A/S에도 이러한 해결책을 알려주었고, 스팀 측에서도 개발자들에게 이러한 이슈를 알려주고 다른 사용자들이 비슷한 문제를 겪을 때에도 신경을 쓰겠다고 하는군요.

한글 윈도우즈 환경에서 스팀 게임 설치 문제 해결 더 읽기"