2013年3月6日水曜日

pygameでフレームアニメ作成ツール作ってました

あけましておめでとうございます。
今回は創作活動の道中の様子を少し。



ゲーム作りともなると色々な開発用ツールが生み出されるのですが、
その中の1つを紹介してみたいと思います。

本来はゲーム制作を目的としたpygameというpythonモジュールですが、
これが割と内容のあるものでして、ビットマップのピクセル編集や音声のサンプルデータ編集もいくらか行う事ができます。(音声まわりはまだ触ってません)
単純な画像編集は普段PILを使いますが、編集内容によってはpygameを持ち出す事も多いです。

そこで、ゲーム演出に重要なアニメーションエフェクトについてはpygameで制作環境を作成する事にしました。
今回はその環境を使い、1つのサンプルが出来るまでの手順を紹介したいと思います。


1.GIMPたんで以下のような画像を描く
想定より大きめに描いておきます。


2.pythonで挙動を定義するコードを描きます。(当サンプルはちょうど100行ほどで書ける演出です)
ここでインポートしてるモジュールが主な成果物です。


#!/usr/bin/python
#-*- encoding: utf-8 -*-
#coding=utf-8
from siraeffanim.EffectAnim_Engine import EffectAnim_Main
from siraeffanim.EffectAnim_Base import *
from siraeffanim.EffectAnim_Template import *
"""============================================================================
依存値の簡易指定~~
"""
#全体フレーム数。大きくするとエフェクト時間をゆったり引き延ばす。
TOTALLENGTH = 50
#出現オブジェ数
OBJECTNUM = 32
"""============================================================================
テストエフェクト。
エフェクトの総まとめ。
各エフェクトセルの使用をここで定義する。
"""
class EffectFrame_Test(EffectFrame):
def __init__(self, **kwargs ):
EffectFrame.__init__( self, kwargs ,
sellSplitSize = (2,3) , #画像ファイルの切り取り数。縦横にそれぞれ何分割するかって値。
CanvasSizeOfByScale = ( 2.5 , 2.5 )
)
self.paraminit( TOTALLENGTH ) #エフェクトの長さ。
#以下セルの定義。
#指定順番がそのまま描画順番になるよ。
#▼もやっ
self.AddSell( EffectFrameSell_Template_Simple(
param_SrcImageSellGridPos = ( 0 , 0 ) ,
lamb_DrawScale = lambda p,x:( lamb_grad_Sin2Line( p , 1.0 ) * x ) ,
lamb_DrawAlpha = lambda p,x:( lamb_grad_Sin2Line( p , 0.6 ) * x ) ,
param_DrawScale = ( 1.6 , 1.6 )
) )
#▼中心ピカー!
self.AddSell( EffectFrameSell_Template_Simple(
param_SrcImageSellGridPos = ( 0 , 1 ) ,
lamb_DrawScale = lambda p,x:( lamb_grad_Sin2Line( p , 0.6 ) * x ) ,
lamb_DrawAlpha = lambda p,x:( lamb_grad_Sin2Line( p , 0.5 ) * x ) ,
param_DrawScale = ( 1.0 , 1.0 )
) )
#▼衝撃シュパーン
self.AddSell( EffectFrameSell_Template_Simple(
param_SrcImageSellGridPos = ( 1 , 1 ) ,
lamb_DrawScale = lambda p,x:( p * x ) ,
lamb_DrawAlpha = lambda p,x:( lamb_grad_Sin2Line( p , 0.8 ) * x ) ,
param_DrawScale = ( 6.0 , 2.0 ) ,
lamb_SurfTransform = lambda surf:( pygame.transform.rotate( surf , 20.0 ) )
) )
#▼ピカピカぱーてぃこ~
#中心ピカーにコレが重なってさらにピカーってなる。その後は小さくなってふわっと消える感じで。
for i in range( OBJECTNUM ) :
#lambdaにそのまま書くと遅延評価されちゃうので、乱数含む値は一旦変数に。
alphatime = ( random.randint(35,98)*0.01 )
scale = ( random.randint(75,200)*0.01 )
xpos = ( random.randint(0,180) - 90 )/2 #中心から最終的に行く位置
ypos = ( random.randint(0,180) - 90 )/2
self.AddSell( EffectFrameSell_Template_Simple(
param_SrcImageSellGridPos = ( 1 , 0 ) ,
lamb_DrawPos = ( lambda p,x: p * x + sin( ( p + x*0.37 ) * PI * 2 ) * 5 , lambda p,x: p * x ) ,
lamb_DrawScale = lambda p,x:( ( 1.0 - p ) * x * ( 0.5 + ( sin( ( p * x )* 5 ) + 1 )/4 ) ) ,
lamb_DrawAlpha = lambda p,x:( lamb_grad_Sin2Line( p , x ) / 2 ) ,
param_DrawPos = ( xpos , ypos ) ,
param_DrawScale = ( scale , scale ) ,
param_DrawAlpha = alphatime ,
Blend = BLEND_ADD
) )
"""============================================================================
実行コード。
ファイルの指定や描画サイズの決定はここで。
"""
if __name__ == '__main__':
getImagePathList = [
os.path.abspath("Effanimmap_Fire1.png").decode('shift-jis'),
os.path.abspath("Effanimmap_EffHelper.png").decode('shift-jis'),
]
drawEffectFrames = ( #1度に複数のエフェクトを同時描画も可能
EffectFrame_Test(
imagepathlist = getImagePathList , #エフェクトイメージファイルのリストを指定
drawRect = DrawRect( (0,0) , (400,400) ) #描画位置、サイズを指定。
)
, )
EffectAnim_Main( drawEffectFrames , CanvasSize = (400,400) ) #実行開始。
view raw gistfile1.pytb hosted with ❤ by GitHub

3.以上のpyを実行するとテスト画面が現れる。


このウィンドウからエクスポートフォームを開き、
フレームスキップやセルサイズの設定をしてフレームアニメとして吐く事ができます。

以下、吐いたもの。(128*128セル設定(全長3200*128)で吐いたものを縮小しました)


非常に小さくなりました。一応ぬるぬる動いていい感じなのです。
一応この工程で"特定色を透明度にする"という操作を行っています。




感想

AviUtlの拡張編集でも同じような事はできる事に気づき、存在意義が疑わしくなってきました。もう黒歴史ですか。

結局今回のプラクティスで実践できたメリットというと・・・
1. ループなどを含めた値変化を利用してアニメーション定義が可能
   → 何千個といったパーティクルを簡単に定義、描画できる
   (もっと他の値に依存させれば活用の幅が広がる??)
2. フレームアニメの自動抽出。
くらいのものでしょうか。

スクリプティングについても、パーティクルの拡散を表す式など、
もっと可読性が上がるような構造化、マクロの用意など、課題が残ります。



成果物のモジュールの公開については、
まだ完成度が至ってない事、そして公開時の心得(ライセンスなど)について僕自身知らない事が多すぎる事があり、ひとまず未定とさせて頂きます。