ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 파이썬 활용 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]
    1. 무기를 쏘면 무기가 위로 쭉 올라가야하므로 weapons에 담긴 무기의 x,y좌표를 들고와서 x는 그대로 가지만 y의 높이는 스피드에 따라 줄어들면서 위로 올라 가게 됨
    2. 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"]
    1. for ball_idx, ball_val in enumerate(balls): 를 하게되면 인덱스 위치와 인덱스의 값을 받아온다
    2. 포문을 돌면서 공의 x,y 좌표와 이미지를 'balls.append'를 통해서 선언했던 변수들로 채워준다
    3. 공의 size/width/height은 이전의 연습과 똑같이 사이즈를 구하는 용도
    4. 공의 x축이 가로 맨끝의 '0'보다 작거나 (즉 화면을 완전히 넘어가거나) 화면(640) - 공(160) 의 크기보다 작아지면 공의 위치를 다시 반대방향으로 바꿈 (즉 'to_x' = 3 * 1 ... 로 튕겨서 반대로 감) 
    5. 세로의 개념도 똑같음. 공이 이미 최초값이 50인데 이 y축(50)이 화면(480) - 스테이지(50) - 공(160)보다 작아지면(공이 스테이지에 닿는 위치) 처음 속도 '-18'의 속도로 튕기게 됨
    6. 공이 스테이지에 안닿으면 'to_y = -6'에서 +0.5 하면서 점점 공이 땅으로 빨리 떨어지게 됨(최초 속도가 '-' 이므로 +0.5씩 하게되면 포물선을 그리면서 떨어지게 됨)
    7. 마지막으로 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))
            ....
    1. 그리는 위치는 백그라운드→ 무기 → 다음 순으로 그린다. (스테이지/캐릭터와 겹치지 않기 위해)
    2. 그려지는 위치는 최초의 선언한 값들로 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
    1. 공과 캐릭터의 rect()정보를 불러 온 뒤 캐릭과 공이 충돌하면 게임 종료
    2. 무기의 rect()정보를 불러오고 무기와 공이 닿으면 공과 무기의 제거 변수 값이 -1에서 인덱스번호로 바뀜
    3. 바뀐 변수가 인덱스의 위치일테니 그 값을 가지고 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
    1. 공과 무기가 충돌 한 후 제일 작은 공이 아니면 현재 공 크기와 그 다음으로 작은 공 rect()정보를 업데이트 함.
      (다음 공들은 계속해서 인덱스에 +1을 하면 자연스럽게 리스트에서 꺼내옴)
    2. balls[]에서 최초 공 이후 append로 쪼개지는 공들을 넣으면 됨. 왼쪽/오른쪽으로 각각 쪼개지는 공을 넣는데 쪼개질때의 좌표는 "현재 공의 좌표 + (현재 공의 크기 반) - (다음 작은 공의 크기 반)" 으로 하면 충돌했을때의 위치에서 양쪽으로 쪼개져서 작은공들이 나옴 
      (왼쪽 오른쪽 두개를 추가하니 양쪽으로 하나씩 떨어지는 것)
    3. 공의 이미지는 인덱스를 추가하면 자연스럽게 들고오게되고 이동방향은 왼쪽 오른쪽 -/+로 선택하고, 속도도 똑같이 인덱스에서 기본으로 세팅되어있기때문에 인덱스꺼를 들고오면 저절로 적용이 됨

     

     

     

     

    05 게임 오버

    • 초기에 설정했던 조건을 충족해서 게임진행이 되도록 해야한다 
      1) 모든 공을 없애면 종료(성공)
      2) 캐릭터는 공에 닿으면 게임 종료(실패)
      3) 시간 제한 99초 초과시 게임 종료(실패)​
    • 시간 추가

     

     

    • 조건에 따른 결과 처리
    1. 시간 초과 시


    2. 모든 공을 없앴을 때
    3. 공과 캐릭이 충돌하면 이미 게임이 종료되도록 세팅이 되어있기때문에 초기 값 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 #캐릭터의 위치는 키보드로 움직인 만큼의 위치
    • 왼쪽 오른쪽 변수를 따로 만들어서 각각 처리해야 물리지 않고 처리 된다!

     

Designed by Tistory.