Counter-Strike 2

Counter-Strike 2

Not enough ratings
サルでも分かるvscriptsの作り方(入門編)
By Shimelar
前回の記事 ← 今回の記事 → 次回の記事
前回のガイドでは全く初心者向けではなかったので、この記事ではプログラミングについてさっぱりの人でもわかるようにvscriptsの使い方を教え、Harakiriさんのガイドなど、他の人が作成したガイドをわかりやすく理解するために解説する。
なお、前回の記事の「vscriptsの使い方」のところまで読み終えたことを前提として話を進める。
※このガイド内での鉤括弧「」は、全て箇所を囲む括弧として使用しています。記号としての意味での使用はしていません。
   
Award
Favorite
Favorited
Unfavorite
前回の振り返り
前回のガイドを見てない、または見たけど忘れた人のためにこのセクションを用意する。
簡単に説明すると、
  1. csgo\scripts\vscriptsの階層のフォルダにnutファイルを作成
  2. hammerでlogic_scriptsを作成し、nutファイルを指定
  3. nutファイルに以下の文を記述(理解しなくても可)
    function pressed(){ activator.SetOrigin(Vector(-300,500,80)); }
  4. func_buttonを生成し、押されたときにnutファイルの「pressed()」関数を呼び出すように
  • Targetnameに「self」「caller」「activator」がある
ここまでが今回のガイドで予習しておいてほしいところだ。
vscriptsのコードを書く前に
vscriptsを使う前に色々準備しておきたいことがあるので、それを順に説明する。

エディターについて
エディターとは、テキストファイルを編集できるソフトウェア、いわばメモ帳である。このガイドではテキストエディターの一例としてAtom[atom.io]を使用する。理由は、
  1. 広く知られたプログラミング特化のエディターだから
  2. vscriptsの言語であるsquirrelに対応している数少ないエディターだから
  3. 無料だから
である。
Atom自体のインストールの仕方と日本語化の方法については各自で調べてほしいので割愛する。
インストールできたら「settings(設定)」の「install(インストール)」で「language-squirrel」と検索する。そうしたら、一番上に出てくるであろうパッケージをインストールする。

これで、前回の記事で作成した例のnutファイルを開くと、このように色付けされるようになるのでわかりやすく使える。

nutファイルの置き場所
前回の記事通りにnutファイルを作成したなら、今の置き場所は
「~~~\Steam\steamapps\common\Counter-Strike Global Offensive\csgo\scripts\vscripts\」
になっているはずである。だが、この階層にファイルを置いておくと、他のファイルと競合する可能性が高いので、さらにフォルダをつくってそこにファイルを置くことを強く勧める。オススメは
「~~~\csgo\scripts\vscripts\<あなたの名前>\<マップ名>\」
である。
vscriptsの基本
vscriptsの言語はsquirrelである。そして、squirrelはC言語に組み込まれることが目的の組み込み型言語である。よって、
vscriptsを覚える=squirrelを覚える=C言語をある程度把握していることが前提
である。このガイドは初心者でもわかるように説明していくので、最初はC言語の内容に軽く触れる。
というわけで、C言語のお約束であるHello World文から始めていこうと思う。

次の文は前回のガイドで使用した、test.nut(ボタンを押したらプレイヤーがテレポートするコード)をすべて消して、上書き保存してかまわない。
function pressed(){ printl("Hello World!") }
上の文を解説する。
この「function」は「関数」と呼ばれている。vscriptsでは基本的に、この関数の集まりをひとまとめのnutファイルにして「logic_scripts」で指定する。
そして、このnutファイルではボタンが押されたときに「pressed()」関数が呼び出される。この「function 関数名()」の1文を「プロトタイプ宣言」という。
そして、CSGOで予め定義されているグローバル関数の一つである「printl」が実行される。「printl」とはゲーム内の開発者コンソールに()の中の文章を出力するグローバル関数である(初級編で解説)。""はstring(文字列)を囲むための必須のルールである。
このnutファイルを保存し、ラウンドをリスタートしてボタンを押せば、あなたの開発者コンソールに「Hello World!」と書いてあるはずである。

