캐릭터 스프라이트는 매초마다 애니메이션 처럼 여러장의 이미지를 flip (바꿈)하여 마치 움직이는 것 처럼 보이는 기술이다. 초당 루프 횟수에 따라 이미지를 순서대로 로드하면 만들 수 있다.

 

파이게임에는 스프라이트 전용 클래스가 있어서 더 수월하게 진행할 수 있다. 허나 스프라이트 클래스를 사용하기 전에 게임루프를 이용해서 직접 구현해 보면 원리적으로 도움이 된다.

 

 

1. 게임 리소스

 

게임 애셋(Asset 자산) 이라고도 한다. 스프라이트는 보기엔 쉬워 보여도 실제로 만들려면 그래픽 전문가가 최소 몇시간은 작업해야 한다. 하지만 시대가 좋아졌다.

 

CC 조건에 무료로 사용할 수 있는 로얄티 프리 리소스가 많이 있다.

 

어쩐지 요새 스팀의 저가형 게임이 다 거기서 거기 비슷해 보이는 게 이유가 있었다.

그렇다고 너무 욕할 필요는 없다. 그만큼 게임을 만들기도 쉬워졌다는 것이다.

 

예전 같으면 전문가들이 수천만원에 달하는 그래픽 도구와 게임 엔진을 구매하는데 돈을 주고 만들었을 법한 게임을 요즘은 고등학생도 리소스를 다운로드 받아 스크립트를 짜면 만들 수 있을 정도로 세상이 변했다.

 

 

 

로열티 프리 리소스도 다 조건이 있고 장단점이 있으니 잘 읽어 보고, 각자가 현명하게 판단해서 사용한다.

 

*프리 게임 애셋

 

Free Game Assets

Collection of various completely free game assets. From 2d character sprites, platformer tileset, game GUI, and many other themes. No hidden costs, for sure.

www.gameart2d.com

 

 

스프라이트 캐릭터

전체 소스를 훑어보고 간다. 사람마다 스타일이 있겠지만 전체를 보고 디테일로 들어가도 되고,

 

디테일부터 만들어 전체로 가는 사람도 있다. 어느 쪽이건 동작이 잘되고 디버그할 수 있으면 그만이다.

import pygame

size = width, height = 800,600
win = pygame.display.set_mode(size)
bg = pygame.transform.rotozoom(pygame.image.load('Flat Nature Art.png'),0,0.85)
char1 = pygame.transform.rotozoom(pygame.image.load('png/Walk (1).png'),0,0.35)
char2 = pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (1).png'),True, False),0,0.35)

#win.fill((255,255,255))

win.blit(bg,(0,0))
pygame.display.update()

pygame.init()
clock = pygame.time.Clock()

# Load an image

walkRight =[pygame.transform.rotozoom(pygame.image.load('png/Walk (1).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (2).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (3).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (4).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (5).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (6).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (7).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (8).png'),-5,0.35)]

