- 更新日: 2019年08月06日
 - 公開日: 2018年07月23日
 
Pythonでゲームを作れるのか?詳しく解説をしてみた

Pythonでできることの一つ「ゲーム開発」。
今回は、Pythonを使ったゲーム感の全体像とサンプルをご紹介します。
Python初心者からPython上級者まで楽しんで頂けると思いますよ。
【Python入門】Pythonでゲームを作れるのか?詳しく解説をしてみた
Pythonとゲーム

ゲームというと娯楽的、余剰的なイメージで「遊び」を連想しますが、Pythonゲームの場合は別格。老若男女、Python学習の取っ掛かりにゲームは非常に有効です。
変数や関数、引数といった文法から入るより、ゲームで手を動かすことで「Pythonができるようになるとこんなことができるんだ」とイメージが湧きやすく、多くの方がモチベーションUPに。 そして結果的にPythonスキルが身につくと、人工知能やデータ解析など仕事としてのスキルUPにも。 こうした理由でPython学習初期に、ゲームに興味を持つ方が多いように思います。
しかし、これは 『Python × ゲーム』 のほんの一部分・・・
ご承知のように現在人工知能開発は活発に行われており、実は Python×ゲーム もこれに関係。 Pythonで書かれたゲームを機械にプレイさせてそれを機械学習、そしてゲームクリアの最適化が実験中。これが何の役に立つかというと、レースゲームからは自動運転技術への活用、パズルゲームでは配達の最適化などが検証。つまり単にゲームを楽しむというのではなく、実務としてPythonを使ったゲームが求められているんですね。
こうしたことから、これからPythonを学ぶにあたって、ゲーム開発も経験しておきたいですね。
「Python × ゲーム × 人工知能」の参考文献:Building a Simple Self-Driving Car Simulator
Pythonで開発されたゲーム事例
これからPythonでゲームを作成&体験する前に、Pythonでどれだけのゲームができるか知っておきたいですよね。以下にPython製のゲームをいくつかリストアップ。パソコンで本稿閲覧中でお時間のある方は、一度リンク先のゲーム見て下さい。
EVE Online/シミュレーション
パイレーツオブカリビア/ロープレ
Galcon/シューティング
SC2VN - The eSports Visual Novel/アニメ系
はじめて上記のゲームを見た時「えっ、Pythonでここまでできるの?」と正直思いました。さすがに C++ に比べると同時処理能力が低くなると言われてはいますが、それでも見応え・やりごたえは十分で感動です。
さすがにこのレベルのもの作ろうと思うと大変ですが、Python初心者でも楽しめるゲーム・プログラムは多数公開されています。
【Pythonのゲーム集サイト】
Pythonでゲーム開発する手順
実際にPythonでゲーム開発を行う場合は、以下の3手法がメイン。
- Pythonのゲーム用ライブラリを利用
 - Python用 Game Engine を利用
 - 純粋にPythonコードでゲーム開発
 
Webやアプリ開発でフレームワークやライブラリが使われるように、ゲーム開発においてもある程度パッケージ化されたソフトを使うと開発の手間が省略できたり、メンテナンス性が向上します。 その結果、ライブラリやGame Engineもたくさん開発されて、それぞれ特色を持ったモノが公開中。以下に主だったソフトをご紹介しますね。
| << ライブラリ >> | 
| PyGame | 
| Arcade | 
| Kivy | 
| Pyglet | 
| << Game Engine >> | 
| cocos2d | 
| Panda3D | 
| Ren'Py | 
| Godot | 
ライブラリとは:特定のタスクを実行できるコードセット。
ゲームエンジンとは:いくつかのライブラリをもったソフト。つまりゲームエンジンの方が多機能。
実際にどのフレームワークもしくは Game Engine を選択するかは、開発するゲームがただのPython学習用なのか、それともAndroidやiOSとしてリリースするのか、Web公開するのかにもよって異なります。 強いて言えば、みんなが使っている人気のソフト(PyGameなど)を使うと困った時に役立つ情報も多いと思いますし、誰かに教えてもらうことも可能でしょう。
CodeCamp提供のPythonデータサイエンスコース
当メディアを運営しているCodeCampではPythonデータサイエンスコースを現役エンジニアのマンツーマンレッスンという形で提供しています。このコースの特徴は
- 数学的な知識がない状態から実務で使えるプログラミングスキルを獲得できる
 - 「データ収集」「データ加工」「モデル構築」を習得できる
 - 企業のマーケター向けの研修にも取り入れられているカリキュラム
 
