[0426] TIL 7일차

nikevapormax·2022년 4월 26일
0

TIL

목록 보기
7/116
post-thumbnail

😂 게임 프로젝트

😭 컨셉

  • 어떤 게임을 만들어야 할지 감이 안와서(물론 실력도 ㅋㅋ) 어제도 이수안님께서 올려주신 슈팅게임을 따라서 만들어 봤다. 슈팅 게임을 만들다 보니 뭔가 디펜스 게임도 괜찮겠다 싶었다.(형은 디펜스 게임 만들어도 좋겠다는 경수의 아이디어) 그래서 진짜 해볼까 하고 진행하게 되었다.

😭 사용 기술

- pygame

  • 비디오 게임 작성용으로 설계된 크로스 플랫폼 Python 모듈 세트

😭 제작 과정

- 게임 화면 틀(깡통)

  • pygame.display.set_mode (resolution = (0,0), flags = 0, depth = 0)
    • 화면상의 창을 나타내는 파이 게임 (pygame.Surface)을 반환
    • resolution에는 게임 창의 너비와 높이를 나타내는 숫자 쌍 입력
    • flags에는 pygame.FULLSCREEN 또는 pygame.OPENGL 입력
    • depth에는 색상에 사용되는 비트 수 입력

      참고 블로그

screen_w = 480 # 가로
screen_h = 640 # 세로
screen = pygame.display.set_mode((screen_w, screen_h)) # 가로, 세로 크기의 게임창 생성
# 게임 생성을 위한 pygame 임포트
import pygame

# 게임 생성을 위한 초기화(필수)
pygame.init()

# 게임 화면 크기 설정 / 게임 타이틀 설정
screen_w = 480 # 가로
screen_h = 640 # 세로
screen = pygame.display.set_mode((screen_w, screen_h)) # 가로, 세로 크기의 게임창 생성
pygame.display.set_caption("dg defence") # 게임 이름

# 이벤트 루프를 통해 게임이 혼자 종료되지 않고 대기할 수 있도록 함
running = True  # 이 상태는 게임이 진행되고 있는 상태
while running:
    for event in pygame.event.get():    # 파이게임의 이벤트들 중에서
        if event.type == pygame.QUIT:   # 창이 닫히는 이벤트가 발생한다면
            running = False             # 게임을 종료해라

# pygame.종료
pygame.quit()

- 게임 배경화면 설정

  • pygame에서 배경화면 뿐만 아니라 다른 이미지를 사용하려면 이미지를 다운받아 저장한 후 불러와 사용할 수 있음
    pygame.image.load(path)
  • 나의 경우, 배경화면에 대한 아이디어가 딱히 없어 그림판에서 내 게임 창의 크기에 맞는 그림을 만든 후 채색해 사용
  • screen.blit(이미지, 대상)
    • 구글링하다보니 원하는 게 안나오던 중 아래 구문을 찾았다.
      blit(background,(x,y)) where (x,y) is the position inside the window where we want the top left of the surface to be.
    • 그러니까 우리는 배경화면을 좌상단에 주어진 좌표에다가 설정할 건데 나는 좌표를 (0, 0)을 준 것
  • pygame.display.update()
    • 화면이 계속 업데이트될 수 있도록 해주며, 괄호 안에 아무런 대상이 없다면 전체 surface를 대상으로 업데이트가 진행됨
# 게임 창의 배경화면
background = pygame.image.load('img/back.png')

while running:
    for event in pygame.event.get():    # 파이게임의 이벤트들 중에서
        if event.type == pygame.QUIT:   # 창이 닫히는 이벤트가 발생한다면
            running = False             # 게임을 종료해라

    screen.blit(background, (0, 0)) # 배경을 그림; 
                                    # 위에서 설정한 백그라운드를 게임창의 (0, 0) 위치에 그려줌
    pygame.display.update() # pygame에서는 배경화면을 매번 그려줘야 함! 수시로 업뎃한다고 생각하자!
    

- 메인 캐릭터 설정

  • 먼저 나의 메인 캐릭터는 게더의 내 캐릭터를 가지고 왔음
  • 그림판으로 옮겨서 다시 사이즈를 맞춰 저장해 준 후 배경화면과 같이 불러옴
  • get_rect().size 를 사용해서 내 캐릭터의 위치 좌표를 얻어옴
