スクロールはロマン
ゲームを作るなら、一度は作ってみたい「スクロールするステージ」。
PICO-8では、複雑でなければ比較的簡単にスクロール演出を実装することが可能です。
ここでは「ジャンプアクションの基本を作る」をベースに、コードを整理しながら横スクロールのステージを作成する手順を紹介します。
まずコードを整理する
「ジャンプアクションの基本を作る」では、プレイヤーに関するコードがすべて、_init()、_update()、_draw()のメインループに直接書かれて、変数もすべてグローバル変数になっていました。
現時点ではプレイヤーしかいないので大きな問題ではないですが、今後いろいろ付け足すことを考えると、プレイヤーのための変数は上書きされないよう束ねて置いておき、動きも関数にまとめて修正や呼び出しを簡単にしたいところです。
プレイヤーの変数をまとめる
function _init()
x=0-- X座標
y=8-- y座標
dx=0-- 横移動量
dy=0-- 縦移動量
counter=1-- アニメーション用カウンター
spr_num={4}-- 初期スプライト(ジャンプポーズ)
gravity=0.5-- 重力加速
max_gravity=6-- 最大落下速度
jumping=true-- ジャンプ中かどうかのフラグ
max_height=6-- ジャンプの最大高
end
_init()で定義している変数の中で、プレイヤーに関係するものは、x, y, dx, dy, counter, spr_num, jumpingです。