C言語を知ってる人は不思議な部分がたくさんあると思う。それを一つずつを挙げていこう。プログラミング初心者の人は飛ばしても構わない。

========================================================

  1. 予約語
    一つ目は予約語の違いだ。予約語とは、その単語で変数と識別子が使えない単語である。以下に予約語を羅列するが、プログラミング初心者の方は覚えずに変数と識別子を新たに指定するときにこの表を参照するだけでも構わない。
    break case catch class clone continue const default delegate delete else enum extends for foreach function if in local null resume return switch this throw try typeof while parent yield constructor vargc vargv instanceof true false static

  2. 型の自由度
    変数を指定するときに、大抵はその変数に型を指定する。だが、vscriptsにはそもそも型の概念がない。整数型、浮動小数点型、文字型、論理型など自由に代入できる。自動的に整数は符号付き32ビット、少数は符号付き32ビット(単精度浮動小数点実数)となる。

  3. vscriptsにエントリーポイントは存在しない
    エントリーポイントとは、そのプログラム全体の開始地点である。vscriptsは関数一つ一つが個別に呼び出すことが可能であり、あくまでmap中のentityを補助することが目的なので、エントリーポイントは不要なのである。*1

  4. セミコロンレス
    C言語はセミコロン「;」は文の終端に必要だが、vscriptsでは必要なく、セミコロンがあってもなくても問題なく動作する
    セミコロンを置くか置かないかは個人の自由です。ここでセミコロン論争してもしょうがないので。

  5. return文と戻り値
    C言語は関数を終了させるために必ず「return文」が必要であった。だが、vscriptsでは必須ではない。
    かといって、戻り値が指定できないわけではない。あくまで「return文」のルールがないだけで、戻り値は指定可能だ。
    function HelloWorld(){ return "Hello World!" } function pressed(){ printl(HelloWorld()) }

========================================================

ここまでC言語との相違点をあげた。このHello World文もいろいろ別解がある。下の文でも全く同じ結果になる。
function pressed(){ print("Hello World!\n"); }
function pressed() printl("Hello World!")
そして大事なのはコメント。コメントを使ってその文の解説をコード上に乗せておこう。
//スラッシュ2つから行末まで何を書いてもよい /* 複数行の コメントもできる */ function pressed(){//pressed関数が呼び出される printl("Hello World!") //コンソールにHello World } //ここまでがpressed()

ここからはvscriptsを書く上でのルールを説明するが、ほとんどsquirrelの書き方と同じである。
変数と定数
前回の記事でも解説したが、vscriptsの変数にはグローバル変数とローカル変数がある。*2

グローバル変数
グローバル変数はプログラム中のどこからでも参照ができる。
グローバル変数は宣言するときに必ず初期化(値を決める初期値を設定)しなければならない。
初期化するには演算子「<-」を使用する。
変数名 <- 初期値; g_bFlag <- false; g_nSize <- 7; g_dPi <- 3.1415926535; //vscriptsはfloat型までしか対応してないので呼び出すと「3.141593」となる g_sPlayerName <- "Player1";
宣言済みのグローバル変数の代入には演算子「=」を使用する。
g_nSize <- 2; g_nSize = 6; printl("Size…" + g_nSize); //Size…6
ただし、このグローバル変数はブロック以外で呼び出されるたびに初期化される
なので、呼び出しても初期化されないようにするためには、コロン2つ「::」を使用する。*3
::g_nPrice <- 80;
これで、一番最初に80に初期化されたあとは値が変わるとそれぞれ保持される。

ローカル変数
ローカル変数とはそのブロック、関数やクラス内でのみ有効な変数。
グローバル変数とは違い初期化する必要はない。初期値がないと「null(空、何も入ってないの意味)」で初期化される。
local 変数名 = 初期値; local m_X; local m_bFlag = false; local m_nSize = 7; local m_fPi = 3.141593; local m_sPlayerName = "Player1";