# 나의 메인 캐릭터
hero = pygame.image.load('img/hero.png')
# 캐릭터의 경우, 나중에 적과 부딪히거나 무기를 발사하는 이벤트때문에 정확한 크기 재야함
hero_size = hero.get_rect().size # 이미지의 크기를 구해옴; 
  • 위의 get_rect().size는 (x좌표, y좌표)에 대한 정보를 가지고 있고, 나는 이것들을 다시 변수를 만들어 할당해줌
hero_w = hero_size[0] # 캐릭터 가로 길이
hero_h = hero_size[1] # 캐릭터 세로 길이
  • 계산된 길이들을 사용해서 캐릭터의 왼쪽 상단 좌표값을 구함
    • 아래의 그림 참고
hero_x_pos = (screen_w / 2) - (hero_w / 2) # 캐릭터의 x 좌표
hero_y_pos = screen_h - hero_h 			   # 캐릭터의 y 좌표

  • 그리고 메인 이벤트 루프 안에 캐릭터를 나타내줌
screen.blit(hero, (hero_x_pos, hero_y_pos))  # 메인 캐릭터를 그림

- 캐릭터가 이동할 곳을 좌표로 생각

  • 나는 캐릭터를 상하좌우 모두 움직이기로 마음을 먹었다. 그래서 pygame의 키보드 이벤트를 적용할 것이다.
# 캐릭터가 이동할 좌표
move_x = 0
move_y = 0

running = True  # 이 상태는 게임이 진행되고 있는 상태
while running:
    for event in pygame.event.get():    # 파이게임의 이벤트들 중에서
        if event.type == pygame.QUIT: # 창이 닫히는 이벤트가 발생한다면
            running = False                   # 게임을 종료해라

        # pygame의 키보드 이벤트를 정의하는 부분
        if event.type == pygame.KEYDOWN: # 내가 키보드를 눌렀어 만약에
            if event.key == pygame.K_LEFT:  # 내가 누른게 왼쪽 방향키라면
                move_x -= 5                 # 왼쪽으로 가야되니까 까줘야지
            elif event.key == pygame.K_RIGHT: # 내가 누른게 오른쪽 방향키라면
                move_x += 5                   # 오른쪽으로 가니까 더하기
            elif event.key == pygame.K_UP:      # 내가 누른게 위 방향키라면
                move_y -= 5                     # 위로 가니까 까줘야지
            elif event.key == pygame.K_DOWN:    # 내가 누른게 아래 방향키라면
                move_y += 5                     # 아래로 가니까 더하기
        # 만약 게임하다가 방향키를 떼면 멈춤
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
                move_x = 0
            elif event.key == pygame.K_UP or event.key == pygame.K_DOWN:
                move_y = 0

    # 캐릭터가 움직인 좌표 계산이 끝나면 여기서 캐릭터의 위치 적용
    # for 문 안에서 이 작업을 하게 되면 for문이 돌 때마다 값이 변할 수 있음
    hero_x_pos += move_x
    hero_y_pos += move_y


하지만 보이는 것과 같이 화면 밖으로 나간다. 이러면 절대 죽을거 같진 않지만 적도 같이 따라 나갈테니까 경곗값을 그려주기로 했다.

  • 아래와 같이 코드를 작성해 주었다. 해당 부분은 이벤트 루프 안에 들어가며, 이벤트 루프의 for문 밖에 있어야 한다.
# 가로 경곗값 처리 (화면 좌우로 나가지 못하도록)
    if hero_x_pos < 0:  # 화면의 가장 왼쪽 밖으로 나가게 되면
        hero_x_pos = 0  # 못 나가게 x좌표를 0으로 준다.
    elif hero_x_pos > screen_w - hero_w:    # 화면의 가장 오른쪽 밖으로 나가게 되면
        hero_x_pos = screen_w - hero_w  # hero의 사진이 딱 벽에 닿을 때의 x좌표를 준다.

    # 세로 경곗값 처리 (화면 위아래로 나가지 못하도록)
    if hero_y_pos < 0:  # 화면의 가장 위쪽 밖으로 나가게 되면
        hero_y_pos = 0  # 못 나가게 y좌표를 0으로 준다.
    elif hero_y_pos > screen_h - hero_h:  # 화면의 가장 아래쪽 밖으로 나가게 되면
        hero_y_pos = screen_h - hero_h  # hero의 사진이 딱 바닥에 닿을 때의 y좌표를 준다.

