Counter-Strike 2

Counter-Strike 2

41 ratings
Vscript、VPhysics、弾幕についての基礎
By Harakiri
Introduction to vphysics, vscript and bullethell for CSGO. (Japanese)
   
Award
Favorite
Favorited
Unfavorite
はじめに
 このガイドではCSGO向けにvscript, vphysics, そして弾幕に関する説明を行う。1章、2章は弾幕をつくるために必要なので、目を通すことをおすすめする。また、本文と直接は関係ない話題は、ガイドの最後の方に補遺としてまとめてある。
 また、本文の中でほかの方のガイドなどを参照しているが、それらの筆者の方にあらかじめ感謝の意を示しておきたい。

[ 更新履歴 ]
2019/7 補遺の「実践的な弾幕」に追加
[1] vscript について
 Vscript を用いると、反復する命令や乱数などを手軽に扱うことができ便利である。この章の多くの内容はプログラミングに関連する話題であるが、筆者はプログラムには疎いため、vscript に関連した特殊な話題を中心に解説していく。
 また、次の List は本文中で多く引用するので、必ず見てほしい。
https://developer.valvesoftware.com/wiki/List_of_Counter-Strike:_Global_Offensive_Script_Functions#CScriptKeyValues
1-1 squirrel について
 CSGO の vscript はsquirrel というプログラミング言語を使っている。squirrel に関しては、下のサイトを参考にするといい。また、vscript はそれ以外にも特有の機能をもっている。

squirrel について
http://squirrelnyuumon.web.fc2.com/index.html

1-2 Vscript を使うための準備
 Script を書く際はメモ帳でもできるが、マイクロソフトの Visual studio code などを使うことを強くお勧めする。Squiirel か Lua のアドオンを入れるとよい。
1-3 Vscript の使い方
 この項目ではスクリプトをつくっていく前に、スクリプトの使用法についてまとめる。これに関しては Shimelar さんのガイドを参照。
https://steamcommunity.com/sharedfiles/filedetails/?id=795395868
 1つだけ補足すると、例えば「csgo/scripts/vscripts/harakiri」というフォルダにある「test.nut」というスクリプトを使うときは「harakiri/test.nut」とスクリプトファイルを指定する。
 また、この際に注意することとして引数(With parameter override with の項目)で string 型を指定する際の「"」は Hammer editor で vmf file を開けなくするので直接指定してはならない。例として、「random walk」という名前の関数を使いたいときに「With parameter override with」の項目を「random_walk("poipoi")」にする感じである。
 もし、vmf file を開けなくなったときは、vmf file をいったんメモ帳などで開いて、「"」を消すといい。これらに関しては下のページの Warning というところに書いてある。
https://developer.valvesoftware.com/wiki/Hammer_Object_Properties_Dialog#Keyvalues

1-4 変数の型について
 変数の型には Int、Float などがあるがそれらについてまとめる。Squirrel の変数については項目 1-1 のリンクを参考にしてほしい。String、Arrey、Table に関しては押さえておいてもらいたい。また下のリンクで、実際の例を見れるので確認しておいてほしい。
https://developer.valvesoftware.com/wiki/Squirrel

 さて、ここまで Squirrel についての話だったが、Vscript 特有の型もある。Vector と Script handle がそれらの例だが、Script handle は重要なので次の項目で説明する。
 ここでは、ひとます Vector に関して説明する。細かくは説明しないが次のコードと、上の List でVecotr とページ内検索してもらえばわかると思う。