定数
変数はあとから演算子を使って値を変えられるが、変える必要がないなら定数という方法もある。定数として扱えるのは数値型(整数や小数)、文字列型(""で囲まれた文字)だけであり、初期値として指定できるのはリテラル値のみである。(リテラル値とは、"文字通りの定数"という意味。直定数。数値型や文字列型だと思えばOK)
定数のスコープはグローバルである。定数と同名のローカル変数があるとローカル変数が優先される。
定数を指定するには予約語の「const」を使用する。
const 定数名 = 初期値; const sMapName = "de_dust2"
配列
複数のデータを扱うために配列という方法がある。
データが複数あるとして、それを一つずつ変数で宣言するのは大変面倒なので、ひとまとめにする機能として配列がある。
例えるなら、カラーボックスの段ごとに別の雑貨を収納してカラーボックス自体を移動したりそれぞれの雑貨を取り出したりして利用するための機能である。

配列の宣言
配列もグローバルにしたりローカルにしたり変数とだいたい同じである。*4
local array1 = []; //空のローカル配列 local array2 = [1,2,3]; //宣言と初期化がされているローカル配列 array3 <- [1,2,3,"four"]; //宣言と初期化がされ、型が混ざっているグローバル配列 array4 <- array(10); //関数が生成され、10個分のnullの要素が格納されているグローバル配列 ::array5 <- array(10,"Element"); //関数が生成され、10個分の"Element"の要素が格納されているブロック外からでも参照できるグローバル配列
array2の「1」や「2」や「3」が要素と言われている。
array4とarray5は
array4 <- [null,null,null,null,null,null,null,null,null,null]; ::array5 <- [ "Element" "Element" "Element" "Element" "Element" "Element" "Element" "Element" "Element" "Element" ];
と同じである。

配列の操作
配列の要素にを変えるには以下のようにする。
local CMYKcolor = ["Cyan","Red","Yellow","Black"]; CMYKcolor[1] = "Magenta"; color = "Key"; CMYKcolor[3] = color; //["Cyan","Magenta","Yellow","Key"]
local RGBcolor = ["Red","Green","Blue"]; printl(RGBcolor[0]);//Red printl(RGBcolor[1]);//Green printl(RGBcolor[2]);//Blue printl(RGBcolor.len());//3 //.len()は配列の要素数を取得できる
気をつけてほしいのがプログラミングの世界では一番最初の数は1ではなく0である。人間界では1、2、3…のように数えるが、コンピュータは0、1、2…である(0の次に1になるとも限らない)。
テーブル
テーブルは配列よりもさらに多くのデータを一度に格納することができる。
例えるなら、雑貨が収納されたカラーボックスの段に名前シールを貼れたりできる。

テーブルの宣言
テーブルはキーと値の集まりで、基本的に「キー = 値」と書く。
この「キー = 値」をまとめてスロットと呼ぶ。
local テーブル名 = { キー名1 = 値, キー名2 = 値, キー名3 = 値 };
local Table1 = { firstkey = "value", secondkey = 53, thirdkey = false }; printl(Table1.firstkey); //value printl(Table1.secondkey); //53
Table2 <- { }; //空のテーブルが生成される(グローバル) Table2.firstkey <- "MinValue"; //空のテーブルにスロットが作成され、キーが「firstkey」になり値が「MinValue」になる Table2.firstkey = 24; //作成されたスロットにはあとから値を代入できる
スロットは予約語の「delete」で削除できる。
local Table3 = { firstkey = "value", secondkey = 53, thirdkey = false }; delete Table3.firstkey; printl(Table3.len()); //2
演算子
+、-、×、÷などの記号を演算子という。
数学とプログラミングで使用される演算子の記号は少し違う。
数学で言う「x=y」は「xはyである」という意味であるが、プログラミングの世界では「xにyを代入」という意味になる。

算術演算子
演算子
意味
x+y
加算
x-y
減算
x*y
乗算
x/y
除算
x%y
剰余
-x
符号反転
local m_nAddition = 1 + 2; //3 local m_nSubtraction = 8 - 3; //5 local m_nMultiplication = 4 * 5; //20 local m_fDivision = 12 / 3; //4
整数の除算のルールはC言語と同じで、0に丸められる。正の場合は切り捨て、負の場合は切り上げになる。
vscriptsは小数点をつけたかどうかで小数点以下の計算が行われる。
local m_fDivision1 = 7 / 3; //2 local m_fDivision2 = 7.0 / 3; //2.000000 local m_fDivision3 = 7.0 / 3.0; //2.333333

