0

私のような同様の質問が複数ありますが、これらの質問は役に立ちませんでした。

ゲームを作っています。ゲーム スレッド、SurfaceView、Activity は既に終了しており、これまでのところ動作しています。問題は、キャンバスが再描画されないことです。起動時に背景にアイコンを描画しますが、ティックごとにアイコンは移動しません (1 秒に 1 回移動する必要があります)。postInvalidateを呼び出す必要がなかったことを言及したいと思います。呼び出したことのない実際の例がありますが、現在の例では機能しません (実際に呼び出す必要がないため、深く掘り下げたくありません)。作業例から現在のコードをコピーしました。概念と実装方法はまったく同じですが、現在のコードはキャンバスを更新しません。onDrawで描画位置を記録するとメソッド、座標が期待どおりに毎秒更新されていることがわかりますので、キャンバスの描画の問題であると確信できます。何時間も検索しましたが、実際の例と何が違うのかわかりませんでした (ただし、別の Android バージョンを使用しており、スレッドを拡張するのは悪いスタイルであるため、スレッドを拡張せずに Runnable を実装しています。それにもかかわらず、私はまた、スレッドを拡張して違いがあるかどうかを確認しましたが、役に立ちません)。canvas.drawColor(Color.BLACK)を使用してキャンバスをきれいにしようとしました、しかしそれも役に立ちませんでした。ティックごとにランダムに変化する背景画像の代わりに背景色を使用しようとしましたが、変化しませんでしたが、常に同じままです。最初の呼び出しでのキャンバスの密度は (たとえば) 240 であることがわかりました。2 回目のティックの後、キャンバスの密度は常に 0 です。ここでは密度が役に立たないことはわかっていますが、重要な情報である可能性があります。誰かのため。

ここに重要なクラスがあります....

ゲームのレイアウト、R.layout.game

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gameContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.mydomain.mygame.base.game.GameSurface
        android:id="@+id/gameSurface"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1.0"
        android:background="@drawable/background_game" >
    </com.mydomain.mygame.base.game.GameSurface>

<!-- ...more definitions -->

</LinearLayout>

GameActivity (レイアウトを含む)

public class GameActivity extends Activity
{
    @SuppressWarnings("unused")
    private GameSurface gameSurface;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.game);

        gameSurface = (GameSurface)findViewById(R.id.gameSurface);
        //TODO on button click -> execute methods
    }
}

GameSurface (onDraw にログインすると、ティックごとに更新された座標が表示されます)

public class GameSurface extends SurfaceView implements SurfaceHolder.Callback
{
    private GameThread thread;
    protected final static int TICK_FREQUENCY = 100;// ms, stays always the same. It's a technical constant which doesn't change

    private static final String TAG = GameSurface.class.getSimpleName();

    public GameSurface(Context context, AttributeSet attrs)
    {
        super(context, attrs);

        ShapeManager.INSTANCE.init(context);

        SurfaceHolder holder = getHolder();
        holder.addCallback(this);

        setFocusable(true); // make sure we get key events

        thread = new GameThread(holder, this);
    }

    public void updateStatus()
    {
        GameProcessor.INSTANCE.updateShapes();
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        for (Shape shape : GameProcessor.INSTANCE.getShapes())
        {
            Log.i(TAG, "getX()=" + shape.getX() + ", getY()=" + shape.getY());
            canvas.drawBitmap(shape.getBitmap(), shape.getX(), shape.getY(), null);
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
    {
        //will never invoked since we only operate in landscape
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder)
    {
        // start the thread here so we don't busy-wait in run
        thread.setRunning(true);
        new Thread(thread).start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder)
    {
        Log.i(TAG, "executing surfaceDestroyed()...");  
        thread.setRunning(false);
    }
}

ゲームスレッド

public class GameThread implements Runnable
{
    private SurfaceHolder surfaceHolder;
    private boolean running = false;
    private GameSurface gameSurface;
    private long lastTick;

    public GameThread(SurfaceHolder surfaceHolder, GameSurface gameSurface)
    {
        this.surfaceHolder = surfaceHolder;
        this.gameSurface = gameSurface;

        lastTick = System.currentTimeMillis();
    }

    @Override
    public void run()
    {
        Canvas canvas;
        while (running)
        {
            canvas = null;
            if (System.currentTimeMillis() > lastTick + GameSurface.TICK_FREQUENCY)
            {
                long timeDifference = System.currentTimeMillis() - (lastTick + GameSurface.TICK_FREQUENCY);

                try
                {
                    canvas = surfaceHolder.lockCanvas(null);
                    synchronized (surfaceHolder)
                    {
                        gameSurface.updateStatus();
                        gameSurface.draw(canvas);
                    }
                }
                finally
                {
                    if (canvas != null)
                    {
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    }
                }
                lastTick = System.currentTimeMillis() - timeDifference;
            }
        }
    }

    public void setRunning(boolean running)
    {
        this.running = running;
    }
}

このコードがキャンバスを更新しない理由はありますか? 説明できません。ShapeManagerGameProcessorは問題とは関係がないため投稿しません (また、現在の状態とゲームの速度をロードして制御するだけです)。

[アップデート]

ゲームスレッドが開始される前に onDraw() が呼び出されることがわかりました。つまり、スレッドがキャンバスを使用する前に、キャンバスがこのメソッドに渡されます。興味深いことに、スレッドの開始後は常に同じキャンバスが使用されますが、最初に渡されるのはキャンバス参照ではありませんcanvas = surfaceHolder.lockCanvas(null); ですが、ティックごとに割り当てられ、常に同じ参照ですが、元の参照ではありません。私の実際の例では、コンストラクターの初期化時にビットマップを作成するため、参照は常に同じです。コンストラクターよりもずっと後に呼び出される onMeasure() から取得した値を使用して計算を行う必要があるため、現在の実装ではそれを行うことはできません。

元のキャンバスをどうにかしてスレッドに渡そうとしましたが、それでも参照が変わります。その間、これが問題だと思いますが、まだ解決方法がわかりません。

4

1 に答える 1

2

よくあることですが、私は自分で解決策を見つけました。明らかに、異なるキャンバス インスタンスに描画するのは本当に問題です。なぜこれが起こるのかはまだわかりません。以前は問題がありませんでした。それでも、setWillNotDraw(true);を設定することでキャンバスへの描画を避けることができます。私の GameSurface コンストラクターで、スレッドで gameSurface.draw(canvas) を呼び出す必要はありませんが、代わりに gameSurface.postInvalidate() を呼び出す必要があります。

于 2013-09-06T19:11:13.650 に答える