""" tobi.py """ #【メモ】 #3D座標は x = 0, y = 0が画面の中心で、z = 0が画面の手前。キャラクターのx, y座標はキャラクターの中心。 #大きさ調整は、自分のz座標を-10、スクリーンのz座標を0(自分とスクリーンの距離10)とした場合、キャラクターのz座標が20の場合には3((20+10)÷10)で割り、30の場合には4((30+10)÷10)で割る計算。 #キャラクターの座標は、自分とスクリーンの距離を10とした場合、キャラクターのz座標が30の場合には、画面の中心の座標から、xを4((30+10)÷10)で割ったものを足し、キャラクターの大きさの半分を4((30+10)÷10)で割ったものをxから引く。yも同様。 #視点の調整は「係数 * z」を x や y に足す(その x や y をさらに上記の通りz座標の奥行きに合わせて割り算する)。 import math import pygame import random import sys from pygame.locals import * pygame.init() pygame.key.set_repeat(5, 5) SURFACE = pygame.display.set_mode((400, 300)) FPSCLOCK = pygame.time.Clock() pygame.display.set_caption("Tobishokunin's Commuting") sysfont = pygame.font.SysFont(None, 24) #イメージ設定 image_background = pygame.image.load("background.jpg") image_title = pygame.image.load("title.png") image_start = pygame.image.load("start.png") image_final_bonus = pygame.image.load("final_bonus.png") image_gameover = pygame.image.load("gameover.png") image_shadow = pygame.image.load("shadow.gif") #効果音 sound_frog_jump = pygame.mixer.Sound("frog_jump.wav") sound_frog_jump.set_volume(0.2) sound_bird_fly = pygame.mixer.Sound("bird_fly.wav") sound_bird_fly.set_volume(0.25) sound_jump = pygame.mixer.Sound("jump.wav") sound_jump.set_volume(0.25) sound_eat = pygame.mixer.Sound("eat.wav") sound_eat.set_volume(0.3) sound_omusubi = pygame.mixer.Sound("omusubi.wav") sound_omusubi.set_volume(0.6) sound_crash = pygame.mixer.Sound("crash.wav") sound_crash.set_volume(0.4) sound_miss = pygame.mixer.Sound("miss.wav") sound_miss.set_volume(0.6) sound_finish = pygame.mixer.Sound("finish.wav") sound_finish.set_volume(0.6) #BGM pygame.mixer.music.load("bgm.wav") class Character: """キャラクターーオブジェクト""" def __init__(self): self.status = 0 self.jump_status = 0 self.image_type = 0 self.x = 999 self.y = 999 self.z = 0 self.vx = 0 self.vy = 0 self.vz = 0 self.width = 0 self.height = 0 self.image_man = pygame.image.load("man.gif") self.image_man_bright = pygame.image.load("man_bright.gif") self.image_man_failed = pygame.image.load("man_failed.gif") self.image_man_smile = pygame.image.load("man_smile.gif") self.image_tree = pygame.image.load("tree.gif") self.image_stopper = pygame.image.load("stopper.gif") self.image_frog = pygame.image.load("frog.gif") self.image_frog_jump1 = pygame.image.load("frog_jump1.gif") self.image_frog_jump2 = pygame.image.load("frog_jump2.gif") self.image_bird_right = pygame.image.load("bird_right.gif") self.image_bird_left = pygame.image.load("bird_left.gif") self.image_omusubi = pygame.image.load("omusubi.gif") self.image_house = pygame.image.load("house.gif") self.image = self.image_frog #表示オン def on(self, image_type, x = 0, y = 0, z = 200): self.status = 1 self.image_type = image_type if self.image_type == 0: #とび職の男 self.jump_status = 1 self.x = 0 self.y = 100 self.z = 0 self.vx = 0 self.vy = 0 self.vz = 0 self.width = 40 self.height = 100 self.image = self.image_man if self.image_type == 1: #樹木 self.x = x self.y = 10 self.z = z self.vx = 0 self.vy = 0 self.vz = -1 self.width = 120 self.height = 280 self.image = self.image_tree if self.image_type == 2: #ストッパー self.x = random.randint(0,3) * 100 - 150 self.y = 115 self.z = z self.vx = 0 self.vy = 0 self.vz = -1 self.width = 100 self.height = 70 self.image = self.image_stopper if self.image_type == 3: #蛙 self.x = random.randint(-160, 160) self.y = 110 self.z = z self.vx = 0 self.vy = 0 self.vz = -1 self.width = 80 self.height = 80 self.image = self.image_frog if self.image_type == 4: #鳥 self.x = random.randint(-160, 160) self.y = random.randint(-110, 40) self.z = z self.vx = 0 self.vy = (random.randint(0, 1) * 2 - 1) * 5 self.vz = -2 self.width = 80 self.height = 80 self.image = self.image_bird_right if self.image_type == 10: #おむすび self.x = random.randint(-170, 170) self.y = 130 self.z = z self.vx = 0 self.vy = 0 self.vz = -1 self.width = 60 self.height = 40 self.image = self.image_omusubi if self.image_type == 20: #家 self.x = -20 self.y = -5 self.z = z self.vx = 0 self.vy = 0 self.vz = -1 self.width = 360 self.height = 310 self.image = self.image_house #移動 def move(self, x, y, z, status): if self.image_type == 1 or self.image_type == 2 or self.image_type == 10 or self.image_type == 20: #樹木、ストッパー、おむすび、家 self.z += self.vz self.y += self.vy elif self.image_type == 3: #蛙 self.status += 1 self.x += self.vx self.y += self.vy self.z += self.vz if self.status >= 5 and self.status < 20 and random.randint(0, 10) == 0: #ジャンプ前 self.status = 20 if self.status >= 20 and self.status <= 22: #ジャンプ動作1 self.image = self.image_frog_jump1 elif self.status == 23: #ジャンプ動作2に移行 if self.z <= 150: sound_frog_jump.play() self.image = self.image_frog_jump2 self.vy = -26 self.vz = -2 if self.x > x: self.vx = random.randint(-10, 0) else: self.vx = random.randint(0, 10) elif self.status >= 24: #ジャンプ動作2 self.vy += 2 if self.y > 110: self.y = 110 self.status = 1 self.vx = 0 self.vy = 0 self.vz = -1 self.image = self.image_frog elif self.image_type == 4: #鳥 self.x += self.vx self.y += self.vy self.z += self.vz if self.x > x: self.vx -= 1 elif self.x < x: self.vx += 1 if (self.x >= 100 and self.vx > 0) or (self.x <= -100 and self.vx < 0): self.vx = int(self.vx / 2) if self.vx >= 0: self.image = self.image_bird_right else: self.image= self.image_bird_left if self.y < -110: self.y = -110 self.vy = -self.vy elif self.y > 40: self.y = 40 self.vy = -self.vy if self.z == 40: sound_bird_fly.play() #スクリーンより前面(z < 0)の場合には消去 if self.z < 0: self.status = 0 self.z = 0 if self.image_type == 10: return(3, self.x) else: return(0, self.x) #衝突判定 if status == 1 and self.image_type <= 9: #敵衝突ミス if self.status > 0 and self.z <= 1 and abs(self.x - x) < self.width / 2 + 10 and abs(self.y - y) < self.height / 2 + 40: self.z = 0 return(1, self.x) else: return(0, self.x) elif status < 200 and self.image_type == 10: #おむすび取得 if self.status > 0 and self.z <= 1 and abs(self.x - x) < self.width / 2 + 10 and abs(self.y - y) < self.height / 2 + 40: self.status = 0 self.z = 0 return(2, self.x) else: return(0, self.x) else: return(0, self.x) #とび職男ミス def failure(self): if self.image_type == 0: self.image = self.image_man_failed self.status += 1 if self.status >= 260: self.status = 10 self.image = self.image_man #とび職男ミス復帰(点滅表示) def blink(self): if self.image_type == 0: self.status += 1 if self.status % 10 < 5: self.image = self.image_man_bright else: self.image = self.image_man if self.status >= 70: self.status = 1 self.image = self.image_man #とび職男フィニシュ def finish(self): if self.image_type == 0: self.image = self.image_man_smile def location_in_view(x1, y1, z1, size_x, size_y, adj_x, adj_y): """ 3D座標から2D上の座標算出 """ x2 = int((x1 + adj_x * z1) / (z1/10 + 1)) + 200 - int(size_x * 0.5 / (z1/10 + 1)) y2 = int((y1 + adj_y * z1) / (z1/10 + 1)) + 150 - int(size_y * 0.5 / (z1/10 + 1)) return (x2, y2) def size_in_view(z1, size_x, size_y): """ 3D座標から2D上のサイズ算出 """ size_x2 = int(size_x / (z1/10 + 1)) size_y2 = int(size_y / (z1/10 + 1)) return (size_x2, size_y2) def draw_background(adjust_x, adjust_y, counter): """ 背景描写 """ a, b = location_in_view(0, 0, 200, 0, 0, adjust_x, adjust_y) #遠景(雪山) SURFACE.blit(image_background,(a - 300, b - 320)) a, b = location_in_view(-200, 150, 200, 0, 0, adjust_x, adjust_y) #地面 pygame.draw.rect(SURFACE, (150, 150, 100), (0, b, 400, 300)) pygame.draw.polygon(SURFACE,(128, 128, 128), #道路 (location_in_view(-200, 150, 200, 0, 0, adjust_x, adjust_y),location_in_view(200, 150, 200, 0, 0, adjust_x, adjust_y), location_in_view(200, 150, 0, 0, 0, adjust_x, adjust_y),location_in_view(-200, 150, 0, 0, 0, adjust_x, adjust_y))) for i in range (200, 0, -16): #道路縞 a = i - 8 - counter % 16 if i - counter % 16 >= 0 else 0 b = i - 15 - counter % 16 if i - 8 - counter % 16 >= 0 else 0 pygame.draw.polygon(SURFACE,(110, 110 , 110), (location_in_view(-200, 150, a, 0, 0, adjust_x, adjust_y), location_in_view(200, 150, a, 0, 0, adjust_x, adjust_y), location_in_view(200, 150, b, 0, 0, adjust_x, adjust_y), location_in_view(-200, 150, b, 0, 0, adjust_x, adjust_y))) def draw_character(image, x, y, z, width, height, adjust_x, adjust_y): """ キャラクター描写 """ #影表示 if width < 300: #幅の狭いキャラクターのみ影表示 c, d = size_in_view(z, width, width) s_image = pygame.transform.scale(image_shadow, (c, d)) s_image.set_alpha(80) a, b = location_in_view(x, 147, z, width, width, adjust_x, adjust_y) SURFACE.blit(s_image, (a, b)) #キャラクター表示 c, d = size_in_view(z, width, height) if width >= 300 and width <= 350: #ロゴのみスムース縮小 image = pygame.transform.smoothscale(image, (c, d)) else: image = pygame.transform.scale(image, (c, d)) a, b = location_in_view(x, y, z, width, height, adjust_x, adjust_y) SURFACE.blit(image, (a, b)) def score_indication(score, best_score, counter, damage): """ スコア表示 """ image = sysfont.render( #ベストスコア "Best Score", True, (255, 255, 255)) SURFACE.blit(image, (10, 2)) image = sysfont.render( "{:0>6}".format(best_score), True, (255, 255, 255)) SURFACE.blit(image, (36, 17)) image = sysfont.render( #スコア "Your Score", True, (255, 255, 255)) SURFACE.blit(image, (120, 2)) image = sysfont.render( "{:0>6}".format(score), True, (255, 255, 255)) SURFACE.blit(image, (147, 17)) image = sysfont.render( #距離 "Distance", True, (255, 255, 255)) SURFACE.blit(image, (230, 2)) distance = 1200 - int(counter / 5) if counter <= 6000 else 0 image = sysfont.render( "{:0>4}".format(distance), True, (255, 255, 255)) SURFACE.blit(image, (248, 17)) image = sysfont.render( "m", True, (255, 255, 255)) SURFACE.blit(image, (286, 17)) image = sysfont.render( #ダメージ "Damage", True, (255, 255, 255)) SURFACE.blit(image, (322, 2)) image = sysfont.render( "{:0>3}".format(damage), True, (255, 255, 255)) SURFACE.blit(image, (340, 17)) image = sysfont.render( "%", True, (255, 255, 255)) SURFACE.blit(image, (371, 17)) def main(): """ メインルーチン """ #変数初期設定(基本) a, b, c, d = 0, 0, 0, 0 round_data = [(50, 0, 0), (50, 0, 0), (0, 80, 0), (0, 70, 0), (50, 80, 0), (50, 70, 0), (10, 0, 0), (15, 0, 0), (0, 0, 100), (0, 0, 50), (0, 30, 0), (0, 30, 0), (30, 0, 50), (30, 0, 50), (50, 50, 50), (50, 50, 50), (30, 40, 0), (15, 40, 40), (15, 30, 30), (0, 0, 0)] character = [Character() for i in range(50)] character_copy = [] counter = 0 best_score = 0 score = 0 point_x = 0 fullscreen_flag = 0 #マウスカーソル非表示 pygame.mouse.set_visible(False) while True: #変数初期設定(タイトル) a = 0 title_z = 150 adjust_x = 0 adjust_y = 5 gameover_flag = 3 damage = 0 counter_character = 1 counter_point = 0 combo = 0 tree_lr = -1 for i in range(len(character)): character[i].status = 0 character[0].on(0) rate_stopper, rate_frog, rate_bird = round_data[0] #樹木初期表示オン for i in range (0, 201, 20): character[counter_character].on(1, tree_lr * 270, 0, i) tree_lr = -tree_lr counter_character += 1 #背景描写 draw_background(adjust_x, adjust_y, counter) #キャラクター描写 character_copy = character for i in range(1, len(character_copy)): if character_copy[i].status > 0: draw_character(character_copy[i].image, character_copy[i].x, character_copy[i].y, character_copy[i].z, character_copy[i].width, character_copy[i].height, adjust_x, adjust_y) #スコア表示 score_indication(score, best_score, counter, damage) #クレジット表示 image = sysfont.render( "© 2017 KUNISAN.JP", True, (255, 255, 255)) SURFACE.blit(image, (125, 270)) #画面コピー filler = SURFACE.copy() #ループ1(タイトル画面) while gameover_flag == 3: #ウィンドウ閉じるボタン for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() elif event.type == KEYDOWN and event.key == K_F11: #F11でフルスクリーンモードへの切り替え fullscreen_flag = not fullscreen_flag if fullscreen_flag: screen = pygame.display.set_mode((400, 300), FULLSCREEN) else: screen = pygame.display.set_mode((400, 300), 0) #キー入力判定 pressed = pygame.key.get_pressed() if pressed[K_SPACE]: a = 1 elif pressed[K_SPACE] == 0 and a == 1: a = 0 gameover_flag = 0 #画面再描写(フルスクリーン対応) SURFACE.blit(filler, (0,0)) #タイトル3D表示 title_z -= 5 if title_z > 0 else 0 draw_character(image_title, 0, -52, title_z, 350, 103, adjust_x, adjust_y) draw_character(image_start, 0, 52, title_z, 300, 35, adjust_x, adjust_y) #画面アップデート pygame.display.update() FPSCLOCK.tick(30) #変数初期設定(ゲーム) score = 0 damage = 0 counter = 0 #BGM再生 pygame.mixer.music.set_volume(0.4) pygame.mixer.music.play() #ループ2(ゲームメインループ) while gameover_flag == 0: #ラウンドデータ読み込み if counter % 300 == 0 and counter < 6000: rate_stopper, rate_frog, rate_bird = round_data[int(counter/300)] #カウンター if character[0].status < 200 and counter < 6000: counter += 1 score += 10 #ウィンドウ閉じるボタン for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() elif event.type == KEYDOWN and event.key == K_F11: #F11でフルスクリーンモードへの切り替え fullscreen_flag = not fullscreen_flag if fullscreen_flag: screen = pygame.display.set_mode((400, 300), FULLSCREEN) else: screen = pygame.display.set_mode((400, 300), 0) #キー入力判定&とび職人移動 if character[0].status < 200 and counter <= 5970: pressed = pygame.key.get_pressed() if pressed[K_LEFT] and character[0].x > -180: character[0].x -= 20 if pressed[K_RIGHT] and character[0].x < 180: character[0].x += 20 if pressed[K_SPACE] and character[0].jump_status == 1: sound_jump.play() character[0].jump_status = 2 character[0].vy = -26 if character[0].jump_status == 2: character[0].y += character[0].vy character[0].vy += 2 if character[0].y >= 100: character[0].y = 100 character[0].jump_status = 1 adjust_x = character[0].x / 30 adjust_y = character[0].y / 20 if counter > 5970: if character[0].x > 0: character[0].x -= 10 elif character[0].x < 0: character[0].x += 10 #キャラクター表示オン if character[0].status < 200: #樹木表示オン if counter % 20 == 0: if character[counter_character].status == 0: character[counter_character].on(1, tree_lr * 280) tree_lr = -tree_lr counter_character += 1 if counter_character >= len(character): counter_character = 1 #ストッパー表示オン if rate_stopper > 0 and random.randint(0, rate_stopper) == 0: if character[counter_character].status == 0: character[counter_character].on(2) counter_character += 1 if counter_character >= len(character): counter_character = 1 #蛙表示オン if rate_frog > 0 and random.randint(0, rate_frog) == 0: if character[counter_character].status == 0: character[counter_character].on(3) counter_character += 1 if counter_character >= len(character): counter_character = 1 #鳥表示オン if rate_bird > 0 and random.randint(0, rate_bird) == 0: if character[counter_character].status == 0: character[counter_character].on(4) counter_character += 1 if counter_character >= len(character): counter_character = 1 #おむすび表示オン if counter > 300 and counter < 5900 and counter % 600 == 150: if character[counter_character].status == 0: character[counter_character].on(10) counter_character += 1 if counter_character >= len(character): counter_character = 1 #家表示オン if counter == 5802: character[counter_character].on(20) if counter_character >= len(character): counter_character = 1 #背景描写 draw_background(adjust_x, adjust_y, counter) #キャラクター移動 if character[0].status < 200 and counter < 6000: for i in range(1, len(character)): if character[i].status > 0: a, b = character[i].move(character[0].x, character[0].y, character[0].z, character[0].status) if a == 1: #ミス処理開始 character[0].status = 200 character[0].jump_status = 2 character[0].vx = 0 character[0].vy = -24 damage += 25 sound_crash.play() sound_miss.play() if a == 2: #得点処理 combo += 1 score += 1000 * combo counter_point = 1 point_x = b - 40 sound_eat.play() sound_omusubi.play() if a == 3: #おむすび取り逃し(コンボ消滅) combo = 0 #ミス処理 if character[0].status >= 200: character[0].failure() if damage == 100 and character[0].status == 259: gameover_flag = 2 elif character[0].status >= 10: character[0].blink() #フィニッシュ処理 if counter == 6000: character[0].finish() score += 30000 gameover_flag = 1 sound_finish.play() #キャラクター描写順番調整(キャラクターオブジェクトをコピーしてZ座標を降順で並び替え) character_copy = character character_copy[0].z = -100 #とび職人を強制的に一番手前に character_copy = sorted(character_copy, key=lambda i: i.z, reverse=True) character_copy[len(character_copy)-1].z = 0 #とび職人のZ座標を0に戻す #キャラクター描写 for i in range(len(character_copy)): if character_copy[i].status > 0: draw_character(character_copy[i].image, character_copy[i].x, character_copy[i].y, character_copy[i].z, character_copy[i].width, character_copy[i].height, adjust_x, adjust_y) #ポイント表示 if counter_point > 0: counter_point += 1 a, b = location_in_view(point_x, 100, 0, 0, 0, adjust_x, adjust_y) image = sysfont.render( "{:^10}".format("1000 x " + str(combo)), True, (255, 0, 0)) SURFACE.blit(image, (a, b)) if counter_point > 30: counter_point = 0 #スコア表示 if score > best_score: best_score = score score_indication(score, best_score, counter, damage) #画面アップデート pygame.display.update() FPSCLOCK.tick(30) #BGM再生確認&リピート if pygame.mixer.music.get_busy() == 0: pygame.mixer.music.play() #仕事場到着またはゲームオーバー表示 if gameover_flag == 1: SURFACE.blit(image_final_bonus, (25, 70)) else: SURFACE.blit(image_gameover, (50, 129)) #画面コピー filler = SURFACE.copy() #変数初期設定(ゲームオーバー) a = 0 counter2 = 0 #ループ3(ゲームオーバー) while gameover_flag <= 2: #カウンター(ゲームオーバー処理用) counter2 += 1 if counter2 >= 210: gameover_flag = 3 #BGMミュート&停止 if counter2 <= 120: pygame.mixer.music.set_volume(0.4 - counter2 / 300) else: pygame.mixer.music.stop() #ウィンドウ閉じるボタン for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() elif event.type == KEYDOWN and event.key == K_F11: #F11でフルスクリーンモードへの切り替え fullscreen_flag = not fullscreen_flag if fullscreen_flag: screen = pygame.display.set_mode((400, 300), FULLSCREEN) else: screen = pygame.display.set_mode((400, 300), 0) #キー入力判定 pressed = pygame.key.get_pressed() if pressed[K_SPACE] == 0 and a == 0: a = 1 elif pressed[K_SPACE] and a == 1: a = 2 elif pressed[K_SPACE] == 0 and a == 2: a = 0 gameover_flag = 3 #画面再描写(フルスクリーン対応) SURFACE.blit(filler, (0,0)) #画面アップデート pygame.display.update() FPSCLOCK.tick(30) if __name__ == '__main__': main()