全259件 (259件中 1-50件目)

当初は、横1ドット×縦22ドットで描画するようにしていましたが水ゲージと合わせて仕様変更します。 River Patrol GOAL METERの実装 | ZX81の回想禄 - 楽天ブログーーーーーscrn.cpp横2ドット×縦48ドットとして、96ドット分でゴールまでの距離を描画するように修正。int goal_level = 0;int goal_count = 0; void goal_meter_init(void){ unsigned char *p; int i; goal_level = 96; goal_count = 2; p = getscrnaddr(0, 2); for (i = 0; i <= 24; i++) { *p = 0x80; p += D_DX; } } void goal_meter_draw(int dy){ unsigned char goal_ptn[4] = {0x00, 0x02, 0x03, 0x84, 0x80}; int y; int ptnno; unsigned char *p, c; int row, step; if (dy < 0) { step = -dy; if (goal_level == 0) { return; } if (goal_count > step) { goal_count -= step; return; } goal_count += 10 - step; goal_level--; row = goal_level >> 2; ptnno = goal_level & 0x03; } else if (dy > 0) { step = dy; goal_count += step; if (goal_count < 10) { return; } goal_count -= 10; goal_level++; row = goal_level >> 2; if (goal_level & 0x03) { ptnno = goal_level & 0x03; } else { if (row < 1) { return; } row --; ptnno = 4; } } else { return; } p = getscrnaddr(row, 2); *p = goal_ptn[ptnno];}水ゲージ、ゴールメーター共に、文字の左に描画します。
2026.07.27
コメント(0)
以前の記事で、アーケードは水ゲージの記事に誤りがありました。 River Patrol 水ゲージの実装➀ | ZX81の回想禄 - 楽天ブログ 誤: 1秒で1ドット減り、200秒(3分20秒)がリミット 正:0.5秒で1ドット減り、 96秒(1分36秒)がリミット 訂正しておきます。ーーーーーscrn.c上記の通りに時間あたりの消費量に計算しなおし、さらにはワニによる水ゲージの減る量を考慮してパラメータにwater_stepを追加して完成です。int water_level = 0;int water_count = 0; void water_gauge_init(void){ unsigned char *p; int row; water_level = 96; water_count = 50; p = getscrnaddr(0, 0); for (row = 0; row <= 24; row++) { *p = 0x80; p += D_DX; }} void water_gauge_draw(int water_step){ unsigned char ptn[4] = {0x00, 0x02, 0x03, 0x84}; int ptnno; unsigned char *p; int row; if (water_level == 0) { return; } if (water_count > water_step) { water_count -= water_step; return; } water_count += 50 - water_step; water_level--; row = water_level >> 2; ptnno = water_level & 0x03; p = getscrnaddr(row, 0); *p = ptn[ptnno];}
2026.07.26
コメント(0)

96から0に減算した時の表示行(row)とパターン(文字コード)を下記としました。そして、毎回1ドット減るようにテスト関数を作成しました。ーーーーーscrn.cint water_level = 0; void water_gauge_init(void){ unsigned char *p; int row; water_level = 96; p = getscrnaddr(0, 0); for (row = 0; row <= 24; row++) { *p = 0x80; p += D_DX; } } void water_gauge_draw(void){ unsigned char ptn[4] = {0x00, 0x02, 0x03, 0x84}; int ptnno; unsigned char *p; int row; if (water_level == 0) { return; } water_level--; row = water_level >> 2; ptnno = water_level & 0x03; p = getscrnaddr(row, 0); *p = ptn[ptnno];}main.cのループにwater_gauge_draw()を追加してテストすると、こんな感じで減っていきます。うん、いい感じ。
2026.07.25
コメント(0)

当初の画面設計では、レイアウトの関係から水ゲージは縦44ドットの予定でした。しかし、これだと実物の200ドットとの差がありすぎて水の減り具合が把握しにくいと思い始めました。では、どうするかというとRally-Xの時と同じように横にもう1ドット増やそうと思います。それなら、バランスを取ってゴールメーターも同様に増やさないと。しかし、そうなると川の端とくっつくので。。。文字とゲージを入れ替える作戦にします。それが、こちらま、まあ枠が太くなりスッキリ感はなくなりましたがこれで縦24×4ドット=96ドット表現が可能となり2秒に1ドット減る事にすれば、そんなに違和感を感じないかと思います。うん、これでいってみよう。
2026.07.24
コメント(0)
水ゲージの実装を検討します。※7/27 高さ192ドット、1ドット0.5秒に訂正アーケード版は256(W)×224(H)のサイズで、水ゲージは縦に192ドット分あり、それを0.5秒ごとに減らしている様子。そのままだと0.5秒で1ドット減り、96秒(1分36秒)がリミット。そして、ワニに触れるたびに1秒に2ドット、3ドットと減らす量が増えている。計算だと、このようにゲージの秒数が変わっていく。 step1(未接触)=96秒 step2(1接触)=80秒 step3(2接触)=64秒 step4(3接触)=48秒 :直進し続けるとゴールまで約50秒なので3接触までは許される設計となっている様子。さて、ドット数の少ないZX81でどのように実装すべきか。。。
2026.07.21
コメント(0)
1面が一通り実装できましたのでテスト動画としてアップしました。 RiverPatrol test5速度が遅いのと、オブジェクトが左右に移動しない水ゲージやスコア関連などなど、まだ未実装ではありますが全体的な動きを把握できるようになりました。
2026.07.16
コメント(0)

水ゲージの追加に合わせて、スコア表示、残機表示などの準備をします。そのため、スクリーン表示関係を行うscrn.hとscrn.cを追加しました。ーーーーーscrn.hmap.hからスクリーン表示関係を移動し更に water gauge処理を追加/* * ZX81 Screen Handling Module header */ #ifndef _ZX81_SCRN_H#define _ZX81_SCRN_H // screen address#define D_FILE 0x400c#define D_DX 33 // 32+1(CR)#define D_DY 24 // screen functionsvoid scrn_init();unsigned char* getscrnaddr(int posy, int posx); // zx81 writevoid zx81_write_str(unsigned char *dstp, const char *asciip, int step);void zx81_write_num(unsigned char *dstp, int num, int ndigit); // goal metervoid goal_meter_init(void);void goal_meter_draw(int dy); // water gaugevoid water_gauge_init(void);void water_gauge_draw(void); #endif // _ZX81_SCRN_Hーーーーーscrn.cscrn.hと同様に、map.cからスクリーン表示関係を移動し更に water gauge処理を追加しました。あとは縦の文字描画も必要となったため、zx81_write_strにstepを追加して、step=1なら横方向step=D_DX(33)なら縦方向の文字描画が可能となりました。水ゲージは位置だけを見たいので、最大量のバーを書いています。/* * ZX81 Screen Handling Module program */#include <stdio.h>#include <string.h> #include "scrn.h"#include "map.h" // ASCII to ZX81-character #define ASCII_BGN 32#define ASCII_END (ASCII_BGN + sizeof(zx81chr) - 1)unsigned char zx81chr[] = {// (32),!(33),"(34),#(35),$(36) 0x00, 0x00, 0x0b, 0x00, 0x0d,// %(37),&(38),'(39),((40),)(41) 0X00, 0X00, 0X00, 0x10, 0x11,// *(42),+(43),,(44),-(45),.(46) 0x17, 0x15, 0x1a, 0x16, 0x1b,// /(47),0(48),1(49),2(50),3(51) 0x18, 0x1c, 0x1d, 0x1e, 0x1f,// 4(52),5(53),6(54),7(55).8(56) 0x20, 0x21, 0x22, 0x23, 0x24,// 9(57),:(58),;(59),<(60),=(61) 0x25, 0x0e, 0x19, 0x13, 0x14,// >(62),?(63),@(64),A(65),B(66) 0x12, 0x0f, 0x0c, 0x26, 0x27,// C(67),D(68),E(69),F(70),G(71) 0x28, 0x29, 0x2a, 0x2b, 0x2c,// H(72),I(73),J(74),K(75),L(76) 0x2d, 0x2e, 0x2f, 0x30, 0x31,// M(77),N(78),O(79),P(80),Q(81) 0x32, 0x33, 0x34, 0x35, 0x36,// R(82),S(83),T(84),U(85),V(86) 0x37, 0x38, 0x39, 0x3a, 0x3b,// W(87),X(88),Y(89),Z(90) 0x3c, 0x3d, 0x3e, 0x3f};#define ZX81CHR_SIZE 59 // ----------------------------------------------------------// 初期化// ----------------------------------------------------------void scrn_init(void){ zx81_write_str(getscrnaddr(1, 27), "1UP", 1); zx81_write_num(getscrnaddr(3, 26), 0, 6); zx81_write_str(getscrnaddr(6, 27), "TOP", 1); zx81_write_num(getscrnaddr(8, 26), 0, 6); zx81_write_str(getscrnaddr(11, 26), "ROUNDS", 1);} #asm;----------------------------------------; HL = HL * 33;----------------------------------------_HL_MULT33: push de ld d, h ld e, l add hl, hl ; *2 add hl, hl ; *4 add hl, hl ; *8 add hl, hl ; *16 add hl, hl ; *32 add hl, de ; *33 pop de ret #endasm unsigned char* getscrnaddr(int posy, int posx){#asm ld ix, 2 add ix, sp push de ld d, (ix+2) ; d=posy ld e, (ix+0) ; e=posx call getscrnaddr_calc pop de ret getscrnaddr_calc:; IN: D=posy, E=posx; OUT: HL= screen address push bc ; HL = posy * 33 ld h, 0 ld l, d call _HL_MULT33 ; HL += posx ld b, 0 ld c, e ; c=posx add hl, bc ; HL += D_FILE ld bc, (D_FILE) inc bc add hl, bc pop bc ret #endasm} void zx81_write_str(unsigned char *dstp, const char *asciip, int step){ #asm ld ix,2 add ix,sp ld e, (ix+0) ld d, (ix+1) ; step ld c, (ix+2) ld b, (ix+3) ; asciip ld l, (ix+4) ld h, (ix+5) ; dstp zx81_write_str_loop: ld a,(bc) cp 0 ret z push de sub ASCII_BGN jp c,zx81_write_str_2 cp ZX81CHR_SIZE ;sizeof(zx81chr) jp nc,zx81_write_str_2 ld de,_zx81chr add a,e jp nc,zx81_write_str_1 inc dzx81_write_str_1: ld e,a ld a,(de) jp zx81_write_str_3 zx81_write_str_2: ld a,0zx81_write_str_3: ld (hl),a pop de inc bc add hl,de jp zx81_write_str_loop #endasm} void zx81_write_num(unsigned char *dstp, int num, int ndigit){ unsigned char ascii[16+1]; int i, n; memset(ascii, 0, sizeof(ascii)); if (ndigit > 16) { return; } i = ndigit-1; while (i >= 0) { n = num % 10; num /= 10; if (num != 0) { ascii[i] = '0' + n; } else { ascii[i] = ((n != 0) || (i == ndigit-1)) ? '0' + n : ' '; } i--; } zx81_write_str(dstp, ascii, 1);} void goal_meter_init(void){ unsigned char *p; int i; zx81_write_str(getscrnaddr(6, 2), "GOAL METER", D_DX); p = getscrnaddr(1, 3); for (i = 1; i <= 22; i++) { *p = 0x05; p += D_DX; } } void goal_meter_draw(int dy){ int y; unsigned char *p, c; if (dy < 0) {// if (map_scroll_y == 0) return; y = map_scroll_y / 22; if (y & 1) { c = 0x01; } else { c = 0x00; } p = getscrnaddr(1 + (y >> 1), 3); *p = c;/*zx_setcursorpos(0,0);printf("%d%d", y, 1+(y>>1));zx_setcursorpos(1,0);printf("%d", c);*/ } else if (dy > 0) {// if (map_scroll_y == 1000-48) return; y = map_scroll_y / 22; if (y & 1) { c = 0x01; } else { if (y == 0) { return; } y -= 2; c = 0x05; } p = getscrnaddr(1 + (y >> 1), 3); *p = c; }} void water_gauge_init(void){ unsigned char *p; int i; zx81_write_str(getscrnaddr(6, 0), "WATER GAUGE", D_DX); p = getscrnaddr(1, 1); for (i = 1; i <= 22; i++) { *p = 0x05; p += D_DX; } } void water_gauge_draw(void){}画面はこんな感じです。
2026.07.15
コメント(0)

