Refactor tilemap layers and collision system to consolidate into single floor layer with improved ladder logic and button/barrier interactions

This commit is contained in:
Brian Fertig 2026-02-23 20:32:03 -07:00
parent be160ed954
commit ab56b161fa
9 changed files with 202 additions and 288 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

View File

@ -8,26 +8,48 @@
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 4, 5, 6, 7,
13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 17],
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0,
0, 11, 11, 11, 11, 11, 42, 42, 42, 42, 42, 18, 1, 1, 2, 0,
0, 0, 0, 0, 0, 0, 0, 0, 21, 32, 0, 18, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 21, 32, 0, 18, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 21, 32, 0, 18, 0, 0, 0, 0,
0, 0, 8, 0, 0, 0, 10, 0, 21, 32, 0, 18, 0, 0, 0, 0,
2147483650, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 18, 0, 0, 21, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0,
0, 0, 18, 0, 0, 21, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0,
0, 0, 18, 0, 0, 21, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0,
0, 0, 18, 0, 0, 21, 0, 19, 0, 0, 32, 0, 0, 0, 0, 0,
3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 4, 5, 4, 5,
13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15],
"height":16,
"width":16,
"x":0,
"y":0
},
{
"data":[23, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 27,
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
18, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
18, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4, 5, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
14, 15, 14, 15, 16, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":16,
"width":16,
"x":16,
"y":0
},
{
"data":[23, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 24, 25,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -47,126 +69,40 @@
"width":16,
"x":0,
"y":16
},
{
"data":[24, 25, 24, 25, 26, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":16,
"width":16,
"x":16,
"y":16
}],
"height":48,
"height":32,
"id":1,
"name":"floor",
"opacity":1,
"startx":-16,
"starty":-16,
"startx":0,
"starty":0,
"type":"tilelayer",
"visible":true,
"width":32,
"x":0,
"y":0
},
{
"chunks":[
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 2147483650, 1, 1, 1, 1, 1, 2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2147483650, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":16,
"width":16,
"x":0,
"y":0
}],
"height":16,
"id":2,
"name":"platforms",
"opacity":1,
"startx":0,
"starty":0,
"type":"tilelayer",
"visible":true,
"width":16,
"x":0,
"y":0
},
{
"chunks":[
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0,
0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 18, 8, 0, 0, 0, 0,
0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0,
0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0,
0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0,
0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0,
0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":16,
"width":16,
"x":0,
"y":0
}],
"height":16,
"id":3,
"name":"ladders",
"opacity":1,
"startx":0,
"starty":0,
"type":"tilelayer",
"visible":true,
"width":16,
"x":0,
"y":0
},
{
"chunks":[
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 9, 0, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 21, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":16,
"width":16,
"x":0,
"y":0
}],
"height":16,
"id":4,
"name":"barriers",
"opacity":1,
"startx":0,
"starty":0,
"type":"tilelayer",
"visible":true,
"width":16,
"x":0,
"y":0
}],
"nextlayerid":5,
"nextobjectid":1,

View File

@ -9,26 +9,31 @@
<tile id="2">
<properties>
<property name="floor" type="bool" value="true"/>
<property name="solid" type="bool" value="true"/>
</properties>
</tile>
<tile id="3">
<properties>
<property name="floor" type="bool" value="true"/>
<property name="solid" type="bool" value="true"/>
</properties>
</tile>
<tile id="4">
<properties>
<property name="floor" type="bool" value="true"/>
<property name="solid" type="bool" value="true"/>
</properties>
</tile>
<tile id="5">
<properties>
<property name="floor" type="bool" value="true"/>
<property name="solid" type="bool" value="true"/>
</properties>
</tile>
<tile id="6">
<properties>
<property name="floor" type="bool" value="true"/>
<property name="solid" type="bool" value="true"/>
</properties>
</tile>
<tile id="7">

View File

