Xangle/Yangle 01 - 簡単なエフェクトを作ろう

MUGEN1.1からExplodのパラメータに"Xangle"、"Yangle"が追加されました。Explodの3D回転が可能になりましたが、仕様なのか仕様じゃないのか分かりませんが大変使いにくいのです。というわけで、このブログで私の分かる範囲で解説していこうと思います

 
このブログではx軸は画面右方向正、y軸は画面下方向正、z軸は画面奥方向正の右手系をとっています



この記事では、左のような画像一枚を使って右のようなエフェクトを作ります
イメージ 1イメージ 2
 
 
そもそもですが、X、Yangleの表す角度は、
というものです。(これ発見するのに2年かかりました)

XYZオイラー角というのは、
まずx軸回転を行いますが、このときy軸、z軸も同時に回転します。回転後y軸回転を行い(このときx軸、z軸も同時に回転)、最後にz軸を中心に回転させて向きを決定するというものです
またこれは、最初の状態から動かないXYZ軸を用いて、
まずZ軸回転を行い、回転後Y軸回転を行い、最後にX軸回転を行うのと全く同じです。こっちの方が分かり易いので以降の説明ではXYZ軸は常に一定と思ってください
 
MUGEN上での回転方向ですが、Xangle、Yangle、angleは左ねじ方向に回ります。ちょっとややこしい。

ちなみにオイラー角は”XYZ”、”YXY”など全部で12種類あり、あらゆる向きを示すことが可能、またそれぞれ変換可能です。

大事なのは最初と最後で、XYZオイラー角では最初に”Z”軸回転(angle)を行いますがこれは”自転”です。もしも回転前の方向ベクトルが(0,0,1)なら、この”自転”で向きは変わらず、ねじれ回転します
後に”X”軸回転(Xangle)を行いますが、これは常に画面上を動かないX軸での回転となります
Yangleはいろいろ弄ってみると分かると思いますが、一見して訳が分からない回転をすると思います
説明しにくい、、、
 

オイラー角の説明はこの辺にしておいて、本題です。

上のようなエフェクトを作るためには、
絶えずZ軸回転を行いながら、
Y軸中心に75°くらい回転させ、
X軸中心に0°くらい回転させればよい
ということになります。簡単ですね。

;宣言して
[State -2,Explod]
type = Explod
trigger1 = time = 0
trigger1 = numexplod(8000) = 0
anim = 8000
ID = 8000
pos = 0, 0
postype = p1
bindtime = -1
removetime = 30
scale = 1, 1
sprpriority = -1
shadow = 0, 0, 0
ownpal = 1

;絶えずZ軸回転
[State -2,Explod]
type = ModifyExplod
trigger1 = numexplod(8000) = 1
ID = 8000
scale = 0.2,0.2
pos = 50,-50
postype = p1
xangle = 0
yangle = 75
angle = time*15

これだけです



が、実は問題があります

例えばこんな感じでExplodを出してみます
[State -2,Explod]
type = Explod
trigger1 = time = 0
trigger1 = numexplod(8001) = 0
anim = 0
ID = 8001
pos = 0, 0
postype = p1
bindtime = -1
removetime = 500
scale = 4, 4
sprpriority = -1
shadow = 0, 0, 0
ownpal = 1
angle = 0
xangle = 90
yangle = 0
イメージ 3
 
少し分かりにくいですが、angle、xangle、yangleは一定のはずなのに、画面上を移動するとExplodの向きが変わるという全く邪魔な機能が働きます。
 
ある一点から撮影するとき、その広さを表す角度を視野角といいますが、MUGENで3D回転を行うと自動で視野角が考慮されるようです。
自分の目の前に手を垂直に立ててみると分かり易いですが、その手を横に水平移動させると少しずつ見えていなかった手の横の部分が見えてきますよね?(このとき顔はまっすぐ固定です)
人間の視野角はたしか190°くらいあったような気がしますが、このように視野角が広いと画面端に行くほどに歪みます。
イメージ 5焦点距離はスクリーンまでの距離です
 
