commit b6920389b0064e0cd19c017e9d59d05515550313 Author: gered Date: Sun Aug 10 19:10:01 2014 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aff5aa5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.DS_Store +.gradle/ +out/ +log/ +target/ +build/ +.settings/ +.project +.classpath +.idea +*.iml +*.eml +*.ipr +*.iws +*.class +*.jar diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8d9476f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Gered King + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f15707 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Tiles³ Framework: Basic Example + +A basic working example application of the "Tiles³ Framework" in action. This just +show cases a complete but very, very simple application that makes use of: + +* [libGDX](http://libgdx.badlogicgames.com/) +* [gdx-toolbox](https://github.com/gered/gdx-toolbox) +* [gdx-tilemap3d](https://github.com/gered/gdx-tilemap3d) +* Basic custom entity/event system and gameplay handling code. + +This basic example is nothing more then a boring walk-around demo. Nothing too +exciting as you would expect from something called a "basic example." + +![Screenshot 2](screenshot.png) + +## What is the "Tiles³ Framework"? + +This is what I'm calling my own custom "framework" (and I'm using the word loosely here) +for building 3D tile-based top-down real-time action games. This framework is built upon +libGDX and my own libraries gdx-toolbox and gdx-tilemap3d and builds on the entity and +event system in those with a bunch of pre-built subsystems somewhat suitable for building +games. + +**This is NOT intended to be a generic game engine / library.** Rather, it is just my +common game template (for these specific types of games) which I'm giving a name +and putting up for anyone else to use. You will _very likely_ want to heavily +customize this, assuming it even meets your needs at all! + +## Running + +You will need Gradle. Clone the repository and then from a terminal: + + $ gradle desktop:run + +And it should download the project dependencies, build and then run. + +## TODO + +[[Write something here briefly explaining the project and code structure]] + +## License + +Distributed under the the MIT License. See LICENSE for more details. diff --git a/assets/consoleFont.fnt b/assets/consoleFont.fnt new file mode 100644 index 0000000..deb62bb --- /dev/null +++ b/assets/consoleFont.fnt @@ -0,0 +1,101 @@ +info face="DeluxeFontReg" size=8 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=0,0 +common lineHeight=9 base=7 scaleW=512 scaleH=512 pages=1 packed=0 +page id=0 file="consoleFont.png" +chars count=95 +char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=7 xadvance=8 page=0 chnl=0 +char id=106 x=0 y=0 width=6 height=9 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=38 x=6 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=35 x=14 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=36 x=22 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=92 x=29 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=64 x=37 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=47 x=45 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=124 x=53 y=0 width=3 height=8 xoffset=3 yoffset=0 xadvance=8 page=0 chnl=0 +char id=62 x=56 y=0 width=6 height=8 xoffset=1 yoffset=0 xadvance=8 page=0 chnl=0 +char id=60 x=62 y=0 width=6 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=125 x=68 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=123 x=75 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=93 x=82 y=0 width=5 height=8 xoffset=1 yoffset=0 xadvance=8 page=0 chnl=0 +char id=91 x=87 y=0 width=5 height=8 xoffset=1 yoffset=0 xadvance=8 page=0 chnl=0 +char id=41 x=92 y=0 width=5 height=8 xoffset=1 yoffset=0 xadvance=8 page=0 chnl=0 +char id=40 x=97 y=0 width=5 height=8 xoffset=1 yoffset=0 xadvance=8 page=0 chnl=0 +char id=59 x=102 y=0 width=4 height=8 xoffset=1 yoffset=1 xadvance=8 page=0 chnl=0 +char id=63 x=106 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=33 x=113 y=0 width=3 height=8 xoffset=2 yoffset=0 xadvance=8 page=0 chnl=0 +char id=48 x=116 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=57 x=124 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=56 x=131 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=55 x=138 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=54 x=145 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=53 x=152 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=52 x=159 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=51 x=167 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=50 x=174 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=49 x=181 y=0 width=5 height=8 xoffset=1 yoffset=0 xadvance=8 page=0 chnl=0 +char id=116 x=186 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=108 x=193 y=0 width=4 height=8 xoffset=1 yoffset=0 xadvance=8 page=0 chnl=0 +char id=107 x=197 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=105 x=204 y=0 width=3 height=8 xoffset=2 yoffset=0 xadvance=8 page=0 chnl=0 +char id=104 x=207 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=102 x=214 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=100 x=221 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=98 x=228 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=90 x=235 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=89 x=243 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=88 x=250 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=87 x=258 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=86 x=266 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=85 x=273 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=84 x=280 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=83 x=287 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=82 x=294 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=81 x=301 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=80 x=308 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=79 x=315 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=78 x=322 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=77 x=330 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=76 x=338 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=75 x=345 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=74 x=353 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=73 x=361 y=0 width=5 height=8 xoffset=1 yoffset=0 xadvance=8 page=0 chnl=0 +char id=72 x=366 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=71 x=373 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=70 x=380 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=69 x=387 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=68 x=394 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=67 x=401 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=66 x=408 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=65 x=415 y=0 width=7 height=8 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=127 x=422 y=0 width=8 height=7 xoffset=0 yoffset=1 xadvance=8 page=0 chnl=0 +char id=37 x=430 y=0 width=8 height=7 xoffset=0 yoffset=1 xadvance=8 page=0 chnl=0 +char id=58 x=438 y=0 width=3 height=7 xoffset=2 yoffset=1 xadvance=8 page=0 chnl=0 +char id=121 x=441 y=0 width=7 height=7 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=113 x=448 y=0 width=7 height=7 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=112 x=455 y=0 width=7 height=7 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=103 x=462 y=0 width=7 height=7 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=42 x=469 y=0 width=9 height=6 xoffset=0 yoffset=1 xadvance=8 page=0 chnl=0 +char id=43 x=478 y=0 width=7 height=6 xoffset=0 yoffset=1 xadvance=8 page=0 chnl=0 +char id=122 x=485 y=0 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=120 x=492 y=0 width=8 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=119 x=500 y=0 width=8 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=118 x=0 y=9 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=117 x=7 y=9 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=115 x=14 y=9 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=114 x=21 y=9 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=111 x=28 y=9 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=110 x=35 y=9 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=109 x=42 y=9 width=8 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=101 x=50 y=9 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=99 x=57 y=9 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=97 x=64 y=9 width=7 height=6 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=61 x=71 y=9 width=7 height=5 xoffset=0 yoffset=2 xadvance=8 page=0 chnl=0 +char id=94 x=78 y=9 width=8 height=5 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=44 x=86 y=9 width=4 height=4 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=0 +char id=39 x=90 y=9 width=4 height=4 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=96 x=94 y=9 width=4 height=4 xoffset=2 yoffset=0 xadvance=8 page=0 chnl=0 +char id=34 x=98 y=9 width=6 height=4 xoffset=1 yoffset=0 xadvance=8 page=0 chnl=0 +char id=126 x=104 y=9 width=8 height=3 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=0 +char id=46 x=112 y=9 width=3 height=3 xoffset=2 yoffset=5 xadvance=8 page=0 chnl=0 +char id=95 x=115 y=9 width=9 height=2 xoffset=0 yoffset=7 xadvance=8 page=0 chnl=0 +char id=45 x=124 y=9 width=7 height=2 xoffset=0 yoffset=3 xadvance=8 page=0 chnl=0 +kernings count=-1 diff --git a/assets/consoleFont.png b/assets/consoleFont.png new file mode 100644 index 0000000..f77bd78 Binary files /dev/null and b/assets/consoleFont.png differ diff --git a/assets/player.atlas.json b/assets/player.atlas.json new file mode 100644 index 0000000..a9b02b9 --- /dev/null +++ b/assets/player.atlas.json @@ -0,0 +1,6 @@ +{ + "texture": "player.png", + "tiles": [ + {"autogrid": true, "numTilesX": 8, "numTilesY": 8, "tileWidth": 16, "tileHeight": 16}, + ] +} \ No newline at end of file diff --git a/assets/player.png b/assets/player.png new file mode 100644 index 0000000..8264b87 Binary files /dev/null and b/assets/player.png differ diff --git a/assets/readme.txt b/assets/readme.txt new file mode 100644 index 0000000..7495d9e --- /dev/null +++ b/assets/readme.txt @@ -0,0 +1,15 @@ +Artwork Credits +--------------- + +Player sprite: + Oryx and Geese (animations) + http://forums.tigsource.com/index.php?topic=8970.0 + +Font: + DeluxeFont by codeman38 + http://www.zone38.net/font/#deluxefont + +Terrain Textures: + From an 8x8 Minecraft Texturepack that I can't find anymore. There are + several of them and none of the ones I checked, at the time I was + writing these credits, exactly match the textures I copied. :( diff --git a/assets/tiles/bricks.tilemesh b/assets/tiles/bricks.tilemesh new file mode 100644 index 0000000..39be9c4 --- /dev/null +++ b/assets/tiles/bricks.tilemesh @@ -0,0 +1,7 @@ +{ + "textureAtlas": "terrain.atlas.json", + "cube": true, + "texture": 7, + "faces": ["ALL"], + "opaqueSides": ["ALL"] +} \ No newline at end of file diff --git a/assets/tiles/dirt.tilemesh b/assets/tiles/dirt.tilemesh new file mode 100644 index 0000000..8068515 --- /dev/null +++ b/assets/tiles/dirt.tilemesh @@ -0,0 +1,7 @@ +{ + "textureAtlas": "terrain.atlas.json", + "cube": true, + "texture": 2, + "faces": ["ALL"], + "opaqueSides": ["ALL"] +} \ No newline at end of file diff --git a/assets/tiles/dirt_grass_top.tilemesh b/assets/tiles/dirt_grass_top.tilemesh new file mode 100644 index 0000000..8c6e489 --- /dev/null +++ b/assets/tiles/dirt_grass_top.tilemesh @@ -0,0 +1,14 @@ +{ + "textureAtlas": "terrain.atlas.json", + "cube": true, + "textures": { + "top": 0, + "left": 1, + "front": 1, + "right": 1, + "back": 1, + "bottom": 2 + }, + "faces": ["ALL"], + "opaqueSides": ["ALL"] +} diff --git a/assets/tiles/grass.tilemesh b/assets/tiles/grass.tilemesh new file mode 100644 index 0000000..7bcea3a --- /dev/null +++ b/assets/tiles/grass.tilemesh @@ -0,0 +1,7 @@ +{ + "textureAtlas": "terrain.atlas.json", + "cube": true, + "texture": 0, + "faces": ["ALL"], + "opaqueSides": ["ALL"] +} \ No newline at end of file diff --git a/assets/tiles/leaves.tilemesh b/assets/tiles/leaves.tilemesh new file mode 100644 index 0000000..37cba31 --- /dev/null +++ b/assets/tiles/leaves.tilemesh @@ -0,0 +1,8 @@ +{ + "textureAtlas": "terrain.atlas.json", + "cube": true, + "texture": 3, + "faces": ["ALL"], + "alpha": true, + "translucency": 0.25, +} \ No newline at end of file diff --git a/assets/tiles/planks.tilemesh b/assets/tiles/planks.tilemesh new file mode 100644 index 0000000..a4eec02 --- /dev/null +++ b/assets/tiles/planks.tilemesh @@ -0,0 +1,7 @@ +{ + "textureAtlas": "terrain.atlas.json", + "cube": true, + "texture": 6, + "faces": ["ALL"], + "opaqueSides": ["ALL"] +} \ No newline at end of file diff --git a/assets/tiles/sand.tilemesh b/assets/tiles/sand.tilemesh new file mode 100644 index 0000000..ac09083 --- /dev/null +++ b/assets/tiles/sand.tilemesh @@ -0,0 +1,7 @@ +{ + "textureAtlas": "terrain.atlas.json", + "cube": true, + "texture": 9, + "faces": ["ALL"], + "opaqueSides": ["ALL"] +} \ No newline at end of file diff --git a/assets/tiles/stone_floor.tilemesh b/assets/tiles/stone_floor.tilemesh new file mode 100644 index 0000000..bf24b40 --- /dev/null +++ b/assets/tiles/stone_floor.tilemesh @@ -0,0 +1,7 @@ +{ + "textureAtlas": "terrain.atlas.json", + "cube": true, + "texture": 8, + "faces": ["ALL"], + "opaqueSides": ["ALL"] +} \ No newline at end of file diff --git a/assets/tiles/terrain.atlas.json b/assets/tiles/terrain.atlas.json new file mode 100644 index 0000000..904e8ac --- /dev/null +++ b/assets/tiles/terrain.atlas.json @@ -0,0 +1,6 @@ +{ + "texture": "terrain.png", + "tiles": [ + {"autogrid": true, "numTilesX": 8, "numTilesY": 8, "tileWidth": 8, "tileHeight": 8}, + ] +} \ No newline at end of file diff --git a/assets/tiles/terrain.png b/assets/tiles/terrain.png new file mode 100644 index 0000000..3905ec0 Binary files /dev/null and b/assets/tiles/terrain.png differ diff --git a/assets/tiles/tiles.json b/assets/tiles/tiles.json new file mode 100644 index 0000000..2a73d9c --- /dev/null +++ b/assets/tiles/tiles.json @@ -0,0 +1,14 @@ +{ + "textureAtlas": "terrain.atlas.json", + "tiles": [ + "grass.tilemesh", + "dirt_grass_top.tilemesh", + "dirt.tilemesh", + "leaves.tilemesh", + "trunk.tilemesh", + "planks.tilemesh", + "bricks.tilemesh", + "stone_floor.tilemesh", + "sand.tilemesh" + ] +} \ No newline at end of file diff --git a/assets/tiles/trunk.tilemesh b/assets/tiles/trunk.tilemesh new file mode 100644 index 0000000..2123ea8 --- /dev/null +++ b/assets/tiles/trunk.tilemesh @@ -0,0 +1,14 @@ +{ + "textureAtlas": "terrain.atlas.json", + "cube": true, + "textures": { + "top": 4, + "left": 5, + "front": 5, + "right": 5, + "back": 5, + "bottom": 4 + }, + "faces": ["ALL"], + "opaqueSides": ["ALL"] +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..610d9ad --- /dev/null +++ b/build.gradle @@ -0,0 +1,40 @@ +buildscript { + repositories { mavenCentral() } +} + +allprojects { + apply plugin: "eclipse" + apply plugin: "idea" + + version = "0.1" + ext.appName = "Tiles³ Basic Example" + ext.gdxVersion = "1.0.0" + + repositories { + mavenCentral() + maven { url "http://oss.sonatype.org/content/repositories/releases/" } + maven { url "http://maven.blarg.ca" } + mavenLocal(); + } +} + +project(":core") { + apply plugin: "java" + + dependencies { + compile "com.badlogicgames.gdx:gdx:$gdxVersion" + compile "ca.blarg.gdx:gdx-toolbox:0.1-SNAPSHOT" + compile "ca.blarg.gdx:gdx-tilemap3d:0.1-SNAPSHOT" + } +} + +project(":desktop") { + apply plugin: "java" + + dependencies { + compile project(":core") + compile "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion" + compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" + compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop" + } +} diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 0000000..f9287d9 --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,9 @@ +apply plugin: "java" + +sourceCompatibility = 1.6 + +sourceSets.main.java.srcDirs = [ "src/main/java/" ] + +eclipse.project { + name = appName + "-core" +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/BasicExampleGameApp.java b/core/src/main/java/ca/blarg/tiles3/basicexample/BasicExampleGameApp.java new file mode 100644 index 0000000..292c4c4 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/BasicExampleGameApp.java @@ -0,0 +1,46 @@ +package ca.blarg.tiles3.basicexample; + +import ca.blarg.gdx.GameApp; +import ca.blarg.gdx.Services; +import ca.blarg.gdx.tilemap3d.assets.TileAssetUtils; +import ca.blarg.gdx.tilemap3d.tilemesh.CollisionShapes; +import com.badlogic.gdx.Application; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.InputMultiplexer; +import com.badlogic.gdx.assets.AssetManager; + +public class BasicExampleGameApp extends GameApp { + public BasicExampleGameApp() { + toggleHeapMemUsageLogging(false); + Gdx.app.setLogLevel(Application.LOG_DEBUG); + } + + @Override + public void onCreate() { + TileAssetUtils.registerLoaders(Services.get(AssetManager.class)); + CollisionShapes.init(); + + InputMultiplexer inputMultiplexer = new InputMultiplexer(); + ContentCache contentCache = new ContentCache(); + + Services.register(this); + Services.register(contentCache); + Services.register(inputMultiplexer); + + Gdx.input.setInputProcessor(inputMultiplexer); + Gdx.input.setCatchBackKey(true); + Gdx.input.setCatchMenuKey(true); + + stateManager.push(GamePlayState.class); + } + + @Override + public void dispose() { + Gdx.input.setInputProcessor(null); + + Services.unregisterAll(); + + super.dispose(); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/ContentCache.java b/core/src/main/java/ca/blarg/tiles3/basicexample/ContentCache.java new file mode 100644 index 0000000..096fcd3 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/ContentCache.java @@ -0,0 +1,43 @@ +package ca.blarg.tiles3.basicexample; + +import ca.blarg.gdx.Services; +import ca.blarg.gdx.graphics.atlas.TextureAtlas; +import ca.blarg.gdx.graphics.atlas.TextureAtlasAnimator; +import ca.blarg.gdx.tilemap3d.tilemesh.TileMeshCollection; +import com.badlogic.gdx.assets.AssetManager; +import com.badlogic.gdx.graphics.g2d.BitmapFont; + +public class ContentCache implements Services.Service { + public BitmapFont font; + + public TileMeshCollection tiles; + public TextureAtlas terrain; + public TextureAtlasAnimator terrainAnimator = new TextureAtlasAnimator(); + + public TextureAtlas player; + + @Override + public void onRegister() { + AssetManager assetManager = Services.get(AssetManager.class); + + assetManager.load("consoleFont.fnt", BitmapFont.class); + + assetManager.load("tiles/terrain.atlas.json", TextureAtlas.class); + assetManager.load("player.atlas.json", TextureAtlas.class); + assetManager.load("tiles/tiles.json", TileMeshCollection.class); + + // normally we would probably want to load asynchronously and show a loading bar of sorts + assetManager.finishLoading(); + + font = assetManager.get("consoleFont.fnt"); + terrain = assetManager.get("tiles/terrain.atlas.json"); + tiles = assetManager.get("tiles/tiles.json"); + player = assetManager.get("player.atlas.json"); + + terrainAnimator.addAllAtlases(assetManager); + } + + @Override + public void onUnregister() { + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/DebugInfoProcess.java b/core/src/main/java/ca/blarg/tiles3/basicexample/DebugInfoProcess.java new file mode 100644 index 0000000..afbb1af --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/DebugInfoProcess.java @@ -0,0 +1,93 @@ +package ca.blarg.tiles3.basicexample; + +import ca.blarg.gdx.GameLooper; +import ca.blarg.tiles3.basicexample.entities.components.PlayerComponent; +import ca.blarg.tiles3.basicexample.entities.components.physics.PhysicsComponent; +import ca.blarg.tiles3.basicexample.entities.components.physics.PositionComponent; +import ca.blarg.tiles3.basicexample.entities.components.rendering.AnimationComponent; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.utils.StringBuilder; +import ca.blarg.gdx.Services; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.gdx.graphics.ExtendedSpriteBatch; +import ca.blarg.gdx.graphics.ViewportContext; +import ca.blarg.gdx.processes.GameProcess; +import ca.blarg.gdx.processes.ProcessManager; +import ca.blarg.tiles3.basicexample.entities.components.StateComponent; + +public class DebugInfoProcess extends GameProcess { + StringBuilder sb = new StringBuilder(1024); + + final ViewportContext viewportContext; + final ExtendedSpriteBatch spriteBatch; + final EntityManager entityManager; + final ContentCache content; + + public DebugInfoProcess(ProcessManager processManager, EventManager eventManager) { + super(processManager, eventManager); + + viewportContext = Services.get(ViewportContext.class); + spriteBatch = Services.get(ExtendedSpriteBatch.class); + entityManager = Services.get(EntityManager.class); + content = Services.get(ContentCache.class); + } + + @Override + public void onRender(float interpolation) { + sb.length = 0; + + GameLooper looper = Services.get(GameLooper.class); + + sb.append("FPS: ").append(Gdx.graphics.getFramesPerSecond()).append(", UD: ").append(looper.getUpdateDelta()) + .append(", PS: ").append(viewportContext.getOrthographicViewport().getUnitsPerPixel()) + .append(" (").append((int)viewportContext.getOrthographicViewport().getWorldWidth()).append('x').append((int)viewportContext.getOrthographicViewport().getWorldHeight()).append(")").append('\n'); + sb.append("CP: ").append(viewportContext.getPerspectiveCamera().position.x).append(',') + .append(viewportContext.getPerspectiveCamera().position.y).append(',') + .append(viewportContext.getPerspectiveCamera().position.z).append('\n'); + + if (entityManager != null) { + Entity player = entityManager.getFirstWith(PlayerComponent.class); + if (player != null) + showEntityInfo(player); + } + + spriteBatch.begin(); + spriteBatch.draw(content.font, 5, viewportContext.getOrthographicViewport().getWorldHeight() - 5, sb, viewportContext.getOrthographicViewport().getUnitsPerPixel()); + spriteBatch.end(); + } + + private void showEntityInfo(Entity entity) { + PositionComponent position = entity.get(PositionComponent.class); + StateComponent state = entity.get(StateComponent.class); + AnimationComponent animation = entity.get(AnimationComponent.class); + PhysicsComponent physics = entity.get(PhysicsComponent.class); + + sb.append("EP: ").append(position.position.x).append(',').append(position.position.y).append(',').append(position.position.z).append('\n'); + + sb.append("EV: ").append(physics.velocity.x).append(',').append(physics.velocity.y).append(',').append(physics.velocity.z).append('\n'); + sb.append("C: ").append(physics.sweptSphere.foundCollision) + .append(" CD: ").append(physics.sweptSphere.nearestCollisionDistance) + .append(" S: ").append(physics.isSliding) + .append(" SA: ").append(MathUtils.radDeg * (float)Math.acos(physics.sweptSphere.slidingPlaneNormal.dot(Vector3.Y))).append('\n'); + sb.append("OG: ").append(physics.isOnGround) + .append(" W: ").append(physics.isWalking).append(" WW: ").append(physics.wasWalking) + .append(" M: ").append(physics.isInMotion).append(" WM: ").append(physics.wasInMotion) + .append(" F: ").append(physics.isFalling).append(" WF: ").append(physics.wasFalling).append('\n'); + sb.append("CP: ").append(physics.nearestIntersection.x).append(',') + .append(physics.nearestIntersection.y).append(',') + .append(physics.nearestIntersection.z).append('\n'); + + sb.append("ES: ").append(state.state).append(" (").append(state.isInActionState).append(" - ").append(state.actionState).append(')').append('\n'); + + sb.append("AS: ").append(animation.currentSequenceName) + .append(" (T: ").append(animation.frameTime).append(", CF: ").append(animation.currentFrame).append(", NF: ").append(animation.nextFrame) + .append(" (FS: ").append(animation.currentSequence.start).append(", FE: ").append(animation.currentSequence.stop).append(")") + .append(" L: ").append(animation.isLooping).append(", UO: ").append(animation.currentSequenceUnoverrideable).append(")") + .append(" A: ").append(animation.isAnimating()).append(", F: ").append(animation.isAnimationFinished()).append('\n'); + + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/GamePlayState.java b/core/src/main/java/ca/blarg/tiles3/basicexample/GamePlayState.java new file mode 100644 index 0000000..06d356e --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/GamePlayState.java @@ -0,0 +1,183 @@ +package ca.blarg.tiles3.basicexample; + +import ca.blarg.gdx.Services; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.gdx.graphics.EulerPerspectiveCamera; +import ca.blarg.gdx.graphics.GraphicsHelpers; +import ca.blarg.gdx.graphics.ViewportContext; +import ca.blarg.gdx.graphics.screeneffects.DimScreenEffect; +import ca.blarg.gdx.graphics.screeneffects.ScreenEffectHelpers; +import ca.blarg.gdx.math.MathHelpers; +import ca.blarg.gdx.states.GameState; +import ca.blarg.gdx.states.StateManager; +import ca.blarg.tiles3.basicexample.entities.components.PlayerComponent; +import ca.blarg.tiles3.basicexample.entities.components.WorldComponent; +import ca.blarg.tiles3.basicexample.entities.events.JumpEvent; +import ca.blarg.tiles3.basicexample.entities.events.MoveAndTurnEvent; +import ca.blarg.tiles3.basicexample.entities.presets.PlayerPreset; +import ca.blarg.tiles3.basicexample.entities.subsystems.*; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.graphics.g3d.ModelBatch; +import com.badlogic.gdx.math.Vector2; + +public class GamePlayState extends GameState { + final ViewportContext viewportContext; + final ModelBatch modelBatch; + final ContentCache content; + + EulerPerspectiveCamera camera; + EntityManager entityManager; + + public GamePlayState(StateManager stateManager, EventManager eventManager) { + super(stateManager, eventManager); + viewportContext = Services.get(ViewportContext.class); + modelBatch = Services.get(ModelBatch.class); + content = Services.get(ContentCache.class); + } + + @Override + public void dispose() { + super.dispose(); + } + + @Override + public void onPush() { + camera = new EulerPerspectiveCamera(60.0f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + camera.moveTo(12.0f, 10.0f, 16.0f); + camera.pitch(35.0f); + gameApp.viewportContext.setPerspectiveCamera(camera); + + entityManager = new EntityManager(eventManager); + Services.register(entityManager); + + initEntitySystem(); + + // world entity + Entity worldEntity = entityManager.add(); + WorldComponent world = worldEntity.add(WorldComponent.class); + world.tileMap = Level.create(); + world.renderGrid = false; + world.renderSkybox = false; + + entityManager.addUsingPreset(PlayerPreset.class); + + processManager.add(DebugInfoProcess.class); + } + + @Override + public void onPop() { + viewportContext.setDefaultPerspectiveCamera(); + Services.unregister(EntityManager.class); + entityManager.dispose(); + } + + @Override + public void onRender(float interpolation) { + GraphicsHelpers.clear(0.0f, 0.0f, 0.0f, 1.0f); + + entityManager.onRender(interpolation); + super.onRender(interpolation); + } + + final static Vector2 moveDirection = new Vector2(); + @Override + public void onUpdateGameState(float delta) { + super.onUpdateGameState(delta); + if (isTransitioning() || !isTopState()) + return; + + if (Gdx.input.isKeyPressed(Input.Keys.ESCAPE) || Gdx.input.isKeyPressed(Input.Keys.BACK)) + setFinished(); + + Entity player = entityManager.getFirstWith(PlayerComponent.class); + + if (player != null) { + float referenceAngle = ((EulerPerspectiveCamera)gameApp.viewportContext.getPerspectiveCamera()).getYaw(); + + moveDirection.set(Vector2.Zero); + if (Gdx.input.isKeyPressed(Input.Keys.W) || Gdx.input.isKeyPressed(Input.Keys.UP)) + moveDirection.y -= 1.0f; + if (Gdx.input.isKeyPressed(Input.Keys.S) || Gdx.input.isKeyPressed(Input.Keys.DOWN)) + moveDirection.y += 1.0f; + if (Gdx.input.isKeyPressed(Input.Keys.A) || Gdx.input.isKeyPressed(Input.Keys.LEFT)) + moveDirection.x -= 1.0f; + if (Gdx.input.isKeyPressed(Input.Keys.D) || Gdx.input.isKeyPressed(Input.Keys.RIGHT)) + moveDirection.x += 1.0f; + + if (moveDirection.len2() != 0.0f) { + moveDirection.nor(); + moveDirection.rotate(referenceAngle); + + MoveAndTurnEvent moveEvent = eventManager.create(MoveAndTurnEvent.class); + moveEvent.entity = player; + moveEvent.angle = MathHelpers.getAngleFromPointOnCircle(moveDirection.x, moveDirection.y); + eventManager.queue(moveEvent); + } + + if (Gdx.input.isKeyPressed(Input.Keys.SPACE)) { + JumpEvent jumpEvent = eventManager.create(JumpEvent.class); + jumpEvent.entity = player; + eventManager.queue(jumpEvent); + } + } + + entityManager.onUpdateGameState(delta); + } + + @Override + public void onUpdateFrame(float delta) { + super.onUpdateFrame(delta); + if (isTransitioning() || !isTopState()) + return; + + content.terrainAnimator.onUpdate(delta); + entityManager.onUpdateFrame(delta); + } + + @Override + public void onAppPause() { + super.onAppPause(); + entityManager.onAppPause(); + } + + @Override + public void onAppResume() { + super.onAppResume(); + entityManager.onAppResume(); + } + + @Override + public void onPause(boolean dueToOverlay) { + super.onPause(dueToOverlay); + if (dueToOverlay) + effectManager.add(DimScreenEffect.class); + } + + @Override + public void onResume(boolean fromOverlay) { + super.onResume(fromOverlay); + if (fromOverlay) + effectManager.remove(DimScreenEffect.class); + } + + @Override + public boolean onTransition(float delta, boolean isTransitioningOut, boolean started) { + return ScreenEffectHelpers.doFadingTransition(effectManager, isTransitioningOut, started); + } + + private void initEntitySystem() { + entityManager.addSubsystem(PreviousPositionSystem.class); + entityManager.addSubsystem(PhysicsSystem.class); + entityManager.addSubsystem(BoundingVolumeWorldPositioningSystem.class); + entityManager.addSubsystem(EntityStateSystem.class); + entityManager.addSubsystem(CameraSystem.class); + entityManager.addSubsystem(AnimationSystem.class); + entityManager.addSubsystem(WorldRendererSystem.class); + entityManager.addSubsystem(BillboardRenderSystem.class); + + entityManager.addPreset(PlayerPreset.class); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/Level.java b/core/src/main/java/ca/blarg/tiles3/basicexample/Level.java new file mode 100644 index 0000000..c1263e4 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/Level.java @@ -0,0 +1,66 @@ +package ca.blarg.tiles3.basicexample; + +import ca.blarg.gdx.Services; +import ca.blarg.gdx.tilemap3d.Tile; +import ca.blarg.gdx.tilemap3d.TileMap; +import ca.blarg.gdx.tilemap3d.lighting.LightSpreadingTileMapLighter; +import ca.blarg.gdx.tilemap3d.lighting.LitChunkVertexGenerator; +import ca.blarg.gdx.tilemap3d.prefabs.TilePrefab; + +public class Level { + public static TileMap create() { + ContentCache content = Services.get(ContentCache.class); + + TileMap map = new TileMap( + 16, 16, 16, + 2, 1, 2, + content.tiles, + new LitChunkVertexGenerator(), + new LightSpreadingTileMapLighter()); + + /* + 0 empty + 1 grass + 2 grass+dirt + 3 dirt + 4 leaves + 5 trunk + 6 planks + 7 stone brick + 8 stone floor tile + 9 sand + */ + + for (int x = 0; x < map.getWidth(); ++x) { + for (int z = 0; z < map.getDepth(); ++z) { + if (x == 0 || z == 0 || x == map.getWidth() - 1 || z == map.getDepth() - 1) { + map.get(x, 0, z).set(7, Tile.FLAG_COLLIDEABLE); + map.get(x, 1, z).set(7, Tile.FLAG_COLLIDEABLE); + map.get(x, 2, z).set(7, Tile.FLAG_COLLIDEABLE); + } else + map.get(x, 0, z).set(2, Tile.FLAG_COLLIDEABLE); + + } + } + + for (int x = 10; x < map.getWidth() - 10; x += 7) { + Prefabs.tree.placeIn(map, x, 1, 3, TilePrefab.Rotation.ROT0, false); + Prefabs.tree.placeIn(map, x, 1, map.getDepth() - 3 - Prefabs.tree.getDepth(), TilePrefab.Rotation.ROT0, false); + } + + for (int z = 10; z < map.getWidth() - 10; z += 7) { + Prefabs.tree.placeIn(map, 3, 1, z, TilePrefab.Rotation.ROT0, false); + Prefabs.tree.placeIn(map, map.getWidth() - 3 - Prefabs.tree.getWidth(), 1, z, TilePrefab.Rotation.ROT0, false); + } + + Prefabs.tree.placeIn(map, 4, 1, 4, TilePrefab.Rotation.ROT0, false); + Prefabs.tree.placeIn(map, map.getWidth() - 4 - Prefabs.tree.getWidth(), 1, 4, TilePrefab.Rotation.ROT0, false); + Prefabs.tree.placeIn(map, 4, 1, map.getDepth() - 4 - Prefabs.tree.getDepth(), TilePrefab.Rotation.ROT0, false); + Prefabs.tree.placeIn(map, map.getWidth() - 4 - Prefabs.tree.getWidth(), 1, map.getDepth() - 4 - Prefabs.tree.getDepth(), TilePrefab.Rotation.ROT0, false); + + map.updateLighting(); + map.updateVertices(); + + return map; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/Prefabs.java b/core/src/main/java/ca/blarg/tiles3/basicexample/Prefabs.java new file mode 100644 index 0000000..977af5a --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/Prefabs.java @@ -0,0 +1,38 @@ +package ca.blarg.tiles3.basicexample; + +import ca.blarg.gdx.tilemap3d.Tile; +import ca.blarg.gdx.tilemap3d.prefabs.TilePrefab; + +public class Prefabs { + public static final TilePrefab tree; + + static { + tree = new TilePrefab(5, 6, 5); + + for (int x = 0; x < 5; ++x) + for (int z = 0; z < 5; ++z) + tree.get(x, 2, z).set(4, Tile.FLAG_COLLIDEABLE); + tree.get(0, 2, 0).set(0, Tile.FLAG_COLLIDEABLE); + tree.get(4, 2, 0).set(0, Tile.FLAG_COLLIDEABLE); + tree.get(0, 2, 4).set(0, Tile.FLAG_COLLIDEABLE); + tree.get(4, 2, 4).set(0, Tile.FLAG_COLLIDEABLE); + + for (int x = 1; x < 4; ++x) + for (int z = 1; z < 4; ++z) + tree.get(x, 3, z).set(4, Tile.FLAG_COLLIDEABLE); + tree.get(0, 3, 2).set(4, Tile.FLAG_COLLIDEABLE); + tree.get(4, 3, 2).set(4, Tile.FLAG_COLLIDEABLE); + tree.get(2, 3, 0).set(4, Tile.FLAG_COLLIDEABLE); + tree.get(2, 3, 4).set(4, Tile.FLAG_COLLIDEABLE); + + tree.get(2, 4, 2).set(4, Tile.FLAG_COLLIDEABLE); + tree.get(1, 4, 2).set(4, Tile.FLAG_COLLIDEABLE); + tree.get(3, 4, 2).set(4, Tile.FLAG_COLLIDEABLE); + tree.get(2, 4, 1).set(4, Tile.FLAG_COLLIDEABLE); + tree.get(2, 4, 3).set(4, Tile.FLAG_COLLIDEABLE); + + tree.get(2, 0, 2).set(5, Tile.FLAG_COLLIDEABLE); + tree.get(2, 1, 2).set(5, Tile.FLAG_COLLIDEABLE); + tree.get(2, 2, 2).set(5, Tile.FLAG_COLLIDEABLE); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/AnimationSequence.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/AnimationSequence.java new file mode 100644 index 0000000..982560d --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/AnimationSequence.java @@ -0,0 +1,61 @@ +package ca.blarg.tiles3.basicexample.entities; + +import com.badlogic.gdx.utils.Pool; + +public class AnimationSequence implements Pool.Poolable { + public int start; + public int stop; + public float delay; + public boolean isMultiDirectional; + public boolean hasDiagonalDirections; + public int directionFrameOffset; + + public AnimationSequence set(int start, int stop, float delay) { + this.start = start; + this.stop = stop; + this.delay = delay; + isMultiDirectional = false; + hasDiagonalDirections = false; + directionFrameOffset = 0; + return this; + } + + public AnimationSequence set(int start, int stop, float delay, boolean isMultiDirectional, int directionFrameOffset) { + this.start = start; + this.stop = stop; + this.delay = delay; + this.isMultiDirectional = isMultiDirectional; + this.hasDiagonalDirections = false; + this.directionFrameOffset = directionFrameOffset; + return this; + } + + public AnimationSequence set(int start, int stop, float delay, boolean isMultiDirectional, boolean hasDiagonalDirections, int directionFrameOffset) { + this.start = start; + this.stop = stop; + this.delay = delay; + this.isMultiDirectional = isMultiDirectional; + this.hasDiagonalDirections = hasDiagonalDirections; + this.directionFrameOffset = directionFrameOffset; + return this; + } + + public AnimationSequence set(AnimationSequence other) { + start = other.start; + stop = other.stop; + delay = other.delay; + isMultiDirectional = other.isMultiDirectional; + hasDiagonalDirections = other.hasDiagonalDirections; + directionFrameOffset = other.directionFrameOffset; + return this; + } + + public void reset() { + start = 0; + stop = 0; + delay = 0.0f; + isMultiDirectional = false; + hasDiagonalDirections = false; + directionFrameOffset = 0; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/AnimationSequenceCollection.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/AnimationSequenceCollection.java new file mode 100644 index 0000000..7c11012 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/AnimationSequenceCollection.java @@ -0,0 +1,31 @@ +package ca.blarg.tiles3.basicexample.entities; + +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.utils.Pools; + +public class AnimationSequenceCollection implements Pool.Poolable { + final ObjectMap sequences = new ObjectMap(); + + public AnimationSequenceCollection() { + } + + public AnimationSequence add(String key) { + if (sequences.containsKey(key)) + throw new UnsupportedOperationException("Sequence with that name present in collection already"); + AnimationSequence sequence = Pools.obtain(AnimationSequence.class); + sequences.put(key, sequence); + return sequence; + } + + public AnimationSequence get(String key) { + return sequences.get(key); + } + + @Override + public void reset() { + for (ObjectMap.Entry i : sequences.entries()) + Pools.free(i.value); + sequences.clear(); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/EntityState.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/EntityState.java new file mode 100644 index 0000000..cafebeb --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/EntityState.java @@ -0,0 +1,14 @@ +package ca.blarg.tiles3.basicexample.entities; + +public enum EntityState { + None, + Idle, + Walking, + Falling, + Jumping, + Dead, + Punching, + Shooting, + Open, + Inactive +} \ No newline at end of file diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/EntityUtils.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/EntityUtils.java new file mode 100644 index 0000000..37eea84 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/EntityUtils.java @@ -0,0 +1,13 @@ +package ca.blarg.tiles3.basicexample.entities; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.tiles3.basicexample.entities.components.physics.PositionComponent; +import com.badlogic.gdx.math.Vector3; + +public class EntityUtils { + public static void getInterpolatedPosition(Entity entity, float alpha, Vector3 outPosition) { + PositionComponent position = entity.get(PositionComponent.class); + outPosition.set(position.lastPosition); + outPosition.lerp(position.position, alpha); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/FacingDirection.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/FacingDirection.java new file mode 100644 index 0000000..6dd184e --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/FacingDirection.java @@ -0,0 +1,85 @@ +package ca.blarg.tiles3.basicexample.entities; + +import com.badlogic.gdx.math.Vector3; +import ca.blarg.gdx.math.MathHelpers; + +public enum FacingDirection { + North(0), + NorthEast(1), + East(2), + SouthEast(3), + South(4), + SouthWest(5), + West(6), + NorthWest(7); + + int value; + + private FacingDirection(int value) { + this.value = value; + } + + public int value() { + return value; + } + + /** + * the enum values and static angle constants are set such that: + * + * 1. east = +x direction + * west = -x direction + * north = -z direction + * south = +z direction + * + * 2. enum.value() * MULTIPLIER = the corresponding static angle constant for + * the enum's direction + * (e.g. FacingDirection.East.value() * MULTIPLIER == FacingDirection.EAST_ANGLE) + */ + + public static float MULTIPLIER = 45.0f; + + public static float NORTH_ANGLE = 0.0f; + public static float NORTHEAST_ANGLE = 45.0f; + public static float EAST_ANGLE = 90.0f; + public static float SOUTHEAST_ANGLE = 135.0f; + public static float SOUTH_ANGLE = 180.0f; + public static float SOUTHWEST_ANGLE = 225.0f; + public static float WEST_ANGLE = 270.0f; + public static float NORTHWEST_ANGLE = 315.0f; + + public static FacingDirection getFacingDirection(float orientationY, boolean limitToQuadrantDirections) { + final float QUADRANT_ANGLE_ADJUSTMENT = 45.0f; + final float QUADRANT_DIRECTION_DIVISOR = 90.0f; + final float OCTANT_ANGLE_ADJUSTMENT = QUADRANT_ANGLE_ADJUSTMENT / 2.0f; + final float OCTANT_DIRECTION_DIVISOR = 45.0f; + + float adjustment = limitToQuadrantDirections ? QUADRANT_ANGLE_ADJUSTMENT : OCTANT_ANGLE_ADJUSTMENT; + float divisor = limitToQuadrantDirections ? QUADRANT_DIRECTION_DIVISOR : OCTANT_DIRECTION_DIVISOR; + + // add 45 degrees initially to kind of "rotate" the 360 degree circle + // clockwise because 315 -> 360 is also part of the "east" quadrant along + // with 0 -> 45. + float adjusted = (orientationY) + adjustment; + if (adjusted >= 360.0f) + adjusted -= 360.0f; + + return FacingDirection.values()[(int)(adjusted / divisor)]; + } + + public static FacingDirection getFacingDirection(Vector3 orientation, boolean limitToQuadrantDirections) { + return getFacingDirection(orientation.y, limitToQuadrantDirections); + } + + public static FacingDirection getFacingDirectionAdjustedForCamera(float objectOrientationY, float cameraYaw, boolean limitToQuadrantDirections) { + // HACK: this pretty much means I should probably just forget about 'adjusting' the angles by 90.0f and such + // and just use the "native" orientations... + float hackyAdjustedObjectOrientationY = objectOrientationY + 180.0f; + + float difference = MathHelpers.rolloverClamp(hackyAdjustedObjectOrientationY - cameraYaw, 0.0f, 360.0f); + return getFacingDirection(difference, limitToQuadrantDirections); + } + + public static float getFacingAngle(FacingDirection direction) { + return ((float)direction.ordinal()) * MULTIPLIER; + } +} \ No newline at end of file diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/PhysicsConstants.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/PhysicsConstants.java new file mode 100644 index 0000000..8f15b0f --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/PhysicsConstants.java @@ -0,0 +1,15 @@ +package ca.blarg.tiles3.basicexample.entities; + +import com.badlogic.gdx.math.Vector3; + +public final class PhysicsConstants { + public static final float UNITS_PER_METRE = 1.0f; + + public static final float GRAVITY_SPEED = 9.8f * UNITS_PER_METRE; + public static final Vector3 GRAVITY = new Vector3(0.0f, -GRAVITY_SPEED, 0.0f); + + public static final float FRICTION_NORMAL = 0.5f; + + public static final float SLOPE_STEEP_Y_ANGLE = 46; + public static final float ON_GROUND_ZERO_TOLERANCE = 0.1f; +} \ No newline at end of file diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/CameraComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/CameraComponent.java new file mode 100644 index 0000000..cd34468 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/CameraComponent.java @@ -0,0 +1,14 @@ +package ca.blarg.tiles3.basicexample.entities.components; + +import ca.blarg.gdx.entities.Component; + +public class CameraComponent extends Component { + public boolean useEntityOrientation; + public float yAngle; + + @Override + public void reset() { + useEntityOrientation = false; + yAngle = 0.0f; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/PlayerComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/PlayerComponent.java new file mode 100644 index 0000000..6bd19d4 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/PlayerComponent.java @@ -0,0 +1,9 @@ +package ca.blarg.tiles3.basicexample.entities.components; + +import ca.blarg.gdx.entities.Component; + +public class PlayerComponent extends Component { + @Override + public void reset() { + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/StateComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/StateComponent.java new file mode 100644 index 0000000..d5e0861 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/StateComponent.java @@ -0,0 +1,17 @@ +package ca.blarg.tiles3.basicexample.entities.components; + +import ca.blarg.gdx.entities.Component; +import ca.blarg.tiles3.basicexample.entities.EntityState; + +public class StateComponent extends Component { + public EntityState state; + public EntityState actionState; + public boolean isInActionState; + + @Override + public void reset() { + state = EntityState.None; + actionState = EntityState.None; + isInActionState = false; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/WorldComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/WorldComponent.java new file mode 100644 index 0000000..0da1eff --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/WorldComponent.java @@ -0,0 +1,17 @@ +package ca.blarg.tiles3.basicexample.entities.components; + +import ca.blarg.gdx.entities.Component; +import ca.blarg.gdx.tilemap3d.TileMap; + +public class WorldComponent extends Component { + public TileMap tileMap; + public boolean renderGrid; + public boolean renderSkybox; + + @Override + public void reset() { + tileMap = null; + renderGrid = false; + renderSkybox = false; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/BoundingSphereComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/BoundingSphereComponent.java new file mode 100644 index 0000000..c96fad5 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/BoundingSphereComponent.java @@ -0,0 +1,15 @@ +package ca.blarg.tiles3.basicexample.entities.components.physics; + +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.Sphere; +import ca.blarg.gdx.entities.Component; + +public class BoundingSphereComponent extends Component { + public final Sphere bounds = new Sphere(Vector3.Zero, 0.0f); + + @Override + public void reset() { + bounds.center.set(Vector3.Zero); + bounds.radius = 0.0f; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/OrientationXZComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/OrientationXZComponent.java new file mode 100644 index 0000000..e505bce --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/OrientationXZComponent.java @@ -0,0 +1,18 @@ +package ca.blarg.tiles3.basicexample.entities.components.physics; + +import com.badlogic.gdx.math.Vector3; +import ca.blarg.gdx.entities.Component; +import ca.blarg.gdx.math.MathHelpers; + +public class OrientationXZComponent extends Component { + public float angle = 0.0f; + + public void getDirectionVector(Vector3 out) { + MathHelpers.getDirectionVector3FromYAxis(angle, out); + } + + @Override + public void reset() { + angle = 0.0f; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/PhysicsComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/PhysicsComponent.java new file mode 100644 index 0000000..e3e7aed --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/PhysicsComponent.java @@ -0,0 +1,95 @@ +package ca.blarg.tiles3.basicexample.entities.components.physics; + +import ca.blarg.gdx.entities.Component; +import ca.blarg.gdx.math.SweptSphere; +import ca.blarg.gdx.tilemap3d.Tile; +import ca.blarg.gdx.tilemap3d.TileCoord; +import ca.blarg.tiles3.basicexample.entities.forces.Force; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.Ray; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Pools; + +public class PhysicsComponent extends Component { + public final Vector3 velocity = new Vector3(); + public final Vector3 lastTickVelocity = new Vector3(); + public final Vector3 currentTickVelocity = new Vector3(); + + public final Vector3 walkingVelocity = new Vector3(); + public float walkingAcceleration; + public float maxWalkSpeed; + public float friction; + + public final Array forces = new Array(false, 4, Force.class); + public final Vector3 forceVelocity = new Vector3(); + + public final SweptSphere sweptSphere = new SweptSphere(); + public final TileCoord collisionTilePosition = new TileCoord(); + public final TileCoord standingOnTilePosition = new TileCoord(); + public Tile standingOnTile; + + public boolean isWalking; + public boolean wasWalking; + public boolean isInMotion; + public boolean wasInMotion; + public boolean isFalling; + public boolean wasFalling; + public boolean isOnGround; + public boolean wasOnGround; + public boolean isSliding; + public boolean wasSliding; + + public float fallDistance; + public float lastYPosition; + public float currentFallDistance; + + public final Ray slidingPlaneNormal = new Ray(Vector3.Zero, Vector3.Zero); + public final Vector3 nearestIntersection = new Vector3(); + + public PhysicsComponent setMovementProperties(float walkingAcceleration, float maxWalkSpeed, float friction) { + this.walkingAcceleration = walkingAcceleration; + this.maxWalkSpeed = maxWalkSpeed; + this.friction = friction; + return this; + } + + public PhysicsComponent setBoundsRadius(float radius) { + sweptSphere.setRadius(radius); + return this; + } + + @Override + public void reset() { + velocity.set(Vector3.Zero); + lastTickVelocity.set(Vector3.Zero); + currentTickVelocity.set(Vector3.Zero); + walkingVelocity.set(Vector3.Zero); + walkingAcceleration = 0.0f; + maxWalkSpeed = 0.0f; + friction = 0.0f; + for (int i = 0; i < forces.size; ++i) + Pools.free(forces.get(i)); + forces.clear(); + forceVelocity.set(Vector3.Zero); + sweptSphere.reset(); + collisionTilePosition.set(0, 0, 0); + standingOnTilePosition.set(0, 0, 0); + standingOnTile = null; + isWalking = false; + wasWalking = false; + isInMotion = false; + wasInMotion = false; + isFalling = false; + wasFalling = false; + isOnGround = false; + wasOnGround = false; + isSliding = false; + wasSliding = false; + fallDistance = 0.0f; + lastYPosition = 0.0f; + currentFallDistance = 0.0f; + slidingPlaneNormal.origin.set(Vector3.Zero); + slidingPlaneNormal.direction.set(Vector3.Zero); + nearestIntersection.set(Vector3.Zero); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/PositionComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/PositionComponent.java new file mode 100644 index 0000000..03c168f --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/physics/PositionComponent.java @@ -0,0 +1,15 @@ +package ca.blarg.tiles3.basicexample.entities.components.physics; + +import com.badlogic.gdx.math.Vector3; +import ca.blarg.gdx.entities.Component; + +public class PositionComponent extends Component { + public final Vector3 position = new Vector3(); + public final Vector3 lastPosition = new Vector3(); + + @Override + public void reset() { + position.set(Vector3.Zero); + lastPosition.set(Vector3.Zero); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/rendering/AnimationComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/rendering/AnimationComponent.java new file mode 100644 index 0000000..8267d45 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/rendering/AnimationComponent.java @@ -0,0 +1,84 @@ +package ca.blarg.tiles3.basicexample.entities.components.rendering; + +import ca.blarg.gdx.entities.Component; +import ca.blarg.tiles3.basicexample.entities.AnimationSequence; +import ca.blarg.tiles3.basicexample.entities.AnimationSequenceCollection; + +public class AnimationComponent extends Component { + public final AnimationSequenceCollection sequences = new AnimationSequenceCollection(); + public int currentFrame; + public int nextFrame; + public float frameTime; + public float interpolation; + public boolean isLooping; + public boolean currentSequenceUnoverrideable; + public boolean hasAnimationFinishEventBeenTriggered; + public String currentSequenceName; + public final AnimationSequence currentSequence = new AnimationSequence(); + + public boolean isAnimating() { + return (currentSequence.start != currentSequence.stop); + } + + public boolean isAnimationFinished() { + // let the last frame of a non-looping animation (even a 1-frame + // "animation") stay visible for at least the sequence's delay time as long + // as it's non-zero + if (!isLooping && currentFrame == currentSequence.stop && currentSequence.delay > 0.0f) { + if (frameTime < currentSequence.delay) + return false; // still working through the last frame time + else + return true; + + } + + return (isAnimating() && !isLooping && currentFrame == currentSequence.stop); + } + + public AnimationComponent setSequence(String name, boolean loop, boolean cannotBeOverridden) { + AnimationSequence sequence = sequences.get(name); + if (sequence == null) + return this; + + currentSequence.set(sequence); + currentSequenceName = name; + + currentFrame = currentSequence.start; + nextFrame = currentFrame + 1; + if (nextFrame > currentSequence.stop) + nextFrame = currentSequence.stop; + + frameTime = 0.0f; + interpolation = 0.0f; + isLooping = loop; + currentSequenceUnoverrideable = cannotBeOverridden; + hasAnimationFinishEventBeenTriggered = false; + return this; + } + + public AnimationComponent stopSequence() { + currentSequence.reset(); + currentFrame = 0; + nextFrame = 0; + frameTime = 0.0f; + interpolation = 0.0f; + isLooping = false; + currentSequenceUnoverrideable = false; + hasAnimationFinishEventBeenTriggered = false; + return this; + } + + @Override + public void reset() { + sequences.reset(); + currentFrame = 0; + nextFrame = 0; + frameTime = 0.0f; + interpolation = 0.0f; + isLooping = false; + currentSequenceUnoverrideable = false; + hasAnimationFinishEventBeenTriggered = false; + currentSequenceName = null; + currentSequence.reset(); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/rendering/AutoAnimationComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/rendering/AutoAnimationComponent.java new file mode 100644 index 0000000..7e7c516 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/rendering/AutoAnimationComponent.java @@ -0,0 +1,29 @@ +package ca.blarg.tiles3.basicexample.entities.components.rendering; + +import com.badlogic.gdx.utils.ObjectMap; +import ca.blarg.gdx.entities.Component; +import ca.blarg.tiles3.basicexample.entities.EntityState; + +public class AutoAnimationComponent extends Component { + public class AutoAnimationSequenceProperties { + public String name; + public boolean loop; + public boolean cannotBeOverridden; + } + + public final ObjectMap mappings = new ObjectMap(); + + public AutoAnimationComponent set(EntityState state, String name, boolean loop, boolean cannotBeOverridden) { + AutoAnimationSequenceProperties props = new AutoAnimationSequenceProperties(); + props.name = name; + props.loop = loop; + props.cannotBeOverridden = cannotBeOverridden; + mappings.put(state, props); + return this; + } + + @Override + public void reset() { + mappings.clear(); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/rendering/BillboardComponent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/rendering/BillboardComponent.java new file mode 100644 index 0000000..67ba511 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/components/rendering/BillboardComponent.java @@ -0,0 +1,43 @@ +package ca.blarg.tiles3.basicexample.entities.components.rendering; + +import com.badlogic.gdx.graphics.Texture; +import ca.blarg.gdx.entities.Component; +import ca.blarg.gdx.graphics.atlas.TextureAtlas; + +public class BillboardComponent extends Component { + public int tileIndex; + public float width; + public float height; + public boolean isAxisAligned; + public Texture texture; + public TextureAtlas atlas; + + public BillboardComponent set(float width, float height, Texture texture, boolean isAxisAligned) { + this.width = width; + this.height = height; + this.texture = texture; + this.isAxisAligned = isAxisAligned; + this.atlas = null; + return this; + } + + public BillboardComponent set(float width, float height, TextureAtlas atlas, int tileIndex, boolean isAxisAligned) { + this.width = width; + this.height = height; + this.atlas = atlas; + this.tileIndex = tileIndex; + this.isAxisAligned = isAxisAligned; + this.texture = null; + return this; + } + + @Override + public void reset() { + tileIndex = 0; + width = 0.0f; + height = 0.0f; + isAxisAligned = false; + texture = null; + atlas = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/AnimationChangeEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/AnimationChangeEvent.java new file mode 100644 index 0000000..a0e2da2 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/AnimationChangeEvent.java @@ -0,0 +1,24 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; +import ca.blarg.tiles3.basicexample.entities.EntityState; + +public class AnimationChangeEvent extends Event { + public Entity entity; + public String sequenceName; + public boolean changeToSequenceForState; + public EntityState state; + public boolean loop; + public boolean overrideExisting; + + @Override + public void reset() { + entity = null; + sequenceName = null; + changeToSequenceForState = false; + state = EntityState.None; + loop = false; + overrideExisting = false; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/AnimationFinishedEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/AnimationFinishedEvent.java new file mode 100644 index 0000000..50c733b --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/AnimationFinishedEvent.java @@ -0,0 +1,13 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class AnimationFinishedEvent extends Event { + public Entity entity; + + @Override + public void reset() { + entity = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/DespawnedEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/DespawnedEvent.java new file mode 100644 index 0000000..c7e303f --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/DespawnedEvent.java @@ -0,0 +1,13 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class DespawnedEvent extends Event { + public Entity entity; + + @Override + public void reset() { + entity = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/EntityExitingTempStateEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/EntityExitingTempStateEvent.java new file mode 100644 index 0000000..df7f880 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/EntityExitingTempStateEvent.java @@ -0,0 +1,13 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class EntityExitingTempStateEvent extends Event { + public Entity entity; + + @Override + public void reset() { + entity = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/EntityInTempStateEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/EntityInTempStateEvent.java new file mode 100644 index 0000000..a2ec3a2 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/EntityInTempStateEvent.java @@ -0,0 +1,13 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class EntityInTempStateEvent extends Event { + public Entity entity; + + @Override + public void reset() { + entity = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/EntityStateChangeEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/EntityStateChangeEvent.java new file mode 100644 index 0000000..c42b22f --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/EntityStateChangeEvent.java @@ -0,0 +1,20 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; +import ca.blarg.tiles3.basicexample.entities.EntityState; + +public class EntityStateChangeEvent extends Event { + public Entity entity; + public EntityState state; + public boolean isActionState; + public boolean clearExistingActionStateInfo; + + @Override + public void reset() { + entity = null; + state = EntityState.None; + isActionState = false; + clearExistingActionStateInfo = false; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/JumpEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/JumpEvent.java new file mode 100644 index 0000000..00616b8 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/JumpEvent.java @@ -0,0 +1,13 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class JumpEvent extends Event { + public Entity entity; + + @Override + public void reset() { + entity = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/MoveAndTurnEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/MoveAndTurnEvent.java new file mode 100644 index 0000000..b36432b --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/MoveAndTurnEvent.java @@ -0,0 +1,15 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class MoveAndTurnEvent extends Event { + public Entity entity; + public float angle; + + @Override + public void reset() { + entity = null; + angle = 0.0f; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/MoveForwardEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/MoveForwardEvent.java new file mode 100644 index 0000000..bbe3082 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/MoveForwardEvent.java @@ -0,0 +1,13 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class MoveForwardEvent extends Event { + public Entity entity; + + @Override + public void reset() { + entity = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/MoveInDirectionEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/MoveInDirectionEvent.java new file mode 100644 index 0000000..317d9fb --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/MoveInDirectionEvent.java @@ -0,0 +1,16 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import com.badlogic.gdx.math.Vector3; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class MoveInDirectionEvent extends Event { + public Entity entity; + public final Vector3 direction = new Vector3(); + + @Override + public void reset() { + entity = null; + direction.set(Vector3.Zero); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/SpawnedEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/SpawnedEvent.java new file mode 100644 index 0000000..755013e --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/SpawnedEvent.java @@ -0,0 +1,13 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class SpawnedEvent extends Event { + public Entity entity; + + @Override + public void reset() { + entity = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/StopMovementEvent.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/StopMovementEvent.java new file mode 100644 index 0000000..2ada7e9 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/events/StopMovementEvent.java @@ -0,0 +1,13 @@ +package ca.blarg.tiles3.basicexample.entities.events; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.events.Event; + +public class StopMovementEvent extends Event { + public Entity entity; + + @Override + public void reset() { + entity = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/forces/Force.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/forces/Force.java new file mode 100644 index 0000000..0a28555 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/forces/Force.java @@ -0,0 +1,87 @@ +package ca.blarg.tiles3.basicexample.entities.forces; + +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.utils.Pool; +import ca.blarg.gdx.math.MathHelpers; + +public class Force implements Pool.Poolable { + boolean active; + final Vector3 direction = new Vector3(); + float strength; + float friction; + final Vector3 currentForce = new Vector3(); + float duration; + float minDuration; + float zeroTolerance = MathHelpers.EPSILON; + + final Vector3 tempCurrentForce = new Vector3(); + + public Force set(Vector3 direction, float strength, float friction) { + active = true; + this.direction.set(direction); + this.strength = strength; + this.friction = friction; + currentForce.set(Vector3.Zero); + duration = 0.0f; + minDuration = 0.0f; + return this; + } + + public Force setMinimumDuration(float minDuration) { + this.minDuration = minDuration; + return this; + } + + public Force setZeroTolerance(float tolerance) { + this.zeroTolerance = tolerance; + return this; + } + + public boolean isActive() { + return active; + } + + public boolean isZeroStrength() { + return MathHelpers.areAlmostEqual(strength, 0.0f, zeroTolerance); + } + + public boolean hasMinDurationPassed() { + return duration >= minDuration; + } + + public Vector3 getCurrentForce() { + tempCurrentForce.set(currentForce); + return tempCurrentForce; + } + + public void kill() { + active = false; + currentForce.set(Vector3.Zero); + } + + public void update(float delta) { + if (!active) + return; + + if (isZeroStrength()) { + kill(); + return; + } + + duration += delta; + currentForce.set(direction).scl(strength); + strength *= friction; + } + + @Override + public void reset() { + active = false; + direction.set(Vector3.Zero); + strength = 0.0f; + friction = 0.0f; + currentForce.set(Vector3.Zero); + duration = 0.0f; + minDuration = 0.0f; + zeroTolerance = MathHelpers.EPSILON; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/forces/JumpForce.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/forces/JumpForce.java new file mode 100644 index 0000000..4d9dbfb --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/forces/JumpForce.java @@ -0,0 +1,72 @@ +package ca.blarg.tiles3.basicexample.entities.forces; + +import ca.blarg.gdx.GameLooper; +import ca.blarg.gdx.Services; +import ca.blarg.gdx.math.MathHelpers; +import ca.blarg.tiles3.basicexample.entities.PhysicsConstants; +import ca.blarg.tiles3.basicexample.entities.components.physics.PhysicsComponent; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector3; + +public class JumpForce extends Force { + PhysicsComponent physicsComponent; + + public Force set(Vector3 direction, float strength, float friction, PhysicsComponent physicsComponent) { + // we will almost certainly want a jump to last beyond 1 update tick + float tickFrequency = Services.get(GameLooper.class).getUpdateFrequency(); + float minimumDuration = tickFrequency + 0.01f; + + super.set(direction, strength, friction) + .setMinimumDuration(minimumDuration) + .setZeroTolerance(PhysicsConstants.ON_GROUND_ZERO_TOLERANCE); + this.physicsComponent = physicsComponent; + return this; + } + + @Override + public void update(float delta) { + super.update(delta); + + // Force.update() call could potentially kill this force and then return + // here, so we should check for that first + if (!isActive()) + return; + + boolean cancelled = false; + + // jumping should be cancelled when the top of the entity hits a surface + // that is more or less perpendicular to the jumping direction (positive Y). + // it should also be stopped if the entity has landed on some ground early + // along in the jump (e.g. they jumped up to a ledge) + if (physicsComponent.sweptSphere.foundCollision || physicsComponent.isSliding) { + if (physicsComponent.isSliding || physicsComponent.slidingPlaneNormal.direction.y > 0.0f) { + // if we're sliding, then check the angle + float slideYAngle = (float)Math.acos(physicsComponent.sweptSphere.slidingPlaneNormal.dot(Vector3.Y)) * MathUtils.radiansToDegrees; + + // Y axis angle's from 135 to 225 means we hit something overhead. + // 180 degrees = hit something perfectly perpendicular to the Y axis + // HACK: also we check for a Y angle of zero as that will handle an + // edge case where the entity jumped and immediately overhead there + // is an obstacle (in this case, most of the time, the slide Y angle + // is actually for the collision below their feet...) + if ((slideYAngle > 135.0f && slideYAngle < 225.0f) || MathHelpers.areAlmostEqual(slideYAngle, 0.0f)) + cancelled = true; + } else + // not sliding, just a full-on collision with something + // (collision but not sliding means it's usually a flat area of the + // ground), so this is really IsOnGround() == TRUE (??) + cancelled = true; + } + + // don't kill it, even if there was a collision found above, if the force + // hasn't been running for the minimum duration yet + if (cancelled && hasMinDurationPassed()) + kill(); + } + + @Override + public void reset() { + super.reset(); + physicsComponent = null; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/presets/BaseObjectEntityPreset.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/presets/BaseObjectEntityPreset.java new file mode 100644 index 0000000..b812605 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/presets/BaseObjectEntityPreset.java @@ -0,0 +1,52 @@ +package ca.blarg.tiles3.basicexample.entities.presets; + +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.entities.EntityPreset; +import ca.blarg.gdx.events.Event; +import ca.blarg.gdx.events.EventListener; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.tiles3.basicexample.entities.events.DespawnedEvent; +import ca.blarg.tiles3.basicexample.entities.events.SpawnedEvent; +import com.badlogic.gdx.utils.Disposable; + +public abstract class BaseObjectEntityPreset extends EntityPreset implements EventListener, Disposable { + public final EventManager eventManager; + + public BaseObjectEntityPreset(EntityManager entityManager) { + super(entityManager); + eventManager = entityManager.eventManager; + + eventManager.addListener(SpawnedEvent.class, this); + eventManager.addListener(DespawnedEvent.class, this); + } + + @Override + public void dispose() { + eventManager.removeListener(SpawnedEvent.class, this); + eventManager.removeListener(DespawnedEvent.class, this); + } + + public void onSpawn(Entity entity) { + } + + public void onDespawn(Entity entity) { + } + + @Override + public boolean handle(Event e) { + if (e instanceof SpawnedEvent) { + SpawnedEvent event = (SpawnedEvent)e; + if (event.entity.getPresetUsedToCreate() == this.getClass()) + onSpawn(event.entity); + } + + else if (e instanceof DespawnedEvent) { + DespawnedEvent event = (DespawnedEvent)e; + if (event.entity.getPresetUsedToCreate() == this.getClass()) + onDespawn(event.entity); + } + + return false; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/presets/PlayerPreset.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/presets/PlayerPreset.java new file mode 100644 index 0000000..ed6e0b8 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/presets/PlayerPreset.java @@ -0,0 +1,65 @@ +package ca.blarg.tiles3.basicexample.entities.presets; + +import ca.blarg.gdx.Services; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.tiles3.basicexample.ContentCache; +import ca.blarg.tiles3.basicexample.entities.EntityState; +import ca.blarg.tiles3.basicexample.entities.PhysicsConstants; +import ca.blarg.tiles3.basicexample.entities.components.CameraComponent; +import ca.blarg.tiles3.basicexample.entities.components.PlayerComponent; +import ca.blarg.tiles3.basicexample.entities.components.StateComponent; +import ca.blarg.tiles3.basicexample.entities.components.physics.*; +import ca.blarg.tiles3.basicexample.entities.components.rendering.*; + +public class PlayerPreset extends BaseObjectEntityPreset { + public PlayerPreset(EntityManager entityManager) { + super(entityManager); + } + + @Override + public Entity create(CreationArgs args) { + ContentCache content = Services.get(ContentCache.class); + + Entity entity = entityManager.add(); + entity.add(BoundingSphereComponent.class).bounds.radius = 0.5f; + entity.add(StateComponent.class).state = EntityState.Idle; + entity.add(PlayerComponent.class); + + CameraComponent camera = entity.add(CameraComponent.class); + camera.useEntityOrientation = false; + + BillboardComponent billboard = entity.add(BillboardComponent.class); + billboard.isAxisAligned = true; + billboard.atlas = content.player; + billboard.width = 1.7f; + billboard.height = 1.7f; + + AnimationComponent animations = entity.add(AnimationComponent.class); + animations.sequences.add("idle").set(0, 0, 0.0f, true, 0); + animations.sequences.add("walk").set(8, 9, 0.15f, true, 0); + animations.sequences.add("dead").set(32, 32, 0.0f); + animations.sequences.add("jump").set(8, 8, 0.0f, true, 1); + animations.sequences.add("fall").set(9, 9, 0.0f, true, 1); + animations.setSequence("idle", true, false); + + AutoAnimationComponent autoAnimation = entity.add(AutoAnimationComponent.class); + autoAnimation.set(EntityState.Idle, "idle", true, false) + .set(EntityState.Walking, "walk", true, false) + .set(EntityState.Falling, "fall", true, false) + .set(EntityState.Jumping, "jump", true, false) + .set(EntityState.Dead, "dead", false, true); + + entity.add(PhysicsComponent.class) + .setMovementProperties(1.0f, 8.0f, PhysicsConstants.FRICTION_NORMAL) + .setBoundsRadius(0.49f); + + PositionComponent position = entity.add(PositionComponent.class); + position.position.set(16.0f, 1.5f, 16.0f); + position.lastPosition.set(position.position); + + entity.add(OrientationXZComponent.class); + + return entity; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/AnimationSystem.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/AnimationSystem.java new file mode 100644 index 0000000..7810d0f --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/AnimationSystem.java @@ -0,0 +1,175 @@ +package ca.blarg.tiles3.basicexample.entities.subsystems; + +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.math.Vector3; +import ca.blarg.gdx.Services; +import ca.blarg.gdx.entities.ComponentSystem; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.events.Event; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.gdx.graphics.ViewportContext; +import ca.blarg.gdx.math.MathHelpers; +import ca.blarg.tiles3.basicexample.entities.FacingDirection; +import ca.blarg.tiles3.basicexample.entities.components.physics.OrientationXZComponent; +import ca.blarg.tiles3.basicexample.entities.components.rendering.AnimationComponent; +import ca.blarg.tiles3.basicexample.entities.components.rendering.AutoAnimationComponent; +import ca.blarg.tiles3.basicexample.entities.components.rendering.BillboardComponent; +import ca.blarg.tiles3.basicexample.entities.events.AnimationChangeEvent; +import ca.blarg.tiles3.basicexample.entities.events.AnimationFinishedEvent; + +public class AnimationSystem extends ComponentSystem { + final ViewportContext viewportContext; + + public AnimationSystem(EntityManager entityManager, EventManager eventManager) { + super(entityManager, eventManager); + viewportContext = Services.get(ViewportContext.class); + + listenFor(AnimationChangeEvent.class); + } + + @Override + public void dispose() { + stopListeningFor(AnimationChangeEvent.class); + } + + @Override + public void onUpdateFrame(float delta) { + updateAnimationSequences(delta); + updateEntityRenderFrames(); + } + + @Override + public boolean handle(Event e) { + if (e instanceof AnimationChangeEvent) + return handle((AnimationChangeEvent)e); + + return false; + } + + private void updateAnimationSequences(float delta) { + for (Entity entity : entityManager.getAllWith(AnimationComponent.class)) { + AnimationComponent animation = entity.get(AnimationComponent.class); + + // only update active animation sequences + if (!animation.isAnimationFinished()) { + animation.frameTime += delta; + animation.interpolation += delta / animation.currentSequence.delay; + + if (animation.frameTime >= animation.currentSequence.delay) { + // move to the next frame + animation.frameTime = 0.0f; + animation.interpolation = 0.0f; + + ++animation.currentFrame; + if (animation.currentFrame > animation.currentSequence.stop) { + animation.currentFrame = animation.currentSequence.start; + if (!animation.isLooping) { + animation.currentSequence.start = animation.currentSequence.stop; + animation.currentFrame = animation.currentSequence.stop; + animation.nextFrame = animation.currentFrame; + animation.frameTime = animation.currentSequence.delay; + + continue; + } + } + + ++animation.nextFrame; + if (animation.nextFrame > animation.currentSequence.stop) { + if (!animation.isLooping) + animation.nextFrame = animation.currentSequence.stop; + else + animation.nextFrame = animation.currentSequence.start; + } + } + } else { + // animation has finished (it is not looping and has reached the end) + if (!animation.hasAnimationFinishEventBeenTriggered) { + // and we haven't yet raised an event to signal this + AnimationFinishedEvent finishedEvent = eventManager.create(AnimationFinishedEvent.class); + finishedEvent.entity = entity; + eventManager.trigger(finishedEvent); + + animation.hasAnimationFinishEventBeenTriggered = true; + } + } + } + } + + private void updateEntityRenderFrames() { + // update texture atlas frame indexes for tile-animation components with + // multi-directional animations based on the current camera orientation + for (Entity entity : entityManager.getAllWith(AnimationComponent.class)) { + AnimationComponent animation = entity.get(AnimationComponent.class); + BillboardComponent billboard = entity.get(BillboardComponent.class); + if (billboard != null) { + OrientationXZComponent orientation = entity.get(OrientationXZComponent.class); + updateBillboardFrame(billboard, animation, orientation, viewportContext.getPerspectiveCamera()); + } else + throw new UnsupportedOperationException("Unsupported animation method."); + } + } + + private boolean handle(AnimationChangeEvent e) { + if (e.changeToSequenceForState) { + // event is for an animation change to a sequence based on an entity + // state provided in the event info. requires an AutoAnimationComponent + AutoAnimationComponent autoAnimation = e.entity.get(AutoAnimationComponent.class); + if (autoAnimation != null) { + // if the entity's auto animation info has a sequence defined for + // the state specified in the event, then switch to that sequence + AutoAnimationComponent.AutoAnimationSequenceProperties sequenceProps = autoAnimation.mappings.get(e.state); + if (sequenceProps != null) { + AnimationComponent animation = e.entity.get(AnimationComponent.class); + + if (!animation.currentSequenceUnoverrideable || + animation.isAnimationFinished() || + e.overrideExisting) + animation.setSequence( + sequenceProps.name, + sequenceProps.loop, + sequenceProps.cannotBeOverridden + ); + } + } + } else { + // we're just changing to a named animation sequence directly + AnimationComponent animation = e.entity.get(AnimationComponent.class); + animation.setSequence(e.sequenceName, true, false); + } + + return false; + } + + static final Vector3 cameraPos = new Vector3(); + static final Vector3 cameraTarget = new Vector3(); + private void updateBillboardFrame(BillboardComponent billboard, AnimationComponent animation, OrientationXZComponent orientation, Camera camera) { + if (billboard.atlas == null) + return; + + if (orientation != null && animation.currentSequence.isMultiDirectional) { + // get the direction to point the entity in based on it's own facing + // direction and the direction the camera is pointed in + cameraPos.set(camera.position); + cameraTarget.set(camera.position) + .add(camera.direction); + float yaw = MathHelpers.getYAngleBetween(cameraTarget, cameraPos); + int direction = FacingDirection.getFacingDirectionAdjustedForCamera(orientation.angle, yaw, !animation.currentSequence.hasDiagonalDirections).value(); + + // +1 because sequences will be specified using start/stop frames that + // are a part of the sequence. e.g. for a 2 frame sequence using frames + // 10 and 11, you would specify start=10 and stop=11, so without +1, the + // below calc would find the length of this sequence to be 1 which is + // not correct... + int sequenceLength = animation.currentSequence.stop - animation.currentSequence.start + 1; + + // offset between frames for different directions + int offset = direction * animation.currentSequence.directionFrameOffset; + + // set the frame index + billboard.tileIndex = animation.currentFrame + (sequenceLength * direction) + offset; + } else { + billboard.tileIndex = animation.currentFrame; + } + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/BillboardRenderSystem.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/BillboardRenderSystem.java new file mode 100644 index 0000000..adfc9a6 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/BillboardRenderSystem.java @@ -0,0 +1,78 @@ +package ca.blarg.tiles3.basicexample.entities.subsystems; + +import ca.blarg.gdx.Services; +import ca.blarg.gdx.entities.ComponentSystem; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.gdx.graphics.BillboardSpriteBatch; +import ca.blarg.gdx.graphics.ViewportContext; +import ca.blarg.tiles3.basicexample.entities.EntityUtils; +import ca.blarg.tiles3.basicexample.entities.components.physics.BoundingSphereComponent; +import ca.blarg.tiles3.basicexample.entities.components.physics.PositionComponent; +import ca.blarg.tiles3.basicexample.entities.components.rendering.BillboardComponent; +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.math.Frustum; +import com.badlogic.gdx.math.Vector3; + +public class BillboardRenderSystem extends ComponentSystem { + private static final float Y_COORD_OFFSET = -1.0f; + private final Vector3 renderPosition = new Vector3(); + + final ViewportContext viewportContext; + final BillboardSpriteBatch billboardSpriteBatch; + + public BillboardRenderSystem(EntityManager entityManager, EventManager eventManager) { + super(entityManager, eventManager); + viewportContext = Services.get(ViewportContext.class); + billboardSpriteBatch = Services.get(BillboardSpriteBatch.class); + } + + @Override + public void onRender(float interpolation) { + Camera camera = viewportContext.getPerspectiveCamera(); + Frustum frustum = camera.frustum; + + billboardSpriteBatch.begin(camera); + + for (Entity entity : entityManager.getAllWith(BillboardComponent.class)) { + BillboardComponent billboard = entity.get(BillboardComponent.class); + PositionComponent position = entity.get(PositionComponent.class); + BoundingSphereComponent bounds = entity.get(BoundingSphereComponent.class); + + if (bounds != null) { + if (!frustum.sphereInFrustum(bounds.bounds.center, bounds.bounds.radius)) + continue; + } else { + if (!frustum.pointInFrustum(position.lastPosition)) + continue; + } + + EntityUtils.getInterpolatedPosition(entity, interpolation, renderPosition); + + BillboardSpriteBatch.Type billboardType = + (billboard.isAxisAligned ? BillboardSpriteBatch.Type.ScreenAligned : + BillboardSpriteBatch.Type.Spherical); + + float difference = (billboard.height / 2.0f) + Y_COORD_OFFSET; + if (difference > 0.0f) + renderPosition.y += difference; + renderPosition.x += 0.35f; + + if (billboard.texture != null) + billboardSpriteBatch.draw( + billboardType, billboard.texture, + renderPosition.x, renderPosition.y, renderPosition.z, + billboard.width, billboard.height + ); + else + billboardSpriteBatch.draw( + billboardType, billboard.atlas.get(billboard.tileIndex), + renderPosition.x, renderPosition.y, renderPosition.z, + billboard.width, billboard.height + ); + } + + billboardSpriteBatch.end(); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/BoundingVolumeWorldPositioningSystem.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/BoundingVolumeWorldPositioningSystem.java new file mode 100644 index 0000000..4b75586 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/BoundingVolumeWorldPositioningSystem.java @@ -0,0 +1,24 @@ +package ca.blarg.tiles3.basicexample.entities.subsystems; + +import ca.blarg.gdx.entities.ComponentSystem; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.tiles3.basicexample.entities.components.physics.BoundingSphereComponent; +import ca.blarg.tiles3.basicexample.entities.components.physics.PositionComponent; + +public class BoundingVolumeWorldPositioningSystem extends ComponentSystem { + public BoundingVolumeWorldPositioningSystem(EntityManager entityManager, EventManager eventManager) { + super(entityManager, eventManager); + } + + @Override + public void onUpdateGameState(float delta) { + for (Entity entity : entityManager.getAllWith(BoundingSphereComponent.class)) { + BoundingSphereComponent bounds = entity.get(BoundingSphereComponent.class); + PositionComponent position = entity.get(PositionComponent.class); + + bounds.bounds.center.set(position.position); + } + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/CameraSystem.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/CameraSystem.java new file mode 100644 index 0000000..126a98e --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/CameraSystem.java @@ -0,0 +1,68 @@ +package ca.blarg.tiles3.basicexample.entities.subsystems; + +import ca.blarg.gdx.Services; +import ca.blarg.gdx.entities.ComponentSystem; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.gdx.graphics.EulerPerspectiveCamera; +import ca.blarg.gdx.graphics.ViewportContext; +import ca.blarg.gdx.math.MathHelpers; +import ca.blarg.tiles3.basicexample.entities.EntityUtils; +import ca.blarg.tiles3.basicexample.entities.components.CameraComponent; +import ca.blarg.tiles3.basicexample.entities.components.physics.OrientationXZComponent; +import com.badlogic.gdx.math.Vector3; + +public class CameraSystem extends ComponentSystem { + static final float FOLLOW_DISTANCE = 3.5f; + static final float FOLLOW_HEIGHT = 9.0f; + static final float FOLLOW_PITCH_ANGLE = 70.0f; + + final static Vector3 targetEntityDirection = new Vector3(); + final static Vector3 targetEntityPosition = new Vector3(); + final static Vector3 tmp = new Vector3(); + + final ViewportContext viewportContext; + + public CameraSystem(EntityManager entityManager, EventManager eventManager) { + super(entityManager, eventManager); + viewportContext = Services.get(ViewportContext.class); + } + + @Override + public void dispose() { + } + + @Override + public void onRender(float interpolation) { + Entity target = entityManager.getFirstWith(CameraComponent.class); + if (target == null) + return; + + CameraComponent cameraProperties = target.get(CameraComponent.class); + + float yAngle; + if (cameraProperties.useEntityOrientation) + yAngle = target.get(OrientationXZComponent.class).angle; + else + yAngle = cameraProperties.yAngle; + + EulerPerspectiveCamera camera = (EulerPerspectiveCamera)viewportContext.getPerspectiveCamera(); + + MathHelpers.getDirectionVector3FromYAxis(yAngle, targetEntityDirection); + EntityUtils.getInterpolatedPosition(target, interpolation, targetEntityPosition); + + // get the new camera position + tmp.set(targetEntityDirection) // behind the direction the player is currently facing + .scl(-1.0f); + MathHelpers.setLengthOf(tmp, FOLLOW_DISTANCE); // FOLLOW_DISTANCE units behind ... + tmp.add(0.0f, FOLLOW_HEIGHT, 0.0f) // ... and FOLLOW_HEIGHT units up ... + .add(targetEntityPosition); // ... from the player's position + + camera.position.set(tmp); + float angle = MathHelpers.getYAngleBetween(targetEntityPosition, tmp) - 90.0f; + camera.turnTo(angle); + camera.pitchTo(FOLLOW_PITCH_ANGLE); + camera.update(); + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/EntityStateSystem.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/EntityStateSystem.java new file mode 100644 index 0000000..42dcf99 --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/EntityStateSystem.java @@ -0,0 +1,162 @@ +package ca.blarg.tiles3.basicexample.entities.subsystems; + +import ca.blarg.gdx.entities.ComponentSystem; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.entities.systemcomponents.InactiveComponent; +import ca.blarg.gdx.events.Event; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.tiles3.basicexample.entities.events.*; +import ca.blarg.tiles3.basicexample.entities.EntityState; +import ca.blarg.tiles3.basicexample.entities.components.StateComponent; + +public class EntityStateSystem extends ComponentSystem { + public EntityStateSystem(EntityManager entityManager, EventManager eventManager) { + super(entityManager, eventManager); + listenFor(EntityStateChangeEvent.class); + listenFor(EntityExitingTempStateEvent.class); + listenFor(AnimationFinishedEvent.class); + listenFor(DespawnedEvent.class); + } + + @Override + public void dispose() { + stopListeningFor(EntityStateChangeEvent.class); + stopListeningFor(EntityExitingTempStateEvent.class); + stopListeningFor(AnimationFinishedEvent.class); + stopListeningFor(DespawnedEvent.class); + } + + @Override + public boolean handle(Event e) { + if (e instanceof EntityStateChangeEvent) + return handle((EntityStateChangeEvent)e); + + else if (e instanceof EntityExitingTempStateEvent) + return handle((EntityExitingTempStateEvent)e); + + else if (e instanceof AnimationFinishedEvent) + return handle((AnimationFinishedEvent)e); + + else if (e instanceof DespawnedEvent) + return handle((DespawnedEvent)e); + + return false; + } + + private boolean handle(EntityStateChangeEvent e) + { + StateComponent state = e.entity.get(StateComponent.class); + if (state != null) { + if (e.isActionState) { + // set action state first + state.actionState = e.state; + state.isInActionState = true; + + // HACK: this was mainly added to prevent the death animation from + // playing twice in a row in a somewhat rare scenario. however + // this does seem to be a sensible check regardless ... + boolean needToChangeAnimation = state.state != state.actionState; + + // trigger state change event + EntityInTempStateEvent actionStateEvent = eventManager.create(EntityInTempStateEvent.class); + actionStateEvent.entity = e.entity; + eventManager.trigger(actionStateEvent); + + if (needToChangeAnimation) { + // trigger animation change + AnimationChangeEvent animationChangeEvent = eventManager.create(AnimationChangeEvent.class); + animationChangeEvent.entity = e.entity; + animationChangeEvent.changeToSequenceForState = true; + animationChangeEvent.state = e.state; + eventManager.trigger(animationChangeEvent); + } + } else { + // HACK: this will (hopefully?) stop entity's from doing there + // death animation and then popping up to idle before + // despawning. need to verify (very hard to reproduce!) + if (state.state == EntityState.Dead && e.state == EntityState.Idle) + return false; + + // HACK: this was mainly added to prevent the death animation from + // playing twice in a row in a somewhat rare scenario. however + // this does seem to be a sensible check regardless ... + boolean needToChangeAnimation = state.state != state.actionState; + + // set non-action state + state.state = e.state; + + if (e.clearExistingActionStateInfo) { + state.actionState = EntityState.None; + state.isInActionState = false; + } + + if (needToChangeAnimation) { + // trigger animation change + AnimationChangeEvent animationChangeEvent = eventManager.create(AnimationChangeEvent.class); + animationChangeEvent.entity = e.entity; + animationChangeEvent.changeToSequenceForState = true; + animationChangeEvent.state = e.state; + + // HACK: this ensures that the death animation will play + animationChangeEvent.overrideExisting = e.state == EntityState.Dead; + + eventManager.trigger(animationChangeEvent); + } + } + } + + return false; + } + + private boolean handle(EntityExitingTempStateEvent e) { + StateComponent state = e.entity.get(StateComponent.class); + if (state != null) { + // HACK: this will (hopefully?) stop entity's from doing their + // death animation and then popping up to idle before + // despawning. need to verify (very hard to reproduce!) + if (state.state == EntityState.Dead) + return false; + + // HACK: this was mainly added to prevent the death animation from + // playing twice in a row in a somewhat rare scenario. however + // this does seem to be a sensible check regardless ... + boolean needToChangeAnimation = state.state != state.actionState; + + // clear action state info + state.isInActionState = false; + state.actionState = EntityState.None; + + if (needToChangeAnimation) { + // trigger animation change + AnimationChangeEvent animationChangeEvent = eventManager.create(AnimationChangeEvent.class); + animationChangeEvent.entity = e.entity; + animationChangeEvent.changeToSequenceForState = true; + animationChangeEvent.state = state.state; + eventManager.trigger(animationChangeEvent); + } + } + + return false; + } + + private boolean handle(AnimationFinishedEvent e) { + StateComponent state = e.entity.get(StateComponent.class); + + // TODO: this all assumes that an AnimationFinishedEvent triggered for + // an entity with a StateComponent marked as "in action state" + // should *always* trigger an EntityExitingTempStateEvent. Maybe + // this should be set up some other way with less assumptions? + if (state != null && state.isInActionState) { + EntityExitingTempStateEvent exitingTempStateEvent = eventManager.create(EntityExitingTempStateEvent.class); + exitingTempStateEvent.entity = e.entity; + eventManager.trigger(exitingTempStateEvent); + } + + return false; + } + + private boolean handle(DespawnedEvent e) { + e.entity.add(InactiveComponent.class); + return false; + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/PhysicsSystem.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/PhysicsSystem.java new file mode 100644 index 0000000..58e1bbb --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/PhysicsSystem.java @@ -0,0 +1,378 @@ +package ca.blarg.tiles3.basicexample.entities.subsystems; + +import ca.blarg.gdx.entities.ComponentSystem; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.events.Event; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.gdx.math.MathHelpers; +import ca.blarg.gdx.math.SweptSphereHandler; +import ca.blarg.gdx.tilemap3d.TileMapSweptSphereCollisionChecker; +import ca.blarg.tiles3.basicexample.entities.EntityState; +import ca.blarg.tiles3.basicexample.entities.PhysicsConstants; +import ca.blarg.tiles3.basicexample.entities.components.WorldComponent; +import ca.blarg.tiles3.basicexample.entities.components.physics.*; +import ca.blarg.tiles3.basicexample.entities.events.*; +import ca.blarg.tiles3.basicexample.entities.forces.Force; +import ca.blarg.tiles3.basicexample.entities.forces.JumpForce; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.Ray; +import com.badlogic.gdx.utils.Pools; + +public class PhysicsSystem extends ComponentSystem { + private class TerrainFrictionValues { + public float friction; + public float walkAcceleration; + public float maxWalkSpeed; + } + + final SweptSphereHandler sweptSphereHandler; + public final TileMapSweptSphereCollisionChecker sweptSphereWorldCollisionChecker; + + static final Vector3 tmp1 = new Vector3(); + final TerrainFrictionValues tmpTerrainFrictionValues = new TerrainFrictionValues(); + + public PhysicsSystem(EntityManager entityManager, EventManager eventManager) { + super(entityManager, eventManager); + listenFor(MoveForwardEvent.class); + listenFor(MoveInDirectionEvent.class); + listenFor(MoveAndTurnEvent.class); + listenFor(StopMovementEvent.class); + listenFor(JumpEvent.class); + + sweptSphereWorldCollisionChecker = new TileMapSweptSphereCollisionChecker(); + sweptSphereHandler = new SweptSphereHandler(sweptSphereWorldCollisionChecker, 5); + sweptSphereHandler.possibleCollisionAreaMinOffset.set(0.0f, -0.5f, 0.0f); + sweptSphereHandler.possibleCollisionAreaMaxOffset.set(0.0f, 0.5f, 0.0f); + } + + @Override + public void dispose() { + stopListeningFor(MoveForwardEvent.class); + stopListeningFor(MoveInDirectionEvent.class); + stopListeningFor(MoveAndTurnEvent.class); + stopListeningFor(StopMovementEvent.class); + stopListeningFor(JumpEvent.class); + } + + static final Vector3 resultingVelocity = new Vector3(); + static final Vector3 gravityVelocity = new Vector3(); + @Override + public void onUpdateGameState(float delta) { + Entity worldEntity = entityManager.getFirstWith(WorldComponent.class); + if (worldEntity == null) + return; // no world to simulate physics for! + + WorldComponent world = worldEntity.get(WorldComponent.class); + sweptSphereWorldCollisionChecker.tileMap = world.tileMap; + + for (Entity entity : entityManager.getAllWith(PhysicsComponent.class)) { + PhysicsComponent physics = entity.get(PhysicsComponent.class); + PositionComponent position = entity.get(PositionComponent.class); + + // to accurately check if the entity is walking we need to check this first! + // walking only adds velocity in the XZ plane. forces can add velocity in any + // direction, so if we added them first, we couldn't just check XZ + updateIsWalkingState(physics); + + // apply forces. this directly affects velocity, so it needs to go next! + processForces(physics, delta); + + getTerrainFrictionFor(physics, tmpTerrainFrictionValues); + + // apply friction and then combine walking and force velocity's into one + // to get the entity's current velocity for this tick + physics.walkingVelocity.scl(tmpTerrainFrictionValues.friction); + + if (physics.walkingVelocity.len() <= 0.12f) + physics.walkingVelocity.set(Vector3.Zero); + if (physics.forceVelocity.len() <= 0.1f) + physics.forceVelocity.set(Vector3.Zero); + + physics.velocity.set(physics.walkingVelocity) + .add(physics.forceVelocity); + + // update last/current tick velocities + physics.lastTickVelocity.set(physics.currentTickVelocity); + physics.currentTickVelocity.set(physics.velocity) + .scl(delta); + + Vector3 gravity = gravityVelocity.set(PhysicsConstants.GRAVITY) + .scl(delta); + + physics.sweptSphere.position.set(position.position); + + resultingVelocity.set(Vector3.Zero); + sweptSphereHandler.handleMovement(physics.sweptSphere, + physics.currentTickVelocity, + gravity, + resultingVelocity, + true, + true, + PhysicsConstants.SLOPE_STEEP_Y_ANGLE); + + position.position.set(physics.sweptSphere.position); + physics.currentTickVelocity.set(resultingVelocity); + physics.collisionTilePosition.set(sweptSphereWorldCollisionChecker.lastCollisionTilePosition); + + physics.isInMotion = physics.sweptSphere.isInMotion; + physics.wasInMotion = physics.sweptSphere.wasInMotion; + physics.isFalling = physics.sweptSphere.isFalling; + physics.wasFalling = physics.sweptSphere.wasFalling; + physics.isSliding = physics.sweptSphere.isSliding; + physics.wasSliding = physics.sweptSphere.wasSliding; + physics.isOnGround = physics.sweptSphere.isOnGround; + physics.wasOnGround = physics.sweptSphere.wasOnGround; + + updateStandingTileCoords(physics, position, world); + + triggerStateChangeEvents(physics, entity); + } + } + + @Override + public boolean handle(Event e) { + if (e instanceof MoveInDirectionEvent) + return handle((MoveInDirectionEvent)e); + + else if (e instanceof MoveForwardEvent) + return handle((MoveForwardEvent)e); + + else if (e instanceof MoveAndTurnEvent) + return handle((MoveAndTurnEvent)e); + + else if (e instanceof StopMovementEvent) + return handle((StopMovementEvent)e); + + else if (e instanceof JumpEvent) + return handle((JumpEvent)e); + + return false; + } + + private boolean handle(MoveInDirectionEvent e) { + if (e.entity.has(PhysicsComponent.class)) { + move(e.entity, e.direction); + return true; + } else + return false; + } + + private boolean handle(MoveForwardEvent e) { + if (e.entity.has(PhysicsComponent.class)) { + OrientationXZComponent orientation = e.entity.get(OrientationXZComponent.class); + tmp1.set(Vector3.Zero); + MathHelpers.getDirectionVector3FromYAxis(orientation.angle, tmp1); + move(e.entity, tmp1); + return true; + } else + return false; + } + + private boolean handle(MoveAndTurnEvent e) { + if (e.entity.has(PhysicsComponent.class)) { + OrientationXZComponent orientation = e.entity.get(OrientationXZComponent.class); + orientation.angle = e.angle; + tmp1.set(Vector3.Zero); + MathHelpers.getDirectionVector3FromYAxis(orientation.angle, tmp1); + move(e.entity, tmp1); + return true; + } else + return false; + } + + private boolean handle(StopMovementEvent e) { + if (e.entity.has(PhysicsComponent.class)) { + stop(e.entity); + return true; + } else + return false; + } + + private boolean handle(JumpEvent e) { + if (e.entity.has(PhysicsComponent.class)) { + jump(e.entity); + return true; + } else + return false; + } + + private void triggerStateChangeEvents(PhysicsComponent physics, Entity entity) { + if (physics.isFalling && !physics.wasFalling) { + EntityStateChangeEvent event = eventManager.create(EntityStateChangeEvent.class); + event.state = EntityState.Falling; + event.entity = entity; + eventManager.trigger(event); + } else if (physics.wasFalling && physics.isWalking) { + EntityStateChangeEvent event = eventManager.create(EntityStateChangeEvent.class); + event.state = EntityState.Walking; + event.entity = entity; + eventManager.trigger(event); + } else if (physics.wasFalling && !physics.isFalling) { + EntityStateChangeEvent event = eventManager.create(EntityStateChangeEvent.class); + event.state = EntityState.Idle; + event.entity = entity; + eventManager.trigger(event); + } else if (physics.isWalking && !physics.wasWalking && !physics.isFalling) { + EntityStateChangeEvent event = eventManager.create(EntityStateChangeEvent.class); + event.state = EntityState.Walking; + event.entity = entity; + eventManager.trigger(event); + } else if (physics.wasWalking && !physics.isWalking) { + EntityStateChangeEvent event = eventManager.create(EntityStateChangeEvent.class); + event.state = EntityState.Idle; + event.entity = entity; + eventManager.trigger(event); + } + } + + static final Vector3 tmpMovementVelocity = new Vector3(); + private void move(Entity entity, Vector3 direction) { + PhysicsComponent physics = entity.get(PhysicsComponent.class); + + getTerrainFrictionFor(physics, tmpTerrainFrictionValues); + + float acceleration = tmpTerrainFrictionValues.walkAcceleration; + float maxSpeed = tmpTerrainFrictionValues.maxWalkSpeed; + + // currentVelocity = a * maxVelocity + (1 - a) * currentVelocity + + tmpMovementVelocity.set(direction).scl(maxSpeed) // maxVelocity + .scl(acceleration); // * a + + physics.walkingVelocity.scl(1 - acceleration) // (1 - a) * currentVelocity + .add(tmpMovementVelocity); // + (a * maxVelocity) + } + + private void stop(Entity entity) { + PhysicsComponent physics = entity.get(PhysicsComponent.class); + physics.velocity.set(Vector3.Zero); + physics.walkingVelocity.set(Vector3.Zero); + } + + private void jump(Entity entity) { + PhysicsComponent physics = entity.get(PhysicsComponent.class); + + if (!physics.isOnGround) + return; + + JumpForce newForce = Pools.obtain(JumpForce.class); + newForce.set(MathHelpers.UP_VECTOR3, PhysicsConstants.GRAVITY_SPEED * 3.0f, 0.6f, physics); + applyForce(physics, newForce); + } + + private void applyForce(PhysicsComponent physics, Force force) { + physics.forces.add(force); + } + + private void applyForce(PhysicsComponent physics, Vector3 direction, float strength, float friction) { + Force newForce = Pools.obtain(Force.class); + newForce.set(direction, strength, friction); + applyForce(physics, newForce); + } + + private void processForces(PhysicsComponent physics, float delta) { + physics.forceVelocity.set(Vector3.Zero); + for (Force force : physics.forces) { + force.update(delta); + physics.forceVelocity.add(force.getCurrentForce()); + } + + // clean up dead forces + int i = 0; + while (i < physics.forces.size) { + Force force = physics.forces.get(i); + if (!force.isActive()) { + // stay at this index for the next iteration since we just removed something (next element now at this same index) + physics.forces.removeIndex(i); + Pools.free(force); + } else + ++i; + } + } + + private void updateIsWalkingState(PhysicsComponent physics) { + physics.wasWalking = physics.isWalking; + + if (!physics.isOnGround) { + physics.isWalking = false; + return; + } + + tmp1.set(physics.walkingVelocity); + tmp1.y = 0.0f; // walking only allows the entity to get a velocity in the XZ plane + + float velocityLength = tmp1.len(); + if (MathHelpers.areAlmostEqual(velocityLength, 0.0f)) + physics.isWalking = false; + else + physics.isWalking = true; + } + + static final Vector3 tmpFeetPosition = new Vector3(); + static final Ray downRay = new Ray(Vector3.Zero, Vector3.Zero); + static final Vector3 tmpCollisionPoint = new Vector3(); + private void updateStandingTileCoords(PhysicsComponent physics, PositionComponent position, WorldComponent world) { + if (physics.isOnGround) { + int feetX = (int)position.position.x; + int feetY = (int)(position.position.y - physics.sweptSphere.radius.y - 0.01f); + int feetZ = (int)position.position.z; + + // prefer to take the tile just below the entity's feet + // however, if that tile is empty, take the collision tile + // (this can sometimes work better, as the collision tile could + // be a tile that the entity is just standing on the very edge of, but + // in fact be mostly standing on an adjacent tile... which could be the + // one we calculate to be under the entity's feet) + + physics.standingOnTile = world.tileMap.get(feetX, feetY, feetZ); + if (!physics.standingOnTile.isEmptySpace()) { + // check the distance from the entity's feet to the nearest + // collidable surface on this tile before we accept it as the + // "standing on" tile + tmpFeetPosition.set(position.position); + tmpFeetPosition.y -= physics.sweptSphere.radius.y; + downRay.set(tmpFeetPosition, MathHelpers.DOWN_VECTOR3); + tmpCollisionPoint.set(Vector3.Zero); + float collisionDistance = 0.0f; + if (world.tileMap.checkForCollisionWithTileMesh(downRay, feetX, feetY, feetZ, world.tileMap.tileMeshes, tmpCollisionPoint)) + collisionDistance = tmp1.set(tmpFeetPosition) + .sub(tmpCollisionPoint) + .len(); + + // TODO: this distance check causes issues with ramps. as you walk + // up/down the ramp, the "standing tile" tends to flip back + // and forth a bit depending on how far up or down the ramp + // you are from the top, assuming the top of the ramp is + // next to a flat surface + if (collisionDistance <= 0.1f) { + // the top of the foot tile is close enough, use it + physics.standingOnTilePosition.set(feetX, feetY, feetZ); + return; + } + } + + // use the collision tile + physics.standingOnTile = world.tileMap.get(physics.collisionTilePosition.x, physics.collisionTilePosition.y, physics.collisionTilePosition.z); + physics.standingOnTilePosition.set(physics.collisionTilePosition); + } else { + // not standing on anything + physics.standingOnTilePosition.set(0, 0, 0); + physics.standingOnTile = null; + } + } + + private void getTerrainFrictionFor(PhysicsComponent physics, final TerrainFrictionValues out) { + out.friction = physics.friction; + out.walkAcceleration = physics.walkingAcceleration; + out.maxWalkSpeed = physics.maxWalkSpeed; + + if (physics.isOnGround) { + if (physics.standingOnTile.isSlippery()) { + out.friction = 0.95f; + out.walkAcceleration = 0.5f; + out.maxWalkSpeed = 2.0f; + } + } + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/PreviousPositionSystem.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/PreviousPositionSystem.java new file mode 100644 index 0000000..0f07f4e --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/PreviousPositionSystem.java @@ -0,0 +1,21 @@ +package ca.blarg.tiles3.basicexample.entities.subsystems; + +import ca.blarg.gdx.entities.ComponentSystem; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.tiles3.basicexample.entities.components.physics.PositionComponent; + +public class PreviousPositionSystem extends ComponentSystem { + public PreviousPositionSystem(EntityManager entityManager, EventManager eventManager) { + super(entityManager, eventManager); + } + + @Override + public void onUpdateGameState(float delta) { + for (Entity entity : entityManager.getAllWith(PositionComponent.class)) { + PositionComponent position = entity.get(PositionComponent.class); + position.lastPosition.set(position.position); + } + } +} diff --git a/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/WorldRendererSystem.java b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/WorldRendererSystem.java new file mode 100644 index 0000000..04bea9e --- /dev/null +++ b/core/src/main/java/ca/blarg/tiles3/basicexample/entities/subsystems/WorldRendererSystem.java @@ -0,0 +1,80 @@ +package ca.blarg.tiles3.basicexample.entities.subsystems; + +import ca.blarg.tiles3.basicexample.entities.components.WorldComponent; +import ca.blarg.tiles3.basicexample.entities.components.physics.PositionComponent; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g3d.ModelBatch; +import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; +import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight; +import com.badlogic.gdx.graphics.g3d.Environment; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.math.Vector3; +import ca.blarg.gdx.Services; +import ca.blarg.gdx.entities.ComponentSystem; +import ca.blarg.gdx.entities.Entity; +import ca.blarg.gdx.entities.EntityManager; +import ca.blarg.gdx.events.EventManager; +import ca.blarg.gdx.graphics.GraphicsHelpers; +import ca.blarg.gdx.graphics.ViewportContext; +import ca.blarg.gdx.math.MathHelpers; +import ca.blarg.gdx.tilemap3d.TileMapRenderer; + +public class WorldRendererSystem extends ComponentSystem { + final static Color AMBIENT_LIGHT = new Color(0.7f, 0.7f, 0.7f, 1.0f); + final static Color DIRECTIONAL_LIGHT = new Color(0.3f, 0.3f, 0.3f, 1.0f); + final static Vector3 renderPosition = new Vector3(); + + final TileMapRenderer tileMapRenderer; + final Environment environment; + + final ViewportContext viewportContext; + final ModelBatch modelBatch; + final ShapeRenderer shapeRenderer; + + public WorldRendererSystem(EntityManager entityManager, EventManager eventManager) { + super(entityManager, eventManager); + + viewportContext = Services.get(ViewportContext.class); + modelBatch = Services.get(ModelBatch.class); + shapeRenderer = Services.get(ShapeRenderer.class); + + tileMapRenderer = new TileMapRenderer(); + + environment = new Environment(); + environment.set(new ColorAttribute(ColorAttribute.AmbientLight, AMBIENT_LIGHT)); + environment.add(new DirectionalLight().set(DIRECTIONAL_LIGHT, MathHelpers.LEFT_VECTOR3)); + environment.add(new DirectionalLight().set(DIRECTIONAL_LIGHT, MathHelpers.RIGHT_VECTOR3)); + environment.add(new DirectionalLight().set(DIRECTIONAL_LIGHT, MathHelpers.DOWN_VECTOR3)); + } + + @Override + public void dispose() { + } + + @Override + public void onRender(float interpolation) { + Entity worldEntity = entityManager.getFirstWith(WorldComponent.class); + if (worldEntity == null) + return; + + WorldComponent world = worldEntity.get(WorldComponent.class); + PositionComponent worldPosition = worldEntity.get(PositionComponent.class); + if (worldPosition != null) + renderPosition.set(worldPosition.position); + else + renderPosition.set(Vector3.Zero); + + if (world.renderSkybox) { + // TODO + } + + if (world.renderGrid) + GraphicsHelpers.renderGridPlane(shapeRenderer, viewportContext.getPerspectiveCamera(), + world.tileMap.getWidth(), world.tileMap.getDepth(), + renderPosition.x, renderPosition.y, renderPosition.z); + + modelBatch.begin(viewportContext.getPerspectiveCamera()); + tileMapRenderer.render(modelBatch, world.tileMap, viewportContext.getPerspectiveCamera(), environment); + modelBatch.end(); + } +} diff --git a/desktop/build.gradle b/desktop/build.gradle new file mode 100644 index 0000000..8c8ebb1 --- /dev/null +++ b/desktop/build.gradle @@ -0,0 +1,45 @@ +apply plugin: "java" + +sourceCompatibility = 1.7 +sourceSets.main.java.srcDirs = [ "src/main/java/" ] +project.ext.mainClassName = "ca.blarg.tiles3.basicexample.desktop.DesktopStarter" +project.ext.assetsDir = new File("../assets"); + +task run(dependsOn: classes, type: JavaExec) { + main = project.mainClassName + classpath = sourceSets.main.runtimeClasspath + standardInput = System.in + workingDir = project.assetsDir + ignoreExitValue = true +} + +task dist(type: Jar) { + from files(sourceSets.main.output.classesDir) + from files(sourceSets.main.output.resourcesDir) + from {configurations.compile.collect {zipTree(it)}} + from files(project.assetsDir); + + manifest { + attributes 'Main-Class': project.mainClassName + } +} + +dist.dependsOn classes + +eclipse { + project { + name = appName + "-desktop" + linkedResource name: 'assets', type: '2', location: 'PARENT-1-PROJECT_LOC/assets' + } +} + +task afterEclipseImport(description: "Post processing after project generation", group: "IDE") { + doLast { + def classpath = new XmlParser().parse(file(".classpath")) + new Node(classpath, "classpathentry", [ kind: 'src', path: 'assets' ]); + def writer = new FileWriter(file(".classpath")) + def printer = new XmlNodePrinter(new PrintWriter(writer)) + printer.setPreserveWhitespace(true) + printer.print(classpath) + } +} diff --git a/desktop/src/main/java/ca/blarg/tiles3/basicexample/desktop/DesktopStarter.java b/desktop/src/main/java/ca/blarg/tiles3/basicexample/desktop/DesktopStarter.java new file mode 100644 index 0000000..a06b3ce --- /dev/null +++ b/desktop/src/main/java/ca/blarg/tiles3/basicexample/desktop/DesktopStarter.java @@ -0,0 +1,16 @@ +package ca.blarg.tiles3.basicexample.desktop; + +import com.badlogic.gdx.backends.lwjgl.LwjglApplication; +import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import ca.blarg.gdx.GdxGameAppListener; +import ca.blarg.tiles3.basicexample.BasicExampleGameApp; + +public class DesktopStarter { + public static void main(String[] args) { + LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration(); + cfg.title = "Tiles³ Basic Example"; + cfg.width = 1280; + cfg.height = 720; + new LwjglApplication(new GdxGameAppListener(BasicExampleGameApp.class), cfg); + } +} \ No newline at end of file diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..8d297cf Binary files /dev/null and b/screenshot.png differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..fd944c9 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include "core", "desktop"