- 캐릭터의 이동 속도 조정

  • FPS 설정
    • frame_ps 안의 60은 조절 가능
# FPS 설정
clock = pygame.time.Clock()

# 캐릭터의 이동 속도
hero_speed = 0.6

running = True  # 이 상태는 게임이 진행되고 있는 상태
while running:
    # 게임 화면의 초당 프레임 수 설정
    frame_ps = clock.tick(60)
    
    # 중략
    
    # 캐릭터가 움직인 좌표 계산이 끝나면 여기서 캐릭터의 위치 적용
    # for 문 안에서 이 작업을 하게 되면 for문이 돌 때마다 값이 변할 수 있음
    hero_x_pos += move_x * frame_ps
    hero_y_pos += move_y * frame_ps

- 적 캐릭터 생성 및 컨셉 고민

  • 말이 디펜스지 내 머리 속에서는 그냥 적 캐릭터를 피해다니거나 총알을 쏴서 처치하는 정도가 다였고, 처치하는 적의 수에 따라 캐릭터의 수도 늘려가는 것도 좋을 것이라 생각해서 이렇게 하기로 했다.
  • 적 캐릭터를 따오는 것도 중요한데, 우리 게더의 캐릭터들을 그대로 쓸까 하다가 내 동료들을 적으로 삼는거 같아서 구글링했다. 아래의 블로그에서 몇몇 캐릭터를 사용했다...

    뽀꼬빠님의 블로그

  • 대략 4시간 이상의 코딩과 고민 끝에 디펜스는 버렸다. 그냥 잘 도망다니는 게임으로 가기로 했다.
  • 적 캐릭터 생성 및 정보 생성
    • 각각의 캐릭터의 크기를 먼저 조정한 후 포토샵을 했어야 했는데, 자꾸 부탁을 하는거 같아 빠르게 하다보니 조정을 못했다.
    • 그래서 구글링을 바로 했다. 그랬더니 사진의 사이즈를 바꿀 수 있는 함수가 바로 나왔다.
      pygame.transform.scale(사진, (가로 사이즈, 세로 사이즈))
    • 또한 나는 원래 적을 랜덤하게 꺼내고 싶었으나 거기까지는 역량이 되지않아 저 랜덤 코드를 그냥 적의 사이즈를 재는 데 사용하자 생각했다. (어짜피 다 사이즈가 같아서 상관은 없을거 같아서)
    • 적 부분에서 하고싶은게 많았는데 결국은 나도코딩 님의 유튭을 들으며 정리했던 내용을 그대로 써버렸다.(원래는 사방에서 적이 나오는 것을 하고싶었음. 그래서 사실 주인공은 상하좌우 다 움직임 ㅋ)
    • 그리고 적의 정보 넣는 것도 깔끔하게 하고싶은데 왠지 객체를 하나 생성해서 돌리면 될거 같은데 당장은 엄두가 안남 -> 클래스 해보자,,,
#  적 캐릭터
enemies = [
    pygame.transform.scale(pygame.image.load('img/enemy 1.png'), (80, 80)),
    pygame.transform.scale(pygame.image.load('img/enemy 2.png'), (80, 80)),
    pygame.transform.scale(pygame.image.load('img/enemy 3.png'), (80, 80)),
    pygame.transform.scale(pygame.image.load('img/enemy 4.png'), (80, 80)),
    pygame.transform.scale(pygame.image.load('img/enemy 5.png'), (80, 80))
]

# enemies에서 랜덤하게 적 꺼내오기
enemy = random.choice(enemies)
enemy_size = enemy.get_rect().size
enemy_w = enemy_size[0]
enemy_h = enemy_size[1]

# 캐릭터가 바닥에 닿고 위로 올라가게 되면 y값이 작아지므로 마이너스 적용
enemy_speed = [-18, -15, -12, -15, -16]