function test2() { local vec1 = Vector(0,0,100); local i = vec1.x; local j = vec1.Length(); //product local vec2 = Vector(0,0,100); printl( vec1.Dot(vec2) ) }
ベクトルの大きさ、内積、外積もとれる。
1-5 Script handle について
 Vscript では Entity そのものを表す変数の型があり、それを Script handle という。例を見てみる。

thruster1 <- Entities.FindByName(null,"boss_thruster1"); train <- Entities.FindByClassname(null,"func_tracktrain"); function test() { local thruster2 = Entities.FindByName(null,"boss_thruster2"); local vec1 = train.GetOrigin(); }

それぞれグローバル変数と、ローカル変数で Entity を指定している。最後の行の GetOrigin はScript handle に対して、働く関数の一種として考えられる。他にも SetOrigin、SetAngles など色々あるので、上の List を参考にしてほしい。
 これらの重要な例として、self、caller、activator、player などがある。そのままの通りで、スクリプト動かしている Entity などが指定される。これらの Targetname も Script handle の一種と考えられる。上記の Shimelar さんのガイドでも説明してあるので、参考にすること。また、いくつか参考になるものを上げておく。
function test() { local vec1 = self.GetOrigin(); local vec2 = random_vector(vec1); self.SetOrigin(vec2); } function random_vector(vec) { return vec + Vector(RandomInt(0,100),RandomInt(0,100),RandomInt(0,100)) }
このコードでは vec2 は vec1 から少し離れたベクトルになる。
https://developer.valvesoftware.com/wiki/Targetname
1-6 サンプルコード
 ここで一旦、サンプルコードをいくつか見て、慣れてもらう。
function windpush() { local boss = Entities.FindByName( null, "boss_phys" ); local dis = activator.GetOrigin() - boss.GetOrigin(); activator.SetVelocity( Vector(dis.x*1400 / dis.Length2D(),dis.y*1400 / dis.Length2D(),500) ); }
最後の行は xy 平面上でのベクトルの大きさを1400にしている。

function angle2(i,j) { local s = i; local t = j; self.SetAngles(0,RandomInt(s - t,s + t),0); EntFireByHandle(self, "ForceSpawn", "" , 0.00, null, null); EntFireByHandle(self, "RunScriptCode", "angle2(" +s+ "," +t+ ")", 0.50, null, null); }
スクリプトの内容は深く考えなくていい。

function start() { local i = RandomInt(0,359); local vec = Vector( 5248 + 660*cos(i*PI/180), 10624 +660*sin(i*PI/180), -3012 ); Entities.FindByName(null,"nu_at3_ball1_temp").SetOrigin(vec); EntFire("nu_at3_ball1_temp", "Runscriptcode", "spawn(0,0)", 0.00); local r = RandomInt(500,700); EntFire("nu_at3_ball2_temp", "Runscriptcode", "spawn("+0+","+i+","+r+")", 0.00); }

function attack7_spell_2_small() { local spell7_small_temp = Entities.FindByName(null,"spell7_small_temp"); local start_angle = RandomInt(0,359); local auau = null; for(local i = 0; i < 12; i++) { auau = (i*30)+start_angle printl(auau) EntFireByHandle(spell7_small_temp,"Forcespawn","",0.02 * i,null,null); } }

EntFire("sound_boss_power","Playsound","",RandomInt(30,59).tofloat()/100);
これはスクリプトの一部だが、RandomInt のところは、0.30 ~ 0.59 秒内から 0.01 秒刻みの乱数を得るために使ってる。
1-7 Script scope について
 Entity がスクリプトを読み込むと、それぞれの Entity ごとに function や 変数などを読み込むが、それらを Scriptscope と呼ぶ。この考え方を説明するために次の例を考える。
 2つの Entity 、move1 と move2 があり、move1 のスクリプトを動かすときに move2 のスクリプトのグローバル変数 poipoi_vec を使いたいとしよう。次のコードを書けばいい。

function test() { local handle_move2 = Entities.FindByName(null,"move2"); local value = handle_move2.GetScriptScope().poipoi_vec; }
Script handle.GetScriptScope().xxxxxxxx で他の Entity のスクリプト内のグローバル変数を得られる。また、
spell6_temp.GetScriptScope().dir = Vector(0,180,0);
のように、他の Entity の Scriptscope をいじることもできる。
1-8 Hook について
 Hook とは特定の Input を受け取ったときに自動で行われる関数のこと。実例を見てもらった方が早い。上の List で Hook とページ内検索してもらうと、いくつか見つけられる。確認することを強く勧める。例をいくつか挙げておく。

function InputKill() { printl("help"); EntFireByHandle(activator,"Kill","",0.00,null,null); }
Kill される時に activator を Kill する。

function OnPostSpawn() { EntFireByHandle(self, "Open", "", 0.0, null, null); }
Spawn した際に、実行される。
point_template 内の func_movelinear などにつけると、Spawn した際にすぐに move_linear が動き出す。また、似たような機能に、logic_relay の Input Onspawn がある。
1-9 デバッグについて
 スクリプト書くと、うまくいかないことがよくある。その時の対処法をまとめる。これも例をとって考える。次のコードがうまく動かなかったとしよう。
function test() { local vec1 = Vector(0,90,0); self.SetAngles(vec1); }

 まずはデバッグをしやすくすために次の2つのコマンドをコンソールに打つ。意味は調べてほしい。
ent_messages_draw 1;developer 2
 また、コードを直した後は、mp_restartgame 1 をコンソールに打ち込むだけでスクリプトは更新される。
 次に、スクリプトの中身に間違いがあるのか、それとも実行するときに間違いがあるのか確認するために、上のコードを次のコードに置き換える。
function test() { printl("test") // local vec1 = Vector(0,90,0); // self.SetAngles(vec1); }
これでコンソールに test と出れば、スクリプトの中身に間違いがあることになる。その後は、一行ずつコメント ( // のこと) を外していき、SetAngles に問題があることがわかる。上の List で SetAngles を調べると、次のようにすればいいことがわかる。
function test() { self.SetAngles(0,90,0); }
結局は SetAngles を Vector で実行しようとしたことが間違いだったことがわかる。
[2] Vphysics について
 この項目では func_physbox などの Vphysics に関連した Entity の紹介をする。SourceEngine 全体の物理エンジンについては補遺で軽く触れる。

 もし、本格的に Vphysics を調べたい人がいれば、次のページを見るとよい。
https://developer.valvesoftware.com/wiki/VPhysics
2-1 func_physbox 関係
func_physbox は Valve Developer Community (VDC) を参照。ほとんどただの箱。
func_physbox_multiplayer は多人数用。
Flag の項目の「 Debris - Don't collide with the player or other debris 」は重要なので調べておくこと。

https://developer.valvesoftware.com/wiki/Func_physbox
2-2 Force
 physbox 等を動かすには Force 関係の Entity を用いる。特に、phys_thruster がよく用いられる。下のリンク参照。
 注意喚起をしておくと、 Flag の項目が一番重要。「Apply Force」をしっかり ON にしておかないと、力がかからないので注意すること。また Torque に関しては回転させる力のことで、物体は動かない。おそらく、角速度ベクトルで力を決めていると思う。

https://developer.valvesoftware.com/wiki/Category:Forces
2-3 Constraints
 Constrain 関係の Entity は物体の運動を制限する。例えば、phys_constraint は2つの物体を1つの物体として扱うようにする。例として、phys_slideconstraint は物体が直線状を動くように制限する。次の動画を見て、イメージを持ってほしい。また下の VDC のリンクのすべての Entity をみることをお勧めする。
https://www.youtube.com/watch?v=-mhPwKt5Wks
https://developer.valvesoftware.com/wiki/Category:Constraints

 話は変わるが、Constrain によってつながれているいくつかの Vphysics entity にたいして、phys_constraintsystem を使うとよい。そうしないと、Phys entity が振動してしまったりする。詳しくは三体問題などで調べるといい。
https://developer.valvesoftware.com/wiki/Physics_optimization
2-4 trigger_vphysics_motion
 この項目では trigger_vphysics_motion を説明する。これは CSGO 用の Hammer editor では標準だと使えない Entity のため、使えるように設定する。補遺 2-1 を参照。

 trigger_vphysics_motion は Vphysics に関する Entity が運動する際の、重力、空気抵抗、最大速度を決めるものである。似たような Entity として trigger_gravity があるが、Flag の項目に「Physics Objects」などがあるにもかかわらず、プレイヤー ( cliant ) にしか使えない。
 trigger_vphysics_motion の重要な項目として、Additional air density for drag、Scale gravity of objects in the field などがあるので確認するように。また Input の「starttouch」は CSGO では動いてないと思われるので、trigger_multiple を使うように。
2-5 func_clip_vphysics
 Phys entity のみにたいしてのみ、衝突する壁 ( solid ) を作るには func_clip_vphysics を使う。普通の tool texture にはそのようなものはない。
[3] 弾幕について
 さて、この章では本題である弾幕についてまとめる。技巧的な点は実際に弾幕を作ってもらった方がよくわかると思うため、ここでは弾幕に関する根本的な技術をまとめたいと思う。
3-1 Entity の基礎知識
 さて、本題に入る前に、必要な知識をまとめたいと思う。

(1) Addoutput、Smartedit
 Entity の Output 処理にAddOutput とは Entity の Parameter ( origin など ) を変えることができる。Entity の Parameter にどうようなものがあるかは、Hammer ediotr の Smartedit を外すことで確認できる。一例として、「AddOutput "origin 0 0 200"」などがある。
https://developer.valvesoftware.com/wiki/Hammer_Object_Properties_Dialog#SmartEdit
https://developer.valvesoftware.com/wiki/AddOutput

実例については IM THE NEW GUY さんの次のガイドを見てほしい。
https://gamebanana.com/tuts/11820

 さて、ここでの重要な例として、いくつかの Entity の Parameter はこの方法では、変更できないということだ。その重要な例が func_movelinear の movedir ( 移動する方向 ) である。このことを覚えてほしい。

(2) point_template
 弾幕をつくる上で、弾のテンプレートを作って、それを何回も使うというのが、基本的な方法である。テンプレートを創るための Entity がこの Entity である。
 この Entity の重要なこととして、 Flag の「 Preserve entity names (Don't do name fixup) 」である。これは template 内の Entity の名前を変更するかどうかについてのものであるが、例をあげる。例えば、move1 という名前の Entity が template 内にあったとして、名前を変更するように設定すると、move1&0034 のように「&0034」といった4桁の数字が足されたものが新たな名前となる。このため、これらの Entity に命令を出そうとするときには、これを考えないといけない。また、template 内での Entity の Output などは、自動的にこれらの Targetname の変更に合うように修正を行う。
https://developer.valvesoftware.com/wiki/Point_template

(3) func_movelinear
 この Entity は弾幕をつくる上で、重要である。しかし、扱いが面倒である。移動する方向を示す Parameter の movedir が上記のように AddOutput で変更できない上に、Vscript の 「__KeyValueFromVector」や「__KeyValueFromString」などからも変更できないためである。このことは最も重要なため覚えておいてほしい。
https://developer.valvesoftware.com/wiki/Func_movelinear

(4) func_tracktrain
 これも移動関係の Entity である。2つのことについて触れる。
 1つ目は Parent した際の train の移動についてである。Train を Parent 付けして動かすには Path_track すべてにも Parent 付けするのが必要である。下のページを参照。
https://developer.valvesoftware.com/wiki/Path_track
 2つ目にクラッシュの原因になることの多い Entity ということである。Path_track の next_target がうまく設定されていないだけですぐにクラッシュする。扱いには気を付けてほしい。
3-2 弾幕についての全般
 弾幕は3つの部分からできている。移動関係、弾幕の見た目、Hitbox 関係、の3つだ。

(1) 移動関係
 弾幕が移動する際に使う Entity は func_movelinear、func_tracktrain、func_physbox の3つだ。ここで、注意することは、ゲーム内で一度にだせる Entity の量が 2048 個であり、立体的に弾幕をつくると、その制限を超えることがよくあるということだ。なので、これから紹介する方法は可能な限り使う Entity を少なくしてある。例えば、2つ目の func_tracktrain の方法は Logic_relay の Onspan を使えばできるが、これを避けるためにスクリプトを用いている。
 もし、仮に Script を使わないでつくるとすると、もっとも単純な弾幕でさえ、track_train 1つと path_track 2個 が必要になるが、下の方法を使うと、1つでできる。
https://developer.valvesoftware.com/wiki/Entity_limit

 func_movelinear は移動関係の Entity が1つで済むためこれを基本に作ることになる。欠点として直線状しか移動できないことがある。もちろん、func_rotaing などと組み合わせて、らせん状に動かせるが、直線が基本となる。

 func_tracktain は最低でも Entity は3つ必要になる。しかし、折れ線上などを好きなように移動できる。そのため、弾幕が Spawn する際にどのようなコースをたどるかわかるならば、使うといい。

 func_physbox は trigger_vphysics_motion で無重力にして使う。physbox の長所として、自由に動かせることがある。例えば、弾が球内で球面に何度も反射するといったように、事前に経路の計算を行うのが難しいときに使う。処理は少し重くなるので、弾は 200 ~300 個以内になると思う。その都度、試してほしい。

(2) 弾幕の見た目
 info_particle_system、env_spritetrail、もしくは上の移動関係の Entity の Texture を変える。Texture に関しては $selfillum、$transparent などが役に立つと思う。

(3) Hitbox 関係
 trigger_hurt もしくは trigger_multiple を用いる。次のスクリプトは、trigger_multiple で使い、当たった時に東方のピチュ音をだし、なおかつ被弾後の無敵判定もある。数回被弾後は tp_origin に飛ばされる。なお、player の Targetname を何回当たったか記録するために使っているが、それを回避するためには、補遺 1-1 の Scriptscope を用いるとよい。

//========================== //by harakiri.allow anyone to use.poi8 //deal with bullet hit and emit hit sound //laser0 > laser1(invincible) > laser2 >....> laser5 = dead //========================== tp_origin <- Vector(0,1536,150); HIT_SOUND1 <- "harakiri2/spellcard.wav"; HIT_SOUND2 <- "harakiri2/pichun.wav"; function Precache() { self.PrecacheScriptSound( HIT_SOUND1 ); self.PrecacheScriptSound( HIT_SOUND2 ); } function tp() { if(activator.GetName() == "laser0") { activator.EmitSound(HIT_SOUND1); activator.__KeyValueFromString("targetname","laser1"); EntFireByHandle(activator, "AddOutput", "targetname laser2", 5.00, null, null); } else if(activator.GetName() == "laser2") { activator.EmitSound(HIT_SOUND1); activator.__KeyValueFromString("targetname","laser3"); EntFireByHandle(activator, "AddOutput", "targetname laser4", 5.00, null, null); } else if(activator.GetName() == "laser4") { activator.EmitSound(HIT_SOUND2); activator.__KeyValueFromString("targetname","laser5"); activator.SetOrigin(tp_origin); } else {} }
3-3 func_movelinear について
 前述のように、func_movelinear は point_template から Spawn した後では移動する方向を変えられない。そのため、Spawn する前に movedir を変える。これが、基本的な考えとなる。point_template の Vscipt hook である PreSpawnInstance を用いる。次のコードを見てもらいたい。

dir <- null; function spawn(i) { self.SetAngles(0,i,0); dir = Vector(0,i,0); EntFireByHandle(self, "Forcespawn", "", 0.00, null, null); } function PreSpawnInstance(func_movelinear,poipoi_laser_move1) { local keyvalues = { movedir = dir } return keyvalues }
上の Vscript の List を見てもらえばわかるが、PreSpawnInstance は template が Forcespawn を受けた時に実行され、Entity が Spawn する前にパラメータを変更する。Point_template が「Runscriptcode、"spawn(30)"」などど Input を受けると、xy 平面上を30度の直線状に移動する弾ができる。実際に試してほしい。ここで注意することは、Spawn する際にグローバル変数の dir が目的のものでないといけないということだ。これは弾幕を自動化する際に考えなければならない。これに関しては補遺 3-4 を参照。
 また、立体的に弾幕をつくる際の注意として、movedir はオイラー角を使っており、その Pitch で z 方向の角度をとっているが、その基準となるのは極座標の z 軸ではなく、xy 平面だということだ。

3-4 func_tracktrain について
 func_tracktrain では、Vscript hook の PostSpawn を使う。logic_relay の Onspawn でも同じことはできるが、なるべく使う Entity を減らすために Hook を用いる。

 PostSpawn は Entity が Spawn した後に、実行される。重要な点として、Spawn 後の Entity の Scripthandle が得られるということがある。弾幕をつくる際には、Spawn した Entity が相互に干渉しないように Flag の Preserve Entity Name を使わないが、そのために、Spawn した Entity の名前が変わってしまい、その Entity を指定しにくくなる。これを一気に解消することができる。

例を見てみよう。次のコードを実際に試してほしい。
function PreSpawnInstance(none,none) function PostSpawn(tbl) { foreach( name, handle in tbl ) { printl( name + ": " + handle ) } }
 まず、はじめにこの Hook を使うには、Prespawninstance が必要ということだ。上のように、ただ置いておくだけでいい。
 次に、tbl について説明すると、この Table 型の変数の名前は何でもよいが、ここには point_template 内の Entity の handle が Spawn する前の Targetname で Label 付けされた Tabel が出てくる。詳しくは変数型 Table と foreach を項目 1-1 で各自調べてほしい。

 実例をあげる。
vec1 <- null; vec2 <- null; //i = angle on xy plane. t = paramether of angle from x axis. function spawn(i,t) { local j = 15*sin(t*2*PI/180) +65; local r1 = null; r1 = 240*tan(j*PI/180); vec1 = Vector(r1*cos(i*PI/180),r1*sin(i*PI/180),0); vec2 = Vector(1024*cos(i*PI/180),1024*sin(i*PI/180), (1024/tan(j*PI/180))); EntFireByHandle(self, "Forcespawn", "", 0.00, null, null); } //prespawninstance is needed to use postspawn function PreSpawnInstance(none,none) function PostSpawn(tbl) { foreach( name, handle in tbl ) { if(name == "spell1_ball1_path2") { handle.SetOrigin(vec1); } else if(name == "spell1_ball1_path3") { handle.SetOrigin(vec2); } else{} } }
r1 は vec1、vec2 を計算するために使っており、座標に関しては詳しく考えなくてよい。すると、2つ目の path_track が vec1 に、3つ目の path_track が vec2 に移される。
3-5 func_physbox について
 Physbox は応用例が多岐にわたるため、trigger_vphysics_motion に関連した話題について述べる。

(1) 「Additional air density for drag」を変えることで、空気抵抗を変えられるが、これによって空気中で弾丸の速度が一定になるようなことを再現できる。
(2) 「Max velocity in field」で弾丸を疑似的に一定の速度にすることができる。
[4] 最後に
 この項目では、弾幕についてのいくつかの紹介と個人的な感想を書き加えたいと思う。

[ 弾幕について ]
 はじめに、補遺に関してはすべて読むことをお勧めする。というのも、補遺を付けたのは本文の内容になるべく一貫性を持たせたかったためである。
 次に、具体的な弾幕を知りたい人は bh_flan_a1、bh_yuudati_a1 がいい例になると思う。最初に、bh_yuudati_a1 の spell1~5 を見てもらうとよい。これらは、癖のない弾幕のため分かりやすいとは思う。次に、bh_yuudati_a1 の nu_at10~15 を見てもらうとよい。最後に、bh_flan_a1 であるが、bh_flan_a1 は攻撃関係をすべてスクリプト ( boss_attack_relay.nut、boss_attack_relay2.nut ) でやっていることに気を付けてもらうと、分かるとは思う。ただ、技巧的な面が大きいので、無理にわかろうとしなくてもいいと思う。

[ 感想 ]
 とりとめなく書いてゆくことを、許していただきたい。
 まず、はじめにこのガイドは弾幕をつくる際の基本的な考え方について説明しただけであって、実際に弾幕をつくっていくには不十分であると思う。弾幕をつくることには、技巧的な面や経験による面もあるので、本格的な弾幕をつくるにはいくつかの練習が必要になってくるだろう。頑張ってほしい。
 次に、もしこのガイドがあなたのマップに役に立ったのであれば、「Bullet hell concept by Harakiri 」といった言葉をマップのどこに入れてもらえるとうれしい。
 弾幕という技術の種はまかれた。この技術が誰かの手によって収穫されること願って、このガイドを区切りたいと思う。
補遺 1-1 Scriptscope の発展的な内容について
 Hammer editor でマップを作る際に Script を設定していない Entity は Scriptscope を持たないと考えられるが、有効にする方法がある。次のスクリプトを見てほしい。
function test() { activator.ValidateScriptScope() activator.GetScriptScope().poipoi <- 50 }
3行目の ValidateScriptScope で activator の Scriptscope を有効にして、2行目で poipoi という変数に 50 という値を入れている。これの応用例として、例えば各プレイヤーの Scriptscope を有効にして、何か値を保存させるということができる。

 話はそれるが、現在の Zombie Escape マップでは、func_brush、phys_box を用いて、今遊んでいるレベル( Stage) を保存している。このシステムは多くの Entity を用いる。そこで、代わりとなるのが func_brush の Sciprtscope に今のレベルを保存する方法である。func_brush はラウンドが変わっても、影響を受けないので、Scriptscope に値を保存する方法が使える。これを用いると、レベル関係の十数個の Entity を1つで実現できる。
補遺 1-2 EntFire などについて
 EntFire はよく使われるが、似たようなものに EntFireByHandle がある。
違いは EntFire は Entity の名前 ( targetname ) を用いるのに対して、EntFireByHandle は Scripthandle を用いること。
EntFire("move1","Kill","",0.00); EntFireByHandle(self,"Kill","",0.00,null,null);

 また、これから書くことは予想であるが、EntFire で命令を 0.00 秒後に出したとして、それが実行されるのは、今実行している Script が終わってからになると思う。この考え方は EntFire が I/O event をつくるだけ、ということから裏付けられると思う。例として次のスクリプトが上げられるが、これは3章を読んだ後に見た方がいいと思う。
 もし、上記の通りであれば、move19 は Vector(0,90,0) の方向に動き、そうでなければ、Vector(0,0,0) の方向に動く ( Vector(0,0,0) は元々の Entity の方向 ) 。
dir <- null; function test() { EntFireByHandle(self,"Forcespawn","",0,null,null); dir = Vector(0,90,0); } function PreSpawnInstance(func_movelinear,move19) { local keyvalues = { movedir = dir } return keyvalues }
補遺 2-1 FGD について
(1) SourceEngine について
 CSGO は SourceEngine を元にしているゲームである。SourceEngine は色々な Entity を使うことができるが、その中には CSGO には対応していない Entity もある。そういった Entity は Hammer Editor で選べないようになっているが、後述の FGD file を追加することで使えるようになる。ただし、Valve 側で想定されたものでないため、問題を起こす可能性もある。

(2) Hammer editor、FGD file について
 普通のマップの vfm file をメモ帳などで開いてもらうと、Entity などの情報が書かれていることがわかると思う。これを分かりやすく表示するのが Hammer editor である。そのため、Hammer editor は vmf を書くためのツールともいえる。(もちろん、コンパイル機能もあってその通りではないが)
 さて、FGD file であるがこれは Hammer editor にとって、Entity を設定するためのマニュアルのようなものである。このファイルは「common\Counter-Strike Global Offensive\bin」フォルダにはいっているので「xxxx.fgd」を見てもらうか、次のリンクを見て、どのようなものか見てほしい。
https://developer.valvesoftware.com/wiki/Half_Life_2.fgd

 次に、この FGD file を Hammer editor に追加する方法をまとめておく。これに関しては次の動画を見てほしい。3:46 から説明が始まる。
https://youtu.be/3CIWqRkLQ3g?t=231

 最後に、追加する FGD file であるが、上記の Half-life2 の FGD file をそのまま追加しようとすると、Hammer がフリーズすることがあるので、私の個人的な FGD file を上げておく。これは、trigger_vphysics_motion、player_speedmod、chicken の3つの FGD file である。
https://gamebanana.com/scripts/10393
補遺 2-2 Vphysics と Qphysycs
 この項目は次のことに動機づけられている。もし、phys_box に Vscript の SetVelocity を使えれば、弾幕にたいして役に立つのではないかといったものだ。特に、曲面に対しての反射を考えた時に非常に応用が利くと思われる。
 結論から言えばできないが、その理由を説明する。

[ Qphysics について ]
 Player や NPC にたいして使われる物理エンジン。Vphysics とはまた違うもの。CSGO では NPC 関係の Entity はほとんど使えないが、chicken は動く。

 さて、Vscript の Setvelocity は色々と試してみたところ、Qphysics で動くものにしか働かないようである。これを用いると、高速で飛ばされる chicken がつくれる。

https://developer.valvesoftware.com/wiki/Vphysics_%26_Qphysics
補遺 3-1 3D 弾幕について
 ここでは、弾幕をつくる際の参考になるように2019年3月現在における、3D 弾幕の進展についてまとめておこうと思う。
 3D 弾幕はゲームよりも動画の方が多い。特に Blennder や MMD で作られたものが多い。検索する際には「3D弾幕」、「3D東方」、「3D bullet hell」などで調べるといい。海外でもたくさん作られてる。それでは、紹介をしていく。

[ 動画 ]
minusT さん:弾幕だけでなく作品としてもすごい。必ず見るべき。Blennder で作ってる。
https://youtu.be/57Zwrx9a9Lo

Virtlux さん:壮大な弾幕。すごい。Blennder で作ってる。
https://youtu.be/XKh29JkI4YM?list=PLuxCCB0r3m4oTJ6WRqRIVNA-AXRHbELp4

[ ゲーム ]
 3D 弾幕はゲーム関係では見つからなかった。特に Unity などですでに作ってる人がいるかと思っていたが、探せなかった。ただし、2D 弾幕ならいくらか見つかった。

東方、東方弾幕風:最近だと東方真珠島という二次創作がすごかった。

Rabi-Ribi:2D の縦アクションと弾幕を混ぜたゲーム。次の動画はネタバレなので注意。
https://www.youtube.com/watch?v=LZ4XGJdnjo0

[ 感想 ]
 個人的な感想を書くと、もし弾幕だけを作りたいならば、Blennder をおすすめする。つくれる弾の数がゲームなんかと比べ物にならないため。
 もし、ゲームとして作る場合には、Unity、UnrealEngine、そして我らがポンコツ SourceEngine ( Hammer editor ) があるが、個人的には SourceEngine をおすすめする。というのも、Unity などは自由すぎて逆に手が出しにくい印象があるため。SourceEngine は元々のゲームの土台が決まっているため、ちょっと弾幕をつくる程度ならこっちの方が便利だと思う。ただ、Unity は 3D 関係の機能が豊富なため、甲乙つけがたい。
補遺 3-2 弾幕マップの弾幕以外のこと
 この項目では、UI や演出などについてまとめておく。

[ スペルカードの名前 ]
 東方原作にように、画面左上に技の名前を出したいときは env_screenoverlay を使うとよい。Texture を画面に表示させるのだが、以下のことに気を付けるとよい。
  • Texture は正方形しか作れいないので、1024 x 1024 でつくって、文字をあらかじめ、モニターのサイズに合わせて拡大縮小するとよい。
  • 文字の背景は透明にすること。

[ Bind ]
 Firtstperseon と Thirdperson に切り替えらるバインドや、二段階目のジャンプなどのスクリプトなどをつくって、何かのキーにあてるとよい。bh_flan の Logic_relay "jump" を参照。

[ その他 ]
  • point_servercommand でのコマンドで sv_airaccelerate 15 などで空気抵抗のようなものを変えられる。
  • 私はできなかったが、Hitbox を変えられたら変えてみるとよい。

補遺 3-3 Multiplayer で気を付けること
 ZombieEscape などのマップに弾幕を使う際は次のことに気を付けるといい。
  • Entity の数が2048個までなので、弾幕を打つ際には余計な Entity を消すとよい。特に Spawn 地点など。
  • 処理を軽くするようにするとよい。大量の弾幕を出す際には、少しずつ Spawn させて、一気に動かすなど。
補遺 3-4 実践的な弾幕(弾幕の自動化)
 球面上に func_movelinear の弾幕を出すことを考える。2つのやり方について説明する。1つ目はスクリプトを何度も実行する方法で、2つ目は env_entity_maker を使うやり方である。
 どちらの方法も次のことに動機づけられている。func_movelinear を Spnwa させる際にはグローバル変数を用いて方向を決めるが、point_template が ForceSpawn を実行する際にその変数が正しくないといけない。これは1つ目で詳しく見る。


[ 1つ目の前に ]
 これは実際に鯖で(64人)でうまく動いたやつで、下2つのより軽いと思う。
SpawnEntityAtLocationを使うのとfor文を使ってる。
//template dir <- null; function PreSpawnInstance(func_movelinear,boss_attack_move) { local keyvalues = { movedir = dir } return keyvalues } //============================================== //entity_maker temp <- Entities.FindByName(null,"boss_attack_temp"); boss <- Entities.FindByName(null,"boss_boss_hitbox"); //two angles should be same const angle_z = 30; const angle_xy = 30; const total_angle_xy_change = 15; function spawn() { local total_angle_xy = 0; spawn2(0,0); spawn2(180,0); //spawn2(z,xy) for(local j = 0; j < 180; j = j+angle_z) { for(local i = (0+total_angle_xy); i < (360+total_angle_xy); i = i+angle_xy) { spawn2(j,i); } total_angle_xy = total_angle_xy + total_angle_xy_change; } } function spawn2(s,t) { local srad = s*PI/180; local trad = t*PI/180; local vec1 = Vector(200*sin(srad)*cos(trad),200*sin(srad)*sin(trad),200*cos(srad)) + boss.GetOrigin(); local vec2 = Vector(s-90,t,0); temp.GetScriptScope().dir = vec2; self.SpawnEntityAtLocation(vec1,vec2); }
[ 1つ目 ]
 まずは、使い方について説明する。下のスクリプトを point_template につけて、GetScriptscope を用いて、必要な値を変えた後、Spawn(0,0) で実行させる。極座標を用いていて、「j」は z 軸からの角度、「i」は xy 平面に平行な面での角度を表している。「rotatinal_angle」は「j」が一つずれることにずれる初期角のようなものを表す。うまく設定すると。弾幕が互い違いになる。

//========================== //by harakiri.allow anyone to use.poi8 //change func_movelinears movedir by prespawninstance //change movedir before spawning. //before run this script, set xy_angle ... by using GetScriptscope. //========================== dir <- null; boss_origin <-Vector(0,0,200); xy_angle <- 9; z_angle <- 9; rotatinal_angle <- 0; rotatinal_angle_total <- 0; function spawn(i,j) { local srad = i*PI/180; local trad = j*PI/180; local vec1 = Vector(100*sin(trad)*cos(srad),100*sin(trad)*sin(srad),100*cos(trad)) dir = Vector(j-90,i,0); self.SetOrigin(boss_origin+vec1); self.SetAngles(j-90,i,0); EntFireByHandle(self, "Forcespawn", "", 0.00, null, null); if(j == 0) { rotatinal_angle_total = 0; EntFireByHandle(self, "RunScriptcode", "spawn(0,"+z_angle+")", 0.00, null, null); } else if(j == 180){} else { if(i<360 + rotatinal_angle_total) { EntFireByHandle(self, "RunScriptcode", "spawn("+(i + xy_angle)+","+j+")", 0.00, null, null); } else { rotatinal_angle_total = rotatinal_angle_total + rotatinal_angle; EntFireByHandle(self, "RunScriptcode", "spawn("+rotatinal_angle_total+","+(j+z_angle)+")", 0.00, null, null); } } } function PreSpawnInstance(func_movelinear,bullet_red_sphere_move) { local keyvalues = { movedir = dir } return keyvalues }

一回スクリプトを動かすごとに、I/O event には下のようなものが命令されることになる。これをくりかえして、球面上に弾幕をつくる。
EntFireByHandle(self, "Forcespawn", "", 0.00, null, null); EntFireByHandle(self, "RunScriptcode", "spawn("+(i + xy_angle)+","+j+")", 0.00, null, null);
さて、プログラマーの人はこれを見て、繰り返し文を使えばいいのに、と思うと思うが、これには理由がある。もし、繰り返し分を使って、スクリプトを書くと、実行する順番として、スクリプトを実行した後に、数十個の ForceSpawn が実行されると思う。その際に、スクリプトに保存されている「dir」は変わらないため、同じ個所にすべての弾幕が出てしまう。つまるところ、Entity が Forcespawn する際に「dir」 という変数の値が正しくないといけないのである。これに関しては補遺 1-2 に書いてある。上の2つ命令は I/O event 上では 0.00秒後に行われるが、ForceSpawn の方が先に実行されるので、「dir」の値が正しいものになっている。
 この方法の利点として、大量の弾幕を少しずつ Spawn させるのが簡単であることが上げられる。上の Runscriptcode を 0.05 秒後などにすることで処理が軽くなる。 
 欠点として、トラブルがあった際に、そこから後に Spawn する予定の弾幕すべてが Spawn されないということがある。

[ 2つ目 ]
 この方法は env_entity_maker の関数 SpawnEntityAtLocation を使う。これは EntFire と違って、スクリプト内のその行を実行したときに実行されるので、繰り返し文が使える。
 前半を env_entity_maker につけて、後半を point_template につける。
//============ //set this to maker dir <- null; boss_origin <- Vector(0,0,200); xy_angle <- 12; z_angle <- 12; temp <-Entities.FindByName(null,"bullet_red_sphere_temp"); function spawn() { local i = 0; local j = 0; while(j != 180) { local srad = i*PI/180; local trad = j*PI/180; local vec1 = Vector(100*sin(trad)*cos(srad),100*sin(trad)*sin(srad),100*cos(trad)); local vec2 = Vector(j-90,i,0); temp.GetScriptScope().dir = vec2; self.SpawnEntityAtLocation(vec1+boss_origin,vec2); if(j == 0) { j = j + z_angle } else { if(i<360) { i = i + xy_angle } else { i = 0 j = j + z_angle } } } } //================ //set following function to template dir <- null; function PreSpawnInstance(func_movelinear,bullet_red_sphere_move) { local keyvalues = { movedir = dir } return keyvalues }

 このスクリプトの肝心なところは次の部分。
temp.GetScriptScope().dir = vec2; self.SpawnEntityAtLocation(vec1+boss_origin,vec2);
 この方法の利点は、何度も Runscriptcode をしないのでミスが起きにくいということ(たぶん)。欠点として、上の方法のように処理を少しずつ行うことができないこと。ただ、これに関しては Squireel のコルーチンという方法でできるかもしれない。
補遺 3-5 point_template の Scripthandle について
 この項目では point_template の Flag の Preserve entity name を使用しないとき、つまりリネームするときの Spawn 後の Entity の Handle を得ることについてまとめておく。

  • 本文のように PostSpawn を使う。
  • Spawn した際に得たい Entity で次の関数を実行する。すると、thruster に Handle が保存される。
    thruster <- null; function SetThruster(){thruster = caller;}
  • 「slice」をつかう。例として、Spawn 後の Entity の名前は「move&0056」となっている。同時に Spawn させた Entity、例えば「rotate&0056」 が move の Handle を得たいとすれば、次のコードを書けばいい。「slice」について各自で調べてほしい。ただ、この方法には注意があって、いくつかの Entity はちゃんと名前が変えられない(リネームされない)ので気を付けてほしい。
    move <-Entities.FindByName( null , "move&" + self.GetName().slice(-4) );
補遺 3-6 PreSpawnInstance のバグ
 確証はないので記録だけ残しておく。
 Prespawninstance の引数のところで、どの Entity のパラメータを変えるか指定するがこれは働いていないっぽい。Template が func_moveliner "move" と func_rotating "rotate" を持っているとして、下のコードを実行すると、どちらも同じ座標に Spawn する。
function PreSpawnInstance(func_rotating,rotate) { local keyvalues = { origin = "-128 1520 678" } return keyvalues }
もしかしたら、この Hook 使い方が違うかもしれない。
補遺 3-7 数学的な知識
 立体的な弾幕をつくるうえで、曲面などの知識があるといいので、軽くまとめておく。細かい話はなしでお願いしたい。

[ リサージュ図形 ]
 普通の曲面はパラメータを2つ用いて表せる。球面を極座標表示して、2つのパラメータを1つのパラメータでまとめる。次のように座標を指定すると、半径200の球面上に画像のような図ができる。。
local s = i*(13);//change value on () local t = i*(11);//same as above local srad = s*PI/180; local trad = t*PI/180; local vec = Vector(200*sin(srad)*cos(trad),200*sin(srad)*sin(trad),200*cos(srad));



コードの1,2行目のパラメータをまとめるところで、x + (1/2) sinx のような大域的に増加する関数をつくってもおもしろい模様になると思う。詳しくは以下のページを参照。
http://d.hatena.ne.jp/Hyperion64/20110801/1312212654
https://demonstrations.wolfram.com/LissajousPatternsOnASphereSurface/


 また、よく知られているリサージュ図形を球面上に書くこともできる。極座標の角度を振動させればいいだけ。

上の画像は次のページより
https://www.researchgate.net/publication/261952571_Superintegrable_Lissajous_systems_on_the_sphere

[ 線織面 ]
 簡単な説明だと、直線を連続的に動かしたような曲面。
下のように2組の束でつくられるのは、一葉双曲面と双曲放物面のみらしい。下のページの「はじめに」の1段落参照。
http://reposit.lib.kumamoto-u.ac.jp/bitstream/2298/31662/1/KKK0063_347-356.pdf

[ その他 ]
いくつか参考になるリンクを張っておく。
星形多面体
https://ja.wikipedia.org/wiki/%E6%98%9F%E5%9E%8B%E5%A4%9A%E9%9D%A2%E4%BD%93
トーラス(ドーナツ型)
https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%BC%E3%83%A9%E3%82%B9
パスカルの蝸牛形(z軸で回転させる)
https://ja.wikipedia.org/wiki/%E3%83%91%E3%82%B9%E3%82%AB%E3%83%AB%E3%81%AE%E8%9D%B8%E7%89%9B%E5%BD%A2
バラ曲線
https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%A9%E6%9B%B2%E7%B7%9A
特別な曲面
http://www.f.waseda.jp/takezawa/math/geometry/surface.htm
下の画像のリンク
https://math.stackexchange.com/questions/1209858/what-equation-will-create-a-3d-rose-curve

31 Comments
♥ KiriLite ♥ Mar 22, 2019 @ 11:23am 
Напиши в моем профиле "+rep good killer"
А я в вашем напишу "+rep"
NRHST Mar 22, 2019 @ 10:35am 
REP 4 REP INSTANTLY!
ENG: Copy&Paste one of these OR write whatever you want and I will rep you back 100%
RU: Пишете что-то из списка ниже , и я вам пишу что захотите :))

+rep best skinseller from all that i know
+rep god of gambling
+rep awp god
+rep fucking cheater :D
best of the best
gg add me
can you sign me??
hy,n i want to trade with you
+rep not scammer
+rep rly good
please add me
Added because I have questions, no begging)
add me , i'm about buy skins
I LOVE YOU <3
NRHST Mar 22, 2019 @ 10:35am 
REP 4 REP INSTANTLY!
ENG: Copy&Paste one of these OR write whatever you want and I will rep you back 100%
RU: Пишете что-то из списка ниже , и я вам пишу что захотите :))

