0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

IchigoJamAdvent Calendar 2023

Day 20

IchigoJam でジャンプアクションゲーム

Posted at

IchigoJam でジャンプアクションゲームを作ってみた。

※IchigoJamはjig.jpの登録商標です。

ルール

主人公を操作して、トゲに落ちないようにしつつ、うまく足場を渡り、ゴールにたどり着ければクリア。

ゲーム画面

操作方法

  • 左矢印キー:左に進む
  • 右矢印キー:右に進む
  • スペースキー:ジャンプ
  • Xキー:最初からやり直す

HetaPad での操作も可能である。

プログラム

OneFiveCrowd で実行する

本体

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

描画プログラムが読み込まれていないようであれば、以下の簡単な描画を実行する。

  1. 以前主人公が描画されていた場所の描画を消去する
  2. 現在の場所に主人公を描画する
  3. 主人公を描画した位置 (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

これらのプログラムでは、以下の処理を行う。

  1. 前回描画した主人公を画面から消去する。
  2. 主人公のx座標およびy座標を8で割った余りに応じて、描画するキャラクターパターンをずらしたキャラクターパターンを生成する。
  3. 生成したキャラクターパターン (最大4枚) を用いて、新しい位置に主人公を描画する。
  4. 主人公を描画した位置の情報を更新する。

この処理により、文字 (VRAM) を書き換えるだけでは8ピクセル単位でしか移動できない中、1ピクセル単位での主人公の描画を実現する。
ただし、この処理は今回のゲームの以下の性質を利用した手抜きである。

  • 主人公以外に動くもの (敵キャラなど) が無い
  • ブロックの位置と大きさは常に8の倍数 (中途半端な高さのブロックや坂などが無い)
  • 主人公が居る位置の背景は常に真っ黒 (他のオブジェクトに重ならない)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?