Help us understand the problem. What is going on with this article?

Pythonista+sceneで画面スクロールを作成する

今回は、Pythonista+sceneで2Dアクションのスクロール処理を作成してみました。

プログラム概要

キャラクターが移動したら画面をスクロールする。画面の端に移動したらスクロールをやめて自由に移動できるようにする。

実装した処理

実装した処理は以下の2つです。
・画面スクロール
・画面端の処理

画面スクロール

キャラクターが移動したら画面をスクロールする処理です。
スクロール中はキャラクターの位置はそのままで、背景やアイテムをキャラクターの移動と逆方向に移動することでスクロールを表現します。

Pythonista_scroll.png

コードは以下の通り。update()メソッドに追記します。

def update(self):
    g = gravity()
    x = self.player.position.x
    y = self.player.position.y
    # 左右の移動
    if abs(g.x) > 0.05:
        max_speed = 40
        self.update_position(max_speed)
    else:
        self.player.texture = standing_texture
        self.walk_step = -1

def update_position(self, max_speed):
    g = gravity()
    x = self.player.position.x
    y = self.player.position.y
    distance = g.x * max_speed 

    x = self.size.w/2
    self.player.position = (x, y)
    for t in self.tiles:
        t['tile1'].position = (t['tile1'].position.x - distance, t['tile1'].position.y)
        t['tile2'].position = (t['tile2'].position.x - distance, t['tile2'].position.y)
    for item in self.items:
        item.position = (item.position.x - distance, item.position.y)
    for item in self.stars:
        item.position = (item.position.x - distance, item.position.y)
    for item in self.mushrooms:
        item.position = (item.position.x - distance, item.position.y)

画面端の処理

境界を用意しないとキャラクターが背景のない位置まで際限なく移動してしまうので画面端に境界が必要です。

画面端に到着したらスクロールをやめてキャラクターの移動に切り替えます。画面端から離れたら再びスクロールを行います。

コードは以下の通り。
「キャラクターが画面の端にいるか」を判定して端にいる場合はスクロールを中断してキャラクターの移動に切り替えます。

def update(self):
    g = gravity()
    x = self.player.position.x
    y = self.player.position.y
    # 左右の移動
    if abs(g.x) > 0.05:
        max_speed = 40
        self.update_position(max_speed)
    else:
        self.player.texture = standing_texture
        self.walk_step = -1

def update_position(self, max_speed):
    g = gravity()
    x = self.player.position.x
    y = self.player.position.y
    distance = g.x * max_speed 
# 追加ここから --------------------------------------------------------------------------
    self.move_count = max(0, min(self.move_count + distance, self.stage_size))
    if self.move_count < self.size.w/2 or self.move_count >= self.stage_size - self.size.w/2:
        self.player.x_scale = cmp(g.x, 0)
        step = int(self.player.position.x / 40) % 2
        if step != self.walk_step:
            self.player.texture = walk_textures[step]
            self.walk_step = step
        # 左右の移動
        x = max(0, min(self.size.w, x + distance))
        self.player.position = (x, y)
    else:
# 追加ここまで --------------------------------------------------------------------------
        x = self.size.w/2
        self.player.position = (x, y)
        for t in self.tiles:
            t['tile1'].position = (t['tile1'].position.x - distance, t['tile1'].position.y)
            t['tile2'].position = (t['tile2'].position.x - distance, t['tile2'].position.y)
        for item in self.items:
            item.position = (item.position.x - distance, item.position.y)
        for item in self.stars:
            item.position = (item.position.x - distance, item.position.y)
        for item in self.mushrooms:
            item.position = (item.position.x - distance, item.position.y)

課題

1.画面の端でジャンプすると画面中央にワープすることがある。
2.スクロール中キャラクターは移動の扱いになっていないので歩行アニメーションが停止する

コード全文

スクロールと関係ない処理の説明は割愛します。

# coding: utf-8

from scene import *
from sound import *

def cmp(a, b):
    return ((b > a) - (b < a))


standing_texture = Texture('emj:Ghost')
walk_textures = [Texture('emj:Ghost'), Texture('emj:Ghost')]

