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