インクリメント・デクリメント
「++」をインクリメント、「--」をデクリメントという。
++は1ずつ増加し、--は1ずつ減少する。
演算子
意味
++x
前置きインクリメント
local m_nA = 2;
local m_nB = ++m_nA; //m_nAもm_nBも3になる
x++
後置きインクリメント
local m_nA = 2;
local m_nB = m_nA++; //m_nBだけ3になる
--x
前置きデクリメント
local m_nA = 2;
local m_nB = --m_nA; //m_nAもm_nBも1になる
x--
後置きインクリメント
local m_nA = 2;
local m_nB = m_nA--; //m_nBだけ1になる

比較演算子
関係演算子ともいう。演算子の左右にある値の比較を行う。
演算子
意味
x==y
等しいかどうか
x!=y
等しくないかどうか
x<y
xがyより小さいかどうか
x>y
xがyより大きいかどうか
x<=y
xがy以下かどうか
x>=y
xがy以上かどうか

論理演算子
論理演算を行う。AND、OR、NOT演算子がある。
演算子
意味
x&y
xかつyかどうか
x||y
xまたはyかどうか
!x
xでないかどうか
論理否定演算子である「!」は論理型(bool型)の変数でしか利用できない。

比較演算子と論理演算子は次のセクションで例を紹介する。
条件分岐
プログラムである条件を満たしたときだけ特定の動作をさせたい時がある(hammer上でいうlogic_branchやlogic_case)。そんなときは条件分岐という処理が使用できる。予約語として「if」「else」「switch」などがある。

if文
条件によって処理を行うか行わないか決めたいときは「if」を使う。
「もし~~なら〇〇する」と言った感じだ。(hammer上でいうlogic_branch)
if(条件式){ 処理 };
上の文だと条件が合うときだけ処理が行われる。
「もし~~なら〇〇する。そうでないなら△△する」といった文にしたいなら、
if(条件式){ 処理1 }else{ 処理2 }
という文になる。条件が合うときだけ処理1を行い、条件が合わないと処理2を行う。
さらに、「もし~~なら〇〇する。そうでなく、もし□□なら△△する。」といった文にしたいなら、
if(条件式1){ 処理1 }else if(条件式2){ 処理2 }else{ 処理3 }
という文になる。
まず条件式1の分岐が起きて、合うなら処理1を行い、条件が合わないとさらに条件式2の分岐が起きて、合うなら処理2を行い、条件が合わないと処理3が行われる。
local team = activator.GetTeam(); if(3==team){//プレイヤーがCTなら printl("Triggered player is CT."); }else if(2==team){//プレイヤーがTなら printl("Triggered player is T."); }else{ printl("Error !!"); }
local team = activator.GetTeam(); if(3==team)printl("Triggered player is CT."); //1行に省略可能

switch文
制御式(変数)によって処理の流れを変えたいときには「switch」を使う。
「もし~~が1なら〇、2なら△、3なら…」と言った感じだ。(hammer上でいうlogic_case)
switch(変数) { case 値1: 処理1 // 変数の値 == 値1 のとき実行される break; case 値2: 処理2 // 変数の値 == 値2 のとき実行される break; ・ ・ ・ default: 処理X // 変数の値がどの値とも異なるとき実行される break; }//☆
「break」はそこから☆印のところまで飛ばされることを意味する。もしbreakがなったら、処理Xまですべての処理が行われてしまう。
local velocity = activator.GetVelocity() switch(velocity) { case 0: printl("Triggered player is stopped."); break; case 250: printl("Triggered player is running."); break; default: printl("Triggered player is walking or have a weapon."); }
繰り返し文
同じ処理を何度も実行したいときには「while文」、「do-while文」、「for文」、そしてvscripts独自のforeach文がある。
これらの繰り返し行われる処理を「反復処理」という。
foreach文については上級編で説明する。

while文
なにかの条件が合うとき、同じ処理をし続けたいならwhile文を使う。
while文は他の繰り返し文と比べて、文字と論理演算子の条件と相性がいい。
「~~であるかぎり〇〇し続ける」と言った感じだ。
while(条件式){ 繰り返したい文;// 条件式が真の間繰り返される }
ループから抜けたいときは「break」、ループの先頭に戻りたいときは「continue」を使う。
while(true){// 条件式が常に true なので、永久ループになる。 // 何らかの処理 break; // break よりも後ろの処理は実行されない。 } // break 文が実行されると処理がここに移る。
while(true){// continue 文が実行されると条件式の判定から処理をやり直す。 // 何らかの処理 continue; // continue よりも後ろの処理は実行されない。 }
local i = 0; while(i<3){//iが3未満なら{}内を実行する。 i++; printl("実行" + i + "回目"); } //3回実行される。4回目を実行するか決めるとき、条件と比較しループを抜ける。

do-while文
while文は条件が偽なら{}内が一度も実行されないが、一回は実行したいならdo-while文を使う。
do-while文は他の繰り返し文と比べて、文字と論理演算子の条件と相性がいい。
「〇〇する。~~であるならまた〇〇する」と言った感じだ。
do{ 繰り返したい文 // 条件式が真の間繰り返される。 }while(条件式);
local i = 0; do{ i++; printl("実行" + i + "回目"); }while(i<3)//iが3未満なら{}内を実行する //3回実行される。3回目が実行された後、条件と比較しループを抜ける。

for文
while文、do-while文で整数が格納された変数を使用した文は基本的にfor文で使われる。
for文は上の例のコードの「local i = 0」「i<3」「i++」を一行にする。
それぞれ、「初期化」「条件式」「更新式」と呼ばれている。
for文は他の繰り返し文と比べて、整数と算術演算子の条件と相性がいい。
for(初期化式; 条件式; 更新式){ 反復を行いたい文; }
//変数iが作成され、初期化され、条件が真なのでループに入る。 for(local i = 0;i<3;i++){//iが3未満なら{}内を実行する。 printl("実行" + i + "回目"); } //反復処理が行われる前に更新式が実行される。その後、条件が合うならループする。 //4回目を実行するか決めるとき、条件と比較しループを抜ける。
おわりに
ここまで、SquirrelとC言語に共通した基本事項について解説していきました。次のガイドではvscripts独自の関数について解説していきたいと思います。
↓次の記事
https://steamcommunity.com/sharedfiles/filedetails/?id=1728925036

何かわからないことがあればTwitter @Shimelarに質問してね。
高評価ボタンを押してくれると執筆者が喜んでまたガイドを書くかも。
注釈
Originally posted by *1:
本当はvscriptsにもmain関数は存在する。だがゲーム内に埋め込まれていて編集はほぼ不可能。
Originally posted by *2:
この記事ではハンガリアン記法を使用しますが、vscriptsは型の指定がないので意味がほぼない。
Originally posted by *3:
少し突っ込んで話すと、グローバル変数はスコープできる範囲が宣言されたクラス・関数の範囲内か、呼び出されたクラス・関数の範囲内である。::を使うと、クラス外や関数外からでもスコープできる。
Originally posted by *4:
vscriptsでは1次元配列しか対応してないが、テーブルと組み合わせれば2次元配列以上を再現が可能。
参考文献
CSGOの関数のまとめ(valve公式)
https://developer.valvesoftware.com/wiki/List_of_Counter-Strike:_Global_Offensive_Script_Functions

Vscript、VPhysics、弾幕についての基礎
https://steamcommunity.com/sharedfiles/filedetails/?id=1680694705

Squirrel Programming Guide(公式ページ)
https://developer.electricimp.com/squirrel/squirrelcrib

Squirrel入門講座
http://squirrelnyuumon.web.fc2.com/

C# によるプログラミング入門
https://ufcpp.net/study/csharp/

今さら聞けない、変数や関数の命名規則と、まず覚えるべき英単語200
https://oxynotes.com/?p=8679

Atom(公式ページ)
https://atom.io/