シューティングゲーム？完成品（一応）

#概要
習いたてのPythonを使って、ゲームを作ってみました。
上を見すぎず作ろうとしたつもりでしたが、これでも無力さを感じました、
![ダウンロード.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638182/a1860942-df72-b05b-77bf-a032a66b99f8.gif)

ゲームとしては、敵の玉に当たらないようにしながらすべての玉を壊すというものです。

しかし正直なところ、これは結論から言うと失敗に終わっています。（笑）

---

#参考
こちらのサイトを参考にさせていただきました。
[selfって？]https://www.sejuku.net/blog/64106
[キー入力]https://www.rumadra.com/2019/06/14/python-tkinter-keyboard/#i-3
[移動入力]https://algorithm.joho.info/programming/python/pygame-keyevent-move-character/

---

#簡単な仕組み

---

##敵の作成
```python
class ENEMY:
    def __init__(self,x,y,dx,dy,color):
        self.x = x
        self.y = y
        self.dx = dx
        self.dy = dy
        self.color = color
    

    def display(self,canvas):
        self.x = self.x + self.dx
        self.y = self.y + self.dy

        canvas.create_oval(self.x -10, self.y-10, self.x +10, self.y +10, fill =self.color , width = 0, tag = self.color)
    
    def move(self):
        if self.x+10 >= canvas.winfo_width():
            self.dx = random.random()*-1
            
        if self.x-10 <= 0:
            self.dx = random.random()
        
        if self.y+10 >= 900:
            self.dy = random.random()*-1
    
        if self.y-1 <= 0:
            self.dy = random.random()

    def erase(self,canvas):
        canvas.delete(self.color)
```
敵を作っていますが、特に変わったところはありません。

---

##プレイヤー
```python
class PLAYER:
    def __init__(self,x,y,color):
        self.x=x
        self.y=y
        self.color=color

    def display(self,canvas):
        canvas.create_polygon(self.x, self.y, self.x-8, self.y+16, self.x+8, self.y+16, fill=self.color, width=1, tag="a")
        
    def move(self,dx,dy):
        global player_place_x,player_place_y
        if self.x+dx >= canvas.winfo_width():
            dx = 0
        elif self.x+dx <= 0:
            dx = 0
        elif self.y+dy >= canvas.winfo_height()-16: #三角形の頂点からの距離調整の16
            dy = 0
        elif self.y+dy <= 0:
            dy = 0
        else:
            self.x = self.x + dx
            self.y = self.y + dy
        player_place_x = self.x
        player_place_y = self.y

    def erase(self,canvas):
        canvas.delete("a")
```
ここも特に変わったところはなく、入力処理によって得た変化量を足して表示を繰り返しているだけです。

---

##移動入力の処理
問題はここからです。
知識量が追い付かなくなってきました。

```python
def KeyUp(event):
    global playerdy
    playerdy= -0.2

def KeyDown(event):
    global playerdy
    playerdy = 0.2

def ResetUp(event):
    global playerdy
    playerdy = 0

def ResetDown(event):
    global playerdy
    playerdy = 0

def KeyRight(event):
    global playerdx
    playerdx = 0.2

def KeyLeft(event):
    global playerdx    
    playerdx = -0.2

  
def ResetRight(event):
    global playerdx
    playerdx=0

def ResetLeft(event):
    global playerdx
    playerdx=0
```
矢印キーを押し続けている間だけ動かそうと思ったところうまくいかず、さらには`pygame`を使わずに`canvas`と`tkinter`だけで行くという謎のこだわりもあって手こずりました。

---

##苦難の元
何につまづいたかというと、これです。

```python
canvas = tk.Canvas(root, width = 1700, height = 900, bg = "black")
canvas.place(x = 0, y = 0)


canvas.focus_set()  #必須注意
```
この下に書いてあるよくわからん一文が必要だったんです。それがわからずに一体何日悩んだことか、、、
これはどうやらキャンバスをアクティブにする一文なのかな？
本当にこれのせいで大量の時間を奪われました（笑）

