Python

【Pythonista3】PythonでiOSゲーム開発#2[ゲーム開発②]

2020年3月22日

Pythonista ゲーム開発②

上から降ってくる隕石を避けるゲーム

▼ゲームというか、ただ横スクロールしているだけなのでゲーム性はありません。一応進んだ分だけ距離を表示するようにしています。

衝突判定処理:衝突判定を行うための範囲をRect(left, top, width, height)で指定します。衝突は"item.frame.intersects(player_hitbox)"で判定し、衝突しているスプライトは"isinstance(item, Meteor)"で判別します。

 def check_collision(self):
    player_hitbox = Rect(self.man.position.x, self.man.position.y, self.man.size.w -50,            self.man.size.h - 50)
 
    for item in list(self.items):
        if item.frame.intersects(player_hitbox):
            if isinstance(item, Meteor):
                self.player_hit(self.man.position)
            elif not item.parent:
                self.items.remove(item)

アイテムのスポーン:アイテムをランダムスポーンさせるため、スポーンするタイミングやスポーン位置をランダムで追加していきます。

def spawn_item(self):
    if random.random() < 0.3:
        meteor = Meteor(parent = self)
        meteor.position = (random.uniform(-self.size.w, self.size.w * 2), self.size.h + 30)
        rotate = Action.repeat(Action.rotate_by(2 * pi, 2.0, TIMING_LINEAR), 4)
        actions = [rotate, Action.remove()]
        meteor.run_action(Action.sequence(actions))
        self.items.append(meteor)

キャラ移動時の反転:キャラ移動に伴う振り向きを"self.man.x_scale = -1"で反転させることができます。今回は画面を横向きで重力センサーを用いているので、左に傾けたときに反転させるようにしています。

g = gravity()
self.man.x_scale = -((g.y > 0) - (g.y < 0))

音声処理:効果音は"sound.play_effect()"で呼び出します。パラメータは以下の要素で指定します。

sound.play_effect('arcade:Explosion_4', 1.0, 1.0)

パラメータの要素

name音声データ
volumeボリューム
pitch再生スピード
pan音の方向を-1.0~1.0で設定。(左:-1.0, 右:1.0)
looping音声のループ設定。デフォルトではFalse

▼スクリプト全体です。一応丁寧に書いたつもりですが、見にくい部分あるかもしれません。

# coding: utf-8
from scene import *
import random
from math import *
import sound

#主人公
class Man(SpriteNode):
	distance_running = 0.0
	standing_texture = Texture('plf:AlienPink_stand')
	walk_textures = [Texture('plf:AlienPink_walk1'), Texture('plf:AlienPink_walk2')]
	walk_step = -1
	jmp_flg1 = False
	jmp_flg2 = False
	jmp = 0.0

#隕石
class Meteor(SpriteNode):
	def __init__(self, **kwargs):
		SpriteNode.__init__(self, 'spc:MeteorBrownBig2', **kwargs)
		self.direction = random.uniform(5.0, 10.0)

