입력 받아 박스 움직이
import sys # 시스템 관련 모듈 (프로그램 종료 등) 사용
from PyQt6.QtWidgets import QApplication, QWidget # PyQt6의 기본 위젯 클래스 불러오기
from PyQt6.QtGui import QPainter, QColor # 그림 그리기(QPainter), 색상(QColor) 클래스
from PyQt6.QtCore import Qt # 키보드 입력 값 등을 사용하기 위한 Qt 상수
# 게임 윈도우 클래스 정의 (QWidget을 상속)
class GameWindow(QWidget):
def __init__(self):
super().__init__() # 부모 클래스(QWidget)의 생성자 호출
self.setWindowTitle("박스 이동 게임") # 윈도우 창 제목 설정
self.setGeometry(100, 100, 800, 600) # 윈도우 위치(x=100, y=100)와 크기(800x600) 설정
self.box_size = 100 # 박스의 크기(100x100)
self.x = 350 # 박스의 초기 X 좌표
self.y = 250 # 박스의 초기 Y 좌표
# 화면을 다시 그릴 때 호출되는 이벤트 함수
def paintEvent(self, event):
painter = QPainter(self) # QPainter 객체 생성 (현재 위젯에 그림을 그림)
painter.setBrush(QColor(100, 200, 255)) # 박스 색상 지정 (하늘색 계열)
painter.drawRect(self.x, self.y, self.box_size, self.box_size) # (x,y) 위치에 박스 그리기
# 키보드 입력이 발생할 때 호출되는 이벤트 함수
def keyPressEvent(self, event):
step = 20 # 박스가 이동하는 거리(픽셀 단위)
# W 키 또는 ↑ 방향키 → 위로 이동
if event.key() in (Qt.Key.Key_W, Qt.Key.Key_Up):
self.y -= step
# S 키 또는 ↓ 방향키 → 아래로 이동
elif event.key() in (Qt.Key.Key_S, Qt.Key.Key_Down):
self.y += step
# A 키 또는 ← 방향키 → 왼쪽으로 이동
elif event.key() in (Qt.Key.Key_A, Qt.Key.Key_Left):
self.x -= step
# D 키 또는 → 방향키 → 오른쪽으로 이동
elif event.key() in (Qt.Key.Key_D, Qt.Key.Key_Right):
self.x += step
# 경계 체크: 박스가 창 밖으로 나가지 않도록 제한
self.x = max(0, min(self.width() - self.box_size, self.x)) # X 좌표 제한
self.y = max(0, min(self.height() - self.box_size, self.y)) # Y 좌표 제한
self.update() # 화면을 다시 그려서 박스 위치 갱신
# 프로그램 실행을 위한 메인 함수
if __name__ == "__main__":
app = QApplication(sys.argv) # QApplication 객체 생성 (PyQt6 프로그램 필수 객체)
window = GameWindow() # GameWindow 객체 생성
window.show() # 윈도우 화면에 표시
sys.exit(app.exec()) # 이벤트 루프 실행 후 프로그램 종료
공과 충돌처리
좋아요! 스페이스바를 누르면 **플레이어 박스의 네 면(위/아래/왼/오른쪽)에서 작은 박스(총알)**가 동시에 발사되고,
- 원과 충돌하면: 해당 원의 색이 바뀌고 총알은 즉시 사라지며
- 충돌이 없으면: 발사 후 3초 뒤 자동 소멸,
- 화면 밖으로 나가면: 즉시 소멸
하도록 한 PyQt6 예제를 드립니다.
아래 코드는 이전 예제를 확장한 것으로, 60FPS 타이머로 총알과 충돌을 갱신합니다.
import sys # 시스템 관련 기능 사용 (프로그램 종료 등)
import random # 원 위치와 크기를 랜덤으로 생성하기 위해 사용
from PyQt6.QtWidgets import QApplication, QWidget # PyQt6 기본 윈도우 및 위젯 클래스
from PyQt6.QtGui import QPainter, QColor # QPainter: 그림 그리기 도구, QColor: 색상 표현
from PyQt6.QtCore import Qt # 키보드 입력 및 기타 상수 사용
# 사각형과 원의 충돌 여부를 판정하는 함수
def rect_circle_collides(rx, ry, rw, rh, cx, cy, r):
"""
사각형(좌상단 rx,ry, 크기 rw,rh)과
원(중심 cx,cy, 반지름 r)의 충돌 판정
"""
# 원의 중심에서 사각형 내부의 가장 가까운 x 좌표 찾기
closest_x = max(rx, min(cx, rx + rw))
# 원의 중심에서 사각형 내부의 가장 가까운 y 좌표 찾기
closest_y = max(ry, min(cy, ry + rh))
# 원 중심과 가장 가까운 점 사이의 거리 계산 (dx, dy)
dx = cx - closest_x
dy = cy - closest_y
# 거리^2 <= 반지름^2 이면 충돌
return (dx * dx + dy * dy) <= (r * r)
# 게임 화면 클래스 (QWidget을 상속)
class GameWindow(QWidget):
def __init__(self):
super().__init__() # 부모 클래스 생성자 호출
self.setWindowTitle("원 충돌 감지 데모") # 창 제목 설정
self.setGeometry(100, 100, 900, 600) # 윈도우 위치(x=100,y=100), 크기(900x600)
# 박스(플레이어) 설정
self.box_size = 100 # 박스 크기 100x100
self.x = (self.width() - self.box_size) // 2 # 박스 초기 X 좌표 (창 가운데)
self.y = (self.height() - self.box_size) // 2 # 박스 초기 Y 좌표 (창 가운데)
self.box_color_normal = QColor(100, 200, 255) # 기본 박스 색상 (파란색 계열)
self.box_color_hit = QColor(255, 80, 80) # 충돌 시 색상 (빨간색 계열)
self.box_color = self.box_color_normal # 현재 박스 색상 (초기: 기본 색)
# 원(장애물) 생성
random.seed(42) # 실행할 때마다 동일한 원이 나오도록 시드 고정
self.circles = [] # 원들의 리스트
for _ in range(5): # 원 5개 생성
r = random.randint(30, 60) # 원 반지름 30~60 사이 랜덤
cx = random.randint(r + 20, self.width() - r - 20) # 원 중심 X 좌표 (창 안쪽)
cy = random.randint(r + 20, self.height() - r - 20) # 원 중심 Y 좌표 (창 안쪽)
self.circles.append((cx, cy, r)) # 리스트에 원 추가 (cx, cy, r)
# 화면 그리기 이벤트 (윈도우가 갱신될 때 자동 호출됨)
def paintEvent(self, event):
p = QPainter(self) # QPainter 객체 생성 (이 위젯에 그림 그리기)
# 원 그리기
p.setPen(Qt.PenStyle.NoPen) # 외곽선 없음
for (cx, cy, r) in self.circles: # 리스트에 있는 원 하나씩 꺼내기
p.setBrush(QColor(180, 180, 180)) # 원 색상 (회색)
p.drawEllipse(cx - r, cy - r, r * 2, r * 2) # 원 그리기 (중심 cx,cy 반지름 r)
# 박스 그리기 (현재 색상에 따라)
p.setBrush(self.box_color) # 박스 색상 설정
p.drawRect(self.x, self.y, self.box_size, self.box_size) # 박스 그리기
# 키보드 입력 이벤트
def keyPressEvent(self, event):
step = 20 # 박스 이동 거리
key = event.key() # 눌린 키 코드 가져오기
# W 또는 ↑ → 위로 이동
if key in (Qt.Key.Key_W, Qt.Key.Key_Up):
self.y -= step
# S 또는 ↓ → 아래로 이동
elif key in (Qt.Key.Key_S, Qt.Key.Key_Down):
self.y += step
# A 또는 ← → 왼쪽으로 이동
elif key in (Qt.Key.Key_A, Qt.Key.Key_Left):
self.x -= step
# D 또는 → → 오른쪽으로 이동
elif key in (Qt.Key.Key_D, Qt.Key.Key_Right):
self.x += step
# 경계 체크 (박스가 창 밖으로 나가지 않게 제한)
self.x = max(0, min(self.width() - self.box_size, self.x))
self.y = max(0, min(self.height() - self.box_size, self.y))
# 충돌 여부 검사 (원 리스트 중 하나라도 충돌하면 hit=True)
hit = any(
rect_circle_collides(self.x, self.y, self.box_size, self.box_size, cx, cy, r)
for (cx, cy, r) in self.circles
)
# 충돌 여부에 따라 색상 변경
self.box_color = self.box_color_hit if hit else self.box_color_normal
self.update() # 화면 갱신 (paintEvent 다시 호출)
# 프로그램 실행부
if __name__ == "__main__":
app = QApplication(sys.argv) # QApplication 객체 생성 (PyQt6 앱 필수)
w = GameWindow() # GameWindow 객체 생성
w.show() # 윈도우 표시
sys.exit(app.exec()) # 이벤트 루프 실행, 종료 시 프로그램 종료
구현 포인트
- QTimer(16ms) 로 총알 이동/수명/충돌을 주기적으로 갱신 → 자연스러운 움직임.
- 총알은 dict(x,y,w,h,vx,vy,spawn)로 관리 → 수명 3초, 화면 밖 제거, 충돌 즉시 제거를 쉽게 처리.
- 충돌 시 원의 색은 랜덤 밝은 톤으로 변경. 필요하면 고정 색이나 단계적으로 바뀌도록 쉽게 수정할 수 있어요.
- 스페이스바를 누를 때마다 4발 동시 발사(네 면). 한 방향만 원하시면 해당 부분만 남기면 됩니다.
원 대신 다각형이나 벽과의 충돌, 혹은 쿨다운(발사 딜레이), 연사(키 유지 시 자동 발사) 기능도 붙여드릴 수 있어요. 원하는 플레이 감각 알려주시면 바로 반영해서 코드 업데이트해드릴게요!
import sys # 시스템 관련 기능 (프로그램 종료 등)
import random # 랜덤 값 생성 (원 위치, 색상)
import time # 시간 측정 (총알 수명 관리)
from PyQt6.QtWidgets import QApplication, QWidget # PyQt6의 기본 앱/창 위젯
from PyQt6.QtGui import QPainter, QColor # QPainter: 그림 그리기, QColor: 색상 표현
from PyQt6.QtCore import Qt, QTimer # Qt: 키보드 상수, QTimer: 주기적 이벤트 발생
# --- 유틸리티 함수 ---
# 사각형과 원의 충돌 판정 함수
def rect_circle_collides(rx, ry, rw, rh, cx, cy, r):
"""
rx,ry: 사각형 좌상단 좌표
rw,rh: 사각형 크기
cx,cy: 원의 중심 좌표
r: 원의 반지름
"""
# 원 중심(cx,cy)에서 사각형 영역 안의 가장 가까운 x 좌표 찾기
closest_x = max(rx, min(cx, rx + rw))
# 원 중심에서 사각형 영역 안의 가장 가까운 y 좌표 찾기
closest_y = max(ry, min(cy, ry + rh))
# 원 중심과 가장 가까운 점 사이 거리 구하기
dx = cx - closest_x
dy = cy - closest_y
# 거리 제곱 <= 반지름 제곱 → 충돌
return (dx * dx + dy * dy) <= (r * r)
# --- 메인 게임 윈도우 클래스 ---
class GameWindow(QWidget):
def __init__(self):
super().__init__() # QWidget 초기화
self.setWindowTitle("스페이스바 발사 & 충돌 데모") # 창 제목
self.setGeometry(100, 100, 900, 600) # 창 위치(x=100,y=100), 크기(900x600)
# 플레이어 박스 설정
self.box_size = 100 # 박스 크기 100x100
self.x = (self.width() - self.box_size) // 2 # 초기 X (화면 중앙)
self.y = (self.height() - self.box_size) // 2 # 초기 Y (화면 중앙)
self.box_color_normal = QColor(100, 200, 255) # 기본 색 (파란색)
self.box_color_hit = QColor(255, 80, 80) # 충돌 시 색 (빨간색)
self.box_color = self.box_color_normal # 현재 박스 색상
# 원(장애물) 설정
random.seed(42) # 동일 실행 시 동일한 원 배치
self.circles = [] # 원 정보를 담을 리스트
for _ in range(5): # 원 5개 생성
r = random.randint(30, 60) # 반지름 30~60
cx = random.randint(r + 20, self.width() - r - 20) # 화면 안에 배치
cy = random.randint(r + 20, self.height() - r - 20)
# 각 원은 dict 형태로 좌표, 반지름, 색상 저장
self.circles.append({
"cx": cx, "cy": cy, "r": r,
"color": QColor(180, 180, 180) # 초기 색은 회색
})
# 총알 리스트 (총알은 dict로 관리)
# {x,y,w,h,vx,vy,spawn}
self.bullets = []
# 주기적으로 update_state() 실행하는 타이머 (16ms ≈ 60FPS)
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_state) # 타이머 신호 → update_state 연결
self.timer.start(16) # 16ms 간격 실행
# --- 총알 발사 함수 ---
def fire_bullets_from_box(self):
now = time.time() # 총알 발사 시점 기록
bullet_size = 12 # 총알 크기
speed = 12 # 총알 속도
# 박스 네 면의 중앙 좌표 계산
left_x = self.x - bullet_size
right_x = self.x + self.box_size
top_y = self.y - bullet_size
bot_y = self.y + self.box_size
mid_x = self.x + self.box_size // 2 - bullet_size // 2
mid_y = self.y + self.box_size // 2 - bullet_size // 2
# 왼쪽으로 발사
self.bullets.append({
"x": left_x, "y": mid_y, "w": bullet_size, "h": bullet_size,
"vx": -speed, "vy": 0, "spawn": now
})
# 오른쪽으로 발사
self.bullets.append({
"x": right_x, "y": mid_y, "w": bullet_size, "h": bullet_size,
"vx": speed, "vy": 0, "spawn": now
})
# 위쪽으로 발사
self.bullets.append({
"x": mid_x, "y": top_y, "w": bullet_size, "h": bullet_size,
"vx": 0, "vy": -speed, "spawn": now
})
# 아래쪽으로 발사
self.bullets.append({
"x": mid_x, "y": bot_y, "w": bullet_size, "h": bullet_size,
"vx": 0, "vy": speed, "spawn": now
})
# --- 키보드 입력 이벤트 ---
def keyPressEvent(self, event):
step = 20 # 이동 거리
key = event.key() # 입력된 키
# 방향 이동
if key in (Qt.Key.Key_W, Qt.Key.Key_Up):
self.y -= step
elif key in (Qt.Key.Key_S, Qt.Key.Key_Down):
self.y += step
elif key in (Qt.Key.Key_A, Qt.Key.Key_Left):
self.x -= step
elif key in (Qt.Key.Key_D, Qt.Key.Key_Right):
self.x += step
# 스페이스바: 총알 발사
elif key == Qt.Key.Key_Space:
self.fire_bullets_from_box()
# 경계 체크 (화면 밖 못 나가게)
self.x = max(0, min(self.width() - self.box_size, self.x))
self.y = max(0, min(self.height() - self.box_size, self.y))
# 박스-원 충돌 → 박스 색상 변경
hit = any(
rect_circle_collides(self.x, self.y, self.box_size, self.box_size, c["cx"], c["cy"], c["r"])
for c in self.circles
)
self.box_color = self.box_color_hit if hit else self.box_color_normal
self.update() # 다시 그리기 요청
# --- 상태 갱신 (총알 이동, 충돌 검사, 제거 처리) ---
def update_state(self):
now = time.time() # 현재 시간
alive_bullets = [] # 살아남은 총알 저장
for b in self.bullets:
# 총알 이동 (vx,vy 만큼)
b["x"] += b["vx"]
b["y"] += b["vy"]
remove = False # 제거 여부 플래그
# (1) 3초 경과 시 제거
if (now - b["spawn"]) >= 3.0:
remove = True
# (2) 화면 밖으로 나가면 제거
if (b["x"] + b["w"] < 0 or b["x"] > self.width() or
b["y"] + b["h"] < 0 or b["y"] > self.height()):
remove = True
# (3) 원과 충돌하면 색 변경 후 제거
if not remove:
for c in self.circles:
if rect_circle_collides(b["x"], b["y"], b["w"], b["h"], c["cx"], c["cy"], c["r"]):
# 원 색상 랜덤 변경
c["color"] = QColor(
random.randint(100, 255),
random.randint(60, 255),
random.randint(60, 255)
)
remove = True
break
# 제거되지 않은 총알만 alive_bullets에 저장
if not remove:
alive_bullets.append(b)
# 현재 총알 리스트 갱신
self.bullets = alive_bullets
self.update() # 다시 그리기
# --- 화면 그리기 ---
def paintEvent(self, event):
p = QPainter(self) # QPainter 객체
# 원들 그리기
p.setPen(Qt.PenStyle.NoPen) # 외곽선 없음
for c in self.circles:
p.setBrush(c["color"]) # 현재 색상
p.drawEllipse(c["cx"] - c["r"], c["cy"] - c["r"], c["r"] * 2, c["r"] * 2)
# 플레이어 박스 그리기
p.setBrush(self.box_color)
p.drawRect(self.x, self.y, self.box_size, self.box_size)
# 총알 그리기
p.setBrush(QColor(30, 30, 30)) # 검정색 총알
for b in self.bullets:
p.drawRect(b["x"], b["y"], b["w"], b["h"])
# --- 프로그램 실행부 ---
if __name__ == "__main__":
app = QApplication(sys.argv) # PyQt 앱 객체 생성
w = GameWindow() # 게임 윈도우 생성
w.show() # 화면 표시
sys.exit(app.exec()) # 이벤트 루프 실행'Codyssey > PyQT6' 카테고리의 다른 글
| PyQT6 게임만들기 타당성 조사 (0) | 2025.09.16 |
|---|