2013-11-26 16 views
5

Tôi đang phát triển Plugin Unity-Android để ghi lại màn hình trò chơi và tạo tệp video mp4.I theo dõi mẫu bản ghi trò chơi phá vỡ Android Breakout trong trang web này: http://bigflake.com/mediacodec/.
Trước tiên, tôi tạo ra lớp CustomUnityPlayer của tôi mà kéo dài lớp UnityPlayer và ghi đè onDrawFrame method.Here là mã lớp CustomUnityPlayer tôi:Trình đọc màn hình Android Plugin trong Unity

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

Và sau đó, tôi tạo ra một CustomUnityPlayerActivity để gọi lớp này

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

Vấn đề của tôi là một tệp video được tạo thành công nhưng trò chơi của tôi không thể hiển thị gì cả. Tôi đọc trên trang web Android Media Codec sample và nhận ra rằng mỗi khung hình sẽ hiển thị hai lần (một lần cho màn hình, một lần cho video) nhưng tôi không thể làm điều này trong Unity.Whenever tôi cố gắng gọi super.onDrawFrame (gl) hai lần trong phương thức onDrawFrame, trò chơi của tôi sẽ bị lỗi.

Bất kỳ giải pháp nào cho vấn đề của tôi? Chúng tôi rất trân trọng bất kỳ sự giúp đỡ nào!

Cảm ơn và quan tâm tốt nhất!

Huy Trần

+1

FWIW, có hai cách tiếp cận cơ bản: (1) hiển thị một số khung hình hai lần (như đã hoàn thành trong Breakout), (2) blit hai lần. Tùy thuộc vào độ phức tạp của cảnh, người ta có thể rẻ hơn người kia. Nếu bạn đang sử dụng GLES 3, có một mẹo để tránh một bản sao trong phương pháp # 2. Trong mọi trường hợp, mẹo thực sự là tích hợp với Unity. – fadden

+0

Cảm ơn bạn đã trả lời của bạn.Now vấn đề của tôi là tích hợp với Unity.Can bạn giải thích rõ ràng hơn về cách tiếp cận # 2.Tôi đã cố gắng với cách tiếp cận # 1 như mô tả ở trên nhưng không thành công! – knighthedspi

+0

FWIW, cả ba cách tiếp cận được trình bày trong Grafika (https://github.com/google/grafika). Xem hoạt động "Ghi lại ứng dụng GL". – fadden

Trả lời

4

Cuối cùng tôi sử dụng một FrameBufferObject (FBO) để render offscreen và nhận được kết cấu ràng buộc của nó để blit hai lần:

  • Render mặt video
  • Vẽ lại màn hình thiết bị

Bạn có thể tìm thêm thông tin chi tiết về giải pháp này bằng cách tham chiếu đến một câu hỏi khác use FBO to record Unity gamescreen

4

Kamcord plug-in có thể giúp bạn: http://www.kamcord.com/

+1

Kamcord hiện chỉ hoạt động trên thiết bị Nexus 4 và 7 chạy Android 4.3 Jelly Bean và Unity 4.2.I muốn phát triển plugin này cho các thiết bị khác – knighthedspi

+2

Vì Kamcord được tích hợp với công cụ trò chơi, đây là cách tiếp cận tốt nhất từ ​​quan điểm kỹ thuật, nhưng tôi khuyên bạn nên xem xét cẩn thận các điều khoản pháp lý ... bạn đồng ý theo dõi ứng dụng của họ và bạn không thể đăng video của mình ("Người dùng cuối sẽ chỉ có thể để truy cập Video Kamcord thông qua các kênh phân phối do Kamcord quyết định "). – fadden

+0

Như tôi đã nói ở trên, Kamcord hiện chỉ hỗ trợ cho thiết bị Nexus.Trong trường hợp của tôi, tôi muốn plugin của tôi có thể hoạt động với nhiều thiết bị khác – knighthedspi

Các vấn đề liên quan