콘텐츠로 이동

7차시: 내가 조종하는 3D 세계 - 키보드·마우스 입력으로 객체 제어하기

⏰ 50분 · keysdown() · scene.mouse · 실시간 입력 처리 · 난이도 ●●●○○

학습목표: 키보드와 마우스 입력을 활용하여 3D 객체를 실시간으로 제어하는 인터랙티브 프로그램을 만들 수 있다.

오늘의 질문: "게임에서 방향키를 누르면 캐릭터가 움직이는 건, 코드로 어떻게 만들어진 걸까요?"


🎮 키보드 입력 (keysdown)

while 루프 안에서 현재 눌린 키를 감지하여 객체를 실시간으로 움직이는 방법을 배웁니다

🖱️ 마우스 입력 (scene.mouse)

마우스 클릭 이벤트를 감지하고, 클릭한 위치에 새 객체를 생성하는 인터랙션을 구현합니다

🕹️ 실습: 공 조종기

화살표 키로 공을 상하좌우로 움직이고, 클릭으로 별을 찍는 미니 프로그램을 완성합니다

🚀 도전: 스페이스바 발사

스페이스바를 누르면 공이 발사되는 기능까지 결합하여 게임의 기초를 만들어봅니다


🔍 도입: 게임 속 입력의 비밀 (5분)

일상의 게임 경험과 연결하여 "사용자 입력"이 무엇인지 탐구합니다. 파이썬의 input()과 VPython 실시간 입력의 차이를 이해합니다.

🎮 핵심 개념 1: 키보드 입력 — keysdown() (12분)

keysdown() 함수의 원리를 배우고, 화살표 키로 공을 움직이는 코드를 단계별로 작성합니다.

🖱️ 핵심 개념 2: 마우스 입력 — scene.mouse (10분)

scene.mouse.getclick()으로 클릭 이벤트를 감지하고, 클릭 위치에 객체를 생성하는 코드를 작성합니다.

🛠️ 통합 실습: 키보드+마우스 결합 프로그램 (15분)

두 입력 방식을 하나의 프로그램에 통합합니다. 에러 경험과 디버깅을 포함합니다.

📝 평가 및 성찰 (8분)

퀴즈 풀이, 연습 문제 도전, 자기점검 체크리스트로 학습을 정리합니다.


🔍 도입: 게임 속 입력의 비밀

여러분, 게임을 할 때를 떠올려 보세요. 방향키를 누르면 캐릭터가 움직이고, 마우스를 클릭하면 공격을 하죠. 이런 동작은 어떻게 만들어지는 걸까요?

비유하자면 이렇습니다. TV 리모컨을 생각해 보세요. 버튼을 누르는 순간, TV가 그 신호를 감지해서 채널을 바꾸거나 볼륨을 조절합니다. 프로그래밍에서도 마찬가지입니다. 키보드와 마우스는 리모컨이고, 우리 프로그램이 TV 역할을 하는 것입니다.

🤔 파이썬의 input()과 뭐가 다를까?

여러분은 이미 파이썬에서 input() 함수를 사용해 봤을 것입니다. 하지만 input()에는 큰 한계가 있습니다.

비교 항목 파이썬 input() VPython keysdown()
입력 방식 텍스트를 타이핑하고 Enter를 누름 키를 누르는 순간 바로 감지
프로그램 동작 입력 대기 중 멈춤 (블로킹) 입력과 애니메이션이 동시에 작동
사용 사례 이름 입력, 메뉴 선택 게임 캐릭터 이동, 실시간 제어
반복 감지 매번 새로 호출해야 함 while 루프 안에서 계속 확인

핵심 차이는 실시간성입니다. 게임에서 방향키를 누르고 있는 동안 캐릭터가 계속 움직여야 하잖아요? input()은 이걸 할 수 없지만, VPython의 keysdown()은 가능합니다!

💡 input()은 "말 한마디 하고 기다리기"이고, keysdown()은 "귀를 계속 열어두고 듣기"입니다.

아래 다이어그램은 두 방식의 흐름 차이를 보여줍니다.

