-
파이썬 활용 05 - 오락실 게임 'PANG' 만들기 (위치/무기/공/이벤트)파이썬/파이썬 활용 2021. 11. 2. 20:37
유투버 '나도코딩'님 강의 참조
01 캐릭터 위치
# 캐릭터 움직임 character_to_x = 0 #좌우니까 x만 # 캐릭터 이동 속도 character_speed = 5 #2. 이벤트 루프 running = True #게임 진행 여부 while running: dt = clock.tick(60) # 게임 화면의 초당 프레임 수를 설정 #3. 이벤트 처리 for event in pygame.event.get(): #event.get()을 통해 사용자의 움직임을 받음 if event.type == pygame.QUIT: #창의 x버튼 running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: character_to_x -= character_speed elif event.key == pygame.K_RIGHT: character_to_x += character_speed elif event.key == pygame.K_SPACE: pass if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT: character_to_x = 0 #4. 게임 캐릭터 위치 (가로/세로 경계값) character_x_pos += character_to_x #캐릭터의 위치는 키보드로 움직인 만큼의 위치 if character_x_pos < 0: character_x_pos = 0 elif character_x_pos > screen_width - character_width: character_x_pos = screen_width - character_width
굿 02 무기 만들기
# 무기 weapon = pygame.image.load(os.path.join(image_path, "weapon.png")) weapon_size = weapon.get_rect().size weapon_width = weapon_size[0] # 무기 발수 (한번에 여러발 가능) weapons = [] #무기 이동 속도 weapon_speed = 10
weapon_x_pos는 x좌표로써 캐릭터의 가운데부분에서 나가야 하므로 캐릭의 총 가로에다 캐릭터의 반 - 무기 반을 해서 붙이면 가운데가 나옴. weapon_y_pos는 무기의 높이로 캐릭터의 높의 제일 위에서 시작 # 무기 위치 weapons = [[w[0], w[1] - weapon_speed] for w in weapons] #무기 위치를 올림 # 무기가 천장에 닿으면 사라짐 weapons = [[w[0], w[1]] for w in weapons if w[1] > 0]
- 무기를 쏘면 무기가 위로 쭉 올라가야하므로 weapons에 담긴 무기의 x,y좌표를 들고와서 x는 그대로 가지만 y의 높이는 스피드에 따라 줄어들면서 위로 올라 가게 됨
- y가 천장에 닿게되면 사라지게 됨
배경을 그리고 무기부터 그려야 스테이지와 캐릭터가 그 후에 무기 위에 그려져서 자연스럽게 나간다 03 공 만들기
# 공 (4개를 각각 따로 처리) ball_images = [ pygame.image.load(os.path.join(image_path, "balloon1.png")), pygame.image.load(os.path.join(image_path, "balloon2.png")), pygame.image.load(os.path.join(image_path, "balloon3.png")), pygame.image.load(os.path.join(image_path, "balloon4.png")), ] # 공 속도 (크기에 따라 속도가 각기 다름) ball_speed_y = [-18, -15, -12, -9] #index 0(공1),1(공2),2(공3),3(공4) # 공 정보 balls = [] #최초 발생 큰공 추가 balls.append({ "pos_x" : 50, #공 x좌표 "pos_y" : 50, #공 y좌표 "img_idx" : 0, # 공의 이미지 인덱스 "to_x" : 3, #공 x축 이동 방향, -3 = 왼쪽, +3 = 오른쪽 "to_y" : -6, #공 y축 이동 방향 "init_speed_y" : ball_speed_y[0] #y 최초 속도 })
- 공 튕기기
#2. 이벤트 루프 running = True #게임 진행 여부 while running: dt = clock.tick(60) # 게임 화면의 초당 프레임 수를 설정 .... #공 위치 정의 for ball_idx, ball_val in enumerate(balls): ball_pos_x = ball_val["pos_x"] ball_pos_y = ball_val["pos_y"] ball_img_idx = ball_val["img_idx"] ball_size = ball_images[ball_img_idx].get_rect().size ball_width = ball_size[0] ball_height = ball_size[1] #공이 가로 화면 끝에 맞으면 다시 돌아 오도록 if ball_pos_x < 0 or ball_pos_x > screen_width - ball_width: ball_val["to_x"] = ball_val["to_x"] * -1 #공이 세로(스테이지에 닿을 때)에 맞으면 다시 튕기도록 if ball_pos_y >= screen_height - stage_height - ball_height: ball_val["to_y"] = ball_val["init_speed_y"] else: #공이 다시 튕긴 후 속도가 증가 ball_val["to_y"] += 0.5 ball_val["pos_x"] += ball_val["to_x"] ball_val["pos_y"] += ball_val["to_y"]
- for ball_idx, ball_val in enumerate(balls): 를 하게되면 인덱스 위치와 인덱스의 값을 받아온다
- 포문을 돌면서 공의 x,y 좌표와 이미지를 'balls.append'를 통해서 선언했던 변수들로 채워준다
- 공의 size/width/height은 이전의 연습과 똑같이 사이즈를 구하는 용도
- 공의 x축이 가로 맨끝의 '0'보다 작거나 (즉 화면을 완전히 넘어가거나) 화면(640) - 공(160) 의 크기보다 작아지면 공의 위치를 다시 반대방향으로 바꿈 (즉 'to_x' = 3 * 1 ... 로 튕겨서 반대로 감)
- 세로의 개념도 똑같음. 공이 이미 최초값이 50인데 이 y축(50)이 화면(480) - 스테이지(50) - 공(160)보다 작아지면(공이 스테이지에 닿는 위치) 처음 속도 '-18'의 속도로 튕기게 됨
- 공이 스테이지에 안닿으면 'to_y = -6'에서 +0.5 하면서 점점 공이 땅으로 빨리 떨어지게 됨(최초 속도가 '-' 이므로 +0.5씩 하게되면 포물선을 그리면서 떨어지게 됨)
- 마지막으로 x와y축위치에서 'to_x'/'to_y'를 각각 더해주면 "+/-" 가 계속 되면서 좌우로 움직이게 됨
#6. 화면에 그리기 (.blit) screen.blit(background, (0,0)) for weapon_x_pos, weapon_y_pos in weapons: screen.blit(weapon, (weapon_x_pos, weapon_y_pos)) for idx, val in enumerate(balls): ball_pos_x = val["pos_x"] ball_pos_y = val["pos_y"] ball_img_idx = val["img_idx"] screen.blit(ball_images[ball_img_idx], (ball_pos_x, ball_pos_y)) ....
- 그리는 위치는 백그라운드→ 무기 → 다음 순으로 그린다. (스테이지/캐릭터와 겹치지 않기 위해)
- 그려지는 위치는 최초의 선언한 값들로 x,y,이미지를 채우면 된다.
(screen.blit(이미지이름, 그려지길 원하는 좌표값)
04 충돌
1) 공과 캐릭터가 충돌 시 게임 종료
#5. 충돌 처리 # 캐릭터 rect 정보 업데이트 character_rect = character.get_rect() character_rect.left = character_x_pos character_rect.top = character_y_pos for ball_idx, ball_val in enumerate(balls): ball_pos_x = ball_val["pos_x"] ball_pos_y = ball_val["pos_y"] ball_img_idx = ball_val["img_idx"] #공 rect 정보 업데이트 ball_rect = ball_images[ball_img_idx].get_rect() ball_rect.left = ball_pos_x ball_rect.top = ball_pos_y #충돌 체크 if character_rect.colliderect(ball_rect): running = False break
2) 공과 무기 충돌 처리
#사라질 무기, 공 정보 weapon_to_remove = -1 ball_to_remove = -1 #2. 이벤트 루프 running = True #게임 진행 여부 while running: .... #5. 충돌 처리 # 캐릭터 rect 정보 업데이트 character_rect = character.get_rect() character_rect.left = character_x_pos character_rect.top = character_y_pos for ball_idx, ball_val in enumerate(balls): ball_pos_x = ball_val["pos_x"] ball_pos_y = ball_val["pos_y"] ball_img_idx = ball_val["img_idx"] #공 rect 정보 업데이트 ball_rect = ball_images[ball_img_idx].get_rect() ball_rect.left = ball_pos_x ball_rect.top = ball_pos_y #충돌 체크 if character_rect.colliderect(ball_rect): running = False break #공과 무기들 충돌 for weapon_idx, weapon_val in enumerate(weapons): weapon_pos_x = weapon_val[0] weapon_pos_y = weapon_val[1] #무기 rect 정보 업데이트 weapon_rect = weapon.get_rect() weapon_rect.left = weapon_pos_x weapon_rect.top = weapon_pos_y #충돌 체크 if weapon_rect.colliderect(ball_rect): weapon_to_remove = weapon_idx ball_to_remove = ball_idx break if ball_to_remove > -1: del balls[ball_to_remove] ball_to_remove = -1 if weapon_to_remove > -1: del weapons[weapon_to_remove] weapon_to_remove = -1
- 공과 캐릭터의 rect()정보를 불러 온 뒤 캐릭과 공이 충돌하면 게임 종료
- 무기의 rect()정보를 불러오고 무기와 공이 닿으면 공과 무기의 제거 변수 값이 -1에서 인덱스번호로 바뀜
- 바뀐 변수가 인덱스의 위치일테니 그 값을 가지고 if문을 써서 제거변수값이 -1에서 인덱스값으로 바뀌었고, 당연히 -1보다 크니 그 인덱스값을 리스트에서 제거를 하면 됨. 그리고 다시 -1로 복귀 시키고 다시 또 받아서 똑같은 처리.
3) 공 쪼개기
- 위의 연습은 공과 무기가 충돌하면 공이 단순 삭제되는 처리이다. 이제는 쪼개는 처리를 해보자
- 일단 제일 큰공은 사라지는게 맞고 그 이후 작은공들이 순서대로 나와서 쪼개지면 되는 것!
#공과 무기들 충돌 for weapon_idx, weapon_val in enumerate(weapons): weapon_pos_x = weapon_val[0] weapon_pos_y = weapon_val[1] #무기 rect 정보 업데이트 weapon_rect = weapon.get_rect() weapon_rect.left = weapon_pos_x weapon_rect.top = weapon_pos_y #충돌 체크 if weapon_rect.colliderect(ball_rect): weapon_to_remove = weapon_idx ball_to_remove = ball_idx #가장 작은 공이 아니면 다음 단계의 공으로 나눔 if ball_img_idx < 3: #현재 공 크기 정보 ball_width = ball_rect.size[0] ball_height = ball_rect.size[1] #나눠진 공 정보 (인덱스가 커질수록 공이 작아지기때문에 +1) samll_ball_rect = ball_images[ball_img_idx+1].get_rect() samll_ball_width = samll_ball_rect.size[0] samll_ball_height = samll_ball_rect.size[1] #왼쪽으로 쪼개지는 공 balls.append({ "pos_x" : ball_pos_x + (ball_width / 2) - (samll_ball_width / 2), #공 x좌표 "pos_y" : ball_pos_y + (ball_height / 2) - (samll_ball_height / 2), #공 y좌표 "img_idx" : ball_img_idx + 1, # 공의 이미지 인덱스 "to_x" : -3, #공 x축 이동 방향, -3 = 왼쪽, +3 = 오른쪽 "to_y" : -6, #공 y축 이동 방향 "init_speed_y" : ball_speed_y[ball_img_idx + 1] #y 최초 속도 }) #오른쪽으로 쪼개지는 공 balls.append({ "pos_x" : ball_pos_x + (ball_width / 2) - (samll_ball_width / 2), #공 x좌표 "pos_y" : ball_pos_y + (ball_height / 2) - (samll_ball_height / 2), #공 y좌표 "img_idx" : ball_img_idx + 1, # 공의 이미지 인덱스 "to_x" : +3, #공 x축 이동 방향, -3 = 왼쪽, +3 = 오른쪽 "to_y" : -6, #공 y축 이동 방향 "init_speed_y" : ball_speed_y[ball_img_idx + 1] #y 최초 속도 }) break
- 공과 무기가 충돌 한 후 제일 작은 공이 아니면 현재 공 크기와 그 다음으로 작은 공 rect()정보를 업데이트 함.
(다음 공들은 계속해서 인덱스에 +1을 하면 자연스럽게 리스트에서 꺼내옴) - balls[]에서 최초 공 이후 append로 쪼개지는 공들을 넣으면 됨. 왼쪽/오른쪽으로 각각 쪼개지는 공을 넣는데 쪼개질때의 좌표는 "현재 공의 좌표 + (현재 공의 크기 반) - (다음 작은 공의 크기 반)" 으로 하면 충돌했을때의 위치에서 양쪽으로 쪼개져서 작은공들이 나옴
(왼쪽 오른쪽 두개를 추가하니 양쪽으로 하나씩 떨어지는 것) - 공의 이미지는 인덱스를 추가하면 자연스럽게 들고오게되고 이동방향은 왼쪽 오른쪽 -/+로 선택하고, 속도도 똑같이 인덱스에서 기본으로 세팅되어있기때문에 인덱스꺼를 들고오면 저절로 적용이 됨
05 게임 오버
- 초기에 설정했던 조건을 충족해서 게임진행이 되도록 해야한다
1) 모든 공을 없애면 종료(성공) 2) 캐릭터는 공에 닿으면 게임 종료(실패) 3) 시간 제한 99초 초과시 게임 종료(실패)
- 시간 추가
- 조건에 따른 결과 처리
- 시간 초과 시
- 모든 공을 없앴을 때
- 공과 캐릭이 충돌하면 이미 게임이 종료되도록 세팅이 되어있기때문에 초기 값 game_result = "Game Over" 가 뜬다
→ 모든 이미지를 바꾸면!
※ 이미지같은경우는 png파일이 제일 좋다. 배경이 투명이기때문에 바로 가져다 쓸 수 있다.
이미지 배경삭제 혹은 png파일 사이즈 조절등은 구글에 그대로 구글링하면 많은 무료 사이트들이 나온다!
06 버그 수정
1) 큰공을 맞혔는데도 큰공 + 왼쪽 공이 남는 버그
- 이 현상은 현재 무기와 공이 충돌시에 포문을 완벽하게 빠져나가야하는데 빠져 나가지 못해서 생기는 현상
첫 break는 이중 포문만 나가게 되서 다시 첫 포문이 돌기때문이다. 이를 트릭을 써서 첫 break를 안타면 계속 돌고 타게되면 break -> 다음 break까지 타게 되는 것 2) 방향키를 빠르게 바꾸면 물리는 현상
# 캐릭터 움직임 character_to_x_LEFT = 0 #좌우니까 x만 character_to_x_RIGHT = 0 #2. 이벤트 루프 running = True #게임 진행 여부 while running: dt = clock.tick(55) # 게임 화면의 초당 프레임 수를 설정 #3. 이벤트 처리 for event in pygame.event.get(): #event.get()을 통해 사용자의 움직임을 받음 if event.type == pygame.QUIT: #창의 x버튼 running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: character_to_x_LEFT -= character_speed elif event.key == pygame.K_RIGHT: character_to_x_RIGHT += character_speed elif event.key == pygame.K_SPACE: # 무기 발사 weapon_x_pos = character_x_pos + (character_width / 2) - (weapon_width / 2) weapon_y_pos = character_y_pos weapons.append([weapon_x_pos, weapon_y_pos]) if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT: character_to_x_LEFT = 0 elif event.key == pygame.K_RIGHT: character_to_x_RIGHT = 0 #4. 게임 캐릭터 위치 (가로/세로 경계값) character_x_pos += character_to_x_LEFT + character_to_x_RIGHT #캐릭터의 위치는 키보드로 움직인 만큼의 위치
- 왼쪽 오른쪽 변수를 따로 만들어서 각각 처리해야 물리지 않고 처리 된다!
'파이썬 > 파이썬 활용' 카테고리의 다른 글
파이썬 활용 07 - 1 -웹 스크래핑 (BeautifulSoup4 - 네이버웹툰) (0) 2021.11.04 파이썬 활용 06 - 웹 스크래핑 (기본개념/정규식/User Agent) (0) 2021.11.03 파이썬 활용 04 - 오락실 게임 'PANG' 만들기 (기본 세팅 및 프레임) (0) 2021.11.02 파이썬 활용 03 - PYGAME 사용해보기 (간단 게임 퀴즈) (0) 2021.11.02 파이썬 활용 02 - PYGAME 사용해보기 (키보드 이벤트/FPS/충돌처리/텍스트삽입) (0) 2021.11.01