---

##プレイヤーの攻撃
プレイヤーが敵を攻撃する要素としてミサイルを実現しました。といってもスペースキーを入力したところから真っすぐ飛ぶだけですが（笑）

```python
class BULLET:
    def __init__(self,x,y):
        self.x = x
        self.y = y
        self.for_del = None
    
    def howmany(self):
        if self.y <= -10:
            return False
        else:
            return True

    def delete(self,canvas):
        canvas.delete(self.for_del)       

    def display(self,canvas):  
        self.for_del=canvas.create_oval(self.x-1,self.y-4,self.x+1,self.y+4,fill="white",width=0)
        self.y = self.y -5
```
まぁ、ここはいいとして次に行きましょう。

---

##THE 苦難再び

```python
def ammo_loop():
    a=0
    b=0
    label = tk.Label(root,text=("残り敵数"+str((len(balls)))+"体"),font=("helvetica",18))
    label.place(x=100,y=100)
    for i in range(len(bullet)):
        for j in range(len(balls)):
            if len(balls) - b <= 0:
                break
            elif (bullet[len(bullet)-i-1].x >= balls[len(balls)-j-1].x-10) and (bullet[len(bullet)-i-1].x <= balls[len(balls)-j-1].x+10) and (bullet[len(bullet)-i-1].y >= balls[len(balls)-j-1].y-10) and (bullet[len(bullet)-i-1].y <= balls[len(balls)-j-1].y+10):
                balls[j].erase(canvas)
                bullet[i].delete(canvas)
                del bullet[len(bullet)-i-1]
                del balls[len(balls)-j-1]
                b = b + 1
                break
    how_long = len(bullet)
    for j in range(len(bullet)):
        count = bullet[how_long-j-1].howmany()
        if  how_long - a <= 0:
            break
        elif count == False:
            del bullet[how_long-j-1]
            a = a + 1   
```
ん～。自分でもよくわかってません。（笑）
残りの敵の数を表示したり、当たり判定をしてます。なににつまづいたかというと、ミサイルの消去です。

ここでのつまづきポイントは<font color=red>ミサイルを消すとリストがバグる！</font>ってことです。
なぜこれが起きたのか僕なりに考えてみたところ、`for文`ですべてのミサイルの要素に適用したところ、一つミサイルが当たってインスタンスが破棄されたときに<font color=red>元の要素の数にずれが生じる</font>からです。そこで、`len`メソッドを用いることで無理やり回避しています。

---

#反省、失敗

-まともに動かない時がある
これは、最悪な失敗でした。当たり判定がしっかりと働かずに弾や敵がその場に残ることがありました。

-時間のせいで未完成
本当は敵がプレイヤーに向けてミサイルを撃ってきたり、効果音をつけたり、360°撃てるようにしたかったけれど、実力不足で無理でした、、、

-コードがぐちゃぐちゃ
途中から自分でもわからなくなったところがあったので、、、

---

#学んだこと
-手を動かせ（試せ）
-調べろ
-見やすく努めろ

---

#まとめ
という感じで完成？という感じです。（笑）それもこれも、自分の勉強不足のせいで一つ一つに時間がかかったせいで、納得のいかない完成度になってしまったからです。今回の難所はおおまかに2つだったのですが、僕の力ではこいつらにバカみたいな時間を食われてしまいました。いつかリベンジしてやる、、、

---

#一応ソースコード