flowchart TD subgraph A["input 방식"] A1["코드 실행"] --> A2["input에서 멈춤"] A2 --> A3["사용자 타이핑"] A3 --> A4["Enter 누름"] A4 --> A5["다음 코드 실행"] end subgraph B["keysdown 방식"] B1["while 루프 시작"] --> B2["현재 눌린 키 확인"] B2 --> B3["키에 따라 동작"] B3 --> B4["애니메이션 업데이트"] B4 --> B1 end

🎮 핵심 개념 1: 키보드 입력 — keysdown()

📖 개념 이해

keysdown() 함수는 지금 이 순간 눌려 있는 키 목록을 반환합니다. 리스트처럼 여러 키를 동시에 감지할 수 있습니다.

정확한 정의를 보겠습니다:

  • keysdown(): 현재 눌려 있는 모든 키를 리스트 형태로 반환하는 VPython 내장 함수
  • 반환값: ['left'], ['left', 'up'] 등 — 동시에 여러 키가 눌릴 수 있음
  • 반드시 while 루프 안에서 사용해야 합니다 (매 프레임마다 확인해야 하니까요!)

자주 쓰는 키 이름을 정리해 두겠습니다:

keysdown()에서의 이름 설명
← 왼쪽 화살표 'left' 왼쪽 이동
→ 오른쪽 화살표 'right' 오른쪽 이동
↑ 위쪽 화살표 'up' 위쪽 이동
↓ 아래쪽 화살표 'down' 아래쪽 이동
스페이스바 ' ' (공백 문자) 점프, 발사 등
A, B, C 등 'a', 'b', 'c' 소문자로 인식

⚠️ 주의: 스페이스바는 'space'가 아니라 ' ' (공백 한 칸)입니다! 이건 많은 분이 헷갈려 하는 부분이에요.

🔨 따라하기: v1 — 가장 기본적인 키 감지

실행 환경: VPython 3.2 (Web VPython 또는 로컬 설치)

먼저 키를 누르면 어떤 키가 눌렸는지 확인만 해보겠습니다.

from vpython import *

# 빈 장면에 공 하나 만들기
ball = sphere(pos=vector(0, 0, 0), radius=0.5, color=color.cyan)

while True:
    rate(30)  # <- 1초에 30번 루프 반복 (애니메이션 속도 제어)
    keys = keysdown()  # <- 여기가 [키보드 입력 감지]
    if len(keys) > 0:
        print(keys)  # 눌린 키 목록을 출력

이 코드를 실행하고 키보드의 화살표 키를 눌러보세요. 아래와 같은 출력이 나옵니다:

['left']
['left']
['left', 'up']
['up']

위 코드의 6번째 줄 keys = keysdown()이 바로 키보드 입력 감지입니다. 매 루프마다 "지금 뭐가 눌려 있나?"를 확인하는 것이죠.

4번째 줄 rate(30)은 이전 차시에서 배운 애니메이션 속도 제어입니다. 이것이 없으면 루프가 너무 빨리 돌아서 컴퓨터에 부담이 됩니다.

🔨 따라하기: v2 — 키에 따라 공 움직이기

이제 단순 출력이 아니라, 실제로 공의 위치를 변경해 보겠습니다. v1에서 print(keys) 부분을 조건문으로 바꿔볼까요?

from vpython import *

ball = sphere(pos=vector(0, 0, 0), radius=0.5, color=color.cyan)

speed = 0.1  # 한 번에 움직일 거리

while True:
    rate(30)
    keys = keysdown()  # 현재 눌린 키 확인

    # 왼쪽 화살표가 눌려 있으면 x좌표 감소
    if 'left' in keys:  # <- 여기가 [특정 키 감지]
        ball.pos.x -= speed

    # 오른쪽 화살표가 눌려 있으면 x좌표 증가
    if 'right' in keys:
        ball.pos.x += speed

    # 위쪽 화살표 → y좌표 증가
    if 'up' in keys:
        ball.pos.y += speed

    # 아래쪽 화살표 → y좌표 감소
    if 'down' in keys:
        ball.pos.y -= speed

