ゲームを作るならRPGツクール的なツールやUnityを使った方が何倍も簡単に作れるのですが、なんとなくPythonistaで作ってみようと思いました。
マップ作成やゲーム性を考えるのも大変だったので、非常に簡単に1画面のみで敵を倒すだけとなっています。

ゲーム紹介
▼左にアタックボタン、右に移動ボタンがあります。敵に攻撃すると消滅し、敵に当たるとノックバックします。
▼スクリプト全体です。使用している関数は以前に紹介したゲーム開発に用いたものを流用しています。
from scene import *
import math
import Map
import sound
import random
#主人公
class Chara(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'plf:AlienYellow_stand', **kwargs)
walk_textures = [Texture('plf:AlienYellow_walk1'), Texture('plf:AlienYellow_walk2')]
hit_texture = Texture('plf:AlienYellow_hit')
move_flg = False
move_direction = (0, 0)
def stand_on(self):
self.texture = Texture('plf:AlienYellow_stand')
#剣
class Sword(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'plf:SwordSilver', **kwargs)
#敵キャラ
class SlimeEnemy(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'plf:Enemy_SlimeBlue', **kwargs)
walk_textures = [Texture('plf:Enemy_SlimeBlue'), Texture('plf:Enemy_SlimeBlue_move')]
hit_texture = Texture('plf:Enemy_SlimeBlue_dead')
move_flg = True
def move_on(self):
self.move_flg = True
#石床
class StoneTile(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'plf:Ground_StoneCenter', **kwargs)
#土床
class GrassTile(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'plf:Ground_GrassCenter', **kwargs)
#上ボタン
class UpButton(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'typb:Up', **kwargs)
#下ボタン
class DownButton(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'typb:Down', **kwargs)
#左ボタン
class LeftButton(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'typb:Left', **kwargs)
#右ボタン
class RightButton(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'typb:Right', **kwargs)
#アタックボタン
class AttackButton(SpriteNode):
def __init__(self, **kwargs):
SpriteNode.__init__(self, 'typb:Forward', **kwargs)
#画面
class GameScene(Scene):
def setup(self):
self.controll_buttons = []
self.map_tiles = []
self.enemys = []
self.create_map()
self.create_chara()
for i in range(20):
self.create_enemy()
self.create_controller()
def update(self):
if self.my_chara.move_flg:
self.chara_move()
self.enemy_move()
self.collision_check()
#コントローラー作成
def create_controller(self):
up_button = UpButton(position = (self.size.w * 0.85, self.size.h * 0.2 + 80), size = (100, 100))
down_button = DownButton(position = (self.size.w * 0.85, self.size.h * 0.2 - 80), size = (100, 100))
left_button = LeftButton(position = (self.size.w * 0.85 - 80, self.size.h * 0.2), size = (100, 100))
right_button = RightButton(position = (self.size.w * 0.85 + 80, self.size.h * 0.2), size = (100, 100))
attack_button = AttackButton(position = (self.size.w * 0.15, self.size.h * 0.2), size = (100, 100))
self.controll_buttons = [up_button, down_button, left_button, right_button, attack_button]
for i in [0, 1, 2, 3, 4]:
self.add_child(self.controll_buttons[i])
#キャラ作成
def create_chara(self):
self.my_chara = Chara(position = self.size / 2, anchor_point = (0.5, 0))
self.add_child(self.my_chara)
#敵生成
def create_enemy(self):
slime_enemy = SlimeEnemy(position = (random.uniform(0, self.size.w), random.uniform(0, self.size.h)),anchor_point = (0.5, 0))
self.enemys.append(slime_enemy)
self.add_child(slime_enemy)
#マップ作成
def create_map(self):
self.spsize = Size(self.size.width / 39, self.size.width /39)
self.d = self.spsize / 2
#ベースとなるノードを用意
self.board = Node()
self.board.size = Size(self.spsize.width * 7, self.spsize.height * 25)
self.add_child(self.board)
#ゲームデータの用意
self.data = Map.data
#ボード作成
y = -1
for raw in self.data:
y += 1
x = -1
for cell in raw:
x += 1
if self.data[y][x] == 1:
block = GrassTile()
elif self.data[y][x] == 2:
block = StoneTile()
block.size = self.spsize
block.position = Point(self.spsize.width * x + self.d.width, self.spsize.height * y + self.d.height)
self.board.add_child(block)
self.map_tiles.append(block)
#キャラ移動
def chara_move(self):
self.my_chara.position += self.my_chara.move_direction
texture = self.my_chara.walk_textures[int((self.my_chara.position.x + self.my_chara.position.y) / 50) % 2]
if not texture is self.my_chara.texture:
self.foot_sound()
self.my_chara.texture = texture
#敵移動
def enemy_move(self):
for enemy in self.enemys:
if enemy.move_flg:
enemy.move_flg = False
direction = (self.my_chara.position - enemy.position) / 50
if random.uniform(0, 100) < 20:
direction += (random.choice([-20, -10, 0]), random.choice([-20, -10, 0]))
elif random.uniform(0, 100) < 40:
direction += (random.choice([0, 10, 20]), random.choice([0, 10, 20]))
enemy.x_scale = -((direction.x > 0) - (direction.x < 0))
direction += enemy.position
enemy.texture = enemy.walk_textures[int((enemy.position.x + enemy.position.y) / 50) % 2]
enemy.run_action(Action.sequence(Action.move_to(direction.x, direction.y, 0.5), Action.call(enemy.move_on)))
#攻撃動作
def attack_action(self):
self.my_chara.stand_on()
self.sword = Sword(size = (100, 100), anchor_point = (0.5, 0))
if self.my_chara.x_scale == 1:
self.sword.position = (self.my_chara.position.x + 15, self.my_chara.position.y + 20)
action = [Action.rotate_by(-math.pi + 0.8, 0.2), Action.remove()]
elif self.my_chara.x_scale == -1:
self.sword.position = (self.my_chara.position.x - 15, self.my_chara.position.y + 20)
action = [Action.rotate_by(math.pi * 0.8, 0.2), Action.remove()]
self.sword.run_action(Action.sequence(action))
self.add_child(self.sword)
sound.play_effect('game:Woosh_1')
self.attack_judge()
#攻撃判定
def attack_judge(self):
if self.my_chara.x_scale == 1:
sword_hitbox = Rect(self.sword.position.x, self.sword.position.y, 80, 100)
elif self.my_chara.x_scale == -1:
sword_hitbox = Rect(self.sword.position.x - 80, self.sword.position.y, 80, 100)
for enemy in self.enemys:
if enemy.frame.intersects(sword_hitbox):
if isinstance(enemy, SlimeEnemy):
sound.play_effect('arcade:Explosion_6')
#self.knock_back(enemy)
self.item_remove(enemy)
#アイテム削除
def item_remove(self, object):
if isinstance(object, SlimeEnemy):
self.enemys.remove(object)
object.remove_from_parent()
#self.create_enemy()
#当たり判定
def collision_check(self):
player_hitbox = Rect(self.my_chara.position.x, self.my_chara.position.y, self.my_chara.size.w - 50, self.my_chara.size.h - 50)
for enemy in self.enemys:
if enemy.frame.intersects(player_hitbox):
if isinstance(enemy, SlimeEnemy):
sound.play_effect('arcade:Explosion_7')
self.knock_back(self.my_chara)
#ノックバック
def knock_back(self, object):
object.move_flg = False
object.texture = object.hit_texture
if isinstance(object, SlimeEnemy):
direction = object.position - self.my_chara.position
object.run_action(Action.sequence(Action.move_by(direction.x, direction.y, 0.5), Action.wait(1.0), Action.call(object.move_on)))
elif isinstance(object, Chara):
object.run_action(Action.sequence(Action.move_to(object.position.x - 150 * object.x_scale, object.position.y, 0.5), Action.call(object.stand_on)))
#歩行音
def foot_sound(self):
player_hitbox = Rect(self.my_chara.position.x, self.my_chara.position.y, self.my_chara.size.w -100, self.my_chara.size.h - 100)
for tile in self.map_tiles:
if tile.frame.intersects(player_hitbox):
if isinstance(tile, StoneTile):
sound.play_effect('rpg:Footstep00')
elif isinstance(tile, GrassTile):
sound.play_effect('rpg:Footstep05')
break
def touch_began(self, touch):
touch_loc = self.point_from_scene(touch.location)
for button in self.controll_buttons:
if touch_loc in button.frame:
if isinstance(button, UpButton):
self.my_chara.move_flg = True
self.my_chara.move_direction = (0, 10)
if isinstance(button, DownButton):
self.my_chara.move_flg = True
self.my_chara.move_direction = (0, -10)
if isinstance(button, LeftButton):
self.my_chara.move_flg = True
self.my_chara.move_direction = (-10, 0)
self.my_chara.x_scale = -1
if isinstance(button, RightButton):
self.my_chara.move_flg = True
self.my_chara.move_direction = (10, 0)
self.my_chara.x_scale = 1
if isinstance(button, AttackButton):
self.attack_action()
def touch_ended(self, touch):
touch_loc = self.point_from_scene(touch.location)
for button in self.controll_buttons:
if touch_loc in button.frame:
if not isinstance(button, AttackButton):
self.my_chara.move_flg = False
self.my_chara.stand_on()
run(GameScene(), PORTRAIT, frame_interval = 2)
▼マップデータは長くなってしまったので別ファイルで作っています。
data = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
まとめ
スクリプト自体は以前にも紹介したゲーム開発で使った関数を流用しているので、そこまで大変ではなかったです。
ただPythonista3で開発してもPythonista経由でしか配布はできないですし、すぐに飽きてしまうので作り込むにはモチベがあまり上がりませんでした。
これからは便利アプリでも作ってみたいなと思います。