```python
#coding = utf-8

#各種インポート
import tkinter as tk
import random

player_place_x = 0
player_place_y = 0
playerdx=0
playerdy=0
check = 0
reloading_time=15
ammo_x = None
ammo_y = None
count = True
reload = 0
global player_life,counter,x,y,a,b,k,t,counting
a = random.randint(0,1700)
b = random.randint(0,600)
x=0
y=0
k=1700
t=900
counter=0
counting=0
player_life = 3
#ランダムな位置へのモブの設置

class ENEMY:
    def __init__(self,x,y,dx,dy,color):
        self.x = x
        self.y = y
        self.dx = dx
        self.dy = dy
        self.color = color
    

    def display(self,canvas):
        self.x = self.x + self.dx
        self.y = self.y + self.dy

        canvas.create_oval(self.x -10, self.y-10, self.x +10, self.y +10, fill =self.color , width = 0, tag = self.color)
    
    def move(self):
        if self.x+10 >= canvas.winfo_width():
            self.dx = random.random()*-1
            
        if self.x-10 <= 0:
            self.dx = random.random()
        
        if self.y+10 >= 900:
            self.dy = random.random()*-1
    
        if self.y-1 <= 0:
            self.dy = random.random()

    def erase(self,canvas):
        canvas.delete(self.color)



class PLAYER:
    def __init__(self,x,y,color):
        self.x=x
        self.y=y
        self.color=color

    def display(self,canvas):
        canvas.create_polygon(self.x, self.y, self.x-8, self.y+16, self.x+8, self.y+16, fill=self.color, width=1, tag="a")
        
    def move(self,dx,dy):
        global player_place_x,player_place_y
        if self.x+dx >= canvas.winfo_width():
            dx = 0
        elif self.x+dx <= 0:
            dx = 0
        elif self.y+dy >= canvas.winfo_height()-16: #三角形の頂点からの距離調整の16
            dy = 0
        elif self.y+dy <= 0:
            dy = 0
        else:
            self.x = self.x + dx
            self.y = self.y + dy
        player_place_x = self.x
        player_place_y = self.y

    def erase(self,canvas):
        canvas.delete("a")

def KeyUp(event):
    global playerdy
    playerdy= -0.2

def KeyDown(event):
    global playerdy
    playerdy = 0.2

def ResetUp(event):
    global playerdy
    playerdy = 0

def ResetDown(event):
    global playerdy
    playerdy = 0

def KeyRight(event):
    global playerdx
    playerdx = 0.2

def KeyLeft(event):
    global playerdx    
    playerdx = -0.2

  
def ResetRight(event):
    global playerdx
    playerdx=0

def ResetLeft(event):
    global playerdx
    playerdx=0


def KeySpace(event):    #スペース入力受付
    global check
    check = 1

def KeySpaceReset(event):   #スペースリリース受付
    global check
    check = 0


class BULLET:
    def __init__(self,x,y):
        self.x = x
        self.y = y
        self.for_del = None
    
    def howmany(self):
        if self.y <= -10:
            return False
        else:
            return True

    def delete(self,canvas):
        canvas.delete(self.for_del)       

    def display(self,canvas):  
        self.for_del=canvas.create_oval(self.x-1,self.y-4,self.x+1,self.y+4,fill="white",width=0)
        self.y = self.y -5

def ammo_loop():
    a=0
    b=0
    label = tk.Label(root,text=("残り敵数"+str((len(balls)))+"体"),font=("helvetica",18))
    label.place(x=100,y=100)
    for i in range(len(bullet)):
        for j in range(len(balls)):
            if len(balls) - b <= 0:
                break
            elif (bullet[len(bullet)-i-1].x >= balls[len(balls)-j-1].x-10) and (bullet[len(bullet)-i-1].x <= balls[len(balls)-j-1].x+10) and (bullet[len(bullet)-i-1].y >= balls[len(balls)-j-1].y-10) and (bullet[len(bullet)-i-1].y <= balls[len(balls)-j-1].y+10):
                balls[j].erase(canvas)
                bullet[i].delete(canvas)
                del bullet[len(bullet)-i-1]
                del balls[len(balls)-j-1]
                b = b + 1
                break
    how_long = len(bullet)
    for j in range(len(bullet)):
        count = bullet[how_long-j-1].howmany()
        if  how_long - a <= 0:
            break
        elif count == False:
            del bullet[how_long-j-1]
            a = a + 1    

def reset():
    global counter,a,b
    if counter >= 400:
        a = random.randint(0,1700)
        b = random.randint(0,900)
        counter = 0
    else:
        counter = counter + 1
        print("print"+str(counter))

def attack():
    global a,b,k,t,counting

    canvas.delete("attack_a")
    canvas.delete("attack_b")
    canvas.create_rectangle(a,-k,a+70,1700-k,fill="red",width = 0,tag="attack_a")
    canvas.create_rectangle(-t,b,900-t,b+70,fill="red",width = 0,tag = "attack_b")
    k = k -10
    t = t -10




def judge():
    global player_place_x,player_place_y,player_life,k,t
    for i in balls:
        if (player_place_x >= i.x-10 and player_place_x <= i.x+10 and player_place_y >= i.y-10 and player_place_y <= i.y+10) or (player_place_x >= a and player_place_x <= a+90 and player_place_y >= -k and player_place_x <= 1700-k) or (player_place_x >= -t and player_place_x <= 900 -t and player_place_y >= b and player_place_y <= b + 70):
            player_life = player_life - 1
            
            if player_life == 0:
                label = tk.Label(root, text =("Game Over!!"), font =("helvetica", 28))
                label.place(x= 850, y= 450)
                label2 = tk.Label(root, text = ("5秒後に終了します"),font = ("helvetica",30))
                label2.place(x=850, y=400)
                root.after(5000,quit)

        
        
def create():   #ミサイルの生成
    global player_place_x,player_place_y
    bullet.append(BULLET(player_place_x,player_place_y))

def reload_time():  #リロード判断
    global reloading_time,reload
    reloading_time = reloading_time - 1
    if reloading_time <= 0:
        reload = 1

def Reload_reset(): #リロードリセット
    global reloading_time
    reloading_time = 50


def player_do():
    player.erase(canvas)
    player.display(canvas)
    player.move(playerdx,playerdy)

def quit():
    root.destroy()

def loop():
    global reload,check

    reset()
    attack()
    judge()
    ammo_loop()
    for i in balls:
        player_do() #プレイヤーの動き
        i.erase(canvas)
        i.display(canvas)
        i.move()
    for i in bullet:
        i.delete(canvas)
        i.display(canvas)


    if check == 1:  #スペース入力判断
        if reload == 1: #リロード判断
            create()
            Reload_reset()
            reload = 0
        else:
            reload_time()
    else:
        reload_time()
    root.after(10,loop)

bullet = []

player = PLAYER(850,820,"white")

balls = [
    ENEMY(random.randint (0,1700),random.randint (0,900),1,-1,"blue"),
    ENEMY(random.randint (0,1700),random.randint (0,900),1,-1,"red"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"purple"),
    ENEMY(random.randint (0,1700),random.randint (0,900),1,1,"yellow"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"silver"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"pink"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"yellow"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"white"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"brown"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"pink"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"gold"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"white"),
    ENEMY(random.randint (0,1700),random.randint (0,900),-1,1,"green")
]


root = tk.Tk()
root.geometry("1700x900")

canvas = tk.Canvas(root, width = 1700, height = 900, bg = "black")
canvas.place(x = 0, y = 0)


canvas.focus_set()  #必須注意
canvas.bind("<Up>",KeyUp)
canvas.bind("<Down>",KeyDown)
canvas.bind("<Right>",KeyRight)
canvas.bind("<Left>",KeyLeft)

canvas.bind("<KeyRelease-Up>",ResetUp)
canvas.bind("<KeyRelease-Down>",ResetDown)
canvas.bind("<KeyRelease-Left>",ResetLeft)
canvas.bind("<KeyRelease-Right>",ResetRight)

canvas.bind("<Key-space>",KeySpace)
canvas.bind("<KeyRelease-space>",KeySpaceReset)

root.after(10,loop)

root.mainloop()
```