started platformer demo, added features back to physics system

This commit is contained in:
Joe Weaver 2021-02-05 13:56:56 -05:00
parent 681d63f202
commit eeaf73bab4
30 changed files with 881 additions and 69 deletions

12
.gitignore vendored
View File

@ -1,2 +1,12 @@
# Exclude node modules
node_modules node_modules
dist/
# Exclude the compiled project
dist/*
# Include the demo_assets folder
!dist/demo_assets/
### IF YOU ARE MAKING A PROJECT, YOU MAY WANT TO UNCOMMENT THIS LINE ###
# !dist/assets/

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

BIN
dist/demo_assets/sounds/jump.wav vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,27 @@
{
"name": "PlatformerPlayer",
"spriteSheetImage": "player.png",
"spriteWidth": 16,
"spriteHeight": 16,
"columns": 5,
"rows": 1,
"durationType": "time",
"animations": [
{
"name": "IDLE",
"frames": [ {"index": 0, "duration": 1} ]
},
{
"name": "WALK",
"frames": [ {"index": 0, "duration": 16}, {"index": 1, "duration": 16}, {"index": 2, "duration": 16}, {"index": 3, "duration": 16} ]
},
{
"name": "JUMP",
"frames":[ {"index": 4, "duration": 32}]
},
{
"name": "FALL",
"frames":[ {"index": 4, "duration": 32}]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

View File

@ -0,0 +1,453 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"format":"json",
"target":"platformer.json"
}
},
"height":20,
"infinite":false,
"layers":[
{
"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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 44, 44, 44, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 44, 44, 44, 44, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 45, 0, 43, 0, 44, 44, 44, 44, 44, 44, 0, 44, 44, 44, 44, 44, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 36, 0, 36, 36, 36, 36, 36, 36, 36, 36, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 45, 0, 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 54, 55, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 45, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 44, 62, 63, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":20,
"id":2,
"name":"Background",
"opacity":1,
"properties":[
{
"name":"Collidable",
"type":"bool",
"value":false
},
{
"name":"Depth",
"type":"int",
"value":0
}],
"type":"tilelayer",
"visible":true,
"width":64,
"x":0,
"y":0
},
{
"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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 20, 20, 20, 8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 11, 12, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 10, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 9, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 16, 12, 12, 12, 12, 10, 12, 12, 12, 12, 12, 12, 9, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 7, 20, 20, 20, 20, 20, 20, 8, 12, 12, 12, 12, 10, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 28, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 16, 12, 12, 13, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 9, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 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, 17, 19, 20, 20, 20, 20, 13, 0, 0, 0, 0, 0, 0, 11, 20, 20, 20, 20, 20, 20, 20, 8, 12, 9, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 2, 2, 2, 2, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 10, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 0, 0, 0, 0, 0, 0, 11, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 2, 2, 2, 2, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 12, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 10, 12, 12, 12, 12, 12, 12, 10, 15, 4, 4, 4, 4, 4, 4, 4, 4],
"height":20,
"id":1,
"name":"Main",
"opacity":1,
"properties":[
{
"name":"Collidable",
"type":"bool",
"value":true
},
{
"name":"Depth",
"type":"int",
"value":1
}],
"type":"tilelayer",
"visible":true,
"width":64,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":4,
"name":"Coins",
"objects":[
{
"gid":25,
"height":16,
"id":2,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":256,
"y":272
},
{
"gid":25,
"height":16,
"id":3,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":272,
"y":272
},
{
"gid":25,
"height":16,
"id":4,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":368,
"y":288
},
{
"gid":25,
"height":16,
"id":5,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":384,
"y":288
},
{
"gid":25,
"height":16,
"id":6,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":400,
"y":288
},
{
"gid":25,
"height":16,
"id":7,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":688,
"y":272
},
{
"gid":25,
"height":16,
"id":8,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":688,
"y":288
},
{
"gid":25,
"height":16,
"id":9,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":688,
"y":304
},
{
"gid":25,
"height":16,
"id":10,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":784,
"y":256
},
{
"gid":25,
"height":16,
"id":11,
"name":"",
"properties":[
{
"name":"Group",
"type":"string",
"value":"Coins"
},
{
"name":"HasPhysics",
"type":"bool",
"value":true
},
{
"name":"IsCollidable",
"type":"bool",
"value":false
},
{
"name":"IsTrigger",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"",
"visible":true,
"width":16,
"x":832,
"y":256
}],
"opacity":1,
"properties":[
{
"name":"Depth",
"type":"int",
"value":1
}],
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
},
{
"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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":20,
"id":3,
"name":"Foreground",
"opacity":1,
"properties":[
{
"name":"Collidable",
"type":"bool",
"value":false
},
{
"name":"Depth",
"type":"int",
"value":2
}],
"type":"tilelayer",
"visible":true,
"width":64,
"x":0,
"y":0
}],
"nextlayerid":5,
"nextobjectid":14,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.3.4",
"tileheight":16,
"tilesets":[
{
"columns":8,
"firstgid":1,
"image":"platformer.png",
"imageheight":128,
"imagewidth":128,
"margin":0,
"name":"platformer_tileset",
"spacing":0,
"tilecount":64,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.2,
"width":64
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

49
src/Platformer.ts Normal file
View File

@ -0,0 +1,49 @@
import PlayerController from "./PlatformerPlayerController";
import Vec2 from "./Wolfie2D/DataTypes/Vec2";
import AnimatedSprite from "./Wolfie2D/Nodes/Sprites/AnimatedSprite";
import Scene from "./Wolfie2D/Scene/Scene";
export default class Platformer extends Scene {
private player: AnimatedSprite;
// Load any assets you will need for the project here
loadScene(){
// Load the player spritesheet
this.load.spritesheet("player", "demo_assets/spritesheets/platformer/player.json");
// Load the tilemap
this.load.tilemap("platformer", "demo_assets/tilemaps/platformer/platformer.json");
// Load the background image
this.load.image("background", "demo_assets/images/platformer_background.png");
// Load a jump sound
this.load.audio("jump", "demo_assets/sounds/jump.wav");
}
// Add GameObjects to the scene
startScene(){
this.addLayer("primary", 1);
// Add the player in the starting position
this.player = this.add.animatedSprite("player", "primary");
this.player.animation.play("IDLE");
this.player.position.set(3*16, 18*16);
// Add physics so the player can move
this.player.addPhysics();
this.player.addAI(PlayerController, {jumpSoundKey: "jump"});
// Size of the tilemap is 64x20. Tile size is 16x16
this.viewport.setBounds(0, 0, 64*16, 20*16);
this.viewport.follow(this.player);
// Add the tilemap. Top left corner is (0, 0) by default
this.add.tilemap("platformer");
// Add a background to the scene
this.addParallaxLayer("bg", new Vec2(0.5, 1), -1);
let bg = this.add.sprite("background", "bg");
bg.position.set(bg.size.x/2, bg.size.y/2);
}
}

View File

@ -0,0 +1,59 @@
import AI from "./Wolfie2D/DataTypes/Interfaces/AI";
import Emitter from "./Wolfie2D/Events/Emitter";
import GameEvent from "./Wolfie2D/Events/GameEvent";
import { GameEventType } from "./Wolfie2D/Events/GameEventType";
import Input from "./Wolfie2D/Input/Input";
import AnimatedSprite from "./Wolfie2D/Nodes/Sprites/AnimatedSprite";
export default class PlayerController implements AI {
protected owner: AnimatedSprite;
protected jumpSoundKey: string;
protected emitter: Emitter;
initializeAI(owner: AnimatedSprite, options: Record<string, any>): void {
this.owner = owner;
this.jumpSoundKey = options.jumpSoundKey;
this.emitter = new Emitter();
}
handleEvent(event: GameEvent): void {
// Do nothing for now
}
update(deltaT: number): void {
// Get the direction from key presses
const x = (Input.isPressed("left") ? -1 : 0) + (Input.isPressed("right") ? 1 : 0);
// Get last velocity and override x
const velocity = this.owner.getLastVelocity();
velocity.x = x * 100 * deltaT;
// Check for jump condition
if(this.owner.onGround && Input.isJustPressed("jump")){
// We are jumping
velocity.y = -250*deltaT;
// Loop our jump animation
this.owner.animation.play("JUMP", true);
// Play the jump sound
this.emitter.fireEvent(GameEventType.PLAY_SOUND, {key: this.jumpSoundKey, loop: false});
} else {
velocity.y += 10*deltaT;
}
if(this.owner.onGround && !Input.isJustPressed("jump")){
// If we're on the ground, but aren't jumping, show walk animation
if(velocity.x === 0){
this.owner.animation.playIfNotAlready("IDLE", true);
} else {
this.owner.animation.playIfNotAlready("WALK", true);
}
}
// If we're walking left, flip the sprite
this.owner.invertX = velocity.x < 0;
this.owner.move(velocity);
}
}

View File

@ -0,0 +1,18 @@
import AI from "../DataTypes/Interfaces/AI";
import GameEvent from "../Events/GameEvent";
import GameNode from "../Nodes/GameNode";
/**
* A very basic AI class that just runs a function every update
*/
export default class ControllerAI implements AI {
protected owner: GameNode;
initializeAI(owner: GameNode, options: Record<string, any>): void {
this.owner = owner;
}
handleEvent(event: GameEvent): void {}
update(deltaT: number): void {}
}