詳細はこちらから確認してみてください!▶︎https://www.lp.codecamp.jp/python
簡単なゲームを作成(Snake Game)
ゲーム開発の初級というと「じゃんけんゲーム*」などが思い浮かびますが、今回は「スネーク・ゲーム」。 蛇を動かして餌をゲットするシンプルなゲームです。 このスネークゲームをあえて以下の3パターンで作成。 これによってPythonゲームのライブラリ感やプレーンテキストの使用感などをご確認頂けると思います。
【スネークゲームの作成パターン】
- ゲームライブラリなしでゲーム作成
 - random や turtle などの基本的ライブラリを使って
 - PyGameを使って
 
ゲームライブラリなしで作成
<< 実行結果 >>
import curses
import random
curses.initscr()
win = curses.newwin(24,70,0,0)
win.border(0)
curses.noecho()
curses.curs_set(0)
win.keypad(1)
win.nodelay(1)
win.timeout(100)
score = 0
snake = [[12,13],[12,14],[12,15]]
food = [20,20]
win.addch(food[0],food[1],'$')
key = curses.KEY_LEFT
win.addstr(0, 30, 'Snake Game!!')
while True:
    win.addstr(0, 3, '点数: ' + str(score) + ' ')                                  
    win.timeout(100)
    newKey = win.getch()
    if newKey not in [curses.KEY_LEFT,curses.KEY_RIGHT,curses.KEY_UP,curses.KEY_DOWN]:
        key = key
    else:
        key = newKey
    if snake[0][0] == 0 or snake[0][0] == 23 or snake[0][1] == 0 or snake[0][1] == 69:
        break
    if snake[0] in snake[1:]:
        break
    newHead = [snake[0][0],snake[0][1]]
    if key == curses.KEY_DOWN:
        newHead[0] += 1
    if key == curses.KEY_UP:
        newHead[0] -= 1
    if key == curses.KEY_LEFT:
        newHead[1] -= 1
    if key == curses.KEY_RIGHT:
        newHead[1] += 1
    snake.insert(0,newHead)
    if snake[0] == food:
        score += 1
        food = []
        food = [random.randint(1,22),random.randint(1,68)]
        win.addch(food[0],food[1],'$')
    else:
        tail = snake.pop()
        win.addch(tail[0],tail[1],' ')
    win.addch(snake[0][0],snake[0][1],curses.ACS_CKBOARD)
