xlib_playground2.html (8025B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width,initial-scale=1"> 6 <link rel="stylesheet" type="text/css" href="/style.css"> 7 <link rel="icon" type="image/x-icon" href="/pics/favicon.ico"> 8 <title>Xlibで遊んでみる2</title> 9 </head> 10 <body> 11 <header> 12 <a href="/">主頁</a> | 13 <a href="/about.html">自己紹介</a> | 14 <a href="/journal">日記</a> | 15 <a href="/farm">農業</a> | 16 <a href="/kitchen">台所</a> | 17 <a href="/computer">電算機</a> | 18 <a href="/poetry">詩</a> | 19 <a href="/books">本棚</a> | 20 <a href="/gallery">絵</a> | 21 <a href="/plant">植物</a> | 22 <a href="https://git.mtkn.jp">Git</a> 23 </header> 24 <main> 25 <article> 26 <h1>Xlibで遊んでみる2</h1> 27 <time>2022-12-22</time> 28 29 <p>前回: <a href="xlib_playground1.html">Xlibで遊んでみる1</a></p> 30 <p>言語はC言語である。ソースコードは<a href="https://git.mtkn.jp/xlib_playground">ここ</a>にある。 31 </p> 32 33 <h2>FPSの固定</h2> 34 <p>前のフレームからの経過時間を計測して<code>1.0/FPS</code>を越えるまで待機させる。このときに<code>nanosleep()</code>を使うとなぜか上手くいかなかった。ナノ秒単位で処理できそうな名前なのに使えない。多分OSのコンテクストスイッチがどうとかいう話やと思う。知らんけど。組み込みとかで使うんかな? 35 </p> 36 37 <p> 38 とりあえず<code>while</code>ループの中でひたすら時刻を読んでいる。リソースの無駄遣いではないのだろうか: 39 </p> 40 <pre><code>#defin FPS 60 41 42 int 43 main(void) 44 { 45 long t0, t1, dt; 46 int fps_count; 47 48 clock_gettime(CLOCK_MONOTONIC, &ts); 49 t0 = ts.tv_nsec; 50 51 while (!quit) { 52 // fix fps 53 dt = 0; 54 while (dt < 1.0 * 1000 * 1000 * 1000 / FPS){ 55 clock_gettime(CLOCK_MONOTONIC, &ts); 56 t1 = ts.tv_nsec; 57 dt = t1 > t0 ? t1 - t0 : t1 - t0 + 1000 * 1000 * 1000; 58 } 59 // count fps. 60 fps_count++; 61 if (t1 < t0){ 62 printf("fps: %u\n", fps_count); 63 fps_count = 0; 64 } 65 clock_gettime(CLOCK_MONOTONIC, &ts); 66 t0 = ts.tv_nsec; 67 } 68 } 69 </code></pre> 70 <p> 71 時刻は<code>clock_gettime()</code>で測定して1秒未満の部分: <code>tv_nsec</code>だけを利用している。<code>tv_nsec</code>はナノ秒ナノで、10<sup>9</sup>を掛けている。<code>dt = t1 > t0 ? t1 - t0 : t1 - t0 + 1000 * 1000 * 1000</code>で前回の時刻と現在の時刻の少数部分を比較している。繰り上がりがあれば前回の時刻よりも現在の時刻の方が小さくなるので1秒足すことで調整している。</p> 72 <p> 73 FPSの計測の部分は、フレーム毎に<code>fps_count</code>を1ずつ増やし、ナノ秒が繰り上がった時点での<code>fps_count</code>を表示している。</p> 74 <p> 75 あまり正確な方法ではないように思うが、コンパクトにまとまったのではないだろうか。</p> 76 77 <h2>キーボード入力の処理</h2> 78 <p>キーボードからの入力を受け取る:</p> 79 <pre><code>XSelectInput(display, window, 80 ExposureMask|KeyPressMask|KeyReleaseMask); 81 </code></pre> 82 <p>ここではキーボードのキーを押した時と離した時に<code>XEvent</code>の通知を受け取るように設定した。 83 </p> 84 <p> 85 <code>XNextEvent()</code>からひとつずつ入力を受け取ると、複数のキーが同時に押された時にうまく処理できなかったので、押されているキーを配列に保存しておくことにした:</p> 86 <pre><code>enum Keys { 87 Key_D, 88 Key_S, 89 Key_A, 90 Key_W, 91 Key_Space, 92 Num_Key, //number of keys in this enum 93 }; 94 enum Key_State { 95 Key_Up, 96 Key_Down, 97 }; 98 99 int key_state[Num_Key]; 100 </code></pre> 101 102 <p> 103 入力の処理は<code>handle_inputs()</code>関数内で行なう。<code>A</code>、<code>S</code>、<code>D</code>、<code>W</code>のうちどれかのキーが押されているとそれぞれ左、下、右、上方向に速度を加算するようにした。また、<code>Q</code>が押されるか、windowが破壊されると<code>quit</code>フラグを<code>1</code>にしてメインループから抜けるようにしている:</p> 104 <pre><code>int quit; 105 106 void 107 handle_inputs(void) 108 { 109 XEvent event; 110 while (XPending(display) > 0) { 111 XNextEvent(display, &event); 112 switch (event.type) { 113 case KeyPress: { 114 switch (XLookupKeysym(&event.xkey, 0)) { 115 case 'q': 116 quit = 1; 117 break; 118 case 'd': 119 key_state[Key_D] = Key_Down; 120 break; 121 case 'a': 122 key_state[Key_A] = Key_Down; 123 break; 124 case 'w': 125 key_state[Key_W] = Key_Down; 126 break; 127 case 's': 128 key_state[Key_S] = Key_Down; 129 break; 130 default: 131 break; 132 } 133 } break; 134 case KeyRelease: { 135 switch (XLookupKeysym(&event.xkey, 0)) { 136 case 'd': 137 key_state[Key_D] = Key_Up; 138 break; 139 case 'a': 140 key_state[Key_A] = Key_Up; 141 break; 142 case 'w': 143 key_state[Key_W] = Key_Up; 144 break; 145 146 case 's': 147 key_state[Key_S] = Key_Up; 148 break; 149 default: 150 break; 151 } 152 } break; 153 case ClientMessage: { 154 if ((Atom) event.xclient.data.l[0] == wm_delete_window) { 155 quit = 1; 156 } 157 } break; 158 default: 159 break; 160 } 161 } 162 163 vx = vy = 0; 164 if (key_state[Key_D] == Key_Down) 165 vx += 300; 166 if (key_state[Key_A] == Key_Down) 167 vx += -300; 168 if (key_state[Key_S] == Key_Down) 169 vy += 300; 170 if (key_state[Key_W] == Key_Down) 171 vy += -300; 172 } 173 </code></pre> 174 175 <p> 176 入力によって変更された速度は、<code>main()</code>関数内で次の座標を計算するために使用される: 177 </p> 178 <pre><code>float px = 200, py = 200; 179 float vx = 0, vy = 0; 180 int width = 40, height = 40; 181 182 int 183 main(void) 184 { 185 /* ... */ 186 quit = 0; 187 while (!quit) { 188 handle_input() 189 /* ... */ 190 px = px + vx * dt / 1000 / 1000 / 1000; 191 py = py + vy * dt / 1000 / 1000 / 1000; 192 // bind within the window 193 if (px < 0) 194 px = 0; 195 if (win_width < px + width) 196 px = win_width - width; 197 if (py < 0) 198 py = 0; 199 if (win_height < py + height) 200 py = win_height - height; 201 202 XClearArea(display, window, 203 0, 0, // position 204 win_width, win_height, // width and height 205 False); 206 XFillRectangle(display, window, gc, 207 px, py, // position 208 width, height); // width and height 209 } 210 /* ... */ 211 } 212 </code></pre> 213 214 <h2>完成品</h2> 215 <a href="https://git.mtkn.jp/xlib_playground/file/ex2/ex2.c.html">ソースコード</a> 216 <p>色を変えてみた。</p> 217 <video controls> 218 <source src="videos/ex2.webm" type="video/webm"> 219 </video> 220 221 <h2>参考</h2> 222 <ul> 223 <li><a href="https://tronche.com/gui/x/xlib/">The Xlib Manual(html conversion)</a></li> 224 </ul> 225 <p>次の記事: <a href="xlib_playground3.html">Xlibで遊んでみる3</a> 226 </p> 227 </article> 228 229 </main> 230 <footer> 231 <address>info(at)mtkn(dot)jp</address> 232 <a href="http://creativecommons.org/publicdomain/zero/1.0?ref=chooser-v1" rel="license noopener noreferrer">CC0 1.0</a> 233 </footer> 234 </body> 235 </html>