initial commit

This commit is contained in:
Gered 2014-08-10 19:10:01 -04:00
commit b6920389b0
73 changed files with 2896 additions and 0 deletions

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
.DS_Store
.gradle/
out/
log/
target/
build/
.settings/
.project
.classpath
.idea
*.iml
*.eml
*.ipr
*.iws
*.class
*.jar

21
LICENSE Normal file
View file

@ -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.

43
README.md Normal file
View file

@ -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.

101
assets/consoleFont.fnt Normal file
View file

@ -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

BIN
assets/consoleFont.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

6
assets/player.atlas.json Normal file
View file

@ -0,0 +1,6 @@
{
"texture": "player.png",
"tiles": [
{"autogrid": true, "numTilesX": 8, "numTilesY": 8, "tileWidth": 16, "tileHeight": 16},
]
}

BIN
assets/player.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

15
assets/readme.txt Normal file
View file

@ -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. :(

View file

@ -0,0 +1,7 @@
{
"textureAtlas": "terrain.atlas.json",
"cube": true,
"texture": 7,
"faces": ["ALL"],
"opaqueSides": ["ALL"]
}

View file

@ -0,0 +1,7 @@
{
"textureAtlas": "terrain.atlas.json",
"cube": true,
"texture": 2,
"faces": ["ALL"],
"opaqueSides": ["ALL"]
}

View file

@ -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"]
}

View file

@ -0,0 +1,7 @@
{
"textureAtlas": "terrain.atlas.json",
"cube": true,
"texture": 0,
"faces": ["ALL"],
"opaqueSides": ["ALL"]
}

View file

@ -0,0 +1,8 @@
{
"textureAtlas": "terrain.atlas.json",
"cube": true,
"texture": 3,
"faces": ["ALL"],
"alpha": true,
"translucency": 0.25,
}

View file

@ -0,0 +1,7 @@
{
"textureAtlas": "terrain.atlas.json",
"cube": true,
"texture": 6,
"faces": ["ALL"],
"opaqueSides": ["ALL"]
}

View file

@ -0,0 +1,7 @@
{
"textureAtlas": "terrain.atlas.json",
"cube": true,
"texture": 9,
"faces": ["ALL"],
"opaqueSides": ["ALL"]
}

View file

@ -0,0 +1,7 @@
{
"textureAtlas": "terrain.atlas.json",
"cube": true,
"texture": 8,
"faces": ["ALL"],
"opaqueSides": ["ALL"]
}

View file

@ -0,0 +1,6 @@
{
"texture": "terrain.png",
"tiles": [
{"autogrid": true, "numTilesX": 8, "numTilesY": 8, "tileWidth": 8, "tileHeight": 8},
]
}

BIN
assets/tiles/terrain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

14
assets/tiles/tiles.json Normal file
View file

@ -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"
]
}

View file

@ -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"]
}

40
build.gradle Normal file
View file

@ -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"
}
}

9
core/build.gradle Normal file
View file

@ -0,0 +1,9 @@
apply plugin: "java"
sourceCompatibility = 1.6
sourceSets.main.java.srcDirs = [ "src/main/java/" ]
eclipse.project {
name = appName + "-core"
}

View file

@ -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();
}
}

View file

@ -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() {
}
}

View file

@ -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');
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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<String, AnimationSequence> sequences = new ObjectMap<String, AnimationSequence>();
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<String, AnimationSequence> i : sequences.entries())
Pools.free(i.value);
sequences.clear();
}
}

View file

@ -0,0 +1,14 @@
package ca.blarg.tiles3.basicexample.entities;
public enum EntityState {
None,
Idle,
Walking,
Falling,
Jumping,
Dead,
Punching,
Shooting,
Open,
Inactive
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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() {
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<Force> forces = new Array<Force>(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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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<EntityState, AutoAnimationSequenceProperties> mappings = new ObjectMap<EntityState, AutoAnimationSequenceProperties>();
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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

45
desktop/build.gradle Normal file
View file

@ -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)
}
}

View file

@ -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);
}
}

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

1
settings.gradle Normal file
View file

@ -0,0 +1 @@
include "core", "desktop"