v1에서 뭐가 바뀌었나요? - print(keys) → 4개의 if문으로 교체 - speed 변수 추가 (움직임 크기를 한 곳에서 관리) - 각 화살표 키마다 ball.pos의 x 또는 y를 변경

위 코드의 12번째 줄 if 'left' in keys:가 바로 특정 키 감지 패턴입니다. in 연산자로 리스트 안에 해당 키가 있는지 확인하는 것이죠. 여러분이 이미 배운 리스트의 in 연산자를 그대로 활용하는 것입니다!

🎉 실행해서 화살표 키를 눌러보세요. 공이 상하좌우로 움직이나요? 대각선으로도 움직여 보세요! (두 키를 동시에 누르면 됩니다)

🔨 따라하기: v3 — 바닥과 벽 추가로 장면 꾸미기

공만 덩그러니 있으면 심심하죠? 바닥과 경계를 추가하고, 공이 경계 밖으로 나가지 않도록 해봅시다.

from vpython import *

# 장면 설정
scene.background = color.white
scene.width = 600
scene.height = 400

# 바닥 만들기 (납작한 상자)
ground = box(pos=vector(0, -3, 0), size=vector(12, 0.2, 6),
             color=color.green)

# 조종할 공
ball = sphere(pos=vector(0, 0, 0), radius=0.5, color=color.cyan)

speed = 0.1
boundary = 5  # 공이 이동할 수 있는 최대 범위

while True:
    rate(30)
    keys = keysdown()

    if 'left' in keys:
        ball.pos.x -= speed
    if 'right' in keys:
        ball.pos.x += speed
    if 'up' in keys:
        ball.pos.y += speed
    if 'down' in keys:
        ball.pos.y -= speed

    # 경계 제한: 공이 범위를 벗어나지 않도록  # <- 여기가 [경계 검사]
    if ball.pos.x > boundary:
        ball.pos.x = boundary
    if ball.pos.x < -boundary:
        ball.pos.x = -boundary
    if ball.pos.y > boundary:
        ball.pos.y = boundary
    if ball.pos.y < -boundary + ball.radius:
        ball.pos.y = -boundary + ball.radius

v2에서 뭐가 바뀌었나요? - 장면 배경색과 크기 설정 추가 - ground 바닥 상자 추가 - boundary 변수와 경계 검사 코드 추가 (31~38번째 줄)

31번째 줄부터 시작하는 경계 검사 코드가 바로 경계 검사(Boundary Check) 패턴입니다. 게임에서 캐릭터가 화면 밖으로 나가지 않도록 하는 기본 기법이에요.

🐛 에러 경험: 흔한 실수

자, 여기서 많은 학습자가 빠지는 함정이 있습니다. 아래 코드를 실행하면 어떤 문제가 생길까요?

from vpython import *

ball = sphere(pos=vector(0, 0, 0), radius=0.5, color=color.cyan)
speed = 0.1

while True:
    rate(30)
    keys = keysdown()

    if 'left' in keys:
        ball.pos.x -= speed
    elif 'right' in keys:       # <- elif 사용!
        ball.pos.x += speed
    elif 'up' in keys:          # <- elif 사용!
        ball.pos.y += speed
    elif 'down' in keys:        # <- elif 사용!
        ball.pos.y -= speed

🤔 질문: 이 코드를 실행하고 왼쪽 키와 위쪽 키를 동시에 누르면 어떻게 될까요?

정답 확인 **문제**: 공이 **왼쪽으로만** 움직이고, 위로는 움직이지 않습니다! **원인**: `elif`는 첫 번째로 참인 조건만 실행하고 나머지는 건너뜁니다. `'left' in keys`가 참이면 `elif 'up' in keys`는 아예 검사하지 않아요. **해결**: 각 키를 **독립적인 `if`문**으로 작성해야 합니다. 그래야 동시에 여러 키를 누를 때 각각 처리됩니다.
# ✅ 올바른 코드: 모두 if로 작성
if 'left' in keys:
    ball.pos.x -= speed