print ('Score: '+str(score))
curses.endwin()
こちらは curses というPythonの基礎学習には登場してこないライブラリを利用して snakeゲーム を実現。基本的にターミナル(コマンドプロンプト)からでないとプログラムを実行できませんでした。
次に基本的なライブラリを使って同じような snakeゲームを作ってみます。
基本的なライブラリを使ってゲーム作成
<< 実行結果 >>
from turtle import Turtle, Screen
import random
import time
SIZE = 20
class Square:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def drawself(self, turtle):
        turtle.goto(self.x - SIZE // 2 - 1, self.y - SIZE // 2 - 1)
        turtle.begin_fill()
        for _ in range(4):
            turtle.forward(SIZE - SIZE // 10)
            turtle.left(90)
        turtle.end_fill()
class Food:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.is_blinking = True
    def changelocation(self):
        self.x = random.randint(0, SIZE) * SIZE - 200
        self.y = random.randint(0, SIZE) * SIZE - 200
    def drawself(self, turtle):
        if self.is_blinking:
            turtle.goto(self.x - SIZE // 2 - 1, self.y - SIZE // 2 - 1)
            turtle.begin_fill()
            for _ in range(4):
                turtle.forward(SIZE - SIZE // 10)
                turtle.left(90)
            turtle.end_fill()
    def changestate(self):
        self.is_blinking = not self.is_blinking
class Snake:
    def __init__(self):
        self.headposition = [SIZE, 0]  
        self.body = [Square(-SIZE, 0), Square(0, 0), Square(SIZE, 0)]  
        self.nextX = 1
        self.nextY = 0
        self.crashed = False
        self.nextposition = [self.headposition[0] + SIZE * self.nextX, self.headposition[1] + SIZE * self.nextY]
    def moveOneStep(self):
        if Square(self.nextposition[0], self.nextposition[1]) not in self.body:
            self.body.append(Square(self.nextposition[0], self.nextposition[1]))
            del self.body[0]
            self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
            self.nextposition = [self.headposition[0] + SIZE * self.nextX, self.headposition[1] + SIZE * self.nextY]
        else:
            self.crashed = True
    def moveup(self):
        self.nextX, self.nextY = 0, 1
    def moveleft(self):
        self.nextX, self.nextY = -1, 0
    def moveright(self):
        self.nextX, self.nextY = 1, 0
    def movedown(self):
        self.nextX, self.nextY = 0, -1
    def eatFood(self):
        self.body.append(Square(self.nextposition[0], self.nextposition[1]))
        self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
        self.nextposition = [self.headposition[0] + SIZE * self.nextX, self.headposition[1] + SIZE * self.nextY]
    def drawself(self, turtle):
        for segment in self.body:
            segment.drawself(turtle)
class Game:
    def __init__(self):
        self.screen = Screen()
        self.artist = Turtle(visible=False)
        self.artist.up()
        self.artist.speed("slowest")
        self.snake = Snake()
        self.food = Food(100, 0)
        self.counter = 0
        self.commandpending = False
        self.screen.tracer(0)
        self.screen.listen()
        self.screen.onkey(self.snakedown, "Down")
        self.screen.onkey(self.snakeup, "Up")
        self.screen.onkey(self.snakeleft, "Left")
        self.screen.onkey(self.snakeright, "Right")
    def nextFrame(self):
        self.artist.clear()
        if (self.snake.nextposition[0], self.snake.nextposition[1]) == (self.food.x, self.food.y):
            self.snake.eatFood()
            self.food.changelocation()
        else:
            self.snake.moveOneStep()
        if self.counter == 10:
            self.food.changestate()
            self.counter = 0
        else:
            self.counter += 1
        self.food.drawself(self.artist)
        self.snake.drawself(self.artist)
        self.screen.update()
        self.screen.ontimer(lambda: self.nextFrame(), 100)
    def snakeup(self):
        if not self.commandpending:
            self.commandpending = True
            self.snake.moveup()
            self.commandpending = False
    def snakedown(self):
        if not self.commandpending:
            self.commandpending = True
            self.snake.movedown()
            self.commandpending = False
    def snakeleft(self):
        if not self.commandpending:
            self.commandpending = True
            self.snake.moveleft()
            self.commandpending = False
    def snakeright(self):
        if not self.commandpending:
            self.commandpending = True
            self.snake.moveright()
            self.commandpending = False
game = Game()
screen = Screen()
screen.ontimer(lambda: game.nextFrame(), 100)
screen.mainloop()
先ほどの snakeゲーム に比べると随分コードが長くなり、オブジェクト指向の構成となっています。snakeの動作制御は、グラフィカル・ライブラリの turtle を使用。こちらはターミナルに限らず IDE からでも実行可能なプログラムです。
そして最後にゲームライブラリを使って snake ゲームをプレイしてみます。
PyGameを使ってゲーム作成
<< 実行結果 >>
from pygame.locals import *
from random import randint
import pygame
import time
class Apple:
    x = 0
    y = 0
    step = 44
    def __init__(self,x,y):
        self.x = x * self.step
        self.y = y * self.step
    def draw(self, surface, image):
        surface.blit(image,(self.x, self.y)) 
class Player:
    x = [0]
    y = [0]
    step = 44
    direction = 0
    length = 3
    updateCountMax = 2
    updateCount = 0
    def __init__(self, length):
       self.length = length
       for i in range(0,2000):
           self.x.append(-100)
           self.y.append(-100)
       self.x[1] = 1*44
       self.x[2] = 2*44
    def update(self):
        self.updateCount = self.updateCount + 1
        if self.updateCount > self.updateCountMax:
            for i in range(self.length-1,0,-1):
                self.x[i] = self.x[i-1]
                self.y[i] = self.y[i-1]
            if self.direction == 0:
                self.x[0] = self.x[0] + self.step
            if self.direction == 1:
                self.x[0] = self.x[0] - self.step
            if self.direction == 2:
                self.y[0] = self.y[0] - self.step
            if self.direction == 3:
                self.y[0] = self.y[0] + self.step
            self.updateCount = 0
    def moveRight(self):
        self.direction = 0
    def moveLeft(self):
        self.direction = 1
    def moveUp(self):
        self.direction = 2
    def moveDown(self):
        self.direction = 3 
    def draw(self, surface, image):
        for i in range(0,self.length):
            surface.blit(image,(self.x[i],self.y[i])) 
class Game:
    def isCollision(self,x1,y1,x2,y2,bsize):
        if x1 >= x2 and x1 <= x2 + bsize:
            if y1 >= y2 and y1 <= y2 + bsize:
                return True
        return False
class App:
    windowWidth = 800
    windowHeight = 600
    player = 0
    apple = 0
    def __init__(self):
        self._running = True
        self._display_surf = None
        self._image_surf = None
        self._apple_surf = None
        self.game = Game()
        self.player = Player(3) 
        self.apple = Apple(5,5)
    def on_init(self):
        pygame.init()
        self._display_surf = pygame.display.set_mode((self.windowWidth,self.windowHeight), pygame.HWSURFACE)
        pygame.display.set_caption('Pygame pythonspot.com example')
        self._running = True
        self._image_surf = pygame.image.load("block.jpg").convert()
        self._apple_surf = pygame.image.load("block.jpg").convert()
    def on_event(self, event):
        if event.type == QUIT:
            self._running = False
    def on_loop(self):
        self.player.update()
        for i in range(0,self.player.length):
            if self.game.isCollision(self.apple.x,self.apple.y,self.player.x[i], self.player.y[i],44):
                self.apple.x = randint(2,9) * 44
                self.apple.y = randint(2,9) * 44
                self.player.length = self.player.length + 1
        for i in range(2,self.player.length):
            if self.game.isCollision(self.player.x[0],self.player.y[0],self.player.x[i], self.player.y[i],40):
                print("You lose! Collision: ")
                print("x[0] (" + str(self.player.x[0]) + "," + str(self.player.y[0]) + ")")
                print("x[" + str(i) + "] (" + str(self.player.x[i]) + "," + str(self.player.y[i]) + ")")
                exit(0)
        pass
    def on_render(self):
        self._display_surf.fill((0,0,0))
        self.player.draw(self._display_surf, self._image_surf)
        self.apple.draw(self._display_surf, self._apple_surf)
        pygame.display.flip()
    def on_cleanup(self):
        pygame.quit()
    def on_execute(self):
        if self.on_init() == False:
            self._running = False
        while( self._running ):
            pygame.event.pump()
            keys = pygame.key.get_pressed() 
            if (keys[K_RIGHT]):
                self.player.moveRight()
            if (keys[K_LEFT]):
                self.player.moveLeft()
            if (keys[K_UP]):
                self.player.moveUp()
            if (keys[K_DOWN]):
                self.player.moveDown()
            if (keys[K_ESCAPE]):
                self._running = False
            self.on_loop()
            self.on_render()
            time.sleep (50.0 / 1000.0);
        self.on_cleanup()
if __name__ == "__main__" :
    theApp = App()
    theApp.on_execute()
ゲーム用ライブラリ PyGame を使っても、結構長くなりますね。 snake の動作制御は PyGame が担ってくれています。
3パターンを比較して
3つともシンプルな snake ゲームなのに、どれもコードが長くてビックリですよね。 Pythonでゲーム開発する際は、基本的には 2番目と 3番目のようなオブジェクト指向が採用されます。
snake の動きを制御する up や down などの命令文も、同じ動作結果なのに違うコードで表現していますので勉強になりますね。このように同じ snake ゲームでも、使うライブラリが違えばコードの書き方も変わります。はじめの内は利用者の多い PyGame を使ってみるのが賢明かもしれませんね。
本格的なゲームを作成

せっかくなので少し本格的なゲームをご紹介。
Street Pyther
download: https://code.google.com/archive/p/street-pyghter/downloads
7年前に公開された Python製のストⅡ です。コードを見てみると、プレイするのとは違って一つ一つの動作をコードで表現するのって結構大変そうですよね。ダウンロードサイトから zipファイル をダウンロードして、srcフォルダ内の streetpyghter.py を実行してみて下さい。 ちなみにPythonで書かれたストⅡも機械学習で最適化することが可能ですね*。
OpenAIのgym
download: https://gym.openai.com/
こちらはゲームを機械学習で楽しむためのライブラリ。ちょっとPython上級者向けの内容になりますが、Pythonで人工知能や機械学習スキルを習得したいと考えている方も多いと思いますのでご紹介。ライブラリ「gym」さえインストールできればサンプルは比較的簡単に動くと思います。
<< 実行した場合の結果 >>

【gymのサンプルコード Jupyter Notebook向け】
import numpy as np
import gym
import random
env = gym.make("Taxi-v2")
env.render()
action_size = env.action_space.n
print("Action size", action_size)
state_size = env.observation_space.n
print("State size", state_size)
qtable = np.zeros((state_size, action_size))
print(qtable)
total_episodes = 100000
total_test_episodes = 1000
max_steps = 99
learning_rate = 0.7
gamma = 0.618
epsilon = 1.0
max_epsilon = 1.0
min_epsilon = 0.01
decay_rate = 0.01
↑ こちらは機械学習の学習回数やパラメーターを設定。
for episode in range(total_episodes):
    state = env.reset()
    step = 0
    done = False
    for step in range(max_steps):
        exp_exp_tradeoff = random.uniform(0,1)
        if exp_exp_tradeoff > epsilon:
            action = np.argmax(qtable[state,:])
        else:
            action = env.action_space.sample()
        new_state, reward, done, info = env.step(action)
        qtable[state, action] = qtable[state, action] + learning_rate * (reward + gamma * 
                                    np.max(qtable[new_state, :]) - qtable[state, action])
        state = new_state
        if done == True: 
            break
    episode += 1
    epsilon = min_epsilon + (max_epsilon - min_epsilon)*np.exp(-decay_rate*episode)
env.reset()
rewards = []
for episode in range(total_test_episodes):
    state = env.reset()
    step = 0
    done = False
    total_rewards = 0
    print("****************************************************")
    print("EPISODE ", episode)
    for step in range(max_steps):
        env.render()
        action = np.argmax(qtable[state,:])
        new_state, reward, done, info = env.step(action)
        total_rewards += reward
        if done:
            rewards.append(total_rewards)
            print ("Score", total_rewards)
            break
        state = new_state
env.close()
print ("Score over time: " +  str(sum(rewards)/total_test_episodes))
Scoreの点数が上がるように学習の最適化をしたいですね。
TensorKart
download: https://github.com/kevinhughes27/TensorKart
こちらは マリオカート64 にディープラーニングのフレームワーク「TensorFlow」を用いて運転の最適化を図るプログラム。このプログラムで初めて知ったのですが、 Nintendo64 のエミュレータ(mupen64plus)ってあるんですね、スゴイです。 こちらのプログラムの実行方法は、 mupen64plus のインストールなどもありますので、 GitHub をご参考下さい。
\AIエンジニアに必要なスキルが身に付く/
まとめ
「Pythonでゲーム」、簡単なものからグラフィックスを駆使した本格的なものまでピンキリです。仮に「ゲーム開発」を主に考えるならC++やCの方がいいと言われますが、ゲーム開発をするのかデータ分析をするのか人工知能を開発するのか定かでない場合は、Pythonがいいかもしれませんね。
今回は「Pythonのゲーム感」全体像をお伝えするためにコードの解説などは控えさせて頂きました。「自分でもコードを理解したい」「オリジナルのゲームを作ってみたい」と思った方、まずはPythonの基礎からはじめてみませんか? CodeCamp ならオンライン×マンツーマンでPythonスキルをスムーズに習得できると思いますよ。
- この記事を書いた人
 - オシママサラ
 














