エフェクトオブジェクト解説

関数リファレンスだけでは説明不足になるので、ここでObjEffectの解説をします。

Sample.EA解説
EA01頂点の意味
EA02プリミティブ
EA03頂点の座標
EA04頂点のUV値
Sample.EBエフェクトサンプル
EB01四角形画像を描画
Sample.ECエフェクトサンプル(応用)
EC01四重結界(もどき)

EA01 頂点の意味
東方弾幕風は2Dゲームですが、描画には3Dグラフィクスの機能を用いています。
3Dといえば、よくテクスチャとかポリゴンとか聞くと思います。
テクスチャとは画像です。絵です。
ポリゴンとは3Dグラフィクスで物体をあらわすのに用いる多角形のことです。

ポリゴンを表すのに用いられるのが頂点です
例えば、三角形を表すには、3つの頂点が必要です。
(3つの点を結べば三角形になりますよね)
ポリゴンはほとんどが三角形の集合で表されます。
三角形は凸な角だけで構成されたりして都合がよいのです。

さて、弾幕風の自機の画像も、弾の画像も
画像ファイルから読み込んだテクスチャの一部を
四角形に切り取って画面に貼り付けているのは、
見ただけで分かると思います。
その四角形もポリゴンなのです。
四角形のポリゴンにテクスチャを貼り付けて、
絵を表示しているのです。


EA02 プリミティブ
ポリゴンは三角形の集合で表されます。
しかしながら、どのような頂点の並びでどのような三角形の集合を
あらわすのか決めないと困ってしまいます。
頂点の並びで定義される図形を決めてやらないといけません。
この頂点の並びで定義される図形をプリミティブといいます。
このプリミティブには三種類が用意されています。

例えば四角形を表すには、TRIANGLELISTでは6つの頂点が必要になりますが、
TRAIANGLESTRIPやTRAIANGLEFANでは4つの頂点で表すことができます。

それでは、よくSTGにでてくる曲がるレーザーにはどれが使いやすいでしょうか。
曲がるレーザーにはTRAIANGLESTRIPが使いやすいですよね。
TRAIANGLESTRIPは、最初の頂点からはしご上に連結した形状を表すので
曲がるレーザーのように、途中が曲がったものには最適なのです。

もちろん、TRAIANGLELISTは、ある意味どんな図形でも表現できるので
当然曲がるレーザーを表現することもできますが、
頂点の数が多くなるので面倒なのです。


EA03 頂点の座標
頂点には座標があります。
ただし、この座標は、移動、回転、拡大前の座標であることに注意しないといけません。
描画する頂点は、Obj_SetPosisionで設定した、x,y座標や、
ObjEffect_SetAngleでの角度、ObjEffect_SetScaleでの拡大率によって
描画先が移動します。
頂点の移動は、拡大→回転→移動の順で行っています。

例えば、↓の図の a と b は、同じ大きさの四角形になるように頂点の座標を配置していますが、
回転の中心や、描画先の場所が異なります。


EA04 頂点のUV値
UV値はテクスチャ画像における座標をあらわします。
本来は、0-1の値をとります(まあ0-1以外の値をとることもできますが)が、
弾幕風では、画像のx座標がuにy座標がvにそのまま対応します。

ポリゴンを描画するときに、各頂点のUV値をもとにポリゴン上の1点1点が
テクスチャ上のどの位置を参照しているのかを求めています。

まあ、大雑把にまとめると
画像の切り抜き部分を指定するのがUV値です。
頂点の座標とUV値は異なったものなので、画像を歪めて表示したりもできます。


EB01 四角形画像を描画
	//幅20、高さ20の四角形ポリゴンに、
	//画像ファイルの(0,0)-(10,10)の四角形部分を対応させて描画する
	task square()
	{
		let obj = Obj_Create(OBJ_EFFECT);//エフェクトオブジェクトを作成
		ObjEffect_SetTexture(obj, imgEffect);//なんらかのテクスチャを設定
		ObjEffect_SetPrimitiveType(obj, PRIMITIVE_TRIANGLESTRIP);//プリミティブタイプを設定
		ObjEffect_CreateVertex(obj, 4);//頂点を4個つくる
		
		//四角形状に各頂点の座標を設定
		ObjEffect_SetVertexXY(obj, 0, -10, -10);
		ObjEffect_SetVertexXY(obj, 1, -10,  10);
		ObjEffect_SetVertexXY(obj, 2,  10, -10);
		ObjEffect_SetVertexXY(obj, 3,  10,  10);
		
		//四角形状に各頂点のUV値を設定。(0,0)-(10,10)の部分。
		ObjEffect_SetVertexUV(obj, 0,  0,  0);
		ObjEffect_SetVertexUV(obj, 1,  0, 10);
		ObjEffect_SetVertexUV(obj, 2, 10,  0);
		ObjEffect_SetVertexUV(obj, 3, 10, 10);

		//STGシーンの真ん中に表示
		Obj_SetPosition(obj, GetCenterX, GetCenterY);
		
		//60秒間待って消滅
		loop(60){yield;}
		Obj_Delete(obj);
	}
	


EC01 四重結界(もどき)

四重結界に似せたエフェクトです。
こんなエフェクトになると、矩形画像だけでは処理しきれないので、
3Dグラフィクスの機能が必要になってきます。

