もくじ
この本の使い方
Sprout68k で書けるプログラムに出てくる関数を、目的から引けるようにまとめたものです。上から順に読むと一通り書けるようになり、後半の「関数図鑑」はコードを書きながら引くための一覧になっています。
できることは、いまのところこの3つです。
絵を描く — 512×512 ドット・65536 色の画面に、点・線・四角・円を描けます。
キーを読む — カーソルキー、スペース、数字、アルファベットが読めます。
文字を出す — printf で、絵の上に重ねて文字を出せます。
音を鳴らす関数はまだありません。マウスやジョイスティックも未対応です。増えたらこのページに追加します。
いちばん短いプログラム
まずはこれです。エディタに貼り付けて実行 を押すと、X68000 の画面に文字が出ます。
#include "x68.h"
void main(void) {
printf("HELLO X68000");
}
#include "x68.h" — この 1 行で、このページに載っている関数がすべて使えるようになります。プログラムの先頭に必ず書きます。
void main(void) — プログラムはここから始まります。この環境では戻り値を返す必要がないので void です。
printf(...) — 文字を表示します。
main から抜けると、起動時のプログラムが停止して待ち続けます。表示するだけのプログラムに for (;;) を書く必要はありません。
絵を描く — 3 つの決まりごと
絵を出すには、かならず次の 3 段が要ります。
x68_screen_open() で画面を開く(最初に 1 回だけ)
描画関数で描く
x68_screen_flip() で画面に出す
描画関数が描いているのは画面そのものではなく、メモリの上の「下書き用紙」です。x68_screen_flip() を呼んだときに、そこから画面へまとめて送られます。この一手間があるおかげで、描いている途中の絵が表示されてチラつくことがありません。絵が出ないときは、まずこの 3 段のどれかが抜けていないかを確かめてください。
#include "x68.h"
void main(void) {
int black = x68_rgb(0, 0, 0);
x68_screen_open();
x68_cls(black);
x68_box_fill(160, 160, 192, 192, x68_rgb(255, 128, 0));
x68_circle(256, 256, 120, x68_rgb(255, 255, 255));
x68_line(0, 0, 511, 511, x68_rgb(0, 255, 255));
x68_screen_flip();
}
座標
左上が (0, 0)、右下が (511, 511) です。y は下へ行くほど大きくなります(算数のグラフとは上下が逆です)。画面からはみ出した部分は自動的に切り取られるので、はみ出しを自分で計算する必要はありません。
色
色は x68_rgb(赤, 緑, 青) で作ります。それぞれ 0〜255 で、0 が暗く 255 が明るい値です。よく使う色は変数に入れて名前を付けておくと、あとで読み返したときに分かりやすくなります。
int black = x68_rgb(0, 0, 0);
int white = x68_rgb(255, 255, 255);
int red = x68_rgb(255, 0, 0);
x68_screen_open();
x68_cls(black);
x68_box_fill(180, 180, 152, 152, red);
x68_box(180, 180, 152, 152, white);
x68_screen_flip();
ゲームの形
動くものを作るときは、次の 1 周をずっと繰り返します。この形はどのゲームでも変わりません。
x68_screen_open();
for (;;) {
/* 1. 画面を消す */
/* 2. キーを読む */
/* 3. 位置を計算する */
/* 4. 描く */
/* 5. 画面に出す */
}
「5. 画面に出す」の x68_screen_flip() が画面の書き換えを待つので、この 1 周は毎秒およそ 55〜60 回になります(描く物がおよそ 60 件までなら毎秒 60 回を保てます。描く物が増えると 1 周が 2 フレーム、3 フレームとかかるようになり、遅くなります)。
下は、この形をそのまま使った短いゲームです。落ちてくる四角を、左右に動く棒で受け止めます。
#include "x68.h"
enum {
kPlayerW = 48,
kPlayerH = 12,
kPlayerY = 460,
kItemSize = 16,
kSpeed = 4,
};
static int player_x = 232;
static int item_x = 100;
static int item_y = 0;
static int score = 0;
/* 1 キーを読んで棒を動かす */
static void ReadInput(void) {
if (x68_key_down(X68_KEY_LEFT)) player_x -= kSpeed;
if (x68_key_down(X68_KEY_RIGHT)) player_x += kSpeed;
if (player_x < 0) player_x = 0;
if (player_x + kPlayerW > X68_SCREEN_W) player_x = X68_SCREEN_W - kPlayerW;
}
/* 2 四角を落として、受け止めたかどうかを見る */
static void UpdateItem(void) {
int caught;
item_y += 2;
caught = (item_y + kItemSize >= kPlayerY) &&
(item_x + kItemSize > player_x) &&
(item_x < player_x + kPlayerW);
if (caught) score++;
if (caught || item_y > X68_SCREEN_H) {
item_y = 0;
item_x = x68_rand_int(X68_SCREEN_W - kItemSize);
}
}
/* 3 描く */
static void Draw(void) {
x68_cls(x68_rgb(0, 0, 48));
x68_box_fill(player_x, kPlayerY, kPlayerW, kPlayerH, x68_rgb(0, 255, 255));
x68_box_fill(item_x, item_y, kItemSize, kItemSize, x68_rgb(255, 224, 0));
x68_locate(0, 0);
printf("SCORE %d ", score);
}
void main(void) {
x68_screen_open();
for (;;) {
ReadInput();
UpdateItem();
Draw();
x68_screen_flip();
}
}
1 キーを読んで棒を動かす — x68_key_down() は押されている間ずっと真になるので、押しっぱなしで動き続けます。動かしたあとで画面の端をはみ出していないか直しています。
2 四角を落として、受け止めたかどうかを見る — 当たり判定は「4 辺が重なっているか」を && で並べるだけです。受け止めたときと、下まで落ちきったときの両方で、上に戻して横位置を引き直しています。
3 描く — x68_cls() で前の位置を消してから、棒・四角・点数の順に描いています。printf の末尾に空白を入れているのは、点数の桁が減ったときに前の数字が残らないようにするためです。
main — 読む・計算する・描く・出す、を並べただけです。処理を小さな関数に分けておくと、あとから中身を差し替えやすくなります。
「押した瞬間だけ 1 回」にしたいときは、前のフレームの状態を変数に覚えておいて比べます。書き方は X68_KEY_* の例にあります。
速さを決める
x68_screen_flip() が画面の書き換えを待つので、間に合っているかぎり 1 周はいつも 1/60 秒 です。速さが勝手にぶれることはありません。だから速さは「1 周に何ドット動かすか」だけで決まります。
1 周に足す値 速さ 画面(512 ドット)を横切る時間
1 毎秒 60 ドット 8.5 秒
2 毎秒 120 ドット 4.3 秒
4 毎秒 240 ドット 2.1 秒
8 毎秒 480 ドット 1.1 秒
迷ったら、自分で動かすものは 4 、勝手に動くものは 2 から試すとちょうどよく感じられます。作例の move.c は 4、catch.c の落ちてくる四角は 2 です。
もっとゆっくり動かしたい
1 周に 1 ドットでも速すぎるときに、間引いてはいけません 。次のように書くと、動かない周が 4 回続いてから 6 ドット飛ぶので、カクカクして見えます。
static int count = 0;
static int x = 100;
if (count % 5 == 0) { /* これはカクつく */
x += 6;
}
count++;
x68_box_fill(x, 200, 16, 16, x68_rgb(255, 255, 0));
そうではなく、本当の位置を 16 倍した整数で持ちます 。この環境に小数はありませんが、16 倍で持てば 1/16 ドット単位で動かせます。画面に出すときだけ 16 で割ります(>> 4 が 16 で割ることにあたります)。
static int x16 = 100 * 16; /* 本当の位置を 16 倍で持つ */
x16 += 12; /* 1 周に 0.75 ドット = 毎秒 45 ドット */
x68_box_fill(x16 >> 4, 200, 16, 16, x68_rgb(255, 255, 0));
16 のかわりに 10 や 100 を使っても構いませんが、16・256 のような 2 の累乗にしておくと、割り算が >> で済むぶん速くなります。
描ける量には上限がある
1 周を 1/60 秒に収めるには、描く回数がおよそ 80 回まで である必要があります(全部が止まっているなら 100 回まで)。x68_pset x68_box_fill x68_line x68_circle がそれぞれ 1 回で、x68_box だけは 4 辺を描くので 4 回と数えます。x68_cls は数えません。
超えると 1 周が 2 フレーム、3 フレームとかかるようになり、ゲーム全体がそのぶん遅くなります 。玉もパドルも点数も、まとめて半分の速さになります。「なんとなく重い」と感じたら、まず 1 周に何回描いているかを数えてみてください。
大きさは関係ありません。画面いっぱいの四角を 1 個描くのも、小さな四角を 1 個描くのも、同じ 1 回です。上限は「面積」ではなく「回数」で決まります。
動かないものがたくさんあるとき
ブロック崩しのブロックのように、置いたまま動かないもの が多いときは、書き方が 2 通りあります。
1. 毎周ぜんぶ描き直す (x68_cls を使う、素直な書き方)。同じ場所に同じものを描き直しても、変わっていないことをライブラリが見分けるので、塗り直しの費用はかかりません 。ただし描く回数は数えるので、上の 80 回の上限には入ります。作例の catch.c・life.c・stars.c はこの書き方です。
2. 動くものだけ描く (x68_frame_begin を使う書き方)。動かないものは最初に 1 回だけ描いておき、以後は「前の位置を背景色で消して、新しい位置に描く」だけにします。描く回数が動くものの分だけで済むので、上限に余裕ができます。作例の breakout がこの書き方です。
2 の書き方では、動いていないものまで毎周消して描き直すと、かえって遅くなります 。「前の位置と今の位置が違うときだけ消して描く」と書いてください。ここを守らないと、1 の素直な書き方より遅くなることがあります。
画面を開く・絵を出す
絵を描くには、まず画面を開きます。描画関数が描くのは画面そのものではなく「裏バッファ」という下書き用紙で、x68_screen_flip() を呼んだときに初めて画面へ出ます。この一手間があるおかげで、絵が描きかけのまま表示されてチラつくことがありません。
x68_screen_openグラフィック画面を使えるようにする
void x68_screen_open(void)
512×512 ドット・65536 色のグラフィック画面に切り替え、絵を描くための裏バッファ(メインメモリ上の下書き用紙、512KB)を用意します。
プログラムの最初に 1 回だけ呼びます。以降の描画関数はすべてこの裏バッファに描きます。
例
#include "x68.h"
void main(void) {
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
x68_box_fill(200, 200, 100, 100, x68_rgb(255, 255, 0));
x68_screen_flip();
}
画面を開いて、黄色い四角を 1 つ描くだけの最小の例です。x68_screen_flip() を呼んで初めて画面に出ます。
これを呼ぶ前に x68_pset() などを呼んでも、何も起きません(エラーにはならないので、絵が出ないときは最初にこれを疑ってください)。
呼んだ直後の画面の中身は決まっていません。まず x68_cls() で背景色を塗ってから描き始めます。
関連 x68_cls ・x68_screen_flip
x68_screen_flip描いた絵を画面に出す
void x68_screen_flip(void)
裏バッファに描いた内容を、実際の画面へ送ります。1 フレームに 1 回呼びます。
送る前に垂直同期(画面の書き換えが一巡する瞬間)を待つので、絵が途中でちぎれて見えることがありません。ゲームの速さがこの待ち時間で決まります(毎秒およそ 55〜60 回)。
送るのは画面全部ではなく「前のフレームと今のフレームで描いた範囲」だけです。動く物が少ないほど速くなります。
例
#include "x68.h"
void main(void) {
int x = 0;
x68_screen_open();
for (;;) {
x68_cls(x68_rgb(0, 0, 32));
x68_box_fill(x, 240, 32, 32, x68_rgb(255, 128, 0));
x68_screen_flip();
x = (x + 1) % X68_SCREEN_W;
}
}
四角を 1 ドットずつ右へ動かします。x68_cls() で前の位置を消し、描いて、flip で出す——これがゲームの基本の 1 周です。
呼び忘れると、いくら描いても画面は変わりません。
1 フレームに 2 回以上呼ぶと、そのたびに垂直同期を待つので単純に遅くなります。
関連 x68_screen_open ・x68_cls ・x68_frame_begin
x68_cls前のフレームに描いた絵を消す
void x68_cls(int color)
名前は clear screen(画面消去)ですが、毎回画面全部を塗り直すわけではありません。前のフレームで描いた場所だけを背景色で塗り戻します。動く物が少ないゲームほど速くなる仕組みです。
見た目は「背景色で画面を消した」のと同じになります。全画面を塗り直すのは、最初の 1 回と、前回と違う色を渡したときだけです。
color背景の色。x68_rgb() で作った値を渡します。
例
#include "x68.h"
void main(void) {
x68_screen_open();
for (;;) {
x68_cls(x68_rgb(0, 0, 64));
x68_pset(x68_rand_int(X68_SCREEN_W), x68_rand_int(X68_SCREEN_H), x68_rgb(255, 255, 255));
x68_screen_flip();
}
}
毎フレーム、濃い青で消してから点を打ち直しています。
引数は「消す量」ではなく背景の色です。
毎フレーム違う色を渡すと、そのたびに全画面を塗り直すことになって遅くなります。背景色は固定にしておくのが無難です。
関連 x68_frame_begin ・x68_rgb
x68_frame_begin画面は消さずに、描画の記録だけ新しくする
void x68_frame_begin(void)
x68_cls() の代わりに呼びます。裏バッファの絵はそのまま残し、「今のフレームでどこを描いたか」の記録だけを空にします。
背景の絵(地面や星空など)を一度描いたら消したくない、動く物だけ描き替えたい、という場合に使います。前の位置を消す作業は自分で行います(そこに背景色の四角を描くなど)。
例
#include "x68.h"
void main(void) {
int x = 0;
int i;
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
for (i = 0; i < 200; i++) {
x68_pset(x68_rand_int(X68_SCREEN_W), x68_rand_int(X68_SCREEN_H), x68_rgb(255, 255, 255));
}
for (;;) {
x68_frame_begin();
x68_box_fill(x, 240, 32, 32, x68_rgb(0, 0, 0));
x = (x + 1) % X68_SCREEN_W;
x68_box_fill(x, 240, 32, 32, x68_rgb(0, 255, 128));
x68_screen_flip();
}
}
星空は最初に 1 回だけ描き、以降は消しません。四角の前の位置だけを自分で黒く塗って消しています。
消す処理を自分で書かないと、動いた跡がそのまま残り続けます(それを狙って線を引く使い方もできます)。
関連 x68_cls ・x68_screen_flip
図形を描く
座標は左上が (0, 0)、右下が (511, 511) です。画面からはみ出した部分は自動的に切り取られるので、はみ出しを気にせず書けます(プログラムが落ちることはありません)。
x68_pset点を 1 つ打つ
void x68_pset(int x, int y, int color)
指定した座標に 1 ドットだけ色を置きます。いちばん基本の描画です。
x横位置。0 が左端、511 が右端。
y縦位置。0 が上端、511 が下端。
color点の色。x68_rgb() で作った値。
例
#include "x68.h"
void main(void) {
int i;
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
for (i = 0; i < 512; i++) {
x68_pset(i, i, x68_rgb(255, 255, 255));
}
x68_screen_flip();
}
斜めの線を点で描いています。画面外に出た分は自動的に無視されます。
画面の外の座標を渡しても何も起きません(落ちません)。
点を何万個も打つのは遅いので、まとまった面は x68_box_fill() を使ってください。
関連 x68_pget ・x68_line
x68_pgetその点に今ある色を読む
int x68_pget(int x, int y)
指定した座標の色を読みます。読むのは裏バッファなので、まだ x68_screen_flip() していない、今のフレームで描いたばかりの色も見えます。
「そこに壁があるか」を色で判定する、といった当たり判定に使えます。
x横位置。
y縦位置。
返り値 その点の色。画面の外を指定した場合は 0。
例
#include "x68.h"
void main(void) {
int wall = x68_rgb(255, 0, 0);
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
x68_box_fill(240, 0, 32, 512, wall);
if (x68_pget(250, 100) == wall) {
printf("HIT WALL");
}
x68_screen_flip();
}
画面の中央に壁を描き、その色と読み取った色が同じかどうかで当たりを判定しています。
0 は「黒」でもあり「画面の外」でもあります。端をまたぐ判定では、座標のほうを先に確かめてください。
関連 x68_pset
x68_line2 点を結ぶ直線を引く
void x68_line(int x1, int y1, int x2, int y2, int color)
(x1, y1) から (x2, y2) まで、太さ 1 ドットの線を引きます。
x1始点の横位置。
y1始点の縦位置。
x2終点の横位置。
y2終点の縦位置。
color線の色。
例
#include "x68.h"
void main(void) {
int i;
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
for (i = 0; i < 512; i += 32) {
x68_line(256, 256, i, 0, x68_rgb(0, 255, 255));
}
x68_screen_flip();
}
画面の中心から放射状に線を引いています。両端が画面の外でも落ちません。
太い線は引けません。太くしたいときは少しずらして何本か引きます。
関連 x68_pset ・x68_box
x68_box四角の枠を描く
void x68_box(int x, int y, int w, int h, int color)
左上が (x, y)、幅 w、高さ h の四角形の枠(太さ 1 ドット)を描きます。中は塗りません。
x左上の横位置。
y左上の縦位置。
w幅(ドット)。0 以下なら何も描きません。
h高さ(ドット)。0 以下なら何も描きません。
color枠の色。
例
#include "x68.h"
void main(void) {
int i;
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
for (i = 0; i < 8; i++) {
x68_box(i * 16, i * 16, 512 - i * 32, 512 - i * 32, x68_rgb(255, 255 - i * 30, 0));
}
x68_screen_flip();
}
入れ子の枠を描いています。
w と h は「右端・下端の座標」ではなく「幅・高さ」です。
関連 x68_box_fill
x68_box_fill四角を塗りつぶす
void x68_box_fill(int x, int y, int w, int h, int color)
左上が (x, y)、幅 w、高さ h の四角形を塗りつぶします。
この環境でいちばん速い描画です。自機・敵・ブロック・棒グラフなど、たいていの物はこれで作れます。
x左上の横位置。
y左上の縦位置。
w幅(ドット)。0 以下なら何も描きません。
h高さ(ドット)。0 以下なら何も描きません。
color塗る色。
例
#include "x68.h"
void main(void) {
int i;
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
for (i = 0; i < 16; i++) {
x68_box_fill(i * 32, 100, 32, 300, x68_rgb(i * 16, 0, 255 - i * 16));
}
x68_screen_flip();
}
色の帯を 16 本並べています。
画面からはみ出す分は切り取られます。はみ出しを自分で計算する必要はありません。
関連 x68_box ・x68_cls
x68_circle円の輪郭を描く
void x68_circle(int x, int y, int r, int color)
中心 (x, y)、半径 r の円を、太さ 1 ドットの輪郭で描きます。中は塗りません。
x中心の横位置。
y中心の縦位置。
r半径(ドット)。0 以下なら何も描きません。
color線の色。
例
#include "x68.h"
void main(void) {
int r;
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
for (r = 16; r < 256; r += 16) {
x68_circle(256, 256, r, x68_rgb(0, 255, 128));
}
x68_screen_flip();
}
同心円を描いています。
塗りつぶした円を描く関数はありません。半径を 1 ずつ変えて何重にも描くと塗りつぶせます(少し遅くなります)。
関連 x68_box_fill
色を作る
色は赤・緑・青の3つの強さ(0〜255)から作ります。作った色の値を、描画関数の color に渡します。
x68_rgb赤・緑・青の強さから色を作る
int x68_rgb(int r, int g, int b)
赤・緑・青をそれぞれ 0〜255 の強さで指定して、描画関数に渡せる色の値を作ります。0 が最も暗く、255 が最も明るい値です。
画面が実際に区別できるのは各色 32 段階なので、たとえば 100 と 103 のような近い値は同じ色になります。
r赤の強さ(0〜255)。範囲外は 0 か 255 に丸められます。
g緑の強さ(0〜255)。
b青の強さ(0〜255)。
返り値 描画関数の color に渡す色の値。
例
#include "x68.h"
void main(void) {
int black = x68_rgb(0, 0, 0);
int white = x68_rgb(255, 255, 255);
int red = x68_rgb(255, 0, 0);
x68_screen_open();
x68_cls(black);
x68_box_fill(100, 100, 300, 100, red);
x68_box_fill(100, 300, 300, 100, white);
x68_screen_flip();
}
よく使う色をあらかじめ変数に入れておくと読みやすくなります。毎フレーム呼んでも構いませんが、名前が付くぶん変数にする利点が大きいです。
色の値をそのまま数字で書く(0xF81F など)こともできますが、並び順が独特なので x68_rgb() を使うのが安全です。
関連 X68_SCREEN_W
X68_SCREEN_W画面の幅(512)
#define X68_SCREEN_W 512
画面の横のドット数です。使える x 座標は 0 〜 X68_SCREEN_W - 1(0〜511)。
画面の端を扱うときは 512 と直接書かず、この名前を使っておくと後で見返したときに意図が分かります。
例
#include "x68.h"
void main(void) {
int x = 0;
int dx = 3;
int w = 32;
x68_screen_open();
for (;;) {
x68_cls(x68_rgb(0, 0, 0));
x += dx;
if (x < 0 || x + w > X68_SCREEN_W) dx = -dx;
x68_box_fill(x, 240, w, 32, x68_rgb(255, 255, 0));
x68_screen_flip();
}
}
画面の右端で跳ね返る処理です。
関連 X68_SCREEN_H
X68_SCREEN_H画面の高さ(512)
#define X68_SCREEN_H 512
画面の縦のドット数です。使える y 座標は 0 〜 X68_SCREEN_H - 1(0〜511)。
X68000 の画面は横長ですが、このライブラリが使うモードでは縦横とも 512 ドットの正方形です。
例
#include "x68.h"
void main(void) {
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
x68_line(X68_SCREEN_W / 2, 0, X68_SCREEN_W / 2, X68_SCREEN_H - 1, x68_rgb(64, 64, 64));
x68_line(0, X68_SCREEN_H / 2, X68_SCREEN_W - 1, X68_SCREEN_H / 2, x68_rgb(64, 64, 64));
x68_screen_flip();
}
画面のちょうど真ん中に印を描きます。
関連 X68_SCREEN_W
キーボードを読む
キーボードは「今このキーが押されているか」を聞く形で読みます。押した瞬間だけ反応させたいときは、前のフレームの状態を自分で覚えておいて比べます。
x68_key_downそのキーが今押されているかを調べる
int x68_key_down(int key)
キーが押されている「間ずっと」真を返します。押した瞬間だけ・離した瞬間だけを取りたい場合は、前のフレームの状態を変数に覚えておいて、変わったかどうかを自分で比べます。
複数のキーを同時に調べられるので、斜め移動もそのまま書けます。
key調べたいキー。X68_KEY_ で始まる定数(下の表)を渡します。
返り値 押されていれば 0 以外、押されていなければ 0。知らないキー番号を渡しても落ちず、0 を返します。
例
#include "x68.h"
void main(void) {
int x = 240;
int y = 240;
x68_screen_open();
for (;;) {
x68_cls(x68_rgb(0, 0, 0));
if (x68_key_down(X68_KEY_LEFT)) x -= 4;
if (x68_key_down(X68_KEY_RIGHT)) x += 4;
if (x68_key_down(X68_KEY_UP)) y -= 4;
if (x68_key_down(X68_KEY_DOWN)) y += 4;
x68_box_fill(x, y, 32, 32, x68_rgb(0, 255, 255));
x68_screen_flip();
}
}
カーソルキーで四角を動かします。上下左右を別々に調べているので、斜めにも動きます。
押しっぱなしでも毎フレーム真になります。「1 回押したら 1 回だけ」にしたいときは前回の状態と比べてください。
この関数を呼んでもキー入力は溜まりません。x68_screen_flip() のタイミングで 1 回読む、という書き方が基本です。
関連 X68_KEY_LEFT
X68_KEY_LEFTキーを表す定数の一覧
#define X68_KEY_LEFT 0x3b /* ほか X68_KEY_* 定数 */
x68_key_down() に渡す定数です。値は X68000 のキーボードが実際に送るコード(スキャンコード)で、実測して確かめたものです。
ここに無いキーも、コードさえ分かれば数値を直接渡せば読めます。
定数 キー
移動・操作
X68_KEY_LEFT← カーソル左
X68_KEY_UP↑ カーソル上
X68_KEY_RIGHT→ カーソル右
X68_KEY_DOWN↓ カーソル下
X68_KEY_SPACEスペース
X68_KEY_ENTERリターン
X68_KEY_ESCESC
数字
X68_KEY_0 〜 X68_KEY_90 〜 9(最上段の数字キー)
アルファベット
X68_KEY_A 〜 X68_KEY_ZA 〜 Z
例
#include "x68.h"
void main(void) {
int previous = 0;
int count = 0;
x68_screen_open();
for (;;) {
int current = x68_key_down(X68_KEY_SPACE);
if (current && !previous) {
count++;
x68_locate(0, 0);
printf("COUNT=%d", count);
}
previous = current;
x68_screen_flip();
}
}
スペースキーを「押した瞬間」だけ拾う書き方です。押しっぱなしでは 1 回しか数えません。
関連 x68_key_down
文字を出す
文字はグラフィック画面とは別の「テキスト画面」に出ます。絵の上に重ねて出るので、点数やデバッグ表示にそのまま使えます。
printf書式を指定して文字を出す
int printf(const char *fmt, ...)
C でおなじみの printf です。文字列の中の %d などの場所に、後ろに並べた値が埋め込まれて表示されます。
この環境の printf は自前の小さな実装なので、使える書式は下の表のものだけです。表に無い書式を書くと、その場所に [BADFMT] と表示されます(黙って無視したり、変な値を出したりはしません)。
1 回の出力は 256 バイトまでです。超えた分は切り捨てられます(落ちはしません)。
fmt書式を含む文字列。改行したいところには \n を書きます。
...書式に埋め込む値。左から順に対応します。
返り値 出力した文字数。
書式 意味
使える書式
%dint を 10 進数で(符号つき)
%uunsigned int を 10 進数で(符号なし)
%xunsigned int を 16 進数で(小文字 a〜f)
%c1 文字として
%s文字列として
%%% の文字そのもの
使えない書式([BADFMT] と表示されます)
%f %e %g小数。この環境に小数の表示はありません
%3d %02x %.2s桁数・0 埋め・精度の指定
%ld %lulong などの長さ指定
%q など上に無い文字
例
#include "x68.h"
void main(void) {
int score = 1200;
int x = 100;
x68_screen_open();
x68_locate(0, 0);
printf("SCORE=%d\n", score);
printf("X=%d HEX=%x\n", x, x);
x68_screen_flip();
}
点数と座標を表示しています。%d が 2 つあるので、値も 2 つ渡します。
%3d のような桁揃えは使えません。桁を揃えたいときは、自分で空白を足すか x68_locate() で位置を決めてください。
小数は扱えません。1.5 倍したいときは 10 倍した整数で計算する、といった工夫をします。
関連 puts ・x68_locate
puts文字列を出して改行する
int puts(const char *s)
文字列をそのまま表示し、最後に必ず改行を 1 つ足します(本物の C の puts と同じ動きです)。
書式が要らないときは printf より短く書けます。
s表示する文字列。
返り値 常に 0。
例
#include "x68.h"
void main(void) {
x68_screen_open();
puts("SPROUT68K");
puts("READY.");
puts("PRESS SPACE");
x68_screen_flip();
}
3 行表示します。改行は puts が自動で入れるので \n は書きません。
改行が必ず付きます。付けたくない場合は printf("%s", s) を使ってください。
関連 printf
x68_locate次に文字を出す位置を決める
void x68_locate(int col, int row)
printf や puts が次に文字を出す位置を指定します。文字の画面は横 96 桁 × 縦 32 行です(グラフィックの座標とは別の数え方です)。
毎フレーム同じ場所に点数を出したいときは、描く前にここで位置を戻します。
col桁(横位置)。0 が左端、95 が右端。
row行(縦位置)。0 が最上段、31 が最下段。
例
#include "x68.h"
void main(void) {
int score = 0;
x68_screen_open();
for (;;) {
x68_cls(x68_rgb(0, 0, 48));
score++;
x68_locate(70, 1);
printf("SCORE %d", score);
x68_screen_flip();
}
}
画面の右上に点数を出し続けます。毎フレーム同じ位置に戻すので、行が流れていきません。
画面の外の位置を渡しても何も起きません(落ちません)。
文字はグラフィックの上に重なって出ます。x68_cls() では消えないので、上書きするか同じ位置に出し直します。
関連 printf ・puts
数と乱数
計算まわりの小さな道具です。敵の出現位置をばらけさせたいときなどに乱数を使います。
srand乱数の出方を決める
void srand(unsigned int seed)
乱数の並びの出発点を決めます。同じ seed を渡せば、毎回まったく同じ順番の乱数が出ます。
そのため、テスト中は固定の値を渡して「毎回同じ動き」にしておくと、不具合を追いやすくなります。
seed乱数の出発点となる数。
例
#include "x68.h"
void main(void) {
int i;
x68_screen_open();
srand(1234);
for (i = 0; i < 5; i++) {
printf("%d ", x68_rand_int(100));
}
x68_screen_flip();
}
同じ seed からは同じ並びが出ます。この例は実行のたびに同じ 5 つの数を表示します。
呼ばなくても乱数は使えますが、その場合は毎回同じ並びになります。
この環境には時計がありません。「毎回違う」にしたいときは、タイトル画面でキーが押されるまでに回った回数を seed にする、といった工夫をします。
関連 rand ・x68_rand_int
rand乱数を 1 つ取り出す
int rand(void)
0 〜 X68_RAND_MAX (32767)の範囲の値を返します。範囲を決めたい場合は、割り算をするより x68_rand_int() のほうが安全で読みやすいです。
返り値 0 〜 X68_RAND_MAX の値。
例
#include "x68.h"
void main(void) {
x68_screen_open();
printf("%d\n", rand());
printf("MAX=%d\n", X68_RAND_MAX);
x68_screen_flip();
}
そのままの乱数を表示します。範囲を絞りたいときは x68_rand_int() を使ってください。
暗号やくじ引きのような、予測されて困る用途には使えない簡単な方式です。
関連 x68_rand_int ・srand
x68_rand_int0 〜 n-1 の乱数を取り出す
int x68_rand_int(int n)
欲しい範囲を直接指定できる乱数です。たとえば x68_rand_int (512) なら、画面のどこかの座標として使える 0〜511 が返ります。
n欲しい範囲の大きさ。0 以下を渡した場合は 0 を返します(0 で割って落ちることはありません)。
返り値 0 以上 n 未満の値。
例
#include "x68.h"
void main(void) {
int i;
x68_screen_open();
x68_cls(x68_rgb(0, 0, 0));
for (i = 0; i < 50; i++) {
x68_box_fill(x68_rand_int(X68_SCREEN_W), x68_rand_int(X68_SCREEN_H), 24, 24,
x68_rgb(x68_rand_int(256), x68_rand_int(256), x68_rand_int(256)));
}
x68_screen_flip();
}
ランダムな位置・ランダムな色の四角を 50 個ばらまきます。
関連 rand ・srand
X68_RAND_MAXrand() が返す最大値(32767)
#define X68_RAND_MAX 0x7fff
rand() が返しうるいちばん大きい値です。
例
#include "x68.h"
void main(void) {
int value = rand() * 100 / (X68_RAND_MAX + 1);
x68_screen_open();
printf("%d", value);
x68_screen_flip();
}
乱数を 0〜99 の範囲に直しています(この計算をしてくれるのが x68_rand_int() です)。
関連 rand
abs符号を取って絶対値にする
int abs(int n)
負の数なら正にして返します。2 点の距離を測るときなどに使います。
n対象の値。
返り値 n の絶対値。
例
#include "x68.h"
void main(void) {
int ax = 100;
int bx = 130;
x68_screen_open();
if (abs(ax - bx) < 40) {
printf("NEAR");
} else {
printf("FAR");
}
x68_screen_flip();
}
2 つの四角が近づいたかどうかを、中心の距離で判定しています。
int で表せる最小の値 -2147483648 だけは、符号を反転した 2147483648 が int に収まらないため、負のまま返ります。C 言語の仕様でそうなる場所で、パソコン側の C コンパイラでも同じ結果になります。
メモリと文字列
C の標準ライブラリでおなじみの関数のうち、この環境で使えるものです。名前と使い方は本物の C と同じです。
memcpyメモリの内容をまとめて複製する
void *memcpy(void *dst, const void *src, unsigned long n)
src から dst へ n バイトぶんそのまま複製します。配列まるごとのコピーに使います。
dst複製先。
src複製元。
n複製するバイト数。
返り値 dst をそのまま返します。
例
#include "x68.h"
void main(void) {
int source[4];
int copy[4];
int i;
for (i = 0; i < 4; i++) source[i] = i * 10;
memcpy(copy, source, sizeof(source));
x68_screen_open();
printf("%d %d", copy[0], copy[3]);
x68_screen_flip();
}
配列を丸ごと複製しています。sizeof を使うと、要素数を変えても書き直さずに済みます。
複製元と複製先が重なっている場合の動きは保証しません(重なるときは自分でループを書いてください)。
関連 memset
memsetメモリを同じ値で埋める
void *memset(void *dst, int c, unsigned long n)
dst から n バイトぶんを、値 c で埋めます。配列を 0 で初期化するのに使うのが定番です。
dst埋める先。
c埋める値(1 バイトぶんだけ使われます)。
n埋めるバイト数。
返り値 dst をそのまま返します。
例
#include "x68.h"
void main(void) {
char alive[40];
memset(alive, 0, sizeof(alive));
x68_screen_open();
printf("%d", alive[10]);
x68_screen_flip();
}
ブロックの生存フラグをまとめて 0 に戻しています。ゲームをやり直すときの初期化に便利です。
int の配列を 1 で埋めると、各要素は 1 ではなく 0x01010101 になります。0 埋め以外は 1 バイトの配列に使うのが安全です。
関連 memcpy
strlen文字列の長さを数える
unsigned long strlen(const char *s)
文字列の文字数を返します(終端の 0 は数えません)。
s対象の文字列。
返り値 文字数。
例
#include "x68.h"
void main(void) {
const char *title = "SPROUT68K";
int col = (96 - (int)strlen(title)) / 2;
x68_screen_open();
x68_locate(col, 10);
puts(title);
x68_screen_flip();
}
文字列の長さから、画面の中央に来る桁位置を計算しています。
返り値は unsigned long です。引き算に使うときは (int) を付けて int にしておくと、負の数になったときの事故を防げます。
関連 printf
生の層(上級・X68000 を直接触る)
ここから下は、X68000 のハードウェアや IOCS(本体 ROM の機能)をそのまま呼ぶ関数です。上の関数と違って、範囲チェックも安全策もありません。渡す値を間違えるとプログラムが止まります。最初のうちは読み飛ばして構いません。
x68_vsync_wait画面の書き換えが一巡するまで待つ
void x68_vsync_wait(void)
垂直同期(表示期間から帰線期間へ移る瞬間)を 1 回待ちます。x68_screen_flip() が内部で呼んでいるのと同じものです。
グラフィック画面を使わず、文字だけのプログラムで速度を揃えたいときに使えます。
例
#include "x68.h"
void main(void) {
int i;
for (i = 0; i < 60; i++) {
x68_vsync_wait();
x68_locate(0, 0);
printf("FRAME %d ", i);
}
}
1 秒あたりおよそ 55〜60 回のペースで数字を数えます。
関連 x68_screen_flip
x68_iocs_printIOCS を直接呼んで文字列を出す
void x68_iocs_print(const char *msg)
本体 ROM の機能(IOCS $21)をそのまま呼びます。printf や puts はこの上に作られています。
書式の解釈も改行の追加もしません。渡した文字列をそのまま出します。
msg0 で終わる文字列。
例
#include "x68.h"
void main(void) {
x68_iocs_print("RAW OUTPUT");
}
printf を通さずに直接出しています。
関連 puts
x68_iocs_locateIOCS を直接呼んで表示位置を決める
void x68_iocs_locate(int col, int row)
本体 ROM の機能(IOCS $23)をそのまま呼びます。x68_locate() はこれをそのまま呼び直しているだけなので、動きは同じです。
col桁(0〜95)。
row行(0〜31)。
例
#include "x68.h"
void main(void) {
x68_iocs_locate(10, 5);
x68_iocs_print("HERE");
}
位置を決めてから直接出力しています。
関連 x68_locate
x68_iocs_bitsnsキーボードの押下状態を 8 キーまとめて読む
unsigned char x68_iocs_bitsns(unsigned long group)
本体 ROM の機能(IOCS $04)をそのまま呼びます。キーは 8 個ずつの組に分かれていて、組の番号(0〜15)を渡すと、その 8 個ぶんの押下状態がビットで返ります。
x68_key_down() はこれを使って 1 キーぶんだけ取り出しています。同時押しを大量に調べるときは、こちらのほうが呼び出し回数を減らせます。
groupキーの組の番号(0〜15)。スキャンコードを 8 で割った商が組の番号です。
返り値 8 キーぶんの押下ビット(1 が押されている状態)。
例
#include "x68.h"
void main(void) {
for (;;) {
x68_locate(0, 0);
printf("GROUP7=%x ", x68_iocs_bitsns(X68_KEY_LEFT / 8));
x68_vsync_wait();
}
}
カーソルキーが含まれる組を丸ごと読み、16 進数で表示しています。
どのビットがどのキーかは、スキャンコードを 8 で割った余りで決まります。
関連 x68_key_down
x68_gvram_mode_65536_1page65536 色 1 ページのグラフィック画面に切り替える
void x68_gvram_mode_65536_1page(void)
画面のモードだけを切り替えます。裏バッファの用意はしません(それをするのが x68_screen_open() です)。
自分で GVRAM($00C00000 から、1 ライン 512 ワード)を直接書きたい場合に使います。
例
#include "x68.h"
void main(void) {
x68_vu16 *gvram = (x68_vu16 *)0x00C00000UL;
int i;
x68_gvram_mode_65536_1page();
for (i = 0; i < 512; i++) {
gvram[i * 512 + i] = 0xFFFF;
}
}
モードだけ切り替えて、GVRAM に直接書き込んでいます。裏バッファも範囲チェックもありません。
関連 x68_screen_open
x68_gvram_copy_movemメインメモリから GVRAM へ高速に転送する
void x68_gvram_copy_movem(void *dst, const void *src, unsigned long batch_count)
MOVEM 命令を使った、この環境でいちばん速い転送です。x68_screen_flip() が内部で使っています。
転送量の単位はバイトではなく「32 バイトのまとまりの個数」です。
dst転送先(GVRAM など)。
src転送元(メインメモリ)。
batch_count32 バイト単位の個数。バイト数ではありません。
例
#include "x68.h"
void main(void) {
static unsigned short pattern[512];
int i;
for (i = 0; i < 512; i++) pattern[i] = 0x07E0;
x68_gvram_mode_65536_1page();
x68_gvram_copy_movem((void *)0x00C00000UL, pattern, 1024 / 32);
}
1024 バイト(32 バイト × 32 個)を GVRAM の先頭へ転送しています。
バイト数を渡すと 32 倍の量を転送してしまい、画面の外まで壊します。
転送先・転送元とも 4 バイト境界に揃っている必要があります。
関連 x68_screen_flip
x68_iocs_disk_readフロッピーディスクから直接読み込む
long x68_iocs_disk_read(unsigned long d1, unsigned long d2, unsigned long byte_count, void *dst)
本体 ROM の機能(IOCS $46)をそのまま呼びます。プログラム本体より後ろのセクタに置いた画像や面データを、実行中に読み込むために使います。
引数はレジスタに入る値そのままの形です。d1 は装置とモード、d2 はトラック・面・セクタの位置を 1 つの数に詰めた値です。
d1装置番号 << 8 | モード。1 台目のフロッピーからの通常読み込みは 0x00009070。
d2セクタ長 << 24 | トラック << 16 | 面 << 8 | セクタ。セクタ長 3 が 1024 バイト。トラックと面は 0 から、セクタは 1 から数えます。
byte_count読み込むバイト数。
dst読み込み先のメモリ。
返り値 IOCS の戻り値。成功していても 0 以外が返ることがあるので、-1(0xFFFFFFFF)のときだけ失敗と判定してください。
例
#include "x68.h"
void main(void) {
static unsigned char buffer[1024];
long result = x68_iocs_disk_read(0x00009070UL, (3UL << 24) | (30UL << 16) | (0UL << 8) | 1UL,
sizeof(buffer), buffer);
x68_screen_open();
if (result == -1) {
puts("DISK ERROR");
} else {
printf("FIRST=%x", buffer[0]);
}
x68_screen_flip();
}
トラック 30・面 0・セクタ 1 から 1024 バイト読み込みます。判定は「-1 のときだけ失敗」です。
戻り値が 0 以外でも成功していることがあります。0 かどうかで判定すると、正常なのに失敗として扱ってしまいます。
プログラム本体が置かれているセクタを読み書きしないよう、置き場所には十分な余裕を取ってください。