@ -10,6 +10,21 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,
0,11,11,11,11,11,42,42,42,42,42,18,1,1,2,0,
0,0,0,0,0,0,0,0,21,32,0,18,0,0,0,0,
0,0,0,0,0,0,0,0,21,32,0,18,0,0,0,0,
0,0,0,0,0,0,0,0,21,32,0,18,0,0,0,0,
0,0,8,0,0,0,10,0,21,32,0,18,0,0,0,0,
2147483650,1,18,1,1,1,1,1,1,1,1,1,1,1,1,1,
0,0,18,0,0,21,0,0,0,0,32,0,0,0,0,0,
0,0,18,0,0,21,0,0,0,0,32,0,0,0,0,0,
0,0,18,0,0,21,0,0,0,0,32,0,0,0,0,0,
0,0,18,0,0,21,0,19,0,0,32,0,0,0,0,0,
3,4,5,6,4,5,6,4,5,6,4,5,4,5,4,5,
13,14,15,15,15,15,15,15,15,15,15,15,15,15,14,15
</chunk>
<chunk x="16" y="0" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -18,14 +33,17 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
3,4,5,6,4,5,6,4,5,6,4,5,4,5,6,7,
13,14,15,15,15,15,15,15,15,15,15,15,15,15,16,17
8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
18,1,1,1,1,2,0,0,0,0,0,0,0,0,0,0,
18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
18,0,0,29,0,0,0,0,0,0,0,0,0,0,0,0,
4,5,4,5,6,7,0,0,0,0,0,0,0,0,0,0,
14,15,14,15,16,17,0,0,0,0,0,0,0,0,0,0
</chunk>
<chunk x="0" y="16" width="16" height="16">
23,24,25,25,25,25,25,25,25,25,25,25,25,25,26,27,
23,24,25,25,25,25,25,25,25,25,25,25,25,25,24,25,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -42,69 +60,21 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
</data>
</layer>
<layer id="2" name="platforms" width="30" height="20">
<data encoding="csv">
<chunk x="0" y="0" width="16" height="16">
<chunk x="16" y="16" width="16" height="16">
24,25,24,25,26,27,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,2147483650,1,1,1,1,1,2,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2147483650,1,1,1,1,1,1,1,1,1,1,1,2,0,0,0,
0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
</data>
</layer>
<layer id="3" name="ladders" width="30" height="20">
<data encoding="csv">
<chunk x="0" y="0" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,
0,8,0,0,0,0,0,0,0,0,18,8,0,0,0,0,
0,18,0,0,0,0,0,0,0,0,0,18,0,0,0,0,
0,18,0,0,0,0,0,0,0,0,0,18,0,0,0,0,
0,18,0,0,0,0,0,0,0,0,0,18,0,0,0,0,
0,18,0,0,0,0,0,0,0,0,0,18,0,0,0,0,
0,18,0,0,0,0,0,0,0,0,0,18,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
</data>
</layer>
<layer id="4" name="barriers" width="30" height="20">
<data encoding="csv">
<chunk x="0" y="0" width="16" height="16">
0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,
0,0,0,0,0,0,9,0,32,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,21,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,21,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,21,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,21,0,20,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>

View File

