当記事はGIFアニメーションを多く含みます。自動再生されないときは画像をタップしてください。
(↑GIFアニメ)
V言語について
2021年頃に一度話題になったV言語ですが、その時点ではちょっと誇大広告気味だったのもあってか、その後下火になりあまり話題にならなくなりました。
2025年になって改めてウォッチしてみると、言語としての安定度も高まってきており 1、GCがデフォルトでオンになっていたり 2、当時OpenGLだけ標準でサポートされていたのが、Metal, D3D11, WebGL2などに対応できるSokolベースのggというフレームワークができたり、それをベースにした新たなGUIフレームワークができたりと、気づかぬうちにいろいろ進化しています。
gg について
(↑ examples/gg/stars.v、GIFアニメ)
ggは、Sokolベースの描画フレームワークで、基本的に2D用ですが、Sokol GL (sgl) というOpenGL互換(OpenGL風)レイヤーの上に構築されているので、3Dにも対応できます。基本的にSokolにできることはggでもできます。
なんとなくp5.js (Processing、Proce55ing) に似たAPIになっていて、それが今回の記事の動機になっています。
V言語は爆速かつ省メモリなので、ggと合わせて、ProcessingやopenFrameworks に代わる(あるいはLÖVE 2Dの代替など)、新たな存在となる可能性もあるように私は感じています。
開発環境について
V言語のインストール自体は簡単で、
$ git clone --depth=1 https://github.com/vlang/v
$ cd v
$ make
$ ./v symlink
ですぐに完了します。makeとありますが、makeは内包されているので何も他にインストールする必要はありません。gccなどの別のコンパイラも基本的には要りません。TCC(Tiny C Compiler)というコンパイラが自動で内包されます。 1
あとは v
と打つと、インタプリタ(対話環境)で遊べます。
v run hello.v
でファイルを実行することもできます。
IDEがほしいときは、VSCodeにv-analyzerをインストールすれば、もうバッチリです。3
v-analyzerのバイナリのインストールが途中で促されますが、環境によっては失敗してしまうこともあるので、その場合はv-analyzer/releasesからダウンロードして解凍してPATHに配置すればOKです。
(v-analyzer、たまにおかしな現象に見舞われるので、詳しくは脚注 3 を参照のこと。)
Hello World
百聞は一見に如かずなので、Hello Worldを書いてみます。
現時点でV言語はQiitaではシンタックスハイライトされないようです。
import gg
const win_width = 600
const win_height = 300
struct App {
mut:
gg &gg.Context = unsafe { nil }
}
fn main() {
mut app := &App{}
app.gg = gg.new_context(
bg_color: gg.white
width: win_width
height: win_height
create_window: true
window_title: 'Hello'
frame_fn: frame
user_data: app
)
app.gg.run()
}
fn frame(mut app App) {
app.gg.begin()
app.draw()
app.gg.end()
}
fn (mut app App) draw() {
g := app.gg
g.draw_text_def(10, 10, 'hello world!')
g.draw_rect_filled(30, 30, 40, 40, gg.blue)
g.draw_rect_empty(25, 25, 50, 50, gg.black)
}
v run hello.v
で実行することができます。
基本的には最後の draw()
の中身だけ見ればOKです。
fn (mut app App) draw() {
g := app.gg
g.draw_text_def(10, 10, 'hello world!')
g.draw_rect_filled(30, 30, 40, 40, gg.blue)
g.draw_rect_empty(25, 25, 50, 50, gg.black)
}
ggでは draw_xxx_filled
で塗りつぶし図形の描画、draw_xxx_empty
で枠線だけの図形描画になるようです。
マウスに追従させてみる
(↑GIFアニメ)
fn (app &App) draw() {
g := app.gg
x := g.mouse_pos_x
y := g.mouse_pos_y
g.draw_text_def(x - 10, y - 20, 'hello world!')
g.draw_rect_filled(x + 10, y + 10, 40, 40, gg.blue)
g.draw_rect_empty(x + 5, y + 5, 50, 50, gg.black)
}
ノリはProcessingとそっくりですね。
キーボードを使う
(↑GIFアニメ)
fn (mut app App) draw() {
mut g := app.gg
if g.is_key_down(gg.KeyCode.space) {
g.draw_text_def(10, 110, 'space key press')
g.draw_rect_filled(10, 10, 100, 100, gg.blue)
}else{
g.draw_text_def(10, 110, 'space key release')
g.draw_rect_empty(10, 10, 100, 100, gg.black)
}
}
こちらもなんとなくProcessingと似ていますね。 (【追記】 ドキュメントを読んでいると、コールバックはちゃんとありました: 参考。fn key_pressed()
などのイベント関数はありませんが、必要なら作れそうな雰囲気ですframe_fn
と同じように keyup_fn
などを設定できます)。
回転させてみる
(↑GIFアニメ)
ggだけでは回転できないので、sgl (Sokol GL) を呼び出します。コードの最上部に import sokol.sgl
が要ります。
fn (mut app App) draw() {
g := app.gg
r := g.mouse_pos_x
sgl.translate(200, 200, 0)
sgl.rotate(sgl.rad(r), 0, 0, 1)
g.draw_text_def(0, 0, 'hello world!')
}
sglの関数一覧を見てみるとわかるのですが、できることは基本的にOpenGLと同じなので、例えば glPushMatrix()
は push_matrix
であったりと、読み替えができる作りになっているようです。
まとめ
今回はこの辺で終わりたいと思いますが、sglのドキュメントや、ggのドキュメント をざっと眺めてみると、どんなことができるかなんとなくわかるかと思います。
ggのexamples (V言語インストール時のフォルダに v/examples/gg
に入っている)には、3Dの例などもあるので、参考にされてみると良いかと思います。( v run xxx.v
で実行可能。)
なお、V言語自体の情報は後述のように2021年に流行ったときとだいぶ変わっているので、英語の公式ドキュメントを参考にされることをおすすめします。
補足: V言語の当初との違いについて
補足になりますが、V言語ではグローバル変数はデフォルトでは無効になっていることなどもあり、2021年当初はV言語にはクロージャがないというのが懸念となっていたりもしていました。
しかし現時点ではクロージャは既に実装されています。
my_int := 1
my_closure := fn [my_int] () {
println(my_int)
}
my_closure() // prints 1
このように、2021年頃にはあった様々な懸念事項の多くが、2025年現在では解消されているので、当時微妙だなと感じた方はまた改めてウォッチされてみると楽しいのではないかと思います。
-
V言語のデフォルト(TCC)では、サードパーティのライブラリを使うときに、C言語のヘッダインクルード時(
#include "xxx.h"
)に正しくパースされないというエラー報告がとても多い印象で、実際にそれで困ることもあるのですが、V言語というよりTCC(Tiny C Compiler)の問題なので、v -cc clang
やv -cc gcc
などしてCコンパイラを切り替えれば(v run
の代わりにv -cc clang run
など)、比較的安定して使えます。 ↩ ↩2 -
GCは重くなるなどいろんな懸念がある方もいるかと思いますが、開発者本人も当初はGCに懐疑的だった様子で、アンチGCだったらしいのですが、実際に導入してみると実用上全く問題なかったとのことで、導入が決まったようです。(GCはオフにすることもできますし、9割くらいを静的解析で自動開放できる
-autofree
もあります。) ↩ -
v-analyzerは、現時点では、
module main
など、main関数を持つvファイルが複数あると、アルファベット順で最初のファイル以外はどうやらパースがおかしくなってしまうようです。V言語の仕様なのかもしれないのですが、私はmkdir dev; ln -s ../xxx.v dev/xxx.v; code dev
として、1つのファイルだけになるようにして回避したりしています。(あるいはアルファベット順で最初になるようにリネームして開発するなど。)【追記】 Go言語と同様に仕様のようなので、Go言語のmain.goと同じく、フォルダに1つにしたほうが良いようですね。init関数といい、よくわからないものはGo言語を参考にするとだいたい分かる感じのようです。 ↩ ↩2