Pythonでゲームを作れるのか?詳しく解説をしてみた



Pythonでゲームを作れるのか?詳しく解説をしてみた

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

目次
  1. 【Python入門】Pythonでゲームを作れるのか?詳しく解説をしてみた
  2. Pythonとゲーム
  3. Pythonで開発されたゲーム事例
  4. Pythonでゲーム開発する手順
  5. 簡単なゲームを作成(Snake Game)
  6. 本格的なゲームを作成
  7. まとめ

【Python入門】Pythonでゲームを作れるのか?詳しく解説をしてみた

Pythonとゲーム

image

画像引用:GitHub/DonkeyKong-Pygame

ゲームというと娯楽的、余剰的なイメージで「遊び」を連想しますが、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など)を使うと困った時に役立つ情報も多いと思いますし、誰かに教えてもらうことも可能でしょう。

簡単なゲームを作成(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 の動きを制御する updown などの命令文も、同じ動作結果なのに違うコードで表現していますので勉強になりますね。このように同じ snake ゲームでも、使うライブラリが違えばコードの書き方も変わります。はじめの内は利用者の多い PyGame を使ってみるのが賢明かもしれませんね。

本格的なゲームを作成

image

せっかくなので少し本格的なゲームをご紹介。

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」さえインストールできればサンプルは比較的簡単に動くと思います。

<< 実行した場合の結果 >>

image


【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 をご参考下さい。

まとめ

「Pythonでゲーム」、簡単なものからグラフィックスを駆使した本格的なものまでピンキリです。仮に「ゲーム開発」を主に考えるならC++やCの方がいいと言われますが、ゲーム開発をするのかデータ分析をするのか人工知能を開発するのか定かでない場合は、Pythonがいいかもしれませんね。

今回は「Pythonのゲーム感」全体像をお伝えするためにコードの解説などは控えさせて頂きました。「自分でもコードを理解したい」「オリジナルのゲームを作ってみたい」と思った方、まずはPythonの基礎からはじめてみませんか? CodeCamp ならオンライン×マンツーマンでPythonスキルをスムーズに習得できると思いますよ。

オシママサラ
この記事を書いた人
オシママサラ
\ 無料体験開催中!/自分のペースで確実に習得!
オンライン・プログラミングレッスンNo.1のCodeCamp