+rep best skinseller from all that i know
+rep god of gambling
+rep awp god
+rep fucking cheater :D
best of the best
gg add me
can you sign me??
hy,n i want to trade with you
+rep not scammer
+rep rly good
please add me
Added because I have questions, no begging)
add me , i'm about buy skins
I LOVE YOU <3
oozin for a bruzin Mar 22, 2019 @ 10:33am 
this looks like an insane guide but its all in japanese lol
whoami? Mar 22, 2019 @ 6:42am 
ENG: Copy&Paste one of these OR write whatever you want and I will rep you back 100%
RU: пишете что-то из списка ниже, и вам напишу что захотите :)

+rep Good player 💜 [/h1]
+rep Amazing Tactics 👌
+rep Epic Clutch ✌
+rep Clutchmeister 👍
+rep Killing Machine _
+rep 1Tap Only 👊
+rep Insane Skills 👌
whoami? Mar 22, 2019 @ 6:42am 
ENG: Copy&Paste one of these OR write whatever you want and I will rep you back 100%
RU: пишете что-то из списка ниже, и вам напишу что захотите :)

+rep Good player 💜 [/h1]
+rep Amazing Tactics 👌
+rep Epic Clutch ✌
+rep Clutchmeister 👍
+rep Killing Machine _
+rep 1Tap Only 👊
+rep Insane Skills 👌
† Magenda Mar 22, 2019 @ 5:42am 
RUS: Выберите что то одно из этого списка и напишите в моём профиле, отвечу тем же!
ENG: Choose the one that's on the list and write in my profile, I will answer the same! <3
PL: Wybierz jeden z komentarzy ponizej i zamiesc w moim profilu, odwdziecze sie tym samym !
TR:Bir tanesini seç ve profilime yaz , aynısını sana yazacağım !
+rep nice skill
+rep machine
+rep good player
+rep nice aim
+rep carry me
+rep you are the best
+rep awp god
+rep one tap god
+rep clutchmeister
E̶n̶c̶h̶a̶n̶t̶i̶x̶💔 Mar 21, 2019 @ 1:17pm 
ENG: Copy&Paste one of these OR write whatever you want and I will rep you back 100%
RU: пишете что-то из списка ниже, и вам напишу что захотите :)

+rep Good player 💜 [/h1]
+rep Amazing Tactics 👌
+rep Epic Clutch ✌
+rep Clutchmeister 👍
+rep Killing Machine _
+rep 1Tap Only 👊
+rep Insane Skills 👌
Stakes Mar 21, 2019 @ 5:51am 
:steamhappy:Hi. I'm make animations for the steam profile to the order. Read more in my profile.:steamhappy:
:steamhappy:Привет. Я делаю анимации для профиля на заказ. Подробнее в моем профиле:steamhappy: