I'm developing an Unity-Android Plugin to record game screen and create a mp4 video file.I follow to Android Breakout game recorder patch sample in this site : http://bigflake.com/mediacodec/.
First, I create my CustomUnityPlayer class that extends UnityPlayer class and override onDrawFrame method.Here is my CustomUnityPlayer class code :
package com.example.screenrecorder;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.ContextWrapper;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
import com.unity3d.player.*;
public class CustomUnityPlayer extends UnityPlayer implements GLSurfaceView.Renderer {
public static final String TAG = "ScreenRecord";
public static final boolean EXTRA_CHECK = true; // enable additional assertions
private GameRecorder recorder;
static final float mProjectionMatrix[] = new float[16];
private final float mSavedMatrix[] = new float[16];
private EGLDisplay mSavedEglDisplay;
private EGLSurface mSavedEglDrawSurface;
private EGLSurface mSavedEglReadSurface;
private EGLContext mSavedEglContext;
// Frame counter, used for reducing recorder frame rate.
private int mFrameCount;
static final float ARENA_WIDTH = 768.0f;
static final float ARENA_HEIGHT = 1024.0f;
private int mViewportWidth, mViewportHeight;
private int mViewportXoff, mViewportYoff;
private final float[] mViewMatrix = new float[16];
private final float[] mRotationMatrix = new float[16];
private float mAngle;
public CustomUnityPlayer(ContextWrapper context) {
// TODO Auto-generated constructor stub
super(context);
this.recorder = GameRecorder.getInstance();
}
private boolean recordThisFrame() {
final int TARGET_FPS = 30;
mFrameCount ++;
switch (TARGET_FPS) {
case 60:
return true;
case 30:
return (mFrameCount & 0x01) == 0;
case 24:
// want 2 out of every 5 frames
int mod = mFrameCount % 5;
return mod == 0 || mod == 2;
default:
return true;
}
}
public void onDrawFrame(GL10 gl){
//record this frame
if (this.recorder.isRecording() && this.recordThisFrame()) {
saveRenderState();
// switch to recorder state
this.recorder.makeCurrent();
super.onDrawFrame(gl);
this.recorder.getProjectionMatrix(mProjectionMatrix);
this.recorder.setViewport();
this.recorder.swapBuffers();
restoreRenderState();
}
}
public void onSurfaceCreated(GL10 paramGL10, EGLConfig paramEGLConfig){
// now repeat it for the game recorder
if (this.recorder.isRecording()) {
Log.d(TAG, "configuring GL for recorder");
saveRenderState();
this.recorder.firstTimeSetup();
super.onSurfaceCreated(paramGL10, paramEGLConfig);
this.recorder.makeCurrent();
//glSetup();
restoreRenderState();
mFrameCount = 0;
}
if (EXTRA_CHECK) Util.checkGlError("onSurfaceCreated end");
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
/*
* We want the viewport to be proportional to the arena size. That way a 10x10
* object in arena coordinates will look square on the screen, and our round ball
* will look round.
*
* If we wanted to fill the entire screen with our game, we would want to adjust the
* size of the arena itself, not just stretch it to fit the boundaries. This can have
* subtle effects on gameplay, e.g. the time it takes the ball to travel from the top
* to the bottom of the screen will be different on a device with a 16:9 display than on
* a 4:3 display. Other games might address this differently, e.g. a side-scroller
* could display a bit more of the level on the left and right.
*
* We do want to fill as much space as we can, so we should either be pressed up against
* the left/right edges or top/bottom.
*
* Our game plays best in portrait mode. We could force the app to run in portrait
* mode (by setting a value in AndroidManifest, or by setting the projection to rotate
* the world to match the longest screen dimension), but that's annoying, especially
* on devices that don't rotate easily (e.g. plasma TVs).
*/
super.onSurfaceChanged(unused, width, height);
if (EXTRA_CHECK) Util.checkGlError("onSurfaceChanged start");
float arenaRatio = ARENA_HEIGHT / ARENA_WIDTH;
int x, y, viewWidth, viewHeight;
if (height > (int) (width * arenaRatio)) {
// limited by narrow width; restrict height
viewWidth = width;
viewHeight = (int) (width * arenaRatio);
} else {
// limited by short height; restrict width
viewHeight = height;
viewWidth = (int) (height / arenaRatio);
}
x = (width - viewWidth) / 2;
y = (height - viewHeight) / 2;
Log.d(TAG, "onSurfaceChanged w=" + width + " h=" + height);
Log.d(TAG, " --> x=" + x + " y=" + y + " gw=" + viewWidth + " gh=" + viewHeight);
GLES20.glViewport(x, y, viewWidth, viewHeight);
mViewportXoff = x;
mViewportYoff = y;
mViewportWidth = viewWidth;
mViewportHeight = viewHeight;
// Create an orthographic projection that maps the desired arena size to the viewport
// dimensions.
//
// If we reversed {0, ARENA_HEIGHT} to {ARENA_HEIGHT, 0}, we'd have (0,0) in the
// upper-left corner instead of the bottom left, which is more familiar for 2D
// graphics work. It might cause brain ache if we want to mix in 3D elements though.
Matrix.orthoM(mProjectionMatrix, 0, 0, ARENA_WIDTH,
0, ARENA_HEIGHT, -1, 1);
Log.d(TAG, "onSurfaceChangedEnd 1 w=" + width + " h=" + height);
if (EXTRA_CHECK) Util.checkGlError("onSurfaceChanged end");
Log.d(TAG, "onSurfaceEnded w=" + width + " h=" + height);
}
public void pause(){
super.pause();
this.recorder.gamePaused();
}
/**
* Saves the current projection matrix and EGL state.
*/
public void saveRenderState() {
System.arraycopy(mProjectionMatrix, 0, mSavedMatrix, 0, mProjectionMatrix.length);
mSavedEglDisplay = EGL14.eglGetCurrentDisplay();
mSavedEglDrawSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
mSavedEglReadSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_READ);
mSavedEglContext = EGL14.eglGetCurrentContext();
}
/**
* Saves the current projection matrix and EGL state.
*/
public void restoreRenderState() {
// switch back to previous state
if (!EGL14.eglMakeCurrent(mSavedEglDisplay, mSavedEglDrawSurface, mSavedEglReadSurface,
mSavedEglContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
System.arraycopy(mSavedMatrix, 0, mProjectionMatrix, 0, mProjectionMatrix.length);
}
}
And then, i create a CustomUnityPlayerActivity to call this class
package com.example.screenrecorder;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import com.unity3d.player.UnityPlayerActivity;
public class CustomUnityActivity extends UnityPlayerActivity {
private CustomUnityPlayer mUnityPlayer;
private GameRecorder mRecorder;
#Override
protected void onCreate(Bundle paramBundle){
Log.e("ScreenRecord","oncreate");
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(paramBundle);
this.mUnityPlayer = new CustomUnityPlayer(this);
if (this.mUnityPlayer.getSettings().getBoolean("hide_status_bar", true))
getWindow().setFlags(1024, 1024);
int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1);
boolean trueColor8888 = false;
mUnityPlayer.init(glesMode, trueColor8888);
View playerView = mUnityPlayer.getView();
setContentView(playerView);
playerView.requestFocus();
this.mRecorder = GameRecorder.getInstance();
this.mRecorder.prepareEncoder(this);
}
public void beginRecord(){
Log.e("ScreenRecord","start record");
this.mUnityPlayer.saveRenderState();
this.mRecorder.firstTimeSetup();
this.mRecorder.setStartRecord(true);
this.mRecorder.makeCurrent();
this.mUnityPlayer.restoreRenderState();
}
public void endRecord(){
Log.e("ScreenRecord","end record");
this.mRecorder.endRecord();
this.mRecorder.setStartRecord(false);
//this.mTransView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
public boolean isRecording(){
return this.mRecorder.isRecording();
}
protected void onDestroy()
{
super.onDestroy();
this.mUnityPlayer.quit();
}
protected void onPause()
{
super.onPause();
this.mUnityPlayer.pause();
}
protected void onResume()
{
super.onResume();
this.mUnityPlayer.resume();
}
public void onConfigurationChanged(Configuration paramConfiguration)
{
super.onConfigurationChanged(paramConfiguration);
this.mUnityPlayer.configurationChanged(paramConfiguration);
}
public void onWindowFocusChanged(boolean paramBoolean)
{
super.onWindowFocusChanged(paramBoolean);
this.mUnityPlayer.windowFocusChanged(paramBoolean);
}
public boolean onKeyDown(int paramInt, KeyEvent paramKeyEvent)
{
return this.mUnityPlayer.onKeyDown(paramInt, paramKeyEvent);
}
public boolean onKeyUp(int paramInt, KeyEvent paramKeyEvent)
{
return this.mUnityPlayer.onKeyUp(paramInt, paramKeyEvent);
}
}
My problem is that a video file is created successful but my game can't render anything.I read on Android Media Codec sample site and recognize that each frame would be render twice (once for the display, once for the video) but I can't do this in Unity.Whenever I try to call super.onDrawFrame(gl) twice in onDrawFrame method , my game will be crashed.
Any solution for my problem? Any help will be greatly appreciated!
Thanks and best regard!
Huy Tran
Finally I use a FrameBufferObject (FBO) to render offscreen and get its binding texture to blit twice:
Render to video surface
Redraw to device screen
You can find more detail about this solution by reference to my another question use FBO to record Unity gamescreen
Kamcord plug-in can help you : http://www.kamcord.com/
Related
I have a program where I am drawing some lines on each frame using ShapeRenderer. On each frame I want to draw on top the previous, faded by a amount, frame.
So what I am doing is first draw a black rectangle with an alpha less than 1 and then draw the current frame. On desktop it works well and the result is something like this
But on Android it does not work. It renders like this.
I tried enabling blending before drawing rectangle
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
Also tried seting the alpha channel to 8 bits in AndroidApplicationConfiguration (config.a = 8)
Nothing of these worked. What else can I try?
Also is there a better way to achieve what I want?
Screen code
package com.waterpaw.trochoids.screen;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.waterpaw.trochoids.Trochoid;
import com.waterpaw.trochoids.Trochoids;
public class GameScreen extends BaseScreen {
private ShapeRenderer sr;
private ScreenViewport viewport;
private List<Trochoid> trochoids = new ArrayList<Trochoid>();
public GameScreen(Trochoids game) {
super(game);
viewport = new ScreenViewport();
Gdx.input.setInputProcessor(this);
sr = new ShapeRenderer();
}
private int resized;
#Override
public void resize(int width, int height) {
viewport.update(width, height);
trochoids.clear();
trochoids.add(new Trochoid(viewport.getWorldWidth(), viewport.getWorldHeight()));
resized = 2;
}
#Override
public void render(float delta) {
if(resized-- > 0) {
Gdx.gl.glClearColor(0f, 0f, 0f, 0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
processInput();
if(!trochoids.get(0).isPaused()) {
updateTrochoids();
renderTrochoids();
}
}
private void processInput() {
if(Gdx.input.isKeyJustPressed(Keys.P)) {
if(trochoids.get(0).isPaused()) {
trochoids.get(0).unpause();
} else {
trochoids.get(0).pause();
}
} else if(Gdx.input.isKeyJustPressed(Keys.SPACE)) {
touchDown(0, 0, 0, 0);
}
}
#Override
public void dispose() {
sr.dispose();
}
#Override
public boolean touchDown(int x, int y, int pointer, int button) {
trochoids.get(0).initialize();
return true;
}
public void renderTrochoids() {
sr.setProjectionMatrix(viewport.getCamera().combined);
float w = viewport.getWorldWidth();
float h = viewport.getWorldHeight();
sr.setColor(0, 0, 0, 0.1f);
sr.begin(ShapeRenderer.ShapeType.Filled);
sr.rect(-w / 2, -h / 2, w, h);
sr.end();
sr.begin(ShapeRenderer.ShapeType.Line);
for(int i = 0; i < trochoids.size(); i++) {
trochoids.get(i).render(sr);
}
sr.end();
}
private void updateTrochoids() {
for(int i = 0; i < trochoids.size(); i++) {
trochoids.get(i).update();
}
}
}
LibGDX uses double buffering on Android, so your technique won't work. Draw to a FrameBuffer so you can ensure your previous drawing is not cleared every other frame. Then draw the FrameBuffer's color texture to the screen on every frame.
I'm trying to set the camera position to the position of a body, but when I do this, the body will jump very noticeably. You can see this with the debug renderer but I have a sprite attached in my code. The jump is always in the direction that the sprite is headed. I've got a fixed time step with interpolation, and I update the sprites position to an interpolated value of the current and last position of a box2d body every frame. Then set the camera to the interpolated position.
import java.util.Random;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.Input.Orientation;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.JointEdge;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.TimeUtils;
import com.badlogic.gdx.utils.viewport.FitViewport;
public class GameScreen implements Screen {
private static learnGame game;
private static OrthographicCamera camera;
private static FitViewport viewport;
private static Random rand;
private static BitmapFont font;
private static Vector3 touch;
private static double frameTime;
private static double accumulator;
private static float animTime = 0f;
private static float step = 1f / 60f;
private static boolean killBody;
private static Buttons buttons;
public static Player tom;
public static InputMultiplexer multiplexer;
public static Bodies world;
public static boolean paused;
private static Sprite tomSprite = new Sprite(new Texture(Gdx.files.internal("chars/bob.png")));
public GameScreen(learnGame learngame) {
GameScreen.game = learngame;
multiplexer = new InputMultiplexer();
Gdx.input.setInputProcessor(multiplexer);
camera = new OrthographicCamera();
viewport = new FitViewport(40, 22.5f, camera);
buttons = new Buttons(game, multiplexer, viewport); //HUD stuff
world = new Bodies(viewport, multiplexer); //creates a box2d world
tom = new Player(world.box2d, 10, 15, 1f, multiplexer); //creates a body with a CircleShape with a radius of 1. Catches user input to apply forces to the body
Assets.loadSprites();
world.box2d.getBodies(world.bodies);
font = new BitmapFont();
touch = new Vector3();
rand = new Random();
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
learnGame.batch.setProjectionMatrix(camera.combined);
frameTime = 0;
if (!paused) {
frameTime = Math.min(Gdx.graphics.getRawDeltaTime(), 0.25);
accumulator += frameTime;
tom.update(); //here I apply forces to the body that I attach a camera to
generalUpdate(delta, touch, camera);
updatePositions(); //get previous positions for interpolation
while (accumulator >= step) {
world.box2d.step(step, 6, 2);
accumulator -= step;
interpolate((float) (accumulator / step));
}
world.box2d.clearForces();
learnGame.batch.begin();
if (tom.getBody().isActive()) {
tomSprite.setSize(tom.getHeight(), tom.getHeight());
tomSprite.setOriginCenter();
tomSprite.setRotation(Assets.tom.angle);//this doesn't work right but not the point.
tomSprite.setPosition(Assets.tom.pos.x, Assets.tom.pos.y);
tomSprite.draw(learnGame.batch);
}
learnGame.batch.end();
cameraUpdate(); //update the camera to the same position as the sprite
} else { //else pause the game
learnGame.batch.begin();
learnGame.batch.draw(Assets.pauset, viewport.getCamera().position.x
- (viewport.getCamera().viewportWidth / 2), viewport.getCamera().position.y
- (viewport.getCamera().viewportHeight / 2), viewport.getWorldWidth(), viewport.getWorldHeight());
learnGame.batch.end();
if (Gdx.input.isKeyJustPressed(Keys.SPACE) || Gdx.input.justTouched()) {
paused = false;
}
}
//destroy fixtures and bodies outside of the world step
for (Fixture fixture : Bodies.fixturesToDestroy) {
if (Bodies.destroyJoint == true) {
world.box2d.destroyJoint(Bodies.joint);
Bodies.joint = null;
Bodies.destroyJoint = false;
}
fixture.getBody().destroyFixture(fixture);
Bodies.fixturesToDestroy.removeValue(fixture, true);
}
for (Body body : Bodies.bodiesToDestroy) {
world.box2d.destroyBody(body);
body.setActive(false);
Bodies.bodiesToDestroy.removeValue(body, true);
}
}
#Override
public void show() {
Assets.firstSound.play();
}
#Override
public void resize(int width, int height) {
viewport.update(width, height, true);
Assets.reloadFont();
}
#Override
public void pause() {
paused = true;
}
#Override
public void resume() {
}
#Override
public void hide() {
paused = true;
}
#Override
public void dispose() {
Bodies.box2d.dispose();
Bodies.debugRenderer.dispose();
Buttons.stage.dispose();
Assets.cFrame.getTexture().dispose();
Assets.firstSound.dispose();
System.out.println("disposed");
}
public void generalUpdate(float delta, Vector3 touch, OrthographicCamera camera) {
if (Gdx.input.isKeyPressed(Keys.PAGE_UP)) {
camera.zoom -= 2f * Gdx.graphics.getDeltaTime();
;
} else if (Gdx.input.isKeyPressed(Keys.PAGE_DOWN)) {
camera.zoom += 2f * Gdx.graphics.getDeltaTime();
}
}
public static void cameraUpdate() {
camera.position.set(Assets.tom.pos, 0);
System.out.println("cam:" + Assets.tom.pos.x);
camera.update();
}
public static void kill() {
world.box2d.dispose();
world.debugRenderer.dispose();
Assets.cFrame.getTexture().dispose();
}
public void updatePositions() {
for (MySprite name : Assets.spriteList) {
name.prevPos = name.body.getTransform().getPosition();
name.prevAngle = name.body.getTransform().getRotation();
}
}
public void interpolate(float alpha) {
for (MySprite name : Assets.spriteList) {
name.pos.x = (name.body.getTransform().getPosition().x) * alpha + name.prevPos.x * (1.0f - alpha);
name.pos.y = (name.body.getTransform().getPosition().y) * alpha + name.prevPos.y * (1.0f - alpha);
name.angle = (name.body.getTransform().getRotation() * alpha + name.prevAngle * (1.0f - alpha));
}
}
}
I have tested my interpolation implementation without the camera moving and it appears to work fine. I've been able to test this on my desktop and on my android. The sprite "jumps" a lot more on my android but it happens on both devices. Not sure where I went wrong here, would really appreciate some input!
It's an old question, I know, but I just solved my Problem today.
I have implemented a fixed Timestep and interpolation, but my body lagged and jittered and jumped like there would be no tomorrow.
Please do following things to your camera Movement:
Use the interpolated Position from the body, NOT the original body position.
Use camera.position.slerp(x, y, z, alpha) to do a smooth movement. (You need to play a bit with the variables.
A really simple Example:
static Vector3 desiredPosition;
static {
desiredPosition = new Vector3(0, 0, 0);
}
public static moveCam(Player player, Camera camera){
desiredPosition.x = player.interpolatedPos.x;
desiredPosition.y = player.interpolatedPos.y;
camera.slerp(desiredPosition, Gdx.graphics.getDeltaTime * 5);
camera.update();
}
and in your render do the following
render(float delta){
moveCam(player, camera);
}
(Instant position setting caused jitter for my moving Object)
I wanted to give up my project, because of this bug. But I managed to do it. I hope it'll help people out there.
€dit: A guess why the jumping body is happening:
The interpolated position is not synchron with the camera (set Position then update world(now interpolated pos is offset with camera), then draw)
But I'm not sure with this.
I am trying to make a cannon game that is printed in an Android Developer's book and having trouble with the drawing of the actual game. I can hear my sounds, the alert dialog at the end of the game is being shown after my timer runs out even though I cant see the timer, and the white background is shown, just no game elements
Here is the main activity:
//CannonGame.java
//Main Activity for the Cannon game app.
package com.deitel.cannongame;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.media.AudioManager;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.GestureDetector.SimpleOnGestureListener;
public class CannonGame extends Activity {
private GestureDetector gestureDetector; //listens for double taps
private CannonView cannonView; //custom view to display the game
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //call super's onCreate method
setContentView(R.layout.main); //inflate the layout
//get the cannonview
cannonView = (CannonView) findViewById(R.id.cannonView);
//initialize the GuestureDetector
gestureDetector = new GestureDetector(this, gestureListener);
//allow for volume keys to set game volume
setVolumeControlStream(AudioManager.STREAM_MUSIC);
} //end method onCreate
//when the app is pushed to the background, pause it
#Override
public void onPause(){
super.onPause(); //call the super method
cannonView.stopGame(); //terminates the game
} //end method onPause
//release resources
#Override
protected void onDestroy(){
super.onDestroy();
cannonView.releaseResources();
} //end method onDestroy
//called when the user touches the screen in this Activity
#Override
public boolean onTouchEvent(MotionEvent event){
//get int representing the type of action which caused this event
int action = event.getAction();
//the user touched the screen or dragged aong the screen
if (action == (MotionEvent.ACTION_DOWN) || action == MotionEvent.ACTION_MOVE)
cannonView.alignCannon(event); //align the cannon
//call the GuestureDetector's onTouchEvent method
return gestureDetector.onTouchEvent(event);
} //end onTouchEvent
//listens for touch events sent to the GuestureDetector
SimpleOnGestureListener gestureListener = new SimpleOnGestureListener(){
//called when the use doubletaps the screen
#Override
public boolean onDoubleTap(MotionEvent e){
cannonView.fireCannonball(e); //fire the cannonball
return true; //the event was handled
} //end method onDoubleTap
}; //end gestureListener
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.cannon_game, menu);
return true;
}
}
Here is the custom view the main activity calls and all it's properties:
//CannonView.java
//Displays the Cannon Game
package com.deitel.cannongame;
import java.util.HashMap;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Paint;
import android.media.SoundPool;
import android.media.AudioManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class CannonView extends SurfaceView implements SurfaceHolder.Callback{
private CannonThread cannonThread; //controls the game loop
private Activity activity; //to display Game Over dialog in GUI thread
private boolean dialogIsDisplayed = false;
//constants for game play
public static final int TARGET_PIECES = 7; //sections in the target
public static final int MISS_PENALTY = 2; //seconds deducted on a miss
public static final int HIT_REWARD = 3; //seconds added on a hit
//variables for the game loop and tracking statistics
private boolean gameOver; //is the game over?
private double timeLeft; //the amount of time left in seconds
private int shotsFired; //the number of shots the user has fired
private double totalTimeElapsed; //the number of seconds elapsed
//variables for the blocker and target
private Line blocker; //start and end points of the blocker
private int blockerDistance; //blocker distance from the left
private int blockerBeginning; //blocker distance from the top
private int blockerEnd; //blocker bottom edge distance from the top
private int initialBlockerVelocity; //initial blocker speed multiplier
private float blockerVelocity; //blocker speed multiplier during game
private Line target; //start and end points of the target
private int targetDistance; //target distance from left
private int targetBeginning; //target distance from the top
private double pieceLength; //length of target piece
private int targetEnd; //target bottom distance from the top
private int initialTargetVelocity; //initial target speed multiplier
private float targetVelocity; //target speed multiplier during game
private int lineWidth; //width of the target and blocker
private boolean[] hitStates; //is each target piece hit?
private int targetPiecesHit; //number of target pieces hit (out of 7)
//variables for the cannon and cannonball
private Point cannonball; //cannonball image's upper-left corner
private int cannonballVelocityX; //cannonball's x velocity
private int cannonballVelocityY; //cannonball's y velocity
private boolean cannonballOnScreen; //is the cannonball on the screen
private int cannonballRadius; //cannonball radius
private int cannonballSpeed; //cannonball speed
private int cannonBaseRadius; //cannon base radius
private int cannonLength; //cannon barrel length
private Point barrelEnd; //the endpoint of the cannon's barrel
private int screenWidth; //width of the screen
private int screenHeight; //height ofthe screen
//constants and variables for managing sounds
private static final int TARGET_SOUND_ID = 0;
private static final int CANNON_SOUND_ID = 1;
private static final int BLOCKER_SOUND_ID = 2;
private SoundPool soundPool; //plays sound effects
private Map<Integer, Integer> soundMap; //maps IDs to SoundPool
//Paint vairables used when drawing each item on the screen
private Paint textPaint; //paint used to draw text
private Paint cannonballPaint; //paint used to draw the cannonball
private Paint cannonPaint; //paint used to draw the cannon
private Paint blockerPaint; //paint used to draw the blocker
private Paint targetPaint; //paint used to draw the target
private Paint backgroundPaint; //paint used to clear the drawing area
//public constructor
public CannonView(Context context, AttributeSet attrs){
super(context, attrs); //call super's constructor
activity = (Activity) context;
//register SurfaceHolder.Calback listener
getHolder().addCallback(this);
//initiate Lines and points representing game items
blocker = new Line(); //create the blocker as a Line
target = new Line(); //create the target as a Line
cannonball = new Point(); //create the cannonball as a point
//initialize hitStates as a boolean array
hitStates = new boolean[TARGET_PIECES];
//initialize SoundPool to play the app's three sound effects
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
//create Map of sounds and pre-load sounds
soundMap = new HashMap<Integer, Integer>(); //create new HashMap
soundMap.put(TARGET_SOUND_ID, soundPool.load(context, R.raw.target_hit, 1));
soundMap.put(CANNON_SOUND_ID, soundPool.load(context, R.raw.cannon_fire, 1));
soundMap.put(BLOCKER_SOUND_ID, soundPool.load(context, R.raw.blocker_hit, 1));
//construct Paints for drawing text, cannonball, cannon, blocker, and target; these are configured
//in method onSizeChanged
textPaint = new Paint(); //paint for drawing text
cannonballPaint = new Paint(); //paint for drawing the cannonball
cannonPaint = new Paint(); //paint for drawing the cannon
blockerPaint = new Paint(); //Paint for drawing the blocker
targetPaint = new Paint(); //Paint for drawing the target
backgroundPaint = new Paint(); //paint for drawing the background
} //end CannonView constructor
//called when the size of this view changes --including when this view is first added to the view hierarchy
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
screenWidth = w; //store the width
screenHeight = h; //store the height
cannonBaseRadius = h / 18; //cannon base radius 1/18 screen height
cannonLength = w / 8; //cannon length 1/8 screen width
cannonballRadius = w / 36; //cannonball radious 1/36 screen width
cannonballSpeed = w * 3 / 2; //cannonball speed multiplier
lineWidth = w / 24; //target and blocker 1/24 screen width
//configure instance variables related to the blocker
blockerDistance = w * 5 / 8; //blocker 5/8 screen width from left
blockerBeginning = h / 8; //distance from top 1/8 screen height
blockerEnd = h * 3 / 8; //distance from top 3/8 screen height
initialBlockerVelocity = h / 2; //initial blocker speed multiplier
blocker.start = new Point(blockerDistance, blockerBeginning);
blocker.end = new Point(blockerDistance, blockerEnd);
//configure instance variables related to the target
targetDistance = w * 7 / 8; //target 7/8 screen width from left
targetBeginning = h / 8; //distance from top 1/8 screen height
targetEnd = h * 7 / 8; //distance from top 7/8 screen height
pieceLength = (targetEnd - targetBeginning) / TARGET_PIECES;
initialTargetVelocity = -h / 4; //initial target speed multiplier
target.start = new Point(targetDistance, targetBeginning);
target.end = new Point(targetDistance, targetEnd);
//endpoint of the cannon's barrel initially points horizontally
barrelEnd = new Point(cannonLength, h / 2);
//configure Paint objects for drawing game elements
textPaint.setTextSize(w / 20); //text size 1/20 of screen width
textPaint.setAntiAlias(true); //smoothes the text
cannonPaint.setStrokeWidth(lineWidth * 1.5f); //set line thickness
blockerPaint.setStrokeWidth(lineWidth); //set line thickness
targetPaint.setStrokeWidth(lineWidth); //set line thickness
backgroundPaint.setColor(Color.WHITE); //set background color
newGame(); //set up and start a new game
} //end method onSizeChange
public void newGame(){
//set every element of hitStates to false--restores target pieces
for (int i = 0; i < TARGET_PIECES; ++i)
hitStates[i] = false;
targetPiecesHit = 0; //no target pieces have been hit
blockerVelocity = initialBlockerVelocity; //set initial blocker velocity
targetVelocity = initialTargetVelocity; //set initial target velocity
timeLeft = 10; //start the countdown at 10 seconds
cannonballOnScreen = false; //the cannonball is not on the screen
shotsFired = 0; //set the initial number of shots fired
totalTimeElapsed = 0.0; //set the time elapsed to zero
blocker.start.set(blockerDistance, blockerEnd);
blocker.end.set(blockerDistance, blockerEnd);
target.start.set(targetDistance, targetBeginning);
target.end.set(targetDistance, targetEnd);
if (gameOver){
gameOver = false; //the game is not over
cannonThread = new CannonThread(getHolder());
cannonThread.start();
} //end if
} //end method newGame
//called repleatedly by the CannonThread to update the game elements
private void updatePositions(double elapsedtimeMS){
double interval = elapsedtimeMS / 1000.0; //convert to seconds
if(cannonballOnScreen){ //if there is currently a shot fired
//update cannonball position
cannonball.x += interval * cannonballVelocityX;
cannonball.y += interval * cannonballVelocityY;
//check for collision with blocker
if(cannonball.x + cannonballRadius > blockerDistance && cannonball.x - cannonballRadius
< blockerDistance && cannonball.y + cannonballRadius > blocker.start.y && cannonball.y - cannonballRadius < blocker.end.y){
cannonballVelocityX *= -1; //reverse cannonball's direction
timeLeft -= MISS_PENALTY; //penalize the user
//play blocker sound
soundPool.play(soundMap.get(BLOCKER_SOUND_ID), 1, 1, 1, 0, 1f);
} //end if
//check for collisions with left and right walls
else if(cannonball.x + cannonballRadius > screenWidth || cannonball.x - cannonballRadius < 0)
cannonballOnScreen = false; //remove cannonball from screen
else if(cannonball.y + cannonballRadius > screenHeight || cannonball.y - cannonballRadius < 0)
cannonballOnScreen = false; //remove cannonball from screen
else if(cannonball.x + cannonballRadius > targetDistance && cannonball.x - cannonballRadius <
targetDistance && cannonball.y + cannonballRadius > target.start.y && cannonball.y - cannonballRadius < target.end.y){
//determine target section number (0 is the top)
int section = (int) ((cannonball.y - target.start.y) / pieceLength);
//check if the piece hasn't been hit yet
if((section >= 0 && section < TARGET_PIECES) && !hitStates[section]){
hitStates[section] = true; //section was hit
cannonballOnScreen = false; //remove cannonball
timeLeft += HIT_REWARD; //add reward to remaining time
//play target hit sound
soundPool.play(soundMap.get(TARGET_SOUND_ID), 1, 1, 1, 0, 1F);
//if all pieces have been hit
if (++targetPiecesHit == TARGET_PIECES){
cannonThread.setRunning(false); //show winning dialog
gameOver = true; //the game is over
} //end if
} //end if
} //end else if
} //end if
//update the blockers position
double blockerUpdate = interval * blockerVelocity;
blocker.start.y += blockerUpdate;
blocker.end.y += blockerUpdate;
//update the target's position
double targetUpdate = interval * targetVelocity;
target.start.y += targetUpdate;
target.end.y += targetUpdate;
//if the blocker hit the top or bottom, reverse direction
if(blocker.start.y < 0 || blocker.end.y > screenHeight)
blockerVelocity *= -1;
//if the target hit the top or bottom, reverse direction
if(target.start.y < 0 || target.end.y > screenHeight)
targetVelocity *= -1;
timeLeft -= interval; //subtract from time left
//if the trimer reached zero
if(timeLeft <= 0){
timeLeft = 0.0;
gameOver = true; //the game is over
cannonThread.setRunning(false);
showGameOverDialog(R.string.lose); //show the losing dialog
} //end if
} //end method updatePositions
//fires a cannonball
public void fireCannonball(MotionEvent event){
if(cannonballOnScreen) //if a cannonball is already on the screen
return; //do nothing
double angle = alignCannon(event); //get the cannon barrel's angle
//move the cannonball to be inside the cannon
cannonball.x = cannonballRadius; //align x-coordinate with cannon
cannonball.y = screenHeight / 2; //centers ball vertically
//get the x component of the total velocity
cannonballVelocityX = (int) (cannonballSpeed * Math.sin(angle));
//get the y component of the total velocity
cannonballVelocityY = (int)(-cannonballSpeed * Math.cos(angle));
cannonballOnScreen = true; //the cannonball is on the screen
++shotsFired; //increment shots fired
//play cannon fired sound
soundPool.play(soundMap.get(CANNON_SOUND_ID), 1, 1, 1, 0, 1f);
} //end method fireCannonball
//aligns the cannon in response to a user touch
public double alignCannon(MotionEvent event){
//get the location of the touch in this view
Point touchPoint = new Point((int) event.getX(), (int) event.getY());
//compute the touch's distance from the center of the screen on the Y axis
double centerMinusY = (screenHeight / 2 - touchPoint.y);
double angle = 0; //initialize angle to 0
//calculate the angle the barrel makes with the horizontal
if(centerMinusY != 0) //prevent division by 0
angle = Math.atan((double) touchPoint.x / centerMinusY);
//if the touch is on the lower half of the screen
if(touchPoint.y > screenHeight / 2)
angle += Math.PI; //adjust the angle
//calculate the endpoint of the cannon barrel
barrelEnd.x = (int) (cannonLength * Math.sin(angle));
barrelEnd.y = (int) (-cannonLength * Math.cos(angle) + screenHeight / 2);
return angle; //return the computed angle
} //end method alignCannon
//draws the game to the given Canvas
public void drawGameElements(Canvas canvas){
//clear the background
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), backgroundPaint);
//display the time remaining
canvas.drawText(getResources().getString(R.string.time_remaining_format, timeLeft), 30, 50, textPaint);
//if a cannonball is currently on the screen, draw it
if(cannonballOnScreen)
canvas.drawCircle(cannonball.x, cannonball.y, cannonballRadius, cannonballPaint);
//draw the cannon barrel
canvas.drawLine(0, screenHeight / 2, barrelEnd.x, barrelEnd.y, cannonPaint);
//draw the cannon base
canvas.drawCircle(0, (int) screenHeight / 2, (int) cannonBaseRadius, cannonPaint);
//draw the blocker
canvas.drawLine(blocker.start.x, blocker.start.y, blocker.end.x, blocker.end.y, blockerPaint);
Point currentPoint = new Point(); //start of current target section
//initialize curPoint to the starting point of the target
currentPoint.x = target.start.x;
currentPoint.y = target.start.y;
//draw the target
for (int i = 1; i <= TARGET_PIECES; ++i){
//if this target piece is not hit, draw it
if(!hitStates[i - 1]){
//alternate coloring the pieces yellow and blue
if(i % 2 == 0)
targetPaint.setColor(Color.YELLOW);
else
targetPaint.setColor(Color.BLUE);
canvas.drawLine(currentPoint.x, currentPoint.y, target.end.x,
(int) (currentPoint.y + pieceLength), targetPaint);
} //end if
//move curPoint to the start of the next piece
currentPoint.y += pieceLength;
} //end for
} //end method drawGameElements
//display an AlertDialog when the game ends
private void showGameOverDialog(int messageId){
//create a dialog dislaying the given String
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
dialogBuilder.setTitle(getResources().getString(messageId));
dialogBuilder.setCancelable(false);
//display number of shots fired an total time elapsed
dialogBuilder.setMessage(getResources().getString(R.string.results_format, shotsFired, totalTimeElapsed));
dialogBuilder.setPositiveButton(R.string.reset_game,
new DialogInterface.OnClickListener() {
//called when "Reset Game" Button is pressed
#Override
public void onClick(DialogInterface dialog, int which) {
dialogIsDisplayed = false;
newGame(); //set up and start a new game
} //end method onClick
} //end anonymous inner class
); //end call to setPositiveButton
activity.runOnUiThread(
new Runnable() {
public void run(){
dialogIsDisplayed = true;
dialogBuilder.show(); //display the dialog
} //end run
}//end runnable
); //end call to runOnUiThread
} //end method showGameOverDialog
//pauses the game
public void stopGame(){
if(cannonThread != null)
cannonThread.setRunning(false);
} //end method stopGame
//releases reources; called by CannonGame's onDestroy method
public void releaseResources(){
soundPool.release(); //release all resource used by the SoundPool
soundPool = null;
} //end method releaseResources
//called when surface changes size
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
} //end method surfaceChanged
//called when surface is first created
#Override
public void surfaceCreated(SurfaceHolder holder){
cannonThread = new CannonThread(holder);
cannonThread.setRunning(true);
cannonThread.start(); //start the game loop thread
} //end method surfaceCreated
//called when surface is destroyed
#Override
public void surfaceDestroyed(SurfaceHolder holder){
//ensure that thread terminates properly
boolean retry = true;
cannonThread.setRunning(false);
while(retry){
try{
cannonThread.join();
retry = false;
} //end try
catch(InterruptedException e){
} //end catch
} //end while
} //end method surfaceDestroyed
//Thread subclass to control the game loop
private class CannonThread extends Thread{
private SurfaceHolder surfaceHolder; //for manipulating canvas
private boolean threadIsRunning = true; //running by default
//initialize the surface holder
public CannonThread(SurfaceHolder holder){
surfaceHolder = holder;
setName("CannonThread");
} //end constructor
//changes the running state
public void setRunning(boolean running){
threadIsRunning = running;
} //end method setRunning
//controls the game loop
#Override
public void run(){
Canvas canvas = null; //used for drawing
long previousFrameTime = System.currentTimeMillis();
while(threadIsRunning){
try{
canvas = surfaceHolder.lockCanvas(null);
//lock the surafceHolder for drawing
synchronized(surfaceHolder){
long currentTime = System.currentTimeMillis();
double elapsedTimeMS = currentTime - previousFrameTime;
totalTimeElapsed += elapsedTimeMS / 1000.0;
updatePositions(elapsedTimeMS); //update game stats
drawGameElements(canvas); //draw
previousFrameTime = currentTime; //updateprevious time
} //end synchronized block
} //end try
finally{
if(canvas != null)
surfaceHolder.unlockCanvasAndPost(canvas);
} //end finally
} //end while
} //end method run
} //end nested class CannonThread
} //end class CannonView
Finally, here is the xml constraint that my custom view uses:
<?xml version="1.0" encoding="utf-8"?>
<com.deitel.cannongame.CannonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/cannonView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"/>
I should mention that I tried the book's example files off their website, and I get the exact same problem.
Change the xml
android:background="#android:color/transparent"
It is likely that you are being burned by overzealous scaling by your fancy android. I would recommend that you use this:
in your manifest. Just put it after your Application information, like this:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourstuff.app"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10"
android:targetSdkVersion="15" />
<application android:label="#string/app_name" android:icon="#drawable/r">
<activity android:name=".LandingActivity"
android:theme="#android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".yourOtherActivity"
android:theme="#android:style/Theme.NoTitleBar.Fullscreen"
/>
</application>
<supports-screens android:anyDensity="true" />
</manifest>
I started working on AndEngine and I have learnt a lot but i am stuck in creating levels selector for my game. I know this is something to do with getTextureManager() as i went through this when i am trying to attach ITextureRegion to my sprite object, that i solved but this time i am going nowhere for last day.
mainActivity.java
import org.andengine.engine.camera.Camera;
import org.andengine.engine.options.EngineOptions;
import org.andengine.engine.options.ScreenOrientation;
import org.andengine.engine.options.WakeLockOptions;
import org.andengine.engine.options.resolutionpolicy.FillResolutionPolicy;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.scene.background.Background;
import org.andengine.opengl.font.FontFactory;
import org.andengine.opengl.texture.ITexture;
import org.andengine.opengl.texture.TextureManager;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlasTextureRegionFactory;
import org.andengine.opengl.texture.atlas.bitmap.BuildableBitmapTextureAtlas;
import org.andengine.opengl.texture.atlas.bitmap.source.IBitmapTextureAtlasSource;
import org.andengine.opengl.texture.atlas.buildable.builder.BlackPawnTextureAtlasBuilder;
import org.andengine.opengl.texture.atlas.buildable.builder.ITextureAtlasBuilder.TextureAtlasBuilderException;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.texture.region.TextureRegion;
import org.andengine.ui.activity.SimpleBaseGameActivity;
import org.andengine.util.color.Color;
import android.graphics.Typeface;
import android.util.Log;
public class Main_Activity extends SimpleBaseGameActivity {
EngineOptions engine;
Camera mCamera;
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
LevelSelector ls;
Scene mScene;
public org.andengine.opengl.font.Font mFont;
ITextureRegion mTextureRegion;
#Override
public EngineOptions onCreateEngineOptions() {
mCamera = new Camera(0, 0, WIDTH, HEIGHT);
engine = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new FillResolutionPolicy(), mCamera);
engine.setWakeLockOptions(WakeLockOptions.SCREEN_ON);
return engine;
}
#Override
protected void onCreateResources() {
BuildableBitmapTextureAtlas mBitmapTextureAtlas = new BuildableBitmapTextureAtlas(this.getTextureManager() ,32 , 32);
mTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mBitmapTextureAtlas, this, "marble.png");
try {
mBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 1, 1));
mBitmapTextureAtlas.load();
} catch (TextureAtlasBuilderException e) {
Log.e("", ""+e);
}
this.mFont = FontFactory.create(this.getFontManager(), this.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32);
this.mFont.load();
}
#Override
protected Scene onCreateScene() {
mScene = new Scene();
mScene.setBackground(new Background(0.09804f, 0.6274f, 0.8784f));
ls = new LevelSelector(5, 2, WIDTH, HEIGHT, mScene, mEngine);
ls.createTiles(mTextureRegion, mFont);
ls.show();
return mScene;
}
}
LayerSelector.java
import org.andengine.engine.options.EngineOptions;
import org.andengine.entity.Entity;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.sprite.Sprite;
import org.andengine.entity.text.Text;
import org.andengine.input.touch.TouchEvent;
import org.andengine.opengl.font.Font;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.texture.region.TextureRegion;
import android.service.wallpaper.WallpaperService.Engine;
public class LevelSelector extends Entity {
/* Level selector layer properties */
private final int COLUMNS = 6;
private final int ROWS = 6;
/* Level selector tile properties */
private final int TILE_DIMENSION = 50;
private final int TILE_PADDING = 15;
private final Scene mScene;
private final org.andengine.engine.Engine mEngine;
/*
* The mChapter variable can allow each LevelSelector object to contain
* level tiles which begin levels in different chapters.
*/
private final int mChapter;
/* Variable containing the current max level unlocked */
private final int mMaxLevel;
/* Camera width and height are needed for the layout */
private final int mCameraWidth;
private final int mCameraHeight;
/* Initial x/y coordinates used for tile positioning */
private final float mInitialX;
private final float mInitialY;
/*
* Variable which defines whether the LevelSelector is hidden or visible
*/
private boolean mHidden = true;
/**
* The LevelSelector object can be used to display a grid of level tiles for
* user selection.
*
* #param pMaxLevel
* Current max unlocked level.
* #param pChapter
* Chapter/world number of this particular LevelSelector.
* #param pCameraWidth
* Camera object's width value.
* #param pCameraHeight
* Camera object's height value.
* #param pScene
* The Scene in which the LevelSelector will be displayed on.
* #param pEngine
* AndEngine's mEngine object.
*/
public LevelSelector(final int pMaxLevel, final int pChapter,
final int pCameraWidth, final int pCameraHeight,
final Scene pScene, final org.andengine.engine.Engine pEngine) {
/* Initialize member variables */
this.mScene = pScene;
this.mEngine = pEngine;
this.mChapter = pChapter;
this.mMaxLevel = pMaxLevel;
this.mCameraWidth = pCameraWidth;
this.mCameraHeight = pCameraHeight;
/*
* Obtain the initial tile's X coordinate by subtracting half of the
* entire level selector width including all tiles and padding from the
* center of the Scene
*/
final float halfLevelSelectorWidth = ((TILE_DIMENSION * COLUMNS) + TILE_PADDING
* (COLUMNS - 1)) * 0.5f;
this.mInitialX = (this.mCameraWidth * 0.5f) - halfLevelSelectorWidth;
/* Same math as above applies to the Y coordinate */
final float halfLevelSelectorHeight = ((TILE_DIMENSION * ROWS) + TILE_PADDING
* (ROWS - 1)) * 0.5f;
this.mInitialY = (this.mCameraHeight * 0.5f) + halfLevelSelectorHeight;
mEngine.getTextureManager();
}
/**
* Create the level tiles with a customized ITextureRegion representation as
* well as a customized Font.
*
* #param pTextureRegion
* The ITextureRegion to supply each of the level tiles.
* #param pFont
* The Font to be displayed by Text written on the tiles,
* specifying tile level number for example.
*/
public void createTiles(final ITextureRegion pTextureRegion,
final org.andengine.opengl.font.Font pFont) {
/* Temp coordinates for placing level tiles */
float tempX = this.mInitialX + TILE_DIMENSION * 0.5f;
float tempY = this.mInitialY - TILE_DIMENSION * 0.5f;
/* Current level of the tile to be placed */
int currentTileLevel = 1;
/*
* Loop through the Rows, adjusting tempY coordinate after each
* iteration
*/
for (int i = 0; i < ROWS; i++) {
/*
* Loop through the column positions, placing a LevelTile in each
* column
*/
for (int o = 0; o < COLUMNS; o++) {
final boolean locked;
/* Determine whether the current tile is locked or not */
if (currentTileLevel <= mMaxLevel) {
locked = false;
} else {
locked = true;
}
/* Create a level tile */
LevelTile levelTile = new LevelTile(tempX, tempY, locked,
currentTileLevel, pTextureRegion, pFont);
/*
* Attach the level tile's text based on the locked and
* currentTileLevel variables pass to its constructor
*/
levelTile.attachText();
/* Register & Attach the levelTile object to the LevelSelector */
mScene.registerTouchArea(levelTile);
this.attachChild(levelTile);
/* Increment the tempX coordinate to the next column */
tempX = tempX + TILE_DIMENSION + TILE_PADDING;
/* Increment the level tile count */
currentTileLevel++;
}
/* Reposition the tempX coordinate back to the first row (far left) */
tempX = mInitialX + TILE_DIMENSION * 0.5f;
/* Reposition the tempY coordinate for the next row to apply tiles */
tempY = tempY - TILE_DIMENSION - TILE_PADDING;
}
}
/**
* Display the LevelSelector on the Scene.
*/
public void show() {
/* Register as non-hidden, allowing touch events */
mHidden = false;
/* Attach the LevelSelector the the Scene if it currently has no parent */
if (!this.hasParent()) {
mScene.attachChild(this);
}
/* Set the LevelSelector to visible */
this.setVisible(true);
}
/**
* Hide the LevelSelector on the Scene.
*/
public void hide() {
/* Register as hidden, disallowing touch events */
mHidden = true;
/* Remove the LevelSelector from view */
this.setVisible(false);
}
public class LevelTile extends Sprite {
/*
* The LevelTile should keep track of level number and lock status. Feel
* free to add additional data within level tiles
*/
private final boolean mIsLocked;
private final int mLevelNumber;
private final org.andengine.opengl.font.Font mFont;
private Text mTileText;
/*
* Each level tile will be sized according to the constant
* TILE_DIMENSION within the LevelSelector class
*/
public LevelTile(float pX, float pY, boolean pIsLocked,
int pLevelNumber, ITextureRegion pTextureRegion, org.andengine.opengl.font.Font pFont) {
super(pX, pY, LevelSelector.this.TILE_DIMENSION,
LevelSelector.this.TILE_DIMENSION, pTextureRegion,
LevelSelector.this.mEngine.getVertexBufferObjectManager());
/* Initialize the necessary variables for the LevelTile */
this.mFont = pFont;
this.mIsLocked = pIsLocked;
this.mLevelNumber = pLevelNumber;
}
/* Method used to obtain whether or not this level tile represents a
* level which is currently locked */
public boolean isLocked() {
return this.mIsLocked;
}
/* Method used to obtain this specific level tiles level number */
public int getLevelNumber() {
return this.mLevelNumber;
}
/*
* Attach the LevelTile's text to itself based on whether it's locked or
* not. If not, then the level number will be displayed on the level
* tile.
*/
public void attachText() {
String tileTextString = null;
/* If the tile's text is currently null... */
if (this.mTileText == null) {
/*
* Determine the tile's string based on whether it's locked or
* not
*/
if (this.mIsLocked) {
tileTextString = "Locked";
} else {
tileTextString = String.valueOf(this.mLevelNumber);
}
/* Setup the text position to be placed in the center of the tile */
final float textPositionX = LevelSelector.this.TILE_DIMENSION * 0.5f;
final float textPositionY = textPositionX;
/* Create the tile's text in the center of the tile */
this.mTileText = new Text( textPositionX,
textPositionY, this.mFont,
tileTextString, tileTextString.length(),
LevelSelector.this.mEngine
.getVertexBufferObjectManager());
/* Attach the Text to the LevelTile */
this.attachChild(mTileText);
}
}
#Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent,
float pTouchAreaLocalX, float pTouchAreaLocalY) {
/* If the LevelSelector is not hidden, proceed to execute the touch
* event */
if (!LevelSelector.this.mHidden) {
/* If a level tile is initially pressed down on */
if (pSceneTouchEvent.isActionDown()) {
/* If this level tile is locked... */
if (this.mIsLocked) {
/* Tile Locked event... */
LevelSelector.this.mScene.getBackground().setColor(
org.andengine.util.color.Color.RED);
} else {
/* Tile unlocked event... This event would likely prompt
* level loading but without getting too complicated we
* will simply set the Scene's background color to green */
LevelSelector.this.mScene.getBackground().setColor(
org.andengine.util.color.Color.GREEN);
/**
* Example level loading:
* LevelSelector.this.hide();
* SceneManager.loadLevel(this.mLevelNumber);
*/
}
return true;
}
}
return super.onAreaTouched(pSceneTouchEvent, pTouchAreaLocalX,
pTouchAreaLocalY);
}
}
}
FYI, this code belongs to code bundle in Android game development using AndEngine cookbook.
I have edited few variables like font and engine , but i have't changed the logic or flow of program.
Now when I run this I get the below screen
Alright i solved my problem, its was the padding i was using, it should't be there.
try {
mBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 1, 1));
mBitmapTextureAtlas.load();
} catch (TextureAtlasBuilderException e) {
Log.e("", ""+e);
}
it should be
try {
mBitmapTextureAtlas.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 0, 0));
mBitmapTextureAtlas.load();
} catch (TextureAtlasBuilderException e) {
Log.e("", ""+e);
}
I'm developing a 2d Game using Canvas/Surfaceview and have a problem with scrolling my background image.
Check out the game - http://youtu.be/4Gi5rRqzZ3M
In the NinJump game, the character Ninja is just jumping in X coordinates and Background image is scrolling at a very high speed, making Ninja look like it is actually running.
I have created the basic setup, created the Ninja, added jump functionality, added background. Now I want to repeat the same background over and over again. How can I accomplish that?
Below are my source files - Main Activity Class
package com.abc.apps;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
public class LadderActivity extends Activity {
private static final String TAG = LadderActivity.class.getSimpleName();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// requesting to turn the title OFF
requestWindowFeature(Window.FEATURE_NO_TITLE);
// making it full screen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// set our MainGamePanel as the View
setContentView(new MainGameBoard(this));
Log.d(TAG, "View added");
}
#Override
protected void onDestroy() {
Log.d(TAG, "Destroying...");
super.onDestroy();
}
#Override
protected void onStop() {
Log.d(TAG, "Stopping...");
super.onStop();
}
}
Game Board extends SurfaceView
package com.abc.apps;
import android.app.Activity;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainGameBoard extends SurfaceView implements SurfaceHolder.Callback{
private MainGameLoop thread;
private Monkey monkey;
private static final String TAG = MainGameLoop.class.getSimpleName();
int currentX, currentY;
public MainGameBoard(Context context) {
super(context);
// TODO Auto-generated constructor stub
// adding the callback (this) to the surface holder to intercept events
//This line sets the current class (MainGamePanel) as the handler for the events happening on the actual surface
getHolder().addCallback(this);
// create monkey and load bitmap INITIALIZE AT LEFT
monkey = new Monkey(BitmapFactory.decodeResource(getResources(), R.drawable.actor),60, 340);
// create the game loop thread
thread = new MainGameLoop(getHolder(), this);
// make the GamePanel focusable so it can handle events.
setFocusable(true);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
thread.setRunning(true);
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
}
catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//For jumping Left
if (event.getX() < (getWidth()/2 - 32)) {
// Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
//Log.d(TAG, "Jump Left");
// Sleep so that the main thread doesn't get flooded with UI events.
try {
Thread.sleep(32);
monkey.setX((getWidth()/2 - 60));
monkey.setY(monkey.getY()-70);
} catch (InterruptedException e) {
// No big deal if this sleep is interrupted.
}
}
//For Jumping Right
if (event.getX() > (getWidth()/2 + 32)) {
//Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
//Log.d(TAG, "Jump Right");
// Sleep so that the main thread doesn't get flooded with UI events.
try {
Thread.sleep(32);
monkey.setX((getWidth()/2 + 60));
monkey.setY(monkey.getY()-70);
} catch (InterruptedException e) {
// No big deal if this sleep is interrupted.
}
}
/* //Middle Portion
if (event.getX() > (getWidth()/2 - 32) && event.getX() < (getWidth()/2 +32)) {
//thread.setRunning(false);
//((Activity)getContext()).finish();
}*/
}
return true;
}
public void render(Canvas canvas) {
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_sc), 0, 0,null);
monkey.draw(canvas);
}
/* #Override
protected void onDraw(Canvas canvas){
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_scene), 0, 0,null);
monkey.draw(canvas);
}*/
}
Main Game Loop
package com.abc.apps;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
public class MainGameLoop extends Thread {
private SurfaceHolder surfaceHolder;
private MainGameBoard gameBoard;
private Monkey monkey;
private static final String TAG = MainGameLoop.class.getSimpleName();
// flag to hold game state
private boolean running = true;
public void setRunning(boolean running) {
this.running = running;
}
#Override
public void run() {
Canvas canvas;
Log.d(TAG, "Starting game loop");
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing on the surface
try {
canvas = surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
// update game state
// render state to the screen
// draws the canvas on the panel
gameBoard.render(canvas);
}
}
finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
public MainGameLoop(SurfaceHolder surfaceHolder, MainGameBoard gameBoard) {
super();
this.surfaceHolder = surfaceHolder;
this.gameBoard = gameBoard;
}
}//MainThread
Monkey Class
package com.abc.apps;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.MotionEvent;
public class Monkey {
private Bitmap bitmap; // the actual bitmap
private int x; // the X coordinate
private int y; // the Y coordinate
private boolean touched; // if monkey is touched
public Monkey(Bitmap bitmap, int x, int y) {
this.bitmap = bitmap;
this.x = x;
this.y = y;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public boolean isTouched() {
return touched;
}
public void setTouched(boolean touched) {
this.touched = touched;
}
public void draw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2), y, paint);
}
}
It looks like you are drawing your background in your MainGameBoard class in the render method.
public void render(Canvas canvas) {
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_sc), 0, 0,null);
monkey.draw(canvas);
}
You should just need 2 drawBitmap calls instead of 1 there.
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_sc), 0, y_offset1,null);
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_sc), 0, y_offset2,null);
I'm making an assumption that each background image has the same height or greater than the screen height; if it is less than the screen height you would need more than 2 instances.
Then you start 1 image at y_offset1 = 0 and the other at y_offset2 = -image_height.
Each draw you would increase y_offset1 and y_offset2 by the same amount. You would then need to do a check for both offsets to see if either has an amount greater than the screen height. If it does then the y_offset that is now "below screen" should be reset to the other y_offset minus the image_height. This will create a scroll image that loops indefinitely.
When using this type of technique it is important to think about your image edges; the image should be designed such that they tile seamlessly, otherwise at the looping point there is a noticeable visual artifact along the edge.