""" 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()