class Game (Scene):
    def setup(self):
        # バックグラウンド設定
        self.background_color = '#004f82'
        ground = Node(parent=self)
        x = 0
        # 地面の高さ
        self.base_height = 50
        self.stage_size = self.size.w * 5
        # 背景の設定
        self.tiles = []
        while x <= self.stage_size + 128:
            tile1 = SpriteNode('plf:Ground_GrassHalf_mid', position=(x - 50, self.base_height))
            ground.add_child(tile1)

            tile2 = SpriteNode('plf:Ground_GrassCenter', position=(x - 50, self.base_height-32))
            ground.add_child(tile2)
            x += 64
            self.tiles.append(
                {
                    'tile1' : tile1,
                    'tile2' : tile2
                }
            )

        # プレイヤーの初期設定
        self.player_height = self.base_height + 32
        self.player = SpriteNode('emj:Ghost')
        self.player.anchor_point = (0.5, 0)
        self.player.position = (self.size.w/2, self.player_height)
        self.add_child(self.player)
        # ジャンプボタンの初期設定
        self.jump = 'ready'
        self.jump_button = SpriteNode('emj:Blue_Circle', position=(320,50))
        self.add_child(self.jump_button)
        # 攻撃ボタンの初期設定
        self.attack_button = SpriteNode('emj:Blue_Circle', position=(250,50), color='red')
        self.add_child(self.attack_button)

        self.bullet = SpriteNode('shp:sun')
        self.bullet.position = -1000, -1000
        self.add_child(self.bullet)

        mushroom = SpriteNode('emj:Mushroom')
        mushroom.anchor_point = (0.5, 0)
        mushroom.position = (self.size.w * 3, self.player_height)
        self.add_child(mushroom)
        self.mushrooms = [mushroom]

        star = SpriteNode('plc:Star')
        star.anchor_point = (0.5, 0)
        star.position = (self.size.w + self.size.w/2, self.player_height + 300)
        self.add_child(star)
        self.stars = [star]

        self.items = []
        for i in range(0, 5):
            for j in range(0,2):
                item = SpriteNode('plf:HudCoin')
                item.anchor_point = (0.5, 0)
                item.position = (self.size.w + 80 + i * 50, self.player_height + 100 + 60 * j)
                self.add_child(item)
                self.items.append(item)
        self.charge = False
        self.power = 0
        self.disp_lock = False
        self.move_count = self.size.w/2

        score_font = ('Futura', 40)
        self.score_label = LabelNode('0', score_font, parent=self)
        # The label is centered horizontally near the top of the screen:
        self.score_label.position = (self.size.w/2, self.size.h - 70)
        # The score should appear on top of everything else, so we set the `z_position` attribute here. The default `z_position` is 0.0, so using 1.0 is enough to make it appear on top of the other objects.
        self.score_label.z_position = 1
        self.score = 0
        self.walk_step = -1


    def update(self):
        g = gravity()
        x = self.player.position.x
        y = self.player.position.y
        # 左右の移動
        if abs(g.x) > 0.05:
            max_speed = 40
            self.update_position(max_speed)
        else:
            self.player.texture = standing_texture
            self.walk_step = -1
        self.check_jump(x,y)
        if self.charge and self.power < 100:
            self.power += 1

        for item in self.mushrooms:
            item.position = (item.position.x -1, item.position.y)
        # 当たり判定
        self.check_item_hit(self.mushrooms, score=500)
        self.check_item_hit(self.items)
        self.check_item_hit(self.stars, score=1500)


    def update_position(self, max_speed):
        g = gravity()
        x = self.player.position.x
        y = self.player.position.y
        distance = g.x * max_speed 

        self.move_count = max(0, min(self.move_count + distance, self.stage_size))
        if self.move_count < self.size.w/2 or self.move_count >= self.stage_size - self.size.w/2:
            self.player.x_scale = cmp(g.x, 0)
            step = int(self.player.position.x / 40) % 2
            if step != self.walk_step:
                self.player.texture = walk_textures[step]
                self.walk_step = step
            # 左右の移動
            x = max(0, min(self.size.w, x + distance))
            self.player.position = (x, y)
        else:
            x = self.size.w/2
            self.player.position = (x, y)
            for t in self.tiles:
                t['tile1'].position = (t['tile1'].position.x - distance, t['tile1'].position.y)
                t['tile2'].position = (t['tile2'].position.x - distance, t['tile2'].position.y)
            for item in self.items:
                item.position = (item.position.x - distance, item.position.y)
            for item in self.stars:
                item.position = (item.position.x - distance, item.position.y)
            for item in self.mushrooms:
                item.position = (item.position.x - distance, item.position.y)

    def touch_began(self, touch):
        self.power = 0
        self.charge = True

    def touch_ended(self, touch):
        # タップした位置の取得
        touch_loc = self.point_from_scene(touch.location)
        # タップした位置がボタンならジャンプ
        if touch_loc in self.jump_button.frame:
            if self.jump == 'ready':
                play_effect('game:Boing_1')
                self.jump = 'up'
        if touch_loc in self.attack_button.frame:
            if self.jump == 'ready':
                x, y = self.player.position
                self.bullet.position = x, y + 50
                move = Action.move_to(x, self.player_height +1000, 0.5 ,TIMING_LINEAR)
                self.bullet.run_action(move)

    def check_jump(self, x, y):
        # 上昇時の処理
        if self.jump == 'up':
            max_height = 180 + self.power
            up_speed = 10
            y = max(self.player_height, min(self.size.h, y + up_speed))
            self.player.position = (x, y)
            if y > max_height + self.player_height:
                self.jump = 'down'

        # 落下時の処理
        if self.jump == 'down':
            down_speed = 10
            y = max(self.player_height, min(self.size.h, y - down_speed))
            self.player.position = (x, y)
            if y == self.player_height:
                self.jump = 'ready'
                self.charge = False


    # 当たり判定
    def check_item_hit(self, items, score=100):
        # 縦横一致したらスコア加算処理をしてアイテムを消す
        for item in items:
            if abs(self.player.position.x - item.position.x) < 30 and abs(self.player.position.y - item.position.y) < 30:
                item.remove_from_parent()
                self.score += score
                items.remove(item)
                self.score_label.text = str(self.score)
            if abs(self.bullet.position.x - item.position.x) < 30 and abs(self.bullet.position.y - item.position.y) < 30:
                item.remove_from_parent()
                self.score += score
                items.remove(item)
                self.score_label.text = str(self.score)

if __name__ == '__main__':
    run(Game(), PORTRAIT, show_fps=True)

実行例(gifアニメ)

Pythonistaの使い方はブログでもまとめ中。上記コードのスクロール以外のアクションについてはブログで解説しています。
Pythonistaの使い方まとめ

miyabikno
SIer系業務7年、Web系業務を3年やってきたプログラミング系ブロガーです。 Pythonの記事をメインで書いています。
https://se.miyabikno-jobs.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした