안녕하세요! 게임 만드는 잉여 쏘코입니다.

 

지난 시간에는 집게의 뼈대를 만들었는데요!

 

 

이번 시간에는 본격적으로 동적인 요소를 넣어보려고 합니다!

무려 집게가 좌우로 흔들리도록 만들 예정입니다 ㅎㅎ

결과 미리보기!!

 

그렇다면 아래에서 바로 확인하시죠!

 

이번 포스팅은 나도코딩님 영상의 46:13~1:09:38까지의 내용을 담고 있습니다!

 

목차

     


    0. 집게 흔들기 #1

    집게를 만들었으니, 이제 게임처럼 흔들어야겠죠?

    7_claw_swing.py 파일을 만들고, 6_claw_line.py 파일을 copy&paste 해줍니다.

     

    가장 먼저 해야할 것은 회전각 설정입니다.

    아래 사진에서는 편의상 아래를 기준(시계방향)으로 10도~170도라고 설정했습니다. (사분면 개념을 가져와서 정확하게 말씀드리면 반시계방향으로 이동시 190도~350도와 같은 개념이라고 생각하시면 되겠습니다!)

    나도코딩님 설명영상 中

     

    먼저 집게의 이동을 결정하기 위해서 변수 3개를 만듭니다. (17~19줄)

    집게의 이동 방향(direction), 프레임당 각도 변경 폭(angle_speed), 최초 각도(angle)를 정의합니다.

     

    LEFT가 어떤 값인지 정의를 해 주지 않았죠?

    게임 관련 변수가 있는 곳에 LEFT와 RIGHT를 각각 -1과 1로 정의를 해줍니다. (70~71줄)

     

    이제 위에서 만든 데이터를 사용하기 위해서 update 메소드를 조금 더 추가해 보도록 하겠습니다.

    만약 왼쪽으로 움직이고 있다면 angle은 매 프레임마다 angle_speed만큼 늘어날 것이고,

    오른쪽으로 움직이고 있다면 angle은 매 프레임마다 angle_speed만큼 줄어들 것입니다. (23~26줄)

     

    그렇다면 LEFT와 RIGHT은 언제 변경될까요?

    각도가 증가하다가 170도까지 증가하게 되면, 더 이상 각도가 증가하면 안됩니다.

    170도가 초과하는 순간 self.angle을 170으로 설정하고 방향을 RIGHT으로 바꿔줍니다.

    이번에는 각도가 감소하다가 10도까지 감소하게 되면, 더 이상 각도가 감소하면 안됩니다.

    10도 아래로 떨어지는 순간 self.angle을 10으로 설정하고 방향을 LEFT로 바꿔줍니다. (28~34줄)

     

    그리고 이것이 정상적으로 돌아가는지 확인하기 위해서 print()문을 이용해서 출력해봅니다. (36줄)

    원하는 대로 잘 동작하고 있네요!

    터미널창에서 print문이 작동!!

     

    잘 작동하는 것을 확인했다면, print문을 주석처리해줍니다.

    추가로 rect_center과 self.rect도 주석처리 해줍니다. 이 부분은 다시 처리해 줄 예정입니다!

     

    이제 기본 이미지를 따로 저장해놓고, 이 기본 이미지를 회전해가면서 출력할 수 있도록 만들어야 합니다.

    update 메소드에서 만들었던 if 구문들 밑에 self.rotate()를 넣어줍니다.

    rotate함수가 없는데 어떻게 사용하냐구요? 당연히 만들어야 합니다 ㅎㅎ

     

    생성자에서 self.original_image 변수를 만들어서 처음 이미지를 넣어줍니다. (이 변수의 image는 기본 이미지로 사용할 것이기 때문에 바뀌지 않습니다!)

    이제 rotate라는 메소드를 생성합니다. pygame.transform.rotozoom 메소드를 사용해서 self.angle만큼 이미지를 회전하도록 만들어줍니다.

    여기서 매개변수로 -self.angle을 넣어주는데, 위에서 말했다시피 일반적인 각도는 반시계방향인데, 우리는 시계방향으로 각도를 봤었죠? 그렇기 때문에 -를 붙여서 넣어주어야 우리가 원하는 각도로 사진을 회전시킬 수 있습니다.

    여기까지 만들고 실행하면, 아래처럼 작동하게 됩니다.

    신기하긴 한데 아직 뭔가 엉성하죠?

     


    1. 집게 흔들기 #2

    먼저 방금 만든 집게의 회전이 왜 저런 식으로 꿈틀대는 지부터 알 필요가 있습니다.

    위에서 이미지를 회전을 시킬 때, 회전각의 중심은 왼쪽 위입니다.

    Rect(left, top 기준으로 영역이 회전하는 것이죠.

     

    현재의 rect를 빨간 선으로 확인하기 위해 아래와 같은 코드를 추가로 넣어봅니다. (48~49줄)

    (print는 콘솔 창에 현재 rect의 위치정보를 숫자로 띄우는 역할을 합니다.)

     

    빨간 네모의 왼쪽 위를 기준으로 회전하고 있습니다.

    집게가 빨간 네모의 윗쪽과 왼쪽은 침범하지 않는게 보이시죠?

    이 말은 현재의 회전축은 빨간 네모의 왼쪽 위라는 말이 됩니다.

    회전축을 빨간 점(빨간 네모의 중심)으로 바꿔야 우리가 원하는 대로 돌아갈 수 있겠죠?

     

    rotate 메소드 안에서 self.rect를 갱신합니다.

    get_rect(center = self.position)을 통해 이미지의 정보를 받아올 때 이미지를 좌표를 중심으로 위치시킨 후에 회전의 중심을 집게의 중심으로 맞춰줍시다. (48줄)

    그러면 우리가 원하는 대로 중심을 기준으로 회전하는 것을 보실 수 있습니다!!

     

    이제 위에서 주석처리했던 offset을 처리해줘야 합니다.

    우리는 집게를 시계추처럼 만들어주고 싶으니까요!

     

    이전에 만들었던 offset 변수에서 pygame.math.Vector2 메소드를 사용했었죠?

    Vector2는 rotate 메소드를 제공해줍니다. 각도만 제공하면 각도에 맞춰서 알아서 x와 y를 계산해주죠!

     

    rotate 메소드 내부에서 offset_rotated라는 변수를 하나 만들어서 생성자에서 만들었던 변수인 self.offset(pygame.math.Vector2로 만든 값)의 rotate 메소드를 사용해서 self.angle만큼 회전시켜줍니다. (48~49줄)

    이 값을 print로 출력하면 아래와 같이 나옵니다! 

    빨라서 잘 안보일 수도 있는데, 잘 보면 우리가 설정한 40까지 갔다가 다시 돌아오는 모습을 볼 수 있습니다.

     

    그림으로 설명하면, 우리가 10도부터 170도까지만 이동하게 설정을 했기 때문에 40만큼의 offset을 적용하고 rotate를 시키면 빨간색 범위에서 움직이게 됩니다. 그 좌표들이 지금 print 되고 있는 것이죠!

    조악한 그림판..

     

    offset까지 처리했다면, 이 값을 그대로 rect에 반영해주면 됩니다.

    self.rect는 처음에 빨간 네모의 왼쪽 위 상태로 고정이었다가, get_rect(center = self.position)을 통해 회전축이 중심으로 이동하면서 조금씩 변화했습니다.

     

    빨간색 점을 중심으로 회전하면서, 길이를 늘려주기 위해서는 이미지의 중심축이 이제는 빨간 곡선에 위치하면 되겠죠?

    다시보는 위 사진

     

    이미지의 중앙의 위치를 self.position이 아닌, self.position에 offset_rotated를 더한 값으로 바꿔줍니다. (51줄)

     

    집게를 매단 끈이 생겼습니다!!! 완전 신기합니다 🤩

    # 7_claw_swing.py
    # 집게를 좌우로 이동시키기
    import os # 경로를 위해 os 라이브러리 불러오기
    import pygame # 파이게임 라이브러리 불러오기
    
    
    # 집게 클래스 (보석 클래스와 거의 동일!)
    class Claw(pygame.sprite.Sprite):
        def __init__(self, image, position):
            super().__init__()
            self.image = image # 회전한 이미지를 가질 예정!
            self.original_image = image # 처음 이미지를 그대로 가지고 있는다!
            self.rect = image.get_rect(center = position)
    
            self.offset = pygame.math.Vector2(default_offset_x_claw, 0) # x위치는 100만큼 이동, y위치는 그대로
            self.position = position
    
            self.direction = LEFT # 집게의 이동 방향 (숫자값)
            self.angle_speed = 2.5 # 1프레임당 집게의 각도 변경폭 (좌우 이동 속도)
            self.angle = 10 # 최초 각도 정의 (오른쪽 끝)
            
    
        def update(self): # 보통 게임을 만들 때 캐릭터가 가만히 있을 때 숨쉬는 동작을 담당하는 부분!
            if self.direction == LEFT : # 왼쪽 방향으로 이동하고 있다면
                self.angle += self.angle_speed # 이동 속도만큼 각도를 증가
            elif self.direction == RIGHT : # 오른쪽 방향으로 이동하고 있다면
                self.angle -= self.angle_speed #이동 속도만큼 각도를 감소
            
            # 만약에 허용 각도 범위를 벗어나면?
            if self.angle > 170:
                self.angle = 170
                self.direction = RIGHT
            elif self.angle < 10:
                self.angle = 10
                self.direction = LEFT
    
            self.rotate()
            # print(self.angle, self.direction) 테스트용
            # rect_center = self.position + self.offset # 위에서 만든 두 변수를 더해줌!!
            # self.rect = self.image.get_rect(center = rect_center) # update 메소드를 실행하면 self.rect가 업데이트 되고, draw를 하면 바뀐 rect를 기준으로 화면에 출력!
    
        def rotate(self): # 원본 이미지가 아닌 새로운 이미지를 만드는 메소드!
            self.image = pygame.transform.rotozoom(self.original_image, -self.angle, 1)
            # 기본 사진을 주어진 각도만큼 회전 (실제로는 반시계방향으로 각도가 계산되지만, 우리는 시계방향으로 거꾸로 적용하기 위해 -를 붙였다!), 이미지의 크기 변동은 X(1)
            # self.image는 회전을 하고 나서 새롭게 만들어진 이미지를 넣기 위한 변수!! (rotozoom은 이미지 회전, 크기설정을 할 수 있는 메소드!)
            # rotozoom은 회전 대상 이미지, 회전 각도, 이미지 크기(scale)을 매개변수로 받는다!
    
            offset_rotated = self.offset.rotate(self.angle) # Vector2를 이용해 받아온 offset 데이터를 각도에 맞춰서 회전시킨 offset을 받아온다 (40으로 설정한 것을 기억하자!)
            # print(offset_rotated) # 확인용
    
            self.rect = self.image.get_rect(center = self.position + offset_rotated) # 집게의 중심점 기준으로 회전하게 만들어준다!
            # print(self.rect) # 확인용
            pygame.draw.rect(screen, RED, self.rect, 1)
    
    
        
        def draw(self, screen):
            screen.blit(self.image, self.rect) # 스크린의 blit을 이용해 Claw 클래스로 만든 객체의 이미지와 위치 정보를 이용해 그 내용을 화면에 출력한다! 
            pygame.draw.circle(screen, RED, self.position, 3) # screen에 빨간 점을 적겠다, 위치는 self.position, 반지름은 3으로 하겠다! - 중심점 표시
            pygame.draw.line(screen, BLACK, self.position, self.rect.center, 5) # 직선 그리기
            # 스크린에 선을 그릴 것이고, 검은 색이고, self.position(중심점)부터 rect.center(집게의 중심점)까지 직선의 두께는 5로 연결하겠다!
    
    
    # 보석 클래스
    class Gemstone(pygame.sprite.Sprite): # pygame의 Sprite를 상속해와서 사용한다!!
        def __init__(self, image, position): # 생성자 (사진인 image와 보석의 위치인 position을 매개변수로 받는다!)
            super().__init__() # 상속받은 Sprite의 생성자를 불러온다! (상속 받았으니 뭔지는 몰라도 초기화 해준다!)
            # 아래 2개의 변수는 Sprite 메소드를 사용하기 위해서 반드시 정의해야 함!!
            self.image = image # 캐릭터가 가진 이미지 정보 - 매개변수로 받아온다!
            self.rect = image.get_rect(center = position) # 캐릭터가 가지는 좌표, 크기 정보
            # 보석마다 위치가 달라져야 하기 때문에 받아온 이미지의 중앙이 매개변수로 받은 position에 맞춰서 rect를 가져오도록 설정한다!
    
    
    def setup_gemstone(): # 보석 클래스에서 설정한 보석의 사진과 위치 정보를 gemstone_group에 넣는 함수! 작은 금은 이해를 위해 나눠서 작성했고, 큰 금부터는 한번에 작성!
        # 작은 금
        small_gold = Gemstone(gemstone_images[0], (200, 380)) # 0번째 이미지를 (200, 300) 위치에 둬라
        gemstone_group.add(small_gold) # 그룹에 추가
        # 큰 금
        gemstone_group.add(Gemstone(gemstone_images[1], (300,500)))
        # 돌
        gemstone_group.add(Gemstone(gemstone_images[2], (300,380)))
        # 다이아몬드
        gemstone_group.add(Gemstone(gemstone_images[3], (900,420)))
    
    
    pygame.init() # 파이게임 초기화
    # 스크린 크기 지정
    screen_width = 1280
    screen_height = 720
    screen = pygame.display.set_mode((screen_width, screen_height)) # 이걸 통해 게임의 창 크기를 설정
    pygame.display.set_caption("Gold Miner") # 게임 제목 설정 "Gold Miner"
    
    clock = pygame.time.Clock() # 프레임값을 조정하기 위한 clock변수 설정
    
    
    # 게임 관련 변수
    default_offset_x_claw = 40 # 중심점으로부터 집게까지의 기본 x 간격
    LEFT = -1 # 왼쪽 방향
    RIGHT = 1 # 오른쪽 방향
    
    
    # 색깔 변수
    RED = (255, 0, 0) # RGB값, 빨간색
    BLACK = (0, 0, 0) # 검은색
    
    
    # 배경 이미지 불러오기
    current_path = os.path.dirname(__file__) # os.path.dirname을 이용해서 현재 파일의 위치(2_background.py 위치) 반환 
    background = pygame.image.load(os.path.join(current_path, "background.png")) # 현재 위치의 파일이 있는 폴더의 background.png파일을 선택하게 됨!!
    
    
    # 4개 보석 이미지 불러오기 (작은 금, 큰 금, 돌, 다이아몬드) - 배경을 가져오는 과정과 동일!!
    gemstone_images = [
        pygame.image.load(os.path.join(current_path, "small_gold.png")), # 작은 금
        pygame.image.load(os.path.join(current_path, "big_gold.png")), # 큰 금
        pygame.image.load(os.path.join(current_path, "stone.png")), # 돌
        pygame.image.load(os.path.join(current_path, "diamond.png")) # 다이아몬드
    ]
    
    
    # 보석 그룹
    gemstone_group = pygame.sprite.Group() # 젬스톤 그룹 생성
    setup_gemstone() # 게임에 원하는 만큼의 보석을 정의
    
    
    # 집게 이미지 불러오기 (보석 이미지 불러오는 방식과 동일!)
    claw_image = pygame.image.load(os.path.join(current_path, "claw.png"))
    claw = Claw(claw_image, (screen_width // 2, 110)) # Claw 클래스를 이용해서 객체 생성!!
    # 가로 위치는 화면 가로 크기 기준으로 절반, 위에서 110px에 위치
    
    
    # 게임이 반복해서 수행될 수 있도록 게임 루프를 만든다!
    running = True
    while running: # 게임이 진행중이라면? while문을 계속해서 반복하게 된다!
        clock.tick(30) # FPS 값이 30으로 고정
    
        for event in pygame.event.get(): # 이벤트를 받아오고
            if event.type == pygame.QUIT: # 게임이 꺼지는 이벤트가 발생했다면
                running = False # running 변수를 False로 바꿔준다!
    
        screen.blit(background, (0, 0)) # 맨 왼쪽 맨 위부터 ((0,0) 좌표부터)그림을 그려주도록 만들어준다!
    
        gemstone_group.draw(screen) # gemstone_group에 있는 모든 Sprite를 screen에다가 그려라!
        claw.update()
        claw.draw(screen)
    
        pygame.display.update() # 설정한 배경화면 이미지를 pygame에 반영! (display에 업데이트!!)
    
    pygame.quit() # while문을 빠져나가면 게임이 끝나도록 설정

     


    오늘은 집게에 끈을 매달아 보았습니다!!

    조금 포스팅이 길어졌는데, 게임에 쓰이는 동적인 모션을 이해하고 그것을 만드는 것이 얼마나 어려운 일인지를 체감할 수 있었습니다..

    여기까지만 해도 충분히 신기한데, 다음 포스팅에서는 이 집게를 발사! 시켜보도록 하겠습니다 :)

     

    그렇다면 다음 포스팅에서 뵙겠습니다! 감사합니다 :)

    반응형
    • 네이버 블로그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기