안녕하세요! 그로스로그 입니다!😊 🌱
제목에 "0편"이라고 붙인 글, 다음 편을 예고하며 끝나는 글. 1기 마지막 회차인데 셋 다 다음을 가리킵니다.
성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 이번 글은 1기를 다시 읽는 마지막 큐레이션입니다.
7회차는 2024년 8월 24일이었습니다. 1기의 마지막 제출일이에요.
01
🔬 엔진 분석 Character Movement Component -0-
허해수 님
인디게임 제작기 연작을 마치고, 이번엔 엔진 안쪽을 뜯어보기 시작한 글입니다. 대상은 Character Movement Component(CMC) — 캐릭터의 입력을 받아 위치를 계산해주는 언리얼의 핵심 컴포넌트예요.
내용의 대부분이 "언제 이걸 커스텀해야 하는가"입니다. 싱글 플레이인지 멀티인지, 캐릭터가 사람 형태인지 아닌지에 따라 답이 갈립니다.
그리고 제목 끝에 붙은
-0-. 1편이 아니라 0편입니다.지금까지 이 연작은 "만드는 이야기"였습니다. 캐릭터를 움직이고, 충돌을 붙이고, 애니메이션을 넣고, 컷신을 만들었어요.
이번 글은 만들지 않습니다. 열어봅니다.
조금 더 들어가 보면
CMC는 언리얼이 기본으로 제공하는 이동 처리 장치입니다. 걷기·수영·날기 세 모드가 들어 있고, 캐릭터 이동에 필요한 계산을 전부 담당해요. 글의 표현으로는 "CMC 하나만으로 캐릭터 이동 로직을 전부 처리할 수 있다"입니다.
문제는 기본 제공 이동만으로는 부족할 때입니다. 벽타기나 행글라이딩 같은 걸 넣고 싶다면요.
글이 이 상황을 세 갈래로 나눕니다.
싱글 플레이나 협동 멀티라면 대체로 커스텀이 필요 없습니다.
달리기 정도는 최대 속도값만 올리면 되고, 블루프린트로 간단히 처리할 수 있다는 거예요. 다만 여기에도 선을 그어둡니다.
일반 멀티플레이 게임이라면 이야기가 달라집니다.
- Blueprint tweaks will not work - BP 소용 No No
- Custom CMC Required
왜 그럴까요? 멀티플레이에서는 서버가 최종 판단자여야 하기 때문입니다. 클라이언트가 "나 지금 여기 있어"라고 말하는 걸 그대로 믿으면 위치를 조작하는 핵이 통합니다. 그래서 서버가 계산하고 클라이언트는 예측만 해요.
동시에 네트워크 지연을 감춰야 합니다. 서버 응답을 기다렸다가 움직이면 조작이 굼떠 보이니까요. 그래서 클라이언트가 미리 계산해서 먼저 움직이고, 나중에 서버 결과와 다르면 슬쩍 보정합니다. 이 두 가지를 동시에 하는 게 어려워서, CMC를 쓰는 게 낫다는 겁니다.
글이 정리한 CMC의 장점 목록에 그 이유가 들어 있습니다.
- Root Motion Compatibility
- Replay System
- Remote Proxy Replication
- Nav Movement and ROV Avoidance - bots and ai
전부 혼자 만들면 몇 달씩 걸릴 것들입니다. 그래서 캡슐 콜리전이 조금 거슬려도 CMC를 쓰라는 결론이 나옵니다. 그마저도 "수치 조정해서 Sphere 콜리전으로 만들면 그만"이라고 답을 달아뒀어요.
🔮 클라이언트 예측이란 무엇인가
목록 중 Client Side Prediction은 조금 더 풀어볼 만합니다. 온라인 게임이 왜 그렇게 만들어졌는지를 설명해주거든요.
멀티플레이에서 서버가 최종 판단자라면, 원래는 이렇게 동작해야 합니다. 플레이어가 앞으로 가는 키를 누르면 → 서버에 알리고 → 서버가 위치를 계산하고 → 결과를 받아서 → 화면을 움직인다.
문제는 이 왕복에 시간이 걸린다는 겁니다. 서울에서 미국 서버까지면 왕복 200밀리초쯤 걸려요. 키를 누르고 0.2초 뒤에 캐릭터가 움직입니다. 조작이 굼떠서 게임이 안 됩니다.
그래서 클라이언트가 먼저 움직여버립니다. 서버에 알리는 것과 동시에, 서버가 어떻게 계산할지를 스스로 예측해서 화면을 즉시 갱신해요. 플레이어는 지연을 못 느낍니다.
나중에 서버 응답이 오면 두 값을 비교합니다. 같으면 아무 일도 없고, 다르면 서버 값이 맞으니 조용히 보정합니다. 벽에 부딪혔는데 통과한 것처럼 보였다면 그때 되돌아가는 거예요.
글이 적은 "hides latency"가 정확히 이겁니다. 지연을 없앤 게 아니라 감춘 것이죠.
그런데 이걸 직접 구현하려면 어렵습니다. 예측한 동작들을 순서대로 기록해두고, 서버 응답이 오면 그 시점부터 다시 계산해야 하거든요. CMC는 이 장치를 이미 갖고 있습니다. 커스텀할 때도 그 틀 위에서 하는 것과 맨바닥에서 만드는 것은 완전히 다릅니다.
"Extremely hard to network without CMC"라고 적은 이유가 여기 있습니다.
📝 원문에서는 이렇게 씁니다
인간형이 아닌 캐릭터를 다루는 대목의 문장이 재밌습니다.
Alternative: Derive from Pawn with custom solution - 오프라인 게임이면 원하는 걸로 해도 좋음.
- Extremely hard to network without CMC
3회차 큐레이션에서 이 연작의 2편을 소개하며 Pawn과 Character의 차이를 다뤘습니다. Pawn은 일반적인 조작 대상이고 Character는 사람 형태에 특화된 상속 클래스라고요.
두 달 뒤, 그 구분이 판단 기준으로 돌아왔습니다. 탱크나 동물이면 Character를 안 쓰고 Pawn에서 직접 만들 수도 있는데, 그러면 네트워크 처리가 극도로 어려워진다는 것. 개념을 정리해뒀기 때문에 이런 판단이 가능해진 겁니다.
큐레이터 노트
제목의 -0-이 이 글의 전부라고 생각합니다.
1편이 아니라 0편입니다. 본격적으로 뜯어보기 전에, 이걸 왜 뜯어봐야 하는지부터 정리한 글이라는 뜻이에요. 실제로 내용도 CMC의 내부 동작이 아니라 "어떤 경우에 이걸 건드려야 하는가"입니다.
그리고 이 0편은 실제로 1편으로 이어집니다. 1기가 끝난 뒤인 9월에 「CharacterMovementComponent 파이프라인 -1-」이 올라왔어요. 성장일지 활동이 끝났는데도 연재는 계속됐습니다.
허해수 님의 1기 일곱 편을 다시 보면 흐름이 분명합니다.
| 회차 | 글 |
|---|---|
| 1회차 | 플러그인으로 하늘 만들기 |
| 2~5회차 | 인디게임 제작기 1~4 (캐릭터 → 충돌 → 애니메이션 → 컷신) |
| 6회차 | 각도에 따른 힘 보정하기 |
| 7회차 | 엔진 분석 -0- |
엔진을 쓰다가, 엔진을 열어보는 데까지 갔습니다. 일곱 편이 이렇게 하나의 곡선을 그립니다.
02
🔒 낙관적 락을 알아보고 간단한 실습해보기
이범수 님
두 사람이 같은 데이터를 동시에 고치려 할 때 어떻게 막을 것인가 — 동시성 제어 이야기입니다. 그중 낙관적 락을 다룹니다.
개념과 동작 방식, 장점과 단점을 먼저 정리하고, 그다음이 이 글의 핵심이에요. 스레드 두 개를 동시에 띄워 실제로 충돌을 일으켜봅니다.
엔티티에
@Version 필드 하나를 붙이고, 테스트 코드를 짜고, 로그를 확인해서 왜 한쪽이 실패했는지를 쿼리 수준에서 짚었습니다.이범수 님은 1기 내내 개념 하나씩을 정리해오셨습니다. 4회차에서 영속성 컨텍스트를, 5회차에서 Spring AOP를 소개해드렸어요.
마지막 글에서 처음으로 실습이 붙었습니다.
조금 더 들어가 보면
문제 상황은 이렇습니다. 상품 가격을 두 사람이 동시에 고칩니다. A는 200원으로, B는 300원으로요.
둘 다 현재 가격을 읽고, 각자 새 값을 계산하고, 저장합니다. 그런데 저장 순서에 따라 나중에 저장한 쪽이 앞의 것을 덮어씁니다. A의 수정이 아예 없던 일이 되는 거죠. 흔히 갱신 손실(lost update)이라고 부릅니다.
이걸 막는 방법이 두 가지입니다.
비관적 락은 "충돌이 날 것"이라고 가정합니다. 데이터를 읽는 순간 자물쇠를 걸어서 다른 사람이 못 건드리게 해요. 확실하지만 기다리는 사람이 생깁니다.
낙관적 락은 반대입니다. "웬만하면 충돌 안 날 것"이라고 가정하고 자물쇠를 안 겁니다. 대신 저장할 때 확인해요. 글의 설명이 정확합니다.
- 수정: 데이터 수정 작업을 진행
- 버전 확인: 수정 작업을 커밋하기 전 현재 데이터의 버전을 처음 조회했을때 버전과 비교
- 충돌 감지: 버전이 일치하면 커밋, 일치하지 않으면 현재 트랜잭션을 롤백하거나 재시도
버전 번호 하나로 판정합니다. 내가 읽을 때 0이었는데 저장하려는 순간에도 0이면 아무도 안 건드린 것이고, 1로 바뀌어 있으면 누가 먼저 고친 겁니다.
🎫 조건부 UPDATE 한 줄로 끝난다
이게 어떻게 자물쇠 없이 가능한지는, 실제로 나가는 SQL을 떠올리면 분명해집니다.
보통의 수정은 이렇습니다.
update product set price = 300 where id = 1
낙관적 락이 걸리면 조건이 하나 붙습니다.
update product set price = 300, version = 1 where id = 1 and version = 0
and version = 0이 전부입니다. 누가 먼저 고쳐서 버전이 1이 돼 있으면, 이 조건에 맞는 행이 없으니 0건이 갱신됩니다.
여기서 중요한 건 데이터베이스가 이 판정을 원자적으로 해준다는 점입니다. 두 스레드가 동시에 같은 UPDATE를 날려도, 데이터베이스는 하나씩 순서대로 처리해요. 먼저 도착한 쪽이 버전을 1로 바꾸고, 나중 쪽은 조건이 안 맞아 0건이 됩니다.
그래서 따로 자물쇠를 걸 필요가 없습니다. 이미 데이터베이스가 갖고 있는 성질을 쓰는 거예요.
JPA는 갱신 건수가 0이면 "누가 먼저 고쳤구나"로 판단하고 예외를 던집니다. 글에서 두 번째 스레드가 받은 ObjectOptimisticLockingFailureException이 그것이고요.
자물쇠를 거는 대신 확인만 한다 — 낙관적이라는 이름이 여기서 나왔습니다. 대부분은 충돌이 안 날 거라고 보고, 나면 그때 처리하는 방식이니까요.
장단점도 나란히 적었습니다.
단점 — 충돌 가능성: 데이터를 자주 변경하는 경우, 충돌이 자주 발생할 수 있음 / 코드 복잡: 충돌을 처리하기 위한 코드가 늘어남
"자주 변경하는 경우"가 판단 기준입니다. 충돌이 드물면 낙관적 락이 빠르고, 잦으면 재시도만 반복하다 오히려 느려져요. 재고가 하나 남은 상품에 수천 명이 몰리는 상황에서는 낙관적 락이 답이 아닙니다.
📝 원문에서는 이렇게 확인합니다
이 글이 앞선 글들과 다른 건 직접 돌려봤다는 점입니다. 엔티티는 이렇게 단순합니다.
@Data
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
@Version
private Integer version; // 버전 필드를 통해 낙관적 락을 구현
}
@Version 한 줄이 전부입니다. 이걸 붙이면 JPA가 알아서 UPDATE 문에 WHERE version = ? 조건을 넣고, 성공하면 버전을 1 올립니다.
그리고 테스트에서 스레드 두 개를 동시에 띄웁니다. 하나는 200원, 하나는 300원으로요. 두 번째 스레드에는 예외가 날 것을 미리 단언해둡니다.
Thread thread2 = new Thread(() -> {
assertThrows(OptimisticLockingFailureException.class, () -> {
productService.updateProductPrice(product.getId(), 300.);
});
});
결과 분석이 정확합니다.
- 스레드 1이 먼저 업데이트를 완료하면서 version을 0에서 1로 변경
- 스레드 2가 동시에 실행되었지만, version이 1로 변경된 후에 version = 0으로 조건을 걸어 업데이트를 시도하기 때문에, 해당 조건이 맞지 않아 업데이트가 실패
그리고 최종 가격이 200원임을 로그로 확인합니다. "동작한다"가 아니라 "이 쿼리가 이 조건 때문에 0건을 갱신했고 그래서 예외가 났다"까지 갔어요.
큐레이터 노트
이 글을 1기 마지막 회차에 놓은 이유가 있습니다. 이범수 님의 일곱 편이 여기서 한 단계 올라가기 때문입니다.
앞선 여섯 편은 전부 "개념 정리"였습니다. Stream API, 영속성 컨텍스트, Docker MySQL 복제, AOP, MSA. 잘 정리된 글이었지만 읽고 옮겨 적은 것에 가까웠어요.
마지막 글에서 처음으로 코드를 짜고 돌려보고 로그를 봅니다. 그것도 동시성이라는, 글로만 읽으면 절대 감이 안 오는 주제로요. 스레드 두 개를 동시에 띄워서 일부러 충돌을 내는 건 읽어서는 배울 수 없는 감각입니다.
그리고 글의 첫 줄이 이렇습니다. "DB Lock에 속하는 낙관적 락을 알아보고" — 낙관적 락이 DB 락의 한 종류라는 것, 즉 짝이 되는 비관적 락이 남아 있다는 걸 알고 쓴 문장입니다.
테스트 코드를 짠 방식도 눈여겨볼 만합니다.
Thread thread2 = new Thread(() -> {
assertThrows(OptimisticLockingFailureException.class, () -> {
productService.updateProductPrice(product.getId(), 300.);
});
});
예외가 날 것을 미리 단언해뒀습니다. 보통 테스트는 "잘 되는지"를 확인하는데, 여기서는 "실패해야 하는 것이 실패하는지"를 확인해요.
이게 동시성 테스트의 어려운 지점입니다. 성공 케이스는 확인하기 쉽습니다. 값이 바뀌었는지 보면 되니까요. 그런데 막혔어야 할 것이 막혔는지는 명시적으로 확인하지 않으면 그냥 지나갑니다. 두 스레드가 모두 성공했는데도 최종 값만 보면 정상처럼 보이거든요.
만약 이 단언이 없었다면, 낙관적 락이 실제로 동작하지 않아도 테스트는 통과했을 겁니다. @Version을 붙였다는 사실과 그게 실제로 막아준다는 사실은 다르니까요.
그리고 마지막에 최종 가격을 로그로 찍어 확인합니다. 200원이 남았다는 건 먼저 성공한 쪽의 값이 살아남았다는 뜻이고, 나중 쪽이 조용히 덮어쓰지 않았다는 증거예요.
03
🛍️ 스프링부트 쇼핑몰 프로젝트5
진한솔 님
3회차에 소개해드린 쇼핑몰 연재의 다섯 번째이자 1기 마지막 편입니다. 이번 주제는 Thymeleaf — 서버에서 HTML을 만들어 내려주는 템플릿 엔진이에요.
컨트롤러를 만들고, Layout Dialect로 머리말과 꼬리말을 여러 페이지에 공유하는 구조를 잡고, 부트스트랩으로 실제 쇼핑몰 화면을 완성합니다.
그리고 마지막 문장에서 다음 편을 예고합니다.
1회차 큐레이션에서 이분의 426자짜리 다짐을 소개해드렸습니다. "원래 하나를 시작해도 끝까지 마치지 못하고 중간에 포기했었는데 이번엔 완주하겠다"는 글이었어요.
여기가 그 연재의 마지막 편입니다. 설치 화면 캡처로 시작한 연재가 다섯 편 만에 화면이 나오는 데까지 왔습니다.
조금 더 들어가 보면
Thymeleaf가 하는 일은 HTML에 데이터를 끼워 넣는 것입니다. 서버가 상품 목록을 조회한 다음, 그 값을 HTML 틀에 채워서 완성된 페이지를 내려보내요.
이 글에서 중요한 건 Layout Dialect 부분입니다.
쇼핑몰에는 페이지가 여러 장인데, 머리말(로고·메뉴·장바구니)과 꼬리말(회사 정보)은 전부 같습니다. 이걸 페이지마다 복사해두면 메뉴 하나 바꿀 때 전부 열어야 해요.
그래서 구조를 이렇게 나눕니다.
fragments에 조각을 두고, layouts에 그 조각들을 배치한 틀을 두고, 각 페이지는 그 틀을 가져다 씁니다. 5회차 큐레이션에서 다룬 "본체를 열지 않고 기능을 더하는 법"과 같은 발상이에요. 공통된 것을 밖으로 빼는 겁니다.
🖼️ 서버가 만드는 화면과 브라우저가 만드는 화면
Thymeleaf가 어떤 위치의 도구인지도 짚어둘 만합니다. 글의 정의가 간결해요.
템플릿 엔진은 HTML 틀에 데이터를 채워 완성된 페이지를 만드는 도구입니다. 서버가 상품 목록을 조회한 다음, 그 값을 틀에 끼워서 완성된 HTML을 브라우저에 보냅니다.
3·4회차에서 다룬 React와 정반대 방식이에요. React는 빈 껍데기를 먼저 보내고 브라우저가 데이터를 받아 화면을 그립니다. Thymeleaf는 서버가 다 그려서 보냅니다.
어느 쪽이 낫다기보다 성격이 다릅니다. 서버에서 그리면 첫 화면이 빨리 뜨고 검색엔진이 내용을 읽기 쉽습니다. 쇼핑몰처럼 상품이 검색에 걸려야 하는 서비스에 맞아요. 대신 화면 일부만 바꾸려 해도 페이지를 다시 받아야 합니다.
스프링 진영에서 Thymeleaf를 권장하는 이유 중 하나는 파일 자체가 그냥 HTML이라는 점입니다. 브라우저로 열면 데이터 없이도 화면 뼈대가 보여요. JSP처럼 서버를 거쳐야만 볼 수 있는 형식과 다릅니다. 디자이너와 개발자가 같은 파일을 두고 작업하기 편하죠.
그리고 진한솔 님이 부트스트랩을 붙인 것도 이 흐름에 맞습니다. 서버가 HTML을 통째로 내려보내니, 그 HTML에 이미 만들어진 CSS를 얹으면 바로 모양이 나옵니다.
📝 원문에서는 이렇게 말합니다
부트스트랩을 쓰는 이유를 적은 대목이 솔직합니다.
자기가 약한 부분을 명시하고 도구로 메웁니다. 디자인을 잘하려고 애쓰는 대신 이미 만들어진 것을 가져다 쓰고, 대신 자기 몫인 서버 쪽에 시간을 씁니다.
부트스트랩은 미리 만들어진 CSS 묶음입니다. 버튼·표·내비게이션바의 모양이 이미 정해져 있어서, 약속된 이름을 HTML에 붙이면 그 모양이 나와요. 색을 고르고 여백을 재고 화면 크기별 대응을 맞추는 일을 건너뛸 수 있습니다.
대가는 "다들 비슷해 보인다"는 것입니다. 부트스트랩으로 만든 화면은 한눈에 알아볼 수 있어요. 그래서 서비스의 개성을 만들어야 하는 단계에서는 결국 벗어나게 됩니다.
그런데 지금 단계에서는 그게 문제가 아닙니다. 화면이 있어야 다음으로 갈 수 있으니까요. 상품 목록이 눈에 보여야 페이징을 붙일지 검색을 넣을지 판단이 서고, 로그인 화면이 있어야 인증을 붙일 수 있습니다.
글이 "쇼핑몰에 맞게 수정을 해서 사용을 했을 때 나오는 최종 결과물"이라고 적은 것도 그래서예요. 가져다 쓰되 그대로 두지 않고 고쳤습니다.
그리고 마지막 두 문장.
다음 시간에는 코딩하는데 오랜시간을 잡아먹었던 Spring Security에 대해 포스팅해보겠습니다.
1기 마지막 제출인데 다음 편을 예고합니다. 그리고 "코딩하는데 오랜시간을 잡아먹었던"이라는 수식이 붙어 있어요. 이미 겪었고, 아직 안 썼다는 뜻입니다.
큐레이터 노트
진한솔 님의 1기 일곱 편을 이어보면 이렇습니다.
| 회차 | 글 |
|---|---|
| 1회차 | 다짐 (426자) |
| 2회차 | Java를 사용해보자 |
| 3~7회차 | 쇼핑몰 프로젝트 1~5 |
첫 글에서 적은 두 줄의 목표가 그대로 목차가 됐습니다. "강의나 교재로 공부한 분량을 정리한다"가 2회차, "작은 프로젝트의 시작과 끝을 정리한다"가 3~7회차예요.
1회차 큐레이션에서 저희는 이렇게 썼습니다. "이 글 하나만 떼어놓고 보면 평범한 다짐입니다. 그런데 뒤에 이어진 여섯 편이 이 글을 증거로 만듭니다."
여기가 그 여섯 번째입니다. 완주하겠다고 적은 사람이 완주했습니다.
그리고 이 연재가 한 번도 회차를 건너뛰지 않았다는 점도 짚어두고 싶습니다.
연재를 시작하는 사람은 많습니다. 어려운 건 이어가는 쪽이에요. 바쁜 주가 오고, 막히는 주가 오고, 한 번 빠지면 다음 회차에 "지난번에 못 올린 것부터"가 밀려서 더 부담이 됩니다. 그러다 조용히 끝납니다.
이 연재가 끊기지 않은 데에는 구조적인 이유도 있어 보입니다. 교재를 따라간다는 것. 다음에 무엇을 쓸지 고민할 필요가 없고, 진도가 곧 글감이 됩니다. 그리고 매 편 끝에 다음 주제를 적어두니, 2주 뒤에 앉았을 때 시작점이 이미 정해져 있어요.
완주는 의지만의 문제가 아니라 형태의 문제이기도 합니다. 완주하고 싶었던 사람이 완주하기 쉬운 형태를 골랐고, 실제로 끝냈습니다.
💡 세 편을 겹쳐 읽으며
1기의 마지막 회차입니다. 마무리하는 글이 나올 자리예요.
그런데 세 편 다 다음을 가리킵니다.
허해수 님은 제목에 -0-을 붙였습니다. 1편이 아니라 0편, 시작하기 전이라는 표시예요. 이범수 님은 "DB Lock에 속하는 낙관적 락"이라고 썼습니다. 짝이 되는 비관적 락이 남아 있다는 뜻입니다. 진한솔 님은 아예 대놓고 예고합니다. "다음 시간에는 Spring Security에 대해 포스팅해보겠습니다."
활동은 끝나는데 글은 안 끝납니다.
그리고 적어도 한 편은 실제로 이어졌습니다. 허해수 님의 「CharacterMovementComponent 파이프라인 -1-」이 그해 9월에 올라왔거든요. 1기 활동이 끝난 뒤였습니다.
또 하나 겹치는 게 있습니다. 세 편 모두 "언제 이걸 쓰고 언제 안 쓰는가"를 다룹니다.
허해수 님은 CMC 커스텀이 필요한 경우와 기본으로 충분한 경우를 상황별로 나눴습니다. 이범수 님은 낙관적 락이 데이터 변경이 잦으면 불리하다고 적었어요. 진한솔 님은 자기가 약한 영역(디자인)을 인정하고 부트스트랩에 맡겼습니다.
일곱 회차를 지나며 "할 줄 안다"에서 "언제 쓸지 안다"로 옮겨간 자리입니다. 첫 회차에 각자 무엇을 쓸지 정하는 것부터 시작했던 사람들이, 마지막 회차에는 무엇을 쓰지 말아야 하는지를 적고 있어요.
이 차이가 왜 중요한지는 검색으로 얻을 수 있는 것과 없는 것을 나눠보면 분명해집니다.
"낙관적 락 쓰는 법"은 검색하면 나옵니다. @Version을 붙이라는 답까지 5분이면 닿아요. 그런데 "우리 서비스에 낙관적 락이 맞나"는 안 나옵니다. 우리 데이터가 얼마나 자주 바뀌는지, 충돌이 났을 때 재시도해도 되는 성격인지를 아는 사람만 답할 수 있거든요.
CMC도 마찬가지입니다. 커스텀하는 방법은 문서에 있지만, "우리 게임은 커스텀이 필요한가"는 우리 게임이 싱글인지 멀티인지, 캐릭터가 사람 형태인지에 달려 있습니다.
그래서 세 글이 전부 조건을 나열하는 형태를 띱니다. 이런 경우엔 이렇게, 저런 경우엔 저렇게. 답 하나를 주는 대신 판단하는 법을 남긴 거예요.
이건 배우는 순서상 나중에 오는 것입니다. 무엇인지 모르면 언제 쓸지도 모르니까요. 일곱 편이 순서대로 쌓인 뒤에야 나오는 종류의 글입니다.
이번 회차에서 가져갈 것
1. 실습으로 확인하기.
이범수 님은 스레드 두 개를 동시에 띄워 일부러 충돌을 냈습니다. 동시성처럼 글로 읽어서는 감이 안 오는 주제는 작은 실습 하나가 열 번 읽기보다 낫습니다.
2. 0편을 쓰기.
어떤 주제를 파기 전에 "이걸 왜 파야 하는지"를 먼저 정리하는 것. 허해수 님의 -0-이 그 자리입니다. 이걸 해두면 중간에 길을 잃지 않아요.
3. 약한 부분은 도구에 맡기기.
진한솔 님은 퍼블리싱이 힘들다고 밝히고 부트스트랩을 썼습니다. 모든 걸 직접 하려다 프로젝트가 멈추는 것보다, 자기 몫을 정하고 나머지는 가져다 쓰는 편이 완주에 유리합니다.
4. 마지막 글에 다음을 적기.
세 분 다 그렇게 했고, 세 분 다 실제로 이어갔습니다. 끝맺음보다 예고가 오래갑니다.
🌱 1기 다시 읽기를 마치며
일곱 편에 걸쳐 아홉 분의 글을 새로 소개해드렸습니다. 정주영·구자용·진한솔·권준형·임준혁·형기상·허해수·이지은·이범수 님입니다.
이 작업을 시작한 이유는 단순합니다. 꾸준히 기록을 남기셨는데 아직 소개해드리지 못한 분들이 계셨어요. 좋은 글이 없어서가 아니라, 저희가 그때 다 읽지 못했기 때문입니다.
다시 읽으면서 알게 된 것이 있습니다. 한 편만 봐서는 안 보이는 게 있다는 것.
진한솔 님의 426자짜리 다짐은 그 자체로는 평범합니다. 그런데 뒤에 여섯 편이 붙으니 증거가 됐어요. 형기상 님의 강의 필기 네 편은 각각 보면 그냥 필기인데, 다섯 번째 글에서 자기 설계로 바뀝니다. 허해수 님의 제작기는 만드는 이야기로 시작해서 엔진을 여는 데까지 갑니다.
2주에 한 번씩 쌓인 기록이라야 보이는 것들입니다.
그리고 한 가지 더. 이번에 다시 읽으면서 원문이 열리지 않는 글도 만났습니다. 서버를 내리셨거나 블로그를 닫으신 경우예요. 6회차에서 말씀드린 한대천 님처럼, 남의 글에만 이름이 남은 분도 있습니다.
기록은 생각보다 쉽게 사라집니다. 그래서 읽을 수 있을 때 읽고 소개해두려고 합니다. 2기 다시 읽기로 이어가겠습니다.
좋은 글 남겨주신 허해수 님, 이범수 님, 진한솔 님께 감사드립니다. 그리고 아무 전례도 없던 첫 기수를 일곱 회차 동안 채워주신 1기 멤버 여러분 모두에게 감사드립니다! 😊
다음 큐레이션으로 다시 찾아오겠습니다! 🌱
- 에디터 · 성장일지 큐레이터 -
※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.