# 적 캐릭터들이 들어올 리스트
enemy_li = []
enemy_li.append({
    'pos_x' : random.randrange(0, screen_w - enemy_w), # 적의 x 좌표
    'pos_y':  random.randrange(0, screen_h - enemy_h), # 적의 y좌표
    'e_idx': 0, # 적 이미지 인덱스
    'enemy_move_x': 3, # 적의 x축 이동 방향 (현재는 오른쪽, 왼쪽은 -3)
    'enemy_move_y' : -6, # 적의 y 축 이동 방향
    'init_speed_y': enemy_speed[0] # y 속도
})
enemy_li.append({
    'pos_x' : random.randrange(0, screen_w - enemy_w), # 적의 x 좌표
    'pos_y':  random.randrange(0, screen_h - enemy_h), # 적의 y좌표
    'e_idx': 0, # 적 이미지 인덱스
    'enemy_move_x': -3, # 적의 x축 이동 방향 (현재는 오른쪽, 왼쪽은 -3)
    'enemy_move_y' : -6, # 적의 y 축 이동 방향
    'init_speed_y': enemy_speed[1] # y 속도
})
enemy_li.append({
    'pos_x' : random.randrange(0, screen_w - enemy_w), # 적의 x 좌표
    'pos_y':  random.randrange(0, screen_h - enemy_h), # 적의 y좌표
    'e_idx': 0, # 적 이미지 인덱스
    'enemy_move_x': 3, # 적의 x축 이동 방향 (현재는 오른쪽, 왼쪽은 -3)
    'enemy_move_y' : -6, # 적의 y 축 이동 방향
    'init_speed_y': enemy_speed[2] # y 속도
})
enemy_li.append({
    'pos_x' : random.randrange(0, screen_w - enemy_w), # 적의 x 좌표
    'pos_y':  random.randrange(0, screen_h - enemy_h), # 적의 y좌표
    'e_idx': 0, # 적 이미지 인덱스
    'enemy_move_x': -3, # 적의 x축 이동 방향 (현재는 오른쪽, 왼쪽은 -3)
    'enemy_move_y' : -6, # 적의 y 축 이동 방향
    'init_speed_y': enemy_speed[3] # y 속도
})
enemy_li.append({
    'pos_x' : random.randrange(0, screen_w - enemy_w), # 적의 x 좌표
    'pos_y':  random.randrange(0, screen_h - enemy_h), # 적의 y좌표
    'e_idx': 0, # 적 이미지 인덱스
    'enemy_move_x': 3, # 적의 x축 이동 방향 (현재는 오른쪽, 왼쪽은 -3)
    'enemy_move_y' : -6, # 적의 y 축 이동 방향
    'init_speed_y': enemy_speed[4] # y 속도
})
  • enumerate를 사용해서 인덱스를 각 리스트의 요소에 달아주었음
  • 포물선 효과도 시도
  • 적 캐릭터를 화면에 그려주는 부분도 엄청 맘에 들진 않는다. 당장 저 방법이 최선인듯 하다,,, 여러 생각을 해봤는데 어짜피 enemy_li 리스트에 모든 적 캐릭터에 대한 정보를 담아놨으니 단순하게 빼 쓰는게 직관적이긴 할 것 같았다. 그런데 너무 더러워서 화가 난다. 더 생각해봐야 할듯하다.
# 적 위치 정의
    for enemy_idx, enemy_val in enumerate(enemy_li):
        enemy_pos_x = enemy_val['pos_x']
        enemy_pos_y = enemy_val['pos_y']
        enemy_index = enemy_val['e_idx']

        # 가로벽에 닿았을 때 적의 이동위치를 변경해주자
        if enemy_pos_x < 0 or enemy_pos_x > (screen_w - enemy_w):
            enemy_val['enemy_move_x'] = enemy_val['enemy_move_x'] * -1

        # 밑바닥에서 튕겨 올라가는 처리
        if enemy_pos_y >= screen_h - enemy_h:
            enemy_val['enemy_move_y'] = enemy_val['init_speed_y']
        else:
            # 그 외의 모든 경우에는 속도를 증가(시작값이 원래 음수) -> 포물선 효과
            enemy_val['enemy_move_y'] += 0.5

        enemy_val['pos_x'] += enemy_val['enemy_move_x']
        enemy_val['pos_y'] += enemy_val['enemy_move_y']
        
