from tkinter import *
import pygame
class GameSound:
def __init__(self):
window = Tk() # 윈도우 생성
window.title("게임사운드") # 제목을 설정
window.geometry("640x480") # 윈도우 크기 설정
window.resizable(0,0)
self.canvas = Canvas(window, bg = "white")
self.canvas.pack(expand=True,fill=BOTH)
window.bind("<KeyPress>",self.keyPressHandler)
window.bind("<KeyRelease>",self.keyReleaseHandler)
#pygame 에서 music vs sound
# music: 배경음악 재생을 위해 사용
# sound: 효과음을 위해 사용
#BG sound.
pygame.init()
pygame.mixer.music.load("bgm.wav") #Loading File Into Mixer
pygame.mixer.music.play(-1) #Playing It In The Whole Device
self.canvas.create_text(320,400,font="Times 15 italic bold",text="Sound Example")
#Effect sound
self.sounds = pygame.mixer
self.sounds.init()
self.s_effect1 = self.sounds.Sound("gunsound.mp3")
while True:
#
window.after(33)
window.update()
def keyReleaseHandler(self, event):
if event.keycode in self.keys:
self.keys.remove(event.keycode)
def keyPressHandler(self, event):
self.s_effect1.play()
self.keys.add(event.keycode)
GameSound()
코드 설명
pygame 모듈을 사용하기 위해 pygame을 import 한다.(line 2)
pygame을 사용하기 위해서는 pygame.init()함수를 호출해야한다.(line 21)
위 코드에서 music.load(), music.play() 함수를 이용하여 배경음악을 무한반복 재생한다.(line 22, 23)
effect sound를 위해 sounds 를 초기화 한다.(line 28)
이펙트 사운드 'gunsound.mp3' 파일을 읽고 sound 객체를 반환한다. sound 객체는 이펙트 사운드는 게임중 재생 및 멈춤을 컨트롤할 수 있다.
line 41에서 이펙트 사운드를 재생한다.
위 코드만으로 간단히 게임에서 필요한 음악 재생은 가능하며 좀 더 다양한 기능을 구현하기 위해서는 아래의 링크를 참조하기 바랍니다.
게임개발자인 "죠르디"는 크레인 인형뽑기 기계를 모바일 게임으로 만들려고 합니다. "죠르디"는 게임의 재미를 높이기 위해 화면 구성과 규칙을 다음과 같이 게임 로직에 반영하려고 합니다.
게임 화면은"1 x 1"크기의 칸들로 이루어진"N x N"크기의 정사각 격자이며 위쪽에는 크레인이 있고 오른쪽에는 바구니가 있습니다. (위 그림은 "5 x 5" 크기의 예시입니다). 각 격자 칸에는 다양한 인형이 들어 있으며 인형이 없는 칸은 빈칸입니다. 모든 인형은 "1 x 1" 크기의 격자 한 칸을 차지하며격자의 가장 아래 칸부터 차곡차곡 쌓여 있습니다.게임 사용자는 크레인을 좌우로 움직여서 멈춘 위치에서 가장 위에 있는 인형을 집어 올릴 수 있습니다. 집어 올린 인형은 바구니에 쌓이게 되는 데, 이때 바구니의 가장 아래 칸부터 인형이 순서대로 쌓이게 됩니다. 다음 그림은 [1번, 5번, 3번] 위치에서 순서대로 인형을 집어 올려 바구니에 담은 모습입니다.
만약 같은 모양의 인형 두 개가 바구니에 연속해서 쌓이게 되면 두 인형은 터뜨려지면서 바구니에서 사라지게 됩니다. 위 상태에서 이어서 [5번] 위치에서 인형을 집어 바구니에 쌓으면 같은 모양 인형두 개가 없어집니다.
크레인 작동 시 인형이 집어지지 않는 경우는 없으나 만약 인형이 없는 곳에서 크레인을 작동시키는 경우에는 아무런 일도 일어나지 않습니다. 또한 바구니는 모든 인형이 들어갈 수 있을 만큼 충분히 크다고 가정합니다. (그림에서는 화면표시 제약으로 5칸만으로 표현하였음)
게임 화면의 격자의 상태가 담긴 2차원 배열 board와 인형을 집기 위해 크레인을 작동시킨 위치가 담긴 배열 moves가 매개변수로 주어질 때, 크레인을 모두 작동시킨 후 터트려져 사라진 인형의 개수를 return 하도록 solution 함수를 완성해주세요.
[제한사항]
board 배열은 2차원 배열로 크기는 "5 x 5" 이상 "30 x 30" 이하입니다.
board의 각 칸에는 0 이상 100 이하인 정수가 담겨있습니다.
0은 빈 칸을 나타냅니다.
1 ~ 100의 각 숫자는 각기 다른 인형의 모양을 의미하며 같은 숫자는 같은 모양의 인형을 나타냅니다.
moves 배열의 크기는 1 이상 1,000 이하입니다.
moves 배열 각 원소들의 값은 1 이상이며 board 배열의 가로 크기 이하인 자연수입니다.
- 먼저 인형을 담을 공간 리스트 변수(temp로 선언)를 선언하고 moves 변수의 값을 이용하여 board 로 부터 인형을 하나씩 옮겨 담는다. 이때 인덱스 를 1씩 증가 시키면서 0이 아닌 수를 찾는다. 만약 찾으면 그 칸은 0 으로 저장하고 찾은 수(인형)은 temp에 append 시킨다.
- moves 에 저장된 값들이 실행이 되고 temp 리스트 끝에서 처음으로 이동하면서 2개씩 연속된 숫자들이 있는지 찾는다. 이때 있으면 제거하고 다시 temp의 끝에서 처음으로 이동한다. 이동 index 가 0보다 클때까지 반복한다.
def solution(board, moves):
answer = 0
temp = []
# 옮기는 코드
for i in moves:
for j in range(len(board)):
if board[j][i-1]!=0:
temp.append(board[j][i-1])
board[j][i-1] = 0
break
#연속된 인형(수) 있는지 찾는 코드
i = len(temp)-1
while i>0:
if (temp[i] == temp[i-1]):
del temp[i-1:i+1]
i = len(temp)-1
answer += 2
else:
i -= 1
return answer
from tkinter import * # tkinter에서 모든 정의를 임포트한다.
import time
import pygame
import random
import time
import socket
import ast
import json
import threading
class GameClient:
def __init__(self,user,host,port):
#사용자 아이디에서 공백 제거
user = user.replace(" " , "")
#네트워크 초기화 및 서버 연결
#
self.port = port # 연결 포트설정
self.host = host #서버 IP
self.user = user #사용자 아이디 설정
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client_socket.connect((self.host, self.port))
#서버 연결 확인을 위한 패킷 송수신
self.client_socket.sendall(self.user.encode())#서버에 사용자 아이디 전송
data = self.client_socket.recv(1024)#서버응답 받기
data = data.decode()#서버에서 받은 데이터를 문자열로 변환
#서버 접속 확인: 접속 실패시 프로그램 종료
if "Success" in data: print("서버에 접속하였습니다.")
elif "Fail" in data:
print(self.user, "동일한 사용자가 존재합니다. 사용자를 변경하기 바랍니다.")
exit()
else:
print("서버에 접속에 실패하였습니다.","프로그램을 종료합니다.")
exit()
#서버에서 전송된 사용자 데이터를 저장하는 변수
self.user_data={}
#
self.receive_thread = threading.Thread(target=self.handle_receive)
self.receive_thread.daemon = True
self.receive_thread.start()
#게임윈도우 만들기 시작...
print("게임윈도우를 생성합니다.")
window = Tk() # 윈도우 생성
window.title("네트워크게임예제") # 제목을 설정
window.geometry("1440x960") # 윈도우 크기 설정
window.resizable(0,0) # 윈도우 크기 조절 X
self.lightingTimer = time.time() # 일정 주기로 lighting effect 효과를 위한 타임 변수
self.keys=set() # 중복된 사용자 입력 키 값을 쉽게 제거하기 위해 set 타입으로 선언
self.canvas = Canvas(window, bg = "white")
self.canvas.pack(expand=True,fill=BOTH)
window.bind("<KeyPress>",self.keyPressHandler)
window.bind("<KeyRelease>",self.keyReleaseHandler)
self.img_dragon = [PhotoImage(file='image/dragon-animated-gif.gif',format = 'gif -index %i' %(i)).subsample(3) for i in range(40)]
self.img_light_effect = PhotoImage(file="image/lightning-effect-png2.png")
self.img_fire = PhotoImage(file = "image/fire_type2.png")
self.img_bg = PhotoImage(file="image/bgimage2.png")
self.img_enemy = PhotoImage(file='image/spaceship.png').subsample(6)
#배경 그리기.
self.canvas.create_image(0,0, image = self.img_bg,anchor = NW,tags="bg")
self.isDebugMode = False
#캔버스에 그려질 미사일에 대한 100 개의 더미 생성, 안보이게 설정
self.fire_obj_list = []
self.fire_obj_debug_list = []
for i in range(100):
self.fire_obj_list.append(self.canvas.create_image(0,0, image = self.img_fire,state='hidden', tags="fire"))
self.fire_obj_debug_list.append(self.canvas.create_rectangle(0,0,5,5, fill='white',state='hidden', tags="fire"))
#캔버스에 그려질 적에 대한 100 개의 더미 생성, 안보이게 설정
self.enemy_obj_list = []
self.enemy_obj_debug_list = []
for i in range(100):
self.enemy_obj_list.append(self.canvas.create_image(0,0, image = self.img_enemy,state='hidden', tags="enemy"))
self.enemy_obj_debug_list.append(self.canvas.create_rectangle(0,0,140,65,state='hidden', tags="enemy"))
#배경사운드
pygame.init()
pygame.mixer.music.load("sound/bgm.wav") #Loading File Into Mixer
pygame.mixer.music.play(-1) #Playing It In The Whole Device
#충돌 이펙트 사운드
self.sounds = pygame.mixer
self.sounds.init()
self.s_effect1 = self.sounds.Sound("sound/destruction.mp3")
self.canvas.create_text(150,60,fill="white",font="Times 15 italic bold",text="입력키: ↑ , ↓ , ← , → , space")
self.canvas.create_text(720,810,fill="white",font="Times 20 italic bold",text="Network Game Example")
self.canvas.create_text(720,850,fill="white",font="Times 20 italic bold",text="Major of computer software, Gyeongsang National Universityty")
#
user_list_in = set()
while True:
#사용자 입력이 있을 경우 서버로 데이터 전송
if(len(self.keys)>0):#각 키값을 ',' 구분된 아스키 코드 값으로 전송
self.client_socket.sendall(','.join(str(e) for e in self.keys).encode())
fire_obj_idx = 0
user_list_from_server = set()
for key, value in self.user_data.items():
if key != 'enemy':
try:
obj = self.canvas.find_withtag(key)
except:
print('프로그램을 종료합니다.')
exit()
img_dragon_idx = value[0]%len(self.img_dragon)
if len(obj)==0:
if self.user != key:
print(key,' 사용자가 입장하였습니다.')
self.canvas.create_image(value[1],value[2], image = self.img_dragon[img_dragon_idx],tags=key)
self.canvas.create_text(value[1]+self.img_dragon[img_dragon_idx].width()/2,value[2]+self.img_dragon[img_dragon_idx].height()/2+20, font="Times 20 italic bold",fill='#ff00ff',text=key+' '+str(value[4]),tag=key)
user_list_in.add(key)
user_list_from_server.add(key)
else:
self.canvas.itemconfig(obj[0], image = self.img_dragon[img_dragon_idx])
self.canvas.moveto(obj[0],value[1],value[2])
#itemcget(item_id,속성): canvas item 들의 속성 값 가져오기
pre_text = self.canvas.itemcget(obj[1],'text')
post_text = key+' '+str(value[4])
#값이 다르면 text 속성 업데이트, 충돌 사운드 재생
if pre_text != post_text:
self.canvas.itemconfig(obj[1], text=post_text)
self.s_effect1.play()
self.canvas.moveto(obj[1],value[1]+self.img_dragon[img_dragon_idx].width()/2,value[2]+self.img_dragon[img_dragon_idx].height()/2+20)
user_list_from_server.add(key)
for i in range(len(value[3])):
if(fire_obj_idx < len(self.fire_obj_list)):
self.canvas.itemconfig(self.fire_obj_list[fire_obj_idx],state='normal')
self.canvas.moveto(self.fire_obj_list[fire_obj_idx],value[3][i][0],value[3][i][1])
if self.isDebugMode:
self.canvas.itemconfig(self.fire_obj_debug_list[fire_obj_idx],state='normal')
else:
self.canvas.itemconfig(self.fire_obj_debug_list[fire_obj_idx],state='hidden')
self.canvas.moveto(self.fire_obj_debug_list[fire_obj_idx],value[3][i][0]+50,value[3][i][1])
fire_obj_idx += 1
else:#만약 보여되는 미사일 수가 더미로 미사일 수보다 많으면 추가로 만들기
self.fire_obj_list.append(self.canvas.create_image(value[3][i][0],value[3][i][1], image = self.img_fire, tags="fire"))
if self.isDebugMode:
self.fire_obj_debug_list.append(self.canvas.create_rectangle(value[3][i][0],value[3][i][1],value[3][i][0]+5,value[3][i][1]+5, fill='white', tags="fire"))
else:
self.fire_obj_debug_list.append(self.canvas.create_rectangle(value[3][i][0],value[3][i][1],value[3][i][0]+5,value[3][i][1]+5, state='hidden', fill='white', tags="fire"))
#서버에 접속이 끊긴 사용자들 제거
user_list_exit = user_list_in - user_list_from_server
for u in list(user_list_exit):
print(u,' 사용자가 퇴장하였습니다.')
user_list_in.remove(u)
objs = self.canvas.find_withtag(u)
for o in objs:
self.canvas.delete(o)
#사용되지 않은 미사일 이미지들은 안보이게 설정
for i in range(fire_obj_idx,len(self.fire_obj_list)):
self.canvas.itemconfig(self.fire_obj_list[i],state='hidden')
self.canvas.itemconfig(self.fire_obj_debug_list[i],state='hidden')
enemy_obj_idx = 0
#
for p in self.user_data['enemy']:
if(enemy_obj_idx < len(self.enemy_obj_list)):
self.canvas.itemconfig(self.enemy_obj_list[enemy_obj_idx],state='normal')
self.canvas.moveto(self.enemy_obj_list[enemy_obj_idx],p[0],p[1])
if self.isDebugMode:
self.canvas.itemconfig(self.enemy_obj_debug_list[enemy_obj_idx],state='normal')
else:
self.canvas.itemconfig(self.enemy_obj_debug_list[enemy_obj_idx],state='hidden')
self.canvas.moveto(self.enemy_obj_debug_list[enemy_obj_idx],p[0],p[1])
enemy_obj_idx += 1
else:#만약 보여되는 적 수가 더미 적 수보다 많으면 추가로 만들기
self.enemy_obj_list.append(self.canvas.create_image(p[0],p[1], image = self.img_enemy, tags="enemy"))
if self.isDebugMode:
self.enemy_obj_debug_list.append(self.canvas.create_rectangle(p[0],p[1],p[0]+5,p[1]+5, fill='red', tags="enemy"))
else:
self.enemy_obj_debug_list.append(self.canvas.create_rectangle(p[0],p[1],p[0]+5,p[1]+5, state='hidden', fill='red', tags="enemy"))
#사용되지 않은 적 이미지들은 안보이게 설정
for i in range(enemy_obj_idx,len(self.enemy_obj_list)):
self.canvas.itemconfig(self.enemy_obj_list[i],state='hidden')
self.canvas.itemconfig(self.enemy_obj_debug_list[i],state='hidden')
self.display_lighting_effect()
window.after(33)
window.update()
#
self.client_socket.close()
self.receive_thread.join()
def keyReleaseHandler(self, event):
if event.keycode==68:
self.isDebugMode = not self.isDebugMode
key = str(event.keycode)
if key in self.keys:
self.keys.remove(key)
def lighting_effect(self):
for i in range(0,random.randint(5,15)):
self.canvas.create_image(random.randint(0,self.canvas.winfo_width()),random.randint(0,self.canvas.winfo_height()), image = self.img_light_effect,anchor = NW,tags="lighting")
def display_lighting_effect(self):
if(self.lightingTimer == -1):
self.lightingTimer = time.time()
self.lighting_effect()
else:
now = time.time()
if(now - self.lightingTimer > 4.0):
self.lightingTimer = -1
elif(now - self.lightingTimer > 2.0):
lightings = self.canvas.find_withtag("lighting")
for light in lightings:
self.canvas.delete(light)
def keyPressHandler(self, event):
key = str(event.keycode)
self.keys.add(key)
def handle_receive(self):
while True:
try:
data = self.client_socket.recv(1024)#데이터가 수신될 때까지 대기함
except:
print("서버 연결 끊김")
break
#byte를 문자열로 디코딩
data = data.decode()
#디코딩된 문자열은 json 형식으로 되어 있음
#네트워크 딜레이로 인해 누적된 패킷중 마지막 패킷만 추출하기
data = data[:data.rfind('}')+1]
data = data[data.rfind('{'):]
#패킷의 형식 오류 확인
if len(data) > 0 and data[0] =='{' and data[-1] == '}':
#json 형식을 딕셔너리 타입으로 변환 후 저장
self.user_data = json.loads(data)
else:
pass#print('패킷오류')
if __name__ == '__main__':
#사용자 아이디, 서버 IP, 연결포트 설정
GameClient('CKang','127.0.0.1',4000) # GameClient 생성한다.
서버 코드입니다.
import socket
import argparse
import threading
import time
import random
import json
user_data = {'enemy':[]}
user_socket = {}
user_image_size = [280,249]
user_missile_size = [50,10]
user_enemy_size = [140, 65]
user_wnd_size = [1440,960]
#frame, 위치 x, 위치 y, 미사일, 점수
user_ini_data = [0,100,480-user_image_size[1]/2,[],0]
def handle_sendToUsers():
while 1:
#모든 미사일의 위치값 업데이트
for key in user_data.keys():
if key != 'enemy':
for i in range(len(user_data[key][3])):
user_data[key][3][i][0] += 9
#미사일, 적 충돌 체크
isChecked = False
for e in user_data['enemy']:
if e[0] < user_data[key][3][i][0]+user_missile_size[0] and user_data[key][3][i][0]+user_missile_size[0] < e[0] + user_enemy_size[0] and e[1] < user_data[key][3][i][1] and user_data[key][3][i][1] < e[1] + user_enemy_size[1]:
e[0] = -1440 #충돌된 적은 화면을 벗어나게 하기
user_data[key][4] += 1#사용자 점수 증가
#print(key,'명중:',user_data[key][4])
isChecked=True
if isChecked:
user_data[key][3][i][0] = 2880#충돌된 미사일은 화면을 벗어나게 하기
#화면을 벗어난 미사일 데이터 제거
user_data[key][3] = [e for e in user_data[key][3] if e[0]<user_wnd_size[0]]
#각 사용자의 프레임 번호 업데이트
for key in user_data.keys():
if key != 'enemy':
user_data[key][0] += 1
#적 생성
if (random.randint(0,100) == 0 and len(user_data.keys())>1):
user_data['enemy'].append([user_wnd_size[0],random.randint(10, user_wnd_size[1]-10)])
# 적 이동
for e in user_data['enemy']:
e[0] -= 4
# 화면 밖으로 벗어난 적 데이터 제거
user_data['enemy'] = [e for e in user_data['enemy'] if e[0]>-user_enemy_size[0]]
#각 서버에 저장된 사용자 데이터를 모든 클라이언트에 전송
for con in user_socket.values():
#딕셔너리 타입을 json 포맷 변환후 바이트형으로 인코딩
user_encode_data = json.dumps(user_data).encode()
#사용자에게 전송함
try:
con.sendall(user_encode_data)
except:
pass
time.sleep(0.03)
def handle_receive(client_socket, addr, user):
missile_time = time.time()
while 1:
try:
#클라이언트로부터 데이터 올때까지 대기함
data = client_socket.recv(1024)
#클라이언트로부터 받은 데이터를 문자열 변환후 입력 키 값들을 분리한다.
key_list = data.decode().split(',')
for key in key_list:
if key == '39' and user_data[user][1] < (user_wnd_size[0]-user_image_size[0]/2): # right direction key
user_data[user][1] = user_data[user][1] + 5
elif key == '37' and -user_image_size[0]/2 < user_data[user][1]: # left direction key
user_data[user][1] = user_data[user][1] - 5
elif key == '38' and -user_image_size[1]/2 < user_data[user][2]: # up direction key
user_data[user][2] = user_data[user][2] - 5
elif key == '40' and user_data[user][2] < (user_wnd_size[1]-user_image_size[1]/2): # down direction key
user_data[user][2] = user_data[user][2] + 5
elif key == '32':
now = time.time()
#미사일 연사속도 조절
if (now-missile_time) > 0.4:#0.4초
missile_time = now
#사용자의 현재 위치값을 이용하여 미사일 최초 위치 설정하고 미사일 위치 데이터 저장
user_data[user][3].append([user_data[user][1]+user_image_size[0]/2+95,user_data[user][2]+user_image_size[1]/2+12])
except:
print(user,': 연결 끊김')
break
del user_data[user]
del user_socket[user]
client_socket.close()
if __name__ == '__main__':
host = "127.0.0.1"
port = 4000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen(5)# 클라이언트 최대 접속 수
send_thread = threading.Thread(target=handle_sendToUsers)
send_thread.daemon = True
send_thread.start()
print('게임서버를 시작합니다.')
while 1:
try:
client_socket, addr = server_socket.accept()#클라이언트 접속 까지 대기
except KeyboardInterrupt:
for user, con in user_socket:
con.close()
server_socket_tcp.close()
print("Keyboard interrupt")
break
user = client_socket.recv(1024)
user = user.decode()
if user in user_data:
print(user,'와 동일한 접속자가 있습니다.(접속실패)')
client_socket.sendall("Fail: 동일한 사용자가 존재합니다.".encode())
else:
print(user,'서버에 접속하였습니다.')
client_socket.sendall("Success".encode())
user_socket[user] = client_socket
user_data[user] = list(user_ini_data)
receive_thread = threading.Thread(target=handle_receive, args=(client_socket, addr,user))
receive_thread.daemon = True
receive_thread.start()
from tkinter import *
import pygame
class GameSound:
def __init__(self):
window = Tk() # 윈도우 생성
window.title("게임사운드") # 제목을 설정
window.geometry("640x480") # 윈도우 크기 설정
window.resizable(0,0)
self.canvas = Canvas(window, bg = "white")
self.canvas.pack(expand=True,fill=BOTH)
window.bind("<KeyPress>",self.keyPressHandler)
window.bind("<KeyRelease>",self.keyReleaseHandler)
#pygame 에서 music vs sound
# music: 배경음악 재생을 위해 사용
# sound: 효과음을 위해 사용
#BG sound.
pygame.init()
pygame.mixer.music.load("bgm.wav") #Loading File Into Mixer
pygame.mixer.music.play(-1) #Playing It In The Whole Device
self.canvas.create_text(320,400,font="Times 15 italic bold",text="Sound Example")
#Effect sound
self.sounds = pygame.mixer
self.sounds.init()
self.s_effect1 = self.sounds.Sound("gunsound.mp3")
while True:
#
window.after(33)
window.update()
def keyReleaseHandler(self, event):
if event.keycode in self.keys:
self.keys.remove(event.keycode)
def keyPressHandler(self, event):
self.s_effect1.play()
self.keys.add(event.keycode)
GameSound()
tkinter에서는 사운드와 관련된 클래스 및 함수를 제공하지 않습니다. 그래서 코드에서는 pygame 모듈을 이용하여 사운드 재생방법을 설명을 하도록 하겠습니다. (앞서 설명된 게임 기본 코드 구조 위에서 추가된 내용들만 설명)
사운드의 종류는 크게 기본적으로 배경사운드, 이펙트사운드로 구분될 수 있습니다. 배경사운드는 게임의 전체적인 분위기를 담당하고 이펙트 사운드는 그 순간의 효과 또는 느낌(타격감, 행위(점프, 스피드업, 아이템 획득), 죽음 등)을 담당하며, 사운드를 게임의 진행에 있어서 필수적인 요소입니다. 그래서 위 코드에서는 배경사운드와 이펙트사운드의 재생 방법에 대해서 설명하도록 하겠습니다.
pygame에서 사운드 재생 방법은 music와 sound 이렇게 2가지를 제공하고 있습니다.
차이점은 music의 경우 하나의 재생만 가능하고 만약 다른 파일을 load() 해서 재생하면 기존 재생은 멈추게 되며, 반대로 sound 는 여러 개가 동시 재생이 가능하고 같은 사운드의 중첩해서 재생도 가능합니다.
그리고 music의 경우 파일이 저장된 장치(ssd 또는 hard disk 등)에서 재생을 하지만 sound의 경우 파일 전체를 메모리에 가져온 후 재생하게 됩니다. 즉 music은 큰 용량을 가진 음악 파일 재생에 유리하며 sound의 경우 작은 용량과 짧은 사운드(이펙트)에 유리합니다.
그러므로 music의 경우 배경사운드에 이용되고 sound의 경우 이펙트사운드에 사용합니다.
위 소스에 대한 주요 코드 설명은 아래와 같습니다.
pygame 모듈을 사용하기 위해 pygame을 import 한다.(line 2)
pygame을 사용하기 위해서는 pygame.init()함수를 호출해야한다.(line 21)
위 코드에서 music.load(), music.play() 함수를 이용하여 배경음악을 무한반복 재생한다.(line 22, 23)
effect sound를 위해 sounds 를 초기화 한다.(line 28)
이펙트 사운드 'gunsound.mp3' 파일을 읽고 sound 객체를 반환한다. sound 객체는 이펙트 사운드는 게임중 재생 및 멈춤을 컨트롤할 수 있다.
line 41에서 이펙트 사운드를 재생한다.
위 코드만으로 간단히 게임에서 필요한 음악 재생은 가능하며 좀 더 다양한 기능을 구현하기 위해서는 아래의 링크를 참조하기 바랍니다.
파이썬에서 tkinter 모듈을 이용하여 GUI 기반의 윈도우 프로그래밍을 할 수 있다. tkinter 에서 가장 기본 단위를 위젯이라고 하며 위젯의 종류에는 버튼(button), 라벨(label), 콤보박스(combobox), 캔버스(canvas) 등 이 있다. 게임을 위해서는 이미지를 이용한 애니메이션이 기본이되며 이와 같은 기능 구현은 캔버스에서 가능하다. 그러므로 작성된 코드에는 사운드를 제외한 대부분의 기능들을 캔버스만을 이용한다.
게임을 만들때 일반적으로코드는 3가지 영역(준비, 무한루프, 이벤트처리)으로 구분되어 작성된다.
준비영역에서는 윈도우를 생성, 사운드 및 이미지 파일등의 리소스 파일 로딩, 그리고 게임에 필요한 변수들을 초기화하는 코드가 작성된다(line5 ~12).
무한루프 영역에서는 게임이 실행되는 동안 변화되는 데이터, 이미지의 위치, 사운드 on / off 등에 대한 코드를 작성한다.(line 15~17)
마지막으로 이벤츠처리 영역에는 키 또는 마우스 입력이 있을 때 처리해야 될 코드(이미지 위치값 이동, 사운드 on/off등)를 작성한다.(위 예제코드에는 이벤트처리 영역이 미 작성됨)
준비영역은 최초 게임 코드가 시작될 시 한번만 실행되며, 무한루프 영역은 계속 반복해서 실행된다.(일반적으로 while 을 이용한 무한루프형식으로 작성됨) 이벤트처리 영역은 키 또는 마우스 입력이 있을 시 실행되고 실행이 끝나면 다시 무한루프 영역의 코드가 실행되며, 이러한 방식을 Event-based Programming 방식이라고 한다. 무한루프 영역에서 키 또는 마우스 입력이 있을 시 바로 이벤트처리 영역으로 이동하는 것이 아닌 루프영역에서 마지막 코드인 window.update() 함수 호출시 이벤트처리 영역으로 이동하여 코드가 실행된다.
준비 영역과 무한루프 영역에 대한 코드 설명은 아래와 같다.
윈도우 프로그래밍에서 가장 먼저 해야될 일은 윈도우 만들기다. Tk 클래스를 이용하여 윈도우 창을 생성할 수 있다.(line 5) 그리고 title, geometry 함수를 이용하여 윈도우 이름과 크기를 설정한다. (line 6~7)
다음으로 캔버스 객체를 생성하는데, 생성시 캔버스가 붙여질 윈도우를 인수로 작성한다.(line 9) 윈도우 틀이 만들어지고 그 앞에 캔버스를 붙인 후 캔버스 위에 원하는 위치에 이미지를 그릴 수 있는 것이다.
윈도우도 하나의 위젯으로 생각할 수 있는데 위젯 간에 서로 붙이기 위해서는 붙일 대상 위젯 지정(line 9)과 실제 붙이는 동작이 있어야 되며 그 중에 하나가 pack() 이라는 함수이다.(line 10) 옵션은 윈도우에 꽉차게 그리기 위해 expand를 true 그리고 fill=BOTH로 설정하였다.
캔버스는 도화지 처럼 무엇이든 그릴 수 있는 함수를 제공하고 있다. 그 함수들의 이름은 "create_"로 시작되며 create_rectangle, create_oval, create_line, create_text, create_image 등 그리는데 필요한 다양한 함수들을 이용할 수 있다. 위 코드에서는 create_text() 함수를 이용하여 "Basic Form" 글자를 320, 400 위치에 작성한다.
캔버스에 무언가를 그리기 위해서는 기준좌표와 기준점이 어디 인지 알아야 된다. 아래 그림은 width: 640, hegiht: 480 크기로 그려진 윈도우의 좌표를 보여준다. 윈도우의 기준좌표는 왼쪽 위가 되며 0, 0 의 좌표값을 가진다. 그리고 그려지는 글자는 글자크기의 가운데 위치가 기준점이 된다. 그래서 글자는 코드에서 작성된 320, 400 의 위치에 글자의 기준점이 맞춰서 그려지게 된다.
무한루프 영역에서는 윈도우 클래스의 after()와 update() 함수가 호출된다(line 16, 17). after() 에는 33 이 작성되어 있으며 33은 대기 시간(단위: ms) 나타낸다. 위 코드에서는 게임영상을 초당 30 프레임로 동작하게 하기위해 33 을 작성하였다. 마지막으로 update() 함수 호출시 변화된 내용의 반영과 앞서 설명된 키 또는 마우스 이벤트들이 처리된다.
무한루프 영역에서 이미지의 위치 변경 코드가 작성되어 있으면 그 코드가 호출된 시점에서 바로 이미지의 위치변경이 되는 것은 아니다. 이미지의 위치변경 코드가 많을 경우 코드마다 바로 위치변경하여 화면에 보여주게 되면 컴퓨터가 많은 부하가 발생할 수 있다. 그렇게 때문에 변화된 내용들을 모아서 특정 시간 마다 한번에 처리하면 부하를 줄일 수 있다. 모든 게임에서 그래픽 애니메이션은 이러한 방식으로 처리됨을 명심하기 바란다.
앞서 설명된 내용들은 다른 프로그래밍 언어(c++, java, visual basic등)로 게임을 제작할 때도 동일한 영역과 동작방식을 가지고 있어서 위 내용들을 이해하면 다른 프로그래밍 언어를 이용한 게임제작도 쉽게 할 수 있다. 모든 게임 개발 및 동작을 위한 코드 구조가 위와 동일하다고 생각하면된다(Unity, UnReal Engine 역시 기본구조는 동일함).