MUGENでの視野角ですが、測りました
焦点距離が500前後(おそらく500)、そのとき視野角は約100°です。
ただし、DEFファイルにあるlocalcoordが320,240の場合。まだよく分かっていませんが、localcoordが大きくなると焦点距離も大きくなるようです。大きい方の数字と比例関係にあるっぽい?
また、
イメージ 4
さっきからメンドクサがって手描きで申し訳ないですが、このグラフはy = (500*y')/zで、Z方向遠くに行けば行くほど小さくなっていく遠近法を表しています。Y'はスクリーン上での原点(画面中央)からの距離です。あるY'での接線とZ軸との角度θは微分なり割り算なりで出ますが
θ = Arctan(-Y'/500)
となります。これだけ歪んでいるってことです。よって、これだけ回転させれば視野角の影響をなくすことができます。
 
さて、この回転は常に画面上を動かない軸で行わなければいけません。○△◇オイラー角でいうところの○軸回転です。よってXYZオイラー角ではX軸回転(Xangle)のみです。これより、Y座標を変化させたときの歪みはXangleで対応できますが、X座標を変化させたときの歪みは対応できません
 
そこで、YXYオイラーに変換します。別にY○○オイラー角なら何でもいいですが、Y○YかY○Xオイラー角をおすすめします。ねじれ回転させるときに便利です。
もうメンドクサイので計算過程は飛ばして結果だけ書きますが、それでもメンドクサイ。"*"や"+"を使いすぎてる?せいでこれを1行で記述するとエラー落ちします。よって計算を分割して変数に一時保存する必要があります。

 
;まず変換前の各angleを変数に保存します。ここでは、
;var(0) コマンド入力時の方向キー
;var(1) ループ回数(1フレーム毎にループするので時間を示す)
;var(2) angle(X1Y2Z3オイラー角のZ3軸回転)
;fvar(0) xangle(X1Y2Z3オイラー角のX1軸回転)
;fvar(1) yangle(X1Y2Z3オイラー角のY2軸回転)
;fvar(2) Y1(Y1X2Y3オイラー角のY1軸回転)
;fvar(3) X2(Y1X2Y3オイラー角のX2軸回転)
;fvar(4) Y3(Y1X2Y3オイラー角のY3軸回転)
;fvar(5) 計算用変数
;fvar(6) 計算用変数
;fvar(7) 計算用変数
;fvar(8) 計算用変数


;注意!!
;angleは整数しか扱えません。そんなことなさそう。なんでそう思ったんだろう
;基本的に各三角関数は弧度法しか扱えません

[Statedef 300]
type    = S
movetype= I
physics = S
ctrl = 0
anim = 130

;方向キーによってちょっとエフェクトを変えます
[State 821, 4]
type = VarSet
trigger1 = time = 0
trigger1 = var(1) = 0
var(0) = (command = "holdfwd") + (command = "holdback")*2

;time = 1でループさせます。つまりvar(1)はループ回数であり時間を示します
[State 821, 4]
type = VarAdd
trigger1 = time = 0
var(1) = 1

;YXYオイラー角変換前の状態の定義です。赤文字Y座標を変化させたときの歪みをXangleで対応したもの。(ScreenHeight/GameHeight)はズーム率
;120 - ScreenPos Y + Pos Yはキャラの原点からの距離、-50とか-80はエフェクトのキャラからの距離
[State 821, 4]
type = Null
triggerall = time = 0
trigger1 = var(0) = 0
trigger1 = 1 || fvar(0) := 0-atan(((120 - ScreenPos Y + Pos Y - 50)*(ScreenHeight/GameHeight))/500)*180/pi
trigger1 = 1 || fvar(1) := 75
trigger1 = 1 ||  var(2) := var(1)*15;angle = time*15と思ってください
trigger2 = var(0) = 1
trigger2 = 1 || fvar(0) := 45-atan(((120 - ScreenPos Y + Pos Y - 80)*(ScreenHeight/GameHeight))/500)*180/pi
trigger2 = 1 || fvar(1) := 45
trigger2 = 1 ||  var(2) := var(1)*15
trigger3 = var(0) = 2
trigger3 = 1 || fvar(0) := 75-atan(((120 - ScreenPos Y + Pos Y - 0)*(ScreenHeight/GameHeight))/500)*180/pi
trigger3 = 1 || fvar(1) := 0
trigger3 = 1 ||  var(2) := var(1)*15
 
;-----------------------------------------
;①まずYXYオイラー角変換

;Y1変換

[State -2, var];式が長くなるので分割
type = varset
trigger1 = time = 0
fvar(5) = ((-sin(fvar(0)*(pi/180))*cos(var(2)*(pi/180)) + cos(fvar(0)*(pi/180))*sin(fvar(1)*(pi/180))*sin(var(2)*(pi/180)))**2 + (cos(fvar(1)*(pi/180))*sin(var(2)*(pi/180)))**2)**.5

[State -2, var];計算
type = varset
trigger1 = time = 0
fvar(2) = -2*atan((cos(fvar(1)*(pi/180))*sin(var(2)*(pi/180)))/(fvar(5) - sin(fvar(0)*(pi/180))*cos(var(2)*(pi/180)) + cos(fvar(0)*(pi/180))*sin(fvar(1)*(pi/180))*sin(var(2)*(pi/180))))*(180/pi)

;X2変換

[State -2, var];計算
type = varset
trigger1 = time = 0
fvar(3) = -acos(cos(fvar(0)*(pi/180))*cos(var(2)*(pi/180)) + sin(fvar(0)*(pi/180))*sin(fvar(1)*(pi/180))*sin(var(2)*(pi/180)))*(180/pi)

;Y3変換

[State -2, var];式が長くなるので分割
type = varset
trigger1 = time = 0
fvar(6) = ((sin(fvar(0)*(pi/180))*cos(fvar(1)*(pi/180)))**2 + (-cos(fvar(0)*(pi/180))*sin(var(2)*(pi/180)) + sin(fvar(0)*(pi/180))*sin(fvar(1)*(pi/180))*cos(var(2)*(pi/180)))**2)**.5

[State -2, var];計算
type = varset
trigger1 = time = 0
fvar(4) = 2*atan((-cos(fvar(0)*(pi/180))*sin(var(2)*(pi/180)) + sin(fvar(0)*(pi/180))*sin(fvar(1)*(pi/180))*cos(var(2)*(pi/180)))/(fvar(6) + sin(fvar(0)*(pi/180))*cos(fvar(1)*(pi/180))))*(180/pi) + 180

;-----------------------------------------
;②変換後、X座標を変化させたときの歪みをY1で対応します

[State -2, var]
type = varadd
trigger1 = time = 0
trigger1 = var(0) = 0
fvar(2) = -facing*atan(((Pos X + facing*50)*(ScreenWidth/GameWidth))/500)*180/pi

[State -2, var]
type = varadd
trigger1 = time = 0
trigger1 = var(0) = 1
fvar(2) = -facing*atan(((Pos X + facing*40)*(ScreenWidth/GameWidth))/500)*180/pi

[State -2, var]
type = varadd
trigger1 = time = 0
trigger1 = var(0) = 2
fvar(2) = -facing*atan(((Pos X + facing*60)*(ScreenWidth/GameWidth))/500)*180/pi

;-----------------------------------------
;③MUGENはXYZオイラー角しか扱えないのでXYZオイラー角変換します

;Xangle(X1)変換

[State -2, var];式が長くなるので分割
type = varset
trigger1 = time = 0
fvar(7) = ((cos(fvar(2)*(pi/180))*cos(fvar(3)*(pi/180))*cos(fvar(4)*(pi/180)) - sin(fvar(2)*(pi/180))*sin(fvar(4)*(pi/180)))**2 + (sin(fvar(3)*(pi/180))*cos(fvar(4)*(pi/180)))**2)**.5

[State -2, var];計算
type = varset
trigger1 = time = 0
fvar(0) = 2*atan((sin(fvar(3)*(pi/180))*cos(fvar(4)*(pi/180)))/(fvar(7) + (cos(fvar(2)*(pi/180))*cos(fvar(3)*(pi/180))*cos(fvar(4)*(pi/180)) - sin(fvar(2)*(pi/180))*sin(fvar(4)*(pi/180)))))*(180/pi)

;Yangle(Y2)変換

[State -2, var];計算
type = varset
trigger1 = time = 0
fvar(1) = -asin(-cos(fvar(2)*(pi/180))*sin(fvar(4)*(pi/180)) - sin(fvar(2)*(pi/180))*cos(fvar(3)*(pi/180))*cos(fvar(4)*(pi/180)))*(180/pi)

;angle(Z3)変換

[State -2, var];式が長くなるので分割
type = varset
trigger1 = time = 0
fvar(8) = ((sin(fvar(2)*(pi/180))*sin(fvar(3)*(pi/180)))**2 + (cos(fvar(2)*(pi/180))*cos(fvar(4)*(pi/180)) - sin(fvar(2)*(pi/180))*cos(fvar(3)*(pi/180))*sin(fvar(4)*(pi/180)))**2)**.5

[State -2, var];計算
type = varset
trigger1 = time = 0
var(2) = ceil(2*atan((sin(fvar(2)*(pi/180))*sin(fvar(3)*(pi/180)))/(fvar(8) + cos(fvar(2)*(pi/180))*cos(fvar(4)*(pi/180)) - sin(fvar(2)*(pi/180))*cos(fvar(3)*(pi/180))*sin(fvar(4)*(pi/180))))*(180/pi)-0.4)

;-----------------------------------------
;あとは簡単。まずExplodを宣言して、
[State -2,Explod]
type = Explod
trigger1 = time = 0
trigger1 = numexplod(8000) = 0
anim = 8000
ID = 8000
pos = 0, 0
postype = p1
bindtime = -1
removetime = 30
scale = 1, 1
sprpriority = -1
shadow = 0, 0, 0
ownpal = 1

;ここで回転させます
[State -2,Explod]
type = ModifyExplod
trigger1 = time = 0
trigger1 = numexplod(8000) = 1
ID = 8000
scale = 0.2,0.2
pos = (var(0) = 0)*50 + (var(0) = 1)*40 + (var(0) = 2)*60,(var(0) = 0)*(-50) + (var(0) = 1)*(-80) + (var(0) = 2)*0
postype = p1
xangle = fvar(0)
yangle = fvar(1)
angle = var(2)
 
;ループ終了処理
[State 821, 4]
type = ChangeState
trigger1 = time = 1
trigger1 = var(1) = 30
trigger1 = 1 || var(1) := 0
value = 0
ctrl = 1

;ループ処理
[State 821, 4]
type = ChangeState
trigger1 = time = 1
value = stateno

ここまで。正直歪みを取るためだけにここまでやる必要はないと思います

さらにさらに問題がありまして、ループ回数var(1) = 24のとき、angleはvar(1)*15ですからangle = 360になります。
sin(360°) = 0、cos(360°) = 1ですが、このように0°とか90°とかでは高確率でバグります。オイラー角変換する際に分母が0となるなどが主な原因です。幸いMUGENが落ちることはなく、基本的には0が入力されます

今回は以上。要するにXYZオイラー角ってことです

この記述を搭載したキャラクターを公開しておきます(XYangle_SankouYou_kfm.zip
DLはブログ右側の”トップ”から。

文が分かりにくかったりMUGEN記述やここで使った数学等について分からない場合はコメントがあればできるだけ答えます。

あと堂々と書いてますが間違ってたらゴメンナサイ
MUGEN製作のメモ置き場
 
 
MUGENキャラの製作状況
現在:
沢田綱吉を製作中(家庭教師ヒットマンREBORN!

プッチ神父を公開中(AI付き)
岸辺露伴の外部AIを公開中
ジョナサン(旧)の外部AIを公開中
仗助の外部AIを公開中
エンポリオの外部AIを公開中
1部ディオを公開中(AI付き)
スティーリーダンを公開中(AI付き)
ステージ2つ公開中

あとたまにMUGEN1.1上で3Dモデル組み立てて遊んでいるので興味ある方はどうぞ
https://0011-mugen.hatenablog.com/archive/category/MUGEN%E5%B0%8F%E3%83%8D%E3%82%BF%E6%8A%80%E8%A1%93

公開先
https://onedrive.live.com/redir?resid=7C0630D7E27F52C3!569&authkey=!ALvVjJz8aRCBjXc&ithint=folder%2czip
Twitter(連絡先)
https://twitter.com/xyz_0011
一応メール
w0eleven@yahoo.co.jp