게임은 또 시작되고..
import pygame
import random
pygame.init()
size = [400, 900]
screen = pygame.display.set_mode(size)
title = "pyGameExample"
pygame.display.set_caption(title)
clock = pygame.time.Clock()
color1 = (160, 250, 208)
color2 = (191, 234, 255)
black_color = (0,0,0)
white_color = (255,255,255)
k = 0
class Img_Object:
def init(self):
self.x = 0
self.y = 0
def add_Img(self, address):
if address[-3:] == "png":
self.img = pygame.image.load(address).convert_alpha()
else:
self.img = pygame.image.load(address)
def change_size(self, width, height):
self.img = pygame.transform.scale(self.img, (width, height))
self.width, self.height = self.img.get_size()
def show_Img(self):
screen.blit(self.img,(self.x, self.y))
def is_crash(a, b):
if (a.x - b.width <= b.x) and (b.x <= a.x + a.width):
if (a.y - b.height <= b.y) and (b.y <= a.y + a.height):
print("CRASH!!!!")
return True
else:
return False
else:
return False
hero = Img_Object()
hero.add_Img("C:/Users/201/Pictures/pyGame/hero1.png")
hero.change_size(80,80)
hero.x = round(size[0] / 2) - round(hero.width / 2)
hero.y = size[1] - hero.height - 70
hero.move = 5
left_move = False
right_move = False
space_on = False
missile_list = []
enemy_list = []
system_exit = 0
while system_exit == 0:
# 4-1. FPS(Frames per Sec) 설정
clock.tick(60)
# 4-2. 입력(키보드, 마우스)의 감지
for event in pygame.event.get():
if event.type == pygame.QUIT:
system_exit = 1
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
left_move = True
if event.key == pygame.K_RIGHT:
right_move = True
if event.key == pygame.K_SPACE:
space_on = True
k = 0
elif event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
left_move = False
if event.key == pygame.K_RIGHT:
right_move = False
if event.key == pygame.K_SPACE:
space_on = False
# 4-3. 입력, 시간에 따른 변화
if left_move == True:
hero.x -= hero.move
if hero.x <= 0:
hero.x = 0
elif right_move == True:
hero.x += hero.move
if hero.x >= size[0] - hero.width:
hero.x = size[0] - hero.width
if space_on == True and k % 6 == 0:
missile = Img_Object()
missile.add_Img("C:/Users/201/Pictures/pyGame/missile.png")
missile.change_size(25,50)
missile.x = hero.x + (hero.width / 2) - (missile.width / 2)
missile.y = hero.y - missile.height - 15
missile.move = 5
missile_list.append(missile)
print(missile_list)
k += 1
delete_missile_list = [] # 지울 애 메모할 공간
for i in range(len(missile_list)):
m = missile_list[i]
m.y -= m.move
if m.y <= -m.height:
delete_missile_list.append(m)
delete_missile_list.reverse()
for d in range(len(delete_missile_list)):
print(missile_list)
del missile_list[d]
print(missile_list)
print("미사일 소멸!")
if random.random() >= 0.98:
enemy = Img_Object()
enemy.add_Img("C:/Users/201/Pictures/pyGame/enemy.png")
enemy.change_size(40,40)
enemy.x = random.randrange(0 + round(hero.width/2), size[0] - enemy.width - round(hero.width/2))
enemy.y = 50
enemy.move = 4
enemy_list.append(enemy)
delete_enemy_list = []
for i in range(len(enemy_list)):
e = enemy_list[i]
e.y += e.move
if e.y >= size[1]:
delete_enemy_list.append(e)
delete_enemy_list.reverse()
for d in range(len(delete_enemy_list)):
print(enemy_list)
del enemy_list[d]
print(enemy_list)
print("적 소멸!")
delete_crash_missile_list = []
delete_crash_enemy_list = []
for i in range(len(missile_list)):
for j in range(len(enemy_list)):
m = missile_list[i]
e = enemy_list[j]
if is_crash(m, e) == True:
delete_crash_missile_list.append(m)
delete_crash_enemy_list.append(e)
delete_crash_missile_list = list(set(delete_crash_missile_list))
delete_crash_enemy_list = list(set(delete_crash_enemy_list))
delete_crash_missile_list.reverse()
delete_crash_enemy_list.reverse()
for d in range(len(delete_crash_missile_list)):
del missile_list[d]
print("적 충돌 소멸!")
for d in range(len(delete_crash_enemy_list)):
del enemy_list[d]
print("미사일 충돌 소멸!")
# 4-4. 전사작업(그리기)
screen.fill(black_color)
hero.show_Img()
for m in missile_list:
m.show_Img()
for e in enemy_list:
e.show_Img()
# 4-5. 업데이트
pygame.display.flip()
pygame.quit()
1) 문제 원인 분석 : 적을 지우는 로직의 코드가 리스트를 활용한 방식인데, 제거 로직은 아래와 같음
for d in range(len(delete_crash_enemy_list)):
del enemy_list[d]
*여기서 문제는 지울 적의 리스트는 잘 만들어졌지만 정작 지우는 방식은 리스트에서 있는 적들을 순차적으로 지원 것으로 미사일에 맞은 적을 제대로 지우지 못하고 있음
2) 조치 및 수정 :
#[수정] 미사일 맞은 적을 정확히 찾아 지우기 (엉뚱한 적이 죽는 현상 해결!)
for e in delete_crash_enemy_list:
if e in enemy_list:
enemy_list.remove(e)
print("미사일 맞은 적 소멸!")
1) 문제 원인 분석 : 위 적을 지우는 로직과 동일한 문제로, 기존 제거 로직은 아래와 같음
기존 코드의 del missile_list[d]는 맞춘 미사일이 아니라 맨 뒤에서 발사된 미사일을 지우는 버그가 있었습니다.
2) 조치 및 수정 :
#[수정] 충돌한 미사일 정확히 찾아 지우기
for m in delete_crash_missile_list:
if m in missile_list:
missile_list.remove(m)
print("적 충돌 미사일 소멸!")enemy_list.remove(e)
print("미사일 맞은 적 소멸!")
파이썬에서 리스트 안의 데이터를 지울 때 사용하는 del과 remove()는 아주 비슷해 보이지만, 지우는 방식(기준)이 완전히 다릅니다.
앞서 파이게임(Pygame) 코드에서 "엉뚱한 적이 죽는 버그"가 발생했던 이유도 바로 이 둘의 차이 때문이었습니다. 알기 쉽게 비교해 드릴게요!
1. del : "위치(순서)"를 기준으로 지우기del은 "리스트의 몇 번째 자리에 있는 녀석을 지워!"라고 명령할 때 사용합니다. 안에 무슨 데이터가 들어있는지는 관심이 없고, 오직 '방 번호(인덱스)'만 봅니다.사용법: del 리스트이름[인덱스]특징: 여러 개를 한 번에 지우는 것(슬라이싱)도 가능합니다.Python과일바구니 = ['사과', '바나나', '포도', '수박']
del 과일바구니[1]
print(과일바구니) # 결과: ['사과', '포도', '수박'] (바나나가 지워짐)
del 과일바구니[1:3] # 1번 방부터 2번 방까지 지워라!
🚨 주의할 점: 리스트에 없는 방 번호를 지우라고 하면 IndexError가 발생하며 프로그램이 튕깁니다.2. remove() : "이름(값)"을 기준으로 지우기remove()는 "방 번호는 모르겠고, 이름이 '사과'인 녀석을 찾아서 지워!"라고 명령할 때 사용합니다. 위치(인덱스)가 아니라 '실제 데이터 값'을 찾아서 지웁니다.사용법: 리스트이름.remove(지울 값)특징: 똑같은 값이 여러 개 있다면 가장 앞에 있는 1개만 지웁니다.Python과일바구니 = ['사과', '바나나', '포도', '바나나']
과일바구니.remove('바나나')
print(과일바구니) # 결과: ['사과', '포도', '바나나']
주의할 점: 바구니 안에 없는 값을 지우라고 하면 ValueError가 발생합니다. 그래서 보통 if '바나나' in 과일바구니: 처럼 값이 있는지 먼저 확인하고 지우는 것이 안전합니다.
한눈에 보는 핵심 차이점구분del 명령어remove() 함수지우는 기준위치 (Index)값 (Value)코드 형태del list[0]list.remove('사과')무엇을 모를 때 쓰나요?안에 무슨 값이 들었는지 모를 때몇 번째 방에 있는지 모를 때여러 개 삭제가능 (슬라이싱 [0:2])불가능 (한 번에 1개씩만 삭제)에러 종류방 번호가 틀리면 IndexError값이 없으면 ValueError 이전 게임 코드에서 버그가 났던 이유이전 코드에서는 화면 밖으로 나간 적을 지우려고 아래처럼 코드를 짰었습니다.
Pythonfor d in range(len(delete_enemy_list)):
del enemy_list[d] # <- 버그 발생 지점
여기서 d는 지워야 할 적 자체가 아니라 0, 1, 2라는 순서(방 번호)입니다. 만약 3번째 방에 있는 적이 총알에 맞았더라도, d가 0부터 시작하기 때문에 컴퓨터는 무조건 del enemy_list[0](맨 앞의 적)을 지워버리는 대참사가 일어난 것입니다.그래서 이를 "순서 상관없이 방금 총알에 맞은 바로 그 적 객체(e)를 찾아서 지워라!"라는 의미로 enemy_list.remove(e)로 바꾼 것입니다.
원의 방정식인 을 에 대해 풀면 이 되는데, 이 수학 공식을 파이썬 코드로 그대로 옮겨서 수많은 점을 찍어 원을 만드는 원리입니다.
초보자분들도 이해하기 쉽도록 코드 한 줄 한 줄에 자세한 주석을 달고, 아래에 전체적인 흐름을 설명해 드릴게요!
import matplotlib.pyplot as plt # 그래프를 그리기 위한 도구(라이브러리)를 가져옵니다.
import math # 수학 계산(루트 등)을 하기 위한 도구(라이브러리)를 가져옵니다.
x_list = [] # x 좌표 점들을 모아둘 빈 바구니(리스트)를 만듭니다.
y_list = [] # y 좌표 점들을 모아둘 빈 바구니(리스트)를 만듭니다.
# 💡 y값을 계산해주는 '함수(자판기)'를 만듭니다.
# 원의 방정식 (x^2 + y^2 = 1)을 y에 대해 정리한 식입니다.
def xsqrt(x):
y = math.sqrt(1 - (x ** 2)) # math.sqrt는 루트(√)를 씌워주는 명령어입니다.
return y # 계산된 y값을 돌려줍니다.
# 💡 -100부터 100까지 총 201번 반복합니다.
for i in range(-100, 101):
x = i / 100 # i를 100으로 나누어 x값을 -1.0부터 1.0까지 0.01 간격으로 촘촘하게 만듭니다.
# 하나의 x위치마다 원의 '위쪽 점'과 '아래쪽 점' 2개가 필요하므로 x를 두 번 넣습니다.
x_list.append(x)
x_list.append(x)
y = xsqrt(x) # 아까 만든 함수로 위쪽 점의 y좌표(양수)를 계산합니다.
y_list.append(y) # 위쪽 점(+y)을 바구니에 넣습니다.
y_list.append(-y) # 아래쪽 점(-y)을 바구니에 넣습니다.
# 바구니에 잘 담겼는지 화면에 글자로 출력해 봅니다.
print(x_list)
print(y_list)
# 💡 모아둔 x, y 좌표들을 바탕으로 그래프를 그립니다.
plt.scatter(x_list, y_list) # 1. 점(scatter)만 콕콕 찍어서 보여줍니다. (원의 윤곽선이 됨)
plt.plot(x_list, y_list) # 2. 찍힌 점들을 순서대로 선(plot)으로 연결합니다.
# (여기서는 위, 아래 점이 번갈아 찍혀서 원 안이 지그재그로 채워지게 됩니다.)
# plt.show() # (팁: 주피터 노트북이 아닌 일반 파이썬에서는 이 코드를 마지막에 써야 그래프 창이 뜹니다!)
import와 리스트)matplotlib을, 루트(√) 계산을 위해 math를 가져왔습니다. 그리고 계산된 점들을 담을 x_list와 y_list라는 빈 상자를 준비했습니다.def 함수)for 반복문)i / 100) 계산기에 넣습니다.x_list에는 같은 x를 두 번 넣고, y_list에는 +y와 -y를 각각 넣은 것입니다.scatter, plot)scatter는 점을 찍어 원의 테두리를 보여주고, plot은 그 점들을 선으로 잇습니다.이 코드를 실행하시면 콘솔 창에는 수많은 숫자가 출력되고, 그래프 화면에는 파란색 지그재그 선과 점으로 이루어진 멋진 원(Circle) 모양이 나타나게 됩니다!