Box2D body lags/jumps when a camera is centered on it - android

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.

Related

LibGdx `ShapeRenderer` on Android does not honor alpha channel

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.

Camera does not follow player

I use libgdx have one player which moves in x direction from left to right. Now I want the camera to follow it (like in Flappy Bird for example).
What happen is that the player go out off screen when it reaches the right border of screen and the camera don't follow him.
I have tried following options but none of them worked:
camera.position.set(player.getX(), camera.position.y, 0);
camera.position.set(player.getX(), 0, 0);
Vector3 vector3= camera.unproject(new Vector3(player.getX(), 0f, 0f));
player.setX(vector3.x);
I know that this question already exists on SO but none answer works in this case. Maybe I miss something important that i don't know.
The code:
Game.java class
public class Game extends com.badlogic.gdx.Game implements ApplicationListener {
public static Vector2 VIEWPORT = new Vector2(320, 480);
public static int WIDTH;
public static int HEIGHT;
#Override
public void create() {
WIDTH = Gdx.graphics.getWidth();
HEIGHT = Gdx.graphics.getHeight();
// VIEWPORT = new Vector2(WIDTH/2, HEIGHT/2);
VIEWPORT = new Vector2(WIDTH, HEIGHT);
setScreen(new GameScreen(this));
}
#Override
public void resize(int width, int height) {
// TODO Auto-generated method stub
super.resize(width, height);
}
#Override
public void resume() {
}
#Override
public void pause() {
}
}
GameScreen.java class
import android.util.Log;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.utils.viewport.FitViewport;
import java.util.Collections;
public class GameScreen implements Screen {
private OrthographicCamera camera;
private Player player;
private PlayerInputHandler inputHandler1, inputHandler2;
private Sound sound;
FitViewport viewp;
public static int WIDTH;
public static int HEIGHT;
int width_spacing = 0;
int height_spacing = 0;
Stage stage;
Skin skin;
public GameScreen(Game game) {
stage = new Stage(new FitViewport(Game.WIDTH, Game.HEIGHT));
camera = (OrthographicCamera) stage.getCamera();
Gdx.input.setInputProcessor(stage);
}
#Override
public void show() {
resetGame();
}
public void resetGame() {
WIDTH = Gdx.graphics.getWidth();
HEIGHT = Gdx.graphics.getHeight();
width_spacing = Game.WIDTH / 24;
height_spacing = Game.HEIGHT / 14;
stage.clear();
skin = new Skin(Gdx.files.internal("data2/uiskin.json"));
prepareInputHandlers();
prepare_stage();
}
public void addPlayer() {
Texture texture = new Texture("player.png");
player = new Player(texture);
player.setPosition(Game.WIDTH / 2, Game.HEIGHT * 2 / 3);
stage.addActor(player);
}
#Override
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
if (delta > 1 / 60f) {
player.setX(player.getX() + (4 * delta));
camera.position.set(player.getX(), camera.position.y, 0);
}
update();
stage.act(delta);
stage.draw();
}
private void update() {
camera.update();
}
private void prepareInputHandlers() {
inputHandler2 = new PlayerInputHandler(player, Input.Keys.LEFT, Input.Keys.RIGHT, Input.Keys.UP, Input.Keys.DOWN);
}
#Override
public void dispose() {
sound.dispose();
player.getTexture().dispose();
}
public void prepare_stage() {
addPlayer();
player.setWidth(64);
player.setHeight(64);
}
#Override
public void resume() {
// TODO Auto-generated method stub
}
#Override
public void hide() {
// TODO Auto-generated method stub
}
#Override
public void pause() {
// TODO Auto-generated method stub
}
}
Player.java class
import com.badlogic.gdx.graphics.Texture;
public class Player extends com.badlogic.gdx.scenes.scene2d.ui.Image{
private Texture texture;
public Player(Texture texture) {
super(texture);
}
public Texture getTexture() {
return texture;
}
#Override
public void act(float delta) {
super.act(delta);
}
}
try to use stage's camera instead of creating your own. Change your GameScreen constructor like this:
public GameScreen(Game game)
{
stage = new Stage(new FitViewport(Game.WIDTH, Game.HEIGHT));
camera = (OrthographicCamera) stage.getCamera();
Gdx.input.setInputProcessor(stage);
}
then in the render method set the camera position just like
camera.position.set(player.getX(), camera.position.y, 0);
before camera.update() call
There is also something strange in your code - why you have camera.position.set() and setting player x in this condition:
if (delta > 1 / 60f) //??
{
player.setX(player.getX() + (4 * delta));
camera.position.set(player.getX(), camera.position.y, 0);
}
I'm pretty sure you don't need this - but even if you need the
camera.position.set(player.getX(), camera.position.y, 0);
should be out of if statement because what is happening now is that even if you are changing your player position (using keybord through PlayerInputHandler object) you **don't update camera position. Try to remove if statement or at least do something like
public void render(float delta)
{
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
if (delta > 1 / 60f)
{
player.setX(player.getX() + (100 * delta));
}
camera.position.set(player.getX(), camera.position.y, 0);
update();
stage.act(delta);
stage.draw();
}
last thing is that if your stage is empty (excluding player) when camera will start to follow player - you will see as player not moving at all :) Add someting more to stage

SetTexture and Sprites not working as intended! (LibGDX)

I searched for and wide on the internet and I have yet to find an answer. I have an object called GameIcon which extends Sprite. Everything about it is okay except for the texture. Here is my code for the GameIcon class.
package com.xx4everPixelatedxx.gaterunner.sprites;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
import com.xx4everPixelatedxx.gaterunner.GateRunner;
import javax.xml.soap.Text;
/**
* Created by Michael Jan on 8/17/2015.
*/
public class GameIcon extends Sprite {
private int vX = 3;
private int vY = 3;
private int r = 9;
private int rotation;
private Vector3 position;
private Texture texture;
public GameIcon(int x, int y) {
position = new Vector3(x, y, 0);
texture = new Texture(Gdx.files.internal("icon_players/icon1.png"));
setTexture(texture);
}
public void update() {
position.add(vX, vY, 0);
rotation = rotation + r;
rotation = rotation % 360;
setRotation(rotation);
setOriginCenter();
}
public void addPosition(int x, int y) {
position.add(x, y, 0);
setOriginCenter();
}
public void negateVelocityX() {
vX = -vX;
}
public void negateRotation() {
r = -r;
}
public Vector3 getPosition() {
return position;
}
public int getvY() {
return vY;
}
public void setvY(int vY) {
this.vY = vY;
}
public int getvX() {
return vX;
}
public void setvX(int vX) {
this.vX = vX;
}
}
Here is the icon1.png:
https://i.gyazo.com/1d52f5e58b227f08809f6c14ae4c94a4.png
Here is what I am getting:
https://i.gyazo.com/1881a9392955af34de5c55b9b8fac391.png
Note that the rotation of the image and the particles are intended. The problem is that the big square should be the texture (icon1.png), and I do not know how to fix this.
(Not enough reputation to post pictures)
I'm not familiar with LibGDX, but this may have something to do with the fact that you are maybe overwriting TextureRegion.texture. Could you try to user your parent class Sprite(Texture) constructor like this:
...
public class GameIcon extends Sprite {
private int vX = 3;
private int vY = 3;
private int r = 9;
private int rotation;
private Vector3 position;
//private Texture texture;
public GameIcon(int x, int y) {
super(new Texture(Gdx.files.internal("icon_players/icon1.png")))
position = new Vector3(x, y, 0);
}
...
As stated by Tenfour04 in comments, this method works because the parent's constructor applies the width and height of the texture, while setTexture() does not.

LibGDX: A screen with 20 Rectangles that can be scrolled

I need to create a menu with 20 vertical Rectangles in LibGdx.
The height of the rectangles is the entire height of the device.
The width of the rectangles is a set 20.0f
I have: private Stage stage; to which I intend to add on to my Rectangles.
The class is called LevelSelectScreen and implements Screen.
This is what I have so far:
public class LevelSelectScreen implements Screen {
private Stage stage;
private int stageNumber = 20;
Array<Rectangle> stageRectangles;
ShapeRenderer shapeRenderer;
public LevelSelectScreen(){
stage = new Stage();
stageRectangles = new Array<Rectangle>(stageNumber);
for(int i = 0;i<stageNumber;i++){
stageRectangles.get(i).setWidth(20f);
stageRectangles.get(i).setHeight(Gdx.graphics.getHeight());
stageRectangles.get(i).setPosition(new Vector2(20*i,0));
}
Gdx.graphics.getWidth();
shapeRenderer = new ShapeRenderer();
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
//shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
shapeRenderer.setColor(0, 1, 0, 1);
for(int i = 0;i<stageNumber;i++){
Rectangle r = stageRectangles.get(i);
shapeRenderer.rect(r.x, r.y, r.getWidth(), r.getHeight());
}
shapeRenderer.end();
}
#Override
public void resize(int width, int height) {
}
#Override
public void show() {
}
#Override
public void hide() {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void dispose() {
}
}
Nothing appears and I don't think I'm using the Shape Renderer correctly.
How do I properly set up my scrollable rectangles?
Edit
Also, found this helpful link.
Varible class:
private Stage stage;
private int stageNumber = 20;
Array<Rectangle> stageRectangles;
ShapeRenderer shapeRenderer;
//new, for no magic number, but not required
private float w = 20f;
private float h = Gdx.graphics.getHeight();
private float space = 5;
private float xPos = w + space;
private float yPos = 0;
In your code:
public LevelSelectScreen(){
stage = new Stage();
stageRectangles = new Array<Rectangle>(stageNumber);
for(int i = 0; i<stageNumber; i++){
stageRectangles.add(new Rectangle(xPos * i,
yPos,
w,
h));
Gdx.app.log("Rectangle add", ""+i);
}
shapeRenderer = new ShapeRenderer();
}
you say that does not work or that I understand, but you're not having a Nullpointer or BoundsException ? anyway grabbing a few adjustments to your code this is what I have, it's what you wanted.
PS: do not would be more comfortable using a scroll and a table for when you have to use the listener, pictures ect, but it's just an idea, I hope you have understood what you were saying
in your code:
for(int i = 0; i<stageNumber; i++){
stageRectangles.get(i).setWidth(20f);
stageRectangles.get(i).setHeight(Gdx.graphics.getHeight());
stageRectangles.get(i).setPosition(new Vector2(25*i,0));
}
.
in this point stageRectangles.get(i).setWidth(20f);
the size is 0, then the array has an initial capacity
of 20 but is empty.
not getting something like this?
IndexOutOfBoundsException: index can't be >= size 0 >= 0

Screen Recorder Android Plugin in Unity

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/

Categories

Resources