@ -5,11 +5,11 @@ export default class Player {
* @param {Phaser.Scene} scene
* @param {number} x World pixel x (center)
* @param {number} y World pixel y (center)
* @param {Phaser.Tilemaps.TilemapLayer} laddersLayer
* @param {Phaser.Tilemaps.TilemapLayer} floorLayer Single combined floor layer
*/
constructor(scene, x, y, laddersLayer) {
constructor(scene, x, y, floorLayer) {
this._scene = scene;
this._laddersLayer = laddersLayer;
this._floorLayer = floorLayer;
this.lives = 3;
this.onLadder = false;
@ -35,6 +35,12 @@ export default class Player {
frameRate: 1,
repeat: 0,
});
scene.anims.create({
key: 'climb',
frames: scene.anims.generateFrameNumbers('player', { start: 2, end: 3 }),
frameRate: 8,
repeat: -1,
});
this._cursors = scene.input.keyboard.createCursorKeys();
this._jumpKey = this._cursors.up;
@ -55,10 +61,10 @@ export default class Player {
const body = sprite.body;
// Tile at player center (for body-of-ladder detection)
const centerTile = this._laddersLayer.getTileAtWorldXY(sprite.x, sprite.y);
const centerTile = this._floorLayer.getTileAtWorldXY(sprite.x, sprite.y);
// Tile just below body bottom (body bottom = sprite.y + 120; +2 puts us inside
// the tile when standing on it, since physics places body bottom at tile top)
const feetTile = this._laddersLayer.getTileAtWorldXY(sprite.x, sprite.y + 122);
const feetTile = this._floorLayer.getTileAtWorldXY(sprite.x, sprite.y + 122);
const centerIsLadder = centerTile && TilePropertyHelper.isLadder(centerTile.index);
const feetIsLadderTop = feetTile && TilePropertyHelper.isLadderTop(feetTile.index);
@ -152,8 +158,12 @@ export default class Player {
const sprite = this._sprite;
if (this.onLadder) {
sprite.anims.stop();
sprite.setFrame(0);
if (sprite.body.velocity.y !== 0) {
sprite.anims.play('climb', true);
} else {
sprite.anims.stop();
sprite.setFrame(2);
}
return;
}

View File

@ -4,6 +4,8 @@ import TilePropertyHelper, {
FLOOR_GIDS,
SOLID_FLOOR_GID,
LADDER_TOP_GID,
BUTTON_GIDS,
BARRIER_SOLID_GIDS,
} from '../systems/TilePropertyHelper.js';
export default class Level1Scene extends Phaser.Scene {
@ -78,25 +80,19 @@ export default class Level1Scene extends Phaser.Scene {
// If tiles render blank, verify with: console.log(map.tilesets[0].name)
const tileset = map.addTilesetImage('platforms', 'platforms', 128, 128, 0, 0);
// Single combined layer — all tile types live here
this._floorLayer = map.createLayer('floor', tileset, 0, 0);
this._platformsLayer = map.createLayer('platforms', tileset, 0, 0);
this._laddersLayer = map.createLayer('ladders', tileset, 0, 0);
this._barriersLayer = map.createLayer('barriers', tileset, 0, 0);
// -------------------------
// Tile collision setup
// -------------------------
// Floor layer: one-way — only collide when player falls down
this._floorLayer.setCollision(FLOOR_GIDS);
// Platforms layer: floor tiles (one-way) + the one solid wall tile
this._platformsLayer.setCollision([...FLOOR_GIDS, SOLID_FLOOR_GID]);
// Ladder layer: only the top rung acts as a one-way surface
this._laddersLayer.setCollision([LADDER_TOP_GID]);
// Barrier layer: initial collision set by ButtonBarrierSystem.init()
// Only register collision on GIDs that should ever block the player.
// Omitting ladder rungs and barrier_open tiles prevents Phaser's face-culling
// from stripping the top face off solid tiles that sit adjacent to those tiles.
this._floorLayer.setCollision([
...FLOOR_GIDS, // one-way floor surfaces
SOLID_FLOOR_GID, // impassable wall tile
LADDER_TOP_GID, // ladder top (one-way surface)
...BUTTON_GIDS, // buttons (both pressed/unpressed) — always act as floor
...BARRIER_SOLID_GIDS, // solid barriers — ButtonBarrierSystem manages toggling
]);
// -------------------------
// Player
@ -104,57 +100,50 @@ export default class Level1Scene extends Phaser.Scene {
// Spawn at tile (3, 11) — center of tile in world pixels
const spawnX = 3 * 128 + 64; // 448
const spawnY = 11 * 128 + 64; // 1472
this._player = new Player(this, spawnX, spawnY, this._laddersLayer);
this._player = new Player(this, spawnX, spawnY, this._floorLayer);
const playerSprite = this._player.sprite;
// -------------------------
// Button / Barrier system
// -------------------------
this._bbSystem = new ButtonBarrierSystem(this, this._barriersLayer);
this._bbSystem = new ButtonBarrierSystem(this, this._floorLayer);
this._bbSystem.init();
// -------------------------
// Physics colliders
// Physics collider — single layer, all tile types
// -------------------------
// Floor layer: one-way (pass through from below, land from above)
this.physics.add.collider(
playerSprite,
this._floorLayer,
null,
(player, tile) => player.body.velocity.y >= 0,
this
);
// Platforms layer: one-way for floor tiles; full collision for solid wall tile
this.physics.add.collider(
playerSprite,
this._platformsLayer,
null,
(player, tile) => {
if (tile.index === SOLID_FLOOR_GID) return true;
return player.body.velocity.y >= 0;
},
this
);
// Ladder layer: top rung as one-way floor; disabled while actively climbing
this.physics.add.collider(
playerSprite,
this._laddersLayer,
null,
(player, tile) => !this._player.onLadder && player.body.velocity.y >= 0,
this
);
// Barriers layer: dynamic — processCallback checks current solid state per tile
this.physics.add.collider(
playerSprite,
this._barriersLayer,
null,
(player, tile) => {
const props = TilePropertyHelper.getPropsByGID(tile.index);
return !!(props && props.solid);
if (!props || Object.keys(props).length === 0) return false;
// Ladder rungs: no collision; top rung: one-way surface (unless climbing)
if (props.ladder) {
if (!props.top) return false;
// Also require the player's feet to be near the tile's top surface.
// Without this, a player walking horizontally at a row below the
// ladder top has velocity.y=0 (>= 0 passes) and gets blocked sideways.
return !this._player.onLadder
&& player.body.velocity.y >= 0
&& player.body.bottom <= tile.pixelY + 32;
}
// Barriers: collision determined by current tile's solid state
if (props.barrier) return !!(props.solid);
// Buttons: unpressed = one-way floor; pressed = passable
if (props.button) return !props.pressed && player.body.velocity.y >= 0;
// Floor tiles: solid floor is impassable all sides; regular floor is one-way
if (props.floor) {
if (props.solid) return true;
return player.body.velocity.y >= 0;
}
return false;
},
this
);
@ -216,7 +205,7 @@ export default class Level1Scene extends Phaser.Scene {
const feetX = sprite.x;
const feetY = sprite.y + 120; // approx bottom of physics body
const tile = this._barriersLayer.getTileAtWorldXY(feetX, feetY + 4);
const tile = this._floorLayer.getTileAtWorldXY(feetX, feetY + 4);
if (!tile || tile.index <= 0) return;
if (this._bbSystem.isButtonTile(tile.index)) {

View File

@ -1,13 +1,13 @@
import TilePropertyHelper, { COLOR_TILES } from './TilePropertyHelper.js';
import TilePropertyHelper, { COLOR_TILES, BARRIER_SOLID_GIDS, BARRIER_OPEN_GIDS, BUTTON_GIDS, BUTTON_ON_GIDS } from './TilePropertyHelper.js';
export default class ButtonBarrierSystem {
/**
* @param {Phaser.Scene} scene
* @param {Phaser.Tilemaps.TilemapLayer} barriersLayer
* @param {Phaser.Tilemaps.TilemapLayer} floorLayer
*/
constructor(scene, barriersLayer) {
constructor(scene, floorLayer) {
this._scene = scene;
this._barriersLayer = barriersLayer;
this._floorLayer = floorLayer;
this._buttons = []; // [{ col, row, color }]
this._barriers = []; // [{ col, row, color }]
@ -15,11 +15,12 @@ export default class ButtonBarrierSystem {
}
/**
* Scan the barriers layer to build button/barrier registries and set initial
* collision state. Call once after the layer is created.
* Scan the floor layer to build button/barrier registries and set initial
* barrier state (all passable no button pressed yet). Call once after
* the layer is created.
*/
init() {
const layer = this._barriersLayer;
const layer = this._floorLayer;
layer.forEachTile((tile) => {
if (tile.index <= 0) return;
@ -36,12 +37,23 @@ export default class ButtonBarrierSystem {
}
});
this._rebuildCollision();
// Set initial barrier states: active color (if any) → solid, all others → passable
for (const bar of this._barriers) {
const gids = COLOR_TILES[bar.color];
const newGID = bar.color === this._activeColor
? gids.barrier_solid
: gids.barrier_open;
this._floorLayer.putTileAt(newGID, bar.col, bar.row);
}
// putTileAt doesn't automatically update Phaser's per-tile collision flags,
// so explicitly sync the barrier tiles' collision state now.
this._refreshCollision();
}
/**
* Press the button at the given tile coordinates.
* Updates button/barrier tile indices and rebuilds collision.
* Active color's barriers become solid; all other barriers become passable.
* @param {number} col Tile column
* @param {number} row Tile row
*/
@ -56,19 +68,33 @@ export default class ButtonBarrierSystem {
for (const btn of this._buttons) {
const gids = COLOR_TILES[btn.color];
const newGID = btn.color === this._activeColor ? gids.btn_on : gids.btn_off;
this._barriersLayer.putTileAt(newGID, btn.col, btn.row);
this._floorLayer.putTileAt(newGID, btn.col, btn.row);
}
// Update all barrier tiles
// Active color's barriers → solid; all others → passable
for (const bar of this._barriers) {
const gids = COLOR_TILES[bar.color];
const newGID = bar.color === this._activeColor
? gids.barrier_open
: gids.barrier_solid;
this._barriersLayer.putTileAt(newGID, bar.col, bar.row);
? gids.barrier_solid
: gids.barrier_open;
this._floorLayer.putTileAt(newGID, bar.col, bar.row);
}
this._rebuildCollision();
this._refreshCollision();
}
/**
* Sync Phaser's per-tile collision flags for all button and barrier tiles.
* putTileAt changes a tile's index but does NOT reset its collision flags,
* so we must explicitly enable/disable collision after any putTileAt call.
* The recalculateFaces=true argument (third param) also fixes face-culling
* on solid tiles adjacent to newly-passable tiles.
*/
_refreshCollision() {
this._floorLayer.setCollision(BUTTON_GIDS, true, true); // btn_off = solid
this._floorLayer.setCollision(BUTTON_ON_GIDS, false, true); // btn_on = passable
this._floorLayer.setCollision(BARRIER_SOLID_GIDS, true, true);
this._floorLayer.setCollision(BARRIER_OPEN_GIDS, false, true);
}
/**
@ -79,35 +105,4 @@ export default class ButtonBarrierSystem {
const props = TilePropertyHelper.getPropsByGID(gid);
return !!(props.button && !props.pressed);
}
/**
* Rebuild which tiles in the barriers layer have active collision.
* Solid: all unpressed buttons + all non-active-color solid barriers.
* Passable: active-color barriers + pressed button.
*/
_rebuildCollision() {
const solidGIDs = [];
for (const [color, gids] of Object.entries(COLOR_TILES)) {
if (color !== this._activeColor) {
solidGIDs.push(gids.btn_off);
solidGIDs.push(gids.barrier_solid);
}
// Active color's btn_on and barrier_open remain passable (not added)
}
// setCollision(indices, collides, recalculate)
// Passing false to a second call clears existing, then we set only solidGIDs
this._barriersLayer.setCollision(solidGIDs, true, true);
// Explicitly remove collision from active-color tiles (in case of leftovers)
if (this._activeColor) {
const gids = COLOR_TILES[this._activeColor];
this._barriersLayer.setCollision(
[gids.btn_on, gids.barrier_open],
false,
true
);
}
}
}

View File

@ -84,6 +84,15 @@ export const SOLID_BARRIER_GIDS = [
21, 31, 41, 51, // solid barriers: red, blue, yellow, green
];
// Unpressed button GIDs — solid, player can land on them
export const BUTTON_GIDS = [9, 10, 29, 30];
// Pressed button GIDs — passable, player walks/falls through
export const BUTTON_ON_GIDS = [19, 20, 39, 40];
// Barrier GIDs separated by state (used for dynamic setCollision updates)
export const BARRIER_SOLID_GIDS = [21, 31, 41, 51];
export const BARRIER_OPEN_GIDS = [22, 32, 42, 52];
export default class TilePropertyHelper {
/** Returns property object for a 0-based tile ID (TSX convention). */
static getProps(tileId) {