次はWATER GAGEの予定でしたが、マップとの割合がわからなかったので先に1面の岩マップとオブジェクトを確定する事にしました。以前、途中まで計画していたので、これをベースにします。 マップデータ管理 | ZX81の回想禄 - 楽天ブログマップデータは250行分です。1行は4ドットのブロックとなるため、全1000ドットとなります。
2026.07.15
コメント(0)
渦からの脱出処理を追加しました。ーーーーーmain.c回転する間にスペースキーを押している回数を数えます。正面を向いた時に1~3回だったら脱出できます。スペースキーの回数は、最終的に調整する予定です。void whirl_move_to_center(int act_no){ : unsigned char key; unsigned char cnt; int spin_frame; : cnt = 0; spin_frame = 0; while (1) { key = key_pressed(); if (key & KBIT_ENT) { return; } if (key & KBIT_SPC) { if (cnt < 0x0f) { cnt++; } } else { cnt = 0; } spin_frame++; if (spin_frame == 16) { spin_frame = 0; } // オブジェクト移動 objects_move(0); // 各種描画 map_render(); objects_render(RENDER_COLLISION); boat_render_spin(spin_frame); objects_render(RENDER_CONTACT); // 全体描画 map_draw(); if (spin_frame == 0) { if ((cnt > 0) && (cnt < 4)) { break; } } }}次は、WATER GAGEの予定です。
2026.07.10
コメント(0)
渦の中心に移動する処理を追加しました。ーーーーーmain.c船の回転イメージはY座標の偶数位置のパターンしかないため、まずは偶数位置へ調整。その後、船と渦のドット差を計算してXの差分の分は船の方を、Yの差分の分はマップ全体を1ドットずつ移動させています。void whirl_move_to_center(int act_no){ int obj_w = 9; // 5*2 -1 int obj_h = 10; // 5*2 -1 int obj_left = active_tbl[act_no].x; int obj_top = active_tbl[act_no].y - obj_h; int dx, dy; // 偶数位置しか回転パターンが無いため // 奇数の場合は上に移動し、傾きはセンターに boat_tilt = TILT_CENTER; while (boat_y & 0x01) { // 自船移動 boat_move(KBIT_UP); } dy = 0; dx = 0; int diffy = obj_top - boat_y; int diffx = obj_left - boat_x; if (diffy > 0) dy = 1; else if (diffy < 0) dy = -1; if (diffx > 0) dx = 1; else if (diffx < 0) dx = -1; while ((dx != 0) || (dy != 0)) { map_scroll(dy); goal_meter_draw(dy); // オブジェクト移動 objects_move(dy); // オブジェクト出現 objects_spawn(map_scroll_y); // 各種描画 map_render(); objects_render(RENDER_COLLISION); boat_render_normal(); objects_render(RENDER_CONTACT); // 全体描画 map_draw(); if (diffy != 0) { diffy -= dy; } if (diffy == 0) { dy = 0; } if (diffx != 0) { diffx -= dx; boat_x += dx; } if (diffx == 0) { dx = 0; } } for (int spin_frame = 0; spin_frame < 16; spin_frame++) { // 各種描画 map_render(); objects_render(RENDER_COLLISION); boat_render_spin(spin_frame); objects_render(RENDER_CONTACT); // 全体描画 map_draw(); }}あとは、脱出処理です。
2026.07.08
コメント(0)
実際に描画する処理を仮実装しました。ーーーーーmain.c渦の中心に移動する関数を追加し、mainからの呼び出しとした。void whirl_move_to_center(int act_no){ int obj_w = 5*2 -1; int obj_h = 5*2 -1; int obj_left = active_tbl[act_no].x; int obj_top = active_tbl[act_no].y - obj_h; boat_x = obj_left; int dy = obj_top - boat_y; map_scroll(dy); goal_meter_draw(dy); // オブジェクト移動 objects_move(dy); // オブジェクト出現 objects_spawn(map_scroll_y); for (int spin_frame = 0; spin_frame < 16; spin_frame++) { // 各種描画 map_render(); objects_render(RENDER_COLLISION); objects_render(RENDER_CONTACT); boat_render_spin(spin_frame); // 全体描画 map_draw(); }}void main(){ : switch (active_tbl[act_no].type) { case OBJ_WHIRL: // 渦処理 whirl_move_to_center(act_no); break;ーーーーーboat.cboat_renderのパラメータに描画する文字と衝突判定有無とし通常のボートを描画するnomalと回転中のボートを描画するsipinに分けて実装// ----------------------------------------------------------// 描画と衝突判定// ----------------------------------------------------------int boat_render_normal(void){ unsigned char *chrp; chrp = &boat_ptn[boat_y & 1][boat_x & 1][boat_tilt][0]; return boat_render(chrp, 1);} int boat_render_spin(int spin_frame){ unsigned char *chrp; chrp = &boat_spin_ptn[spin_frame][0]; return boat_render(chrp, 0);} int boat_render(unsigned char *chrp, int set_collision){ unsigned char c; unsigned char *pos; unsigned char p;// unsigned char p1; int p1; int y, x; int collision = 0; pos = getrenderaddr(boat_y>>1, boat_x>>1); for (y = 0; y < 5; y++) :渦のセンターにボートが表示されて1回転して通常処理に戻ります。実際に渦と重ねると、少し見づらい気がします。あとは、タイミングよくスペースキーを押すと脱出したり引き寄せる途中を描画する処理を追加します。
2026.07.06
コメント(0)
渦に接近した時は下記の処理を想定しています。➀渦の中心に移動➁ボート回転➂真上付近でスペースを押すと脱出まずはボート回転のパターンを定義します。0番目が真上で、1増えると右に23.5度ずつ傾いていく感じで全16パターンを停止しました。回転パターンのイメージはこちらです。 River Patrol 船の360度回転 | ZX81の回想禄 - 楽天ブログunsigned char boat_spin_ptn[16][25] ={// 0 {0x00,0x87,0x82,0x00,0x00, 0x00,0x06,0x02,0x04,0x00, 0x00,0x82,0x86,0x05,0x00, 0x00,0x82,0x83,0x05,0x00, 0x00,0x00,0x01,0x00,0x00},// 1 {0x00,0x00,0x83,0x86,0x00, 0x00,0x06,0x00,0x85,0x00, 0x87,0x03,0x86,0x05,0x00, 0x02,0x80,0x06,0x00,0x00, 0x00,0x00,0x01,0x00,0x00},// 2 {0x00,0x00,0x87,0x83,0x00, 0x00,0x06,0x01,0x85,0x00, 0x81,0x03,0x05,0x05,0x00, 0x85,0x82,0x07,0x00,0x00, 0x00,0x02,0x00,0x00,0x00},// 3 {0x00,0x00,0x87,0x83,0x00, 0x00,0x81,0x01,0x87,0x01, 0x84,0x04,0x05,0x06,0x00, 0x02,0x82,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00},// 4 {0x00,0x00,0x00,0x00,0x00, 0x85,0x84,0x03,0x83,0x00, 0x84,0x87,0x01,0x83,0x01, 0x02,0x03,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00},// 5 {0x00,0x83,0x00,0x00,0x00, 0x85,0x05,0x07,0x04,0x00, 0x03,0x83,0x01,0x85,0x00, 0x00,0x02,0x86,0x83,0x01, 0x00,0x00,0x00,0x00,0x00},// 6 {0x87,0x81,0x04,0x00,0x00, 0x81,0x01,0x07,0x04,0x00, 0x02,0x07,0x01,0x86,0x00, 0x00,0x02,0x86,0x81,0x00, 0x00,0x00,0x00,0x00,0x00},// 7 {0x00,0x83,0x05,0x00,0x00, 0x85,0x03,0x85,0x04,0x00, 0x00,0x07,0x01,0x86,0x00, 0x00,0x02,0x83,0x85,0x00, 0x00,0x00,0x00,0x01,0x00},// 8 {0x00,0x83,0x82,0x04,0x00, 0x00,0x82,0x87,0x05,0x00, 0x00,0x05,0x01,0x05,0x00, 0x00,0x85,0x85,0x00,0x00, 0x00,0x00,0x01,0x00,0x00},// 9 {0x00,0x00,0x82,0x04,0x00, 0x00,0x81,0x02,0x84,0x00, 0x87,0x01,0x03,0x05,0x00, 0x85,0x87,0x06,0x00,0x00, 0x00,0x01,0x00,0x00,0x00},// 10 {0x00,0x00,0x81,0x83,0x00, 0x00,0x06,0x05,0x84,0x04, 0x87,0x01,0x03,0x07,0x00, 0x85,0x83,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00},// 11 {0x00,0x00,0x87,0x04,0x00, 0x00,0x06,0x05,0x80,0x00, 0x85,0x00,0x86,0x06,0x01, 0x86,0x83,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,0x00},// 12 {0x00,0x00,0x00,0x00,0x00, 0x87,0x06,0x84,0x84,0x00, 0x86,0x04,0x86,0x85,0x01, 0x00,0x02,0x03,0x03,0x00, 0x00,0x00,0x00,0x00,0x00},// 13 {0x87,0x83,0x00,0x00,0x00, 0x86,0x00,0x84,0x04,0x00, 0x02,0x04,0x05,0x81,0x01, 0x00,0x02,0x86,0x03,0x00, 0x00,0x00,0x00,0x00,0x00},// 14 {0x87,0x83,0x00,0x00,0x00, 0x85,0x00,0x03,0x04,0x00, 0x00,0x05,0x07,0x84,0x04, 0x00,0x02,0x82,0x80,0x00, 0x00,0x00,0x02,0x00,0x00},// 15 {0x87,0x86,0x04,0x00,0x00, 0x85,0x00,0x02,0x04,0x00, 0x00,0x82,0x03,0x86,0x00, 0x00,0x02,0x81,0x07,0x00, 0x00,0x00,0x01,0x00,0x00}};移動時はXとYの偶数と奇数それぞれのイメージを持ちましたがXとYの偶数のみとしました。
2026.07.03
コメント(0)
ワニに接触した時、ZX81は音を出せないので画面を少しブリンクさせます。こんな感じでしょうか。ーーーーーmain.c case OBJ_CROC: // ワニ処理 for (int i = 0; i < 5; i++) { zx_fast(); zx_slow(); for (int j = 0; j < 5; j++) { wait_vsync(); } } // 増水 break;
2026.06.30
コメント(0)

次に接触判定を元に漂流者処理を仮実装しました。以下追加点及び変更点ですmapには、描画する位置datp(VMEMかrender)を指定して文字または数字を描画する関数を追加。ーーーーーmap.h// zx81 writevoid zx81_write_str(unsigned char *dstp, const char *asciip);void zx81_write_num(unsigned char *dstp, int num, int ndigit);ーーーーーmap.cvoid zx81_write_str(unsigned char *dstp, const char *asciip){ #asm ld ix,2 add ix,sp ld c, (ix+0) ld b, (ix+1) ; asciip ld e, (ix+2) ld d, (ix+3) ; dstp zx81_write_str_loop: ld a,(bc) cp 0 ret z sub ASCII_BGN jp c,zx81_write_str_2 cp ZX81CHR_SIZE ;sizeof(zx81chr) jp nc,zx81_write_str_2 ld hl,_zx81chr add a,l jp nc,zx81_write_str_1 inc hzx81_write_str_1: ld l,a ld a,(hl) jp zx81_write_str_3 zx81_write_str_2: ld a,0zx81_write_str_3: ld (de),a inc bc inc de jp zx81_write_str_loop #endasm} void zx81_write_num(unsigned char *dstp, int num, int ndigit){ unsigned char ascii[16+1]; int i, n; memset(ascii, 0, sizeof(ascii)); if (ndigit > 16) { return; } i = ndigit-1; while (i >= 0) { n = num % 10; num /= 10; if (num != 0) { ascii[i] = '0' + n; } else { ascii[i] = ((n != 0) || (i == ndigit-1)) ? '0' + n : ' '; } i--; } zx81_write_str(dstp, ascii);}objetsには、ドクロ描画を追加。ーーーーーobjects.hvoid objects_draw_skull(int act_no);ーーーーーobjects.cunsigned char skull_chr[DRIFTER_CW*DRIFTER_CH] = { 0x06,0x07,0x04, 0x84,0x84,0x01, 0x00,0x00,0x00}; #define D_DX 33 // 32+1(CR)#define D_DY 24// ----------------------------------------------------------// ドクロ描画// ----------------------------------------------------------void objects_draw_skull(int act_no){ int obj_y, y, obj_x; unsigned char *chrp; unsigned char *pos; int chrw, chrh, dy; obj_y = (active_tbl[act_no].y >> 1) - active_tbl[act_no].h; obj_x = active_tbl[act_no].x >> 1; chrp = &skull_chr[0]; pos = getscrnaddr(obj_y, obj_x + 4); chrw = active_tbl[act_no].w; chrh = active_tbl[act_no].h; dy = obj_y; for (y = 0; y < chrh; y++) { memcpy(pos, chrp, chrw); pos += D_DX; chrp += chrw; dy++; }}mainは、漂流者処理を改造。ーーーーーmain.cvoid main(){ : case OBJ_DRIFTER: // 漂流者処理 if (active_tbl[act_no].contacted == 1) { // 得点表示 int y = active_tbl[act_no].y - (active_tbl[act_no].h * 2) - 1; int x = active_tbl[act_no].x; zx81_write_num(getscrnaddr((y >> 1) + 1, (x >> 1) + 4), 100, 3); } else { // ドクロ表示 objects_draw_skull(act_no); } active_tbl[act_no].type = OBJ_NONE; for (int i = 0; i < 100; i++) { wait_vsync(); } break; default:【正面衝突】正面からの衝突は3ドット幅としました。この赤線の間に船首が接触すると。。。ドクロ表示します。【救出】この赤線の以外で船首が接触すると。。。救出成功となり、得点を表示します。
2026.06.28
コメント(0)
衝突判定はtypeを元に判定予定でしたがよく考えるとactive_tblにアクセスするのでactive_tblの番号を返すようにしました。以下変更点です。ーーーーーmain.cobjects_contactの戻り値をtypeではなく番号を返すよう変更。また、objects_contact判定より前にmap_drawを行う事で見た目での描画と接触判定のずれをなくしました。void main(){ : // 衝突以外の描画 objects_render(RENDER_CONTACT); // 全体描画 map_draw(); // 接触判定 int act_no = objects_contact(); if (act_no < ACTIVE_MAX) { switch (active_tbl[act_no].type) { case OBJ_WHIRL: // 渦処理 break; case OBJ_CROC: // ワニ処理 break; case OBJ_DRIFTER: // 漂流者処理 break; default: break; } } // 衝突判定 if (collision == 1) { // 衝突処理// break; }}ーーーーーobjects.hactive_tblを他から参照可能としました。// active table#define ACTIVE_MAX 5 struct ACTIVE_TBL { int y; // 画面上のY座標 int x; // 画面上のX座標 int h; // 描画文字の高さ int w; // 描画文字の幅 int area; // w * h のキャッシュ unsigned char *p; // 描画文字のポインタ ObjectType type; // オブジェクト種別 int speed_step; // Y移動速度(0=移動しない、1=毎回, 2=2回に1回) int speed_cnt; // カウンタ(speed_y で初期化) int contacted; // 1度だけ接触判定するためのフラグ};extern struct ACTIVE_TBL active_tbl[ACTIVE_MAX];ーーーーーobjects.c接触判定時にactive_tblの番号を返すようにしました。// ----------------------------------------------------------// 接触// ----------------------------------------------------------int objects_contact(void){ int act_no; for (act_no = 0; act_no < ACTIVE_MAX; act_no++) { if (active_tbl[act_no].type > OBJ_RAFT) { if (active_tbl[act_no].contacted == 0) { active_tbl[act_no].contacted = boat_contact(&active_tbl[act_no]); if (active_tbl[act_no].contacted != 0) { return (act_no); } } } } return (act_no);}ーーーーーboat.hボートの接触判定関数を追加しました。int boat_contact(struct ACTIVE_TBL *active_tblp);ーーーーーboat.c追加したボートの接触判定関数にて、渡されたテーブルの座標と自船の座標を比較して接触判定を行っています。下記の値を返します。・0 … 接触なし・1 … 接触あり・2 … センターで接触(※)※船がセンター(傾いていない)時に オブジェクトと船のX座標センター差がー1、0、+1の時int boat_contact(struct ACTIVE_TBL *actp){ // オブジェクトの矩形(左下基準) int obj_w = (actp->w * 2)-1; int obj_h = (actp->h * 2)-1; int obj_left = actp->x; int obj_top = actp->y - obj_h; int obj_right = obj_left + obj_w - 1; int obj_bottom = obj_top + obj_h - 1; // ボートの矩形(左上基準) int boat_w = 5; int boat_h = 5; int boat_left = boat_x+2; int boat_top = boat_y+2; int boat_right = boat_left + boat_w - 1; int boat_bottom = boat_top + boat_h - 1; // 接触判定 if (boat_left <= obj_right && boat_right >= obj_left && boat_top <= obj_bottom && boat_bottom >= obj_top) { // X方向センター接触判定 int boat_cx = (boat_left + boat_right) / 2; int obj_cx = (obj_left + obj_right) / 2;// if ((abs(boat_cx - obj_cx) < 2) && int diff = boat_cx - (obj_cx - 1); if ((diff >= 0 && diff <= 2) && (boat_tilt == TILT_CENTER)) { return 2; // 直進状態でXセンター接触 } return 1; } return 0;}(absを使いたくなかったので、少し小細工をしています。)
2026.06.27
コメント(0)
衝突処理の次は接触判定に移ります。objects_renderにパラメータを追加して、衝突と接触に分けます。・RENDER_COLLISION …衝突用 敵船、ドラム缶、流木・RENDER_CONTACT … 接触用 渦、ワニ、漂流者RENDER_COLLISIONで描画後は、map_renderでの岩描画と同様に単純に自船との衝突判定にて衝突処理を実施します。RENDER_CONTACTで描画後は、各オブジェクトとの座標判定により自船との接触判定にて各接触判定を実施します。メインの処理は、こんな感じにしました。ーーーーーmain.c : while (1) { key = key_pressed(); if (key & KBIT_ENT) { break; } // 自船移動 dy = boat_move(key); // MAP、メーター更新 int advanced = map_scroll(dy); map_scroll(dy); goal_meter_draw(dy); // オブジェクト移動 objects_move(dy); // オブジェクト出現 if (advanced) { objects_spawn(map_last_row); } // 各種描画と衝突判定 map_render(); objects_render(RENDER_COLLISION); collision = boat_render(); // 衝突以外の描画と接触判定 objects_render(RENDER_CONTACT); type = objects_contact(); switch (type) { case OBJ_WHIRL: // 渦回転処理 break; case OBJ_CROC; // 漏水増加処理 break; case OBJ_DRIFTER; // 救助処理 break; } // 全体描画 map_draw(); // 衝突判定 if (collision == 1) { // 衝突処理 break; } }
2026.06.22
コメント(0)

衝突判定、若干変更しました。前回の処理に少し手を加えると、衝突した部分全部が’*’表示となります。ーーーーーboat.c// ----------------------------------------------------------// 描画と衝突判定// ----------------------------------------------------------int boat_render(void){ : // 衝突判定// if (collision == 0) …ここをコメント { if ((p ^ c) != p1) { collision = 1;// 衝突位置を仮に*に置き換えp = 0x17; } }こんな感じとなります。
2026.06.21
コメント(0)

以前から構想していたのですが自船の描画の再に背景MAPと合成している箇所に衝突判定を入れる事としました。ーーーーーboat.cboat_renderの戻り値をintにして、collision=0または1を返します。// ----------------------------------------------------------// 描画と衝突判定// ----------------------------------------------------------int boat_render(void){ unsigned char *chrp, c; unsigned char *pos; unsigned char p;// unsigned char p1; int p1; int y, x; int collision = 0; pos = getrenderaddr(boat_y>>1, boat_x>>1); chrp = &boat_ptn[boat_y & 0x01][boat_x & 0x01][boat_tilt][0]; for (y = 0; y < 5; y++) { for (x = 0; x < 5; x++) {// 左上0x01、右上0x02、左下0x04だが// 右下が含まれると0x80+0x07(左上、右下、左下反転)となるため// 合成時に0x80があればBIT反転後orし、再度反転する処理を入れる // 船のBIT反転 c = *chrp; if (c & 0x80) { c ^= 0x07; } // mapのBIT反転 p = *pos; if (p & 0x80) { p ^= 0x07; } // 合成 p1 = p; p |= c; // 衝突判定 if (collision == 0) { if ((p ^ c) != p1) { collision = 1;// 衝突位置を仮に*に置き換えp = 0x17; } } // 合成結果をBIT戻しして書き戻し if (p & 0x80) { p ^= 0x07; }/* 後日のアセンブラ化のためBIT反転処理メモ bit 7, a ; bit7 をチェック jr z, skip ; 0 ならスキップ xor 0x07 ; bit0?2 を反転skip:*/ *pos = p; pos++; chrp++; } pos += RENDER_DX-5; } return (collision);}ーーーーーmainc戻り値のcollitonが1なら処理を中断するようにしています。void main(){ unsigned char key; unsigned char *pos; int dy; int collision; map_init(); boat_init(); goal_meter_init(); objects_init(); while (1) { key = key_pressed(); if (key & KBIT_ENT) { break; } // 自船移動 dy = boat_move(key); // MAP、メーター更新 map_scroll(dy); goal_meter_draw(dy); // オブジェクト移動と更新 objects_move(dy); objects_update(render_pos); // 各種描画と衝突判定 map_render(); objects_render(); collision = boat_render(); // 全体描画 map_draw(); // 衝突判定 if (collision == 1) { break; } // if (key & KBIT_SPC)// {// in_Wait(100);// } }}衝突判定については、boat_render()にて背景と自船のorを取って背景に戻す際箇所に処理を加えて更に自船のxorを取った戻の値に戻らなかったら衝突と考えます。なお、当初は元の値をp1に格納して比較していましたがunsigned dharだと変な値となったため、intに変更しています。コンパイラの不具合かな。また、その際に衝突箇所がわかるように*に置き換え処理を入れましたが何回かPLAYすると衝突位置が非常にわかりやすいので最終的に仕様として残すかもしれません。【15%OFFクーポン】【糖質オフの豆100%麺】ZENB ゼンブ ヌードル 丸麺 8食~ 送料無料 パスタ そば ラーメン | 糖質オフ グルテンフリー 糖質制限 糖質コントロール 小麦粉不使用 たんぱく質 食物繊維 低GI 置き換え ダイエット時の栄養補給に価格:2,620円~(税込、送料無料) (2026/6/19時点)楽天で購入
2026.06.19
コメント(0)
時間がかかりましたが、ここまで検討した結果をソースに反映させます。ーーーーーobjects.h/* * ZX81 Objects Handling Module header */ #ifndef _ZX81_OBJECTS_H#define _ZX81_OBJECTS_H // objects functionsvoid objects_init(void);void objects_move(int dy);void objects_update(int render_pos);void objects_render(); #endif // _ZX81_OBJECTS_Hーーーーーobjects.cpp/* * ZX81 Objects Handling Module program */ #include <stdio.h>#include <string.h> #include "map.h"#include "objects.h" typedef enum { OBJ_NONE, OBJ_WHIRL, OBJ_ENEMY, OBJ_CROC, OBJ_BARREL, OBJ_RAFT, OBJ_DRIFTER, OBJ_MAX} ObjectType; #define WHIRL_CW 5#define WHIRL_CH 5unsigned char whirl_chr[2][WHIRL_CW*WHIRL_CH] = { {0x87,0x03,0x01,0x87,0x00, 0x05,0x00,0x03,0x04,0x04, 0x05,0x05,0x00,0x01,0x05, 0x87,0x02,0x01,0x87,0x01, 0x00,0x00,0x03,0x01,0x00}, {0x00,0x00,0x83,0x04,0x00, 0x02,0x87,0x04,0x02,0x04, 0x04,0x05,0x00,0x04,0x05, 0x05,0x00,0x83,0x01,0x00, 0x02,0x83,0x00,0x02,0x00}}; #define ENEMY_CW 3#define ENEMY_CH 5unsigned char enemy_chr[4][ENEMY_CW*ENEMY_CH] = { {0x80,0x81,0x05, 0x05,0x00,0x05, 0x80,0x84,0x05, 0x02,0x07,0x00, 0x00,0x00,0x00}, {0x85,0x82,0x80, 0x85,0x00,0x85, 0x85,0x07,0x80, 0x00,0x84,0x01, 0x00,0x00,0x00}, {0x83,0x87,0x04, 0x07,0x03,0x05, 0x82,0x83,0x05, 0x84,0x81,0x01, 0x00,0x01,0x00}, {0x87,0x04,0x83, 0x85,0x03,0x84, 0x85,0x83,0x81, 0x02,0x82,0x07, 0x00,0x02,0x00}}; #define CROC_CW 3#define CROC_CH 5unsigned char croc_chr[4][CROC_CW*CROC_CH] = { {0x84,0x04,0x00, 0x84,0x80,0x04, 0x81,0x80,0x00, 0x85,0x07,0x01, 0x00,0x01,0x00}, {0x02,0x82,0x00, 0x02,0x80,0x82, 0x87,0x80,0x05, 0x00,0x80,0x03, 0x00,0x02,0x00}, {0x00,0x87,0x04, 0x87,0x80,0x04, 0x84,0x80,0x00, 0x81,0x80,0x01, 0x00,0x07,0x00}, {0x00,0x00,0x83, 0x00,0x81,0x82, 0x02,0x80,0x05, 0x87,0x80,0x07, 0x00,0x85,0x01}}; #define BARREL_CW 3#define BARREL_CH 4unsigned char barrel_chr[4][BARREL_CW*BARREL_CH] = { {0x07,0x84,0x00, 0x07,0x84,0x00, 0x07,0x84,0x00, 0x03,0x03,0x00}, {0x85,0x03,0x05, 0x85,0x03,0x05, 0x85,0x03,0x05, 0x02,0x03,0x01}, {0x83,0x83,0x00, 0x82,0x81,0x00, 0x82,0x81,0x00, 0x82,0x81,0x00}, {0x87,0x83,0x04, 0x85,0x83,0x05, 0x85,0x83,0x05, 0x85,0x83,0x05}}; #define RAFT_CW 3#define RAFT_CH 4unsigned char raft_chr[4][RAFT_CW*RAFT_CH] = {{0x06,0x86,0x00, 0x05,0x85,0x00, 0x07,0x84,0x00, 0x02,0x01,0x00},{0x87,0x03,0x04, 0x85,0x00,0x05, 0x85,0x03,0x05, 0x00,0x03,0x00},{0x87,0x04,0x00, 0x05,0x85,0x00, 0x82,0x81,0x00, 0x86,0x06,0x00},{0x00,0x83,0x00, 0x85,0x00,0x05, 0x85,0x83,0x05, 0x02,0x83,0x01},}; #define DRIFTER_CW 3#define DRIFTER_CH 3unsigned char drifter_chr[5][DRIFTER_CW*DRIFTER_CH] = { {0x04,0x04,0x04, 0x84,0x80,0x01, 0x00,0x00,0x00}, {0x87,0x87,0x87, 0x02,0x80,0x07, 0x00,0x00,0x00}, {0x04,0x00,0x04, 0x86,0x82,0x01, 0x02,0x03,0x00}, {0x87,0x00,0x87, 0x02,0x81,0x06, 0x00,0x03,0x01}, {0x06,0x07,0x04, 0x84,0x84,0x01, 0x00,0x00,0x00}}; // object table#define OBJECTS_MAX 9 struct { int y; int x; ObjectType type;} objects_tbl[OBJECTS_MAX] ={ {230,10, OBJ_ENEMY}, {210,13, OBJ_CROC}, {190,8, OBJ_BARREL}, {170,13, OBJ_RAFT}, {150,8, OBJ_DRIFTER}, { 50,11, OBJ_WHIRL}, { 74,4, OBJ_WHIRL}, {125,6, OBJ_WHIRL}, {200,13, OBJ_WHIRL}}; // active table#define ACTIVE_MAX 5 struct { int y; // 画面上のY座標 int x; // 画面上のX座標 int h; // 描画文字の高さ int w; // 描画文字の幅 int area; // w * h のキャッシュ unsigned char *p; // 描画文字のポインタ ObjectType type; // オブジェクト種別 int speed_y; // Y移動速度(0=移動しない、1=毎回, 2=2回に1回) int speed_cnt;// カウンタ(speed_y で初期化)} active_tbl[ACTIVE_MAX]; // render size#define RENDER_DX 22#define RENDER_DY 24 // objects出現位置static unsigned int objects_y; // ----------------------------------------------------------// 初期化// ----------------------------------------------------------void objects_init(void){ int i; for (i = 0; i < ACTIVE_MAX; i++) { active_tbl[i].type = OBJ_NONE; } objects_y = 0xffff;} // ----------------------------------------------------------// 出現// ----------------------------------------------------------static void objects_active(int obj_no){ int i; unsigned char *chrp; int chrw, chrh; int speed_y; for (i = 0; i < ACTIVE_MAX; i++) { if (active_tbl[i].type == OBJ_NONE) { break; } } if (i == ACTIVE_MAX) { return; } speed_y = 4; switch (objects_tbl[obj_no].type) { case OBJ_WHIRL: chrp = &whirl_chr[0][0]; chrw = WHIRL_CW; chrh = WHIRL_CH; speed_y = 0; break; case OBJ_ENEMY: chrp = &enemy_chr[0][0]; chrw = ENEMY_CW; chrh = ENEMY_CH; break; case OBJ_CROC: chrp = &croc_chr[0][0]; chrw = CROC_CW; chrh = CROC_CH; speed_y = 2; break; case OBJ_BARREL: chrp = &barrel_chr[0][0]; chrw = BARREL_CW; chrh = BARREL_CH; break; case OBJ_RAFT: chrp = &raft_chr[0][0]; chrw = RAFT_CW; chrh = RAFT_CH; break; case OBJ_DRIFTER: chrp = &drifter_chr[0][0]; chrw = DRIFTER_CW; chrh = DRIFTER_CH; break; default: return; } active_tbl[i].y = 1; active_tbl[i].x = objects_tbl[obj_no].x; active_tbl[i].w = chrw; active_tbl[i].h = chrh; active_tbl[i].area = chrw*chrh; active_tbl[i].p = chrp; active_tbl[i].type = objects_tbl[obj_no].type; active_tbl[i].speed_y = speed_y; active_tbl[i].speed_cnt = speed_y;} // ----------------------------------------------------------// 更新// ----------------------------------------------------------void objects_update(int render_pos){ int obj_no; int obj_y; // 出現位置の更新チェック obj_y = (render_pos + 1) / 4; if (objects_y <= obj_y) { return; } objects_y = obj_y; // 出現位置の更新によるオブジェクト出現処理 for (obj_no = 0; obj_no < OBJECTS_MAX; obj_no++) { if (objects_tbl[obj_no].y == obj_y) { break; } } if (obj_no == OBJECTS_MAX) { return; } objects_active(obj_no);} // ----------------------------------------------------------// 移動// ----------------------------------------------------------void objects_move(int dy){ int act_no, obj_y; for (act_no = 0; act_no < ACTIVE_MAX; act_no++) { switch (active_tbl[act_no].type) { case OBJ_WHIRL: case OBJ_ENEMY: case OBJ_CROC: case OBJ_BARREL: case OBJ_RAFT: case OBJ_DRIFTER: break; default: continue; } if (active_tbl[act_no].speed_y != 0) { active_tbl[act_no].speed_cnt--; if (active_tbl[act_no].speed_cnt == 0) { active_tbl[act_no].y++; active_tbl[act_no].speed_cnt = active_tbl[act_no].speed_y; } } active_tbl[act_no].y -= dy; // 画面外は消去 obj_y = active_tbl[act_no].y / 2; if ((obj_y < 0) || (obj_y > 23 + active_tbl[act_no].h)) { active_tbl[act_no].type = OBJ_NONE; } }} // ----------------------------------------------------------// 描画// ----------------------------------------------------------void objects_render(){ int act_no, obj_y, y; unsigned char *chrp; unsigned char *pos; int chrw, chrh, dy; for (act_no = 0; act_no < ACTIVE_MAX; act_no++) { if (active_tbl[act_no].type == OBJ_NONE) { continue; } chrp = active_tbl[act_no].p; if (active_tbl[act_no].type == OBJ_WHIRL) { if (active_tbl[act_no].y & 0x01) { chrp += active_tbl[act_no].area; } } else { if (active_tbl[act_no].x & 0x01) { chrp += active_tbl[act_no].area; } if (active_tbl[act_no].y & 0x01) { chrp += active_tbl[act_no].area * 2; } } obj_y = (active_tbl[act_no].y / 2) - active_tbl[act_no].h; if (obj_y < 0) { pos = getrenderaddr(0, active_tbl[act_no].x); } else { pos = getrenderaddr(obj_y, active_tbl[act_no].x); } chrw = active_tbl[act_no].w; chrh = active_tbl[act_no].h; dy = obj_y; for (y = 0; y < chrh; y++) { if ((dy <= 23) && (dy >= 0)) { memcpy(pos, chrp, chrw); } if (dy >= 0) { pos += RENDER_DX; } chrp += chrw; dy++; } }}渦はobjectsの中に含めたので、whirl.hとwhirl.cは削除しました。川の位置がobject_tblの位置に到達したら対象のObjectTypeをavtive_tblにて最高5つまで管理し画面からoutするまで動きと描画を行います。それぞれにY座標の移動速度を設定できるためより完成に近づいてきました。1ドットずつの移動も問題なさそうです。ただし、衝突判定と横移動は未実装ですので順次検討していきます。
2026.06.18
コメント(0)
それぞれのオブジェクト管理の方法について、変更をいくつか実施しました。1.渦もObjects化 渦の処理を別にしていたのは、渦だけは一旦出現した後 船が戻った場合に再度出現するとの思い込みからでした。 River Patrolを検証のためプレイしたところ 渦も一度しか出現しないため、Objectsに含める事としました。 ※参考 RiverPatrol 渦巻をスクロールしてみる | ZX81の回想禄 - 楽天ブログ2.オブジェクト判定 オブジェクトは衝突だけでなく、オブジェクト判定処理とします。 グループ➀ 衝突検出で沈没 ・OBJ_ENEMY … 敵の船 ・OBJ_BARREL … ドラム缶 ・OBJ_RAFT … 流木 グループ➁ 衝突検出で浸水量UP ・OBJ_CROC … ワニ グループ➂ 衝突検出で救出or死亡 ・OBJ_DRIFTER … 漂流者 グループ➃ 接近による渦処理 ・OBJ_WHIRL… 渦処理 また忙しくなり、中々すすみませんが。。。
2026.06.15
コメント(0)
それぞれのオブジェクト管理の方法について、検討していきます。1.出現位置管理 渦巻の時のように、出現位置テーブルで管理します。 XY座標に加え、オブジェクト種別を追加予定です。 struct { unsigned char x; // X座標 unsigned int y; // Y座標 unsigned char type; // 種別(enum ObjectType) } ; 渦巻と異なる挙動としては、1度しかオブジェクトは出現しないようにします。 現在の船の位置と別に、川の到達位置を変数で管理して 1度過ぎた所はオブジェクトを出現させないようにします。 これは、点数加算にも使用できそうです。 ※渦巻の処理については、下記を参照ください。 RiverPatrol 渦巻をスクロールしてみる | ZX81の回想禄 - 楽天ブログ2.オブジェクト管理 オブジェクトの出現位置になったら、 オブジェクトの画面出現から画面外までの管理テーブルにコピーします。 struct { unsigned char x; // 現在のX unsigned char y; // 現在のY(画面内) unsigned char type; // 種別 }; このテーブルを参照しながら種別ごとの動きをさせます。3.オブジェクト衝突 2のテーブルを利用して、衝突も管理します。 グループ➀ 衝突検出で沈没 ・OBJ_ENEMY … 敵の船 ・OBJ_BARREL … ドラム缶 ・OBJ_RAFT … 流木 グループ➁ 衝突検出で浸水量UP ・OBJ_CROC … ワニ グループ➂ 衝突検出で救出or死亡 ・OBJ_DRIFTER … 漂流者順次検討したいと思います。
2026.06.09
コメント(0)

ちょこちょこ手直しばかりしています。全て少しずつ調整しております。キリがないので、この辺にしておきます。あとは全体と動きを見ながら調整したいと思います。
2026.06.05
コメント(0)

object.h、cppに仮表示処理を入れて、テスト表示してみました。エントリーNo.1 敵船エントリーNo.2 ワニエントリーNo.3 ドラム缶エントリーNo.4 流木エントリーNo.5 漂流者わかっていましたが、船と一緒に表示すると色々駄目ですね。流木とドラム缶は少し長いので要調整。ワニは…大きいのはわかっているのですが、これ以上小さくすると何かわからない人も大きいですね。。うーむむむむ頑張ってみますが、如何ともし難いかもです。
2026.06.02
コメント(0)
オブジェクトの管理は、object.h、cppにて一括管理する事としました。定義名はこんな感じです。・OBJ_ENEMY … 敵の船・OBJ_CROC … ワニ・OBJ_BARREL … ドラム缶・OBJ_RAFT … 流木・OBJ_DRIFTER … 漂流者早速、コードに落とし込んでいきます。ここからキャラクターコードや、出現位置描画などを肉付けしていく予定です。また、今回から***_renderとしているのあ先々バックバッファに書き込んでチラツキを抑えるように書き換える予定としています。(その分描画が遅延しますが、Rally-ZXほど描画処理にかからないと踏んでいます。)ーーーーーobjects.h/* * ZX81 Objects Handling Module header */ #ifndef _ZX81_OBJECTS_H#define _ZX81_OBJECTS_H // objects functionsvoid objects_init(void);void objects_render(int render_pos); #endif // _ZX81_OBJECTS_Hーーーーーobjects.c/* * ZX81 Objects Handling Module program */ #include <stdio.h>#include <string.h> #include "map.h"#include "objects.h" typedef enum { OBJ_ENEMY, OBJ_CROC, OBJ_BARREL, OBJ_RAFT, OBJ_DRIFTER,} ObjectType;// ----------------------------------------------------------// 初期化// ----------------------------------------------------------void objects_init(void){} // ----------------------------------------------------------// VRAM へ描画// start_y を先頭として、論理 0..RENDER_DY-1 を順に出力// ----------------------------------------------------------void objects_render(int render_pos){}あ、そういえば当たり判定も必要ですね。順次検討していきます。
2026.06.01
コメント(0)

以前計画していたサイズでは描画が難しいため、全体的に縮小する方向で再検討しました。 リバーパトロール製作検討② | ZX81の回想禄 - 楽天ブログボートワニドラム缶と流木厳しいですね。特にワニは。。。どうやっても無理やりな感じが消えないです。
2026.05.16
コメント(0)

次にオブジェクトを検討していきます。まずは漂流者から。船との対比から、W:6×H:4ドットとしました。縦と横に1ドットずらした分と手を上下に上げている感じで2×4=8パターンとしました。最後は、正面から衝突した場合用としてPC-8001に「RiverRescue」を参考にどくろパターンも用意。
2026.05.14
コメント(0)
マップ上下と渦、船の加減速と左右の動きとゴールメーターまでをテスト動画としてアップしました。 RiverPatrol test4ぶつかっても転覆しないし、渦にも巻きこまれませんが川を上る感じは、前回のテスト動画より感じられるかと思います。Rally-ZXの時も思いましたが、音が無いのはやはり寂しいです。
2026.05.12
コメント(0)

船の左右移動と加速・停止が形になってきたのでMAPの位置を示す「GOAL METER」も実装します。表示はこんな感じです。メーター描画は1行目から22行目の44ドット分とし、ゴールまでの1000-48ドットから0ドットまでの進み具合を示します。ーーーーーmap.h(追加点)// goal metervoid goal_meter_init(void);void goal_meter_draw(int dy);ーーーーーmap.cpp(追加点)// ASCII to ZX81-character #define ASCII_BGN 32#define ASCII_END (ASCII_BGN + sizeof(zx81chr) - 1)unsigned char zx81chr[] = {// (32),!(33),"(34),#(35),$(36) 0x00, 0x00, 0x0b, 0x00, 0x0d,// %(37),&(38),'(39),((40),)(41) 0X00, 0X00, 0X00, 0x10, 0x11,// *(42),+(43),,(44),-(45),.(46) 0x17, 0x15, 0x1a, 0x16, 0x1b,// /(47),0(48),1(49),2(50),3(51) 0x18, 0x1c, 0x1d, 0x1e, 0x1f,// 4(52),5(53),6(54),7(55).8(56) 0x20, 0x21, 0x22, 0x23, 0x24,// 9(57),:(58),;(59),<(60),=(61) 0x25, 0x0e, 0x19, 0x13, 0x14,// >(62),?(63),@(64),A(65),B(66) 0x12, 0x0f, 0x0c, 0x26, 0x27,// C(67),D(68),E(69),F(70),G(71) 0x28, 0x29, 0x2a, 0x2b, 0x2c,// H(72),I(73),J(74),K(75),L(76) 0x2d, 0x2e, 0x2f, 0x30, 0x31,// M(77),N(78),O(79),P(80),Q(81) 0x32, 0x33, 0x34, 0x35, 0x36,// R(82),S(83),T(84),U(85),V(86) 0x37, 0x38, 0x39, 0x3a, 0x3b,// W(87),X(88),Y(89),Z(90) 0x3c, 0x3d, 0x3e, 0x3f};void goal_meter_init(void){ char *strp = "GOAL METER"; unsigned char *p, c; int i; p = getscrnaddr(6, 2); for (i = 0; i <= strlen(strp); i++) { if ((strp[i] >= ASCII_BGN) && (strp[i] <= ASCII_END)) { *p = zx81chr[strp[i] - ASCII_BGN]; } p += D_DX; } p = getscrnaddr(1, 3); for (i = 1; i <= 22; i++) { *p = 0x05; p += D_DX; }}void goal_meter_draw(int dy){ int y; unsigned char *p, c; if (dy < 0) { y = render_pos / 22; if (y & 1) { c = 0x01; } else { c = 0x00; } p = getscrnaddr(1 + (y >> 1), 3); *p = c; } else if (dy > 0) { y = render_pos / 22; if (y & 1) { c = 0x01; } else { if (y == 0) { return; } y -= 2; c = 0x05; } p = getscrnaddr(1 + (y >> 1), 3); *p = c; }}zx81chrテーブルはRally-ZXからの使いまわしです。goal_meter_initでGOAL METERの文字と、0x05(縦2ブロック)を縦に描画し、goal_meter_drawで、dyに応じて変化する所だけを描画する事としました。先々は高速化のため全てアセンブラ化する予定ですが船とmapスクロール(+渦)だけなら、ちらつきはあるもののC言語でもある程度の速度が出ています。
2026.05.11
コメント(0)
続いて船の横方向の移動です。こちらも、以前検討していました。 船の移動(縦+横方向) | ZX81の回想禄 - 楽天ブログ縦と横を組み合わせて微調整した結果、横もテーブル化する事として、縦テーブルも調整しました。ーーーーーship.c->boat.c(名称変更) shipは大きい船も含むようで、川下りとしてはboatが適正と AIから指摘されたので、ファイル名、関数名等を全てboatに変更しました。ーーーーーboat.cpp(変更点)typedef struct { char boat_y; // ボートY座標 char dy; // map scroll dy(-1=上、+1下) char speed_down; // Spaceキーを離している場合の次のテーブル参照先 char speed_up; // Spaceキーを押している場合 〃 char isturn; // 左右操作が可能(0:FALSE、1:TRUE)} SPEED_TBL; static const SPEED_TBL speed_tbl[] = {// 停止 {33, 1, 1, 8, 0}, // table0: 停止 {33, 0, 0, 8, 0}, // table1: 停止 {32, 0, 1, 9, 1}, // table2: 停止 {31, 0, 2,10, 1}, // table3: 停止 {30, 0, 3,11, 1}, // table4: 停止 {29, 0, 4,12, 1}, // table5: 停止 {28, 0, 5,13, 1}, // table6: 停止 {27, 0, 6,14, 1}, // table7: 停止// 加速 {32, 0, 1, 9, 1}, // table8: 加速 {31, 0, 2,10, 1}, // table9: 加速 {30, -1, 3,11, 1}, // table10:加速 {29, 0, 4,12, 1}, // table11:加速 {28, -1, 5,13, 1}, // table12:加速 {27, -1, 6,18, 1}, // table13:加速 {26, -1, 7,15, 1}, // table14:最大加速 {26, -1,14,16, 1}, // table15:最大加速 {26, -1,15,17, 1}, // table16:最大加速 {26, -1,16,18, 1}, // table17:最大加速 {26, -1,17,18, 1}, // table18:最大加速}; typedef struct { char dx; // X移動量(-1, 0, +1) char tilt; // 表示する傾き(0=center, 1=23°, 2=45°) char tilt_down; // 戻す場合の次のテーブル参照先 char tilt_left; // 左へ傾く場合の 〃 char tilt_right; // 右へ傾く場合の 〃} LR_TBL; const LR_TBL lr_tbl[] = { { -1, 0, 1, 0, 1 }, // table0: -1移動 - 45°(最大) { -1, 0, 2, 0, 2 }, // table1: -1移動 - 45°(最大) { -1, 0, 3, 0, 3 }, // table2: -1移動 - 45°(最大) { -1, 0, 4, 0, 4 }, // table3: -1移動 - 45°(最大) { -1, 0, 5, 0, 5 }, // table4: -1移動 - 45°(最大) { -1, 1, 6, 4, 6 }, // table5: -1移動 - 23° { 0, 1, 7, 5, 7 }, // table6: 0移動 - 23° { -1, 1, 8, 6, 8 }, // table7: -1移動 - 23° { 0, 1, 9, 7, 9 }, // table8: 0移動 - 23° { 0, 2, 9, 8, 10}, // table9: センター { 0, 3, 9, 9, 11}, // table10: 0移動 + 23° { 1, 3, 10, 10, 12}, // table11: 1移動 + 23° { 0, 3, 11, 11, 13}, // table12: 0移動 + 23° { 1, 3, 12, 12, 14}, // table13: 1移動 + 23° { 1, 4, 13, 13, 18}, // table14: 1移動 + 45°(最大) { 1, 4, 14, 14, 18}, // table15: 1移動 + 45°(最大) { 1, 4, 15, 15, 18}, // table16: 1移動 + 45°(最大) { 1, 4, 16, 16, 18}, // table17: 1移動 + 45°(最大) { 1, 4, 17, 17, 18}, // table18: 1移動 + 45°(最大)}; #define LR_CENTER 9 :static int boat_speed;static int boat_x;static int boat_y;static int boat_tilt;static int tilt_level; // ----------------------------------------------------------// 初期化// ----------------------------------------------------------void boat_init(void){ boat_speed = 0; boat_x = 18; boat_y = speed_tbl[boat_speed].boat_y; tilt_level = LR_CENTER; boat_tilt = lr_tbl[tilt_level].tilt;} :void boat_move(unsigned char key){ // --- テーブル遷移 --- if (key & KBIT_SPC) { boat_speed = speed_tbl[boat_speed].speed_up; } else { boat_speed = speed_tbl[boat_speed].speed_down; } // --- Y位置更新 --- boat_y = speed_tbl[boat_speed].boat_y; // --- マップスクロール --- int dy = speed_tbl[boat_speed].dy; if (dy != 0) { map_scroll(dy); goal_meter_draw(dy); } // --- 左右操作 int turn = 0; int dx = 0; if (speed_tbl[boat_speed].isturn) { if (key & KBIT_LT) { if (key & KBIT_SPC) { tilt_level = lr_tbl[tilt_level].tilt_left; dx = lr_tbl[tilt_level].dx; turn = 1; } else { if (tilt_level == LR_CENTER) { dx = -1; } } } else if (key & KBIT_RT) { if (key & KBIT_SPC) { tilt_level = lr_tbl[tilt_level].tilt_right; dx = lr_tbl[tilt_level].dx; turn = 1; } else { if (tilt_level == LR_CENTER) { dx = 1; } } } } // --- 傾きを戻す if (turn == 0) { tilt_level = lr_tbl[tilt_level].tilt_down; if (dx == 0) { dx = lr_tbl[tilt_level].dx; } } boat_tilt = lr_tbl[tilt_level].tilt; // --- 左右移動 if (dx == -1) { if (boat_x > 0) { boat_x--; } } else if (dx == 1) { if (boat_x < 36) { boat_x++; } }}以上、変更点のみ記述しました。最大速度や最大傾斜の際は戻す場合に慣性が感じられるように戻すテーブルを増やしてみました。もう少し調整して動画にしたいと思います。
2026.05.10
コメント(0)
船の縦方向の移動について、以前検討していました。 船の移動(縦方向) | ZX81の回想禄 - 楽天ブログしかし、いざ慣性も含めてプログラムしてみたところ荒いドットのせいかなかなかうまくいかなかったためテーブル方式としてみました。typedef struct { char boat_y; // ボートY座標 char dy; // map scroll dy(-1=上、+1下) char speed_down; // Spaceキーを離している場合の次のテーブル参照先 char speed_up; // Spaceキーを押している場合 〃 char isturn; // 左右操作が可能(0:FALSE、1:TRUE)} SPEED_TBL; static const SPEED_TBL speed_tbl[] = {// 停止(0,1の繰り返しで2回に1回マップが+1スクロール) {33, 1, 1, 2, 0}, // table0: 停船 {33, 0, 0, 2, 0}, // table1: 停船// 微加速:(2-4で2回に1回マップが-1スクロール) {32, 0, 1, 3, 1}, // table2: 微加速1 {31, -1, 2, 4, 1}, // table3: 微加速1 {30, 0, 3, 5, 1}, // table4: 微加速1// 加速(5-18で毎回マップが-1スクロール) {29, -1, 9, 8, 1}, // table5: 加速1 {29, -1, 4, 8, 1}, // table6: 加速1 {29, -1, 6, 8, 1}, // table7: 加速1 {28, -1,12, 11,1}, // table8: 加速1 {28, -1, 7, 11,1}, // table9: 加速1 {28, -1, 9, 11,1}, // table10:加速1 {27, -1,16, 15,1}, // table11:加速1 {27, -1,10, 15,1}, // table12:加速1 {27, -1,12, 15,1}, // table13:加速1 {27, -1,13, 15,1}, // table14:加速1 {26, -1,18, 18,1}, // table15:加速1 {26, -1,14, 18,1}, // table16:加速1 {26, -1,16, 18,1}, // table17:加速1 {26, -1,17, 18,1}, // table18:加速1};ポイントは、加速時と減速時のテーブル移行先を決める事で慣性とし左右の移動可能かも含めてみました。次は左右のテーブルも決めていきます。
2026.05.09
コメント(0)
本来は、船の慣性を考慮するべきなのですがキー入力により、上下左右に船を動かしてMAPとの連動を確認するための処理用として、まずは組み込みこんでみます。ーーーーーmap.cpp(変更点)void main(){ unsigned char key; unsigned char *pos; map_init(); ship_init(); map_draw(); ship_draw(); while (1) { key = key_pressed();// if (key == 0)// {// continue;// } if (key & KBIT_ENT) { break; } /* if (key & KBIT_UP) { map_scroll(-1); } else if (key & KBIT_DN) { map_scroll(1); }*/ ship_move(key); map_draw(); whirl_draw(render_pos); ship_draw(); if (key & KBIT_SPC) { in_Wait(100); } }}ーーーーーship.h(新規)/* * ZX81 Ship Handling Module header */ #ifndef _ZX81_SHIP_H#define _ZX81_SHIP_H // functionsvoid ship_init(void); void ship_draw(void);void ship_move(unsigned char key); #endif // _ZX81_SHIP_Hーーーーーship.c(新規)/* * ZX81 Ship Handling Module program */ #include <stdio.h>#include <string.h> #include "key.h"#include "map.h"#include "ship.h" unsigned char shipptn[2][5][25] ={ { // even-right45 {0x87,0x83,0x00,0x00,0x00, 0x85,0x00,0x03,0x04,0x00, 0x00,0x05,0x07,0x84,0x04, 0x00,0x02,0x82,0x80,0x00, 0x00,0x00,0x02,0x00,0x00}, // even-right23 {0x87,0x86,0x04,0x00,0x00, 0x85,0x00,0x02,0x04,0x00, 0x00,0x82,0x03,0x86,0x00, 0x00,0x02,0x81,0x07,0x00, 0x00,0x00,0x01,0x00,0x00}, // even-center {0x00,0x87,0x82,0x00,0x00, 0x00,0x06,0x02,0x04,0x00, 0x00,0x82,0x86,0x05,0x00, 0x00,0x82,0x83,0x05,0x00, 0x00,0x00,0x01,0x00,0x00}, // even-left23 {0x00,0x00,0x83,0x86,0x00, 0x00,0x06,0x00,0x85,0x00, 0x87,0x03,0x86,0x05,0x00, 0x02,0x80,0x06,0x00,0x00, 0x00,0x00,0x01,0x00,0x00}, // even-left45 {0x00,0x00,0x87,0x83,0x00, 0x00,0x06,0x01,0x85,0x00, 0x81,0x03,0x05,0x05,0x00, 0x85,0x82,0x07,0x00,0x00, 0x00,0x02,0x00,0x00,0x00} }, { // odd-right45 {0x00,0x83,0x04,0x00,0x00, 0x00,0x05,0x02,0x86,0x00, 0x00,0x85,0x85,0x03,0x82, 0x00,0x00,0x84,0x81,0x05, 0x00,0x00,0x00,0x01,0x00}, // odd-right23 {0x00,0x06,0x83,0x00,0x00, 0x00,0x05,0x00,0x86,0x00, 0x00,0x85,0x06,0x03,0x04, 0x00,0x00,0x86,0x80,0x01, 0x00,0x00,0x02,0x00,0x00}, // odd-center {0x00,0x00,0x81,0x04,0x00, 0x00,0x87,0x01,0x86,0x00, 0x00,0x85,0x06,0x81,0x00, 0x00,0x85,0x83,0x81,0x00, 0x00,0x00,0x02,0x00,0x00}, // odd-left23 {0x00,0x00,0x87,0x06,0x04, 0x00,0x87,0x01,0x00,0x05, 0x00,0x06,0x03,0x81,0x00, 0x00,0x84,0x82,0x01,0x00, 0x00,0x00,0x02,0x00,0x00}, // odd-left45 {0x00,0x00,0x00,0x83,0x04, 0x00,0x87,0x03,0x00,0x05, 0x87,0x07,0x84,0x85,0x00, 0x00,0x80,0x81,0x01,0x00, 0x00,0x00,0x01,0x00,0x00} }}; // screen address#define D_DX 33 // 32+1(CR)#define D_DY 24 int ship_x = 0;int ship_y = 0;//int ship_ // ----------------------------------------------------------// 初期化// ----------------------------------------------------------void ship_init(void){ ship_x = 0; ship_y = 12;} // ----------------------------------------------------------// VRAM へ描画// start_y を先頭として、論理 0..RENDER_DY-1 を順に出力// ----------------------------------------------------------void ship_draw(void){ unsigned char *chrp, c; unsigned char *pos, p; int y, x; pos = getscrnaddr(ship_y, 4+(ship_x>>1)); if (ship_x & 0x01) { chrp = &shipptn[1][2][0]; } else { chrp = &shipptn[0][2][0]; } for (y = 0; y < 5; y++) {// memcpy(pos, chrp, 5);// pos += D_DX;// chrp += 5; for (x = 0; x < 5; x++) {// *pos |= *chrp; p = *pos; if (p & 0x80) { p ^= 0x07; } c = *chrp; if (c & 0x80) { c ^= 0x07; } p |= c; if (p & 0x80) { p ^= 0x07; }/* bit 7, a ; bit7 をチェック jr z, skip ; 0 ならスキップ xor 0x07 ; bit0?2 を反転skip:*/ *pos = p; pos++; chrp++; } pos += D_DX-5; }} // ----------------------------------------------------------// 移動処理// key入力及び時間経過に伴う減速処理// ----------------------------------------------------------void ship_move(unsigned char key){ if (key & KBIT_UP) { map_scroll(-1); } else if (key & KBIT_DN) { map_scroll(1); } if (key & KBIT_LT) { if (ship_x > 0) { ship_x--; } } else if (key & KBIT_RT) { if (ship_x < 35) { ship_x++; } }}いつものように、下記でビルドします。 zcc +zx81 map.c key.c ship.c whirl.c main.c -createa-app今回は右下の0x80の処理を毎回行っていて処理が少し重いようですが船と背景の重ね合わせは上手くいっているようです。次は船の慣性を考えていきます。
2026.04.23
コメント(0)

では、実際の計算方法を検討します。まずは、重ね合わせるブロック文字の双方に対して0x80ビットをチェックします。ビットが立っていれば、0x07で反転します。その後orを取り、再び0x80ビットをチェックして0x07で再反転するものです。// C言語の場合unsigned char v; // 対象データ if (v & 0x80) { v ^= 0x07; // 下位3ビットを反転}// アセンブラの場合 ld a, (value) bit 7, a jr z, no_flip xor 0x07no_flip: ld (value), a今回は、岩のパターンをベタ書きし、その上に描画する渦、船、ワニなどを重ね合わせしていきます。速度が遅いようならば、重ね合わせ用のブロックは反転しないように持っておくのも手かもしれません。また、この重ね合わせの処理は船の衝突判定時にも関係していて岩などの障害物から船のブロックの差分を引いた時に元のブロックイメージと異なる場合は衝突にしたいと考えています。1ドットでも当たれば衝突となるので、シビアすぎるかもしれません。
2026.04.21
コメント(0)

ZX81では、以前紹介した通り2x2のブロック文字が定義されておりこの組み合わせでセミグラフィック表示(64x48)を実現しています。※文字セットはこちらを参照 Appendix A - The character set(文字セット) | ZX81の回想禄 - 楽天ブログ4×4ドットのブロック文字コードは下記となります。そう、左上が01、右上が02、左下が04そして、右下だけが何故か87となっています。全指定だと80になりますので、右下を指定すると01~04は反転する事となります。これから、描画した岩のブロック文字に船などを重ね書きするのですが単純にorすると右下のドット有り/無しでそれ以外が反転してしまう事となります。一工夫する必要がありそうです。
2026.04.17
コメント(0)

実際にスクロールしてみると、どれもそんなに悪くないイメージです。・偶数行・奇数行しかし、一番上のとぐろパターンは狙いすぎなのと、ちょっと飽きそうです。2番目の波立つ感じも悪くはないのですが外に広がるようなので回転のイメージではないかなと。3番目は、当初一番ないかと思ったのですがスクロールさせると回転しているイメージが一番あるのでもう少し調整して採用したいなと思いました。
2026.04.14
コメント(0)
結局3つから絞れなかったのでスクロールに合わせて渦巻を表示してみて決めたいと思います。ーーーーーmain.cpp#include <stdio.h>#include <string.h>#include <input.h> // in_Wait #include "key.h"#include "map.h"#include "whirl.h" void main(){ unsigned char key; unsigned char *pos; map_init(); map_draw(); while (1) { key = key_pressed(); if (key == 0) { continue; } if (key & KBIT_ENT) { break; } if (key & KBIT_UP) { map_scroll(-1); } else if (key & KBIT_DN) { map_scroll(1); } map_draw(); whirl_draw(render_pos); if (key & KBIT_SPC) { in_Wait(100); } }}ーーーーーwhirl.h/* * ZX81 Whirl Handling Module header */ #ifndef _ZX81_WHIRL_H#define _ZX81_WHIRL_H extern int render_pos; // Map functionsvoid whirl_init(void);void whirl_draw(int render_pos); #endif // _ZX81_WHIRL_Hーーーーーwhirl.cpp/* * ZX81 Whirl Handling Module program */ #include <stdio.h>#include <string.h> #include "map.h"#include "whirl.h" unsigned char whirl_chr[][50] = {{0x87,0x03,0x03,0x86,0x00, 0x05,0x06,0x03,0x04,0x05, 0x86,0x02,0x01,0x05,0x05, 0x00,0x03,0x03,0x87,0x01, 0x02,0x03,0x03,0x01,0x00, 0x00,0x83,0x83,0x83,0x00, 0x06,0x87,0x83,0x04,0x00, 0x05,0x05,0x83,0x02,0x04, 0x05,0x86,0x83,0x01,0x05, 0x02,0x83,0x83,0x06,0x00}, {0x87,0x01,0x00,0x86,0x00, 0x01,0x06,0x02,0x04,0x01, 0x00,0x04,0x00,0x04,0x00, 0x86,0x02,0x02,0x87,0x01, 0x00,0x01,0x00,0x01,0x00, 0x00,0x83,0x83,0x04,0x00, 0x02,0x87,0x83,0x02,0x00, 0x05,0x01,0x00,0x01,0x05, 0x01,0x86,0x83,0x01,0x01, 0x02,0x83,0x83,0x06,0x00}, {0x87,0x03,0x01,0x00,0x00, 0x05,0x00,0x03,0x04,0x04, 0x05,0x05,0x00,0x01,0x05, 0x00,0x02,0x01,0x87,0x01, 0x00,0x00,0x03,0x01,0x00, 0x00,0x00,0x83,0x04,0x00, 0x00,0x87,0x04,0x02,0x04, 0x04,0x05,0x00,0x04,0x05, 0x05,0x00,0x83,0x01,0x00, 0x02,0x83,0x00,0x00,0x00} /*{0x87,0x01,0x02,0x86,0x00},{0x01,0x06,0x01,0x04,0x05},{0x04,0x04,0x00,0x04,0x00},{0x86,0x00,0x03,0x87,0x01},{0x00,0x03,0x00,0x01,0x00}, {0x00,0x83,0x00,0x04,0x00},{0x06,0x00,0x83,0x02,0x04},{0x01,0x01,0x00,0x01,0x00},{0x04,0x86,0x04,0x01,0x05},{0x02,0x04,0x87,0x06,0x00},*/ }; #define WHIRL_MAX 3 struct { int y; int x;} whirl_tbl[WHIRL_MAX] ={ { 1,11}, { 4,13}, { 7,15}}; // screen address#define D_DX 33 // 32+1(CR)#define D_DY 24 // ----------------------------------------------------------// 初期化// ----------------------------------------------------------void whirl_init(void){} // ----------------------------------------------------------// VRAM へ描画// start_y を先頭として、論理 0..RENDER_DY-1 を順に出力// ----------------------------------------------------------void whirl_draw(int render_pos){ int i, wy, y, dy; unsigned char *chrp; unsigned char *pos; for (i = 0; i < WHIRL_MAX; i++) { wy = whirl_tbl[i].y * 2; dy = wy - ((render_pos+1) / 2); if ((dy >= -4) && (dy <= 23)) { if (dy < 0) { pos = getscrnaddr(0, whirl_tbl[i].x+4); } else { pos = getscrnaddr(dy, whirl_tbl[i].x+4); } if (render_pos & 1) { chrp = &whirl_chr[i][25]; } else { chrp = &whirl_chr[i][0]; } for (y = 0; y < 5; y++) { if ((dy <= 23) && (dy >= 0)) { memcpy(pos, chrp, 5); } if (dy >= 0) { pos += D_DX; } chrp += 5; dy++; } } } }いつものように、下記でビルドします。 zcc +zx81 map.c key.c whirl.c main.c -create-app表示結果については、次回確認していきます。
2026.04.13
コメント(0)

上下スクロールの検討が一通り終わったのでスクロールに合わせた障害物を検討していきます。船の大きさを縮小した時に気づいてはいたのですが船とのバランスを取るには、他の障害物も縮小が必須です。 リバーパトロール製作検討② | ZX81の回想禄 - 楽天ブログなので、まずは渦巻のイメージから再検討をします。6×6文字から5×5文字が適当なようです。表示仕様はPC-8001と同じように偶数行と奇数行の2つのパターンの切替を目指します。・とぐろ2.回転3.波が立つうーむ。イメージだけではどうしてもしっくりこないので実装して実際に表示を確認しながら決める事とします。
2026.04.10
コメント(0)
更にアセンブラ化を進めていきます。まずは、指定された1行分お岩を描画するrock_render_lineを試してみます。ーーーーーmap.cpp(変更点)void rock_render_line(int phys_y, int logical_row){/* int dx; unsigned char bit = 0x80; int rockx = 0; int rocky = (render_pos + logical_row * 2 + 1) / 4; int ptn = (render_pos + logical_row * 2) & 0x03; // 0 or 2 if (render_pos & 0x01) { memset(&render_buf_odd[phys_y][1], 0, RENDER_DX - 2); for (dx = 1; dx <= RENDER_DX - 2; dx++) { if (rock_tbl[rocky][rockx] & bit) memcpy(&render_buf_odd[phys_y][dx], &rock_chr[ptn][0], 2); bit >>= 1; if (!bit) { bit = 0x80; rockx++; } } } else { memset(&render_buf_even[phys_y][1], 0, RENDER_DX - 2); for (dx = 1; dx <= RENDER_DX - 2; dx++) { if (rock_tbl[rocky][rockx] & bit) memcpy(&render_buf_even[phys_y][dx], &rock_chr[ptn][0], 2); bit >>= 1; if (!bit) { bit = 0x80; rockx++; } } }*/#asm ld ix, 2 add ix, sp // render_pos + logical_row * 2 ld hl, (_render_pos) ld a, (ix+0) ; logical_row ld d, 0 ld e, a add hl, de ; *1 add hl, de ; *2 rock_render_line_setrockchr: // int ptn = (render_pos + logical_row * 2) & 0x03; // rock_render_line_rock_chr = &rock_chr[ptn][0]; push hl ld a, 0x03 and a, l ld hl, _rock_chr ld d, 0 ld e, a add hl, de ; *1 add hl, de ; *2 ld (_rock_render_line_rock_chr), hl pop hl rock_render_line_setroctbl: // int rocky = (render_pos + logical_row * 2 + 1) / 4; // rock_render_line_rock_tbl = &rock_tbl[rocky][0]; inc hl ; +1 srl h rr l ; ÷ 2 srl h rr l ; ÷ 4 ld de, _rock_tbl ex de, hl add hl, de add hl, de add hl, de ld (_rock_render_line_rock_tbl), hl // render_buf_odd[phys_y][dx] // render_buf_even[phys_y][dx] // 奇数判定のため最下位BITチェック ld a, (_render_pos) bit 0, a jr z, rock_render_line_setevenaddr rock_render_line_setoddaddr: ld bc, _render_buf_odd jr rock_render_line_setrenderbuf rock_render_line_setevenaddr: ld bc, _render_buf_even rock_render_line_setrenderbuf: ld a, (ix+2) ; phys_y ld h, 0 ld l, a push de ld d, h ld e, l add hl, hl ; *2 add hl, hl ; *4 add hl, de ; *5 add hl, hl ; *10 add hl, de ; *11 add hl, hl ; *22 pop de add hl, bc // memset(&render_buf_odd[phys_y][1], 0, RENDER_DX - 2) inc hl push hl ld b,RENDER_DX-2rock_render_line_rockclr: ld (hl), 0 inc hl djnz rock_render_line_rockclr pop hl rock_render_line_rockdraw: // for (dx = 1; dx <= RENDER_DX - 2; dx++) ld b, RENDER_DX-2 ld c, 0x80 // if (rock_tbl[rocky][rockx] & bit) ld de, (_rock_render_line_rock_tbl) rock_render_line_rockloop: ld a, (de) and a, c jp z, rock_render_line_nextrock push de // memcpy(&render_buf_odd[phys_y][dx], &rock_chr[ptn][0], 2); ld de, (_rock_render_line_rock_chr) ld a, (de) ld (hl), a inc de inc hl ld a, (de) ld (hl), a dec hl pop de rock_render_line_nextrock: // bit >>= 1; srl c // if (!bit) { bit = 0x80; rockx++; } jr nz, rock_render_line_chkloop rock_render_line_nexttbl: ld c, 0x80 inc de rock_render_line_chkloop: inc hl djnz rock_render_line_rockloop ret ._rock_render_line_rock_chr defw 0._rock_render_line_rock_tbl defw 0 #endasm}いつものように、下記でビルドします。 zcc +zx81 map.c key.c main.c -createa-app試してみると・・・28行スクロールにかかる時間が1秒になりました。うん、この速さならゲームとして使えそうです。上下スクロールの基礎がこれで固まった気がします。
2026.04.09
コメント(0)
アセンブラ化を進めていきます。まずは、全体を再描画するmap_drawを試してみます。ーーーーーmap.cpp(変更点)// ----------------------------------------------------------// VRAM へ描画// start_y を先頭として、論理 0..RENDER_DY-1 を順に出力// ----------------------------------------------------------void map_draw(void){/* int logical_row; int src_y; unsigned char *curr_bufp, *top_bufp; unsigned char *pos = getscrnaddr(0, 4); if (render_pos & 1) { src_y = start_y; curr_bufp = &render_buf_odd[src_y][0]; top_bufp = &render_buf_odd[0][0]; } else { src_y = start_y; curr_bufp = &render_buf_even[src_y][0]; top_bufp = &render_buf_even[0][0]; } for (logical_row = 0; logical_row < RENDER_DY; logical_row++) { memcpy(pos, curr_bufp, RENDER_DX); pos += D_DX; curr_bufp += RENDER_DX; src_y++; if (src_y >= RENDER_DY) { src_y = 0; curr_bufp = top_bufp; } }*/ // 描画開始位置をHLレジスタに取得 getscrnaddr(0, 4); #asm ld (_map_draw_scrnaddr),hl // 奇数判定のため最下位BITチェック ld a,(_render_pos) bit 0,a jr z,map_draw_setevenaddr map_draw_setoddaddr: ld hl,_render_buf_odd jr map_draw_settopbuf map_draw_setevenaddr: ld hl,_render_buf_even map_draw_settopbuf: ld (_map_draw_topbuf),hl map_draw_setsrcy: ld a,(_start_y) ld (_map_draw_srcy),a map_draw_setcurrbuf: ; HL = start_y * RENDER_DX ld d,0 ld e,a ld hl,0 add hl, de ; *1 0 0001 add hl, hl ; *2 0 0010 add hl, hl ; *4 0 0100 add hl, de ; *5 0 0101 add hl, hl ; *10 0 1010 add hl, de ; *11 0 1011 add hl, hl ; *22 1 0110 ld de,(_map_draw_topbuf) add hl,de; ld (_map_draw_currbuf),hl map_draw_buf2scrn: ld b,RENDER_DY ld de,(_map_draw_scrnaddr); ld hl,(_map_draw_currbuf) ld a,(_map_draw_srcy) map_draw_linecopy: push bc ld bc,RENDER_DX ldir pop bc inc a cp a,RENDER_DY jr nz,map_draw_nextline ld a,0 ld hl,(_map_draw_topbuf) map_draw_nextline: push hl ld hl,D_DX-RENDER_DX add hl,de ex de,hl pop hl djnz map_draw_linecopy ret ._map_draw_scrnaddr defw 0._map_draw_topbuf defw 0;._map_draw_currbuf defw 0._map_draw_srcy defb 0 #endasm}いつものように、下記でビルドします。 zcc +zx81 map.c key.c main.c -createa-app試してみると・・・28行スクロールにかかる時間が3秒→2秒になりました。あまり期待していなかっただけに、正直嬉しい!アセンブラ化は少々面倒ではありますが、目に見えて速度が向上すると、モチベーションが上がります!特にZX81では顕著に差が出るので、この作業は病みつきになります。さあ、皆さん!今こそ、この言葉を思い出すときです! Your Computer 誌「ZX81は喜びと失望の両方だ」 シンクレア ZX81 - Wikipedia 「評価」より
2026.04.07
コメント(0)

1行ずらしてコピーする事(行コピー方式)で高速化しましたがさらなる方法としてリングバッファでの描画の実装を試みます。1行上にスクロールする場合を図解してみました。 0~22行を1~23行にコピー後に0行目のみを再描画する事で、 全行描画よりも高速化が可能となりました。 しかし、23行分をコピーする事はZX81にとっては痛いタイムロスとなります。 次に、今回紹介する描画開始位置をずらしていく方式です。 描画開始位置であるstart_yを+1とし、元々start_yの行のみを再描画するものです。 メリットは行コピーを行わないため速度向上が見込まれること。 デメリットはソースが少し複雑化する事です。以下、ソースコードとなります。ーーーーーmap.cpp/* * ZX81 Map Handling Module program */ #include <stdio.h>#include <string.h> #include "map.h" unsigned char rock_tbl[19][3] = {// screen{0x80,0x00,0x00},{0x40,0x00,0x00},{0x20,0x00,0x00},{0x10,0x00,0x00},{0x08,0x00,0x00},{0x04,0x00,0x00},{0x02,0x00,0x00},{0x01,0x00,0x00},{0x00,0x80,0x00},{0x00,0x40,0x00},{0x00,0x20,0x00},{0x00,0x10,0x00},// scroll{0x00,0x08,0x00},{0x00,0x04,0x00},{0x00,0x02,0x00},{0x00,0x01,0x00},{0x00,0x00,0x80},{0x00,0x00,0x40},{0x00,0x00,0x20},}; unsigned char rock_chr[4][2] ={{0x81, 0x05},{0x80, 0x01},{0x03, 0x00},{0x87, 0x04}}; int start_y = 0; // Render buffers for smooth 2x2 tile scrolling (even/odd frames)#define RENDER_DX 22#define RENDER_DY 24unsigned char render_buf_even[RENDER_DY][RENDER_DX];unsigned char render_buf_odd[RENDER_DY][RENDER_DX]; int render_pos; // screen address #define D_FILE 0x400c#define D_DX 33 // 32+1(CR)#define D_DY 24 unsigned char* getscrnaddr(int posy, int posx){#asm ld ix,2 add ix,sp ; push de ld d, (ix+2) ; d=posy ld e, (ix+0) ; e=posx call getscrnaddr; pop de ret getscrnaddr:; IN: D=posy, E=posx; OUT: HL= screen address; push bc ; HL = posy * 33 ld h, 0 ld l, d add hl, hl ; *2 add hl, hl ; *4 add hl, hl ; *8 add hl, hl ; *16 add hl, hl ; *32 ld b, 0 ld c, d add hl, bc ; +1 -> *33 ; HL += posx ld b, 0 ld c, e ; c=posx add hl, bc ; HL += D_FILE ld bc,(D_FILE) inc bc add hl, bc ; pop bc ret #endasm} void rock_render_line(int phys_y, int logical_row){ int dx; unsigned char bit = 0x80; int rockx = 0; int rocky = (render_pos + logical_row * 2 + 1) / 4; int ptn = (render_pos + logical_row * 2) & 0x03; // 0 or 2 if (render_pos & 0x01) { memset(&render_buf_odd[phys_y][1], 0, RENDER_DX - 2); for (dx = 1; dx <= RENDER_DX - 2; dx++) { if (rock_tbl[rocky][rockx] & bit) memcpy(&render_buf_odd[phys_y][dx], &rock_chr[ptn][0], 2); bit >>= 1; if (!bit) { bit = 0x80; rockx++; } } } else { memset(&render_buf_even[phys_y][1], 0, RENDER_DX - 2); for (dx = 1; dx <= RENDER_DX - 2; dx++) { if (rock_tbl[rocky][rockx] & bit) memcpy(&render_buf_even[phys_y][dx], &rock_chr[ptn][0], 2); bit >>= 1; if (!bit) { bit = 0x80; rockx++; } } }} // ----------------------------------------------------------// 初期化// ----------------------------------------------------------void map_init(void){ int logical_row; int phys_y; render_pos = 0; start_y = 0; // ----------------------------- // even バッファの初期化(render_pos=0) // ----------------------------- for (logical_row = 0; logical_row < RENDER_DY; logical_row++) { render_buf_even[logical_row][0] = 0x82; render_buf_even[logical_row][RENDER_DX-1] = 0x81; phys_y = (start_y + logical_row) % RENDER_DY; rock_render_line(phys_y, logical_row); } // ----------------------------- // odd バッファの初期化(render_pos-1 にして描く) // ----------------------------- render_pos--; for (logical_row = 0; logical_row < RENDER_DY; logical_row++) { render_buf_odd[logical_row][0] = 0x07; render_buf_odd[logical_row][RENDER_DX-1] = 0x84; phys_y = (start_y + logical_row) % RENDER_DY; rock_render_line(phys_y, logical_row); } // 初期状態は render_pos++ に戻す render_pos++;} // ----------------------------------------------------------// スクロール// dy < 0 : 上へ 1dot// dy > 0 : 下へ 1dot//// ルール:// render_pos++ の結果が奇数なら start_y++// render_pos-- の結果が偶数なら start_y--// ----------------------------------------------------------void map_scroll(int dy){ int phys_y; int logical_row; if (dy < 0) { if (render_pos == 0) return; // 上へ 1dot render_pos--; // 結果が偶数なら start_y を -1 if ((render_pos & 1) == 0) start_y = (start_y - 1 + RENDER_DY) % RENDER_DY; logical_row = 0; // 画面の最上段が新規 phys_y = start_y; // 論理 0 行目の物理行 rock_render_line(phys_y, logical_row); } else if (dy > 0) { if (render_pos == 28) return; // 下へ 1dot render_pos++; // 結果が奇数なら start_y を +1 if (render_pos & 1) start_y = (start_y + 1) % RENDER_DY; logical_row = RENDER_DY - 1; // 画面の最下段が新規 phys_y = (start_y + logical_row) % RENDER_DY; rock_render_line(phys_y, logical_row); }} // ----------------------------------------------------------// VRAM へ描画// start_y を先頭として、論理 0..RENDER_DY-1 を順に出力// ----------------------------------------------------------void map_draw(void){ int logical_row; int src_y; unsigned char *curr_bufp, *top_bufp; unsigned char *pos = getscrnaddr(0, 4); if (render_pos & 1) { src_y = start_y; curr_bufp = &render_buf_odd[src_y][0]; top_bufp = &render_buf_odd[0][0]; } else { src_y = start_y; curr_bufp = &render_buf_even[src_y][0]; top_bufp = &render_buf_even[0][0]; } for (logical_row = 0; logical_row < RENDER_DY; logical_row++) { memcpy(pos, curr_bufp, RENDER_DX); pos += D_DX; curr_bufp += RENDER_DX; src_y++; if (src_y >= RENDER_DY) { src_y = 0; curr_bufp = top_bufp; } }}※他のソースの変更はありません。いつものように、下記でビルドします。 zcc +zx81 map.c key.c main.c -createa-appしかし、多少なりとも描画速度が速くなる事を期待したのですが。。。結果は行コピーとほぼ変わらない程度でした。非常に残念です。開始位置を計算する割り算や余りなどの計算が増えたためと予想します。これは、まあ仕方ないですね。あとはアセンブラ化して高速化してみます。
2026.04.06
コメント(0)
前回のソースでは描画があまりにも遅かったのでまずは、画面バッファを移動方向に合わせて-1なら1行下にコピーして最上段の1行のみを描画+1なら1行上にコピーして最下段の1行のみを描画としてみました。 ーーーーーmap.cpp/* * ZX81 Map Handling Module program */ #include <stdio.h>#include <string.h> #include "map.h" unsigned char rock_tbl[19][3] = {// screen{0x80,0x00,0x00},{0x40,0x00,0x00},{0x20,0x00,0x00},{0x10,0x00,0x00},{0x08,0x00,0x00},{0x04,0x00,0x00},{0x02,0x00,0x00},{0x01,0x00,0x00},{0x00,0x80,0x00},{0x00,0x40,0x00},{0x00,0x20,0x00},{0x00,0x10,0x00},// scroll{0x00,0x08,0x00},{0x00,0x04,0x00},{0x00,0x02,0x00},{0x00,0x01,0x00},{0x00,0x00,0x80},{0x00,0x00,0x40},{0x00,0x00,0x20},}; unsigned char rock_chr[4][2] ={{0x81, 0x05},{0x80, 0x01},{0x03, 0x00},{0x87, 0x04}}; // Render buffers for smooth 2x2 tile scrolling (even/odd frames)#define RENDER_DX 22#define RENDER_DY 24unsigned char render_buf_even[RENDER_DY][RENDER_DX];unsigned char render_buf_odd[RENDER_DY][RENDER_DX]; int render_pos; // screen address #define D_FILE 0x400c#define D_DX 33 // 32+1(CR)#define D_DY 24 unsigned char* getscrnaddr(int posy, int posx){#asm ld ix,2 add ix,sp ; push de ld d, (ix+2) ; d=posy ld e, (ix+0) ; e=posx call getscrnaddr; pop de ret getscrnaddr:; IN: D=posy, E=posx; OUT: HL= screen address; push bc ; HL = posy * 33 ld h, 0 ld l, d add hl, hl ; *2 add hl, hl ; *4 add hl, hl ; *8 add hl, hl ; *16 add hl, hl ; *32 ld b, 0 ld c, d add hl, bc ; +1 -> *33 ; HL += posx ld b, 0 ld c, e ; c=posx add hl, bc ; HL += D_FILE ld bc,(D_FILE) inc bc add hl, bc ; pop bc ret #endasm} void rock_render_line(int phys_y){ int dx; unsigned char bit = 0x80; int rockx = 0; int rocky = (render_pos + phys_y * 2 + 1) / 4; int ptn = (render_pos + phys_y * 2) & 0x03; // 0 or 2 if (render_pos & 0x01) { memset(&render_buf_odd[phys_y][1], 0, RENDER_DX - 2); for (dx = 1; dx <= RENDER_DX - 2; dx++) { if (rock_tbl[rocky][rockx] & bit) memcpy(&render_buf_odd[phys_y][dx], &rock_chr[ptn][0], 2); bit >>= 1; if (!bit) { bit = 0x80; rockx++; } } } else { memset(&render_buf_even[phys_y][1], 0, RENDER_DX - 2); for (dx = 1; dx <= RENDER_DX - 2; dx++) { if (rock_tbl[rocky][rockx] & bit) memcpy(&render_buf_even[phys_y][dx], &rock_chr[ptn][0], 2); bit >>= 1; if (!bit) { bit = 0x80; rockx++; } } }} // ----------------------------------------------------------// 初期化// ----------------------------------------------------------void map_init(void){ int phys_y; render_pos = 0; // ----------------------------- // even バッファの初期化(render_pos=0) // ----------------------------- for (phys_y = 0; phys_y < RENDER_DY; phys_y++) { render_buf_even[phys_y][0] = 0x82; render_buf_even[phys_y][RENDER_DX-1] = 0x81; rock_render_line(phys_y); } // ----------------------------- // odd バッファの初期化(render_pos-1 にして描く) // ----------------------------- render_pos--; for (phys_y = 0; phys_y < RENDER_DY; phys_y++) { render_buf_odd[phys_y][0] = 0x07; render_buf_odd[phys_y][RENDER_DX-1] = 0x84; rock_render_line(phys_y); } // 初期状態は render_pos++ に戻す render_pos++;} // ----------------------------------------------------------// スクロール// dy < 0 : 上へ 1dot// dy > 0 : 下へ 1dot// ----------------------------------------------------------void map_scroll(int dy){ int phys_y; if (dy < 0) { if (render_pos == 0) return; // 上へ 1dot render_pos--; // 結果が偶数ならバッファを1行上にコピー(memcpyはコピーが重なるためNG) if ((render_pos & 1) == 0) { memmove(&render_buf_even[1][0], &render_buf_even[0][0], RENDER_DX * (RENDER_DY - 1)); memmove(&render_buf_odd[1][0], &render_buf_odd[0][0], RENDER_DX * (RENDER_DY - 1)); } phys_y = 0; rock_render_line(phys_y); } else if (dy > 0) { if (render_pos == 28) return; // 下へ 1dot render_pos++; // 結果が奇数ならバッファ全体を1行下にコピー if (render_pos & 1) { memcpy(&render_buf_even[0][0], &render_buf_even[1][0], RENDER_DX * (RENDER_DY - 1)); memcpy(&render_buf_odd[0][0], &render_buf_odd[1][0], RENDER_DX * (RENDER_DY - 1)); } phys_y = RENDER_DY - 1; rock_render_line(phys_y); }} // ----------------------------------------------------------// VRAM へ描画// ----------------------------------------------------------void map_draw(void){ int phys_y; unsigned char *curr_bufp; unsigned char *pos = getscrnaddr(0, 4); if (render_pos & 1) { curr_bufp = &render_buf_odd[0][0]; } else { curr_bufp = &render_buf_even[0][0]; } for (phys_y = 0; phys_y < RENDER_DY; phys_y++) { memcpy(pos, curr_bufp, RENDER_DX); pos += D_DX; curr_bufp += RENDER_DX; }} 28ドット分スクロールした結果前回が14秒、今回が3秒程度かかってります。 約4倍高速化された事となります。
2026.04.03
コメント(0)

次に、岩のみですが1dot単位で上下描画するためのmap.hとmap.cを作成しました。岩のイメージは以前から変更していません。一面のマップも準備していますがとりあえずは左端から行をずらしながら右端まで描画するようにしています。ーーーーmap.h/* * ZX81 Map Handling Module header */ #ifndef _ZX81_MAP_H#define _ZX81_MAP_H // screen functionsunsigned char* getscrnaddr(int posy, int posx); // Map functionsvoid map_init(void);void map_scroll(int dy);void map_draw(void); #endif // _ZX81_MAP_Hーーーーmap.cpp/* * ZX81 Map Handling Module program */ #include <stdio.h>#include <string.h> #include "map.h" unsigned char rock_tbl[19][3] = {// screen{0x80,0x00,0x00},{0x40,0x00,0x00},{0x20,0x00,0x00},{0x10,0x00,0x00},{0x08,0x00,0x00},{0x04,0x00,0x00},{0x02,0x00,0x00},{0x01,0x00,0x00},{0x00,0x80,0x00},{0x00,0x40,0x00},{0x00,0x20,0x00},{0x00,0x10,0x00},// scroll{0x00,0x08,0x00},{0x00,0x04,0x00},{0x00,0x02,0x00},{0x00,0x01,0x00},{0x00,0x00,0x80},{0x00,0x00,0x40},{0x00,0x00,0x20},}; // Render buffers for smooth 2x2 tile scrolling (even/odd frames)#define RENDER_DX 22#define RENDER_DY 24unsigned char render_buf_even[RENDER_DY][RENDER_DX];unsigned char render_buf_odd[RENDER_DY][RENDER_DX]; int render_pos; // screen address #define D_FILE 0x400c#define D_DX (32 + 1)#define D_DY 24 unsigned char* getscrnaddr(int posy, int posx){#asm ld ix,2 add ix,sp ; push de ld d, (ix+2) ; d=posy ld e, (ix+0) ; e=posx call getscrnaddr; pop de ret getscrnaddr:; IN: D=posy, E=posx; OUT: HL= screen address; push bc ; HL = posy * 33 ld h, 0 ld l, d add hl, hl ; *2 add hl, hl ; *4 add hl, hl ; *8 add hl, hl ; *16 add hl, hl ; *32 ld b, 0 ld c, d add hl, bc ; +1 -> *33 ; HL += posx ld b, 0 ld c, e ; c=posx add hl, bc ; HL += D_FILE ld bc,(D_FILE) inc bc add hl, bc ; pop bc ret #endasm} void rock_render_at(int dy, int dx){ // 2×2 ブロックをbufp に書き込む char rock_chr[4][2] = { {0x81, 0x05},{0x80, 0x01}, {0x03, 0x00},{0x87, 0x04} }; int ptn; if (render_pos & 0x01) { ptn = (render_pos + dy*2) & 0x03; memcpy(&render_buf_odd[dy][dx], &rock_chr[ptn][0], 2); } else { ptn = (render_pos + dy*2) & 0x02; memcpy(&render_buf_even[dy][dx], &rock_chr[ptn][0], 2); }} void rock_render_all(void){ int dy, dx; unsigned char bit; int rockx, rocky; // 描画位置により、描画エリアを初期化 if (render_pos & 0x01) { memset(&render_buf_odd[0][0], 0x00, sizeof(render_buf_odd)); for (dy = 0; dy < RENDER_DY; dy++) { render_buf_odd[dy][0] = 0x07; render_buf_odd[dy][RENDER_DX-1] = 0x84; } } else { memset(&render_buf_even[0][0], 0x00, sizeof(render_buf_even)); for (dy = 0; dy < RENDER_DY; dy++) { render_buf_even[dy][0] = 0x82; render_buf_even[dy][RENDER_DX-1] = 0x81; } } // 岩を描画 for (dy = 0; dy < RENDER_DY; dy++) { bit = 0x80; rockx = 0; rocky = (render_pos + (dy*2)+1) / 4; for (dx = 1; dx <= RENDER_DY-2; dx++) { // bitが有効ならば岩を描画 if (rock_tbl[rocky][rockx] & bit) { rock_render_at(dy, dx); } // bitをShift。最後のbitまで参照したら次のテーブルへ移動 bit = bit >> 1; if (bit == 0) { bit = 0x80; rockx++; } } }} void map_init(void){ render_pos = 0; rock_render_all();} void map_scroll(int dy){ if (dy < 0) { if (render_pos == 0) { return; } render_pos--; } else { if (render_pos == 28) { return; } render_pos++; } rock_render_all();} void map_draw(void){ int dy; unsigned char *pos; pos = getscrnaddr(0, 4); if (render_pos & 0x01) { for (dy = 0; dy < D_DY; dy++) { memcpy(pos, &render_buf_odd[dy][0], RENDER_DX); pos += D_DX; } } else { for (dy = 0; dy < D_DY; dy++) { memcpy(pos, &render_buf_even[dy][0], RENDER_DX); pos += D_DX; } }}ーーーーmap.cpp#include <stdio.h>#include <string.h>#include "key.h"#include "map.h"void main(){ unsigned char key; unsigned char *pos; map_init(); map_draw(); while (1) { key = key_pressed(); if (key == 0) { continue; } if (key & KBIT_ENT) { break; } if (key & KBIT_UP) { map_scroll(-1); } else if (key & KBIT_DN) { map_scroll(1); } map_draw(); }}ここまでで、key.c、key.h、map.c、map.h、main.cが出そろいました。最後に下記コマンドでビルド(※)して、a.pファイルを作成します。 zcc +zx81 key.c map.c main.c -create-app※zccについては、以前の記事を参考にしてください。 z88dkセットアップ | ZX81の回想禄 - 楽天ブログ起動すると下記の表示になりますので、矢印の上下キーにて、1ドットスクロールする事を確認できます。ただ、書き換えに0.5秒近くかかっていて非常に遅いです。原因は全体のイメージを毎回更新しているのが原因のようなのでバッファーの表示開始位置をずらして、必要な箇所だけ更新する方法を検討したいと思います。
2026.04.02
コメント(0)
まずは、key.hとkey.cを作成しました。リアルタイムキー入力については、以前紹介したので詳細な説明は省きますがI/Oから直接キー入力を参照しています。 Mine Sweeper その3 | ZX81の回想禄 - 楽天ブログーーーーkey.h/* * ZX81 Keyboard Handling API header */ #ifndef _ZX81_KEY_H#define _ZX81_KEY_H // Bitmask flags for pressed keys (logical game input)#define KBIT_UP (1 << 0) // Up arrow (SHIFT + 7)#define KBIT_DN (1 << 1) // Down arrow (SHIFT + 6)#define KBIT_LT (1 << 2) // Left arrow (SHIFT + 5)#define KBIT_RT (1 << 3) // Right arrow (SHIFT + 8)#define KBIT_ENT (1 << 4) // ENTER#define KBIT_SPC (1 << 5) // SPACE // Key functionsunsigned char key_pressed(void); #endif // _ZX81_KEY_Hーーーーkey.c/* * ZX81 Keyboard Handling API program */ #include "key.h" /* * key_pressed: * Scan keyboard and return bitmask of pressed keys. */ unsigned char key_pressed(void){#asm key_pressed_read: ld a,0xf7 in a,(0xfe) ld d,a ld a,0xef in a,(0xfe) ld e,a ld a,0xbf in a,(0xfe) ld h,a ld a,0x7f in a,(0xfe) ld l,a key_pressed_bitset: ld a,0 key_pressed_up: bit 3,e jr nz,key_pressed_down or a,#KBIT_UP key_pressed_down: bit 4,e jr nz,key_pressed_left or a,#KBIT_DN key_pressed_left: bit 4,d jr nz,key_pressed_right or a,#KBIT_LT key_pressed_right: bit 2,e jr nz,key_pressed_enter or a,#KBIT_RT key_pressed_enter: bit 0,h jr nz,key_pressed_spc or a,#KBIT_ENT key_pressed_spc: bit 0,l jr nz,key_pressed_bitret or a,#KBIT_SPC key_pressed_bitret: ld h,0 ld l,a ret #endasm}いつもの矢印キーとSPCとEnterの6つのキーの押下げを判定可能です。
2026.04.01
コメント(0)
前回までに、検討とテスト動作が確認できたので少しずつではありますが、実際のプログラムに着手していきます。テスト動作では1ドット単位の上方向のスクロールのみでしたのでまずは、1ドット単位の上下スクロールを作成したいと思います。計画としては、main関数からマップ用の関数を順次呼び出す事とします。・map_init() … マップの初期化・map_scroll() … 上下スクロール・map_draw() … マップ描画他にも上下スクロール用のキー押下関数も準備します。・key_pressed() … キー押下(KBIT_XXXをBITで返す)テスト用のメイン関数はこんなものでしょうか。void main(){ unsigned char key; map_init(); map_draw(); while (1) { key = key_pressed(); if (key == 0) { continue; } if (key & KBIT_ENT) { break; } if (key & KBIT_UP) { map_scroll(-1); } else if (key & KBIT_DN) { map_scroll(1); } map_draw(); }}上下キーの押下に合わせて、スクロールする事を確認するのが当面の目標です。
2026.03.31
コメント(0)
年度末の多忙につき、ZX81(正確にはエミュレータですが)になかなか触れる事ができません。やりたい事は増える一方なのですが。。。【Rally-Xを他機種で】先日完成したRally-ZXも、他機種で動かしたい願望も強まっています。最初に触れたMZシリーズでも動かしてみたい。では、その実現性はというと。【MZシリーズとの相性】ZX81の文字コードはASCIIと異なる独自のもので簡易グラフィック用の2×2のブロック文字があったりしますがMZ-80C、K、K2Eなども同様です。(2×2のブロック文字は隙間がありますが)特定のアドレスに値を書き込む事で文字を表示するのも同じです。また、開発に使ったz88dkはMZ-80シリーズに対応しているようです。なんだかいけそうな気がします。【他には】 MZ-700で色をつけてみたり、MZ-1500 でPCGを使ったり。。。BEEP音があるので効果音も付けられそうですが、音関係は未経験だったりします。また、MZのエミュレータの使い方なども調べる必要がありそうですね。早く暇(4月?)にならないかな。
2026.03.26
コメント(0)

毎回ですが、ゲーム開発が16Kバイトでは厳しいためZX81に何とか16Kバイトを超える設定にできないものかEightyOneの設定を調べたら32K、48KのRAMパックが選択は可能でした。「Options」-「Hardware」 この設定により、48KRAMになるかと思いきや...メモリの上限は4004Hと4005Hに格納されているので確認してみると16K以上を設定しても、8000Hとしかなりません。 また、実際に8000H前後を見ると8000H以降は07Hとなっていてアクセスできないようです。 どうやら、別ハードでのハイレゾ対応のための設定のようです。なので、実際に設定すると本体のRAMは16KRAMまでしか認識しません。 0000H~3FFFH ROM 4000H~43FFH 本体のみ(1KRAM) or 4000H~7FFFH 本体+拡張RAMパック(16KRAM)Copilot様にも確認しました。その後も、Chroma81モードやZXpand+モードも検索してみましたが開発用のEightyOne Ver1.29aまたはZXpand+のROMが必要との事でしばらくWeb検索しても入手できず、試す事ができませんでした。最後に勧められたのは...「EightyOne」のソースを入手して、改造するのだ!いや、それはさすがに無理!( ノД`)シクシク…
2026.03.24
コメント(0)

またまた1Kバイトでの凄いプログラムを見つけました。youtubeでの動画投稿を紹介します。 QFrogger 1K for the ZX81残念ながら実行ファイルを見つけきれませんでしたので動画からの切り取りです。×がカエルで、上部のスコアとXXX(カエル×3)の下の5個のくぼみがゴールかと思います。ZX81は画像を含めた一部のメモリも保存できるので1Kシリーズはそれを活用しているのはわかりますが前回のK-MANもそうですが、このレベルになってくると理解を超えてきます。本当にす凄いです。
2026.01.07
コメント(0)

続いて、横方向の船の移動も加えてみます。➃から右+上となった場合は、③3dotまで23度傾きながら移動➁に入ったら、45度傾きながら移動0度、23度、45度の傾きで船を半透明にして重ねてみました。条件文で縦横の慣性を判定しようと思いましたが、dotが荒い場合はテーブル化した方が省メモリかもしれませんね。
2025.12.08
コメント(0)

次は船の移動を考えていきます。実際のRiver Patrolとはドット数も文字数も異なるためプレイした感覚で決めていきます。まずは、縦方向の移動から。イメージ図を作成してみました。➀26dotのエリア…侵入できません。➁4dotのエリア …加速中は1dot上に船移動と1dot下に全体スクロールを交互にします。 4dotの1番上に到達したら、1dot下に全体スクロールを毎回します。 …停止中は船を1dot下に移動します。(2回に1回?) ※ただし、4dotの1番上に到達していた場合は 慣性で全体を4dotまで下にスクロールします。➂3dotのエリア …加速中は1dot上に船移動します。(2回に1回?) …停止中は1dot下に船移動と1dot上に全体スクロールを交互にします。 3dotの1番下に到達したら、1dot上に全体スクロールを毎回します。⑤4dotのエリア…侵入できません。ドット数が荒い分、スムーズさには欠けそうですがどんな感じになりますやら。
2025.12.04
コメント(0)
さて、早く次のStepへ進めばいいのですがどうしても回転が見たかったので、またまたテスト動画をアップしました。 RiverPatrol test3左右キーによる船の傾き、上キーによる上流移動(慣性は無視)スペースキーを押す事で何もない所での船の360度回転までを作りこみいそいそと動画を撮った次第です。速度とチラつきはさておき、なんとなく回転しているように見えなくもない。(と思いたいw)これで、心置きなく次へ進めそうです。
2025.12.01
コメント(0)

以前試しに作った船のドットパターンが大きいと判断したので少し小さめに再調整しました。斜めのパターンは如何ともし難いですが…これを元に、赤い部分を中心として360度回転用のパターンを検討しました。さて、どう見えるのうやら。この下に渦のパターンを重ねるのは絶望でしかありませんがもうやってみるしかないですね。
2025.11.29
コメント(0)
全259件 (259件中 1-50件目)


![]()