X68000 ZのMicro Pythonで、ひとコマアニメーションアプリ「まいくろえほん」を作成したので、紹介します。
ひとコマアニメーションとは
- 2018年キッズプラザ大阪コンピュータ工房ワークショップで子供むけに開発された、アニメ制作手法です。1コマ絵を描くだけでホワイトボードアニメーションが作れます。出典はこちら
幼稚園の子供でもできるので、キッズプラザ大阪では、毎日 150-200本のひとコマアニメーションが作られています
ひとコマアニメーションが作れるアプリ
- うるさいえほん
- まいくろえほん
- 9VAeきゅうべえ
- PEAS motch!
うるさいえほんは、JavaScriptで記述された、音声合成つきひとコマアニメーションアプリです。使い方、説明はこちら
まいくろえほんは、うるさいえほんを、X68000 Z で再現しようとつくったアプリです。
9VAeきゅうべえは、ひとコマアニメの元になった無料のモーショングラフィックスアプリです。
PEAS motch!は、キッズプラザ大阪で使われているアプリで、9VAeから作られました。
まいくろえほん
うるさいえほんに掲載されているひまわり.onep を再生した結果です。作者は、ごっほこはま(2022)
仕様
- メニュー外をクリックしたあと描画できます。線をひくと音が鳴ります。Runで再生
- RYGCBMW:描画色設定(赤黄緑水色青紫白)
- 数字: ペンの太さ。9以上は〇で描きます
- HSV:描画色の調整。マウスカーソルが設定された色。
- <-- -->: アプリと同じフォルダに OnePファイルがある場合、メニューに表示し、クリックで再生できます。そのファイルの切り替え。上のひまわりの例では、ONEP-HAN.ONE, MUKASHI.ONE, HIMAWARI.ONE というOnePファイルが同じフォルダにあります。
- Quit:描画した絵をout.oneファイルに保存して終了。
- X68000 は描画性能が低いため、太い線を4本の直線または円で表現しています。
MicroPython版「まいくろえほん」 ソースコード
X68000 Z または、エミュレータXM6で動きます。描画、音の発声に x68k.iocs関数を使っています。x68k.iocs関数の説明はこちら
ehon.py
import sys
import os
from x68k import *
from struct import pack
iocs(i.OS_CUROF) #Text Cursor OFF
#crtmod(14,1) #255x255x64kColor
crtmod(12,1) #512x512x64kColor
iocs(i.MS_CURON) #Mouse Cursor ON
iocs(i.SKEY_MOD,0) #SoftKeyboard OFF
g=GVRam(0) #Graphic
tx=TVRam() #Text VRam
SAVFILE="out.one"
TMPFILE="out.###"
stIS = -1
stSaved = -1
strk = [] # line (x,y)
strc = [] # line color
onep = [] # oneP file name
sx0,sy0 = -999,-999
oneS=0 # oneP start
wrH,wrS,wrV=128,31,15
wrtCol=iocs(i.HSVTORGB,(wrH<<16)|(wrS<<8)|wrV)
wrtW=2
colHue =(0,32,64,96,128,160,0) #RYGCBMR
def fnQuit(x):
drawMenu(0,0)
fnSave(x)
iocs(i.OS_CURON) #Text Cursor ON
crtmod(16,1) #768x512x16
iocs(i.SKEY_MOD,-1) #SoftKeyboard ON
iocs(i.B_CLR_ST,2) #Screen Reset
sys.exit()
def opm_init():
opmdrv = iocs(i.B_LPEEK, a1=(0x000400 + 4 * 0xf0))
if opmdrv < 0 or (opmdrv >= 0xfe0000 and opmdrv <= 0xffffff):
iocs(i.OS_CURON) #Text Cursor ON
crtmod(16,1) #768x512x16
iocs(i.SKEY_MOD,-1) #SoftKeyboard ON
iocs(i.B_CLR_ST,2) #Screen Reset
print("OPMDRV is not installed")
sys.exit()
iocs(i.OPMDRV, 0x00)
def opm_alloc(size):
iocs(i.OPMDRV, 0x01, (1 << 16) | size)
#iocs(i.OPMDRV, 0x01, (2 << 16) | size)
#iocs(i.OPMDRV, 0x01, (3 << 16) | size)
iocs(i.OPMDRV, 0x02, (1 << 16) | 1) #assign 1
#iocs(i.OPMDRV, 0x02, (2 << 16) | 2) #assign 2
#iocs(i.OPMDRV, 0x02, (3 << 16) | 3) #assign 3
def opm_free():
ret = 0
ret |= iocs(i.OPMDRV, 0x07, 1)
#ret |= iocs(i.OPMDRV, 0x07, 2)
#ret |= iocs(i.OPMDRV, 0x07, 3)
return ret
def opm_trk(trk, mml):
mml += "\x00"
iocs(i.OPMDRV, 0x06, trk, a1=mml)
opm_init()
opm_alloc(1000)
iocs(i.OPMDRV, 0x05, 200) #tempo 32-200
mml='@65A'
opm_trk(1,mml)
def setPal():
global wrtCol,wrH,wrS,wrV
wrtCol=iocs(i.HSVTORGB,((wrH%192)<<16)|((wrS%32)<<8)|(wrV%32))
iocs(i.TPALET,8,wrtCol) #Mouse Color
if wrtCol&0xf800 > 0xc800:
iocs(i.TPALET,7,0x30) #Mouse Edge
else:
iocs(i.TPALET,7,0xfffe)
def fnRGBW(x):
global wrtCol,wrH,wrS,wrV
wrS,wrV=31,31
if x/8<6:
wrH=colHue[int(x/8)]
else:
wrS=0
setPal()
def fnHue(x):
global wrH
if len(onep)>0:
fnRun(-1)
return
if x/8<4:
wrH-=8
else:
wrH+=8
setPal()
def fnSat(x):
global wrS
if len(onep)>1:
fnRun(-2)
return
if x/8<4:
wrS-=4
else:
wrS+=4
setPal()
def fnVal(x):
global wrV
if len(onep)>2:
fnRun(-3)
return
if x/8<4:
wrV-=4
else:
wrV+=4
setPal()
def getPal(col):
rr=int("0x"+col[0:2])
gg=int("0x"+col[2:4])
bb=int("0x"+col[4:6])
return ((gg<<8)&0xf800)|((rr<<3)&0x07c0)|((bb>>2)&0x3e)
def getRGB(cc):
rr=(cc & 0x7c0)>>3
gg=(cc & 0xf800)>>8
bb=(cc & 0x3e)<<2
return '#{:02x}{:02x}{:02x}'.format(rr,gg,bb)
def drawLine(lin):
global mml
x=lin.find('width=')
wid=1
if x>0:
y=lin[x+7:].find('\"')
wid=int(float(lin[x+7:x+7+y])/2)
x=lin.find('stroke=')
pal=15
if x>0:
y=lin[x:].find('#')
x+=y+1
pal=getPal(lin[x:x+6])
y=lin.find('d="M')
x=y+4
x0,y0=-999,-999
while 1:
y=lin[x:].find(',')
xx=int(float(lin[x:x+y]))
x+=y+1
y=lin[x:].find('L')
if y<0:
return 0
yy=int(float(lin[x:x+y]))
x+=y+1
if x0!=-999 or y0!=-999:
if wid>8:
g.circle(x0,y0,wid,pal,0,360,388)
else:
g.line(x0,y0,xx-wid,yy,pal)
if wid>0:
g.line(x0,y0,xx,yy-wid,pal)
g.line(x0,y0,xx+wid,yy,pal)
g.line(x0,y0,xx,yy+wid,pal)
iocs(i.OPMDRV, 0x08, pack('L',1), 0,0) #play 1ch
x0,y0=xx,yy
btn = iocs(i.MS_GETDT)&0xff #R-btn
if btn>0:
return 1
def fnRun(x):
g.wipe()
if x<0:
drawMenu(0,0)
g.fill(0,0,512,512,wrtCol)
fn=onep[-x-1+oneS]
print(fn)
else:
fn=SAVFILE
try:
with open(fn) as f:
for line in f:
if '</page' in line:
break
if '<path' in line:
brk=drawLine(line)
if brk>0:
break
except:
pass
drawMenu(mx,my)
def fnSave(x):
global mx,my,stIS,stSaved
if stIS < 0:
return
if stIS<=stSaved:
return
to = open(TMPFILE,'w')
to.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n')
to.write('<onep viewBox="0 0 512 512">\n')
to.write('<page>\n')
wrt=0
try:
with open(SAVFILE) as f:
for line in f:
if '</page' in line:
break
if wrt>0:
to.write(line)
if '<page' in line:
wrt=1
except:
pass
for st in range(stIS+1):
if stIS<=stSaved:
continue
to.write( strc[st] +' d="'+strk[st]+'" />\n')
stSaved = stIS
to.write('</page>\n')
to.write('</onep>\n')
to.close()
try:
os.remove(SAVFILE)
except:
pass
os.rename(TMPFILE,SAVFILE)
def fnSaveRun(x):
drawMenu(0,0) #menu off
fnSave(x)
fnRun(x)
def fnSelect(x):
global oneS
if x/8<4:
if oneS>0:
oneS-=1
else:
if oneS+3<len(onep):
oneS+=1
drawMenu(0,0)
drawMenu(mx,my)
def fnWidth(x):
global wrtW
if x/8<4:
if wrtW>=2:
wrtW=int(wrtW/2)
else:
if wrtW<=32:
wrtW*=2
drawMenu(0,0)
drawMenu(mx,my)
#-------------------
menu=('RYGCBMW','- %d +','- H +','- S +','- V +','<-- -->','Run','Quit')
func=(fnRGBW ,fnWidth ,fnHue ,fnSat ,fnVal ,fnSelect ,fnSaveRun,fnQuit)
#-------------------
files=[f for f in os.listdir(".")]
for fn in files:
if fn.find(SAVFILE)>=0:
continue
if fn.find('.one')>0 or fn.find('.ONE')>0:
onep.append(fn)
mnOn=0
mh=len(menu)*16
mw=0
for it in menu:
if len(it)>mw:
mw=len(it)
for it in onep:
if len(it)>mw:
mw=len(it)
mw *= 8
mx,my=100,100
def drawMenu(x,y):
global mnOn,mx,my
if mnOn>0:
mnOn=0
iocs(i.B_CLR_ST,2)
return
if x+mw>511:
x=511-mw
if y+mh>511:
y=511-mh
iocs(i.TPALET,2,0x1e)
mnOn,mx,my = 1,x-(x%8), y-(y%16)
tx.box(0,mx-4,my-4,mw+8,mh+8)
y=int(my/16)
z=0
for it in menu:
iocs(i.B_LOCATE,int(mx/8),y)
if z==1:
print("- ",wrtW," +")
elif z==5 and len(onep)==0:
print("No OneP")
elif z>=2 and z-2<len(onep) and z<=4:
print(onep[z-2+oneS]) #File Name
else:
print(it)
y+=1
z+=1
tx.rev(1,mx-4,my-4,mw+8,mh+8)
def execMenu(x,y):
global mnOn,mx,my #for Menu
if mnOn==0 or x<mx or x>mx+mw or y<my or y>my+mh:
return 0
if y-my<len(func)*16:
func[int((y-my)/16)](x-mx)
return 1
def addLine():
global strk,stIS,wrtW
strk.append('x')
strc.append(' <path stroke="'+ getRGB(wrtCol)+'"' +' width="'+str(wrtW)+'"' )
stIS +=1
sx0,sy0 = -999,-999
def addPoint(x,y):
global sx0,sy0,wrtCol,wrtW
if abs(x-sx0)<3 and abs(y-sy0)<3:
return
if strk[stIS][0]!='M':
strk[stIS]='M'+str(x)+','+str(y)
else:
strk[stIS]=strk[stIS]+'L'+str(x)+","+str(y)
if wrtW>8:
g.circle(sx0,sy0,wrtW,wrtCol,0,360,388)
else:
g.line(sx0,sy0,x-wrtW,y,wrtCol)
g.line(sx0,sy0,x,y-wrtW,wrtCol)
g.line(sx0,sy0,x+wrtW,y,wrtCol)
g.line(sx0,sy0,x,y+wrtW,wrtCol)
iocs(i.OPMDRV, 0x08, pack('L',1), 0,0) #play 1ch
sx0,sy0 = x,y
# Main Loop
mnOn=1
fnRun(0)
drawMenu(100,100)
bt0=0
while 1:
pos = iocs(i.MS_CURGT)
x = (pos >>16)&0xffff
y = pos & 0xffff
btn = iocs(i.MS_GETDT)&0xffff
if btn==bt0:
if mnOn==0 and btn & 0xff00: #left Down
addPoint(x,y)
continue
bt0=btn
if btn & 0xff: #right
drawMenu(x,y)
if btn & 0xff00: #left
if execMenu(x,y):
continue
if mnOn:
drawMenu(x,y)
addLine()
iocs(i.OS_CURON)
input("End")
crtmod(16,1)
iocs(i.SKEY_MOD,-1)
iocs(i.B_CLR_ST,2)
関数 | 処理内容 | 引数 |
---|---|---|
fnQuit | 終了処理 | |
opm_init | FM音源初期化 | |
opm_alloc | FM音源トラック用メモリ確保 | |
opm_free | FM音源メモリ解放 | |
opm_trk | 音の追加 | mml=音 |
setPal | テキストグラフィックのパレット設定 | |
fnRGBW | 色の設定 | x=クリック水平位置 |
fnHue | 色合い設定 | x=クリック水平位置 |
fnSat | 彩度設定 | x=クリック水平位置 |
fnVal | 明度設定 | =クリック水平位置 |
getPal | 線の色からX68Kの色への変換 | col=RRGGBB文字 |
getRGB | X68K色からRGB文字列への変換 | cc=X68K色 |
drawLine | 線を描く | lin=<path文字列 |
fnRun | 再生 | |
fnSave | ファイル保存 | |
fnSaveRun | 保存して再生 | |
drawMenu | ポップアップメニュー表示 | (x,y)表示位置 |
execMenu | メニュー項目の実行 | (x,y)クリックした位置 |
addLine | 線のタグを1つ追加 | |
addPoint | 線の座標点を追加し、描画も行う | (x,y)座標 |
実行のしかた
- X68000 Z または、X68Kエミュレータが必要です。
- Micro Python用SDカードを作ります。作り方はこちら。automount.xdfというフロッピーディスクイメージを作ります。
- 上のソースコードを ehon.py ファイルに保存し、automount.xdf の中にいれます。
- X68000 Z のコマンドプロンプトから以下の命令を実行します。
micropyt ehon.py
音を出すには、OPMDRV をいれておく必要があります。
OneP データ解説
- ひとコマアニメーション(One Picture Animation)を保存するXMLテキストフォーマットです。拡張子は「.onep」または「.one」
- うるさいえほんの「はっぴょう」にサンプルデータがあります。「むかしばなし」がおすすめ
- 再生したあと「ファイル」「ファイルにほぞん」でダウンロードできます。
- OnePデータは9VAeきゅうべえで読み込み、保存ができます。
- 文字コードは UTF8 ですが、まいくろえほんは ShiftJIS
サンプルデータ(はあと)
うるさいえほんの「はっぴょう」にある「はあと.onep」です
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<onep viewBox="0 0 640 480">
<page>
<path stroke="#000000" width="10" d="M312.33,332L308.33,332L307.33,332L297.33,332L281.33,329L259.33,318L232.33,297L212.33,273L205.33,252L205.33,236L209.33,221L215.33,212L225.33,207L234.33,207L245.33,208L256.33,216L269.33,225L279.33,232L282.33,235L287.33,230L300.33,218L316.33,206L339.33,199L353.33,199L361.33,199L365.33,202L367.33,210L368.33,228L365.33,249L356.33,266L341.33,286L331.33,300L326.33,306L322.33,312L319.33,316L315.33,321L309.33,327L307.33,332L305.33,334L304.33,334"/>
<path stroke="#ff0000" width="50" d="M307.33,372L307.33,372L304.33,370L294.33,368L280.33,366L262.33,362L255.33,360L250.33,357L244.33,355L237.33,353L230.33,350L221.33,346L213.33,342L209.33,341L205.33,338L202.33,334L197.33,332L191.33,325L186.33,318L181.33,311L175.33,305L174.33,301L171.33,298L169.33,292L167.33,286L163.33,280L161.33,272L160.33,266L157.33,260L156.33,251L155.33,245L155.33,238L155.33,231L155.33,222L155.33,216L156.33,208L157.33,204L159.33,197L163.33,190L167.33,183L171.33,178L172.33,176L177.33,172L180.33,168L184.33,164L189.33,162L195.33,158L199.33,156L201.33,154L203.33,153L206.33,152L210.33,151L216.33,151L220.33,151L222.33,151L226.33,151L231.33,151L237.33,155L239.33,156L244.33,159L246.33,161L250.33,162L255.33,164L257.33,166L261.33,167L262.33,168L263.33,170L265.33,170L268.33,171L270.33,172L273.33,173L279.33,175L282.33,179L283.33,180L284.33,180L286.33,178L295.33,175L305.33,171L314.33,168L326.33,165L338.33,162L349.33,158L358.33,156L362.33,155L367.33,154L369.33,154L369.33,154L371.33,154L373.33,154L379.33,154L388.33,154L396.33,158L399.33,159L403.33,162L408.33,164L411.33,169L415.33,176L419.33,183L421.33,189L422.33,195L422.33,203L422.33,211L422.33,217L422.33,225L422.33,231L421.33,237L419.33,246L417.33,252L413.33,259L411.33,267L408.33,272L406.33,278L405.33,283L401.33,287L399.33,291L395.33,298L392.33,302L387.33,310L383.33,315L381.33,319L377.33,325L373.33,329L370.33,334L365.33,340L361.33,346L358.33,350L352.33,357L346.33,362L341.33,368L337.33,370L332.33,373L326.33,375L323.33,376L323.33,376L321.33,377L321.33,378"/>
<path stroke="#29ad63" width="1" d="M314.33,373L311.33,371L307.33,370L301.33,369L297.33,368L293.33,368L287.33,367L281.33,367L275.33,366L272.33,365L268.33,364L263.33,362L259.33,361L253.33,359L251.33,357L244.33,350L237.33,344L230.33,338L220.33,332L213.33,328L209.33,326L206.33,324L205.33,324L202.33,322L200.33,321L197.33,320L193.33,318L191.33,316L188.33,315L185.33,313L183.33,312L181.33,307L177.33,300L173.33,293L171.33,286L167.33,279L164.33,271L161.33,262L160.33,256L158.33,250L157.33,242L157.33,237L157.33,231L157.33,225L157.33,220L157.33,214L157.33,210L157.33,204L157.33,198L159.33,193L161.33,186L162.33,182L165.33,176L167.33,174L172.33,169L174.33,164L178.33,163L181.33,158L185.33,158L187.33,157L191.33,155L192.33,154L195.33,154L199.33,153L203.33,153L208.33,153L216.33,153L221.33,153L226.33,154L230.33,155L235.33,156L239.33,158L243.33,160L247.33,161L250.33,163L255.33,166L259.33,168L260.33,170L261.33,172L262.33,173L265.33,175L267.33,176L268.33,177L269.33,180L271.33,180L273.33,182L273.33,182L274.33,183L276.33,184L277.33,184L277.33,184L278.33,185L279.33,185L279.33,186L282.33,186L283.33,188L284.33,190L284.33,191L284.33,194L282.33,196L281.33,197L280.33,198L279.33,198L278.33,198L275.33,194L275.33,194L275.33,193L275.33,189L278.33,186L285.33,182L290.33,180L295.33,178L297.33,177L298.33,176L302.33,176L307.33,175L313.33,172L318.33,171L323.33,170L327.33,169L332.33,167L336.33,166L339.33,164L343.33,164L348.33,163L350.33,163L356.33,162L361.33,161L367.33,160L369.33,160L373.33,160L376.33,160L381.33,160L388.33,164L392.33,164L393.33,165L397.33,168L399.33,170L403.33,173L407.33,176L410.33,180L411.33,182L415.33,186L417.33,189L418.33,194L421.33,196L421.33,202L421.33,205L421.33,209L421.33,216L421.33,222L421.33,227L421.33,230L421.33,233L421.33,239L421.33,242L421.33,246L421.33,249L420.33,253L419.33,256L418.33,260L417.33,262L415.33,266L413.33,272L411.33,276L408.33,279L405.33,287L401.33,294L399.33,297L397.33,300L395.33,306L393.33,310L390.33,317L388.33,320L385.33,324L383.33,329L379.33,334L372.33,342L369.33,346L364.33,350L358.33,356L352.33,360L348.33,362L341.33,367L333.33,370L327.33,373L321.33,373L318.33,376L317.33,376L317.33,376L316.33,376L315.33,376L314.33,376L313.33,376L313.33,376L312.33,376L309.33,374L306.33,373L304.33,371L301.33,370"/>
<path stroke="#2c8bc4" width="40" d="M163.33,369L162.33,369L160.33,369L157.33,370L156.33,372L150.33,375L142.33,382L131.33,388L123.33,392L122.33,393L123.33,394L125.33,394"/>
<path stroke="#2c8bc4" width="40" d="M216.33,406L217.33,406L218.33,406L216.33,415L208.33,428L203.33,436L203.33,440"/>
<path stroke="#2c8bc4" width="40" d="M300.33,426L301.33,429L301.33,441L300.33,453L300.33,460L300.33,460L300.33,461L300.33,462"/>
<path stroke="#2c8bc4" width="40" d="M387.33,406L387.33,406L391.33,406L395.33,410L400.33,418L404.33,426L408.33,434L410.33,437"/>
<path stroke="#2c8bc4" width="40" d="M430.33,364L431.33,364L435.33,364L441.33,364L450.33,365L471.33,375L485.33,380L489.33,381L489.33,381L491.33,381L491.33,381"/>
<path stroke="#2c8bc4" width="40" d="M473.33,298L473.33,297L474.33,296L479.33,296L485.33,296L497.33,293L513.33,290L521.33,288"/>
<path stroke="#2c8bc4" width="40" d="M483.33,218L483.33,216L486.33,216L490.33,216L497.33,214L511.33,209L526.33,202L539.33,195L543.33,193L542.33,193L539.33,193"/>
<path stroke="#2c8bc4" width="40" d="M472.33,149L471.33,148L475.33,146L484.33,141L493.33,137L499.33,135"/>
<path stroke="#2c8bc4" width="40" d="M422.33,103L423.33,103L423.33,102L431.33,96L445.33,82L454.33,72L454.33,72"/>
<path stroke="#2c8bc4" width="40" d="M363.33,98L363.33,97L363.33,94L361.33,86L357.33,69L353.33,56L352.33,51L351.33,51"/>
<path stroke="#2c8bc4" width="40" d="M293.33,113L293.33,113L293.33,112L292.33,106L292.33,97L292.33,82L292.33,70L292.33,58L292.33,52"/>
<path stroke="#2c8bc4" width="40" d="M209.33,91L209.33,88L209.33,78L197.33,57L191.33,46L191.33,44"/>
<path stroke="#2c8bc4" width="40" d="M159.33,110L155.33,108L145.33,100L135.33,92L126.33,86L121.33,84L120.33,84L119.33,84"/>
<path stroke="#2c8bc4" width="40" d="M113.33,167L112.33,169L108.33,169L105.33,169L102.33,168L97.33,166L86.33,161L64.33,153L56.33,150L53.33,149L53.33,149L53.33,149L54.33,149L55.33,149"/>
<path stroke="#2c8bc4" width="40" d="M91.33,215L89.33,216L87.33,218L85.33,219L85.33,220L79.33,222L61.33,227L37.33,230L23.33,232L18.33,232L18.33,232"/>
<path stroke="#2c8bc4" width="40" d="M86.33,273L86.33,274L86.33,275L81.33,281L59.33,292L39.33,302L29.33,305L28.33,305L32.33,305L33.33,305"/>
<path stroke="#2c8bc4" width="40" d="M107.33,324L109.33,324L103.33,328L92.33,334L83.33,338L81.33,339"/>
</page>
</onep>
OneP フォーマットの拡張計画
- 今後、OnePフォーマットを拡張し、9VAeの全データを保存できるようにしたいと考えています。今、9VAeは、SVG形式で保存していますが、ブラウザで再生できるようにするため、かなり複雑な構造です。それをもっと可読性の高いテキストフォーマットで保存したいと思っています。