walkLeft = [pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (1).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (2).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (3).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (4).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (5).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (6).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (7).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (8).png'),True, False),1,0.35)]
print(walkLeft)

vel = 10
x = 50
y = 350
left = False
right = False
walkcount = 0
stopd = True


def redrawG():
    global walkcount

    win.blit(bg, (0, 0))
    #win.fill((255, 255, 255))

    if walkcount >= 32:
        walkcount = 0

    # walk right
    if right:
        win.blit(walkRight[walkcount//4],(x,y))
        print(walkcount//4)
        walkcount += 1
    #walk left
    elif left:
        win.blit(walkLeft[walkcount//4],(x,y))
        walkcount += 1

    elif stopd :
        win.blit(char1, (x, y))
        print("Right Direction")
    else:
        win.blit(char2, (x,y))
        print("Left Direction")
        pass
    pygame.display.update()


run = True

while run:
    clock.tick(48)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == KEYUP:
            if right:
                right = False
                stopd = True
            if left:
                left = False
                stopd = False
            pass

    keys = pygame.key.get_pressed()

    if keys[pygame.K_d]:
        if x > width - 100:
            x = width - 100
        x += vel
        right = True
        left = False
    if keys[pygame.K_a]:
        if x < -150:
            x = -150
        x -= vel
        right = False
        left = True

    redrawG()

pygame.quit()

 

다른 캐릭터나 추가의 기능없이 캐릭터 스프라이트와 이동에 관한 코드만 정리했다.

 

초반 import 문은 pygame 을 가져왔다. 다음 줄은 파이게임을 초기화 시키면서 필요한 준비를 한다. 중요한 것은 윈도우 창을 띄우고 배경을 넣고 몇몇 정지 화면(서 있는 화면)의 좌우를 설정하는 일이다.

size = width, height = 800,600
win = pygame.display.set_mode(size)
bg = pygame.transform.rotozoom(pygame.image.load('Flat Nature Art.png'),0,0.85)
char1 = pygame.transform.rotozoom(pygame.image.load('png/Walk (1).png'),0,0.35)
char2 = pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (1).png'),True, False),0,0.35)

#win.fill((255,255,255))

win.blit(bg,(0,0))
pygame.display.update()

pygame.init()
clock = pygame.time.Clock()

 

*이미지를 로드한다. 스프라이트를 만들기 위해 리스트 자료형을 사용한다. 원본을 그대로 사용하려다 보니 사이즈 조절이 필요하다. transform 메소드로 해결한다.

 

*walkRight 은 오른쪽으로 걸을 때이고 walkLeft 는 왼쪽으로 걸을 때의 스프라이트다. 한쪽 방향의 스프라이트만 있어도 이미지를 flip 하여 반대방향의 동작에 사용할 수 있다. 로드하는 줄이 길어졌는데 사전에 리소스에 대한 손질을 해준다면 이렇게 복잡할 필요는 없을 것이다.

 

* 이미지는 최적화를 위해서 convert 해주는게 좋다. 여기서는 간결함을 위해 그대로 사용해본다.

 

* 위 로열티 프리 웹사이트에서 공룡을 받았다. 다른 로열티 프리 자료를 사용해도 파일명만 잘 가져오면 작동한다. 단 같은 수의 스프라이트여야 한다. (현재 8장) 장수가 많아지면 redrawG의 blit 부분을 업데이트 한다.

walkRight =[pygame.transform.rotozoom(pygame.image.load('png/Walk (1).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (2).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (3).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (4).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (5).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (6).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (7).png'),-5,0.35),
            pygame.transform.rotozoom(pygame.image.load('png/Walk (8).png'),-5,0.35)]

walkLeft = [pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (1).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (2).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (3).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (4).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (5).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (6).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (7).png'),True, False),1,0.35),
            pygame.transform.rotozoom(pygame.transform.flip(pygame.image.load('png/Walk (8).png'),True, False),1,0.35)]
print(walkLeft)

 

*게임 내 루프에서 사용할 몇가지 변수를 또 설정해준다.

 

vel은 캐릭터 속도 x, y 는 시작 좌표, left,right,stopd 는 걷는 행동에 관련한 bool 형이고 walkcount 는 프레임을 조절하기 위한 변수이다.

vel = 10
x = 50
y = 350
left = False
right = False
walkcount = 0
stopd = True

 

*redrawG 게임 다시 그리기 함수이다. 아무일도 하지 않아도 while 문에서 매 프레임 호출된다.

 

이동하며 스프라이트가 변하는 로직이 담겨있다. 처음에 헷갈리더라도 몇번 구현하다 보면 알게된다. 혹시 변수의 값들이 궁굼하면 중간중간 print 함수로 변수의 값 변화를 콘솔에서 확인한다. 

 

변수 walkcount와 몫기능을 활용해서 8장의 스프라이트를 1초에 32번 보여주도록 한다. 그럴려면 1장을 4번 보여주면 된다. 카운트 0,1,2,3 은 4로 나누면 0이 된다. 카운트 4,5,6,7 은 4로 나누면 몫이 1이 된다.

 

즉 surface 객체 [0] 4번  4/32초

    surface 객체 [1] 4번  4/32초

 

이렇게 화면에 표시하게 된다. 빨라서 우리눈에 잘 보이지는 않겠지만 이 동작이 while 문에 정해놓은 FPS 숫자값만큼 계속 반복되고 있다. 마지막 elif 와 else는 섰을 때의 동작이다. print를 콘솔에 출력하여 어느 지점에 서있는지 확인한다.

 

def redrawG():
    global walkcount

    win.blit(bg, (0, 0))
    #win.fill((255, 255, 255))

    if walkcount >= 32:
        walkcount = 0

    # walk right
    if right:
        win.blit(walkRight[walkcount//4],(x,y))
        print(walkcount//4)
        walkcount += 1
    #walk left
    elif left:
        win.blit(walkLeft[walkcount//4],(x,y))
        walkcount += 1

    elif stopd :
        win.blit(char1, (x, y))
        print("Right Direction")
    else:
        win.blit(char2, (x,y))
        print("Left Direction")
        pass
    pygame.display.update()

 

* 마지막 게임루프의 끝이다.

 

캐릭터가 정지하는 것은 KEYUP에 설정한다.

 

다음 키설정은 pygame.key.get_pressed( )를 사용한다.

 

여기서는 두개의 동작만 설정하지만 다양한 동작들을 추가할 수 있을 것이다.

 

좌우로 이동하는 것은 지난 포스트에서 다 커버 한 내용이다.

while run:
    clock.tick(48)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYUP:
            if right:
                right = False
                stopd = True
            if left:
                left = False
                stopd = False
            pass

    keys = pygame.key.get_pressed()

    if keys[pygame.K_d]:
        if x > width - 100:
            x = width - 100
        x += vel
        right = True
        left = False
    if keys[pygame.K_a]:
        if x < -150:
            x = -150
        x -= vel
        right = False
        left = True

    redrawG()

pygame.quit()

 

공룡 캐릭터

 

이 포스트 내용을 만들면서 몇 가지 인터넷의 자료들을 조사했다.

 

특히 유튜브 스타 테크 위드 팀의 웹사이트 내용을 참고했다. 추천 유튜버 중의 하나이다.

 

*테크 위드 팀

 

https://techwithtim.net/tutorials/game-development-with-python/

 

techwithtim.net

 

공유하기

facebook twitter kakaoTalk kakaostory naver band