if 'right' in keys:      # elif가 아니라 if!
    ball.pos.x += speed
if 'up' in keys:          # elif가 아니라 if!
    ball.pos.y += speed
if 'down' in keys:        # elif가 아니라 if!
    ball.pos.y -= speed
**핵심**: 동시 입력을 허용하려면 `if`-`if`-`if`, 하나만 선택하게 하려면 `if`-`elif`-`elif`를 사용합니다!

이것은 정말 중요한 패턴입니다. 아래 표로 정리해 두겠습니다:

패턴 코드 구조 동시 입력 사용 상황
독립 조건 if / if / if ✅ 가능 방향키 이동 (대각선 허용)
배타 조건 if / elif / elif ❌ 불가 메뉴 선택 (하나만 실행)

🖱️ 핵심 개념 2: 마우스 입력 — scene.mouse

📖 개념 이해

키보드만으로는 뭔가 부족하죠? 이번에는 마우스 클릭을 감지해 봅시다.

비유를 하나 들어볼게요. 키보드 입력이 "리모컨 버튼 누르기"라면, 마우스 클릭은 "화면 위 원하는 곳을 손가락으로 터치하기"입니다. 단순히 "뭔가를 했다"는 신호뿐 아니라, "어디를" 했는지 위치 정보까지 알려준다는 점이 다릅니다.

VPython에서 마우스 입력을 처리하는 핵심 도구:

속성/함수 설명 반환값
scene.mouse.getclick() 클릭할 때까지 대기, 클릭 정보 반환 클릭 이벤트 객체
scene.mouse.pos 마우스 현재 위치 (3D 좌표) vector(x, y, z)
scene.waitfor('click') 클릭을 기다리는 또 다른 방법 이벤트 객체

⚠️ 중요한 차이: getclick()keysdown()은 성격이 다릅니다! keysdown()은 "지금 눌려있는 키"를 확인하지만, getclick()은 "클릭이 일어날 때까지 기다렸다가" 정보를 줍니다.

아래 다이어그램으로 흐름을 비교해 보겠습니다:

flowchart LR subgraph K["keysdown 흐름"] K1["매 프레임마다<br/>키 확인"] --> K2{"키 눌림?"} K2 -- 예 --> K3["동작 실행"] K2 -- 아니오 --> K4["다음 프레임"] K3 --> K4 end subgraph M["mouse 이벤트 흐름"] M1["이벤트<br/>등록"] --> M2["클릭 발생<br/>대기"] M2 --> M3["클릭 위치<br/>가져오기"] M3 --> M4["동작 실행"] end

🔨 따라하기: v1 — 클릭한 곳에 공 만들기

가장 기본적인 마우스 클릭 처리를 해봅시다. 클릭할 때마다 그 위치에 빨간 공이 생깁니다.

from vpython import *

scene.background = color.white

# 안내 문구 (3D 장면 위에 표시)
scene.caption = "화면을 클릭하면 빨간 공이 생깁니다!"

while True:
    rate(30)
    # 클릭 이벤트 확인  # <- 여기가 [마우스 이벤트 감지]
    evt = scene.mouse.getclick()
    # 클릭한 위치 가져오기  # <- 여기가 [클릭 위치 추출]
    click_pos = evt.pos
    # 클릭 위치에 빨간 공 생성
    sphere(pos=click_pos, radius=0.3, color=color.red)

실행하고 3D 장면의 여러 곳을 클릭해 보세요. 클릭한 곳마다 빨간 공이 나타납니다!

위 코드의 11번째 줄 evt = scene.mouse.getclick()이 바로 마우스 이벤트 감지입니다. 이 줄에서 프로그램은 클릭이 일어날 때까지 잠시 기다립니다.

12번째 줄 click_pos = evt.pos클릭 위치 추출입니다. evt.posvector(x, y, z) 형태로 3D 좌표를 알려줍니다.

🔨 따라하기: v2 — 클릭할 때마다 다른 색상의 별 찍기

단순 빨간 공 대신, 클릭할 때마다 랜덤 색상으로 별(작은 구)을 찍어봅시다.