IchigoJam でジャンプアクションゲームを作ってみた。
※IchigoJamはjig.jpの登録商標です。
ルール
主人公を操作して、トゲに落ちないようにしつつ、うまく足場を渡り、ゴールにたどり着ければクリア。
操作方法
- 左矢印キー:左に進む
- 右矢印キー:右に進む
- スペースキー:ジャンプ
- Xキー:最初からやり直す
HetaPad での操作も可能である。
プログラム
本体
10 ' ジャンプアクション
20 CLS:POKE#7E0,8,8,28,28,62,62,127,127:FORX=0TO31:POKE#900+X,6:POKE#BE0+X,6:NEXT:FORY=1TO22:POKE#900+Y*32,6:POKE#91F+Y*32,6:NEXT:M="AVDAUDfVBIVFIUF0VDUVIUUIUPAPOAJNAAMDJH@OG@TH@ZFDZG@ZHD"
30 X=PEEK(M):Y=PEEK(M+1)&31:L=PEEK(M+2)&31:IFX-34P=#FC-#F6*!(X&32):X=X&31:FORI=0TOL:POKE#900+Y*32+X+I,P:NEXT:M=M+3:GOTO30ELSELC27,7:?"GOAL";:E=0:F=-8
40 X=16:Y=160:V=0:D=0:N=0:G=0:J=0:T=TICK()
50 K=BTN(-1):IFK&16AND!JAND(SCR(X/8,Y/8+1)=6ORSCR((X+7)/8,Y/8+1)=6)V=-14
60 J=K&16:V=V+(V<15):Y=Y+V/2:IFSCR(X/8,Y/8)=6ORSCR((X+7)/8,Y/8)=6Y=(Y+7)&~7:V=0
70 L=SCR(X/8,(Y+7)/8):R=SCR((X+7)/8,(Y+7)/8):IFL=6ORR=6Y=Y&~7:V=0:IFX>215ANDY<49PLAY"O4G8L16BABAB#4":G=1
80 IFL=#FCORR=#FCY=Y&~7:BEEP80,30:N=1
90 IFK&3=1X=X-2:D=1:IFSCR(X/8,Y/8)=6ORSCR(X/8,(Y+7)/8)=6X=(X+7)&~7
100 IFK&3=2X=X+2:D=0:IFSCR((X+7)/8,Y/8)=6ORSCR((X+7)/8,(Y+7)/8)=6X=X&~7
110 P=#F4*N+(#FB+D*2)*!N:IF[0]=#4591Q=USR(#800,0)ELSELCE/8,F/8:?CHR$(0);:LCX/8,Y/8:?CHR$(P);:E=X:F=Y
120 IFINKEY()=88GOTO40ELSEIFNORGGOTO120
130 U=TICK():IFU=TGOTO130ELSET=U:GOTO50
描画プログラム
本体のみでも実行できるが、主人公の動きがカクカクしたものになる。
描画プログラムを使用することで、なめらかな動きを実現できる。
描画プログラムを使用する場合は、本体の1個前のファイル番号に保存して実行すること。
10 'ジャンプアクション ビョウガプログラム
20 A=#800:LET[0],#2FB7,#E001,#4505,#8082,#2000,#4770:IFUSR(#800,0)GOTO80
30 D="IADEO@KECCJCAHHH@BBBIIEN@@BB@@O@BOOHCEJFGFHHN@CNOF@@CIJGK@COK@COJ@COKNAICDJCEHHHIIHHCLAL@HBE@@BBBBF@BDAMFMANOKMA@GBD@LD@COAI@HBB@GBE@ED@CDGHBD@BNLD@CLGDBD@JCLG@GFALGOALEBANOEMA@DDJ@@O@@IOHBF"
40 GOSUB130
50 D="JBALJCE@HHIAHHAHH@EIH@O@KMN@NBNANC@IDBAHMD@DKDBBJCLB@HIKAHLJ@HEB@AIKAH@GBBA@D@AAD@@DKLAJG@AB@J@@DB@@M@EJG@@IDB@FM@B@CCAB@JAJG@@@DB@AM@AB@JEJG@G@DG"
60 GOSUB130
70 GOTO120
80 D="IADEIG@O@@@@OIAOGMGA@FL@GNLB@CMEDO@MHCIEFO@M@ADFONHFHEBHIBDOHCMHJO@NICHH@HOBHN@HACHH@OO@LBIH@CMEJO@OHCMELO@ODBHGJADGBCB@@G@@AA@GOMAGNEOOACOGGE@@CJIHJADFICGGGE@@@CLF@H@@BB@FCCEFOF@@BC@HLH@@BA"
90 GOSUB130
100 D="HBBC@@LH@@HE@H@E@HOMAFOMOB@CMGJO@OHCMGLO@OBCIJNO@LBCIKOO@LCGNFNANCAC@F@FBNONHFHBD@DAFAFCLI@E@BACEGCE@@KJIFACMGCE@@AF@GKJIFAMHIIMHIBCH@LFA@BAHBAILAJCH@LFA@HALIBAHBBCH@LFAB@ALEBAHBJCH@LFABHBH@"
110 GOSUB130
120 LRUNFILE()+1
130 C=PEEK(D):IFC=34RETURNELSEPOKEA,C%16*16+PEEK(D+1)%16:A=A+1:D=D+2:GOTO130
ライセンス
このプログラム (本体および描画プログラム) は、CC BY 4.0 でライセンスする。
このプログラム (改造したものを含む) を公開の場で利用する際は、出典を示していただけると嬉しい。
これは、Qiitaの利用規約に基づくプログラムの利用を禁止するものではない。
実行結果例
IchigoJam Q でも実行はできるが、ゲームの速度がゆっくりとしたものになる。
IchigoJam R では、満足のいく速度で実行できる。
以下の動画は、IchigoJam R で実行した様子である。
最初は本体のみで実行し、クリア後描画プログラムを読み込んで実行する。
解説
本体
10行目:タイトル
10 ' ジャンプアクション
FILES
対応のタイトル行である。
20~30行目:マップ初期化
20 CLS:POKE#7E0,8,8,28,28,62,62,127,127
画面を初期化し、#FC
にトゲのキャラクターパターンを設定する。
FORX=0TO31:POKE#900+X,6:POKE#BE0+X,6:NEXT FORY=1TO22:POKE#900+Y*32,6:POKE#91F+Y*32,6:NEXT
画面の外周部分をレンガで埋める。
M="AVDAUDfVBIVFIUF0VDUVIUUIUPAPOAJNAAMDJH@OG@TH@ZFDZG@ZHD"
画面内側のマップデータを用意する。
マップデータは3文字1組で、順に「左端のx座標と種類」「左端のy座標」「長さ」を表す。
座標と長さは、文字のキャラクターコードの下位5ビットで表す。
最初の文字の下から6ビット目 (1-origin) は、0でレンガを、1でトゲを表す。
30 X=PEEK(M):Y=PEEK(M+1)&31:L=PEEK(M+2)&31
マップデータの組をメモリから変数に読み込む。
IFX-34P=#FC-#F6*!(X&32):X=X&31
マップデータの終わり "
でなければ、描画する文字を設定し、x座標を取り出す。
FORI=0TOL:POKE#900+Y*32+X+I,P:NEXT:M=M+3:GOTO30
指定された長さの分だけ設定した文字を画面に書き込む。
次の組の処理へ移る。
ELSELC27,7:?"GOAL";:E=0:F=-8
マップデータの描画後、ゴールの文字を用意する。
主人公を描画した座標 (消去処理用) を初期化する。
40行目:状態の初期化
40 X=16:Y=160:V=0:D=0:N=0:G=0:J=0:T=TICK()
以下の変数を初期化する。
変数 | 意味 |
---|---|
X | 主人公のx座標 (ピクセル) |
Y | 主人公のy座標 (ピクセル) |
V | 主人公の落下速度 (0.5ピクセル/フレーム) |
D | 主人公の向き (0:右、1:左) |
N | ゲームオーバーフラグ |
G | ゲームクリア (ゴール) フラグ |
J | 前フレームにジャンプボタンが押されていたか |
T | 現フレームの時刻 |
50行目:ジャンプ処理
50 K=BTN(-1)
ボタンの状態を変数 K
に読み込む。
IFK&16AND!JAND(SCR(X/8,Y/8+1)=6ORSCR((X+7)/8,Y/8+1)=6)V=-14
以下の条件がすべて成り立つならば、主人公の落下速度を上向きの大きい値に設定する (ジャンプする)。
- 現フレームでジャンプボタンが押されている
- 前フレームでジャンプボタンが押されていない
- 主人公の左端もしくは右端の1ピクセル下がレンガである
60行目:落下・頭を打つ処理
60 J=K&16
現フレームのジャンプボタンの状態を保存する。
V=V+(V<15)
主人公を下方向に加速させる。
ただし、ブロックを突き抜けないようにするため、速度が大きくなりすぎないようにする。
Y=Y+V/2
現在の速度に応じて、主人公を落下 (もしくは上昇) させる。
IFSCR(X/8,Y/8)=6ORSCR((X+7)/8,Y/8)=6Y=(Y+7)&~7:V=0
主人公の左端もしくは右端の上端がレンガにめりこんだ場合は、めりこまない位置に主人公を下げ、上昇を止める。
70~80行目:足元の処理
70 L=SCR(X/8,(Y+7)/8):R=SCR((X+7)/8,(Y+7)/8)
主人公の左端および右端の足元のキャラクターを取得し、それぞれ変数 L
および R
に格納する。
IFL=6ORR=6Y=Y&~7:V=0
足元にレンガがあるならば、主人公をめりこまない位置まで上げ、落下を止める。
IFX>215ANDY<49PLAY"O4G8L16BABAB#4":G=1
(足元にレンガがあり、かつ) 主人公がゴールエリアに居るならば、ゴール音を再生し、ゴールフラグを立てる。
80 IFL=#FCORR=#FCY=Y&~7:BEEP80,30:N=1
足元にトゲがあるならば、主人公をめりこまない位置まで上げ、ブザー音を再生し、ゲームオーバーフラグを立てる。
90~100行目:左右移動の処理
90 IFK&3=1X=X-2:D=1:IFSCR(X/8,Y/8)=6ORSCR(X/8,(Y+7)/8)=6X=(X+7)&~7
左移動キーが押されているならば、主人公を左に動かし、向きを左に設定する。
主人公の上端もしくは下端の左端がレンガにめり込んだ場合、めり込まない位置に移動する。
100 IFK&3=2X=X+2:D=0:IFSCR((X+7)/8,Y/8)=6ORSCR((X+7)/8,(Y+7)/8)=6X=X&~7
同様に、右方向の処理を行う。
110行目:主人公の描画
110 P=#F4*N+(#FB+D*2)*!N
状態に応じ、描画するキャラクターを設定する。
ゲームオーバーフラグ N
が立っている場合、爆発 (#F4
) を描画する。
そうでない場合、左向きフラグ D
が立っているなら左向きの人 (#FD
) を、立っていないなら右向きの人 (#FB
) を描画する。
IF[0]=#4591Q=USR(#800,0)
描画プログラムが読み込まれているようであれば、描画プログラムを実行する。
ELSELCE/8,F/8:?CHR$(0);:LCX/8,Y/8:?CHR$(P);:E=X:F=Y
描画プログラムが読み込まれていないようであれば、以下の簡単な描画を実行する。
- 以前主人公が描画されていた場所の描画を消去する
- 現在の場所に主人公を描画する
- 主人公を描画した位置 (
E
,F
) を更新する
120行目:停止・再スタート処理
120 IFINKEY()=88GOTO40ELSEIFNORGGOTO120
x
が入力された場合、初期化処理に移り、ゲームを再スタートする。
そうでなく、ゲームオーバーフラグ N
またはゴールフラグ G
が立っている場合は、処理をここで止める。
130行目:ウェイト処理
130 U=TICK():IFU=TGOTO130ELSET=U:GOTO50
時刻がフレーム開始時点と同じである間、待機する。
時刻が進んだら、次のフレームの処理に移る。
描画プログラム
環境 (M0 / RV32C) を判別し、それに応じて適切なバージョンの描画プログラム (マシン語) を配列領域に格納する。
マシン語のプログラムは、2文字で1バイトを表す形式で埋め込んでいる。
最初の文字の下位4ビットがデータの上位4ビット、次の文字の下位4ビットがデータの下位4ビットである。
格納後、LRUN
により1個次のファイル番号に格納されているプログラムを実行する。
以下のプログラムで配列全体をエンコードし、最後の余計なゼロ (@@
) をカットして埋め込んだ。
10 FOR I=0 TO 202
20 ?CHR$(#40 + PEEK(#800+I)/16,#40 + PEEK(#800+I)%16);
30 NEXT
以下が、埋め込んだマシン語のソースコードである。
これらについても、本体と同様に CC BY 4.0 でライセンスする。
M0 用
ORG #800
UDATAW `0100010110010001 ' Rn - Rm (識別用)
PUSH {LR,R4,R5,R6,R7}
' 前回描いた主人公を画面から消去する
R3 = @VAR_E
R0 = [R3 + 0]W ' R0 = 消去対象X座標
R2 = 2
R1 = [R3 + R2]S ' R1 = 消去対象Y座標
R2 = 0
GOSUB @M0_DRAW
R6 = @VAR_O
R6 = [R6 + 1]W
R6 -= #E0
R6 = R6 << 3
R7 = @VRAM
R7 -= #B0
R7 -= #B0
R7 -= #A0 ' R7 = #700 に相当するアドレス
R6 = R7 + R6 ' R6 = 描画するキャラクターパターンのアドレス (RAM上)
R3 = @VAR_W
R0 = [R3 + 1]W ' R0 = 描画対象X座標
R1 = [R3 + 2]W ' R1 = 描画対象Y座標
' 既存のキャラクターパターンをゼロクリアする
R4 = R7 + 0
R5 = 8
R2 = 0
@CP_ERASE
[R4 + 0]L = R2
R4 = R4 + 4
R5 = R5 - 1
IF !0 GOTO @CP_ERASE
' X,Yのオフセットに応じてキャラクターパターンを書き込む
R4 = 7
R4 &= R1
R7 = R7 + R4
R2 = 8
R5 = 7
R5 &= R0
@CP_WRITE
R4 = [R6 + 0]
R4 = R4 << 8
R4 >>= R5
[R7 + 16] = R4 ' #E2 に右上を、#E3 に右下を書き込む
R4 = R4 >> 8
[R7 + 0] = R4 ' #E0 に左上を、#E1 に左下を書き込む
R6 = R6 + 1
R7 = R7 + 1
R2 = R2 - 1
IF !0 GOTO @CP_WRITE
' 現在の位置に主人公を描く (R0, R1 は上で座標を設定済み)
R2 = [@DRAW_PUTTERN]L
GOSUB @M0_DRAW
' 描画した位置を更新する
R2 = @VAR_W
R3 = @VAR_E
R0 = [R2 + 1]W
R1 = [R2 + 2]W
[R3 + 0]W = R0
[R3 + 1]W = R1
POP {PC,R4,R5,R6,R7}
ALIGN 4,0,0
@DRAW_PUTTERN
DATAL #E3E1E2E0
' (R0, R1) に R2 で定義されるパターンを描画する (# 右下 左下 右上 左上)
@M0_DRAW
R1 & R1
IF MI GOTO @M0_DRAW_END
PUSH {R2}
R3 = @VRAM
R2 = R0 >> 3
R3 = R3 + R2
R2 = R1 >> 3
R2 = R2 << 5
R3 = R3 + R2
R2 = 7
R0 &= R2
R1 &= R2
POP {R2}
[R3 + 0] = R2
R2 = R2 >> 8
R0 & R0
IF 0 GOTO @M0_DRAW_X1_Y0_END
[R3 + 1] = R2
@M0_DRAW_X1_Y0_END
R1 & R1
IF 0 GOTO @M0_DRAW_END
R3 += 32
R2 = R2 >> 8
[R3 + 0] = R2
R0 & R0
IF 0 GOTO @M0_DRAW_END
R2 = R2 >> 8
[R3 + 1] = R2
@M0_DRAW_END
RET
ORG #900 - 44
@VAR_E
ORG #900 - 24
@VAR_O
ORG #900 - 8
@VAR_W
ORG #900
@VRAM
RV32C 用
MODE RV32C
ORG #800
UDATAW `0100010110010001 ' R11 = 4 (識別用)
R31 = PC
R31 += -2
SP += -1
PUSH R1, 0
PUSH R31, 1
' 前回描いた主人公を画面から消去する
R10 = [R31 + #D4]W ' R10 = 消去対象X座標
R11 = [R31 + #D6]S ' R11 = 消去対象Y座標
R12 = 0
R13 = R31
GOSUB @RV32C_DRAW
POP R31, 1
R17 = [R31 + #EA]W ' キャラクターパターン (変数 P)
R17 = R17 + -#E0
R17 <<= 3
R16 = R31 + -#100 ' R16 = #700 に相当するアドレス
R17 += R16 ' R17 = 描画するキャラクターパターンのアドレス (RAM上)
R10 = [R31 + #FA]W ' R0 = 描画対象X座標
R11 = [R31 + #FC]W ' R1 = 描画対象Y座標
' 既存のキャラクターパターンをゼロクリアする
R14 = R16
R15 = 8
@CP_ERASE
[R14 + 0]L = R0
R14 += 4
R15 += -1
IF R15 GOTO @CP_ERASE
' X,Yのオフセットに応じてキャラクターパターンを書き込む
R14 = R11 & 7
R16 += R14
R13 = 8
R15 = R10 & 7
@CP_WRITE
R12 = [R17 + 0]
R12 <<= 8
R12 = R12 >> R15
[R16 + 16] = R12 ' #E2 に右上を、#E3 に右下を書き込む
R12 >>= 8
[R16 + 0] = R12 ' #E0 に左上を、#E1 に左下を書き込む
R17 += 1
R16 += 1
R13 += -1
IF R13 GOTO @CP_WRITE
' 描画した位置を更新する
R14 = [R31 + #FA]W
R15 = [R31 + #FC]W
[R31 + #D4]W = R14
[R31 + #D6]W = R15
' 現在の位置に主人公を描く (R10, R11 は上で座標を設定済み)
R12 = #E3E1E000
R12 = R12 + #2E0
R13 = R31
POP R1, 0
SP += 1
' @RV32C_DRAW へフォールスルーする
' 配列が R13 から始まるとき、(R10, R11) に R12 で定義されるパターンを描画する (# 右下 左下 右上 左上)
@RV32C_DRAW
IF R11 < R0 GOTO @RV32C_DRAW_END
R14 = R10 >> 3
R13 += R14
R14 = R11 >> 3
R14 <<= 5
R13 += R14
R10 &= 7
R11 &= 7
[R13 + #100] = R12
R12 >>= 8
IF !R10 GOTO @RV32C_DRAW_X1_Y0_END
[R13 + #101] = R12
@RV32C_DRAW_X1_Y0_END
IF !R11 GOTO @RV32C_DRAW_END
R12 >>= 8
[R13 + #120] = R12
IF !R10 GOTO @RV32C_DRAW_END
R12 >>= 8
[R13 + #121] = R12
@RV32C_DRAW_END
RET
ORG #900 - 44
@VAR_E
ORG #900 - 24
@VAR_O
ORG #900 - 8
@VAR_W
ORG #900
@VRAM
これらのプログラムでは、以下の処理を行う。
- 前回描画した主人公を画面から消去する。
- 主人公のx座標およびy座標を8で割った余りに応じて、描画するキャラクターパターンをずらしたキャラクターパターンを生成する。
- 生成したキャラクターパターン (最大4枚) を用いて、新しい位置に主人公を描画する。
- 主人公を描画した位置の情報を更新する。
この処理により、文字 (VRAM) を書き換えるだけでは8ピクセル単位でしか移動できない中、1ピクセル単位での主人公の描画を実現する。
ただし、この処理は今回のゲームの以下の性質を利用した手抜きである。
- 主人公以外に動くもの (敵キャラなど) が無い
- ブロックの位置と大きさは常に8の倍数 (中途半端な高さのブロックや坂などが無い)
- 主人公が居る位置の背景は常に真っ黒 (他のオブジェクトに重ならない)