コード編集画面で新しいタブをつくり、必要な変数を_init()からカットして、次のような形になるようペーストします。
-- player
player={
x=16,
y=64,
dx=0,
dy=0,
counter=1,
spr_num={4},
jumping=true
}
「player」という名前のテーブルの中に、変数をまとめました。
変数はテーブルの要素になるので、それぞれ「,」で区切ります(区切らないとエラーになる)。
プレイヤーの動きを関数にする
同じ要領で、_update()からもコードを移動します。
_update()はすべてプレイヤーに関する内容なので全てをカットし、同じタブに次のような形になるようペーストして、player_move()という変数にします。
function player_move()
-- horizontal
player.dx=0
if btn(0) then
player.dx-=2
player.f=true
end
if btn(1) then
player.dx+=2
player.f=false
end
if btn(🅾️) and
not player.jumping then
player.dy-=max_height
player.jumping=true
end
player.dy=
min(player.dy+gravity,max_gravity)
-- horizontal
if h_collision(player.x+player.dx,player.y) then
player.dx=0
end
-- heading
if player.jumping
and heading(player.x+player.dx,player.y+player.dy) then
player.dy=1
player.y=flr((player.y+player.dy+1)/8)*8
end
-- landing
if landing(player.x+player.dx,player.y+player.dy) then
player.y=flr((player.y+player.dy+7)/8)*8-8
player.dy=0
player.jumping=false
end
-- in midair
if player.dy>1 then
player.jumping=true
end
-- move
player.x+=player.dx
player.y+=player.dy
--player.x=mid(0,player.x,120)
-- animation frame
if not player.jumping then
player.spr_num={1,2,3,2}
else
player.spr_num={4}
end
if player.dx!=0 then
player.counter+=0.5
end
end
このコードから参照していた変数は、もともとは_init()の中で定義されていたグローバル変数(どこからでもアクセスできる変数)です。
しかし今回、プレイヤーの変数は「player」テーブルにまとめたので、参照先が変わっています。
テーブルの中の変数を読み出すときは、テーブル名.変数名となるよう、変数名の前に「.」で繋いでテーブル名を付け加えます。
変数はすべてplayerテーブルに入っているので、player_move()の中にある変数の前に「player.」を追加しました。
最後に、_draw()からもコードを持ってきます。
_draw()にはcls()だけを残して、player_move()の下にplayer_draw()を作成します。
こちらも同様に、変数名の前にplayer.を追記しています。
function player_draw()
local idx=
flr(player.counter%#player.spr_num)+1
spr(player.spr_num[idx],
player.x,player.y,1,1,player.f)
end
メインループを修正する
コードを移動したので、メインループは次のようにすっかり短くなりました。
function _init()
gravity=0.5
max_gravity=6
max_height=6
end
function _update()
end
function _draw()
cls()
end
ここに、先ほど定義した関数を書き込みます。
function _init()
gravity=0.5
max_gravity=6
max_height=6
end
function _update()
--プレイヤーを動かす
player_move()
end
function _draw()
cls()
--プレイヤーを描く
player_draw()
end
この状態で実行すると、元と同じように動作するはずです。
メインループが見やすくなったところで、スクロールを実装してみましょう。
camera()を使って画面をスクロールさせる
画面をスクロールさせるのに最も簡単な方法は、camera()を使うことです。
CAMERA([X, Y])
Set a screen offset of -x, -y for all drawing operations
CAMERA() to reset

camera()は画面位置のオフセット(-x, -y)を指定する関数、つまり、PICO-8世界のどこを切り取って見せるかを決める機能といえます。
camera(100,0)とすると、デフォルトの場所から右に100px移動した先の絵が表示されます。
これは表示する(画面に切り取る)場所が変わっただけなので、PICO-8世界に表示されているキャラクターの絶対位置は変わりません。
たとえば、(20,80)の座標にキャラクターがいる状態でcamera(100,0)としても、キャラクターの位置はそのまま、ただ、表示の外にいて見えなくなるというだけです。
スクロールのサンプルその1(プレイヤーに常に追従する)

サンプルのcartを用意しました。
この画像(gravity_and_jumping_scroll1.p8.png)をダウンロードして、PICO-8で開いてください。
少しスプライトを追加して、簡単なマップが設定してあります。
camera()は_draw()の中で使います。
function _draw()
cls()
camera(player.x,0)
map(0,0,0,0,48,16)
--draw player
player_draw()
end

幅48セル分のマップを表示して、カメラのX座標と、プレイヤーのX座標が同期するようにしてあります。
動かしてみると、プレイヤーの動きに合わせて画面がスクロールします。
ただ、このままだと端に張り付いてちょっと気持ち悪いですね。
スクロールのサンプルその2(画面の真ん中で追従する)

一般にある横スクロールゲームのように、プレイヤーがある地点まで来たらスクロールを始めるように改造します。
この画像(gravity_and_jumping_scroll2.p8.png)をダウンロードして、PICO-8で開いてください。
カメラの部分に少し手を加えます。
-- camera
cam={
x=0,
y=0
}
function cam_update()
if player.x>63 then
cam.x=player.x-64
end
end
タブ3に、カメラの座標のテーブル(cam)と、それを計算する関数(cam_update)を設定しました。
_update()の中でカメラの位置を設定、camera()にはcam.xでX座標を指定します。
function _update()
--move player
player_move()
cam_update()-- カメラの位置を設定
end
function _draw()
cls()
camera(cam.x,0)-- カメラ専用の座標で表示
map(0,0,0,0,48,16)
--draw player
player_draw()
end
プレイヤーのX座標が63(画面の真ん中)を超えているとき、カメラのX座標とプレイヤーのX座標を同期させます。
ただそのまま一緒の数字にすると、スクロールが始まると同時にまたプレイヤーが画面の端にいる状態になってしまうので、カメラの位置は、プレイヤーが移動したを分引いて、真ん中に表示されるよう調整しています。

どうでしょう?
かなりゲームっぽい画面になってきました。
スプライトやマップ、プレイヤーのパラメーターを書き換えて、遊んでみてください。
おまけ(多重スクロール)

camera()は一度しか使えないわけではありません。

この画像(gravity_and_jumping_scroll3.p8.png)をダウンロードして、PICO-8で開いてください。
何かを描画する前に、それぞれ座標の違うcamera()を実行しておくと、違う座標にあるものを一つの画面に表示することができます。
これを使うと、サンプルのように多重スクロールのような効果が可能です。
スコアや残機表示など、スクロールさせたくないものは、camera()もしくはcamera(0,0)とした後に描画すると、スクロールしないで同じ場所に表示されます。
function _draw()
cls(1)
camera(cam.x*0.4,0)-- 遠景のカメラ座標。0.4をかけて移動を遅くしている
map(0,16,0,20,30,20)
camera(cam.x,0)-- 近景のカメラ座標
map(0,0,0,0,stage_width,16)
--draw player
player_draw()
--text
camera()-- ステータス表示のカメラ座標
print("this is demo",2,120,7)
end
まとめ
camera()
を使えば、簡単に横スクロールステージを作れるcamera()
の座標調整で自然な追従やUI固定も実現可能- スクロールは楽しい!