#画面			
class Game (Scene):

	#初期設定
	def setup(self):
		
		#背景描画
		self.background = Node()
		self.background.add_child(SpriteNode('plf:BG_Blue_land', position=(0, self.size.h / 2), size = self.size))
		self.background.add_child(SpriteNode('plf:BG_Blue_land', position=(self.size.w, self.size.h / 2),size = self.size))
		
		#パネル地面設置
		x = -self.size.w
		while x <= self.size.w + self.size.w:
			tile = SpriteNode('plf:Ground_GrassMid', position=(x, 64))
			tile.size *= 2
			self.background.add_child(tile)
			x += 128
		self.add_child(self.background)
			
		#キャラ作成
		self.man = Man(parent=self)
		self.man.anchor_point = (0.5, 0)
		self.man.position = (self.size.w/2, 128)
		self.add_child(self.man)
		
		#スコア作成
		score_font = ('Futura', 40)
		self.score_label = LabelNode('0', score_font, parent=self)
		self.score_label.position = (self.size.w/2, self.size.h - 70)
		self.score_label.z_position = 1
		self.score = 0
		
		self.items = []
		self.game_over = False
		self.max_speed = 40
	
	#NewGame
	def new_game(self):
		for item in self.items:
			item.remove_from_parent()
		self.items = []
		self.score = 0
		self.man.distance_running = 0
		self.walk_step = -1
		self.man.alpha = 1.0
		self.man.position = (self.size.w/2, 128)
		self.game_over = False
	
	#更新
	def update(self):
		if not self.game_over:
			if(not self.man.jmp_flg1):
				self.update_running()
			if(self.man.jmp_flg1):
				self.update_jumping()		
			self.update_score()
			self.update_jump()
			self.update_items()
			self.check_collision()
			if random.random() < 0.05 * 10:
				self.spawn_item()
		
	def update_running(self):
		g = gravity()
		if abs(g.y) > 0.05:
			self.man.x_scale = -((g.y > 0) - (g.y < 0))
			self.man.distance_running = self.man.distance_running - g.y * self.max_speed
			step = int(self.man.distance_running / self.max_speed) % 2
			if step != self.man.walk_step:
				self.man.texture = self.man.walk_textures[step]
				sound.play_effect('rpg:Footstep00', 0.20, 1.0 + 0.5 * step)
				self.man.walk_step = step
				self.update_background(-g.y)
		else:
			self.man.texture = self.man.standing_texture
			self.man.walk_step = -1
			
	def update_jumping(self):
		g = gravity()
		if abs(g.y) > 0.05:
			self.man.x_scale = -((g.y > 0) - (g.y < 0))
			self.update_background(-g.y)
			self.man.distance_running = self.man.distance_running - g.y * 60
			
	def update_score(self):
		self.score_label.text = str(math.floor(self.man.distance_running/100)) + 'm'
		
	def update_background(self, v):
		if(abs(self.background.position.x) <= self.size.w / 2):
			self.background.position -= (v * 30, 0)
		elif(self.background.position.x > self.size.w / 2):
			self.background.position = (-self.size.w / 2, 0)
		else:
			self.background.position = (self.size.w / 2, 0)
			
	def update_jump(self):
		if(self.man.jmp > 0):
			self.man.position += (0.0, self.man.jmp)
			self.man.jmp -= 0.5
		if(self.man.jmp_flg1 and self.man.jmp <= 0):
			self.man.jmp -= 1.0
			self.man.position += (0.0, self.man.jmp)
			if(self.man.position.y <= 128):
				self.man.position = (self.size.w / 2, 128)
				self.man.jmp = 0.0
				self.man.jmp_flg1 = False
				self.man.jmp_flg2 = False
				sound.play_effect('drums:Drums_01', 1.0, 1.0)
				
	def update_items(self):
		g = gravity()
		for item in self.items:
			direction = item.direction
			item.position += (g.y * self.max_speed, -direction)
		
	def spawn_item(self):
		if random.random() < 0.3:
			meteor = Meteor(parent = self)
			meteor.position = (random.uniform(-self.size.w, self.size.w * 2), self.size.h + 30)
			rotate = Action.repeat(Action.rotate_by(2 * pi, 2.0, TIMING_LINEAR), 4)
			actions = [rotate, Action.remove()]
			meteor.run_action(Action.sequence(actions))
			self.items.append(meteor)
			
	def check_collision(self):
		player_hitbox = Rect(self.man.position.x, self.man.position.y, self.man.size.w -50, self.man.size.h - 50)
		for item in self.items:
			if item.frame.intersects(player_hitbox):
				if isinstance(item, Meteor):
					self.player_hit(self.man.position)
			elif not item.parent:
				self.items.remove(item)		
				
	def player_hit(self, position):
		self.game_over = True
		sound.play_effect('arcade:Explosion_4', 1.0, 1.0)
		explosion = SpriteNode('shp:Explosion00', position, size = (50, 50))
		actions = [Action.scale_to(10.0, 1.0, TIMING_LINEAR), Action.fade_to(0.0, 1.0, TIMING_LINEAR)]
		explosion.run_action(Action.group(actions))
		self.add_child(explosion)
		self.man.alpha = 0.0
		for item in list(self.items):
			item.remove_from_parent()
		self.run_action(Action.sequence(Action.wait(1.0), Action.call(self.new_game)))
				
	def touch_began(self, touch):
		if(not self.man.jmp_flg1 and not self.game_over):
			sound.play_effect('game:Woosh_1', 1.0, 1.0)
			self.man.jmp = 10.0
			self.man.jmp_flg1 = True
		elif(self.man.jmp_flg1 and not self.man.jmp_flg2):		
			sound.play_effect('game:Woosh_1', 1.0, 1.0)
			self.man.jmp = 10.0
			self.man.jmp_flg2 = True
			
if __name__ == '__main__':
	run(Game(), LANDSCAPE, show_fps=True)

飛んでくる星を避けてコインを取るゲーム

▼1個目のゲームの内容をほぼ流用していますが、無限ジャンプで星を避けながらコインを取りスコアを稼ぎます。

