【未完成】ジェスチャ入力 - Kivy Advent Calendar 2013
(※編集中です...)
Kivyはジェスチャ認識のライブラリも持っています。
サンプルコード (https://github.com/kivy/kivy/tree/master/examples/gestures) もあり簡単なテストができますが、これは4種類の一筆書きシンボル(check、circle、square、cross)を固定で持って識別するだけです。そこで
- 自分でパターンを登録して識別できるか
- Palmの一筆書き入力 (Grafiti) 並に数十個のパターンを入れても実用的な時間で識別できるか
という点を実験してみたいと思います。
2013-12-26 追記
ごめんなさい。この記事もギブアップです。休み中にゆっくりやります。
(kivy.storageを使おうと思ったら、Kivy Launcher 1.7.2で実装されてなかった...)
一旦クローズさせてください。ひとまずKivy Launcherと戯れてみて思ったことを。
Kivy 1.8はもうちょい
トラッキング追ってますが、大量に通知メールが来ていてKivyチームがバグをぷちぷち直しているところみたいです。なんか今週中にとか言ってたような気もしますが、今年中も無理じゃないのって勢い...orz
(まあバグよりはPython3対応の方に力を入れているみたいですが...)
pyjnius使わなくなるかも
Advent Calendarでpyjniusネタいろいろやったのですが、全部無駄になるかもしれません。
というのも写真、加速度センサ、GPSなど、機種依存なものは「plyer」というモジュールでまとめてまして、これを使えば少し楽に書けるようになりそうです。
https://plyer.readthedocs.org/en/latest/
ただ、今のところiOS (pyobjc) の対応が含まれてないので、Kivy-iOSの人に頑張ってもらうことになりそう...
今後tryしたいところ
自分も十分理解していないので、やろうと思っていても出来なかったネタも多かった一方、ドキュメントで動くといっておきながら動かないものが多々あって結構ハマりました。
- kivy.garden
- kivy.uix.camera
- カメラからの動画をリアルタイムで画面上に写すものですが、少なくともNexus7-2013では動きません(作者自身もgstreamer使わないと無理とか言ってますし)。
- kivy.storage
- key-valueストア。1.7.0で入ったと書いてあり、2013-12-25 ジェスチャの記録に使おうと思ったら、影も形もありませんでした...
SL4Aは?
言及はしましたがリリースが停滞しているので使うのはどうなんだろうって感じ。
完全停止しているわけではなくバグ直しは引き続き行われていますが、リリースは難しいのかなって状況です。
cymunkを使って雪を降らせる - Kivy Advent Calendar 2013
cymunkは2次元物理エンジンChipMunk (http://chipmunk-physics.net/) のCython実装で、KivyLauncerにもデフォルトで入っています。
https://cymunk.readthedocs.org/en/latest/
オブジェクトの重力、衝突、弾性の計算処理をやってくれるもので、cymunkのサンプルコードではカラフルなボールを弾ませています。
このサンプルコードを
- カラーボールではなく小さい白い結晶にする
- タッチしなくてもランダムで発生させる
- 重力を弱くして、左右に揺れるようにする
- 弾性はほぼゼロにする
- 地面等に着いたら跡は残しつつ計算対象から外す
とすれば、雪が降り積もってるようにみえないかなあ…と思ったので実験してみました。
結果は…
なんか「地面等に着いたら」の処理をきちんとやっていないので、地面を雪玉が転がってたりして。
あまり雪っぽくないかな。
(/sdcard/kivy/letitsnow/)
android.txt | お約束 |
flake.png | 雪の結晶みたいな画像 (なければcircle.pngをリネームして) |
main.py | スクリプト本体 |
(android.txt)
title=letitsnow author=cheeseshop orientation=landscape
(main.py)
from kivy.clock import Clock from kivy.app import App from kivy.graphics import Color, Rectangle from kivy.uix.widget import Widget from kivy.properties import DictProperty, ListProperty from kivy.core.image import Image from cymunk import Space, Segment, Vec2d, Body, Circle from random import random from kivy.lang import Builder from os.path import dirname, join class Snow(Widget): cbounds = ListProperty() cmap = DictProperty({}) flist = ListProperty() def __init__(self, **kwargs): super(Snow, self).__init__(**kwargs) self.init_space() self.bind(size=self.update_bounds, pos=self.update_bounds) self.texture = Image(join(dirname(__file__), 'flake.png')).texture Clock.schedule_interval(self.step, 1 / 30.) def init_space(self): self.space = space = Space() space.iterations = 30 space.gravity = (0, -300) space.sleep_time_threshold = 0.5 space.collision_slop = 0.5 for x in xrange(4): seg = Segment(space.static_body, Vec2d(0, 0), Vec2d(0, 0), 0) seg.elasticity = 0.0 self.cbounds.append(seg) space.add_static(seg) self.update_bounds() def update_bounds(self, *largs): a, b, c, d = self.cbounds x0, y0 = self.pos x1 = self.right y1 = self.top self.space.remove_static(a) self.space.remove_static(b) self.space.remove_static(c) self.space.remove_static(d) a.a = (x0, y0); a.b = (x1, y0) b.a = (x1, y0); b.b = (x1, y1) c.a = (x1, y1); c.b = (x0, y1) d.a = (x0, y1); d.b = (x0, y0) self.space.add_static(a) self.space.add_static(b) self.space.add_static(c) self.space.add_static(d) def step(self, dt): if random() < 0.4: self.add_flake(random() * self.right, self.top - 20, 5) if random() < 0.2: self.space.gravity = (100-random()*200, -50*random()-50) self.space.step(1 / 30.) self.update_objects() def update_objects(self): for body, obj in self.cmap.iteritems(): p = body.position radius, color, rect = obj rect.pos = p.x - radius, p.y - radius rect.size = radius * 2, radius * 2 def add_flake(self, x, y, radius): body = Body(100, 1e9) body.position = x, y circle = Circle(body, radius) circle.elasticity = 0.1 self.space.add(body, circle) with self.canvas.before: color = Color(1, 1, 1) rect = Rectangle( texture=self.texture, pos=(self.x - radius, self.y - radius), size=(radius * 2, radius * 2)) self.cmap[body] = (radius, color, rect) self.flist.append((body, circle) ) if len(self.flist) > 300: body, circle = self.flist.pop(0) self.space.remove(body) self.space.remove(circle) class SnowApp(App): def build(self): return Snow() if __name__ == '__main__': SnowApp().run()
スクリーンショットを撮る/タイマーを使う
端末のスクリーンショットを撮る機能、Nexus7-2013では[電源]+[音量小]を同時に0.3秒くらい押すと撮れますが、まあこれがやりにくい。失敗してボリューム表示まで撮れてしまったり…
Kivy自体にもスクリーンショットを取る機能があって、「from kivy.modules import keybinding」とインポートすると「F12」キーを押して撮れます。でもいつもキー入力できるとは限らないし、「ボタンを押したら撮る」だとボタンそのものが写ってしまいます。
ここでは「2013-12-09 画面転換」でスクリーンショットを撮るため、アプリ自体にセルフタイマーつきのスクリーンショット機能を追加してみます。
解説
タイマーについて
ワンショットタイマー (schedule_once) をkv言語側で使ってみました。Clockクラスはkv言語の組み込みではないのでimportが必要ですが、メソッドが増えるよりはいいかなと…
注意点としてはタイマーのコールバックは1つ引数を取るってところでしょうか。dtはシステム時間っぽいです。まあ全然使わないけど引数の数を合わせないといけないので…
スクリーンショット
特にファイル名を指定しなければ「scheenshot0001.png」という形式で、main.pyと同じフォルダに保存してくれます。ただ保存に時間がかかるので、画面が一瞬固まるのが分かると思います。
Home画面とSetting画面。Settingの下から2番目が「5秒後にスクリーンショットを撮る」ボタン。
(/sdcard/kivy/capture/)
android.txt | お約束 |
main.py | 5行追加しただけ ほとんど使い回し |
(android.txt)
title=capture author=cheeseshop orientation=portrait
(main.py)
from kivy.app import App from kivy.properties import StringProperty from kivy.uix.screenmanager import ScreenManager, Screen from kivy.uix.screenmanager import ShaderTransition, SlideTransition, FadeTransition, WipeTransition from kivy.core.window import Window FADEINOUT_TRANSITION_FS = '''$HEADER$ uniform float t; uniform sampler2D tex_in; uniform sampler2D tex_out; void main(void) { float t1 = 0.45; float t2 = 0.55; vec4 cin = vec4(texture2D(tex_in, tex_coord0.st)); vec4 cout = vec4(texture2D(tex_out, tex_coord0.st)); vec4 frag_col = t < t1 ? vec4*1 on_text: app.settr(self.text) Button: pos_hint: {'center_x':0.5, 'top':0.4} size_hint: (None, None) size: (200, 50) text: 'Capture after 5sec' on_press: Clock.schedule_once(app.capture, 5) Button: pos_hint: {'center_x':0.5, 'top':0.2} size_hint: (None, None) size: (200, 50) text: 'Back to home' on_press: root.manager.transition = app.tr1; root.manager.current = 'home' ''') class FadeInOutTransition(ShaderTransition): fs = StringProperty(FADEINOUT_TRANSITION_FS) class HomeScreen(Screen): pass class SettingScreen(Screen): pass class ScreenApp(App): trs = { 'Slide_H': (SlideTransition(direction='right'), SlideTransition(direction='left')), 'Slide_V': (SlideTransition(direction='up'), SlideTransition(direction='down')), 'Fade': FadeTransition(), 'Wipe': WipeTransition(), 'FadeIO': FadeInOutTransition(), } def settr(self, tr): trs = self.trs[tr] if isinstance(trs, tuple): self.tr0 = trs[0] self.tr1 = trs[1] else: self.tr0 = trs self.tr1 = trs def capture(self, dt): Window.screenshot() def build(self): self.sm = ScreenManager() self.sm.add_widget(HomeScreen(name='home')) self.sm.add_widget(SettingScreen(name='setting')) self.settr('Fade') return self.sm if __name__ == '__main__': ScreenApp().run()
*1:1.0 - t / t1) * cout) :
t > t2 ? vec4((t - t2) / (1.0 - t2) * cin) : vec4(0.0);
gl_FragColor = frag_col;
}
'''
from kivy.lang import Builder
Builder.load_string('''
#:import Clock kivy.clock.Clock
【未完成】RPG風にキャラクタを動かす (2) - Kivy Advent Calendar 2013
今度は「背景がスクロール+キャラクタが足踏み」で移動しているように見せます。背景をScrollViewにして、これをAnimationでコントロールできるかどうか、という実験です。
FloatLayout (背景) Image (キャラクタ) ... 位置&画像を変更 ↓ FloatLayout ScrollView (背景) ... 位置を変更 Image (キャラクタ) ... 画像を変更
(22:30)
申し訳ありませんが、今回はギブアップさせてください。
今のところうまくいっているのは次のとおり。
- 前回 (2013-12-19) から引継いだUI
- FloatLayout上で背景フレーム (ScrollView) とキャラクタ (Image) を重ね合わせる
- ScrollViewの内部にマップ (GridLayoutで配置したImage群) を持たせる
次の点がうまくいっていません。
- ScrollViewの左上隅にマップが固定されてしまい、(scroll_x,scroll_y)プロパティを変更しても動かすことができない
(スクリーンショットでも分かりづらいとは思いますが) キャラクタの初期位置はマップの中央で、背景の要素(マップチップ)とキャラクタが揃うはずなのですが、マップがGame領域の左上詰めとなって動かない状況なので、当然キャラクタと揃っていません。
おそらくsize_hintなどの指定が間違っているんだと思いますが、いろいろ試行錯誤しても動く気配すらない状況です…年末年始の宿題とさせてください。
イベント処理 - Kivy Advent Calendar 2013
相変わらずネタ切れ/時間切れで申し訳ないです。イベント処理の問題で少し加筆 (http://d.hatena.ne.jp/cheeseshop/20131205) したので、その件で少し書くことにします。
Kivyのイベント処理は、各ウィジェットクラスが「あるタイミング」で「イベントに結びつけた関数群を実行する」というものです。今回のAdvent Calendarで使ってきたものを挙げると
記事 | クラス | イベント | 動作 |
---|---|---|---|
2013-12-01 ファイル選択 | Button | on_release | Load/Cancelボタンを押したら実行(ダミー) |
2013-12-03 QRコード | TextInput | on_text_validate | Enterキーを押したらQRコード生成 |
2013-12-05 スライド | RstDocument | on_touch_up | タップしたらポップアップを開く |
2013-12-08 ポップアップ | FloatLayout | on_touch_up | タップしたらバブルを開く |
2013-12-09 画面転換 | Spinner | on_text | 選択肢を変更したら画面効果差替 |
2013-12-09 画面転換 | Button | on_press | ボタンを押したら別スクリーンに転換 |
2013-12-10 アプリ復帰 | App | on_pause | 別のアプリへの切替時 |
2013-12-10 アプリ復帰 | App | on_resume | 別のアプリからの復帰時 |
2013-12-11 カード | Scatter | on_touch_up | タッチしたらひっくり返す |
2013-12-13 流行語 | Button | on_press | ボタンを押したら喋る |
2013-12-14 写真 | StackLayout | on_touch_up | タッチしたらカメラ起動 |
2013-12-14 写真 | PythonActivity | on_activity_result | カメラ終了のタイミング |
2013-12-15 テキスト共有 | Button | on_press | ボタンを押したらメーラ起動 |
2013-12-16 Webブラウザ | Button | on_press | ボタンを押したらブラウザ起動 |
2013-12-16 Webブラウザ | Button | on_press | ボタンを押したらブラウザ起動 |
2013-12-18 Launcher | Button | on_press | ボタンを押したらKivyLauncher起動 |
2013-12-19 キャラ移動 | Button | on_press | ボタンを押したらキャラクタ移動 |
2013-12-19 キャラ移動 | Animation | on_progress | 画像を順次変えてアニメ |
2013-12-19 キャラ移動 | Animation | on_complete | 排他制御を解除 |
まあ「ボタンを押して○○する」のが多いです。でも中にはアニメ、アプリ、アクティビティのようにKivyにおけるユーザ操作とは関係なくイベントが発生するものがあります。
on_touch_up / on_touch_downについて
さて、問題だったのは「2013-12-05 スライド」のon_touch_upです。ここでは画面をダブルタップしたらreStructureTextソースをポップアップを見せていましたが、ここではon_touch_upの戻り値で問題が解決したかのように書いていました。実際にはそうではありませんでした。一見「今開いたスライド」のソースを見せていましたが、実は
- 全部のスライド (page01〜page03) のソースが一斉に開く (どれが一番上になるかは不定)
- 外部をタップしたときには3つとも閉じた
というものでした。必ずしも表示しているスライドのソースが見えなかったということです。
何故こうなるのかといえば、on_touch_upというのはタッチした場所にあるウィジェットへ送られるイベントではなく、単に「タッチパネルから指が離れた」というイベントだからです。
つまり、「タッチした場所以外に置かれたウィジェット」に対してもon_touch_upは発生するし、しかもScreenManagerやCarouselのように画面転換によって「非表示になっているウィジェット」にもon_touch_upは発生してしまうのでした。
: orientation: 'vertical' Label: text: 'LABEL1' on_touch_down: self.text = 'LABEL1: DOWN' on_touch_up: self.text = 'LABEL1: UP' Label: text: 'LABEL2' on_touch_down: self.text = 'LABEL2: DOWN' on_touch_up: self.text = 'LABEL2: UP' Label: text: 'LABEL3' on_touch_down: self.text = 'LABEL3: DOWN' on_touch_up: self.text = 'LABEL3: UP'
: orientation: 'vertical' Label: text: 'LABEL1' on_touch_down: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL1: DOWN') on_touch_up: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL1: UP') Label: text: 'LABEL2' on_touch_down: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL2: DOWN') on_touch_up: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL2: UP') Label: text: 'LABEL3' on_touch_down: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL3: DOWN') on_touch_up: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL3: UP')
(まあ一般的にイベントハンドラは、kv言語よりもメソッドで書く方が楽だと思います…)
ScreenManagerやCarouselの非表示ウィジェットについては、それが現在表示されているものか調べることができますから、それを条件にしてイベントを受け付けるか受け付けないかを切り替えることになります。
self.collide_point(*touch.pos) | 自身にタッチしたか |
self.manager.current_screen is self | ScreenManagerが自身を表示しているか |
self.parent.current_slide is self | Carouselが自身を表示しているか (2013-12-05 スライドはこれを使用) |
on_touch_up/on_touch_downは、全画面で使う分にはこういった問題はない (2013-12-08 ポップアップなど) のですが、複数あったり非表示要素がある場合は注意が必要です。
KivyLauncherとSL4A+Py4Aの比較 - Kivy Advent Calendar 2013
さて今回KivyLauncherをいろいろといじってきましたが、2年前はSL4A+Py4Aをいろいろと試していました。
- http://d.hatena.ne.jp/cheeseshop/20110326
- http://d.hatena.ne.jp/cheeseshop/20110226
- http://d.hatena.ne.jp/cheeseshop/20110225
となると、この2つはどう違うのか気になるところかもしれません。
KivyLauncherは、まずSDLやOpenGLを組み合わせてマルチメディア環境を作ってからPythonを起動します。まあマルチメディア環境といっても、AndroidのSurfaceViewとSDL関連のクラスをくっつけて、Androidのサービスプログラムとして動くようにしたものです。Unityみたいなゲームエンジンに比べれば貧弱です。でも、最低限フレーム(画面)とサウンドとタッチパネル入力にアクセスできればいいという発想ですので、ライブラリ次第でどのようなUIでも組み立てられます。
起動されたPythonからはAndroidのAPIではなく、そのマルチメディア環境にアクセスします。Kivyのライブラリはその上でウィジェットの描画、クロックやタッチイベントの管理を行うことでアニメーションやインタラクションが実現されています。
Windows・MacOSX・iOS上のKivyの場合も (実装はDirectXだったりいろいろ違うのですが) 同じマルチメディア環境を作り、その上でウィジェット描画やイベント管理を行うので、ハードウェアの違い(カメラ、GPS、加速度センサ)を避けるという条件であれば、マルチプラットフォームのアプリケーションを作ることが可能なわけです。
SL4Aでのプログラミングのアプローチは、これとは対照的なものになります。
SL4Aから起動されたPythonは、SL4Aを経由してAndroid APIにアクセスして描画やハードウェア操作が利用できる形になっています。SL4AはAndroidと密接に連携していて、描画もAndroid本来のレイアウトエンジンを使うことになります。SL4Aはプログラミング言語としてPython、Jruby、PHPなどが使えますが、同じ名前のAPIを呼び出すので、見た目はJavaでAndroidプログラミングするのに近い形になります。
マルチプラットフォームを意識するのであればKivyしか選択肢はありませんが、もし「Androidだけで動けばよい」プログラムであればKivyで書くかSL4Aで書くか迷うところかもしれません。
SL4Aは、おそらくAndroidアプリをJavaで開発した経験があれば早く馴染むと思います。またWebViewベースのアプリケーションを作る場合はSL4Aを選択することになります。
一方KivyはこれまでのAndroidプログラミングとは異なる形態なので、Javaによる開発経験がない方が向いていると思います。ゲームプログラミングや組み込みのLinux端末を経験した人は、Kivyの方が馴染むかもしれません。
あと、KivyLauncherとSL4A+Py4Aのすぐに分かる違いとして、Androidアプリのパーミッションがあります。KivyLauncherはUSBストレージ、ネットワーク、Bluetooth程度しか要求していませんが、SL4AはAndroid APIの大半を網羅しているため、連絡帳だのSMSだの通話だの、ありとあらゆるパーミッションを要求しています。
さすがにこんなSL4Aを「インストールしてください」とお客さんにお願いするのは難しいので、最低限のパーミッションでAPKパッケージを作って渡すしかないです。そもそもSL4AがGoogle Playで公開できないのも、このパーミッションが影響していると思います。
一方のKivyLauncherはGoogle Playからインストール可能になっています。まだ試してはいませんが、KivyLauncherでpyjniusを使ってGPS情報を取得するアプリを作ったら、GPS関連のパーミッションをつけるためにビルドし直す必要があるでしょう。
RPG風にキャラクタを動かす - Kivy Advent Calendar 2013
Animationオブジェクトの実験ですが、無駄にゲーム機っぽいインタフェースにしてみました。
でもA/B/X/Yキーはダミーです。動くのは上下左右キーだけ。
エラーチェック何もしてませんので、平気で画面外へ出て行っちゃいます...
※spellyonさんのサイト「点睛集積」から素材をお借りしています (http://dispell.net/)
(/sdcard/kivy/thrpg/)
android.txt | お約束 |
main.py | スクリプト本体 |
th7_remari.png | 霊夢と魔理沙の24x32チビキャラファイル(背景を透過化) |
chara.atlas | 上の画像に紐付けるatlasファイル 霊夢のデータしか使ってません... |
(android.txt)
title=thrpg author=cheeseshop orientation=landscape
(chara.atlas)
{ "th7_remari.png": { "N0": [ 24, 224, 24, 32], "N1": [ 0, 224, 24, 32], "N2": [ 24, 224, 24, 32], "N3": [ 48, 224, 24, 32], "E0": [ 24, 192, 24, 32], "E1": [ 0, 192, 24, 32], "E2": [ 24, 192, 24, 32], "E3": [ 48, 192, 24, 32], "S0": [ 24, 160, 24, 32], "S1": [ 0, 160, 24, 32], "S2": [ 24, 160, 24, 32], "S3": [ 48, 160, 24, 32], "W0": [ 24, 128, 24, 32], "W1": [ 0, 128, 24, 32], "W2": [ 24, 128, 24, 32], "W3": [ 48, 128, 24, 32] } }
(main.py)
from kivy.app import App from kivy.animation import Animation from kivy.uix.label import Label from kivy.uix.button import Button from kivy.uix.image import Image from kivy.uix.boxlayout import BoxLayout from kivy.uix.relativelayout import RelativeLayout from itertools import cycle from kivy.lang import Builder Builder.load_string('''\: orientation: 'vertical' Label: size_hint_y: 3./8. GridLayout: size_hint_y: 2./8. rows: 3 Label: Button: id: btn_N text: '^' on_press: app.move('N') Label: Button: id: btn_W text: '<' on_press: app.move('W') Label: Button: id: btn_E text: '>' on_press: app.move('E') Label: Button: id: btn_S text: 'v' on_press: app.move('S') Label: Label: size_hint_y: 3./8. : orientation: 'vertical' Label: size_hint_y: 3./8. GridLayout: size_hint_y: 2./8. rows: 3 Label: Button: text: 'X' Label: Button: text: 'Y' Label: Button: text: 'A' Label: Button: text: 'B' Label: Label: size_hint_y: 3./8. : game: _game orientation: 'horizontal' ControlPad: size_hint_x: 1./8. Game: id: _game size_hint_x: 6./8. ActionPad: size_hint_x: 1./8. : canvas: Color: rgb: (.25, .50, .25) Rectangle: size: self.size pos: (0, 0) : drct: 'E' source: 'atlas://chara/E0' allow_stretch: True size: (192, 256) size_hint: (None, None) ''') class Root(BoxLayout): pass class ControlPad(BoxLayout): pass class ActionPad(BoxLayout): pass class Game(RelativeLayout): pass class Character(Image): pass class GameApp(App): def cycle(self, iter=cycle(list('0123'))): return iter.next() def reload(self, anim, ch, progress): ch.source = 'atlas://chara/%s%s' % (ch.drct,self.cycle()) ch.reload() def clear(self, anim, ch): self.moving = False def move(self, drct, *args): if self.moving: return False self.moving = True self.anim = Animation( d=1./1., s=1./8., t='linear', x=self.ch.x+(drct=='E')*192-(drct=='W')*192, y=self.ch.y+(drct=='N')*256-(drct=='S')*256) self.ch.drct = drct self.anim.bind(on_progress=self.reload) self.anim.bind(on_complete=self.clear) self.anim.start(self.ch) return False def build(self): root = Root() self.moving = False self.ch = Character() self.ch.pos = (0, 0) self.ch.drct = 'S' root.game.add_widget(self.ch) return root if __name__ == '__main__': GameApp().run()
解説
Kivyのアニメーション機能
Animationオブジェクトは、描画タイミングにあわせてウィジェットのサイズや位置といったプロパティを逐次変更していくというものです。
引数には次のようなものを指定してインスタンスを生成します。
- サイズや位置の最終値 (x, y, pos, size)
- 描画タイミング (d:アニメーションの時間、s:描画間隔)
- トランジション関数 (t:文字列または3つの引数[初期値,最終値,0〜1の値]を取る関数)
その後 start(widget) メソッドで、引数に指定したウィジェットの移動を開始します。
歩行アニメーション
Animationオブジェクトは、あくまでウィジェットの「数値の」プロパティを順次変更するものです。
関数をうまく使えば画像を変更するのも可能かもしれませんが、ちょっと面倒なので別の方法を取ることにしました。
このサンプルでは、描画タイミングで「on_progressイベントが発生する」ことを利用しています。on_progressで呼び出すハンドラの第2引数には移動中のウィジェットが入るので、そこで同じ向きのキャラクタ画像 (3パターン) を順次入れ替えて歩行のアニメーションを実現しています。
排他処理
アニメーションはプログラムをブロックせず並行に走るので、動作中に別の移動ボタンを受け付けてしまう可能性があります。このサンプルでは「ひとつの移動が終わってから次のキーを受け付けるようにしたい」ので、移動が終わったときのon_completeイベントとフラグ (moving) を使って排他制御をしています。
キー操作がリピートしない
ゲームキーパッドもどきはon_pressを使っているので、その都度離して押してを繰り返さないといけないです。この件については後で解決策を探してみます...
(たぶんon_touch_downを使えばいいんでしょうが、本当にそのボタンを押したかをcollide_pointでチェックする必要があります)