View File

@ -1,5 +1,6 @@
import AI from "../DataTypes/Interfaces/AI"; import AI from "../DataTypes/Interfaces/AI";
import StateMachine from "../DataTypes/State/StateMachine"; import StateMachine from "../DataTypes/State/StateMachine";
import GameEvent from "../Events/GameEvent";
import GameNode from "../Nodes/GameNode"; import GameNode from "../Nodes/GameNode";
/** /**

View File

@ -1,4 +1,6 @@
import GameEvent from "../../Events/GameEvent";
import GameNode from "../../Nodes/GameNode"; import GameNode from "../../Nodes/GameNode";
import Actor from "./Actor";
import Updateable from "./Updateable"; import Updateable from "./Updateable";
/** /**
@ -7,4 +9,7 @@ import Updateable from "./Updateable";
export default interface AI extends Updateable { export default interface AI extends Updateable {
/** Initializes the AI with the actor and any additional config */ /** Initializes the AI with the actor and any additional config */
initializeAI(owner: GameNode, options: Record<string, any>): void; initializeAI(owner: GameNode, options: Record<string, any>): void;
/** Handles events from the Actor */
handleEvent(event: GameEvent): void;
} }

View File

@ -1,4 +1,7 @@
import Physical from "../Interfaces/Physical";
import AABB from "../Shapes/AABB"; import AABB from "../Shapes/AABB";
import Vec2 from "../Vec2";
import Hit from "./Hit";
/** /**
* A class that contains the area of overlap of two colliding objects to allow for sorting by the physics system. * A class that contains the area of overlap of two colliding objects to allow for sorting by the physics system.
@ -6,16 +9,32 @@ import AABB from "../Shapes/AABB";
export default class AreaCollision { export default class AreaCollision {
/** The area of the overlap for the colliding objects */ /** The area of the overlap for the colliding objects */
area: number; area: number;
/** The AABB of the other collider in this collision */ /** The AABB of the other collider in this collision */
collider: AABB; collider: AABB;
/** Type of the collision */
type: string;
/** Ther other object in the collision */
other: Physical;
/** The tile, if this was a tilemap collision */
tile: Vec2;
/** The physics hit for this object */
hit: Hit;
/** /**
* Creates a new AreaCollision object * Creates a new AreaCollision object
* @param area The area of the collision * @param area The area of the collision
* @param collider The other collider * @param collider The other collider
*/ */
constructor(area: number, collider: AABB){ constructor(area: number, collider: AABB, other: Physical, type: string, tile: Vec2){
this.area = area; this.area = area;
this.collider = collider; this.collider = collider;
this.other = other;
this.type = type;
this.tile = tile;
} }
} }