アイテム獲得処理:アイテムの獲得処理は実は衝突処理と全く同じ処理です。アイテムごとに動作を決めればOKです。

 def check_collision(self):
    player_hitbox = Rect(self.man.position.x, self.man.position.y, self.man.size.w -100, self.man.size.h - 100)
    for item in list(self.items):
        if item.frame.intersects(player_hitbox):
            if isinstance(item, Star):
                self.player_hit(self.man.position)
            elif isinstance(item, Coin):
                self.get_item(item)
            elif isinstance(item, Food):
                self.get_item(item)
        elif not item.parent:
            self.items.remove(item)

▼スクリプト全体です。一応丁寧に書いたつもりですが、見にくい部分あるかもしれません。

from scene import *
import random
from math import *
import sound

#主人公
class Man(SpriteNode):
	def __init__(self, **kwargs):
		SpriteNode.__init__(self, 'plf:AlienBlue_stand', **kwargs)
			
	stand_texture = Texture('plf:AlienBlue_stand')
	walk_textures = [Texture('plf:AlienBlue_walk1'), Texture('plf:AlienBlue_walk2')]
	jmp_flg = False
	jmp = 0.0
	score = 0
	jmp_count = 0

#隕石
class Star(SpriteNode):
	def __init__(self, **kwargs):
		SpriteNode.__init__(self, 'emj:Star_1', **kwargs)
		self.direction = random.uniform(5.0, 10.0)

#コイン
class Coin (SpriteNode):
	def __init__(self, **kwargs):
		SpriteNode.__init__(self, 'plf:Item_CoinGold', **kwargs)
		self.direction = random.uniform(5.0, 10.0)
		
#食べ物
class Food (SpriteNode):
	def __init__(self, **kwargs):
		SpriteNode.__init__(self, 'emj:Meat_On_Bone', **kwargs)
		self.direction = 2.0
	
