I'm trying to implement smooth TMX map scrolling and zooming with SurfaceScrollDetector and PinchZoomDetector.
Here is my code:
public class TMXTiledMapExample extends SimpleBaseGameActivity implements IOnSceneTouchListener, IScrollDetectorListener, IPinchZoomDetectorListener {
private static final int CAMERA_WIDTH = 480;
private static final int CAMERA_HEIGHT = 320;
private SmoothCamera camera;
private TMXTiledMap mTMXTiledMap;
private SurfaceScrollDetector mScrollDetector;
private PinchZoomDetector mPinchZoomDetector;
private float mPinchZoomStartedCameraZoomFactor;
#Override
public EngineOptions onCreateEngineOptions() {
this.camera = new SmoothCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT, 400, 400, 10f);
final CroppedResolutionPolicy canvasSurface = new CroppedResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT);
EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, canvasSurface, this.camera);
if (MultiTouch.isSupported(this)) {
if (MultiTouch.isSupportedDistinct(this)) {
Toast.makeText(this, "MultiTouch detected --> Both controls will work properly!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "MultiTouch detected, but your device has problems distinguishing between fingers.\n\nControls are placed at different vertical locations.", Toast.LENGTH_LONG)
.show();
}
} else {
Toast.makeText(this, "Sorry your device does NOT support MultiTouch!\n\n(Falling back to SingleTouch.)\n\nControls are placed at different vertical locations.", Toast.LENGTH_LONG).show();
}
return engineOptions;
}
#Override
public void onCreateResources() {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
}
#Override
public Scene onCreateScene() {
this.mEngine.registerUpdateHandler(new FPSLogger());
final Scene scene = new Scene();
scene.setOnAreaTouchTraversalFrontToBack();
this.mScrollDetector = new SurfaceScrollDetector(this);
this.mPinchZoomDetector = new PinchZoomDetector(this);
scene.setOnSceneTouchListener(this);
scene.setTouchAreaBindingOnActionDownEnabled(true);
try {
final TMXLoader tmxLoader = new TMXLoader(this.getAssets(), this.mEngine.getTextureManager(), TextureOptions.BILINEAR, this.getVertexBufferObjectManager(),
new ITMXTilePropertiesListener() {
#Override
public void onTMXTileWithPropertiesCreated(final TMXTiledMap pTMXTiledMap, final TMXLayer pTMXLayer, final TMXTile pTMXTile,
final TMXProperties<TMXTileProperty> pTMXTileProperties) {
}
});
this.mTMXTiledMap = tmxLoader.loadFromAsset("tmx/desert2.tmx");
} catch (final TMXLoadException e) {
Debug.e(e);
}
final TMXLayer tmxLayer = this.mTMXTiledMap.getTMXLayers().get(0);
scene.attachChild(tmxLayer);
this.camera.setBounds(0, 0, tmxLayer.getHeight(), tmxLayer.getWidth());
this.camera.setBoundsEnabled(true);
return scene;
}
#Override
public void onScrollStarted(final ScrollDetector pScollDetector, final int pPointerID, final float pDistanceX, final float pDistanceY) {
final float zoomFactor = this.camera.getZoomFactor();
this.camera.offsetCenter(-pDistanceX / zoomFactor, -pDistanceY / zoomFactor);
}
#Override
public void onScroll(final ScrollDetector pScollDetector, final int pPointerID, final float pDistanceX, final float pDistanceY) {
final float zoomFactor = this.camera.getZoomFactor();
this.camera.offsetCenter(-pDistanceX / zoomFactor, -pDistanceY / zoomFactor);
}
#Override
public void onScrollFinished(final ScrollDetector pScollDetector, final int pPointerID, final float pDistanceX, final float pDistanceY) {
final float zoomFactor = this.camera.getZoomFactor();
this.camera.offsetCenter(-pDistanceX / zoomFactor, -pDistanceY / zoomFactor);
}
#Override
public void onPinchZoomStarted(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent) {
this.mPinchZoomStartedCameraZoomFactor = this.camera.getZoomFactor();
}
#Override
public void onPinchZoom(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent, final float pZoomFactor) {
this.camera.setZoomFactor(this.mPinchZoomStartedCameraZoomFactor * pZoomFactor);
}
#Override
public void onPinchZoomFinished(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent, final float pZoomFactor) {
this.camera.setZoomFactor(this.mPinchZoomStartedCameraZoomFactor * pZoomFactor);
}
#Override
public boolean onSceneTouchEvent(final Scene pScene, final TouchEvent pSceneTouchEvent) {
this.mPinchZoomDetector.onTouchEvent(pSceneTouchEvent);
if (this.mPinchZoomDetector.isZooming()) {
this.mScrollDetector.setEnabled(false);
} else {
if (pSceneTouchEvent.isActionDown()) {
this.mScrollDetector.setEnabled(true);
}
this.mScrollDetector.onTouchEvent(pSceneTouchEvent);
}
return true;
}
}
Scrolling and zooming in this code are not very smooth. Where is the issue with it ? How to create nice, inertial scrolling ?
One more question for zooming. How it could be restricted with min and max zoomfactor ? Should I place a special conditions into the IPinchZoomDetectorListener methods or it could be achived with some parameters provided to my camera object ?
Also, what is a "normal" values for the three last SmoothCamera parameters - final float pMaxVelocityX, final float pMaxVelocityY, final float pMaxZoomFactorChange ?
I have done similar type of work before so I can help you in achieving your objective.
First of all if you want zoom in/out functionality then you have to use ZoomCamera in which you have to only give width and height, all other parameters are used by default value.
mZoomCamera = new ZoomCamera(0, 0, Constants.CAMERA_WIDTH,
Constants.CAMERA_HEIGHT);
mZoomCamera.setBounds(0f, 0f, Constants.CAMERA_WIDTH,
Constants.CAMERA_HEIGHT);
mZoomCamera.setBoundsEnabled(true);
In the example code, you see how I set bounds for camera so there is no chance camera can go out of this. And also I thing you get abnormal behaviour because of inappropriate velocity you are providing.
For for setting specific zoom value you can use following code,
private void setCameraZoomFactor(float pZoomFactor) {
newZoomFactor = mPinchZoomStartedCameraZoomFactor * pZoomFactor;
if (newZoomFactor < 1f)
newZoomFactor = 1f;
if (newZoomFactor > 1.5f)
newZoomFactor = 1.5f;
mZoomCamera.setZoomFactor(newZoomFactor);
}
You have to call this method in your onPinchZoom method.
I think now I have given answer of your all of the questions.
Related
I have global variables called:
private int currentLevel;
private int mapHeight;
I have a constructor for my game, where currentLevel = level entered when setScreen() method is called:
public ElevatorLevel(Game g, int level)
{
super(g, level);
currentLevel = level;
}
In my create() method, I set mapHeight to 750 * currentLevel (which starts as one), however when I try to spawn in the blades as seen below, they spawn between -200 and 300. This is because the program is not recognizing currentLevel (I am assuming), so it is multiplying it by nothing, resulting with -200.
public void create()
{
world = new World(new Vector2(0, -9.8f), true);
timeElapsed = 0;
mapHeight = 750 * currentLevel;
blade = new PhysicsActor();
blade.storeAnimation( "", exTex );
blade.setOriginCenter();
blade.circularBoundary();
blade.setMaxSpeed(50);
blade.setDeceleration(50);
bladesList = new ArrayList<PhysicsActor>();
for (int i = 0; i < 3 ; i++)
{
blades = blade.clone();
float xCoord = randomFloatGenerator(440, 20);
float yCoord = randomFloatGenerator(mapHeight - 200, 300);
blades.setPosition(xCoord, yCoord);
mainStage.addActor(blades);
bladesList.add(blades);
}
I also have a Label in my update(float dt) method that is set to:
timeLabel.setText("Level: " + currentLevel);
As seen in the image above, currentLevel is recognized by the label, but not by mapHeight in create().
How do I get currentLevel to be recognized by the create() method?
Below is the standart portrait app code that i use almost in every libgdx project. I never use create method. Instead I use constructor, this code is the most bugfree one that i could have found.
GAME OR MENU SCREEN:
public class TutorialScreen implements Screen {
private OrthographicCamera camera;
public static final float WORLD_HEIGHT = 240;
public static final float WORLD_WIDTH = 135;
private Viewport viewport;
private Stage stage;
private EntryPoint game;
private AdsController adsController;
public TutorialScreen(final EntryPoint game, final AdsController adsController){
this.adsController = adsController;
this.game = game;
adsController.hideBannerAd();
float aspectRatio = (float) (Gdx.graphics.getHeight() / Gdx.graphics.getWidth());
camera = new OrthographicCamera(aspectRatio * WORLD_WIDTH, WORLD_HEIGHT);
camera.setToOrtho(false);
viewport = new FitViewport(WORLD_WIDTH , WORLD_HEIGHT,camera );
stage = new Stage(viewport, game.batch);
}
#Override
public void show() {
}
#Override
public void render(float delta) {
stage.draw();
stage.act();
}
#Override
public void resize(int width, int height) {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void hide() {
}
#Override
public void dispose() {
}
}
ENTRY POINT THAT YOUR APP WILL OPEN FIRST:
package some.package;
public class EntryPoint extends Game {
SpriteBatch batch;
final AdsController adsController;
public EntryPoint(final AdsController adsController ){
this.adsController = adsController; //Interface for admob
}
#Override
public void create () {
batch = new SpriteBatch();
this.setScreen(new YourScreenClass(this,adsController)); //Above is tutorial so this would be new TutorialScreen(this,adsController)
}
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.end();
super.render();
}
#Override
public void dispose () {
batch.dispose();
}
}
Might have missed some curly brackets or ";" but you get the idea. This way you would not encounter any reference issues.
I am a beginner to libgdx android and I want to achieve the below screen in my game
And I wrote the following code
public class MyGdxGame implements ApplicationListener {
private static final int VIRTUAL_WIDTH = 480;
private static final int VIRTUAL_HEIGHT = 800;
private static final float ASPECT_RATIO =(float)VIRTUAL_WIDTH/(float)VIRTUAL_HEIGHT;
private Camera camera;
private Rectangle viewport;
private SpriteBatch sb;
ShapeRenderer sp;
#Override
public void create()
{
sb = new SpriteBatch();
camera = new OrthographicCamera(VIRTUAL_WIDTH, VIRTUAL_HEIGHT);
sp=new ShapeRenderer();
}
#Override
public void render()
{
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// update camera
camera.update();
// camera.apply(Gdx.gl10);
// set viewport
Gdx.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height);
// clear previous frame
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
// DRAW EVERYTHING
float sx=viewport.x;
float sy=viewport.y;
float ex=viewport.getWidth();
float ey=viewport.getHeight();
sp.begin(ShapeRenderer.ShapeType.Line);
sp.setColor(Color.BLACK);
sp.rect(sx,sy,ex,ey);
sp.line(sx,sy,ex,ey);
sp.line(1,1,480,800);
sp.line(1,ey/2,ex-1,ey/2);
sp.line(ex/2,1,ex/2,ey-1);
sp.line(1,ey-1,ex-1,1);
sp.circle(ex/2,ey/2,40);
sp.end();
}
#Override
public void dispose()
{
sp.dispose();
}
#Override
public void resize(int width, int height)
{
// calculate new viewport
float aspectRatio = (float)width/(float)height;
float scale = 1f;
Vector2 crop = new Vector2(0f, 0f);
sb = new SpriteBatch();
camera = new OrthographicCamera(VIRTUAL_WIDTH, VIRTUAL_HEIGHT);
if(aspectRatio > ASPECT_RATIO)
{
scale = (float)height/(float)VIRTUAL_HEIGHT;
crop.x = (width - VIRTUAL_WIDTH * scale) / 2f;
}
else if(aspectRatio < ASPECT_RATIO)
{
scale = (float)width/(float)VIRTUAL_WIDTH;
crop.y = (height - VIRTUAL_HEIGHT*scale)/2f;
}
else
{
scale = (float)width/(float)VIRTUAL_WIDTH;
}
float w = (float)VIRTUAL_WIDTH*scale;
float h = (float)VIRTUAL_HEIGHT*scale;
viewport = new Rectangle(crop.x, crop.y, w, h);
}
}
With the above code I got the actual screen in my device which is correctly fits in my device.
When I run this on some other devices, the lines and rects are unaligned. Like this
How to resolve this?
You can use in this way :
public class GdxTest extends ApplicationAdapter {
OrthographicCamera camera;
ShapeRenderer shapeRenderer;
float screenOffset=10,circleRadius=30;
#Override
public void create() {
camera=new OrthographicCamera();
shapeRenderer=new ShapeRenderer();
shapeRenderer.setAutoShapeType(true);
}
#Override
public void render() {
Gdx.gl.glClearColor(1,1,1,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin();
shapeRenderer.setColor(Color.BLACK);
shapeRenderer.circle(camera.viewportWidth/2,camera.viewportHeight/2,circleRadius);
shapeRenderer.rect(screenOffset,screenOffset,camera.viewportWidth-2*screenOffset,camera.viewportHeight-2*screenOffset);
shapeRenderer.line(screenOffset,screenOffset,camera.viewportWidth-screenOffset,camera.viewportHeight-screenOffset);
shapeRenderer.line(screenOffset,camera.viewportHeight-screenOffset,camera.viewportWidth-screenOffset,screenOffset);
shapeRenderer.line(screenOffset,camera.viewportHeight/2,camera.viewportWidth-screenOffset,camera.viewportHeight/2);
shapeRenderer.line(camera.viewportWidth/2,screenOffset,camera.viewportWidth/2,camera.viewportHeight-screenOffset);
shapeRenderer.end();
}
#Override
public void resize(int width, int height) {
camera.setToOrtho(false,width,height);
screenOffset=width<height?width*.04f:height*.04f;
circleRadius=width<height?width*.075f:height*.075f;
}
#Override
public void dispose() {
shapeRenderer.dispose();
}
}
And the output is :
Im making an app that displays multiple random circles on the screen. I want to know if i can expand the radius WHILE it is displaying the circle then disapeers. I have already written the code to randomly display the circles here it is.
public class SplashLaunch extends View{
Handler cool = new Handler();
DrawingView v;
ObjectAnimator aa = new ObjectAnimator();
Paint newPaint = new Paint();
int randomWidthOne = 0;
int randomHeightOne = 0;
private static int radiusOne = 300;
final int redColorOne = Color.RED;
final int greenColorOne = Color.GREEN;
private static int lastColorOne;
private final Random theRandom = new Random();
public SplashLaunch(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
private final Runnable circleUpdater = new Runnable() {
#SuppressLint("NewApi")
#Override
public void run() {
lastColorOne = theRandom.nextInt(2) == 1 ? redColorOne : greenColorOne;
newPaint.setColor(lastColorOne);
cool.postDelayed(this, 500);
int x = 0;
while(x<=255){
newPaint.setAlpha(x);
x++;
}
invalidate();
}
};
#Override
protected void onAttachedToWindow(){
super.onAttachedToWindow();
cool.post(circleUpdater);
}
protected void onDetachedFromWindow(){
super.onDetachedFromWindow();
cool.removeCallbacks(circleUpdater);
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if(theRandom == null){
randomWidthOne =(int) (theRandom.nextInt(Math.abs(getWidth()-radiusOne/2)) + radiusOne/2f);
randomHeightOne = (theRandom.nextInt((int)Math.abs((getHeight()-radiusOne/2 + radiusOne/2f))));
}else {
randomWidthOne =(int) (theRandom.nextInt(Math.abs(getWidth()-radiusOne/2)) + radiusOne/2f);
randomHeightOne = (theRandom.nextInt((int)Math.abs((getHeight()-radiusOne/2 + radiusOne/2f))));
}
canvas.drawCircle(randomWidthOne, randomHeightOne, radiusOne, newPaint);
}
}
I want to know if i can expand the radius WHILE it is displaying the circle then disappears
Yes you can. You simply animate the Scale of your view to a value greater than 1 to expand the view then back to 0 to make it "disappear".
I have made a class for the level generation and have got so far with it:
public class LevelGenerator {
private Sprite environment;
private float leftEdge, rightEdge, minGap, maxGap, y;
public Enemy enemy;
public LevelGenerator(Sprite environment, float leftEdge, float rightEdge,
float minGap, float maxGap) {
this.environment = environment;
this.leftEdge = leftEdge;
this.rightEdge = rightEdge;
this.minGap = minGap;
this.maxGap = maxGap;
}
public void generate(float topEdge){
if(y + MathUtils.random(minGap, maxGap) < topEdge)
return;
y = topEdge;
float x = MathUtils.random(leftEdge, rightEdge);
}
Basically, what I want to happen is for the enemy block to randomly generate on the sides of the screen. Here is the enemy block class (very simple):
public class Enemy extends Sprite{
public Enemy(Sprite sprite) {
super(sprite);
}
#Override
public void draw(Batch spriteBatch){
super.draw(spriteBatch);
}
}
This is what the game looks like at the moment when the block is just simply drawn on the game screen in a static position: http://i.imgur.com/SIt18Qn.png. What I am trying to achieve is for these "enemy" blocks to spawn randomly on either side of the screen but I can't seem to figure out a way to do it with the code I have so far.
Thank you!
I could not test but I think it will be fine, you have a rectangle if you want to see if it collides with another actor, if so updates its position in the update and draw method, and ramdon method start customizing to see if the coordinates, which colicionan be assigned to another actor rectagulo enemy or bye.
public class overFlowEnemy extends Sprite {
private final float maxH = Gdx.graphics.getHeight();
private final float maxW = Gdx.graphics.getWidth();
private Rectangle rectangle;
private Random random = new Random();
private float inttt = 0;
private float randomN = 0;
private boolean hasCollided = false;
public overFlowEnemy(Sprite sprite) {
super(sprite);
crearEnemigo();
rectangle = new Rectangle(getX(), getY(), getWidth(), getHeight());
}
#Override
public void draw(Batch spriteBatch) {
super.draw(spriteBatch);
}
private void crearEnemigo(){
setX(RandomNumber((int)maxW));
setY(RandomNumber((int)maxH));
}
private int RandomNumber(int pos) {
random.setSeed(System.nanoTime() * (long) inttt);
this.randomN = random.nextInt(pos);
inttt += randomN;
return (int)randomN;
}
public Rectangle getColliderActor(){
return this.rectangle;
}
}
the class as this should create a random enemy.
Edit: rereading your question, is that my English is not very good, and I think you wanted to be drawn only on the sides of the screen if so, tell me or adapts the class because when you create thought, which was across the screen.
I just added another class, if you can and want to work as you tell me which is correct, and delete the other.
public class overFlow extends Sprite {
private final float maxH = Gdx.graphics.getHeight();
private final float maxW = Gdx.graphics.getWidth();
private Rectangle rectangle;
private Random random = new Random();
private float inttt = 0;
private float randomN = 0;
private boolean hasCollided = false;
public overFlow(Sprite sprite) {
super(sprite);
crearEnemigo();
rectangle = new Rectangle(getX(), getY(), getWidth(), getHeight());
}
#Override
public void draw(Batch spriteBatch) {
super.draw(spriteBatch);
}
private void crearEnemigo(){
setX(RandomNumber((int)maxW, true));
setY(RandomNumber((int)maxH, false));
}
private int RandomNumber(int pos, boolean w) {
random.setSeed(System.nanoTime() * (long) inttt);
if (w = true){
this.randomN = random.nextInt((pos));
if(randomN % 2 == 0){
randomN = (pos - getWidth());
}else{
randomN = 0; //left screen
}
}else{
this.randomN = random.nextInt(pos - (int)getHeight());
}
inttt += randomN;
return (int)randomN;
}
public Rectangle getColliderActor(){
return this.rectangle;
}
}
I'm attempting to use notification badges as described HERE
It works flawlessly on my 4.4.2 device, but on my 4.0.4 device I get a crash on open.
I am getting this error and a force close even inside a try/catch.
Here is the offending code:
try {
Utils.setBadgeCount(this, ordersIcon, ordersCount, getResources().getColor(R.color.red_text));
Utils.setBadgeCount(this, balancesIcon, balancesCount, getResources().getColor(R.color.green_text));
} catch (Exception e) {
Toast.makeText(this, "Dynamic menu icons not available on this device.", Toast.LENGTH_LONG).show();
}
Commenting out the two lines inside the try block stops the fatal exception from being thrown.
Here's the setBadgeCount method.
public static void setBadgeCount(Context context, LayerDrawable icon, int count, int color) {
BadgeDrawable badge;
Drawable reuse = icon.findDrawableByLayerId(R.id.ic_badge);
if (reuse != null && reuse instanceof BadgeDrawable) {
badge = (BadgeDrawable) reuse;
} else {
badge = new BadgeDrawable(context, color);
}
badge.setCount(count);
icon.mutate();
icon.setDrawableByLayerId(R.id.ic_badge, badge);
}
And the BadgeDrawable class:
public class BadgeDrawable extends Drawable {
private float mTextSize;
private Paint mBadgePaint;
private Paint mTextPaint;
private Rect mTxtRect = new Rect();
private String mCount = "";
private boolean mWillDraw = false;
public BadgeDrawable(Context context, int color) {
mTextSize = context.getResources().getDimension(R.dimen.badge_text_size);
mBadgePaint = new Paint();
mBadgePaint.setColor(color);
mBadgePaint.setAntiAlias(true);
mBadgePaint.setStyle(Paint.Style.FILL);
mTextPaint = new Paint();
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
mTextPaint.setTextSize(mTextSize);
mTextPaint.setAntiAlias(true);
mTextPaint.setTextAlign(Paint.Align.CENTER);
}
#Override
public void draw(Canvas canvas) {
if (!mWillDraw) {
return;
}
Rect bounds = getBounds();
float width = bounds.right - bounds.left;
float height = bounds.bottom - bounds.top;
// Position the badge in the top-right quadrant of the icon.
float radius = ((Math.min(width, height) / 2) - 1) / 2;
float centerX = width - radius - 1;
float centerY = radius + 1;
// Draw badge circle.
canvas.drawCircle(centerX, centerY, radius, mBadgePaint);
// Draw badge count text inside the circle.
mTextPaint.getTextBounds(mCount, 0, mCount.length(), mTxtRect);
float textHeight = mTxtRect.bottom - mTxtRect.top;
float textY = centerY + (textHeight / 2f);
canvas.drawText(mCount, centerX, textY, mTextPaint);
}
/*
* Sets the count (i.e notifications) to display.
*/
public void setCount(int count) {
mCount = Integer.toString(count);
// Only draw a badge if there are notifications.
mWillDraw = count > 0;
invalidateSelf();
}
#Override
public void setAlpha(int alpha) {
// do nothing
}
#Override
public void setColorFilter(ColorFilter cf) {
// do nothing
}
#Override
public int getOpacity() {
return PixelFormat.UNKNOWN;
}
}
Any ideas?
Use throwable instead of Exception
try {
Utils.setBadgeCount(this, ordersIcon, ordersCount, getResources().getColor(R.color.red_text));
Utils.setBadgeCount(this, balancesIcon, balancesCount, getResources().getColor(R.color.green_text));
} catch (**Throwable** e) {
Toast.makeText(this, "Dynamic menu icons not available on this device.", Toast.LENGTH_LONG).show();
}