I'm making a clone of old Super Mario and I am trying to shake the brick tile when mario in his little form hits it from below. The concept is taken from a great libgdx tutorial by Brent Aureli and everything he wrote/shown works, however when I made this (seemingly small) modification, the modification itself behaves strange.
Namely, when little mario hits the tile (any brick tile) ALL tiles of this kind get this offset. From my understanding, the access is getCell().getTile().setOffsetY(value) which should mean
get a single cell -> get tile in that cell -> set offset to this one tile that is in the given cell.
Documentation also suggests it should work like that:
https://libgdx.badlogicgames.com/nightlies/docs/api/
replacing tile with null when big mario hits the tile works as intended, so I don't understand why the approach with getCell.getTile doesn't.
Finally, I'm aware that cells are immobile, all I want is a purely cosmetic shift of the actual texture for half a second or so - and that is possible, but in the current state it offsets ALL similar tiles.
For the fun of it I
public class Brick extends InteractiveTile {
private static TiledMapTileSet tileSet;
public Brick(PlayScreen screen, MapObject object) {
super(screen, object);
tileSet = screen.getMap().getTileSets().getTileSet("tileset_gutter");
fixture.setUserData(this);
setCategoryFilter(SuperMario.BRICK_BIT);
}
#Override
public void onHeadHit(Mario mario) {
if(mario.isBig()){
SuperMario.manager.get("audio/breakblock.wav", Sound.class).play();
setCategoryFilter(SuperMario.DESTROYED_BIT);
getCell().setTile(null);
}
else {
SuperMario.manager.get("audio/bump.wav", Sound.class).play();
// DOES THE EXACT SAME AS LATER 3 LINES
// getCell().getTile().setOffsetY(2);
getCell().setTile(tileSet.getTile(28)); //this can change texture of individual tile.
TiledMapTile tile = getCell().getTile();
tile.setOffsetY(2);
getCell().setTile(tile);
}
}
}
InteractiveTile:
public abstract class InteractiveTile {
private World world;
private TiledMap map;
private TiledMapTile tile;
private Rectangle bounds;
protected Body body;
protected final Fixture fixture;
protected PlayScreen screen;
protected MapObject object;
public InteractiveTile(PlayScreen screen, MapObject object){
this.screen = screen;
this.world = screen.getWorld();
this.map = screen.getMap();
this.object = object;
this.bounds = ((RectangleMapObject)object).getRectangle();
BodyDef bdef = new BodyDef();
FixtureDef fdef = new FixtureDef();
PolygonShape shape = new PolygonShape();
bdef.type = BodyDef.BodyType.StaticBody;
bdef.position.set(
(bounds.getX()+ bounds.getWidth()/2)/ SuperMario.PPM,
(bounds.getY() + bounds.getHeight()/2)/SuperMario.PPM);
body = world.createBody(bdef);
shape.setAsBox(
(bounds.getWidth()/2)/SuperMario.PPM,
(bounds.getHeight()/2)/SuperMario.PPM); fdef.shape = shape;
fixture = body.createFixture(fdef);
}
public abstract void onHeadHit(Mario mario);
public TiledMapTileLayer.Cell getCell(){
TiledMapTileLayer layer = (TiledMapTileLayer) map.getLayers().get(1);
return layer.getCell(
//REVERT SCALING (PPM) AND by tile size
(int)(body.getPosition().x * SuperMario.PPM / 16),
(int)(body.getPosition().y * SuperMario.PPM / 16));
}
public void setCategoryFilter(short filterBit){
Filter filter = new Filter();
filter.categoryBits = filterBit;
fixture.setFilterData(filter);
}
}
is there an error in this code, or it is a limitation of the engine?
PS - the original (unmodified)files can be found on Brent Aurelis github site:
https://github.com/BrentAureli/SuperMario
Related
Recently I have read that you can work with 2 cameras whenever you involve Box2d, which only works in meters.
In my Renderer class, I receive all the entities in the game and then draw them.
I work with ashley library so all my entities are a formed by components.
Now, in my renderer method I check if the current Entity has BodyComponent. If true, then it draws the Sprite in the physicsCam, who has a Viewport of meters. Otherwise, it will just draw the Sprite in sceneCam, which has a Viewport of pixels.
My problem is I try to draw the Sprite according to the Body's position in meters, but it is drawn not exactly where the body is.
NOTE: I also have PhysicsDebugSystem which basically just call the DebugRenderer.render() method.
Here is my RenderSystem class:
package engine.systems;
import com.badlogic.ashley.core.Component;
import com.badlogic.ashley.core.ComponentMapper;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.Family;
import com.badlogic.ashley.systems.IteratingSystem;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.Array;
import engine.Values;
import engine.components.BodyComponent;
import engine.components.SpriteComponent;
import engine.components.TransformComponent;
public class RenderSystem extends IteratingSystem{
private SpriteBatch batch;
private Array<Entity> renderQueue;
private OrthographicCamera sceneCam;
private OrthographicCamera physicsCam;
private ComponentMapper<SpriteComponent> txt;
private ComponentMapper<TransformComponent> trs;
#SuppressWarnings("unchecked")
public RenderSystem(SpriteBatch batch) {
super(Family.all(TransformComponent.class, SpriteComponent.class).get());
txt = ComponentMapper.getFor(SpriteComponent.class);
trs = ComponentMapper.getFor(TransformComponent.class);
sceneCam = new OrthographicCamera(Values.WIDTH, Values.HEIGHT);
sceneCam.setToOrtho(false);
physicsCam = new OrthographicCamera(Values.WIDTH, Values.HEIGHT);
physicsCam.setToOrtho(false);
renderQueue = new Array<Entity>();
this.batch = batch;
}
#Override
public void update(float deltaTime) {
super.update(deltaTime);
Gdx.gl.glClearColor(230/255f, 242/255f, 242/255f, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
sceneCam.update();
physicsCam.update();
batch.enableBlending();
batch.begin();
for (Entity entity : renderQueue) {
Component comp = entity.getComponent(BodyComponent.class);
if(comp == null) {
batch.setProjectionMatrix(sceneCam.combined);
} else {
batch.setProjectionMatrix(physicsCam.combined);
}
SpriteComponent texture = txt.get(entity);
TransformComponent transform = trs.get(entity);
if (texture.region == null || transform.isHidden) {
continue;
}
texture.region.setPosition(transform.position.x, transform.position.y);
texture.region.setRotation(transform.rotation);
texture.region.setScale(transform.scale.x, transform.scale.y);
texture.region.draw(batch);
}
batch.end();
renderQueue.clear();
}
#Override
protected void processEntity(Entity entity, float deltaTime) {
renderQueue.add(entity);
}
public OrthographicCamera getCamera() {
return sceneCam;
}
}
In my GameWorld class I construct the ball in this method:
public void initBall() {
Entity entity = new Entity();
float x = Values.WIDTH/2*Values.PPM;
float y = Values.HEIGHT*Values.PPM;
SpriteComponent txt = new SpriteComponent();
txt.region = skin.getSprite("soccerball");
txt.region.setSize(Values.BALL_WIDTH, Values.BALL_HEIGHT);
entity.add(txt);
TransformComponent trs = new TransformComponent();
trs.isHidden = false;
trs.position.x = x;
trs.position.y = y;
entity.add(trs);
BodyComponent bc = new BodyComponent();
BodyDef bodyDef = new BodyDef();
// Set our body to dynamic
bodyDef.type = BodyDef.BodyType.DynamicBody;
// Set our body's starting position in the world
bodyDef.position.set(x, y+Values.BALL_HEIGHT/2);
bc.body = world.createBody(bodyDef);
bc.body.applyAngularImpulse(50f, true);
CircleShape circle = new CircleShape();
circle.setRadius(Values.BALL_WIDTH/2f);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = circle;
fixtureDef.density = 20f;
fixtureDef.friction = 0.4f;
fixtureDef.restitution = 0.6f;
bc.body.createFixture(fixtureDef);
circle.dispose();
entity.add(bc);
engine.addEntity(entity);
}
Here's the screenshot I took during the simulation:
The reason your ball sprite seems to be rendered offset from your box2d body is because your texture's anchor is it's bottom left corner, which means that when you set your texture's position you basically set the position of it's bottom left corner. To properly position your ball you'll need to subtract it's half-width and half-height from it's position:
final float halfWidth = texture.region.getRegionWidth() * 0.5f;
final float halfHeight = texture.region.getRegionHeight() * 0.5f;
texture.region.setPosition(transform.position.x - halfWidth, transform.position.y - halfHeight);
I'll also urge you to read this box2d blog post since, from the code you posted, you don't seem to scale your box2d entites which means that that ball you have is Values.BALL_WIDTH METERS wide. Judging from the screenshot it looks like it's ~2.5m radius. That's one huge ball!
Right I'm working with libGDX/Box2D at the moment and I am completely stumped by a bug I discovered while following this set of tutorials libGDX.
In my game I create an actor and add it to my game stage. The actor is placed on the screen at a given y coordinate. The game simply loops through an animation to simulate running.
The problem is, when I place the actor on the game stage it's y coordinate is somehow given the wrong y coordinate, retrieved using the following code snippet.
body.getPosition().y
The y coordinate of the actor changes from the correct position of 2.5 to an incorrect position of 3.6 on game startup. The actor then operates as normal from it's updated (incorrect) y position.
I've debugged the bejaysus of my code and I can't see anything that would be causing this. Has anyone come across this kind of behaviour before? Is this some kind of Box2D nuance I am not aware of?
Game Actor Class
public abstract class GameActor extends Actor {
protected Body body;
protected UserData userData;
protected Rectangle screenRectangle;
public GameActor() {
}
public GameActor(Body body) {
this.body = body;
this.userData = (UserData) body.getUserData();
screenRectangle = new Rectangle();
}
#Override
public void act(float delta) {
super.act(delta);
if (body.getUserData() != null) {
updateRectangle();
} else {
// This means the world destroyed the body (enemy or runner went out of bounds)
remove();
}
}
public abstract UserData getUserData();
private void updateRectangle() {
screenRectangle.x = transformToScreen(body.getPosition().x - userData.getWidth() / 2);
screenRectangle.y = transformToScreen(body.getPosition().y - userData.getHeight() / 2);
screenRectangle.width = transformToScreen(userData.getWidth());
screenRectangle.height = transformToScreen(userData.getHeight());
}
protected float transformToScreen(float n) {
return Constants.WORLD_TO_SCREEN * n;
}
}
Runner Class
public class Runner extends GameActor {
...
#Override
public void draw(Batch batch, float parentAlpha) {
super.draw(batch, parentAlpha);
float x = screenRectangle.x - (screenRectangle.width * 0.1f);
float y = screenRectangle.y;
float width = screenRectangle.width * 1.5f;
stateTime += Gdx.graphics.getRawDeltaTime();
batch.draw(runningAnimation.getKeyFrame(stateTime, true), x, y - 40, width, screenRectangle.height);
}
}
}
For example a dynamic body is in a corner and is infinitely walking towards a wall (needs to be a static body) in that corner. If another body is walking towards the same direction, and is colliding with the first body, the collision will be completely ignored. It's the only situation where this happens.
Under here the photo:
By the next worldstep, the green rectangle overlaps the red ball. If there wasn't a static body, the collision works fine.
Thanks in advance.
It is specific of Box2D. If One dynamic body is moving towards another dynamic body perpendicularly and there's a static body on their path, dynamic bodies will overlap each other anyway.
To fix this bug, you can use contact listener to catch the moment when bodies collide and directly set the position of the green body according to the position of the red body. Something like that:
public class CoreClass extends Game {
Array<Body> bodies = new Array<Body>();
World world = new World(new Vector2(0.0f, 0.0f), true);
Body bodyRed;
Body bodyGreen;
private float prevX;
private float prevY;
#Override
public void create() {
BodyDef bodyBD = new BodyDef();
bodyBD.type = BodyType.DynamicBody;
FixtureDef bodyFD = new FixtureDef();
bodyFD.density = 1.0f;
bodyFD.friction = 1.0f;
bodyFD.restitution = 1.0f;
bodyRed = world.createBody(bodyBD);
bodyGreen = world.createBody(bodyBD);
bodyRed.createFixture(bodyFD);
bodyGreen.createFixture(bodyFD);
prevX = bodyGreen.getPosition().x; prevY = bodyGreen.getPosition().y;
world.setContactListener(new ContactListener() {
#Override
public void beginContact(Contact contact) {
world.getBodies(bodies);
bodies.get(contact.getChildIndexA()).getPosition().set(prevX, prevY);
}
#Override
public void endContact(Contact contact) {
}
#Override
public void preSolve(Contact contact, Manifold oldManifold) {
}
#Override
public void postSolve(Contact contact, ContactImpulse impulse) {
}
});
}
#Override
public void render() {
super.render();
prevX = bodyGreen.getPosition().x;
prevY = bodyGreen.getPosition().y;
world.step(1/45f, 2, 6);
}
The Jmoneky Engine gives example code for endless randomly generated Terrain. My Problem is, the code has no comments or indicators to edit the View distance. I am trying to use this example code to build a game, but it looks really bad if the render distance is so short you can see the bottom of the world(void)
The Code:
public class TerrainFractalGridTest extends SimpleApplication {
private Material mat_terrain;
private TerrainGrid terrain;
private float grassScale = 64;
private float dirtScale = 16;
private float rockScale = 128;
public static void main(final String[] args) {
TerrainFractalGridTest app = new TerrainFractalGridTest();
app.start();
}
private CharacterControl player3;
private FractalSum base;
private PerturbFilter perturb;
private OptimizedErode therm;
private SmoothFilter smooth;
private IterativeFilter iterate;
#Override
public void simpleInitApp() {
this.flyCam.setMoveSpeed(100f);
ScreenshotAppState state = new ScreenshotAppState();
this.stateManager.attach(state);
// TERRAIN TEXTURE material
this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md");
// Parameters to material:
// regionXColorMap: X = 1..4 the texture that should be appliad to state X
// regionX: a Vector3f containing the following information:
// regionX.x: the start height of the region
// regionX.y: the end height of the region
// regionX.z: the texture scale for the region
// it might not be the most elegant way for storing these 3 values, but it packs the data nicely :)
// slopeColorMap: the texture to be used for cliffs, and steep mountain sites
// slopeTileFactor: the texture scale for slopes
// terrainSize: the total size of the terrain (used for scaling the texture)
// GRASS texture
Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
grass.setWrap(WrapMode.Repeat);
this.mat_terrain.setTexture("region1ColorMap", grass);
this.mat_terrain.setVector3("region1", new Vector3f(15, 200, this.grassScale));
// DIRT texture
Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
dirt.setWrap(WrapMode.Repeat);
this.mat_terrain.setTexture("region2ColorMap", dirt);
this.mat_terrain.setVector3("region2", new Vector3f(0, 20, this.dirtScale));
// ROCK texture
Texture rock = this.assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
rock.setWrap(WrapMode.Repeat);
this.mat_terrain.setTexture("region3ColorMap", rock);
this.mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale));
this.mat_terrain.setTexture("region4ColorMap", rock);
this.mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale));
this.mat_terrain.setTexture("slopeColorMap", rock);
this.mat_terrain.setFloat("slopeTileFactor", 32);
this.mat_terrain.setFloat("terrainSize", 513);
this.base = new FractalSum();
this.base.setRoughness(0.7f);
this.base.setFrequency(1.0f);
this.base.setAmplitude(1.0f);
this.base.setLacunarity(2.12f);
this.base.setOctaves(8);
this.base.setScale(0.02125f);
this.base.addModulator(new NoiseModulator() {
#Override
public float value(float... in) {
return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1);
}
});
FilteredBasis ground = new FilteredBasis(this.base);
this.perturb = new PerturbFilter();
this.perturb.setMagnitude(0.119f);
this.therm = new OptimizedErode();
this.therm.setRadius(5);
this.therm.setTalus(0.011f);
this.smooth = new SmoothFilter();
this.smooth.setRadius(1);
this.smooth.setEffect(0.7f);
this.iterate = new IterativeFilter();
this.iterate.addPreFilter(this.perturb);
this.iterate.addPostFilter(this.smooth);
this.iterate.setFilter(this.therm);
this.iterate.setIterations(1);
ground.addPreFilter(this.iterate);
this.terrain = new TerrainGrid("terrain", 33, 129, new FractalTileLoader(ground, 256f));
this.terrain.setMaterial(this.mat_terrain);
this.terrain.setLocalTranslation(0, 0, 0);
this.terrain.setLocalScale(2f, 1f, 2f);
this.rootNode.attachChild(this.terrain);
TerrainLodControl control = new TerrainGridLodControl(this.terrain, this.getCamera());
control.setLodCalculator(new DistanceLodCalculator(33, 2.7f)); // patch size, and a multiplier
this.terrain.addControl(control);
this.getCamera().setLocation(new Vector3f(0, 300, 0));
this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
}
#Override
public void simpleUpdate(final float tpf) {
}}
So following what J Atkin said about Terrain Grid, I found a endless terrain example that is a terrain grid. The Cells loaded class is protected, which means I have to extends it in the class to access it. In Jmonkey, the main class has to extend a simple application in order to run. Java doesn't allow multiple extensions, therefor I build a second class to allow access.
public class ViewTerrain extends TerrainGrid{
public void setView(int numberofcells){
super.cellsLoaded = numberofcells;
}
}
problem I am having with this class is that I don't know how to keep the original declaration IE.
this.terrain = new TerrainGrid("terrain", 65, 257, new ImageTileLoader(assetManager, new Namer() {
public String getName(int x, int y) {
return "Scenes/TerrainMountains/terrain_" + x + "_" + y + ".png";
}
}));
Looking at the source it seems TerrainGrid dynamically redefines an internal TerrainQuad tree based upon which grid the camera is in and the surrounding grid tiles. It would seem to me that you should define these tiles to be the size of the area you would like visible at any one time. Try updating patchSize in the constructor to be larger.
If I have a GameStage with the following:
...
public GameStage() {
world = WorldUtils.createWorld();
setUpWorld();
setupCamera();
debugRenderer = new Box2DDebugRenderer();
}
private void setUpWorld() {
world = WorldUtils.createWorld();
world.setContactListener(this);
setupGround();
setupBall();
setupJoint();
}
private void setupBall() {
ball = new Ball(WorldUtils.createBall(world));
addActor(ball);
}
private void setupGround() {
ground = new Ground(WorldUtils.createGround(world));
addActor(ground);
}
private void setupJoint() {
jointDef = new MouseJointDef();
jointDef.bodyA = ???
jointDef.collideConnected = true;
jointDef.maxForce = 50f;
}
...
How do I reference the bodies within each actor so that I can add a joint between two bodies. Specifically I am trying to add a mousejoint to the ball body so that I can add a directional impulse (mousejoint is destroyed in touchup or if max impulse is reached). I set the ground as bodyA during the setupJoint method. In the touchdown method I set the bodyB as the ball. I then find the x and y values (unprojecting them first to move from World to local coordinates) from the touch and set the position of the mouse using jointDef.target.set. My problem is I cannot figure out to get the body from the actor. Here is an example of my createBall method in the WorldUtils class.
...
public static Body createBall(World world) {
// BALL
// Body Definition
BodyDef ballDef = new BodyDef();
ballDef.type = BodyDef.BodyType.DynamicBody;
ballDef.position.set(0, 10); // 1 Meter up not 1 pixel
// Ball shape
CircleShape ballShape = new CircleShape();
ballShape.setRadius(.5f);
// Fixture Definition
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = ballShape;
fixtureDef.density = 2.0f;
fixtureDef.friction = .5f; // 0 to 1
fixtureDef.restitution = .5f; // 0 to 1
Body body = world.createBody(ballDef);
body.createFixture(fixtureDef);
body.resetMassData();
body.setUserData(new BallUserData());
ballShape.dispose();
return body;
}
...
My Ball and Ground are defined like this
...
public class Ground extends GameActor {
public Ground(Body body) {
super(body);
}
#Override
public GroundUserData getUserData() {
return (GroundUserData) userData;
}
}
I am sure I am doing something stupid or I am misunstanding how the connection between scene2d and box2d works.
Thanks