diff --git a/Tutorials/TurnBasedRPG/Project.xml b/Tutorials/TurnBasedRPG/Project.xml index 381421f9f..5a54627d5 100644 --- a/Tutorials/TurnBasedRPG/Project.xml +++ b/Tutorials/TurnBasedRPG/Project.xml @@ -62,6 +62,7 @@ + diff --git a/Tutorials/TurnBasedRPG/assets/data/room-001.json b/Tutorials/TurnBasedRPG/assets/data/room-001.json index b99e25a37..bcd4969e2 100644 --- a/Tutorials/TurnBasedRPG/assets/data/room-001.json +++ b/Tutorials/TurnBasedRPG/assets/data/room-001.json @@ -1,5 +1,5 @@ { - "ogmoVersion": "3.3.0", + "ogmoVersion": "3.4.0", "width": 640, "height": 480, "offsetX": 0, @@ -16,33 +16,35 @@ "gridCellsY": 30, "entities": [ {"name": "player", "id": 0, "_eid": "40117707", "x": 128, "y": 144, "originX": 0, "originY": 0}, - {"name": "coin", "id": 1, "_eid": "40304284", "x": 320, "y": 96, "originX": 0, "originY": 0}, - {"name": "coin", "id": 2, "_eid": "40304284", "x": 352, "y": 80, "originX": 0, "originY": 0}, - {"name": "coin", "id": 3, "_eid": "40304284", "x": 192, "y": 144, "originX": 0, "originY": 0}, - {"name": "coin", "id": 4, "_eid": "40304284", "x": 224, "y": 144, "originX": 0, "originY": 0}, - {"name": "coin", "id": 5, "_eid": "40304284", "x": 256, "y": 144, "originX": 0, "originY": 0}, - {"name": "coin", "id": 6, "_eid": "40304284", "x": 288, "y": 144, "originX": 0, "originY": 0}, - {"name": "coin", "id": 7, "_eid": "40304284", "x": 304, "y": 160, "originX": 0, "originY": 0}, - {"name": "coin", "id": 8, "_eid": "40304284", "x": 304, "y": 192, "originX": 0, "originY": 0}, - {"name": "coin", "id": 9, "_eid": "40304284", "x": 304, "y": 224, "originX": 0, "originY": 0}, - {"name": "coin", "id": 10, "_eid": "40304284", "x": 304, "y": 256, "originX": 0, "originY": 0}, - {"name": "coin", "id": 11, "_eid": "40304284", "x": 304, "y": 304, "originX": 0, "originY": 0}, - {"name": "coin", "id": 12, "_eid": "40304284", "x": 320, "y": 288, "originX": 0, "originY": 0}, - {"name": "coin", "id": 13, "_eid": "40304284", "x": 336, "y": 304, "originX": 0, "originY": 0}, - {"name": "coin", "id": 14, "_eid": "40304284", "x": 288, "y": 288, "originX": 0, "originY": 0}, - {"name": "coin", "id": 15, "_eid": "40304284", "x": 272, "y": 304, "originX": 0, "originY": 0}, - {"name": "coin", "id": 16, "_eid": "40304284", "x": 256, "y": 224, "originX": 0, "originY": 0}, - {"name": "coin", "id": 17, "_eid": "40304284", "x": 256, "y": 192, "originX": 0, "originY": 0}, - {"name": "coin", "id": 18, "_eid": "40304284", "x": 208, "y": 192, "originX": 0, "originY": 0}, - {"name": "coin", "id": 19, "_eid": "40304284", "x": 208, "y": 224, "originX": 0, "originY": 0}, - {"name": "enemy", "id": 20, "_eid": "40444291", "x": 304, "y": 112, "originX": 0, "originY": 0}, - {"name": "enemy", "id": 21, "_eid": "40444291", "x": 272, "y": 208, "originX": 0, "originY": 0}, - {"name": "boss", "id": 22, "_eid": "40444462", "x": 304, "y": 288, "originX": 0, "originY": 0} + {"name": "coin", "id": 1, "_eid": "40304284", "x": 416, "y": 48, "originX": 0, "originY": 0}, + {"name": "coin", "id": 2, "_eid": "40304284", "x": 496, "y": 48, "originX": 0, "originY": 0}, + {"name": "coin", "id": 3, "_eid": "40304284", "x": 192, "y": 136, "originX": 0, "originY": 0}, + {"name": "coin", "id": 4, "_eid": "40304284", "x": 240, "y": 136, "originX": 0, "originY": 0}, + {"name": "coin", "id": 5, "_eid": "40304284", "x": 288, "y": 136, "originX": 0, "originY": 0}, + {"name": "coin", "id": 6, "_eid": "40304284", "x": 336, "y": 136, "originX": 0, "originY": 0}, + {"name": "coin", "id": 7, "_eid": "40304284", "x": 392, "y": 176, "originX": 0, "originY": 0}, + {"name": "coin", "id": 8, "_eid": "40304284", "x": 392, "y": 224, "originX": 0, "originY": 0}, + {"name": "coin", "id": 9, "_eid": "40304284", "x": 392, "y": 272, "originX": 0, "originY": 0}, + {"name": "coin", "id": 10, "_eid": "40304284", "x": 392, "y": 320, "originX": 0, "originY": 0}, + {"name": "coin", "id": 11, "_eid": "40304284", "x": 336, "y": 400, "originX": 0, "originY": 0}, + {"name": "coin", "id": 12, "_eid": "40304284", "x": 368, "y": 368, "originX": 0, "originY": 0}, + {"name": "coin", "id": 13, "_eid": "40304284", "x": 400, "y": 400, "originX": 0, "originY": 0}, + {"name": "coin", "id": 14, "_eid": "40304284", "x": 304, "y": 368, "originX": 0, "originY": 0}, + {"name": "coin", "id": 15, "_eid": "40304284", "x": 272, "y": 400, "originX": 0, "originY": 0}, + {"name": "coin", "id": 16, "_eid": "40304284", "x": 304, "y": 256, "originX": 0, "originY": 0}, + {"name": "coin", "id": 17, "_eid": "40304284", "x": 304, "y": 224, "originX": 0, "originY": 0}, + {"name": "coin", "id": 18, "_eid": "40304284", "x": 240, "y": 224, "originX": 0, "originY": 0}, + {"name": "coin", "id": 19, "_eid": "40304284", "x": 240, "y": 256, "originX": 0, "originY": 0}, + {"name": "enemy", "id": 20, "_eid": "40444291", "x": 464, "y": 64, "originX": 0, "originY": 0}, + {"name": "enemy", "id": 21, "_eid": "40444291", "x": 272, "y": 240, "originX": 0, "originY": 0}, + {"name": "boss", "id": 22, "_eid": "40444462", "x": 336, "y": 384, "originX": 0, "originY": 0}, + {"name": "coin", "id": 23, "_eid": "40304284", "x": 496, "y": 80, "originX": 0, "originY": 0}, + {"name": "coin", "id": 24, "_eid": "40304284", "x": 416, "y": 80, "originX": 0, "originY": 0} ] }, { "name": "walls", - "_eid": "40116503", + "_eid": "02788814", "offsetX": 0, "offsetY": 0, "gridCellWidth": 16, @@ -50,7 +52,7 @@ "gridCellsX": 40, "gridCellsY": 30, "tileset": "tiles", - "data": [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2, 2, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 1, 1, 1, 1, 1, 1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2, 2, 2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + "data": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 16, 16, 16, 16, 16, 21, 28, 28, 22, 28, 22, 22, 28, 22, 22, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 4, 9, 9, 9, 9, 9, 9, 9, 5, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 16, 16, 21, 7, 3, 1, 1, 1, 2, 1, 1, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 18, 18, 18, 18, 18, 18, 18, 19, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 3, 1, 1, 1, 1, 1, 1, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 28, 28, 28, 28, 22, 22, 28, 23, 16, 16, 16, 20, 16, 16, 16, 16, 16, 16, 24, 21, 7, 1, 1, 1, 2, 1, 1, 1, 2, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 4, 5, 9, 9, 9, 9, 9, 34, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 35, 7, 1, 1, 1, 1, 3, 1, 1, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 1, 1, 1, 1, 1, 1, 32, 28, 28, 28, 22, 28, 28, 28, 28, 22, 28, 28, 33, 7, 1, 30, 26, 26, 26, 26, 26, 26, 27, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 1, 1, 1, 1, 1, 1, 9, 9, 5, 9, 9, 9, 9, 9, 9, 9, 5, 9, 9, 8, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 12, 13, 13, 13, 13, 13, 14, 30, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 31, 10, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 1, 1, 1, 2, 1, 1, 23, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 21, 11, 1, 23, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 1, 1, 1, 2, 1, 1, 23, 21, 28, 28, 28, 28, 28, 28, 28, 22, 22, 34, 35, 7, 1, 23, 16, 16, 16, 16, 20, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 3, 1, 1, 1, 1, 1, 23, 21, 4, 9, 9, 9, 9, 9, 9, 9, 9, 32, 33, 7, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 25, 26, 26, 26, 26, 26, 26, 26, 27, 21, 7, 1, 1, 1, 2, 1, 1, 1, 1, 9, 9, 8, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 21, 7, 2, 1, 1, 1, 1, 1, 1, 1, 30, 31, 10, 1, 23, 16, 20, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 16, 16, 16, 21, 7, 1, 1, 1, 1, 1, 1, 2, 1, 23, 21, 12, 14, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 21, 12, 14, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 17, 18, 18, 18, 18, 18, 18, 18, 35, 12, 14, 34, 19, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 22, 22, 22, 22, 29, 28, 28, 33, 12, 14, 32, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 21, 22, 22, 29, 28, 28, 28, 28, 33, 12, 14, 32, 23, 16, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 4, 9, 5, 9, 9, 9, 9, 9, 8, 1, 9, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 1, 1, 1, 1, 3, 1, 1, 1, 2, 1, 23, 20, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 16, 16, 16, 16, 16, 16, 21, 11, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 21, 7, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 23, 20, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 16, 21, 7, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], "exportMode": 0, "arrayMode": 0 } diff --git a/Tutorials/TurnBasedRPG/assets/data/turnBasedRPG.ogmo b/Tutorials/TurnBasedRPG/assets/data/turnBasedRPG.ogmo index ee52ccbe6..b01766817 100644 --- a/Tutorials/TurnBasedRPG/assets/data/turnBasedRPG.ogmo +++ b/Tutorials/TurnBasedRPG/assets/data/turnBasedRPG.ogmo @@ -1,6 +1,6 @@ { "name": "HaxeFlixel Tutorial", - "ogmoVersion": "3.3.0", + "ogmoVersion": "3.4.0", "levelPaths": ["."], "backgroundColor": "#282c34ff", "gridColor": "#3c4049cc", @@ -29,7 +29,7 @@ "definition": "tile", "name": "walls", "gridSize": {"x": 16, "y": 16}, - "exportID": "40116503", + "exportID": "02788814", "exportMode": 0, "arrayMode": 0, "defaultTileset": "tiles" @@ -54,7 +54,7 @@ {"x": 1, "y": 1} ] }, - "color": "#ff0000ff", + "color": "#00e3ffff", "tileX": false, "tileY": false, "tileSize": {"x": 16, "y": 16}, @@ -162,7 +162,7 @@ {"x": 1, "y": 1} ] }, - "color": "#00e3ffff", + "color": "#ff0000ff", "tileX": false, "tileY": false, "tileSize": {"x": 16, "y": 16}, @@ -182,6 +182,6 @@ } ], "tilesets": [ - {"label": "tiles", "path": "../images/tiles.png", "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAYAAABQrvyxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODRBQjNDODZENDhCMTFFMzk5N0RDMzAxQTc5NEM5RkMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODRBQjNDODdENDhCMTFFMzk5N0RDMzAxQTc5NEM5RkMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4NEFCM0M4NEQ0OEIxMUUzOTk3REMzMDFBNzk0QzlGQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4NEFCM0M4NUQ0OEIxMUUzOTk3REMzMDFBNzk0QzlGQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrOLMN8AAACUSURBVHja7JWhDoAgEEBPRrERbVYCX+If8Z8Eqo2IyYi7wOYYTncYuI3XD94bDKaUErSwWZtWY0izu3NAnc3zEhpBAVyIQvAeWuKPEEAAQ7I8IjjLswso5RHZq2iNUp7lFRoBI2AEQN+vEL5A+EM/MSvV7wm8ySNnjH0GfJGvRQhu8mWE/GPz2g/5hUVrkvw94hJgALO2Vr1s43dIAAAAAElFTkSuQmCC", "tileWidth": 16, "tileHeight": 16, "tileSeparationX": 0, "tileSeparationY": 0} + {"label": "tiles", "path": "../images/tiles.png", "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAACQCAMAAACPiZ3DAAAABGdBTUEAALGOfPtRkwAAAFRQTFRFAAAAqrfM/udh/q40/smc5O35af/UdeP/0XbQAJnbWmmIm0yj/3BtJZVq4Zpl6EU3Q+Gz////98KCz4JUvWxKUmB8wMvcJitEdjs26qVsi5u0PyYxhk+Z/gAAABx0Uk5TAP///////////////////////////////////6l3rVEAAAMZSURBVGiB7ZdbcusgDED90R0QkwTvf5+XNxKSLMBJm2mvpmMHDAch9KDb1slzUvr5bwB8fb0CYFCXtTY+wmtpC7MAvLwxKxoYA3+a0FwGgH0s2iDPNCYqsg7Isg4ID6/CFMBcBRANprZQPTpbz6STmdOACYuFLXQAo0i/gDHYqX4bIPeh1jiAGjjmolFA6zH1IQIMBaCVzRNkoRVA3sAMgJk/p8GPA07mvweAvn8eoA+mHviZADJABYBLB1vOxfgYBJwK2cIKIBCKDsuAqxq0hLMGaDr82BbGAayjzdiAc3XYXgRMGFHV4H63bljs/Y7G+3YBIEqPtGACA3Dh1hUfRVADtl0AgK8uAvYJCQDcfpkN5gC2tTe0J02KDVxrf7YNqG8ggFVtQH0D+YH7PBsM2OIkFpgIYGzxllh4XJT/gL8COIpMAw5RxgB1eHZ8nnAKcCiWWnkZBlgeYCcBew7+fQUQJxZAySLfCjiOPqWkXgZAfSUNTYsXcU4CMC4yC3jcbg8R0MwgA25eGMCQDXyHCLBg+eQDBHAkgLgFfIwLRlQBaaJ0jLoNxGwBATkuBUCyXviTAa74wymA00D3gwI43cIFG4z4gZBx5/zgfYDns6bi+A/GKcDfhgiACgG0EPA24gBdXeCjUdBgJK2XRf3qNJhm6oIHoB3UykTqAqlMaZ7vJYDej7PwACrHzgP2wershb9toiFbsL2t1r9JqLg092GLp08MCCVk2wy40QVGAaJUwONBUwolsYBqAw0gaSDfwAYB12Tjj7pJnw/69gbznQPpu6TzNp6VBsBeC+8D5J6I2gIA1oE+nOHbAzQbqIC281oAuzrQ5wPYTluQ9Y+xz/en9ysAug3OvxI/yMdXzZHTeBzV/C800hdyjOX82zECQAjHkh4kQP/uC0lLDxmg26ACunyQAbofdBr0AP0Y3w0Ir91xuw++SquxBMD5ILYnAUj79GMU4HYJsLtxAGuDQUCJ8OwHwBEmbED1/26A5geweuEL34AfxNTOyqAflCxM60JMabofKGld9wMVoPmBUhf0Y1TS+lXAP2vxLx2XEutMAAAAAElFTkSuQmCC", "tileWidth": 16, "tileHeight": 16, "tileSeparationX": 0, "tileSeparationY": 0, "tileMarginX": 0, "tileMarginY": 0} ] } \ No newline at end of file diff --git a/Tutorials/TurnBasedRPG/assets/images/bar_empty.png b/Tutorials/TurnBasedRPG/assets/images/bar_empty.png new file mode 100644 index 000000000..baec0777c Binary files /dev/null and b/Tutorials/TurnBasedRPG/assets/images/bar_empty.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/bar_filled.png b/Tutorials/TurnBasedRPG/assets/images/bar_filled.png new file mode 100644 index 000000000..20ca1b36b Binary files /dev/null and b/Tutorials/TurnBasedRPG/assets/images/bar_filled.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/boss.png b/Tutorials/TurnBasedRPG/assets/images/boss.png index 5e346d49f..5e529eeee 100644 Binary files a/Tutorials/TurnBasedRPG/assets/images/boss.png and b/Tutorials/TurnBasedRPG/assets/images/boss.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/button.png b/Tutorials/TurnBasedRPG/assets/images/button.png index 32f436866..61d6e6de2 100644 Binary files a/Tutorials/TurnBasedRPG/assets/images/button.png and b/Tutorials/TurnBasedRPG/assets/images/button.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/coin.png b/Tutorials/TurnBasedRPG/assets/images/coin.png index 2a5820aa8..5ea4008ab 100644 Binary files a/Tutorials/TurnBasedRPG/assets/images/coin.png and b/Tutorials/TurnBasedRPG/assets/images/coin.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/enemy.png b/Tutorials/TurnBasedRPG/assets/images/enemy.png index 7672375f2..07544008f 100644 Binary files a/Tutorials/TurnBasedRPG/assets/images/enemy.png and b/Tutorials/TurnBasedRPG/assets/images/enemy.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/font.png b/Tutorials/TurnBasedRPG/assets/images/font.png new file mode 100644 index 000000000..84e2038b8 Binary files /dev/null and b/Tutorials/TurnBasedRPG/assets/images/font.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/health.png b/Tutorials/TurnBasedRPG/assets/images/health.png index d39bd4ae8..7a549c929 100644 Binary files a/Tutorials/TurnBasedRPG/assets/images/health.png and b/Tutorials/TurnBasedRPG/assets/images/health.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/player.png b/Tutorials/TurnBasedRPG/assets/images/player.png index 6bb1e9add..e786ae632 100644 Binary files a/Tutorials/TurnBasedRPG/assets/images/player.png and b/Tutorials/TurnBasedRPG/assets/images/player.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/pointer.png b/Tutorials/TurnBasedRPG/assets/images/pointer.png index 7e0cc1ca4..38d46edf4 100644 Binary files a/Tutorials/TurnBasedRPG/assets/images/pointer.png and b/Tutorials/TurnBasedRPG/assets/images/pointer.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/small_button.png b/Tutorials/TurnBasedRPG/assets/images/small_button.png new file mode 100644 index 000000000..bd39131e9 Binary files /dev/null and b/Tutorials/TurnBasedRPG/assets/images/small_button.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/tiles.png b/Tutorials/TurnBasedRPG/assets/images/tiles.png index f16b622f5..429add584 100644 Binary files a/Tutorials/TurnBasedRPG/assets/images/tiles.png and b/Tutorials/TurnBasedRPG/assets/images/tiles.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/ui_section.png b/Tutorials/TurnBasedRPG/assets/images/ui_section.png new file mode 100644 index 000000000..d69ffec7d Binary files /dev/null and b/Tutorials/TurnBasedRPG/assets/images/ui_section.png differ diff --git a/Tutorials/TurnBasedRPG/assets/images/uiback.png b/Tutorials/TurnBasedRPG/assets/images/uiback.png new file mode 100644 index 000000000..864106cf0 Binary files /dev/null and b/Tutorials/TurnBasedRPG/assets/images/uiback.png differ diff --git a/Tutorials/TurnBasedRPG/assets/sounds/coin.wav b/Tutorials/TurnBasedRPG/assets/sounds/coin.wav index 5396aeeec..b92bdabdf 100644 Binary files a/Tutorials/TurnBasedRPG/assets/sounds/coin.wav and b/Tutorials/TurnBasedRPG/assets/sounds/coin.wav differ diff --git a/Tutorials/TurnBasedRPG/assets/sounds/combat.wav b/Tutorials/TurnBasedRPG/assets/sounds/combat.wav index ec0cc74e4..135ff3d8a 100644 Binary files a/Tutorials/TurnBasedRPG/assets/sounds/combat.wav and b/Tutorials/TurnBasedRPG/assets/sounds/combat.wav differ diff --git a/Tutorials/TurnBasedRPG/assets/sounds/fled.wav b/Tutorials/TurnBasedRPG/assets/sounds/fled.wav index ccd9497a3..862d8d3fe 100644 Binary files a/Tutorials/TurnBasedRPG/assets/sounds/fled.wav and b/Tutorials/TurnBasedRPG/assets/sounds/fled.wav differ diff --git a/Tutorials/TurnBasedRPG/assets/sounds/hurt.wav b/Tutorials/TurnBasedRPG/assets/sounds/hurt.wav index 54491328d..5c09f1165 100644 Binary files a/Tutorials/TurnBasedRPG/assets/sounds/hurt.wav and b/Tutorials/TurnBasedRPG/assets/sounds/hurt.wav differ diff --git a/Tutorials/TurnBasedRPG/assets/sounds/lose.wav b/Tutorials/TurnBasedRPG/assets/sounds/lose.wav index c543d5ec9..9d83705e5 100644 Binary files a/Tutorials/TurnBasedRPG/assets/sounds/lose.wav and b/Tutorials/TurnBasedRPG/assets/sounds/lose.wav differ diff --git a/Tutorials/TurnBasedRPG/assets/sounds/miss.wav b/Tutorials/TurnBasedRPG/assets/sounds/miss.wav index 55a36b623..95d0a2da1 100644 Binary files a/Tutorials/TurnBasedRPG/assets/sounds/miss.wav and b/Tutorials/TurnBasedRPG/assets/sounds/miss.wav differ diff --git a/Tutorials/TurnBasedRPG/assets/sounds/select.wav b/Tutorials/TurnBasedRPG/assets/sounds/select.wav index 6b7a9c9d7..73ec4f5f4 100644 Binary files a/Tutorials/TurnBasedRPG/assets/sounds/select.wav and b/Tutorials/TurnBasedRPG/assets/sounds/select.wav differ diff --git a/Tutorials/TurnBasedRPG/assets/sounds/win.wav b/Tutorials/TurnBasedRPG/assets/sounds/win.wav index a58220ca7..f62b62a77 100644 Binary files a/Tutorials/TurnBasedRPG/assets/sounds/win.wav and b/Tutorials/TurnBasedRPG/assets/sounds/win.wav differ diff --git a/Tutorials/TurnBasedRPG/source/CombatHUD.hx b/Tutorials/TurnBasedRPG/source/CombatHUD.hx deleted file mode 100644 index 07eb8b5a2..000000000 --- a/Tutorials/TurnBasedRPG/source/CombatHUD.hx +++ /dev/null @@ -1,507 +0,0 @@ -package; - -import flash.filters.ColorMatrixFilter; -import flash.geom.Matrix; -import flash.geom.Point; -import flixel.FlxG; -import flixel.FlxSprite; -import flixel.addons.effects.chainable.FlxEffectSprite; -import flixel.addons.effects.chainable.FlxWaveEffect; -import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.sound.FlxSound; -import flixel.text.FlxText; -import flixel.tweens.FlxEase; -import flixel.tweens.FlxTween; -import flixel.ui.FlxBar; -import flixel.util.FlxColor; - -using flixel.util.FlxSpriteUtil; - -/** - * This enum is used to set the valid values for our outcome variable. - * Outcome can only ever be one of these 4 values and we can check for these values easily once combat is concluded. - */ -enum Outcome -{ - NONE; - ESCAPE; - VICTORY; - DEFEAT; -} - -enum Choice -{ - FIGHT; - FLEE; -} - -class CombatHUD extends FlxTypedGroup -{ - // These public variables will be used after combat has finished to help tell us what happened. - public var enemy:Enemy; // we will pass the enemySprite that the playerSprite touched to initialize combat, and this will let us also know which enemySprite to kill, etc. - public var playerHealth(default, null):Int; // when combat has finished, we will need to know how much remaining health the playerSprite has - public var outcome(default, null):Outcome; // when combat has finished, we will need to know if the playerSprite killed the enemySprite or fled - - // These are the sprites that we will use to show the combat hud interface - var background:FlxSprite; // this is the background sprite - var playerSprite:Player; // this is a sprite of the playerSprite - var enemySprite:Enemy; // this is a sprite of the enemySprite - - // These variables will be used to track the enemySprite's health - var enemyHealth:Int; - var enemyMaxHealth:Int; - var enemyHealthBar:FlxBar; // This FlxBar will show us the enemySprite's current/max health - - var playerHealthCounter:FlxText; // this will show the playerSprite's current/max health - - var damages:Array; // This array will contain 2 FlxText objects which will appear to show damage dealt (or misses) - - var pointer:FlxSprite; // This will be the pointer to show which option (Fight or Flee) the user is pointing to. - var selected:Choice; // this will track which option is selected - var choices:Map; // this map will contain the FlxTexts for our 2 options: Fight and Flee - - var results:FlxText; // this text will show the outcome of the battle for the playerSprite. - - var alpha:Float = 0; // we will use this to fade in and out our combat hud - var wait:Bool = true; // this flag will be set to true when don't want the playerSprite to be able to do anything (between turns) - - var fledSound:FlxSound; - var hurtSound:FlxSound; - var loseSound:FlxSound; - var missSound:FlxSound; - var selectSound:FlxSound; - var winSound:FlxSound; - var combatSound:FlxSound; - - var screen:FlxSprite; - - public function new() - { - super(); - - screen = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.TRANSPARENT); - var waveEffect = new FlxWaveEffect(FlxWaveMode.ALL, 4, -1, 4); - var waveSprite = new FlxEffectSprite(screen, [waveEffect]); - add(waveSprite); - - // first, create our background. Make a black square, then draw borders onto it in white. Add it to our group. - background = new FlxSprite().makeGraphic(120, 120, FlxColor.WHITE); - background.drawRect(1, 1, 118, 44, FlxColor.BLACK); - background.drawRect(1, 46, 118, 73, FlxColor.BLACK); - background.screenCenter(); - add(background); - - // next, make a 'dummy' playerSprite that looks like our playerSprite (but can't move) and add it. - playerSprite = new Player(background.x + 36, background.y + 16); - playerSprite.animation.frameIndex = 3; - playerSprite.active = false; - playerSprite.facing = RIGHT; - add(playerSprite); - - // do the same thing for an enemySprite. We'll just use enemySprite type REGULAR for now and change it later. - enemySprite = new Enemy(background.x + 76, background.y + 16, REGULAR); - enemySprite.animation.frameIndex = 3; - enemySprite.active = false; - enemySprite.facing = LEFT; - add(enemySprite); - - // setup the playerSprite's health display and add it to the group. - playerHealthCounter = new FlxText(0, playerSprite.y + playerSprite.height + 2, 0, "3 / 3", 8); - playerHealthCounter.alignment = CENTER; - playerHealthCounter.x = playerSprite.x + 4 - (playerHealthCounter.width / 2); - add(playerHealthCounter); - - // create and add a FlxBar to show the enemySprite's health. We'll make it Red and Yellow. - enemyHealthBar = new FlxBar(enemySprite.x - 6, playerHealthCounter.y, LEFT_TO_RIGHT, 20, 10); - enemyHealthBar.createFilledBar(0xffdc143c, FlxColor.YELLOW, true, FlxColor.YELLOW); - add(enemyHealthBar); - - // create our choices and add them to the group. - choices = new Map(); - choices[FIGHT] = new FlxText(background.x + 30, background.y + 48, 85, "FIGHT", 22); - choices[FLEE] = new FlxText(background.x + 30, choices[FIGHT].y + choices[FIGHT].height + 8, 85, "FLEE", 22); - add(choices[FIGHT]); - add(choices[FLEE]); - - pointer = new FlxSprite(background.x + 10, choices[FIGHT].y + (choices[FIGHT].height / 2) - 8, AssetPaths.pointer__png); - pointer.visible = false; - add(pointer); - - // create our damage texts. We'll make them be white text with a red shadow (so they stand out). - damages = new Array(); - damages.push(new FlxText(0, 0, 40)); - damages.push(new FlxText(0, 0, 40)); - for (d in damages) - { - d.color = FlxColor.WHITE; - d.setBorderStyle(SHADOW, FlxColor.RED); - d.alignment = CENTER; - d.visible = false; - add(d); - } - - // create our results text object. We'll position it, but make it hidden for now. - results = new FlxText(background.x + 2, background.y + 9, 116, "", 18); - results.alignment = CENTER; - results.color = FlxColor.YELLOW; - results.setBorderStyle(SHADOW, FlxColor.GRAY); - results.visible = false; - add(results); - - // like we did in our HUD class, we need to set the scrollFactor on each of our children objects to 0,0. We also set alpha to 0 (so we can fade this in) - forEach(function(sprite:FlxSprite) - { - sprite.scrollFactor.set(); - sprite.alpha = 0; - }); - - // mark this object as not active and not visible so update and draw don't get called on it until we're ready to show it. - active = false; - visible = false; - - fledSound = FlxG.sound.load(AssetPaths.fled__wav); - hurtSound = FlxG.sound.load(AssetPaths.hurt__wav); - loseSound = FlxG.sound.load(AssetPaths.lose__wav); - missSound = FlxG.sound.load(AssetPaths.miss__wav); - selectSound = FlxG.sound.load(AssetPaths.select__wav); - winSound = FlxG.sound.load(AssetPaths.win__wav); - combatSound = FlxG.sound.load(AssetPaths.combat__wav); - } - - /** - * This function will be called from PlayState when we want to start combat. It will setup the screen and make sure everything is ready. - * @param playerHealth The amount of health the playerSprite is starting with - * @param enemy This links back to the Enemy we are fighting with so we can get it's health and type (to change our sprite). - */ - public function initCombat(playerHealth:Int, enemy:Enemy) - { - screen.drawFrame(); - var screenPixels = screen.framePixels; - - if (FlxG.renderBlit) - screenPixels.copyPixels(FlxG.camera.buffer, FlxG.camera.buffer.rect, new Point()); - else - screenPixels.draw(FlxG.camera.canvas, new Matrix(1, 0, 0, 1, 0, 0)); - - var rc:Float = 1 / 3; - var gc:Float = 1 / 2; - var bc:Float = 1 / 6; - screenPixels.applyFilter(screenPixels, screenPixels.rect, new Point(), - new ColorMatrixFilter([rc, gc, bc, 0, 0, rc, gc, bc, 0, 0, rc, gc, bc, 0, 0, 0, 0, 0, 1, 0])); - - combatSound.play(); - this.playerHealth = playerHealth; // we set our playerHealth variable to the value that was passed to us - this.enemy = enemy; // set our enemySprite object to the one passed to us - - updatePlayerHealth(); - - // setup our enemySprite - enemyMaxHealth = enemyHealth = if (enemy.type == REGULAR) 2 else 4; // each enemySprite will have health based on their type - enemyHealthBar.value = 100; // the enemySprite's health bar starts at 100% - enemySprite.changeType(enemy.type); // change our enemySprite's image to match their type. - - // make sure we initialize all of these before we start so nothing looks 'wrong' the second time we get - wait = true; - results.text = ""; - pointer.visible = false; - results.visible = false; - outcome = NONE; - selected = FIGHT; - movePointer(); - - visible = true; // make our hud visible (so draw gets called on it) - note, it's not active, yet! - - // do a numeric tween to fade in our combat hud when the tween is finished, call finishFadeIn - FlxTween.num(0, 1, .66, {ease: FlxEase.circOut, onComplete: finishFadeIn}, updateAlpha); - } - - /** - * This function is called by our Tween to fade in/out all the items in our hud. - */ - function updateAlpha(alpha:Float) - { - this.alpha = alpha; - forEach(function(sprite) sprite.alpha = alpha); - } - - /** - * When we've finished fading in, we set our hud to active (so it gets updates), and allow the playerSprite to interact. We show our pointer, too. - */ - function finishFadeIn(_) - { - active = true; - wait = false; - pointer.visible = true; - selectSound.play(); - } - - /** - * After we fade our hud out, we set it to not be active or visible (no update and no draw) - */ - function finishFadeOut(_) - { - active = false; - visible = false; - } - - /** - * This function is called to change the Player's health text on the screen. - */ - function updatePlayerHealth() - { - playerHealthCounter.text = playerHealth + " / 3"; - playerHealthCounter.x = playerSprite.x + 4 - (playerHealthCounter.width / 2); - } - - override public function update(elapsed:Float) - { - if (!wait) // if we're waiting, don't do any of this. - { - updateKeyboardInput(); - updateTouchInput(); - } - super.update(elapsed); - } - - function updateKeyboardInput() - { - #if FLX_KEYBOARD - // setup some simple flags to see which keys are pressed. - var up:Bool = false; - var down:Bool = false; - var fire:Bool = false; - - // check to see any keys are pressed and set the cooresponding flags. - if (FlxG.keys.anyJustReleased([SPACE, X, ENTER])) - { - fire = true; - } - else if (FlxG.keys.anyJustReleased([W, UP])) - { - up = true; - } - else if (FlxG.keys.anyJustReleased([S, DOWN])) - { - down = true; - } - - // based on which flags are set, do the specified action - if (fire) - { - selectSound.play(); - makeChoice(); // when the playerSprite chooses either option, we call this function to process their selection - } - else if (up || down) - { - // if the playerSprite presses up or down, we move the cursor up or down (with wrapping) - selected = if (selected == FIGHT) FLEE else FIGHT; - selectSound.play(); - movePointer(); - } - #end - } - - function updateTouchInput() - { - #if FLX_TOUCH - for (touch in FlxG.touches.justReleased()) - { - for (choice in choices.keys()) - { - var text = choices[choice]; - if (touch.overlaps(text)) - { - selectSound.play(); - selected = choice; - movePointer(); - makeChoice(); - return; - } - } - } - #end - } - - /** - * Call this function to place the pointer next to the currently selected choice - */ - function movePointer() - { - pointer.y = choices[selected].y + (choices[selected].height / 2) - 8; - } - - /** - * This function will process the choice the playerSprite picked - */ - function makeChoice() - { - pointer.visible = false; // hide our pointer - switch (selected) // check which item was selected when the playerSprite picked it - { - case FIGHT: - // if FIGHT was picked... - // ...the playerSprite attacks the enemySprite first - // they have an 85% chance to hit the enemySprite - if (FlxG.random.bool(85)) - { - // if they hit, deal 1 damage to the enemySprite, and setup our damage indicator - damages[1].text = "1"; - FlxTween.tween(enemySprite, {x: enemySprite.x + 4}, 0.1, { - onComplete: function(_) - { - FlxTween.tween(enemySprite, {x: enemySprite.x - 4}, 0.1); - } - }); - hurtSound.play(); - enemyHealth--; - enemyHealthBar.value = (enemyHealth / enemyMaxHealth) * 100; // change the enemySprite's health bar - } - else - { - // change our damage text to show that we missed! - damages[1].text = "MISS!"; - missSound.play(); - } - - // position the damage text over the enemySprite, and set it's alpha to 0 but it's visible to true (so that it gets draw called on it) - damages[1].x = enemySprite.x + 2 - (damages[1].width / 2); - damages[1].y = enemySprite.y + 4 - (damages[1].height / 2); - damages[1].alpha = 0; - damages[1].visible = true; - - // if the enemySprite is still alive, it will swing back! - if (enemyHealth > 0) - { - enemyAttack(); - } - - // setup 2 tweens to allow the damage indicators to fade in and float up from the sprites - FlxTween.num(damages[0].y, damages[0].y - 12, 1, {ease: FlxEase.circOut}, updateDamageY); - FlxTween.num(0, 1, .2, {ease: FlxEase.circInOut, onComplete: doneDamageIn}, updateDamageAlpha); - - case FLEE: - // if the playerSprite chose to FLEE, we'll give them a 50/50 chance to escape - if (FlxG.random.bool(50)) - { - // if they succeed, we show the 'escaped' message and trigger it to fade in - outcome = ESCAPE; - results.text = "ESCAPED!"; - fledSound.play(); - results.visible = true; - results.alpha = 0; - FlxTween.tween(results, {alpha: 1}, .66, {ease: FlxEase.circInOut, onComplete: doneResultsIn}); - } - else - { - // if they fail to escape, the enemySprite will get a free-swing - enemyAttack(); - FlxTween.num(damages[0].y, damages[0].y - 12, 1, {ease: FlxEase.circOut}, updateDamageY); - FlxTween.num(0, 1, .2, {ease: FlxEase.circInOut, onComplete: doneDamageIn}, updateDamageAlpha); - } - } - - // regardless of what happens, we need to set our 'wait' flag so that we can show what happened before moving on - wait = true; - } - - /** - * This function is called anytime we want the enemySprite to swing at the playerSprite - */ - function enemyAttack() - { - // first, lets see if the enemySprite hits or not. We'll give him a 30% chance to hit - if (FlxG.random.bool(30)) - { - // if we hit, flash the screen white, and deal one damage to the playerSprite - then update the playerSprite's health - FlxG.camera.flash(FlxColor.WHITE, .2); - FlxG.camera.shake(0.01, 0.2); - hurtSound.play(); - damages[0].text = "1"; - playerHealth--; - updatePlayerHealth(); - } - else - { - // if the enemySprite misses, show it on the screen - damages[0].text = "MISS!"; - missSound.play(); - } - - // setup the combat text to show up over the playerSprite and fade in/raise up - damages[0].x = playerSprite.x + 2 - (damages[0].width / 2); - damages[0].y = playerSprite.y + 4 - (damages[0].height / 2); - damages[0].alpha = 0; - damages[0].visible = true; - } - - /** - * This function is called from our Tweens to move the damage displays up on the screen - */ - function updateDamageY(damageY:Float) - { - damages[0].y = damages[1].y = damageY; - } - - /** - * This function is called from our Tweens to fade in/out the damage text - */ - function updateDamageAlpha(damageAlpha:Float) - { - damages[0].alpha = damages[1].alpha = damageAlpha; - } - - /** - * This function is called when our damage texts have finished fading in - it will trigger them to start fading out again, after a short delay - */ - function doneDamageIn(_) - { - FlxTween.num(1, 0, .66, {ease: FlxEase.circInOut, startDelay: 1, onComplete: doneDamageOut}, updateDamageAlpha); - } - - /** - * This function is triggered when our results text has finished fading in. If we're not defeated, we will fade out the entire hud after a short delay - */ - function doneResultsIn(_) - { - FlxTween.num(1, 0, .66, {ease: FlxEase.circOut, onComplete: finishFadeOut, startDelay: 1}, updateAlpha); - } - - /** - * This function is triggered when the damage texts have finished fading out again. They will clear and reset them for next time. - * It will also check to see what we're supposed to do next - if the enemySprite is dead, we trigger victory, if the playerSprite is dead we trigger defeat, otherwise we reset for the next round. - */ - function doneDamageOut(_) - { - damages[0].visible = false; - damages[1].visible = false; - damages[0].text = ""; - damages[1].text = ""; - - if (playerHealth <= 0) - { - // if the playerSprite's health is 0, we show the defeat message on the screen and fade it in - outcome = DEFEAT; - loseSound.play(); - results.text = "DEFEAT!"; - results.visible = true; - results.alpha = 0; - FlxTween.tween(results, {alpha: 1}, 0.66, {ease: FlxEase.circInOut, onComplete: doneResultsIn}); - } - else if (enemyHealth <= 0) - { - // if the enemySprite's health is 0, we show the victory message - outcome = VICTORY; - winSound.play(); - results.text = "VICTORY!"; - results.visible = true; - results.alpha = 0; - FlxTween.tween(results, {alpha: 1}, 0.66, {ease: FlxEase.circInOut, onComplete: doneResultsIn}); - } - else - { - // both are still alive, so we reset and have the playerSprite pick their next action - wait = false; - pointer.visible = true; - } - } -} diff --git a/Tutorials/TurnBasedRPG/source/GameOverState.hx b/Tutorials/TurnBasedRPG/source/GameOverState.hx deleted file mode 100644 index f824af731..000000000 --- a/Tutorials/TurnBasedRPG/source/GameOverState.hx +++ /dev/null @@ -1,100 +0,0 @@ -package; - -import flixel.FlxG; -import flixel.FlxSprite; -import flixel.FlxState; -import flixel.text.FlxText; -import flixel.ui.FlxButton; -import flixel.util.FlxAxes; -import flixel.util.FlxColor; - -class GameOverState extends FlxState -{ - var titleText:FlxText; // the title text - var messageText:FlxText; // the final score message text - var scoreIcon:FlxSprite; // sprite for a coin icon - var scoreText:FlxText; // text of the score - var highscoreText:FlxText; // text to show the highscore - var mainMenuButton:FlxButton; // button to go to main menu - - /** - * Called from PlayState, this will set our win and score variables - * @param win true if the player beat the boss, false if they died - * @param score the number of coins collected - */ - public function new(win:Bool, score:Int) - { - super(); - - #if FLX_MOUSE - FlxG.mouse.visible = true; - #end - - // create and add each of our items - - titleText = new FlxText(0, 20, 0, if (win) "You Win!" else "Game Over!", 22); - titleText.alignment = CENTER; - titleText.screenCenter(FlxAxes.X); - add(titleText); - - messageText = new FlxText(0, (FlxG.height / 2) - 18, 0, "Final Score:", 8); - messageText.alignment = CENTER; - messageText.screenCenter(FlxAxes.X); - add(messageText); - - scoreIcon = new FlxSprite((FlxG.width / 2) - 8, 0, AssetPaths.coin__png); - scoreIcon.screenCenter(FlxAxes.Y); - add(scoreIcon); - - scoreText = new FlxText((FlxG.width / 2), 0, 0, Std.string(score), 8); - scoreText.screenCenter(FlxAxes.Y); - add(scoreText); - - // we want to see what the highscore is - var highscore = checkHighscore(score); - - highscoreText = new FlxText(0, (FlxG.height / 2) + 10, 0, "Highscore: " + highscore, 8); - highscoreText.alignment = CENTER; - highscoreText.screenCenter(FlxAxes.Y); - add(highscoreText); - - mainMenuButton = new FlxButton(0, FlxG.height - 32, "Main Menu", switchToMainMenu); - mainMenuButton.screenCenter(FlxAxes.X); - mainMenuButton.onUp.sound = FlxG.sound.load(AssetPaths.select__wav); - add(mainMenuButton); - - FlxG.camera.fade(FlxColor.BLACK, 0.33, true); - } - - /** - * This function will compare the new score with the saved highscore. - * If the new score is higher, it will save it as the new highscore, otherwise, it will return the saved highscore. - * @param score The new score - * @return the highscore - */ - function checkHighscore(score:Int):Int - { - var highscore:Int = score; - if (FlxG.save.data.highscore != null && FlxG.save.data.highscore > highscore) - { - highscore = FlxG.save.data.highscore; - } - else - { - // data is less or there is no data; save current score - FlxG.save.data.highscore = highscore; - } - return highscore; - } - - /** - * When the user hits the main menu button, it should fade out and then take them back to the MenuState - */ - function switchToMainMenu():Void - { - FlxG.camera.fade(FlxColor.BLACK, 0.33, false, function() - { - FlxG.switchState(MenuState.new); - }); - } -} diff --git a/Tutorials/TurnBasedRPG/source/HUD.hx b/Tutorials/TurnBasedRPG/source/HUD.hx deleted file mode 100644 index 09db963d2..000000000 --- a/Tutorials/TurnBasedRPG/source/HUD.hx +++ /dev/null @@ -1,46 +0,0 @@ -package; - -import flixel.FlxG; -import flixel.FlxSprite; -import flixel.group.FlxGroup; -import flixel.text.FlxText; -import flixel.util.FlxColor; - -using flixel.util.FlxSpriteUtil; - -class HUD extends FlxTypedGroup -{ - var background:FlxSprite; - var healthCounter:FlxText; - var moneyCounter:FlxText; - var healthIcon:FlxSprite; - var moneyIcon:FlxSprite; - - public function new() - { - super(); - background = new FlxSprite().makeGraphic(FlxG.width, 20, FlxColor.BLACK); - background.drawRect(0, 19, FlxG.width, 1, FlxColor.WHITE); - healthCounter = new FlxText(16, 2, 0, "3 / 3", 8); - healthCounter.setBorderStyle(SHADOW, FlxColor.GRAY, 1, 1); - moneyCounter = new FlxText(0, 2, 0, "0", 8); - moneyCounter.setBorderStyle(SHADOW, FlxColor.GRAY, 1, 1); - healthIcon = new FlxSprite(4, healthCounter.y + (healthCounter.height / 2) - 4, AssetPaths.health__png); - moneyIcon = new FlxSprite(FlxG.width - 12, moneyCounter.y + (moneyCounter.height / 2) - 4, AssetPaths.coin__png); - moneyCounter.alignment = RIGHT; - moneyCounter.x = moneyIcon.x - moneyCounter.width - 4; - add(background); - add(healthIcon); - add(moneyIcon); - add(healthCounter); - add(moneyCounter); - forEach(function(sprite) sprite.scrollFactor.set(0, 0)); - } - - public function updateHUD(health:Int, money:Int) - { - healthCounter.text = health + " / 3"; - moneyCounter.text = Std.string(money); - moneyCounter.x = moneyIcon.x - moneyCounter.width - 4; - } -} diff --git a/Tutorials/TurnBasedRPG/source/Main.hx b/Tutorials/TurnBasedRPG/source/Main.hx index 44c432989..ad37778ca 100644 --- a/Tutorials/TurnBasedRPG/source/Main.hx +++ b/Tutorials/TurnBasedRPG/source/Main.hx @@ -4,6 +4,7 @@ import flixel.FlxG; import flixel.FlxGame; import flixel.util.FlxSave; import openfl.display.Sprite; +import states.MenuState; class Main extends Sprite { diff --git a/Tutorials/TurnBasedRPG/source/MenuState.hx b/Tutorials/TurnBasedRPG/source/MenuState.hx deleted file mode 100644 index 29a71272d..000000000 --- a/Tutorials/TurnBasedRPG/source/MenuState.hx +++ /dev/null @@ -1,78 +0,0 @@ -package; - -import flixel.FlxG; -import flixel.FlxState; -import flixel.text.FlxText; -import flixel.ui.FlxButton; -import flixel.util.FlxColor; - -class MenuState extends FlxState -{ - var titleText:FlxText; - var playButton:FlxButton; - var optionsButton:FlxButton; - #if desktop - var exitButton:FlxButton; - #end - - override public function create() - { - titleText = new FlxText(20, 0, 0, "HaxeFlixel\nTutorial\nGame", 22); - titleText.alignment = CENTER; - titleText.screenCenter(X); - add(titleText); - - playButton = new FlxButton(0, 0, "Play", clickPlay); - playButton.onUp.sound = FlxG.sound.load(AssetPaths.select__wav); - playButton.x = (FlxG.width / 2) - 10 - playButton.width; - playButton.y = FlxG.height - playButton.height - 10; - add(playButton); - - optionsButton = new FlxButton(0, 0, "Options", clickOptions); - optionsButton.x = (FlxG.width / 2) + 10; - optionsButton.y = FlxG.height - optionsButton.height - 10; - add(optionsButton); - - #if desktop - exitButton = new FlxButton(FlxG.width - 28, 8, "X", clickExit); - exitButton.loadGraphic(AssetPaths.button__png, true, 20, 20); - add(exitButton); - #end - - if (FlxG.sound.music == null) // don't restart the music if it's already playing - { - #if flash - FlxG.sound.playMusic(AssetPaths.HaxeFlixel_Tutorial_Game__mp3, 1, true); - #else - FlxG.sound.playMusic(AssetPaths.HaxeFlixel_Tutorial_Game__ogg, 1, true); - #end - } - - FlxG.camera.fade(FlxColor.BLACK, 0.33, true); - - super.create(); - } - - function clickPlay() - { - FlxG.camera.fade(FlxColor.BLACK, 0.33, false, function() - { - FlxG.switchState(PlayState.new); - }); - } - - function clickOptions() - { - FlxG.camera.fade(FlxColor.BLACK, 0.33, false, function() - { - FlxG.switchState(OptionsState.new); - }); - } - - #if desktop - function clickExit() - { - Sys.exit(0); - } - #end -} diff --git a/Tutorials/TurnBasedRPG/source/Coin.hx b/Tutorials/TurnBasedRPG/source/objects/Coin.hx similarity index 74% rename from Tutorials/TurnBasedRPG/source/Coin.hx rename to Tutorials/TurnBasedRPG/source/objects/Coin.hx index 70e2994e4..4f8d39dad 100644 --- a/Tutorials/TurnBasedRPG/source/Coin.hx +++ b/Tutorials/TurnBasedRPG/source/objects/Coin.hx @@ -1,4 +1,4 @@ -package; +package objects; import flixel.FlxSprite; import flixel.tweens.FlxEase; @@ -9,7 +9,9 @@ class Coin extends FlxSprite public function new(x:Float, y:Float) { super(x, y); - loadGraphic(AssetPaths.coin__png, false, 8, 8); + loadGraphic(AssetPaths.coin__png, true, 12, 12); + animation.add("idle", [0, 1], 4); + animation.play("idle"); } override function kill() diff --git a/Tutorials/TurnBasedRPG/source/Enemy.hx b/Tutorials/TurnBasedRPG/source/objects/Enemy.hx similarity index 70% rename from Tutorials/TurnBasedRPG/source/Enemy.hx rename to Tutorials/TurnBasedRPG/source/objects/Enemy.hx index db26ef240..0e5ea9bd6 100644 --- a/Tutorials/TurnBasedRPG/source/Enemy.hx +++ b/Tutorials/TurnBasedRPG/source/objects/Enemy.hx @@ -1,9 +1,10 @@ -package; +package objects; import flixel.FlxG; import flixel.FlxSprite; import flixel.math.FlxPoint; import flixel.math.FlxVelocity; +import flixel.tile.FlxTilemap; import flixel.sound.FlxSound; using flixel.util.FlxSpriteUtil; @@ -16,8 +17,8 @@ enum EnemyType class Enemy extends FlxSprite { - static inline var WALK_SPEED:Float = 40; - static inline var CHASE_SPEED:Float = 70; + static inline var WALK_SPEED:Float = 50; + static inline var CHASE_SPEED:Float = 90; var brain:FSM; var idleTimer:Float; @@ -27,15 +28,19 @@ class Enemy extends FlxSprite public var type(default, null):EnemyType; public var seesPlayer:Bool; public var playerPosition:FlxPoint; + public var maxHP:Int; + public var hp:Int; public function new(x:Float, y:Float, type:EnemyType) { super(x, y); - this.type = type; - var graphic = if (type == BOSS) AssetPaths.boss__png else AssetPaths.enemy__png; - loadGraphic(graphic, true, 16, 16); - setFacingFlip(LEFT, false, false); - setFacingFlip(RIGHT, true, false); + + changeType(type); + maxHP = type == REGULAR ? 2 : 4; + hp = maxHP; + + setFacingFlip(LEFT, true, false); + setFacingFlip(RIGHT, false, false); animation.add("d_idle", [0]); animation.add("lr_idle", [3]); animation.add("u_idle", [6]); @@ -43,9 +48,8 @@ class Enemy extends FlxSprite animation.add("lr_walk", [3, 4, 3, 5], 6); animation.add("u_walk", [6, 7, 6, 8], 6); drag.x = drag.y = 10; - setSize(8, 8); - offset.x = 4; - offset.y = 8; + setSize(12, 12); + offset.set(6, 12); brain = new FSM(idle); idleTimer = 0; @@ -55,11 +59,18 @@ class Enemy extends FlxSprite stepSound = FlxG.sound.load(AssetPaths.step__wav, 0.4); stepSound.proximity(x, y, FlxG.camera.target, FlxG.width * 0.6); } + + public function hurt(damage:Int) + { + hp -= damage; + } - override public function update(elapsed:Float) + override function update(elapsed:Float) { if (this.isFlickering()) + { return; + } var action = "idle"; if (velocity.x != 0 || velocity.y != 0) @@ -67,37 +78,31 @@ class Enemy extends FlxSprite action = "walk"; if (Math.abs(velocity.x) > Math.abs(velocity.y)) { - if (velocity.x < 0) - facing = LEFT; - else - facing = RIGHT; + facing = (velocity.x < 0) ? LEFT : RIGHT; } else { - if (velocity.y < 0) - facing = UP; - else - facing = DOWN; + facing = (velocity.y < 0) ? UP : DOWN; } - - stepSound.setPosition(x + frameWidth / 2, y + height); + + stepSound.setPosition(x + width / 2, y + height); stepSound.play(); } - + switch (facing) { case LEFT, RIGHT: animation.play("lr_" + action); - + case UP: animation.play("u_" + action); - + case DOWN: animation.play("d_" + action); - + case _: } - + brain.update(elapsed); super.update(elapsed); } @@ -125,7 +130,9 @@ class Enemy extends FlxSprite idleTimer = FlxG.random.int(1, 4); } else + { idleTimer -= elapsed; + } } function chase(elapsed:Float) @@ -139,6 +146,14 @@ class Enemy extends FlxSprite FlxVelocity.moveTowardsPoint(this, playerPosition, CHASE_SPEED); } } + + public function checkVision(player:Player, walls:FlxTilemap) + { + // Store the player position + player.getMidpoint(playerPosition); + // Cast a ray from here to the player and see if a wall is blocking + seesPlayer = walls.ray(getMidpoint(FlxPoint.weak()), playerPosition); + } public function changeType(type:EnemyType) { @@ -146,7 +161,7 @@ class Enemy extends FlxSprite { this.type = type; var graphic = if (type == BOSS) AssetPaths.boss__png else AssetPaths.enemy__png; - loadGraphic(graphic, true, 16, 16); + loadGraphic(graphic, true, 24, 24); } } } diff --git a/Tutorials/TurnBasedRPG/source/FSM.hx b/Tutorials/TurnBasedRPG/source/objects/FSM.hx similarity index 91% rename from Tutorials/TurnBasedRPG/source/FSM.hx rename to Tutorials/TurnBasedRPG/source/objects/FSM.hx index 9a7c4f116..8a6dd49c4 100644 --- a/Tutorials/TurnBasedRPG/source/FSM.hx +++ b/Tutorials/TurnBasedRPG/source/objects/FSM.hx @@ -1,3 +1,5 @@ +package objects; + class FSM { public var activeState:Float->Void; diff --git a/Tutorials/TurnBasedRPG/source/Player.hx b/Tutorials/TurnBasedRPG/source/objects/Player.hx similarity index 56% rename from Tutorials/TurnBasedRPG/source/Player.hx rename to Tutorials/TurnBasedRPG/source/objects/Player.hx index 32a3186e6..5ed1a8c8e 100644 --- a/Tutorials/TurnBasedRPG/source/Player.hx +++ b/Tutorials/TurnBasedRPG/source/objects/Player.hx @@ -1,4 +1,4 @@ -package; +package objects; import flixel.FlxG; import flixel.FlxSprite; @@ -7,16 +7,21 @@ import flixel.sound.FlxSound; class Player extends FlxSprite { - static inline var SPEED:Float = 100; + static inline var SPEED:Float = 110; + /** Reaches top speed in 0.15 seconds */ + static inline var ACCEL:Float = SPEED / 0.15; - var stepSound:FlxSound; + public final maxHP:Int = 3; + public var hp:Int; public function new(x:Float = 0, y:Float = 0) { + hp = maxHP; super(x, y); - loadGraphic(AssetPaths.player__png, true, 16, 16); - setFacingFlip(LEFT, false, false); - setFacingFlip(RIGHT, true, false); + + loadGraphic(AssetPaths.player__png, true, 24, 24); + setFacingFlip(LEFT, true, false); + setFacingFlip(RIGHT, false, false); animation.add("d_idle", [0]); animation.add("lr_idle", [3]); animation.add("u_idle", [6]); @@ -25,20 +30,44 @@ class Player extends FlxSprite animation.add("u_walk", [6, 7, 6, 8], 6); drag.x = drag.y = 800; - setSize(8, 8); - offset.set(4, 8); - - stepSound = FlxG.sound.load(AssetPaths.step__wav); + maxVelocity.x = maxVelocity.y = SPEED; + setSize(12, 12); + offset.set(6, 12); + } + + public function hurt(damage:Int) + { + hp -= damage; } override function update(elapsed:Float) { - updateMovement(); super.update(elapsed); + + updateMovement(); } function updateMovement() { + var action = "idle"; + // check if the player is moving, and not walking into walls + if (velocity.x != 0 || velocity.y != 0) + { + // FlxG.sound.play(AssetPaths.step__wav) + action = "walk"; + } + + switch (facing) + { + case LEFT, RIGHT: + animation.play("lr_" + action); + case UP: + animation.play("u_" + action); + case DOWN: + animation.play("d_" + action); + case _: + } + var up:Bool = false; var down:Bool = false; var left:Bool = false; @@ -58,65 +87,48 @@ class Player extends FlxSprite left = left || virtualPad.buttonLeft.pressed; right = right || virtualPad.buttonRight.pressed; #end - + + // Cancel out opposing directions if (up && down) + { up = down = false; + } + if (left && right) + { left = right = false; - - if (up || down || left || right) + } + + acceleration.set(0, 0); + if (right) { - var newAngle:Float = 0; - if (up) - { - newAngle = -90; - if (left) - newAngle -= 45; - else if (right) - newAngle += 45; - facing = UP; - } - else if (down) - { - newAngle = 90; - if (left) - newAngle += 45; - else if (right) - newAngle -= 45; - facing = DOWN; - } - else if (left) - { - newAngle = 180; - facing = LEFT; - } - else if (right) - { - newAngle = 0; - facing = RIGHT; - } - - // determine our velocity based on angle and speed - velocity.setPolarDegrees(SPEED, newAngle); + facing = RIGHT; + acceleration.x = ACCEL; } - - var action = "idle"; - // check if the player is moving, and not walking into walls - if ((velocity.x != 0 || velocity.y != 0) && touching == NONE) + else if (left) { - stepSound.play(); - action = "walk"; + facing = LEFT; + acceleration.x = -ACCEL; } - - switch (facing) + + if (down) { - case LEFT, RIGHT: - animation.play("lr_" + action); - case UP: - animation.play("u_" + action); - case DOWN: - animation.play("d_" + action); - case _: + facing = DOWN; + acceleration.y = ACCEL; + } + else if (up) + { + facing = UP; + acceleration.y = -ACCEL; + } + + // Prevent faster speeds on diagonal movement + var magnitude = velocity.length; + if (magnitude > SPEED) + { + // Reduce speed to SPEED but maintain direction + velocity.x *= SPEED / magnitude; + velocity.y *= SPEED / magnitude; } } } diff --git a/Tutorials/TurnBasedRPG/source/states/GameOverState.hx b/Tutorials/TurnBasedRPG/source/states/GameOverState.hx new file mode 100644 index 000000000..d5758c50f --- /dev/null +++ b/Tutorials/TurnBasedRPG/source/states/GameOverState.hx @@ -0,0 +1,106 @@ +package states; + +import flixel.util.FlxTimer; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.FlxState; +import flixel.text.FlxBitmapText; +import flixel.text.FlxText; +import flixel.ui.FlxButton; +import flixel.util.FlxAxes; +import flixel.util.FlxColor; +import ui.LargeText; + +class GameOverState extends FlxState +{ + /** + * Called from PlayState, this will set our win and score variables + * @param win Whether the player beat the boss, or died + * @param score The number of coins collected + */ + public function new(win:Bool, score:Int) + { + super(); + + #if FLX_MOUSE + FlxG.mouse.visible = true; + #end + + // create and add each of our items + + var titleText = new LargeText(0, 20, if (win) "You Win!" else "Game Over!"); + titleText.screenCenter(FlxAxes.X); + add(titleText); + + var messageText = new FlxText(0, (FlxG.height / 2) - 18, 0, "Final Score: 0", 8); + messageText.screenCenter(FlxAxes.X); + add(messageText); + + // Fade the camera from black + FlxG.camera.fade(FlxColor.BLACK, 0.33, true); + + // Count up the points for dramatic effect + FlxTween.num(0, score, 1.0, // from 0 to score in 1.0 second + { + startDelay: 0.33,// wait for the fade to complete + ease: FlxEase.circOut, + onComplete: function (tween:FlxTween) + { + // Wait 1 second and then show the highscore + new FlxTimer().start(0.5, (_)->showHighscore(score)); + } + }, + function updateText(tweenedScore) + { + messageText.text = "Final Score: " + Math.floor(tweenedScore); + } + ); + } + + function showHighscore(score:Int) + { + // Get previous highscore + var highscore = 0; + if (FlxG.save.data.highscore != null) + { + highscore = FlxG.save.data.highscore; + } + + var highscoreText = new FlxText(0, (FlxG.height / 2) + 10, 0, "Highscore: " + highscore, 8); + add(highscoreText); + + // New high score + if (score > highscore) + { + FlxG.save.data.highscore = score; + highscoreText.text = "New Highscore!"; + } + + highscoreText.screenCenter(FlxAxes.XY); + + // Wait a second then show the + new FlxTimer().start(1.0, (_)->showButton()); + } + + function showButton() + { + var mainMenuButton = new FlxButton(0, FlxG.height - 32, "Main Menu", switchToMainMenu); + mainMenuButton.loadGraphic(AssetPaths.button__png, true, 80, 20); + mainMenuButton.screenCenter(FlxAxes.X); + mainMenuButton.onUp.sound = FlxG.sound.load(AssetPaths.select__wav); + add(mainMenuButton); + } + + /** + * When the user hits the main menu button, it should fade out and then take them back to the MenuState + */ + function switchToMainMenu():Void + { + FlxG.camera.fade(FlxColor.BLACK, 0.33, false, function() + { + FlxG.switchState(MenuState.new); + }); + } +} diff --git a/Tutorials/TurnBasedRPG/source/states/MenuState.hx b/Tutorials/TurnBasedRPG/source/states/MenuState.hx new file mode 100644 index 000000000..ea92059e3 --- /dev/null +++ b/Tutorials/TurnBasedRPG/source/states/MenuState.hx @@ -0,0 +1,111 @@ +package states; + +import ui.OptionsSubState; +import ui.LargeText; +import flixel.FlxG; +import flixel.FlxState; +import flixel.text.FlxBitmapText; +import flixel.text.FlxText; +import flixel.ui.FlxButton; +import flixel.util.FlxColor; +import flixel.addons.editors.ogmo.FlxOgmo3Loader; + +class MenuState extends FlxState +{ + override public function create() + { + // NOTE: differs from tutorial! + var map = new FlxOgmo3Loader(AssetPaths.turnBasedRPG__ogmo, AssetPaths.room_001__json); + var walls = map.loadTilemap(AssetPaths.tiles__png, "walls"); + walls.x -= 123; + walls.y -= 10; + add(walls); + + // Use FlxBitmapText for crisper edges on large text + var titleText = new LargeText(0, 16, "DUNGEON\nCRAWLER"); + titleText.alignment = CENTER; + titleText.setBorderStyle(OUTLINE, 0xFF3f2631); + titleText.screenCenter(X); + add(titleText); + + // TUTORIAL VERSION: + // var titleText = new FlxText(0, 16, 0, "DUNGEON\nCRAWLER", 32); + // titleText.alignment = CENTER; + // titleText.screenCenter(X); + // add(titleText); + + var playButton = new FlxButton(0, 0, "Play", clickPlay); + playButton.loadGraphic(AssetPaths.button__png, true, 80, 20); + playButton.onUp.sound = FlxG.sound.load(AssetPaths.select__wav); + playButton.x = (FlxG.width / 2) - 10 - playButton.width; + playButton.y = FlxG.height - playButton.height - 10; + add(playButton); + + var optionsButton = new FlxButton(0, 0, "Options", clickOptions); + optionsButton.loadGraphic(AssetPaths.button__png, true, 80, 20); + optionsButton.x = (FlxG.width / 2) + 10; + optionsButton.y = FlxG.height - optionsButton.height - 10; + add(optionsButton); + + #if desktop + var exitButton = new FlxButton(FlxG.width - 28, 8, "X", clickExit); + exitButton.loadGraphic(AssetPaths.small_button__png, true, 20, 20); + add(exitButton); + #end + + if (FlxG.sound.music == null) // don't restart the music if it's already playing + { + initSound(); + } + + FlxG.camera.fade(FlxColor.BLACK, 0.33, true); + + super.create(); + } + + function initSound() + { + var volumes:{ music:Float, sound:Float } = null; + if (FlxG.save.data.volumes != null) + { + volumes = FlxG.save.data.volumes; + } + else + { + volumes = { music:0.5, sound:1.0 }; + } + + FlxG.sound.defaultMusicGroup.volume = volumes.music; + FlxG.sound.defaultSoundGroup.volume = volumes.sound; + + #if flash + FlxG.sound.playMusic(AssetPaths.HaxeFlixel_Tutorial_Game__mp3, 1.0, true); + #else + FlxG.sound.playMusic(AssetPaths.HaxeFlixel_Tutorial_Game__ogg, 1.0, true); + #end + } + + function clickPlay() + { + FlxG.camera.fade(FlxColor.BLACK, 0.33, false, function() + { + FlxG.switchState(PlayState.new); + }); + } + + function clickOptions() + { + openSubState(new OptionsSubState()); + // FlxG.camera.fade(FlxColor.BLACK, 0.33, false, function() + // { + // FlxG.switchState(OptionsState.new); + // }); + } + + #if desktop + function clickExit() + { + Sys.exit(0); + } + #end +} diff --git a/Tutorials/TurnBasedRPG/source/OptionsState.hx b/Tutorials/TurnBasedRPG/source/states/OptionsState.hx similarity index 76% rename from Tutorials/TurnBasedRPG/source/OptionsState.hx rename to Tutorials/TurnBasedRPG/source/states/OptionsState.hx index 4a567f211..7c1d2e0b2 100644 --- a/Tutorials/TurnBasedRPG/source/OptionsState.hx +++ b/Tutorials/TurnBasedRPG/source/states/OptionsState.hx @@ -1,8 +1,9 @@ -package; +package states; import flixel.FlxG; import flixel.FlxState; import flixel.text.FlxText; +import flixel.text.FlxBitmapText; import flixel.ui.FlxBar; import flixel.ui.FlxButton; import flixel.util.FlxAxes; @@ -11,49 +12,41 @@ import flixel.util.FlxColor; class OptionsState extends FlxState { // define our screen elements - var titleText:FlxText; var volumeBar:FlxBar; - var volumeText:FlxText; var volumeAmountText:FlxText; - var volumeDownButton:FlxButton; - var volumeUpButton:FlxButton; - var clearDataButton:FlxButton; - var backButton:FlxButton; #if desktop var fullscreenButton:FlxButton; #end - + override public function create():Void { // setup and add our objects to the screen - titleText = new FlxText(0, 20, 0, "Options", 22); - titleText.alignment = CENTER; - titleText.screenCenter(FlxAxes.X); + var titleText = new LargeText(0, 32, "Options"); + titleText.screenCenter(X); add(titleText); - - volumeText = new FlxText(0, titleText.y + titleText.height + 10, 0, "Volume", 8); + + var volumeText = new FlxText(0, titleText.y + titleText.height + 10, 0, "Volume", 8); volumeText.alignment = CENTER; - volumeText.screenCenter(FlxAxes.X); + volumeText.screenCenter(X); add(volumeText); - + // the volume buttons will be smaller than 'default' buttons - volumeDownButton = new FlxButton(8, volumeText.y + volumeText.height + 2, "-", - clickVolumeDown); - volumeDownButton.loadGraphic(AssetPaths.button__png, true, 20, 20); + var volumeDownButton = new FlxButton(8, volumeText.y + volumeText.height + 2, "-", clickVolumeDown); + volumeDownButton.loadGraphic(AssetPaths.small_button__png, true, 20, 20); volumeDownButton.onUp.sound = FlxG.sound.load(AssetPaths.select__wav); add(volumeDownButton); - - volumeUpButton = new FlxButton(FlxG.width - 28, volumeDownButton.y, "+", clickVolumeUp); - volumeUpButton.loadGraphic(AssetPaths.button__png, true, 20, 20); + + var volumeUpButton = new FlxButton(FlxG.width - 28, volumeDownButton.y, "+", clickVolumeUp); + volumeUpButton.loadGraphic(AssetPaths.small_button__png, true, 20, 20); volumeUpButton.onUp.sound = FlxG.sound.load(AssetPaths.select__wav); add(volumeUpButton); - + volumeBar = new FlxBar(volumeDownButton.x + volumeDownButton.width + 4, volumeDownButton.y, LEFT_TO_RIGHT, Std.int(FlxG.width - 64), Std.int(volumeUpButton.height)); volumeBar.createFilledBar(0xff464646, FlxColor.WHITE, true, FlxColor.WHITE); add(volumeBar); - + volumeAmountText = new FlxText(0, 0, 200, (FlxG.sound.volume * 100) + "%", 8); volumeAmountText.alignment = CENTER; volumeAmountText.borderStyle = FlxTextBorderStyle.OUTLINE; @@ -61,23 +54,25 @@ class OptionsState extends FlxState volumeAmountText.y = volumeBar.y + (volumeBar.height / 2) - (volumeAmountText.height / 2); volumeAmountText.screenCenter(FlxAxes.X); add(volumeAmountText); - + #if desktop fullscreenButton = new FlxButton(0, volumeBar.y + volumeBar.height + 8, FlxG.fullscreen ? "FULLSCREEN" : "WINDOWED", clickFullscreen); + fullscreenButton.loadGraphic(AssetPaths.button__png, true, 80, 20); fullscreenButton.screenCenter(FlxAxes.X); add(fullscreenButton); #end - - clearDataButton = new FlxButton((FlxG.width / 2) - 90, FlxG.height - 28, "Clear Data", - clickClearData); + + var clearDataButton = new FlxButton((FlxG.width / 2) - 90, FlxG.height - 28, "Clear Data", clickClearData); + clearDataButton.loadGraphic(AssetPaths.button__png, true, 80, 20); clearDataButton.onUp.sound = FlxG.sound.load(AssetPaths.select__wav); add(clearDataButton); - - backButton = new FlxButton((FlxG.width / 2) + 10, FlxG.height - 28, "Back", clickBack); + + var backButton = new FlxButton((FlxG.width / 2) + 10, FlxG.height - 28, "Back", clickBack); + backButton.loadGraphic(AssetPaths.button__png, true, 80, 20); backButton.onUp.sound = FlxG.sound.load(AssetPaths.select__wav); add(backButton); - + // update our bar to show the current volume level updateVolume(); diff --git a/Tutorials/TurnBasedRPG/source/PlayState.hx b/Tutorials/TurnBasedRPG/source/states/PlayState.hx similarity index 52% rename from Tutorials/TurnBasedRPG/source/PlayState.hx rename to Tutorials/TurnBasedRPG/source/states/PlayState.hx index b2a51962b..9b6d0dcaf 100644 --- a/Tutorials/TurnBasedRPG/source/PlayState.hx +++ b/Tutorials/TurnBasedRPG/source/states/PlayState.hx @@ -1,101 +1,91 @@ -package; +package states; import flixel.FlxG; import flixel.FlxState; import flixel.addons.editors.ogmo.FlxOgmo3Loader; -import flixel.group.FlxGroup.FlxTypedGroup; +import flixel.group.FlxGroup; import flixel.sound.FlxSound; import flixel.tile.FlxTilemap; import flixel.util.FlxColor; #if mobile import flixel.ui.FlxVirtualPad; #end +import objects.Coin; +import objects.Enemy; +import objects.Player; +import ui.CombatSubState; +import ui.HUD; using flixel.util.FlxSpriteUtil; class PlayState extends FlxState { var player:Player; - var map:FlxOgmo3Loader; var walls:FlxTilemap; var coins:FlxTypedGroup; var enemies:FlxTypedGroup; - + var hud:HUD; var money:Int = 0; - var health:Int = 3; - - var inCombat:Bool = false; - var combatHud:CombatHUD; - - var ending:Bool; - var won:Bool; - - var coinSound:FlxSound; - + #if mobile public static var virtualPad:FlxVirtualPad; #end - + override public function create() { #if FLX_MOUSE FlxG.mouse.visible = false; #end - - map = new FlxOgmo3Loader(AssetPaths.turnBasedRPG__ogmo, AssetPaths.room_001__json); + + var map = new FlxOgmo3Loader(AssetPaths.turnBasedRPG__ogmo, AssetPaths.room_001__json); walls = map.loadTilemap(AssetPaths.tiles__png, "walls"); walls.follow(); - walls.setTileProperties(1, NONE); - walls.setTileProperties(2, ANY); + walls.setTileProperties(0, NONE, null, null, 16); + walls.setTileProperties(16, ANY, null, null, 20); add(walls); - + coins = new FlxTypedGroup(); add(coins); - + enemies = new FlxTypedGroup(); add(enemies); - + player = new Player(); map.loadEntities(placeEntities, "entities"); add(player); - + FlxG.camera.follow(player, TOPDOWN, 1); - + hud = new HUD(); add(hud); - - combatHud = new CombatHUD(); - add(combatHud); - - coinSound = FlxG.sound.load(AssetPaths.coin__wav); - + #if mobile virtualPad = new FlxVirtualPad(FULL, NONE); add(virtualPad); #end - + FlxG.camera.fade(FlxColor.BLACK, 0.33, true); - + super.create(); } - + function placeEntities(entity:EntityData) { var x = entity.x; var y = entity.y; - + switch (entity.name) { case "player": player.setPosition(x, y); - + case "coin": coins.add(new Coin(x + 4, y + 4)); - + case "enemy": enemies.add(new Enemy(x + 4, y, REGULAR)); - + case "boss": enemies.add(new Enemy(x + 4, y, BOSS)); } @@ -104,62 +94,12 @@ class PlayState extends FlxState override public function update(elapsed:Float) { super.update(elapsed); - - if (ending) - { - return; - } - - if (inCombat) - { - if (!combatHud.visible) - { - health = combatHud.playerHealth; - hud.updateHUD(health, money); - if (combatHud.outcome == DEFEAT) - { - ending = true; - FlxG.camera.fade(FlxColor.BLACK, 0.33, false, doneFadeOut); - } - else - { - if (combatHud.outcome == VICTORY) - { - combatHud.enemy.kill(); - if (combatHud.enemy.type == BOSS) - { - won = true; - ending = true; - FlxG.camera.fade(FlxColor.BLACK, 0.33, false, doneFadeOut); - } - } - else - { - combatHud.enemy.flicker(); - } - inCombat = false; - player.active = true; - enemies.active = true; - - #if mobile - virtualPad.visible = true; - #end - } - } - } - else - { - FlxG.collide(player, walls); - FlxG.overlap(player, coins, playerTouchCoin); - FlxG.collide(enemies, walls); - enemies.forEachAlive(checkEnemyVision); - FlxG.overlap(player, enemies, playerTouchEnemy); - } - } - - function doneFadeOut() - { - FlxG.switchState(() -> new GameOverState(won, money)); + + FlxG.collide(player, walls); + FlxG.overlap(player, coins, playerTouchCoin); + FlxG.collide(enemies, walls); + enemies.forEachAlive(checkEnemyVision); + FlxG.overlap(player, enemies, playerTouchEnemy); } function playerTouchCoin(player:Player, coin:Coin) @@ -168,22 +108,14 @@ class PlayState extends FlxState { coin.kill(); money++; - hud.updateHUD(health, money); - coinSound.play(true); + hud.updateMoney(money); + FlxG.sound.play(AssetPaths.coin__wav); } } function checkEnemyVision(enemy:Enemy) { - if (walls.ray(enemy.getMidpoint(), player.getMidpoint())) - { - enemy.seesPlayer = true; - enemy.playerPosition = player.getMidpoint(); - } - else - { - enemy.seesPlayer = false; - } + enemy.checkVision(player, walls); } function playerTouchEnemy(player:Player, enemy:Enemy) @@ -196,13 +128,40 @@ class PlayState extends FlxState function startCombat(enemy:Enemy) { - inCombat = true; - player.active = false; - enemies.active = false; - combatHud.initCombat(health, enemy); - #if mobile virtualPad.visible = false; #end + + FlxG.sound.play(AssetPaths.combat__wav); + openSubState(new CombatSubState(player, enemy, (outcome)->handleCombatOutcome(outcome, enemy))); + } + + function handleCombatOutcome(outcome:CombatOutcome, enemy:Enemy) + { + hud.updateHealth(player.hp); + switch(outcome) + { + case VICTORY: + enemy.kill(); + if (enemy.type == BOSS) + { + fadeToGameOver(true); + } + case ESCAPED: + enemy.flicker(); + case DEFEAT: + player.alive = false; + + fadeToGameOver(false); + } + } + + function fadeToGameOver(won:Bool) + { + function onComplete() + { + FlxG.switchState(()->new GameOverState(won, money)); + } + FlxG.camera.fade(FlxColor.BLACK, 0.33, false, onComplete); } } diff --git a/Tutorials/TurnBasedRPG/source/ui/CombatSubState.hx b/Tutorials/TurnBasedRPG/source/ui/CombatSubState.hx new file mode 100644 index 000000000..c144676bf --- /dev/null +++ b/Tutorials/TurnBasedRPG/source/ui/CombatSubState.hx @@ -0,0 +1,528 @@ +package ui; + +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.group.FlxGroup; +import flixel.group.FlxSpriteGroup; +import flixel.math.FlxRect; +import flixel.text.FlxText; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.ui.FlxBar; +import flixel.util.FlxAxes; +import flixel.util.FlxColor; +import flixel.util.FlxTimer; +import flixel.util.FlxDirectionFlags; +import flixel.addons.display.FlxSliceSprite; +import flixel.addons.effects.chainable.FlxEffectSprite; +import flixel.addons.effects.chainable.FlxWaveEffect; + +import objects.Player; +import objects.Enemy; + +import openfl.filters.ColorMatrixFilter; +import openfl.geom.Matrix; +import openfl.geom.Point; + +enum CombatOutcome +{ + ESCAPED; + VICTORY; + DEFEAT; +} + +private enum Choice +{ + FIGHT; + FLEE; +} + +/** + * A SubState that pauses the main game to show the combat UI + */ +class CombatSubState extends flixel.FlxSubState +{ + /** The UI handling the actual battling */ + var ui:CombatUI; + + public function new (player:Player, enemy:Enemy, callback:(CombatOutcome)->Void) + { + super(); + + // Adds a neat wave effect to the game, this helps separate the game from the UI + var waveEffect = new CombatWaveEffect(4, 4); + add(waveEffect); + + /** Called when the UI determines an outcome of the battle */ + function outcomeReceived(outcome:CombatOutcome) + { + // disable the UI, tween it away, then pass the outcome to the callback + ui.active = false; + FlxTween.tween(ui, { y:FlxG.height }, 0.5, { ease: FlxEase.backIn, onComplete: + function (_) + { + // send the result to the play state and close + callback(outcome); + close(); + } + }); + waveEffect.fadeOut(0.5); + } + + // Create the UI + ui = new CombatUI(player, enemy, outcomeReceived); + ui.screenCenter(); + // ignore camera scroll to maintain screen position + ui.scrollFactor.set(0, 0); + add(ui); + + // store target y and then hide UI below the screen + var targetY = ui.y; + ui.y = FlxG.height; + + // disable the UI, tween it up from the bottom, then enable it + ui.active = false; + function onTweenComplete(tween:FlxTween) + { + ui.active = true; + } + FlxG.camera.flash(FlxColor.WHITE, .2); + FlxTween.tween(ui, { y:targetY }, 0.5, + { + startDelay: 0.2,// wait for flash + ease: FlxEase.backOut, + onComplete: onTweenComplete + } + ); + waveEffect.fadeIn(1.0); + } +} + +/** + * The UI that controls combat, extends `FlxSpriteGroup` which changes its members + * when certain properties are changed, namely `x`, `y`, `alpha` and `scrollFactor`. + */ +class CombatUI extends FlxSpriteGroup +{ + /** A function that handles the outcome of this match */ + var callback:(CombatOutcome)->Void; + + /** A reference to the player sprite in the main game */ + var player:Player; + /** A sprite representing our hero */ + var playerIcon:FlxSprite; + /** Used to show the result of attacks */ + var playerDmg:FlxText; + /** Displays the player's health */ + var playerHealthBar:FlxBar; + + /** A reference to our current opponent in the main game */ + var enemy:Enemy; + /** A sprite representing our enemy */ + var enemyIcon:FlxSprite; + /** Used to show the result of attacks */ + var enemyDmg:FlxText; + /** Displays the enemy's health */ + var enemyHealthBar:FlxBar; + + /** A group of texts that show damage or missed attacks */ + var attackResults:FlxTypedGroup; + + /** Shows the currently selected option (FIGHT or FLEE). */ + var pointer:FlxSprite; + /** Tracks which option is selected */ + var selected:Choice = FIGHT; + /** Contains text displaying our 2 options: FIGHT and FLEE */ + var choices:Map; + + public function new (player:Player, enemy:Enemy, callback:(CombatOutcome)->Void) + { + this.callback = callback; + this.player = player; + this.enemy = enemy; + super(); + + // Make a background to visially separate the ui from the game underneath + var bg = new FlxSliceSprite(AssetPaths.uiback__png, new FlxRect(16, 16, 16, 16), 128, 128); + // Stretch the sections rather than tiling for better performance + bg.stretchTop + = bg.stretchRight + = bg.stretchLeft + = bg.stretchBottom + = bg.stretchCenter + = true; + add(bg); + + final border = 6; + var section = new FlxSliceSprite(AssetPaths.ui_section__png, new FlxRect(16, 16, 16, 16), bg.width - border * 2, 72); + section.x = border; + section.y = bg.height - section.height - border; + // Stretch the sections rather than tiling for better performance + section.stretchTop + = bg.stretchRight + = bg.stretchLeft + = bg.stretchBottom + = section.stretchCenter + = true; + add(section); + + // Make a 'dummy' player and health bar + playerIcon = createAvatar(24, 8, player, player.maxHP); + playerIcon.facing = RIGHT; + + // Make a 'dummy' enemy and health bar + enemyIcon = createAvatar(80, 8, enemy, enemy.maxHP); + enemyIcon.facing = LEFT; + + attackResults = new FlxTypedGroup(); + for (i in 0...2) + { + var text = new FlxText(0, 0); + // Use same color as the pointer + text.color = 0xFFe5e5e5; + text.setBorderStyle(SHADOW, FlxColor.RED); + attackResults.add(text); + add(text); + text.kill(); + } + + // create our choices and add them to the group. + choices = new Map(); + choices[FIGHT] = new LargeText(40, 64, "FIGHT", 2); + choices[FLEE] = new LargeText(40, choices[FIGHT].y + choices[FIGHT].height + 10, "FLEE", 2); + add(choices[FIGHT]); + add(choices[FLEE]); + + pointer = new FlxSprite(16, choices[FIGHT].y + (choices[FIGHT].height / 2) - 8, AssetPaths.pointer__png); + add(pointer); + } + + /** + * Creates and adds an Icon and health bar for the target fighter + */ + function createAvatar(x:Float, y:Float, target:FlxSprite, maxHealth:Float) + { + // Create an "dummy" icon of the target + var icon = new FlxSprite(x, y); + // Use the target's graphic to have the same frames + icon.loadGraphicFromSprite(target); + icon.animation.play("lr_idle"); + + icon.setFacingFlip(LEFT, true, false); + icon.setFacingFlip(RIGHT, false, false); + add(icon); + + // create a health bar + var bar = new FlxBar(0, icon.y + icon.height + 2, LEFT_TO_RIGHT, 30, 10); + bar.createImageBar("assets/images/bar_empty.png", "assets/images/bar_filled.png", 0x0, 0x0); + bar.setRange(0, maxHealth); + // tracks the target's health automatically + bar.parent = target; + bar.parentVariable = "hp"; + bar.update(0);//redraw + // yellow bar, yellow border, red underneath + centerOn(bar, icon, X); + add(bar); + + return icon; + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + // Only check input between turns when the pointer is showing + if (pointer.exists) + { + #if FLX_KEYBOARD + updateKeyboardInput(); + #end + + #if FLX_TOUCH + updateTouchInput(); + #end + } + } + + /** + * Call this function to place the pointer next to the currently selected choice + */ + inline function hilightChoice(choice:Choice) + { + selected = choice; + centerOn(pointer, choices[selected], Y); + } + + #if FLX_KEYBOARD + function updateKeyboardInput() + { + // Create helper func with shorter name + var justPressed = FlxG.keys.anyJustPressed; + var justReleased = FlxG.keys.anyJustReleased; + if (justPressed([SPACE, X, ENTER])) + { + FlxG.sound.play(AssetPaths.select__wav); + onChoose(selected); + } + // if the playerSprite presses up or down (but not both) + else if (justReleased([W, UP]) != justReleased([S, DOWN])) + { + FlxG.sound.play(AssetPaths.select__wav); + // Move the cursor up or down (with wrapping) + hilightChoice(selected == FIGHT ? FLEE : FIGHT); + } + } + #end + + #if FLX_TOUCH + function updateTouchInput() + { + for (touch in FlxG.touches.justReleased()) + { + for (choice in choices.keys()) + { + var text = choices[choice]; + if (touch.overlaps(text)) + { + FlxG.sound.play(AssetPaths.select__wav); + hilightChoice(choice); + onChoose(choice); + return; + } + } + } + } + #end + + function onChoose(selected:Choice) + { + // kill texts from last round + attackResults.killMembers(); + // hide our pointer + pointer.exists = false; + // check which item was selected when the playerSprite picked it + switch (selected) + { + case FIGHT: + playerAttack(); + + case FLEE: + // 50% chance to flee + if (FlxG.random.bool(50)) + { + // Success + FlxG.sound.play(AssetPaths.fled__wav); + showOutcome(ESCAPED); + } + else + { + // Failure, enemy gets a free attack + enemyAttack(); + } + } + } + + function showAttackResult(icon:FlxSprite, msg:String, ?onComplete:()->Void) + { + // recycle dead text + var text = attackResults.recycle(); + text.text = msg; + centerOn(text, icon, XY); + // text.alpha = 0; + + var callback = onComplete != null ? (_)->onComplete() : null; + FlxTween.tween(text, { alpha: 2 }, 0.5, { ease: FlxEase.circOut, onComplete: callback }); + FlxTween.tween(text, { y: text.y - 12 }, 1, { ease: FlxEase.circOut, onComplete:(_)->text.kill() }); + } + + function playerAttack() + { + // Move the player towards the enemy then back + FlxTween.tween(playerIcon, {x: playerIcon.x + 12}, 0.1) + .then(FlxTween.tween(playerIcon, {x: playerIcon.x}, 0.1)); + + var result:String; + // 85% chance for player to hit + if (FlxG.random.bool(85)) + { + // Success + FlxG.sound.play(AssetPaths.hurt__wav); + // Move the enemy, then move it back + FlxTween.tween(enemyIcon, {x: enemyIcon.x + 4}, 0.1, {startDelay: 0.1}) + .then(FlxTween.tween(enemyIcon, {x: enemyIcon.x}, 0.1)); + + // Deal 1 damage to the enemy + enemy.hurt(1); + result = "1"; + } + else + { + // We missed + FlxG.sound.play(AssetPaths.miss__wav); + result = "MISS!"; + } + + // Show the attack result then it's the enemy's turn + if (enemy.hp > 0) + { + showAttackResult(enemyIcon, result, enemyAttack); + } + else + { + showAttackResult(enemyIcon, result, roundEnd); + } + } + + function enemyAttack() + { + // Move the enemy towards the player, then move it back + FlxTween.tween(enemyIcon, {x: enemyIcon.x - 12}, 0.1) + .then(FlxTween.tween(enemyIcon, {x: enemyIcon.x}, 0.1)); + + var result:String; + // 30% chance to hit + if (FlxG.random.bool(30)) + { + // Flash the screen white + FlxG.camera.flash(FlxColor.WHITE, .2); + FlxG.camera.shake(0.01, 0.2); + + // Move the enemy, then move it back + FlxTween.tween(playerIcon, {x: playerIcon.x - 4}, 0.1, {startDelay: 0.1}) + .then(FlxTween.tween(playerIcon, {x: playerIcon.x}, 0.1)); + // Deal 1 damage + player.hurt(1); + FlxG.sound.play(AssetPaths.hurt__wav); + result = "1"; + } + else + { + // Missed + FlxG.sound.play(AssetPaths.miss__wav); + result = "MISS!"; + } + + showAttackResult(playerIcon, result, roundEnd); + } + + function roundEnd() + { + if (player.hp <= 0) + { + showOutcome(DEFEAT); + } + else if (enemy.hp <= 0) + { + showOutcome(VICTORY); + } + else + { + // Enables UI for net turn + pointer.exists = true; + } + } + + function showOutcome(outcome:CombatOutcome) + { + var text = new LargeText(0, 0, outcome.getName(), 3); + text.color = FlxColor.YELLOW; + text.setBorderStyle(SHADOW, FlxColor.GRAY); + // Adding a sprite to a sprite group will change the x/y by the group's, so add first + add(text); + text.screenCenter(); + + // Store desired y, then tween to it + var targetY = text.y; + text.y = -text.height; + FlxTween.tween(text, { y: targetY }, 1, { ease: FlxEase.backOut, onComplete: + function (_) + { + // Hold it there for a sec, then start the outro + FlxTimer.wait(1.0, ()->callback(outcome)); + } + }); + } + + /** + * Centers the first sprite's so it's center x mathes the center x of the target + */ + static inline function centerOn(sprite:FlxSprite, target:FlxSprite, axes:FlxAxes = XY) + { + if (axes.x) + { + sprite.x = target.x + (target.width - sprite.width) / 2; + } + + if (axes.y) + { + sprite.y = target.y + (target.height - sprite.height) / 2; + } + } +} + +/** + * Helper class that handles the wave effect + */ +private class CombatWaveEffect extends FlxEffectSprite +{ + /** + * [Description] + * @param strength How strong you want the effect + * @param speed How fast you want the effect to move, higher values = faster + */ + public function new(strength = 10, speed = 3.0) + { + var screen = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.TRANSPARENT); + super(screen, [new FlxWaveEffect(FlxWaveMode.ALL, strength, -1, speed)]); + scrollFactor.set(0, 0); + + // Draw the camera to a bitmap + screen.drawFrame(); + var screenPixels = screen.framePixels; + if (FlxG.renderBlit) + screenPixels.copyPixels(FlxG.camera.buffer, FlxG.camera.buffer.rect, new Point()); + else + screenPixels.draw(FlxG.camera.canvas, new Matrix(1, 0, 0, 1, 0, 0)); + + // Apply desaturating color matrix + var rc:Float = 1 / 3; + var gc:Float = 1 / 2; + var bc:Float = 1 / 6; + screenPixels.applyFilter(screenPixels, screenPixels.rect, new Point(), + new ColorMatrixFilter([rc, gc, bc, 0, 0, rc, gc, bc, 0, 0, rc, gc, bc, 0, 0, 0, 0, 0, 1, 0])); + } + + /** + * Tweens the `alpha` and `strength` from 0 + * @param time The duration of the fade + */ + public function fadeIn(time:Float) + { + var waveEffect:FlxWaveEffect = cast this.effects[0]; + var strength = waveEffect.strength; + // Fade effect in + // alpha = 0; + FlxTween.num(0.0, 1.0, time, (n)-> + { + alpha = n; + waveEffect.strength = Math.floor(strength * FlxEase.circOut(Math.max(n - 0.5, 0) * 2)); + }); + } + + /** + * Tweens the `alpha` and `strength` to 0 + * @param time The duration of the fade + */ + public function fadeOut(time:Float) + { + var waveEffect:FlxWaveEffect = cast this.effects[0]; + var strength = waveEffect.strength; + // Fade effect in + // alpha = 1.0; + FlxTween.num(1.0, 0.0, time, (n)-> + { + alpha = n; + waveEffect.strength = Math.floor(strength * FlxEase.circOut(Math.max(n - 0.5, 0) * 2)); + }); + } +} \ No newline at end of file diff --git a/Tutorials/TurnBasedRPG/source/ui/HUD.hx b/Tutorials/TurnBasedRPG/source/ui/HUD.hx new file mode 100644 index 000000000..b240d19a1 --- /dev/null +++ b/Tutorials/TurnBasedRPG/source/ui/HUD.hx @@ -0,0 +1,52 @@ +package ui; + +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.group.FlxGroup.FlxTypedGroup; +import flixel.text.FlxText; +import flixel.util.FlxColor; +import objects.Coin; + +using flixel.util.FlxSpriteUtil; + +class HUD extends FlxTypedGroup +{ + var healthCounter:FlxText; + var moneyCounter:FlxText; + + public function new() + { + super(); + + var healthIcon = new FlxSprite(4, 4, AssetPaths.health__png); + healthCounter = new FlxText(0, 0, 0, "3 / 3", 8); + healthCounter.setBorderStyle(SHADOW, FlxColor.GRAY, 1, 1); + healthCounter.x = healthIcon.x + healthIcon.width; + healthCounter.y = healthIcon.y + (healthIcon.height - healthCounter.height) / 2; + add(healthIcon); + add(healthCounter); + + var moneyIcon = new Coin(0, 4); + moneyIcon.solid = false; + moneyCounter = new FlxText(0, 0, 0, "00", 8); + moneyCounter.setBorderStyle(SHADOW, FlxColor.GRAY, 1, 1); + moneyIcon.x = FlxG.width - 4 - moneyIcon.width - moneyCounter.width; + moneyCounter.x = moneyIcon.x + moneyIcon.width; + moneyCounter.y = moneyIcon.y + (moneyIcon.height - moneyCounter.height) / 2; + moneyCounter.text = "0"; + add(moneyIcon); + add(moneyCounter); + + forEach(function(sprite) sprite.scrollFactor.set(0, 0)); + } + + public function updateHealth(health:Int) + { + healthCounter.text = health + " / 3"; + } + + public function updateMoney(money:Int) + { + moneyCounter.text = Std.string(money); + } +} diff --git a/Tutorials/TurnBasedRPG/source/ui/LargeText.hx b/Tutorials/TurnBasedRPG/source/ui/LargeText.hx new file mode 100644 index 000000000..dd3f87120 --- /dev/null +++ b/Tutorials/TurnBasedRPG/source/ui/LargeText.hx @@ -0,0 +1,64 @@ +package ui; + +import flixel.FlxG; +import flixel.math.FlxPoint; +import flixel.math.FlxRect; +import flixel.text.FlxBitmapFont; +import flixel.text.FlxBitmapText; + +/** + * Note: This is not in the tutorial, the tutorial uses FlxText everywhere, which is easier to teach. + * Feel free to use this in your games + * Font created by Rick Hoppmann: https://tinyworlds.itch.io/free-pixel-font-thaleah + */ +@:forward +abstract LargeText(FlxBitmapText) to FlxBitmapText +{ + static function getDefaultFont() + { + final graphic = FlxG.bitmap.add(AssetPaths.font__png); + final font = FlxBitmapFont.findFont(graphic.imageFrame.frame); + if (font != null) + { + return font; + } + + final chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!"; + final widths = ['I'.code=>3, '!'.code=>3, 'T'.code=>7]; + final defaultWidth = 8; + final defaultHeight = 8; + final font = FlxBitmapFont.fromMonospace(graphic, "", new FlxPoint(defaultWidth, defaultHeight)); + + var x:Int = 0; + for (i in 0...chars.length) + { + final charCode = chars.charCodeAt(i); + final width = if (widths.exists(charCode)) widths[charCode] else defaultWidth; + final frame = FlxRect.get(x, 0, width, defaultHeight); + @:privateAccess + font.addCharFrame(charCode, frame, FlxPoint.weak(), width); + x += width; + } + return font; + } + + public function new (x = 0.0, y = 0.0, text = "", scale = 4) + { + this = new FlxBitmapText(x, y, text, getDefaultFont()); + this.text = text; + this.autoUpperCase = true; + setScale(scale); + } + + function setScale(scale:Int) + { + this.scale.set(scale, scale); + this.updateHitbox(); + } + + inline public function setBorderStyle(style, color = 0x0, size = 1, quality = 1) + { + this.setBorderStyle(style, color, size, quality); + this.updateHitbox(); + } +} \ No newline at end of file diff --git a/Tutorials/TurnBasedRPG/source/ui/OptionsSubState.hx b/Tutorials/TurnBasedRPG/source/ui/OptionsSubState.hx new file mode 100644 index 000000000..a17242f2f --- /dev/null +++ b/Tutorials/TurnBasedRPG/source/ui/OptionsSubState.hx @@ -0,0 +1,272 @@ +package ui; + +import flixel.FlxSprite; +import flixel.FlxG; +import flixel.addons.display.FlxSliceSprite; +import flixel.group.FlxGroup; +import flixel.group.FlxSpriteGroup; +import flixel.math.FlxRect; +import flixel.text.FlxText; +import flixel.ui.FlxBar; +import flixel.ui.FlxButton; +import flixel.util.FlxColor; + +/** + * A SubState that pauses the main menu to show the options UI + */ +class OptionsSubState extends flixel.FlxSubState +{ + public function new () + { + super(); + + // black-out the main menu + var back = new FlxSprite(); + back.makeGraphic(1, 1, 0x80000000); + back.setGraphicSize(FlxG.width, FlxG.height); + back.updateHitbox(); + add(back); + + // add the UI + var ui = new OptionsUI(()->close()); + add(ui); + } +} + +/** + * The UI that controls options, extends `FlxSpriteGroup` which changes its members + * when certain properties are changed, namely `x`, `y`, `alpha` and `scrollFactor`. + */ +class OptionsUI extends FlxGroup +{ + + // define our screen elements + var musicBar:VolumeBar; + var soundBar:VolumeBar; + var fullscreenButton:FlxButton; + + public function new (onClose:()->Void) + { + super(); + + if (FlxG.save.data.volumes == null) + { + initSave(); + } + + // Make a background to visially separate the ui from the game underneath + var bg = new FlxSliceSprite(AssetPaths.uiback__png, new FlxRect(16, 16, 16, 16), 200, 160); + // Stretch the sections rather than tiling them for better performance + bg.stretchTop + = bg.stretchRight + = bg.stretchLeft + = bg.stretchBottom + = bg.stretchCenter + = true; + bg.screenCenter(XY); + add(bg); + + // place the group in the screen center, this will move the bg, and anything added later + + var titleText = new LargeText(bg.x, bg.y + 4, "Options", 2); + titleText.screenCenter(X); + add(titleText); + + var gap = 8; + var barX = bg.x + gap; + var barY = titleText.y + titleText.height + gap; + var barWidth = bg.width - gap * 2; + + musicBar = new VolumeBar(barX, barY, barWidth, "Music", FlxG.sound.defaultMusicGroup.volume, updateMusic); + add(musicBar); + barY += musicBar.height + gap; + + soundBar = new VolumeBar(barX, barY, barWidth, "Sound", FlxG.sound.defaultSoundGroup.volume, updateSound); + add(soundBar); + // barY += soundBar.height + gap; + + fullscreenButton = new Button(0, soundBar.y + soundBar.height + 8, FlxG.fullscreen ? "Windowed" : "Fullscreen", clickFullscreen); + fullscreenButton.screenCenter(X); + add(fullscreenButton); + + var clearDataButton = new Button(bg.x + 10, 0, "Clear Data", clickClearData); + clearDataButton.y = bg.y + bg.height - clearDataButton.height - 10; + add(clearDataButton); + + var backButton = new Button(0, clearDataButton.y, "Back", onClose); + backButton.x = bg.x + bg.width - backButton.width - 10; + add(backButton); + } + + function clickFullscreen() + { + fullscreenButton.text = FlxG.fullscreen ? "Windowed" : "Fullscreen"; + FlxG.fullscreen = !FlxG.fullscreen; + FlxG.save.data.fullscreen = FlxG.fullscreen; + FlxG.save.flush(); + } + + function initSave() + { + FlxG.save.data.volumes = + { + music: FlxG.sound.defaultMusicGroup.volume, + sound: FlxG.sound.defaultSoundGroup.volume + }; + FlxG.sound.muted = false; + FlxG.sound.volume = 1.0; + FlxG.save.data.fullscreen = FlxG.fullscreen; + FlxG.save.flush(); + trace(FlxG.save.data); + } + + /** + * The user wants to clear the saved data - we just call erase on our save object and then reset the volume to .5 + */ + function clickClearData() + { + FlxG.save.erase(); + initSave(); + musicBar.setVolume(0.5, true); + soundBar.setVolume(1.0, true); + } + + /** + * Whenever we want to show the value of volume, we call this to change the bar and the amount text + */ + function updateMusic(volume:Float) + { + FlxG.sound.defaultMusicGroup.volume = volume; + FlxG.save.data.volumes.music = volume; + FlxG.save.flush(); + } + + /** + * Whenever we want to show the value of volume, we call this to change the bar and the amount text + */ + function updateSound(volume:Float) + { + FlxG.sound.defaultSoundGroup.volume = volume; + FlxG.save.data.volumes.sound = volume; + FlxG.save.flush(); + } +} + +class VolumeBar extends FlxSpriteGroup +{ + var bar:FlxBar; + var amountText:FlxText; + var label:String; + var onChange:(amount:Float)->Void; + + /** + * + * @param x + * @param y + * @param width + * @param label + * @param onChange + * @return + */ + public function new (x:Float, y:Float, width:Float, label:String, volume:Float, onChange:(amount:Float)->Void) + { + this.label = label; + this.onChange = onChange; + super(); + + // var label = new FlxText(0, 0, 0, label, 8); + // label.x = (width - label.width) / 2; + // add(label); + + // the volume buttons will be smaller than 'default' buttons + var downButton = new SmallButton(0, 0, "-", clickDown); + add(downButton); + + var upButton = new SmallButton(0, downButton.y, "+", clickUp); + upButton.x = width - upButton.width; + add(upButton); + + var barWidth = Std.int(width - (4 + upButton.width) * 2); + var barHeight = Std.int(upButton.height); + bar = new FlxBar(downButton.x + downButton.width + 4, downButton.y, LEFT_TO_RIGHT, barWidth, barHeight); + bar.createFilledBar(0xff464646, FlxColor.WHITE, true, FlxColor.WHITE); + add(bar); + + amountText = new FlxText(0, 0, 200, "100%", 8); + amountText.alignment = CENTER; + amountText.setBorderStyle(OUTLINE, 0xff464646); + amountText.x = bar.x + (bar.width - amountText.width) / 2; + amountText.y = bar.y + (bar.height - amountText.height) / 2; + add(amountText); + + // + this.x = x; + this.y = y; + setVolume(volume); + } + + function clickDown() + { + setVolumeHelper(bar.value - 10); + } + + function clickUp() + { + setVolumeHelper(bar.value + 10); + } + + function setVolumeHelper(volume:Float, dispatch = true) + { + bar.value = Math.round(volume); // Note: bar.value is automatically clamped between 0 and 100 + amountText.text = label + " " + bar.value + "%"; + + if (dispatch) + { + onChange(bar.value / 100); + } + } + + public function setVolume(volume:Float, dispatch = false) + { + setVolumeHelper(volume * 100, dispatch); + } + + override function destroy() + { + // remove references but do not destroy + bar = null; + amountText = null; + onChange = null; + + // this will actually destroy them + super.destroy(); + } +} + +/** + * Helper class for creating an 80x20 button with a custom graphic + */ +class Button extends FlxButton +{ + public function new (x, y, label, onClick) + { + super(x, y, label, onClick); + + loadGraphic(AssetPaths.button__png, true, 80, 20); + onUp.sound = FlxG.sound.load(AssetPaths.select__wav); + } +} + +/** + * Helper class for creating small 20x20 buttons + */ +class SmallButton extends FlxButton +{ + public function new (x, y, label, onClick) + { + super(x, y, label, onClick); + + loadGraphic(AssetPaths.small_button__png, true, 20, 20); + onUp.sound = FlxG.sound.load(AssetPaths.select__wav); + } +} \ No newline at end of file