View File

@ -146,7 +146,13 @@ export default class AABB extends Shape {
// We hit on the left or right size // We hit on the left or right size
hit.normal.x = -signX; hit.normal.x = -signX;
hit.normal.y = 0; hit.normal.y = 0;
} else if(Math.abs(tnearx - tneary) < 0.0001){
// We hit on the corner
hit.normal.x = -signX;
hit.normal.y = -signY;
hit.normal.normalize();
} else { } else {
// We hit on the top or bottom
hit.normal.x = 0; hit.normal.x = 0;
hit.normal.y = -signY; hit.normal.y = -signY;
} }
@ -190,6 +196,70 @@ export default class AABB extends Shape {
return true; return true;
} }
/**
* Determines whether these AABBs are JUST touching - not overlapping.
* Vec2.x is -1 if the other is to the left, 1 if to the right.
* Likewise, Vec2.y is -1 if the other is on top, 1 if on bottom.
* @param other The other AABB to check
* @returns The collision sides stored in a Vec2 if the AABBs are touching, null otherwise
*/
touchesAABB(other: AABB): Vec2 {
let dx = other.x - this.x;
let px = this.hw + other.hw - Math.abs(dx);
let dy = other.y - this.y;
let py = this.hh + other.hh - Math.abs(dy);
// If one axis is just touching and the other is overlapping, true
if((px === 0 && py >= 0) || (py === 0 && px >= 0)){
let ret = new Vec2();
if(px === 0){
ret.x = other.x < this.x ? -1 : 1;
}
if(py === 0){
ret.y = other.y < this.y ? -1 : 1;
}
return ret;
} else {
return null;
}
}
/**
* Determines whether these AABBs are JUST touching - not overlapping.
* Also, if they are only touching corners, they are considered not touching.
* Vec2.x is -1 if the other is to the left, 1 if to the right.
* Likewise, Vec2.y is -1 if the other is on top, 1 if on bottom.
* @param other The other AABB to check
* @returns The side of the touch, stored as a Vec2, or null if there is no touch
*/
touchesAABBWithoutCorners(other: AABB): Vec2 {
let dx = other.x - this.x;
let px = this.hw + other.hw - Math.abs(dx);
let dy = other.y - this.y;
let py = this.hh + other.hh - Math.abs(dy);
// If one axis is touching, and the other is strictly overlapping
if((px === 0 && py > 0) || (py === 0 && px > 0)){
let ret = new Vec2();
if(px === 0){
ret.x = other.x < this.x ? -1 : 1;
} else {
ret.y = other.y < this.y ? -1 : 1;
}
return ret;
} else {
return null;
}
}
/** /**
* Calculates the area of the overlap between this AABB and another * Calculates the area of the overlap between this AABB and another
* @param other The other AABB * @param other The other AABB

View File

@ -116,24 +116,14 @@ export default class StateMachine implements Updateable {
* Handles input. This happens at the very beginning of this state machine's update cycle. * Handles input. This happens at the very beginning of this state machine's update cycle.
* @param event The game event to process * @param event The game event to process
*/ */
handleInput(event: GameEvent): void { handleEvent(event: GameEvent): void {
if(this.active){
this.currentState.handleInput(event); this.currentState.handleInput(event);
} }
}
// @implemented // @implemented
update(deltaT: number): void { update(deltaT: number): void {
// If the state machine isn't currently active, ignore all events and don't update
if(!this.active){
this.receiver.ignoreEvents();
return;
}
// Handle input from all events
while(this.receiver.hasNextEvent()){
let event = this.receiver.getNextEvent();
this.handleInput(event);
}
// Delegate the update to the current state // Delegate the update to the current state
this.currentState.update(deltaT); this.currentState.update(deltaT);
} }