# 적 캐릭터를 그림
 if elapsed_time < 10:
        screen.blit(enemies[0], (enemy_li[0]['pos_x'], enemy_li[0]['pos_y']))
    if elapsed_time > 10:
        screen.blit(enemies[0], (enemy_li[0]['pos_x'], enemy_li[0]['pos_y']))
        screen.blit(enemies[1], (enemy_li[1]['pos_x'], enemy_li[1]['pos_y']))
    if elapsed_time > 20:
        screen.blit(enemies[0], (enemy_li[0]['pos_x'], enemy_li[0]['pos_y']))
        screen.blit(enemies[1], (enemy_li[1]['pos_x'], enemy_li[1]['pos_y']))
        screen.blit(enemies[2], (enemy_li[2]['pos_x'], enemy_li[2]['pos_y']))
    if elapsed_time > 30:
        screen.blit(enemies[0], (enemy_li[0]['pos_x'], enemy_li[0]['pos_y']))
        screen.blit(enemies[1], (enemy_li[1]['pos_x'], enemy_li[1]['pos_y']))
        screen.blit(enemies[2], (enemy_li[2]['pos_x'], enemy_li[2]['pos_y']))
        screen.blit(enemies[3], (enemy_li[3]['pos_x'], enemy_li[3]['pos_y']))
        screen.blit(enemies[4], (enemy_li[4]['pos_x'], enemy_li[4]['pos_y']))

- 충돌 설정

  • 나는 총을 쏘지 않기 때문에 부딪히지만 않으면 게임 성공이다. 그래서 이 부분은 그나마 간략하게 설정할 수 있었다.
 # 충돌 처리
    # 캐릭터 rect 정보
    hero_rect = hero.get_rect()
    hero_rect.left = hero_x_pos
    hero_rect.top = hero_y_pos

    for enemy_idx, enemy_val in enumerate(enemy_li):
        enemy_pos_x = enemy_val['pos_x']
        enemy_pos_y = enemy_val['pos_y']
        enemy_index = enemy_val['e_idx']

        # 적 rect 정보
        enemy_rect = enemies[enemy_index].get_rect()
        enemy_rect.left = enemy_pos_x
        enemy_rect.top = enemy_pos_y

        # 충돌한다잉~
        if hero_rect.colliderect(enemy_rect):
            running = False
            break

- 게임 시간 및 게임 완료 혹은 오버 메시지

  • 적 캐릭터를 화면에 나타내는 부분에서 elapsed_time을 기준으로 캐릭터가 한명씩 더 나오도록 설정
  • 시간들을 계산하여 게임이 성공했는지에 대한 여부 파악
  • pygame.time.delay(3000)를 통해 게임 결과에 대한 메세지를 3초동안 볼 수 있도록 설정
# 게임 폰트 정의
game_font = pygame.font.Font(None, 40)

# 총 게임시간
total_time = 60
# 시작 시간
start_ticks = pygame.time.get_ticks()

# 게임 종료 메세지
game_result = "GAME OVER!!!!!"

running = True
while:
	 # 경과 시간 계산
    elapsed_time = (pygame.time.get_ticks() - start_ticks) / 1000
    timer = game_font.render(f"Time : {int(total_time - elapsed_time)}", True, (255, 255, 255))
    
    screen.blit(timer, (10, 10))
    # 시간이 초과됐다면
    if total_time - elapsed_time <= 0:
        game_result = "YOU WIN!!!!"
        running = False
        
    pygame.display.update() # pygame에서는 배경화면을 매번 그려줘야 함! 수시로 업뎃한다고 생각하자!   
    
# 게임오버 메시지
msg = game_font.render(game_result, True, (255, 255, 0))
msg_rect = msg.get_rect(center = (int(screen_w/2), int(screen_h/2)))
screen.blit(msg, msg_rect)
pygame.display.update()

# 메시지를 보여주기위해서 3초 대기합니다
pygame.time.delay(3000)

# pygame.종료
pygame.quit()
profile
https://github.com/nikevapormax

0개의 댓글