#画面			
class Game (Scene):

	#初期設定
	def setup(self):
		
		#背景
		self.background = Node()
		self.background.add_child(SpriteNode('plf:BG_Blue_shroom', position=(0, self.size.h / 2), size = self.size))
		self.background.add_child(SpriteNode( 'plf:BG_Blue_shroom', position=(self.size.w, self.size.h / 2),size = self.size))
		
		#地面設置
		x = -self.size.w
		while x <= self.size.w + self.size.w:
			tile = SpriteNode('plf:Ground_GrassMid', position=(x, 64))
			tile.size *= 2
			self.background.add_child(tile)
			x += 128
		self.add_child(self.background)
			
		#キャラ作成
		self.man = Man(parent=self)
		self.man.texture = self.man.stand_texture
		self.man.anchor_point = (0.5, 0)
		self.man.position = (self.size.w / 10, 128)
		self.add_child(self.man)
		
		#スコア作成
		score_font = ('Futura', 40)
		self.score_label = LabelNode('0', score_font, parent=self)
		self.score_label.position = (self.size.w/2, self.size.h - 70)
		
		#シーン変数
		self.items = []
		self.game_over = False
		self.scroll_speed = 2
	
	#NewGame
	def new_game(self):
		for item in self.items:
			item.remove_from_parent()
		self.items = []
		self.man.alpha = 1.0
		self.man.score = 0
		self.man.texture = self.man.stand_texture
		self.man.position = (self.size.w / 10, 128)
		self.game_over = False
	
	#更新
	def update(self):
		if not self.game_over:
			self.update_background()
			self.update_score()
			self.update_jump()
			self.update_items()
			self.check_collision()
			self.update_walking()
			self.spawn_item()
				
	def update_walking(self):
		self.man.score += 10
		if(not self.man.jmp_flg):
			step = int(self.man.score / 100) % 2
			self.man.texture = self.man.walk_textures[step]
				
	def update_score(self):
		self.score_label.text = 'Score' + ' : ' + str(math.floor(self.man.score))
	
	#背景移動
	def update_background(self):
		if(abs(self.background.position.x) <= self.size.w / 2):
			self.background.position -= (self.scroll_speed, 0)
		elif(self.background.position.x > self.size.w / 2):
			self.background.position = (-self.size.w / 2, 0)
		else:
			self.background.position = (self.size.w / 2, 0)
	
	#ジャンプ処理
	def update_jump(self):
		if self.man.jmp > 0:
			if self.man.position.y + self.man.jmp < self.size.h:
				self.man.position += (0.0, self.man.jmp)
				self.man.jmp -= 0.5
			else:
				self.man.position = (self.size.w / 10, self.size.h)
				self.man.jmp = 0
		if self.man.jmp_flg and self.man.jmp <= 0 :
			self.man.jmp -= 1.0
			self.man.position += (0.0, self.man.jmp)
			if(self.man.position.y <= 128):
				self.man.position = (self.size.w / 10, 128)
				self.man.jmp = 0.0
				self.man.jmp_flg = False
				sound.play_effect('drums:Drums_01', 1.0, 1.0)
	
	#アイテム移動
	def update_items(self):
		for item in self.items:
			direction = item.direction
			item.position -= (direction, 0)		
	
	#スポーン
	def spawn_item(self):
		if random.random() < 0.05:
			star = Star(parent = self)
			star.position = (self.size.w + 120, random.uniform(128, self.size.h))
			star.run_action(Action.repeat(Action.rotate_by(2 * pi, 2.0, TIMING_LINEAR), 4))
			self.items.append(star)
		if random.random() < 0.10:
			coin = Coin(parent = self)
			coin.position = (self.size.w + 128, random.uniform(128, self.size.h))
			coin.run_action(Action.repeat(Action.rotate_by(2 * pi, 2.0, TIMING_LINEAR), 4))
			self.items.append(coin)
		if random.random() < 0.001:
			food = Food(parent = self)
			food.anchor_point = (0.5, 0)
			food.position = (self.size.w + 128, 128)
			self.items.append(food)
			
	#衝突判定
	def check_collision(self):
		player_hitbox = Rect(self.man.position.x, self.man.position.y, self.man.size.w -100, self.man.size.h - 100)
		for item in self.items:
			if item.frame.intersects(player_hitbox):
				if isinstance(item, Star):
					self.player_hit(self.man.position)
				elif isinstance(item, Coin):
					self.get_item(item)
				elif isinstance(item, Food):
					self.get_item(item)
			elif not item.parent:
				self.items.remove(item)		
	
	#アイテムゲット			
	def get_item(self, item):
		sound.play_effect('digital:PowerUp7')
		item.remove_from_parent()
		self.items.remove(item)
		self.man.score += 1000
	
	#衝突		
	def player_hit(self, position):
		self.explosion(position)
		self.game_over = True
		self.man.alpha = 0.0
		for item in list(self.items):
			item.remove_from_parent()
		self.run_action(Action.sequence(Action.wait(1.0), Action.call(self.new_game)))
	
	#爆発	
	def explosion(self, position):
		sound.play_effect('arcade:Explosion_4', 1.0, 1.0)
		explosion = SpriteNode('shp:Explosion00', position, size = (50, 50))
		actions = [Action.scale_to(10.0, 1.0, TIMING_LINEAR), Action.fade_to(0.0, 1.0, TIMING_LINEAR)]
		explosion.run_action(Action.group(actions))
		self.add_child(explosion)
	
	#タッチ処理			
	def touch_began(self, touch):
		if(not self.game_over):
			sound.play_effect('game:Woosh_1', 1.0, 1.0)
			self.man.jmp = 10.0
			self.man.jmp_flg = True
			self.man.jmp_count += 1
			self.man.texture = self.man.walk_textures[self.man.jmp_count % 2]
			
if __name__ == '__main__':
	run(Game(), LANDSCAPE, show_fps=True)

まとめ

ゲームといっても単純なものしか作っていません。もう少し凝ったゲームを作ろうと思ったのですが、一人でマップを作ったり背景画像を作ったりするのはモチベ上がらず大変だったのでやめました。

時間をかければ結構なゲームを作り出せる可能性を感じましたが、手っ取り早くクオリティの高いゲームを作りたいならUnity使ったほうが良い気がしますね。

次回は便利アプリに挑戦したいと思います。

よく読まれている記事

1

DeepFaceLab 2.0とは DeepFaceLab 2.0は機械学習を利用して動画の顔を入れ替えるツールです。 以前にDeepFaceLab 1.0を記事としてアップしていましたが、2.0は以 ...

2

自作PCで、多くのパーツをCorsair製品で揃えたので、iCUEでライティング制御していきました。 私のPCでは、表示されている4つのパーツが制御されています。ここで、HX750i電源ユニットは、L ...

3

コンピュータは有限桁の数値しか扱う事はできないので、桁数の多い場合や無限小数の場合は四捨五入され切り捨てられます。なので実際の数値とは多少の誤差が生じますが、これを丸め誤差といいます。 なので、コンピ ...

-Python