四重結界では、エフェクトの最終段階では四角形(結界)が4つあります。
この四角形は、四角形の画像をそのまま4つ貼り付けているわけではありません。
四角形の画像をそのまま貼り付けると、
結界のラインが動いているエフェクトを表すことはできません。
ポリゴンで四角形の辺を形作っています。

このサンプルでは八角形→四角形のみですが
本当の四重結界は、
ほとんど円(16角形くらい?)→八角形→六角形→四角形と変化していきます。


↑YukariSpell.pngの画像
	script_spell QuadrupleBorder
	{
		let playerX = GetPlayerX;//スペル発動時の自機のx座標
		let playerY = GetPlayerY;//スペル発動時の自機のy座標
		let current = GetCurrentScriptDirectory();
		let imgEffect = current~"YukariSpell.png";
		@Initialize
		{
			LoadGraphic(imgEffect);
			SetPlayerInvincibility(360);//無敵時間を設定
			
			//四重結界タスクを起動
			run();
		}
		@MainLoop
		{
			yield;
		}
		@Finalize
		{
			DeleteGraphic(imgEffect);
		}	
		task run()
		{
			ascent(i in 0..4)
			{
				//4つの結界を、ループでずらしながら作成
				border(i);
				loop(32){yield;}
			}
			loop(224){yield;}
			End();
		}	
		
		task border(let num)
		{
			//結界1個分のタスク
			//合計4つ起動されます
			let obj = Obj_Create(OBJ_SPELL);//スペルオブジェクトを作成
			ObjEffect_SetTexture(obj, imgEffect);//テクスチャを設定
			ObjEffect_SetRenderState(obj, ADD);//描画方法を設定
			ObjEffect_SetPrimitiveType(obj, PRIMITIVE_TRIANGLESTRIP);//プリミティブタイプを設定
			ObjEffect_CreateVertex(obj, 18);//頂点を18個つくる
			Obj_SetPosition(obj, playerX, playerY);//座標を設定	
			ObjEffect_SetAngle(obj, 0,0,45+(num)*22.5);//角度を設定
			
			//色設定
			let red =   [255,   0, 255, 255];
			let green = [255,   0,   0,   0];
			let blue =  [255, 255, 255,   0];
			let color= num;
		
			task vertex(let vrtx)
			{
				//頂点を制御するタスク
				//頂点の半分は内側の辺を表す
				let x = 0;//頂点のx座標
				let y = 0;//頂点のy座標
				let u = 32 * truncate(vrtx/2);
				let v = 16 * (vrtx%2);
				let angle = 45*truncate(vrtx/2);//八角形にするための角度設定
				let alpha=0;
				task alphaState
				{	//α値を変更するためのタスク
					alpha=0;
					loop(60){alpha+=5; yield;}
					loop(150){yield;}
					loop{alpha-=5; yield;}
				}
				alphaState;		
					
				task xy
				{	//xy座標を変更するためのタスク
					let radius = 128;
					x = radius * cos(angle);
					y = radius * sin(angle);
					
					//図形の内側と外側を分けるため頂点の半分は少し待機
					if(vrtx%2 == 0){loop(16){yield;}}
					
					loop(16)
					{	//最初は少しずつ小さくなる
						radius -= 128/16;
						x = radius * cos(angle);
						y = radius * sin(angle);
						yield;
					}
					if(vrtx%2 != 0){loop(16){yield;}}	
					if(vrtx%2 == 0){loop(32){yield;}}			
					loop(64)
					{	//結界が広がる
						if(vrtx%2 == 0)
						{	//内側
							radius += 224/64-0.75;
						}
						else
						{	//外側
							radius += 224/64;
						}
						x = radius * cos(angle);
						y = radius * sin(angle);
						yield;
					}
					
					if(vrtx%4==2 || vrtx%4==3)
					{	
						//最終的に四角形にするため、
						//八角形の内の半分を四角形の辺上の頂点にする
						while(!Obj_BeDeleted(obj))
						{
							let tx = (radius * cos(angle-45) + radius * cos(angle+45) )/2;
							let ty = (radius * sin(angle-45) + radius * sin(angle+45) )/2;
							x += (tx-x) / 8;
							y += (ty-y) / 8;
							yield;
						}
					}
				}
				xy;
				
				task uv
				{
					//UV値を制御するタスク
					loop(64){yield;}
					while(!Obj_BeDeleted(obj))
					{
						u-=2;
						yield;
					}
				}
				uv;
				
				while(!Obj_BeDeleted(obj))
				{
					//頂点の座標を設定
					ObjEffect_SetVertexXY(obj, vrtx, x, y);
					
					//頂点のUV値を設定
					ObjEffect_SetVertexUV(obj, vrtx, u, v);
					
					//頂点カラーを設定
					ObjEffect_SetVertexColor(obj, vrtx, alpha, red[color],green[color],blue[color]);	
					yield;
				}
			}
			
			ascent(i in 0..18)
			{	
				//18個の頂点のタスクを開始する
				vertex(i);
			}
	
			task intersection
			{	//あたり判定タスク
				let radius=0;
				loop(60){yield;}
				while(!Obj_BeDeleted(obj))
				{		
					//当たり判定を適当に広げていく
					radius+=4;
					if(radius>192){radius=192;}
					SetIntersectionCircle(playerX, playerY, radius, 2, true);//当たり判定を登録
					yield;
				}
			}
			intersection;
	
			while(!Obj_BeDeleted(obj))
			{
				yield;
			}
		}
	}