基本のマップサイズは、128×32セル分
PICO-8の仕様として、
https://www.lexaloffle.com/dl/docs/pico-8_manual.html#Specifications
Sprites: Single bank of 128 8×8 sprites (+128 shared)
Map: 128 x 32 Tilemap (+ 128 x 32 shared)
スプライトは8×8ピクセルが128枚、マップサイズは128×32セル(1セル8×8ピクセル=1スプライト)が基本です。
マップの下半分(33行目から)と、スプライトの128番目以降はメモリを共有しているので、スプライトを128枚に収めてその分をマップに全振りしたとしても、128×64セルが最大です。
大きなマップを使うには?
大きなマップを使う方法としては、マップをマップエディターを使って登録するのではなく、文字列など何らかの形式に変換して、必要に応じてマップメモリーに展開するという方法が考えられます。それであれば、呼び出し方次第で無限にマップを広げることもできなくはありません。
今回紹介するのは、それとは違う方法で、最大256×128セル、標準の4倍のマップをオンメモリで使用する方法です。
Big Maps
https://www.lexaloffle.com/bbs/?tid=45538
Big Maps
Similar to gfx memory mapping, the map can now be placed at address 0x8000 and above (in increments of 0x100). This gives 4 times as much runtime space as the default map, and an additional POKE is provided to allow customisable map sizes.
Legal values are 0x20 (the default) and 0x80 and above. There are two pokes you need:
poke(0x5f56, 0x80) -- set the map to 0x80. Default is 0x20 poke(0x5f57, 0) -- set the width of the map (0 means 256). Defaults to 128The height of the map is determined by how much data is available in the memory section. So in this case, there is is 32k available divided by 256, which gives a map height of 128 cels -> 256×128.
PICO-8のマニュアルによると、メモリの0x8000以降はユーザーデータとなっています。
poke(0x5f56, 0x80)
とすることで、マップメモリを0x8000に変更し、
poke(0x5f57, 0)
で、マップの幅を256セルに変更できます。
ユーザーデータとして利用できるのは32KB(32768byte)で、それを256byteで割ると128。
つまり、256×128セル分のデータになる、ということのようです。
サンプルコード
function _init()
poke(0x5f56, 0x80)-- マップメモリ変更
poke(0x5f57, 0)-- マップサイズ変更
camx, camy, camspd = 0, 0, 4-- カメラ用変数
mwidth, mheight = 256, 128-- マップサイズ
-- マップ生成(別記)
local terrain_map = generate_terrain_map()
poke_map_data(terrain_map)
end
function _update60()
-- ボタン
if btn(0) then camx -= camspd end
if btn(1) then camx += camspd end
if btn(2) then camy -= camspd end
if btn(3) then camy += camspd end
-- カメラ座標
camx = mid(0, camx, (mwidth - 16) * 8)
camy = mid(0, camy, (mheight - 16) * 8)
end
function _draw()
cls(12)
camera(camx, camy)
map(0, 0, 0, 0, mwidth, mheight)
camera(0, 0)
rectfill(1, 1, 33, 7, 0)
print(camx .. "," .. camy, 2, 2, 7)
end

広い!
PICO-8の画面サイズは16×16セルなので、画面数にして、16×8=128画面分。これは、初代ゼルダの伝説の地上と同じ画面数です。ちょっとしたARPGには十分な広さ。
ただし、マップエディターは使えない
Note that the map editor still always writes and loads at 0x2000. This feature doesn’t come with extra cartridge space to match! So to use 0x8000 and above, you’ll need to manually mset() or memcpy() some map data. This feature will be most useful for carts that procedurally generate their maps or have some custom data storage scheme.
The map address is observed by all map functions: mget(), mset(), map(), tline()
マップエディターは、元々のメモリを参照するので、エディターで作ったマップをそのまま表示することはできません。メモリの操作が必要です。
ただし、マップを操作する関数は変更したメモリを自動的に参照してくれるので、mset()などはそのまま使うことができます。
あなたなら、これをどう使う?
おまけ
地形を自動生成するコードを、Claude先生に出力してもらいました。
参考までに紹介します。
上記コードの
local terrain_map = generate_terrain_map()
poke_map_data(terrain_map)
の部分ですね。
-- 地形生成
-- スプライト定義:
-- 0: 水面, 1: 砂地, 2: 土, 3: 草原, 4: 森, 5: 岩場
-- ノイズ関数
function improved_noise(x, y)
-- 複数の周波数を組み合わせ
local n1 = sin(x * 0.02 + y * 0.03) * 0.5
local n2 = sin(x * 0.05 - y * 0.04) * 0.3
local n3 = sin(x * 0.1 + y * 0.08) * 0.2
local n4 = sin(x * 0.2 - y * 0.15) * 0.1
return n1 + n2 + n3 + n4
end
-- 複数オクターブのノイズ
function fractal_noise(x, y)
local total = 0
local amplitude = 1
local frequency = 0.01
for i = 1, 4 do
total += improved_noise(x, y, frequency) * amplitude
amplitude *= 0.5
frequency *= 2
end
return total
end
-- 地形の高さから地形タイプを決定
function get_terrain_type(height)
if height < -0.3 then
return 0 -- 水面
elseif height < -0.1 then
return 1 -- 砂地
elseif height < 0.1 then
return 2 -- 土
elseif height < 0.3 then
return 3 -- 草原
elseif height < 0.5 then
return 4 -- 森
else
return 5 -- 岩場
end
end
-- 地形マップを生成
function generate_terrain_map()
local map = {}
for y = 0, mheight - 1 do
for x = 0, mwidth - 1 do
-- 基本的な高さ
local height = fractal_noise(x, y)
-- 島のような形状を作るため、端に向かって低くする
local center_x = mwidth / 2
local center_y = mheight / 2
local distance = sqrt((x - center_x) ^ 2 + (y - center_y) ^ 2)
local max_distance = sqrt(center_x ^ 2 + center_y ^ 2)
local island_factor = 1 - (distance / max_distance) ^ 2
height = height * island_factor
-- 地形タイプを決定
local terrain_type = get_terrain_type(height)
-- マップに保存
map[y * mwidth + x] = terrain_type
end
end
return map
end
function poke_map_data(map)
poke(0x8000, unpack(map))
end