pythonで動くものを作りたい!と思い、『Pythonでつくるゲーム開発入門講座』(実践編含む)で勉強中です。
なぜpythonのtkinterなのか→私の最終目的はゲーム開発ではなく、スクレイピングなどのデータ収集や、データ分析、業務効率化など。よって、ゲーム開発に適したC++やUnity?でなく、pythonでゲーム作成。pygameライブラリを使わないのも同じ理由。
スクレイピングやデータ分析、機械学習をピンポイントで勉強しても、今のところ応用方法が思いつかないので、遠回りになるけれど、ゲームを作成しながら、プログラミングを覚えようという魂胆。NCPを動かすのに、機械学習とかディープラーニングを組み込んでそのあたりの実践経験も積めたらなぁとか欲張ってます。
ある程度学習出来たので、サンプルコードを参考(というか改造)にして、不思議なダンジョンゲームっぽいものを作ってみました。
風来の試練やトルネコを思い浮かべる方が多いと思いますが、私は不思議なダンジョンと言えば、チョコボの不思議なダンジョンを思い浮かべます。(あとはテリーのワンダーランド?)最終的にはチョコボに近い'何か'を作りたいでうすね。
とりあえず、コードとキャラチップをgithubに保存してみました。
キャラチップはぴぽや倉庫さんより拝借いたしました。
マップを自動生成する、maze_maker.pyとゲーム本体のtkmaze.pyに分けてみました。
import random
class maze_maker:
"""
ダンジョンを自動生成する
"""
def __init__(self,MAZE_W,MAZE_H):
self.MAZE_W = MAZE_W
self.MAZE_H = MAZE_H
self.maze = [[0]*self.MAZE_W for y in range(self.MAZE_H)]
self.DUNGEON_W = MAZE_W*3
self.DUNGEON_H = MAZE_H*3
self.dungeon = [[0]*self.DUNGEON_W for y in range(self.DUNGEON_H)]
def make_maze(self):
"""
迷路を作る
"""
XP = [ 0, 1, 0,-1]
YP = [-1, 0, 1, 0]
#周囲の柱
for x in range(self.MAZE_W):
self.maze[0][x] = 1
self.maze[self.MAZE_H-1][x] = 1
for y in range(self.MAZE_H):
self.maze[y][0] = 1
self.maze[y][self.MAZE_W-1] = 1
#中を空っぽに
for y in range(1,self.MAZE_H-1):
for x in range(1,self.MAZE_W-1):
self.maze[y][x] = 0
#柱
for y in range(2,self.MAZE_H-2,2):
for x in range(2,self.MAZE_W-2,2):
self.maze[y][x] = 1
for y in range(2,self.MAZE_H-2,2):
for x in range(2,self.MAZE_W-2,2):
d = random.randint(0,3)
if x > 2:
d = random.randint(0,2)
self.maze[y+YP[d]][x+XP[d]] = 1
def make_dungeon(self):
"""
迷路からダンジョンを作る
"""
self.make_maze()
for y in range(self.DUNGEON_H):
for x in range(self.DUNGEON_W):
self.dungeon[y][x] = 9
for y in range(1,self.MAZE_H-1):
for x in range(1,self.MAZE_W-1):
dx = x*3+1
dy = y*3+1
if self.maze[y][x] == 0:
if random.randint(0,99) < 20:
for ry in range(-1,2):
for rx in range(-1,2):
self.dungeon[dy+ry][dx+rx] = 0
else:
self.dungeon[dy][dx] = 0
if self.maze[y-1][x] == 0:
self.dungeon[dy-1][dx] = 0
if self.maze[y+1][x] == 0:
self.dungeon[dy+1][dx] = 0
if self.maze[y][x-1] == 0:
self.dungeon[dy][dx-1] = 0
if self.maze[y][x+1] == 0:
self.dungeon[dy][dx+1] = 0
def put_event(self):
while True:
x = random.randint(3,self.DUNGEON_W-4)
y = random.randint(3,self.DUNGEON_H-4)
if(self.dungeon[y][x] == 0):
for ry in range(-1,2):
for rx in range(-1,2):
self.dungeon[y+ry][x+rx] = 0
self.dungeon[y][x] = 1
break
for i in range(60):
x = random.randint(3,self.DUNGEON_W-4)
y = random.randint(3,self.DUNGEON_H-4)
if(self.dungeon[y][x] == 0):
self.dungeon[y][x] = random.choice([2,3,3,3,4])
"""
冥土で冥土探索。それは終わりなき旅。ゴールは無くて、敵を避けつつひたすら階段を降りていく。
1歩ごとにスコアカウント。階段を降りるとプラス。ハイスコアを目指そう!
"""
import tkinter
import maze_maker
from PIL import Image,ImageTk
import random
#キー入力
key = ''
koff = False
def key_down(e):
global key,koff
key = e.keysym
koff = False
def key_up(e):
global koff
koff = True
CHIP_SIZE = 32
DIR_UP = 3
DIR_DOWN = 0
DIR_LEFT = 1
DIR_RIGHT = 2
chara_x = chara_y = 146
chara_d = chara_a = 0
obj_a = 0
emy_num = 4
emy_list_x = [0]*emy_num
emy_list_y = [0]*emy_num
emy_list_d = [0]*emy_num
emy_list_a = [0]*emy_num
ANIMATION = [1,0,1,2]
#素材は「ぴぽや http://blog.pipoya.net/」様より
imgplayer_pass = 'image/charachip01.png'
emy_img_pass = 'image/pipo-charachip019.png'
emy2_img_pass = 'image/hone.png'
emy3_img_pass = 'image/majo.png'
emy3_kageimg_pass = 'image/majo_kage.png'
takara_img_pass = 'image/pipoya_mcset1_obj01.png'
obj_pass = 'image/pipoya_mcset1_obj02.png'
obj2_pass = 'image/pipo-hikarimono005.png'
yuka_pass = 'image/pipoya_mcset1_at_gravel1.png'
kebe_pass = 'image/pipoya_mcset1_bridge01.png'
map_data = maze_maker.maze_maker(11,7)
map_data.make_dungeon()
map_data.put_event()
tmr = 0
idx = 1
floor_count = 0
item_count = 0
pl_life=150
pl_stamina = 150
pl_damage = 0
def move_player():
global chara_x,chara_y,chara_a,chara_d,pl_stamina,pl_life
if key == 'Up':
chara_d = DIR_UP
check_wall()
if key == 'Down':
chara_d = DIR_DOWN
check_wall()
if key == 'Left':
chara_d = DIR_LEFT
check_wall()
if key == 'Right':
chara_d = DIR_RIGHT
check_wall()
check_event()
if tmr%4 == 0:
if pl_stamina > 0:
pl_stamina -= 1
else:
pl_life -= 1
if pl_life <= 0:
pl_life = 0
idx = 2
chara_a = chara_d*3 + ANIMATION[tmr%4]
def emy_set(emy_num):
while True:
x = random.randint(3,map_data.DUNGEON_W-4)
y = random.randint(3,map_data.DUNGEON_H-4)
if map_data.dungeon[y][x] == 0:
emy_list_x[emy_num] = x*CHIP_SIZE+(CHIP_SIZE//2)
emy_list_y[emy_num] = y*CHIP_SIZE+(CHIP_SIZE//2)
break
def draw_text(txt):
st_fnt = ('Times New Roman',60)
canvas.create_text(200,200,text=txt,font=st_fnt,fill='red',tag='SCREEN')
def damage_cal(pl_damage):
global pl_life,idx
if pl_life <= pl_damage:
pl_life = 0
idx = 2
else:
pl_life -= pl_damage
def move_emy(emy_num):
cy = int(emy_list_y[emy_num]//CHIP_SIZE)
cx = int(emy_list_x[emy_num]//CHIP_SIZE)
emy_list_d[emy_num] = random.randint(0,3)
if emy_list_d[emy_num] == DIR_UP:
if map_data.dungeon[cy-1][cx] != 9:
emy_list_y[emy_num] -= 32
if emy_list_d[emy_num] == DIR_DOWN:
if map_data.dungeon[cy+1][cx] != 9:
emy_list_y[emy_num] += 32
if emy_list_d[emy_num] == DIR_LEFT:
if map_data.dungeon[cy][cx-1] != 9:
emy_list_x[emy_num] -= 32
if emy_list_d[emy_num] == DIR_RIGHT:
if map_data.dungeon[cy][cx+1] != 9:
emy_list_x[emy_num] += 32
emy_list_a[emy_num] = emy_list_d[emy_num]*3 + ANIMATION[tmr%4]
if abs(emy_list_x[emy_num]-chara_x) <= 30 and abs(emy_list_y[emy_num]-chara_y) <= 30:
pl_damage = 10*random.choice([1,2,2,3,3])
damage_cal(pl_damage)
def obj_animation():
global obj_a
obj_a = ANIMATION[tmr%4]
def check_wall():
global chara_x,chara_y,chara_a,chara_d
cy = int(chara_y//CHIP_SIZE)
cx = int(chara_x//CHIP_SIZE)
if chara_d == DIR_UP:
if map_data.dungeon[cy-1][cx] != 9:
chara_y -= 32
if chara_d == DIR_DOWN:
if map_data.dungeon[cy+1][cx] != 9:
chara_y += 32
if chara_d == DIR_LEFT:
if map_data.dungeon[cy][cx-1] != 9:
chara_x -= 32
if chara_d == DIR_RIGHT:
if map_data.dungeon[cy][cx+1] != 9:
chara_x += 32
def check_event():
global chara_x,chara_y,chara_a,chara_d,idx,pl_damage
global floor_count,emy_count,item_count,pl_life,pl_stamina
cy = int(chara_y//CHIP_SIZE)
cx = int(chara_x//CHIP_SIZE)
if map_data.dungeon[cy][cx] == 1:
#ワープの魔法陣にのった
map_data.make_dungeon()
map_data.put_event()
emy_set(0)
emy_set(1)
emy_set(2)
floor_count += 1
chara_x = chara_y = 146
if map_data.dungeon[cy][cx] == 2:
#トラップの魔法陣に乗った
if item_count > 0:
item_count -= 1
map_data.dungeon[cy][cx] = 0
else:
pl_damage = 5*random.choice([1,2,2,3,4,3])
damage_cal(pl_damage)
map_data.dungeon[cy][cx] = 0
if map_data.dungeon[cy][cx] == 3:
#アイテムに接触
item_count += 1
map_data.dungeon[cy][cx] = 0
if map_data.dungeon[cy][cx] == 4:
#食料に接触
pl_stamina_recover = 5*random.choice([1,2,2,3,4,3])
if pl_stamina + pl_stamina_recover > 150:
pl_stamina = 150
else:
pl_stamina += pl_stamina_recover
map_data.dungeon[cy][cx] = 0
def split_chip(chip_pass,chip_img_x,chip_img_y):
'''
複数のチップを1単位のチップに分割する
'''
chip_list = []
for cy in range(0,chip_img_y,CHIP_SIZE):
for cx in range(0,chip_img_x,CHIP_SIZE):
chip_list.append(ImageTk.PhotoImage(Image.open(chip_pass).crop((cx,cy,cx+CHIP_SIZE,cy+CHIP_SIZE))))
return chip_list
def draw_screen():
st_fnt = ('Times New Roman',30)
canvas.delete('SCREEN')
for my in range(len(map_data.dungeon)):
for mx in range(len(map_data.dungeon[0])):
if map_data.dungeon[my][mx] != 9:
canvas.create_image(mx*CHIP_SIZE+(CHIP_SIZE//2),my*CHIP_SIZE+(CHIP_SIZE//2),image=yuka_img[8],tag='SCREEN')
if map_data.dungeon[my][mx] == 1:
canvas.create_image(mx*CHIP_SIZE+(CHIP_SIZE//2),my*CHIP_SIZE+(CHIP_SIZE//2),image=obj2_img[obj_a],tag='SCREEN')
if map_data.dungeon[my][mx] == 2:
canvas.create_image(mx*CHIP_SIZE+(CHIP_SIZE//2),my*CHIP_SIZE+(CHIP_SIZE//2),image=obj2_img[6+obj_a],tag='SCREEN')
if map_data.dungeon[my][mx] == 3:
canvas.create_image(mx*CHIP_SIZE+(CHIP_SIZE//2),my*CHIP_SIZE+(CHIP_SIZE//2),image=takara_img[5],tag='SCREEN')
if map_data.dungeon[my][mx] == 4:
canvas.create_image(mx*CHIP_SIZE+(CHIP_SIZE//2),my*CHIP_SIZE+(CHIP_SIZE//2),image=obj_img[25],tag='SCREEN')
if map_data.dungeon[my][mx] == 9:
canvas.create_image(mx*CHIP_SIZE+(CHIP_SIZE//2),my*CHIP_SIZE+(CHIP_SIZE//2),image=kabe_img[28],tag='SCREEN')
canvas.create_image(chara_x,chara_y,image=imgplayer[chara_a],tag='SCREEN')
canvas.create_image(emy_list_x[0],emy_list_y[0],image=emy_img[emy_list_a[0]],tag='SCREEN')
canvas.create_image(emy_list_x[1],emy_list_y[1],image=emy2_img[emy_list_a[1]],tag='SCREEN')
canvas.create_image(emy_list_x[2],emy_list_y[2],image=emy3_img[emy_list_a[2]],tag='SCREEN')
canvas.create_image(emy_list_x[2],emy_list_y[2],image=emy3_kageimg[emy_list_a[2]],tag='SCREEN')
canvas.create_text(1110,50,text='{} 階'.format(floor_count),font=st_fnt,fill='black',tag='SCREEN')
canvas.create_text(1110,100,text='{} 個'.format(item_count),font=st_fnt,fill='black',tag='SCREEN')
canvas.create_rectangle(1060,135,1210,160,fill='black',tag='SCREEN')
canvas.create_rectangle(1060,135,1060+pl_life,160,fill='limegreen',tag='SCREEN')
canvas.create_text(1130,148,text='LIFE',font=('Times New Roman',15),fill='white',tag='SCREEN')
canvas.create_rectangle(1060,165,1210,190,fill='black',tag='SCREEN')
canvas.create_rectangle(1060,165,1060+pl_stamina,190,fill='blue',tag='SCREEN')
canvas.create_text(1130,178,text='STAMINA',font=('Times New Roman',15),fill='white',tag='SCREEN')
canvas.create_text(1155,210,text='{}のダメージを受けた!'.format(pl_damage),font=('Times New Roman',15),fill='black',tag='SCREEN')
def main():
global tmr,koff,key,idx
tmr += 1
draw_screen()
if idx == 1:
if tmr == 1:
for emy in range(0,emy_num):
emy_set(emy)
move_player()
obj_animation()
if tmr%2 == 0:
for emy in range(0,emy_num):
move_emy(emy)
if pl_life == 0:
idx = 2
if idx == 2:
draw_text('You Died')
if tmr == 20:
idx = 1
print('hiu')
if koff == True:
key = ''
koff = False
root.after(130,main)
root = tkinter.Tk()
root.title('メイドで冥土探検!')
root.bind('<KeyPress>',key_down)
root.bind('<KeyRelease>',key_up)
canvas = tkinter.Canvas(width=1256,height=864)
imgplayer = split_chip(imgplayer_pass,96,128)
emy_img = split_chip(emy_img_pass,96,128)
emy2_img = split_chip(emy2_img_pass,96,128)
emy3_img = split_chip(emy3_img_pass,96,128)
emy3_kageimg = split_chip(emy3_kageimg_pass,96,128)
takara_img = split_chip(takara_img_pass,256,64)
obj_img = split_chip(obj_pass,256,224)
obj2_img = split_chip(obj2_pass,96,128)
yuka_img = split_chip(yuka_pass,64,160)
kabe_img = split_chip(kebe_pass,256,192)
canvas.pack()
main()
root.mainloop()
プレイ画面。
全く面白く無いですが、マップの生成、プレイヤーが足踏みしながら方向を変える、ワープゾーンがアニメーション、敵キャラの配置・移動、ダメージ計算・・・など、とりあえずダンジョンゲームに必要な基礎的な処理はまずまず揃ったのでは?と思います。ちなみに、敵への攻撃はまだ実装できていません(汗
改良したい事
- 敵がバカで適当にうろうろしているだけ。追いかけてくるとか、移動スピードを変えるとか組み込んでみたい
- 攻撃できるようにして、敵を撃破できるようにする
- (かなり発展?)プレイヤーのレベルやらステータスとか作り込む
- 【願望】敵に強化学習or機械学習を組み込む
コードもまだまだ整理出来ていない気がするので、少しずつ改良したいです。
こだわり?
ピクセル数を指定してキャラチップを分解する関数。色々情報を集めながら頑張って考えました。