View File

@ -62,6 +62,9 @@ export default class Debug {
* @param color The color of the box to draw * @param color The color of the box to draw
*/ */
static drawBox(center: Vec2, halfSize: Vec2, filled: boolean, color: Color): void { static drawBox(center: Vec2, halfSize: Vec2, filled: boolean, color: Color): void {
let alpha = this.debugRenderingContext.globalAlpha;
this.debugRenderingContext.globalAlpha = color.a;
if(filled){ if(filled){
this.debugRenderingContext.fillStyle = color.toString(); this.debugRenderingContext.fillStyle = color.toString();
this.debugRenderingContext.fillRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2); this.debugRenderingContext.fillRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2);
@ -71,6 +74,8 @@ export default class Debug {
this.debugRenderingContext.strokeStyle = color.toString(); this.debugRenderingContext.strokeStyle = color.toString();
this.debugRenderingContext.strokeRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2); this.debugRenderingContext.strokeRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2);
} }
this.debugRenderingContext.globalAlpha = alpha;
} }
/** /**

View File

@ -70,8 +70,8 @@ export default class Game {
this.DEBUG_CANVAS = <HTMLCanvasElement>document.getElementById("debug-canvas"); this.DEBUG_CANVAS = <HTMLCanvasElement>document.getElementById("debug-canvas");
// Give the canvas a size and get the rendering context // Give the canvas a size and get the rendering context
this.WIDTH = this.gameOptions.viewportSize.x; this.WIDTH = this.gameOptions.canvasSize.x;
this.HEIGHT = this.gameOptions.viewportSize.y; this.HEIGHT = this.gameOptions.canvasSize.y;
// For now, just hard code a canvas renderer. We can do this with options later // For now, just hard code a canvas renderer. We can do this with options later
this.renderingManager = new CanvasRenderer(); this.renderingManager = new CanvasRenderer();
@ -89,8 +89,8 @@ export default class Game {
} }
// Size the viewport to the game canvas // Size the viewport to the game canvas
const viewportSize = new Vec2(this.WIDTH, this.HEIGHT); const canvasSize = new Vec2(this.WIDTH, this.HEIGHT);
this.viewport = new Viewport(viewportSize.scaled(0.5), viewportSize); this.viewport = new Viewport(canvasSize, this.gameOptions.zoomLevel);
// Initialize all necessary game subsystems // Initialize all necessary game subsystems
this.eventQueue = EventQueue.getInstance(); this.eventQueue = EventQueue.getInstance();

View File

@ -3,7 +3,10 @@
/** The options for initializing the @reference[GameLoop] */ /** The options for initializing the @reference[GameLoop] */
export default class GameOptions { export default class GameOptions {
/** The size of the viewport */ /** The size of the viewport */
viewportSize: {x: number, y: number}; canvasSize: {x: number, y: number};
/* The default level of zoom */
zoomLevel: number;
/** The color to clear the canvas to each frame */ /** The color to clear the canvas to each frame */
clearColor: {r: number, g: number, b: number} clearColor: {r: number, g: number, b: number}
@ -25,7 +28,8 @@ export default class GameOptions {
static parse(options: Record<string, any>): GameOptions { static parse(options: Record<string, any>): GameOptions {
let gOpt = new GameOptions(); let gOpt = new GameOptions();
gOpt.viewportSize = options.viewportSize ? options.viewportSize : {x: 800, y: 600}; gOpt.canvasSize = options.canvasSize ? options.canvasSize : {x: 800, y: 600};
gOpt.zoomLevel = options.zoomLevel ? options.zoomLevel : 1;
gOpt.clearColor = options.clearColor ? options.clearColor : {r: 255, g: 255, b: 255}; gOpt.clearColor = options.clearColor ? options.clearColor : {r: 255, g: 255, b: 255};
gOpt.inputs = options.inputs ? options.inputs : []; gOpt.inputs = options.inputs ? options.inputs : [];
gOpt.showDebug = !!options.showDebug; gOpt.showDebug = !!options.showDebug;

View File

@ -101,8 +101,7 @@ export default abstract class CanvasNode extends GameNode implements Region {
// @implemented // @implemented
debugRender(): void { debugRender(): void {
Debug.drawBox(this.relativePosition, this.sizeWithZoom, false, Color.BLUE);
super.debugRender(); super.debugRender();
let color = this.isColliding ? Color.RED : Color.GREEN;
Debug.drawBox(this.relativePosition, this.sizeWithZoom, false, color);
} }
} }

View File

@ -31,12 +31,12 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
private _id: number; private _id: number;
/*---------- PHYSICAL ----------*/ /*---------- PHYSICAL ----------*/
hasPhysics: boolean; hasPhysics: boolean = false;
moving: boolean; moving: boolean = false;
onGround: boolean; onGround: boolean = false;
onWall: boolean; onWall: boolean = false;
onCeiling: boolean; onCeiling: boolean = false;
active: boolean; active: boolean = false;
collisionShape: Shape; collisionShape: Shape;
colliderOffset: Vec2; colliderOffset: Vec2;
isStatic: boolean; isStatic: boolean;
@ -97,10 +97,19 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
} }
get relativePosition(): Vec2 { get relativePosition(): Vec2 {
return this.inRelativeCoordinates(this.position);
}
/**
* Converts a point to coordinates relative to the zoom and origin of this node
* @param point The point to conver
* @returns A new Vec2 representing the point in relative coordinates
*/
inRelativeCoordinates(point: Vec2): Vec2 {
let origin = this.scene.getViewTranslation(this); let origin = this.scene.getViewTranslation(this);
let zoom = this.scene.getViewScale(); let zoom = this.scene.getViewScale();
return this.position.clone().sub(origin).scale(zoom); return point.clone().sub(origin).scale(zoom);
} }
/*---------- UNIQUE ----------*/ /*---------- UNIQUE ----------*/
@ -132,7 +141,6 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
* @param velocity The velocity with which the object will move. * @param velocity The velocity with which the object will move.
*/ */
finishMove(): void { finishMove(): void {
console.log("finish");
this.moving = false; this.moving = false;
this.position.add(this._velocity); this.position.add(this._velocity);
if(this.pathfinding){ if(this.pathfinding){
@ -307,22 +315,35 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
* @param deltaT The timestep of the update. * @param deltaT The timestep of the update.
*/ */
update(deltaT: number): void { update(deltaT: number): void {
// Defer event handling to AI.
while(this.receiver.hasNextEvent()){
this._ai.handleEvent(this.receiver.getNextEvent());
}
// Update our tweens
this.tweens.update(deltaT); this.tweens.update(deltaT);
} }
// @implemented // @implemented
debugRender(): void { debugRender(): void {
let color = this.isColliding ? Color.RED : Color.GREEN; // Draw the position of this GameNode
Debug.drawPoint(this.relativePosition, color); Debug.drawPoint(this.relativePosition, Color.BLUE);
// If velocity is not zero, draw a vector for it // If velocity is not zero, draw a vector for it
if(this._velocity && !this._velocity.isZero()){ if(this._velocity && !this._velocity.isZero()){
Debug.drawRay(this.relativePosition, this._velocity.clone().scaleTo(20).add(this.relativePosition), color); Debug.drawRay(this.relativePosition, this._velocity.clone().scaleTo(20).add(this.relativePosition), Color.BLUE);
} }
// If this has a collider, draw it // If this has a collider, draw it
if(this.isCollidable && this.collisionShape){ if(this.hasPhysics && this.collisionShape){
Debug.drawBox(this.collisionShape.center, this.collisionShape.halfSize, false, Color.RED); let color = this.isColliding ? Color.RED : Color.GREEN;
if(this.isTrigger){
color = Color.PURPLE;
}
color.a = 0.2;
Debug.drawBox(this.inRelativeCoordinates(this.collisionShape.center), this.collisionShape.halfSize.scaled(this.scene.getViewScale()), true, color);
} }
} }
} }

View File

@ -89,6 +89,7 @@ export default class BasicPhysicsManager extends PhysicsManager {
node.onCeiling = false; node.onCeiling = false;
node.onWall = false; node.onWall = false;
node.collidedWithTilemap = false; node.collidedWithTilemap = false;
node.isColliding = false;
// Update the swept shapes of each node // Update the swept shapes of each node
if(node.moving){ if(node.moving){
@ -110,7 +111,7 @@ export default class BasicPhysicsManager extends PhysicsManager {
let area = node.sweptRect.overlapArea(collider); let area = node.sweptRect.overlapArea(collider);
if(area > 0){ if(area > 0){
// We had a collision // We had a collision
overlaps.push(new AreaCollision(area, collider)); overlaps.push(new AreaCollision(area, collider, other, "GameNode", null));
} }
} }
@ -120,7 +121,7 @@ export default class BasicPhysicsManager extends PhysicsManager {
let area = node.sweptRect.overlapArea(collider); let area = node.sweptRect.overlapArea(collider);
if(area > 0){ if(area > 0){
// We had a collision // We had a collision
overlaps.push(new AreaCollision(area, collider)); overlaps.push(new AreaCollision(area, collider, other, "GameNode", null));
} }
} }
@ -135,21 +136,27 @@ export default class BasicPhysicsManager extends PhysicsManager {
// Sort the overlaps by area // Sort the overlaps by area
overlaps = overlaps.sort((a, b) => b.area - a.area); overlaps = overlaps.sort((a, b) => b.area - a.area);
// Keep track of hits to use later
let hits = [];
/*---------- RESOLUTION PHASE ----------*/ /*---------- RESOLUTION PHASE ----------*/
// For every overlap, determine if we need to collide with it and when // For every overlap, determine if we need to collide with it and when
for(let other of overlaps){ for(let overlap of overlaps){
// Do a swept line test on the static AABB with this AABB size as padding (this is basically using a minkowski sum!) // Do a swept line test on the static AABB with this AABB size as padding (this is basically using a minkowski sum!)
// Start the sweep at the position of this node with a delta of _velocity // Start the sweep at the position of this node with a delta of _velocity
const point = node.collisionShape.center; const point = node.collisionShape.center;
const delta = node._velocity; const delta = node._velocity;
const padding = node.collisionShape.halfSize; const padding = node.collisionShape.halfSize;
const otherAABB = other.collider; const otherAABB = overlap.collider;
const hit = otherAABB.intersectSegment(node.collisionShape.center, node._velocity, node.collisionShape.halfSize); const hit = otherAABB.intersectSegment(node.collisionShape.center, node._velocity, node.collisionShape.halfSize);
overlap.hit = hit;
if(hit !== null){ if(hit !== null){
hits.push(hit);
// We got a hit, resolve with the time inside of the hit // We got a hit, resolve with the time inside of the hit
let tnearx = hit.nearTimes.x; let tnearx = hit.nearTimes.x;
let tneary = hit.nearTimes.y; let tneary = hit.nearTimes.y;
@ -164,11 +171,39 @@ export default class BasicPhysicsManager extends PhysicsManager {
if(hit.nearTimes.x >= 0 && hit.nearTimes.x < 1){ if(hit.nearTimes.x >= 0 && hit.nearTimes.x < 1){
// Any tilemap objects that made it here are collidable
if(overlap.type === "Tilemap" || overlap.other.isCollidable){
node._velocity.x = node._velocity.x * tnearx; node._velocity.x = node._velocity.x * tnearx;
node.isColliding = true;
}
} }
if(hit.nearTimes.y >= 0 && hit.nearTimes.y < 1){ if(hit.nearTimes.y >= 0 && hit.nearTimes.y < 1){
// Any tilemap objects that made it here are collidable
if(overlap.type === "Tilemap" || overlap.other.isCollidable){
node._velocity.y = node._velocity.y * tneary; node._velocity.y = node._velocity.y * tneary;
node.isColliding = true;
}
}
}
}
// Check if we ended up on the ground, ceiling or wall
for(let overlap of overlaps){
let collisionSide = overlap.collider.touchesAABBWithoutCorners(node.collisionShape.getBoundingRect());
if(collisionSide !== null){
// If we touch, not including corner cases, check the collision normal
if(overlap.hit !== null){
if(collisionSide.y === -1){
// Node is on top of overlap, so onGround
node.onGround = true;
} else if(collisionSide.y === 1){
// Node is on bottom of overlap, so onCeiling
node.onCeiling = true;
} else {
// Node wasn't touching on y, so it is touching on x
node.onWall = true;
}
} }
} }
} }
@ -209,7 +244,7 @@ export default class BasicPhysicsManager extends PhysicsManager {
let area = node.sweptRect.overlapArea(collider); let area = node.sweptRect.overlapArea(collider);
if(area > 0){ if(area > 0){
// We had a collision // We had a collision
overlaps.push(new AreaCollision(area, collider)); overlaps.push(new AreaCollision(area, collider, tilemap, "Tilemap", new Vec2(col, row)));
} }
} }
} }

View File

@ -141,6 +141,18 @@ export default class AnimationManager {
} }
} }
/**
* Plays the specified animation. Does not restart it if it is already playing
* @param animation The name of the animation to play
* @param loop Whether or not to loop the animation. False by default
* @param onEnd The name of an event to send when this animation naturally stops playing. This only matters if loop is false.
*/
playIfNotAlready(animation: string, loop: boolean = false, onEnd?: string): void {
if(this.currentAnimation !== animation){
this.play(animation, loop, onEnd);
}
}
/** /**
* Plays the specified animation * Plays the specified animation
* @param animation The name of the animation to play * @param animation The name of the animation to play

View File

@ -77,20 +77,23 @@ export default class TilemapFactory {
let sceneLayer; let sceneLayer;
let isParallaxLayer = false; let isParallaxLayer = false;
let depth = 0;
if(layer.properties){ if(layer.properties){
for(let prop of layer.properties){ for(let prop of layer.properties){
if(prop.name === "Parallax"){ if(prop.name === "Parallax"){
isParallaxLayer = prop.value; isParallaxLayer = prop.value;
} else if(prop.name === "Depth") {
depth = prop.value;
} }
} }
} }
if(isParallaxLayer){ if(isParallaxLayer){
console.log("Adding parallax layer: " + layer.name) console.log("Adding parallax layer: " + layer.name)
sceneLayer = this.scene.addParallaxLayer(layer.name, new Vec2(1, 1)); sceneLayer = this.scene.addParallaxLayer(layer.name, new Vec2(1, 1), depth);
} else { } else {
sceneLayer = this.scene.addLayer(layer.name); sceneLayer = this.scene.addLayer(layer.name, depth);
} }
if(layer.type === "tilelayer"){ if(layer.type === "tilelayer"){
@ -144,19 +147,19 @@ export default class TilemapFactory {
// Layer is an object layer, so add each object as a sprite to a new layer // Layer is an object layer, so add each object as a sprite to a new layer
for(let obj of layer.objects){ for(let obj of layer.objects){
// Check if obj is collidable // Check if obj is collidable
let isCollidable = false;
let hasPhysics = false; let hasPhysics = false;
let isStatic = true; let isCollidable = false;
let isTrigger = false;
let group = ""; let group = "";
if(obj.properties){ if(obj.properties){
for(let prop of obj.properties){ for(let prop of obj.properties){
if(prop.name === "Collidable"){ if(prop.name === "HasPhysics"){
isCollidable = prop.value;
} else if(prop.name === "Static"){
isStatic = prop.value;
} else if(prop.name === "hasPhysics"){
hasPhysics = prop.value; hasPhysics = prop.value;
} else if(prop.name === "Collidable"){
isCollidable = prop.value;
} else if(prop.name === "IsTrigger"){
isTrigger = prop.value;
} else if(prop.name === "Group"){ } else if(prop.name === "Group"){
group = prop.value; group = prop.value;
} }
@ -194,8 +197,10 @@ export default class TilemapFactory {
// Now we have sprite. Associate it with our physics object if there is one // Now we have sprite. Associate it with our physics object if there is one
if(hasPhysics){ if(hasPhysics){
sprite.addPhysics(sprite.boundary.clone(), Vec2.ZERO, isCollidable, isStatic); // Make the sprite a static physics object
sprite.addPhysics(sprite.boundary.clone(), Vec2.ZERO, isCollidable, true);
sprite.group = group; sprite.group = group;
sprite.isTrigger = isTrigger;
} }
} }
} }

View File

@ -180,7 +180,7 @@ export default class Scene implements Updateable {
this.renderingManager.render(visibleSet, this.tilemaps, this.uiLayers); this.renderingManager.render(visibleSet, this.tilemaps, this.uiLayers);
let nodes = this.sceneGraph.getAllNodes(); let nodes = this.sceneGraph.getAllNodes();
this.tilemaps.forEach(tilemap => tilemap.visible && tilemap.debugRender()); this.tilemaps.forEach(tilemap => tilemap.visible ? nodes.push(tilemap) : 0);
Debug.setNodes(nodes); Debug.setNodes(nodes);
} }

View File

@ -37,7 +37,7 @@ export default class Viewport {
/** The size of the canvas */ /** The size of the canvas */
private canvasSize: Vec2; private canvasSize: Vec2;
constructor(initialPosition: Vec2, canvasSize: Vec2){ constructor(canvasSize: Vec2, zoomLevel: number){
this.view = new AABB(Vec2.ZERO, Vec2.ZERO); this.view = new AABB(Vec2.ZERO, Vec2.ZERO);
this.boundary = new AABB(Vec2.ZERO, Vec2.ZERO); this.boundary = new AABB(Vec2.ZERO, Vec2.ZERO);
this.lastPositions = new Queue(); this.lastPositions = new Queue();
@ -46,13 +46,20 @@ export default class Viewport {
this.canvasSize = Vec2.ZERO; this.canvasSize = Vec2.ZERO;
this.focus = Vec2.ZERO; this.focus = Vec2.ZERO;
// Set the center (and make the viewport stay there) // Set the size of the canvas
this.setCenter(initialPosition); this.setCanvasSize(canvasSize);
this.setFocus(initialPosition);
console.log(canvasSize, zoomLevel);
// Set the size of the viewport // Set the size of the viewport
this.setSize(canvasSize); this.setSize(canvasSize);
this.setCanvasSize(canvasSize); this.setZoomLevel(zoomLevel);
console.log(this.getHalfSize().toString());
// Set the center (and make the viewport stay there)
this.setCenter(this.view.halfSize.clone());
this.setFocus(this.view.halfSize.clone());
} }
/** Enables the viewport to zoom in and out */ /** Enables the viewport to zoom in and out */

View File

@ -11,6 +11,22 @@ export default class MathUtils {
return x < 0 ? -1 : 1; return x < 0 ? -1 : 1;
} }
/**
* Returns whether or not x is between a and b
* @param a The min bound
* @param b The max bound
* @param x The value to check
* @param exclusive Whether or not a and b are exclusive bounds
* @returns True if x is between a and b, false otherwise
*/
static between(a: number, b: number, x: number, exclusive?: boolean): boolean {
if(exclusive){
return (a < x) && (x < b);
} else {
return (a <= x) && (x <= b);
}
}
/** /**
* Clamps the value x to the range [min, max], rounding up or down if needed * Clamps the value x to the range [min, max], rounding up or down if needed
* @param x The value to be clamped * @param x The value to be clamped

View File

@ -0,0 +1,2 @@
# Demos
This folder contains the demo projects created in the guides section of the Wolfie2D documentation, as well as any extra demos created for Wolfie2D.

View File

@ -1,7 +1,6 @@
/* #################### IMPORTS #################### */ /* #################### IMPORTS #################### */
// Import from Wolfie2D or your own files here // Import from Wolfie2D or your own files here
import Vec2 from "./Wolfie2D/DataTypes/Vec2"; import Vec2 from "./Wolfie2D/DataTypes/Vec2";
import Debug from "./Wolfie2D/Debug/Debug";
import Input from "./Wolfie2D/Input/Input"; import Input from "./Wolfie2D/Input/Input";
import Graphic from "./Wolfie2D/Nodes/Graphic"; import Graphic from "./Wolfie2D/Nodes/Graphic";
import { GraphicType } from "./Wolfie2D/Nodes/Graphics/GraphicTypes"; import { GraphicType } from "./Wolfie2D/Nodes/Graphics/GraphicTypes";
@ -32,7 +31,7 @@ export default class default_scene extends Scene {
// The first argument is the key of the sprite (you get to decide what it is). // The first argument is the key of the sprite (you get to decide what it is).
// The second argument is the path to the actual image. // The second argument is the path to the actual image.
// Paths start in the "dist/" folder, so start building your path from there // Paths start in the "dist/" folder, so start building your path from there
this.load.image("logo", "assets/wolfie2d_text.png"); this.load.image("logo", "demo_assets/wolfie2d_text.png");
} }
// startScene() is where you should build any game objects you wish to have in your scene, // startScene() is where you should build any game objects you wish to have in your scene,

