diff --git a/.gitignore b/.gitignore index d26c746..c1721ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,12 @@ +# Exclude node modules node_modules -dist/ \ No newline at end of file + +# 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/ + diff --git a/dist/demo_assets/images/platformer_background.png b/dist/demo_assets/images/platformer_background.png new file mode 100644 index 0000000..2f221a9 Binary files /dev/null and b/dist/demo_assets/images/platformer_background.png differ diff --git a/dist/demo_assets/images/wolfie2d_text.png b/dist/demo_assets/images/wolfie2d_text.png new file mode 100644 index 0000000..53d51d9 Binary files /dev/null and b/dist/demo_assets/images/wolfie2d_text.png differ diff --git a/dist/demo_assets/sounds/jump.wav b/dist/demo_assets/sounds/jump.wav new file mode 100644 index 0000000..71d9bb8 Binary files /dev/null and b/dist/demo_assets/sounds/jump.wav differ diff --git a/dist/demo_assets/spritesheets/platformer/player.json b/dist/demo_assets/spritesheets/platformer/player.json new file mode 100644 index 0000000..c9eab64 --- /dev/null +++ b/dist/demo_assets/spritesheets/platformer/player.json @@ -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}] + } + ] +} \ No newline at end of file diff --git a/dist/demo_assets/spritesheets/platformer/player.png b/dist/demo_assets/spritesheets/platformer/player.png new file mode 100644 index 0000000..4db71ac Binary files /dev/null and b/dist/demo_assets/spritesheets/platformer/player.png differ diff --git a/dist/demo_assets/tilemaps/platformer/platformer.json b/dist/demo_assets/tilemaps/platformer/platformer.json new file mode 100644 index 0000000..15654e1 --- /dev/null +++ b/dist/demo_assets/tilemaps/platformer/platformer.json @@ -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 +} \ No newline at end of file diff --git a/dist/demo_assets/tilemaps/platformer/platformer.png b/dist/demo_assets/tilemaps/platformer/platformer.png new file mode 100644 index 0000000..288791f Binary files /dev/null and b/dist/demo_assets/tilemaps/platformer/platformer.png differ diff --git a/src/Platformer.ts b/src/Platformer.ts new file mode 100644 index 0000000..0f3aabe --- /dev/null +++ b/src/Platformer.ts @@ -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); + } +} \ No newline at end of file diff --git a/src/PlatformerPlayerController.ts b/src/PlatformerPlayerController.ts new file mode 100644 index 0000000..98e4648 --- /dev/null +++ b/src/PlatformerPlayerController.ts @@ -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): 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); + } +} \ No newline at end of file diff --git a/src/Wolfie2D/AI/ControllerAI.ts b/src/Wolfie2D/AI/ControllerAI.ts new file mode 100644 index 0000000..8eb3c62 --- /dev/null +++ b/src/Wolfie2D/AI/ControllerAI.ts @@ -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): void { + this.owner = owner; + } + + handleEvent(event: GameEvent): void {} + + update(deltaT: number): void {} +} \ No newline at end of file diff --git a/src/Wolfie2D/AI/StateMachineAI.ts b/src/Wolfie2D/AI/StateMachineAI.ts index 7322544..1ee0ddf 100644 --- a/src/Wolfie2D/AI/StateMachineAI.ts +++ b/src/Wolfie2D/AI/StateMachineAI.ts @@ -1,5 +1,6 @@ import AI from "../DataTypes/Interfaces/AI"; import StateMachine from "../DataTypes/State/StateMachine"; +import GameEvent from "../Events/GameEvent"; import GameNode from "../Nodes/GameNode"; /** diff --git a/src/Wolfie2D/DataTypes/Interfaces/AI.ts b/src/Wolfie2D/DataTypes/Interfaces/AI.ts index 3e544fc..a7572e9 100644 --- a/src/Wolfie2D/DataTypes/Interfaces/AI.ts +++ b/src/Wolfie2D/DataTypes/Interfaces/AI.ts @@ -1,4 +1,6 @@ +import GameEvent from "../../Events/GameEvent"; import GameNode from "../../Nodes/GameNode"; +import Actor from "./Actor"; import Updateable from "./Updateable"; /** @@ -7,4 +9,7 @@ import Updateable from "./Updateable"; export default interface AI extends Updateable { /** Initializes the AI with the actor and any additional config */ initializeAI(owner: GameNode, options: Record): void; + + /** Handles events from the Actor */ + handleEvent(event: GameEvent): void; } \ No newline at end of file diff --git a/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts b/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts index 260f247..1d2b94e 100644 --- a/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts +++ b/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts @@ -1,4 +1,7 @@ +import Physical from "../Interfaces/Physical"; 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. @@ -6,16 +9,32 @@ import AABB from "../Shapes/AABB"; export default class AreaCollision { /** The area of the overlap for the colliding objects */ area: number; + /** The AABB of the other collider in this collision */ 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 * @param area The area of the collision * @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.collider = collider; + this.collider = collider; + this.other = other; + this.type = type; + this.tile = tile; } } \ No newline at end of file diff --git a/src/Wolfie2D/DataTypes/Shapes/AABB.ts b/src/Wolfie2D/DataTypes/Shapes/AABB.ts index a0a4cce..ad6fb42 100644 --- a/src/Wolfie2D/DataTypes/Shapes/AABB.ts +++ b/src/Wolfie2D/DataTypes/Shapes/AABB.ts @@ -146,7 +146,13 @@ export default class AABB extends Shape { // We hit on the left or right size hit.normal.x = -signX; 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 { + // We hit on the top or bottom hit.normal.x = 0; hit.normal.y = -signY; } @@ -190,6 +196,70 @@ export default class AABB extends Shape { 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 * @param other The other AABB diff --git a/src/Wolfie2D/DataTypes/State/StateMachine.ts b/src/Wolfie2D/DataTypes/State/StateMachine.ts index bd6fd5d..3215c62 100644 --- a/src/Wolfie2D/DataTypes/State/StateMachine.ts +++ b/src/Wolfie2D/DataTypes/State/StateMachine.ts @@ -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. * @param event The game event to process */ - handleInput(event: GameEvent): void { - this.currentState.handleInput(event); + handleEvent(event: GameEvent): void { + if(this.active){ + this.currentState.handleInput(event); + } } // @implemented 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 this.currentState.update(deltaT); } diff --git a/src/Wolfie2D/Debug/Debug.ts b/src/Wolfie2D/Debug/Debug.ts index a4cefd3..7cb2090 100644 --- a/src/Wolfie2D/Debug/Debug.ts +++ b/src/Wolfie2D/Debug/Debug.ts @@ -62,6 +62,9 @@ export default class Debug { * @param color The color of the box to draw */ static drawBox(center: Vec2, halfSize: Vec2, filled: boolean, color: Color): void { + let alpha = this.debugRenderingContext.globalAlpha; + this.debugRenderingContext.globalAlpha = color.a; + if(filled){ this.debugRenderingContext.fillStyle = color.toString(); 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.strokeRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2); } + + this.debugRenderingContext.globalAlpha = alpha; } /** diff --git a/src/Wolfie2D/Loop/Game.ts b/src/Wolfie2D/Loop/Game.ts index db3036e..b5bc04b 100644 --- a/src/Wolfie2D/Loop/Game.ts +++ b/src/Wolfie2D/Loop/Game.ts @@ -70,8 +70,8 @@ export default class Game { this.DEBUG_CANVAS = document.getElementById("debug-canvas"); // Give the canvas a size and get the rendering context - this.WIDTH = this.gameOptions.viewportSize.x; - this.HEIGHT = this.gameOptions.viewportSize.y; + this.WIDTH = this.gameOptions.canvasSize.x; + this.HEIGHT = this.gameOptions.canvasSize.y; // For now, just hard code a canvas renderer. We can do this with options later this.renderingManager = new CanvasRenderer(); @@ -89,8 +89,8 @@ export default class Game { } // Size the viewport to the game canvas - const viewportSize = new Vec2(this.WIDTH, this.HEIGHT); - this.viewport = new Viewport(viewportSize.scaled(0.5), viewportSize); + const canvasSize = new Vec2(this.WIDTH, this.HEIGHT); + this.viewport = new Viewport(canvasSize, this.gameOptions.zoomLevel); // Initialize all necessary game subsystems this.eventQueue = EventQueue.getInstance(); diff --git a/src/Wolfie2D/Loop/GameOptions.ts b/src/Wolfie2D/Loop/GameOptions.ts index f6bb9e8..50bd213 100644 --- a/src/Wolfie2D/Loop/GameOptions.ts +++ b/src/Wolfie2D/Loop/GameOptions.ts @@ -3,7 +3,10 @@ /** The options for initializing the @reference[GameLoop] */ export default class GameOptions { /** 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 */ clearColor: {r: number, g: number, b: number} @@ -25,7 +28,8 @@ export default class GameOptions { static parse(options: Record): 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.inputs = options.inputs ? options.inputs : []; gOpt.showDebug = !!options.showDebug; diff --git a/src/Wolfie2D/Nodes/CanvasNode.ts b/src/Wolfie2D/Nodes/CanvasNode.ts index b059462..b4d4f30 100644 --- a/src/Wolfie2D/Nodes/CanvasNode.ts +++ b/src/Wolfie2D/Nodes/CanvasNode.ts @@ -101,8 +101,7 @@ export default abstract class CanvasNode extends GameNode implements Region { // @implemented debugRender(): void { + Debug.drawBox(this.relativePosition, this.sizeWithZoom, false, Color.BLUE); super.debugRender(); - let color = this.isColliding ? Color.RED : Color.GREEN; - Debug.drawBox(this.relativePosition, this.sizeWithZoom, false, color); } } \ No newline at end of file diff --git a/src/Wolfie2D/Nodes/GameNode.ts b/src/Wolfie2D/Nodes/GameNode.ts index 25cb554..51854b7 100644 --- a/src/Wolfie2D/Nodes/GameNode.ts +++ b/src/Wolfie2D/Nodes/GameNode.ts @@ -31,12 +31,12 @@ export default abstract class GameNode implements Positioned, Unique, Updateable private _id: number; /*---------- PHYSICAL ----------*/ - hasPhysics: boolean; - moving: boolean; - onGround: boolean; - onWall: boolean; - onCeiling: boolean; - active: boolean; + hasPhysics: boolean = false; + moving: boolean = false; + onGround: boolean = false; + onWall: boolean = false; + onCeiling: boolean = false; + active: boolean = false; collisionShape: Shape; colliderOffset: Vec2; isStatic: boolean; @@ -97,10 +97,19 @@ export default abstract class GameNode implements Positioned, Unique, Updateable } 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 zoom = this.scene.getViewScale(); - return this.position.clone().sub(origin).scale(zoom); + return point.clone().sub(origin).scale(zoom); } /*---------- UNIQUE ----------*/ @@ -132,7 +141,6 @@ export default abstract class GameNode implements Positioned, Unique, Updateable * @param velocity The velocity with which the object will move. */ finishMove(): void { - console.log("finish"); this.moving = false; this.position.add(this._velocity); if(this.pathfinding){ @@ -307,22 +315,35 @@ export default abstract class GameNode implements Positioned, Unique, Updateable * @param deltaT The timestep of the update. */ 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); } // @implemented debugRender(): void { - let color = this.isColliding ? Color.RED : Color.GREEN; - Debug.drawPoint(this.relativePosition, color); + // Draw the position of this GameNode + Debug.drawPoint(this.relativePosition, Color.BLUE); // If velocity is not zero, draw a vector for it 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.isCollidable && this.collisionShape){ - Debug.drawBox(this.collisionShape.center, this.collisionShape.halfSize, false, Color.RED); + if(this.hasPhysics && this.collisionShape){ + 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); } } } diff --git a/src/Wolfie2D/Physics/BasicPhysicsManager.ts b/src/Wolfie2D/Physics/BasicPhysicsManager.ts index 8aec56b..5144f2b 100644 --- a/src/Wolfie2D/Physics/BasicPhysicsManager.ts +++ b/src/Wolfie2D/Physics/BasicPhysicsManager.ts @@ -89,6 +89,7 @@ export default class BasicPhysicsManager extends PhysicsManager { node.onCeiling = false; node.onWall = false; node.collidedWithTilemap = false; + node.isColliding = false; // Update the swept shapes of each node if(node.moving){ @@ -110,7 +111,7 @@ export default class BasicPhysicsManager extends PhysicsManager { let area = node.sweptRect.overlapArea(collider); if(area > 0){ // 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); if(area > 0){ // 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 overlaps = overlaps.sort((a, b) => b.area - a.area); + // Keep track of hits to use later + let hits = []; /*---------- RESOLUTION PHASE ----------*/ // 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!) // Start the sweep at the position of this node with a delta of _velocity const point = node.collisionShape.center; const delta = node._velocity; 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); + overlap.hit = hit; + if(hit !== null){ + hits.push(hit); + // We got a hit, resolve with the time inside of the hit let tnearx = hit.nearTimes.x; let tneary = hit.nearTimes.y; @@ -164,11 +171,39 @@ export default class BasicPhysicsManager extends PhysicsManager { if(hit.nearTimes.x >= 0 && hit.nearTimes.x < 1){ - node._velocity.x = node._velocity.x * tnearx; + // Any tilemap objects that made it here are collidable + if(overlap.type === "Tilemap" || overlap.other.isCollidable){ + node._velocity.x = node._velocity.x * tnearx; + node.isColliding = true; + } } if(hit.nearTimes.y >= 0 && hit.nearTimes.y < 1){ - node._velocity.y = node._velocity.y * tneary; + // Any tilemap objects that made it here are collidable + if(overlap.type === "Tilemap" || overlap.other.isCollidable){ + 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); if(area > 0){ // We had a collision - overlaps.push(new AreaCollision(area, collider)); + overlaps.push(new AreaCollision(area, collider, tilemap, "Tilemap", new Vec2(col, row))); } } } diff --git a/src/Wolfie2D/Rendering/Animations/AnimationManager.ts b/src/Wolfie2D/Rendering/Animations/AnimationManager.ts index e4cc0c5..05dfc6b 100644 --- a/src/Wolfie2D/Rendering/Animations/AnimationManager.ts +++ b/src/Wolfie2D/Rendering/Animations/AnimationManager.ts @@ -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 * @param animation The name of the animation to play diff --git a/src/Wolfie2D/Scene/Factories/TilemapFactory.ts b/src/Wolfie2D/Scene/Factories/TilemapFactory.ts index eafea11..6eb1d7b 100644 --- a/src/Wolfie2D/Scene/Factories/TilemapFactory.ts +++ b/src/Wolfie2D/Scene/Factories/TilemapFactory.ts @@ -77,20 +77,23 @@ export default class TilemapFactory { let sceneLayer; let isParallaxLayer = false; + let depth = 0; if(layer.properties){ for(let prop of layer.properties){ if(prop.name === "Parallax"){ isParallaxLayer = prop.value; + } else if(prop.name === "Depth") { + depth = prop.value; } } } if(isParallaxLayer){ 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 { - sceneLayer = this.scene.addLayer(layer.name); + sceneLayer = this.scene.addLayer(layer.name, depth); } 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 for(let obj of layer.objects){ // Check if obj is collidable - let isCollidable = false; let hasPhysics = false; - let isStatic = true; + let isCollidable = false; + let isTrigger = false; let group = ""; if(obj.properties){ for(let prop of obj.properties){ - if(prop.name === "Collidable"){ - isCollidable = prop.value; - } else if(prop.name === "Static"){ - isStatic = prop.value; - } else if(prop.name === "hasPhysics"){ + if(prop.name === "HasPhysics"){ hasPhysics = prop.value; + } else if(prop.name === "Collidable"){ + isCollidable = prop.value; + } else if(prop.name === "IsTrigger"){ + isTrigger = prop.value; } else if(prop.name === "Group"){ 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 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.isTrigger = isTrigger; } } } diff --git a/src/Wolfie2D/Scene/Scene.ts b/src/Wolfie2D/Scene/Scene.ts index 9669840..8dcbcde 100644 --- a/src/Wolfie2D/Scene/Scene.ts +++ b/src/Wolfie2D/Scene/Scene.ts @@ -180,7 +180,7 @@ export default class Scene implements Updateable { this.renderingManager.render(visibleSet, this.tilemaps, this.uiLayers); 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); } diff --git a/src/Wolfie2D/SceneGraph/Viewport.ts b/src/Wolfie2D/SceneGraph/Viewport.ts index c09e679..320d469 100644 --- a/src/Wolfie2D/SceneGraph/Viewport.ts +++ b/src/Wolfie2D/SceneGraph/Viewport.ts @@ -37,7 +37,7 @@ export default class Viewport { /** The size of the canvas */ private canvasSize: Vec2; - constructor(initialPosition: Vec2, canvasSize: Vec2){ + constructor(canvasSize: Vec2, zoomLevel: number){ this.view = new AABB(Vec2.ZERO, Vec2.ZERO); this.boundary = new AABB(Vec2.ZERO, Vec2.ZERO); this.lastPositions = new Queue(); @@ -46,13 +46,20 @@ export default class Viewport { this.canvasSize = Vec2.ZERO; this.focus = Vec2.ZERO; - // Set the center (and make the viewport stay there) - this.setCenter(initialPosition); - this.setFocus(initialPosition); + // Set the size of the canvas + this.setCanvasSize(canvasSize); + + console.log(canvasSize, zoomLevel); // Set the size of the viewport 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 */ diff --git a/src/Wolfie2D/Utils/MathUtils.ts b/src/Wolfie2D/Utils/MathUtils.ts index d763cc1..a910b18 100644 --- a/src/Wolfie2D/Utils/MathUtils.ts +++ b/src/Wolfie2D/Utils/MathUtils.ts @@ -11,6 +11,22 @@ export default class MathUtils { 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 * @param x The value to be clamped diff --git a/src/Wolfie2D/_Demos/readme.md b/src/Wolfie2D/_Demos/readme.md new file mode 100644 index 0000000..6086b2f --- /dev/null +++ b/src/Wolfie2D/_Demos/readme.md @@ -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. \ No newline at end of file diff --git a/src/default_scene.ts b/src/default_scene.ts index 51303a8..8166edb 100644 --- a/src/default_scene.ts +++ b/src/default_scene.ts @@ -1,7 +1,6 @@ /* #################### IMPORTS #################### */ // Import from Wolfie2D or your own files here import Vec2 from "./Wolfie2D/DataTypes/Vec2"; -import Debug from "./Wolfie2D/Debug/Debug"; import Input from "./Wolfie2D/Input/Input"; import Graphic from "./Wolfie2D/Nodes/Graphic"; 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 second argument is the path to the actual image. // 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, diff --git a/src/main.ts b/src/main.ts index c863d82..d5c2cc3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,19 @@ 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. (function main(){ // 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 = { - viewportSize: {x: 800, y: 600}, - clearColor: {r: 0, g: 0, b: 0}, + canvasSize: {x: 800, y: 600}, + 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. @@ -20,5 +26,5 @@ import default_scene from "./default_scene"; let sceneOptions = {}; // Add our first scene. This will load this scene into the game world. - demoGame.getSceneManager().addScene(default_scene, sceneOptions); + demoGame.getSceneManager().addScene(Platformer, sceneOptions); })(); \ No newline at end of file