View File

@ -1,13 +1,19 @@
import Game from "./Wolfie2D/Loop/Game"; import Game from "./Wolfie2D/Loop/Game";
import default_scene from "./default_scene"; import Platformer from "./Platformer";
// The main function is your entrypoint into Wolfie2D. Specify your first scene and any options here. // The main function is your entrypoint into Wolfie2D. Specify your first scene and any options here.
(function main(){ (function main(){
// These are options for initializing the game // These are options for initializing the game
// Here, we'll simply set the size of the viewport, and make the background of the game black // Here, we'll set the size of the viewport, color the background, and set up key bindings.
let options = { let options = {
viewportSize: {x: 800, y: 600}, canvasSize: {x: 800, y: 600},
clearColor: {r: 0, g: 0, b: 0}, zoomLevel: 4,
clearColor: {r: 34, g: 32, b: 52},
inputs: [
{ name: "left", keys: ["a"] },
{ name: "right", keys: ["d"] },
{ name: "jump", keys: ["space", "w"]}
]
} }
// Create our game. This will create all of the systems. // Create our game. This will create all of the systems.
@ -20,5 +26,5 @@ import default_scene from "./default_scene";
let sceneOptions = {}; let sceneOptions = {};
// Add our first scene. This will load this scene into the game world. // Add our first scene. This will load this scene into the game world.
demoGame.getSceneManager().addScene(default_scene, sceneOptions); demoGame.getSceneManager().addScene(Platformer, sceneOptions);
})(); })();