Using a custom SurfaceView and thread for Android game programming (example)

后端 未结 1 1192
后悔当初
后悔当初 2021-01-07 05:38

I am trying to use SurfaceView, but the lockCanvas(null) is confusing, and the app freezes when I exit the activity. Also, nothing is displayed, even though the

相关标签:
1条回答
  • 2021-01-07 06:22

    The solution is probably that setWillNotDraw(false); was not called.

    Therefore, a referential implementation that works properly would be the following:

    public class GameSurfaceView extends SurfaceView implements SurfaceHolder.Callback
    {
        private SurfaceHolder holder;
    
        private MyThread myThread;
    
        private GameController gameController;
    
        private Paint paint;
    
        private int width;
        private int height;
    
        public GameSurfaceView(Context context, GameController gameController, int width, int height)
        {
            super(context);
            holder = getHolder(); 
    
            holder.addCallback(this);
    
            this.gameController = gameController;
            this.width = width;
            this.height = height;
            paint = new Paint();
            //initialize paint object parameters
    
            setWillNotDraw(false); //this line is very important!
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder)
        {
        }
    
        @Override
        // This is always called at least once, after surfaceCreated
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
        {
            if (myThread == null)
            {
                myThread = new MyThread(holder, gameController);
                myThread.setRunning(true);
                myThread.start();
            }
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder)
        {
            boolean retry = true;
            myThread.setRunning(false);
            while (retry)
            {
                try
                {
                    myThread.join();
                    retry = false;
                }
                catch (InterruptedException e)
                {
                    Log.d(getClass().getSimpleName(), "Interrupted Exception", e);
                }
            }
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event)
        {
            System.out.println(event.getX() + " " + event.getY());
            gameController.onTouchEvent(event); //handle user interaction
            return super.onTouchEvent(event);
        }
    
        @Override
        protected void onDraw(Canvas canvas)
        {
            super.onDraw(canvas);
            canvas.drawText("Hello world!", width/20, 20, paint);
            gameController.draw(canvas);
        }
    
        public Thread getThread()
        {
            return thread;
        }
    
        public class MyThread extends Thread
        {
            private SurfaceHolder holder;
            private boolean running = false;
    
            private GameController gameController;
    
            public MyThread(SurfaceHolder holder, GameController gameController)
            {
                this.holder = holder;
                this.gameController = gameController;
            }
    
            @Override
            public void run()
            {
                Canvas canvas = null;
                while (running)
                {
                    gameController.update(); //update the time between last update() call and now
                    try
                    {
                        canvas = holder.lockCanvas(null);
                        synchronized (holder)
                        {
                            postInvalidate();
                        }
                    }
                    finally
                    {
                        if (canvas != null)
                        {
                            holder.unlockCanvasAndPost(canvas);
                        }
                    }
                }
    
            }
    
            public void setRunning(boolean b)
            {
                running = b;
            }
        }
    }
    

    The surface itself doesn't really want to answer what its width and height is, therefore it's easier if we get its measurements from the outside, and provide it for our custom View from the outside:

    //Fragment onCreateView()
        Display display = getActivity().getWindowManager().getDefaultDisplay();
        RelativeLayout rl = (RelativeLayout)view.findViewById(R.id.gameplay_container);
        rl.addView(new GameSurfaceView(this.getActivity().getApplicationContext(), gameController, display.getWidth(), display.getHeight()));
        return view;
    }
    

    Now we are able to draw onto the SurfaceView from our own Thread, and we are also able to seperate all game-related logic in a GameController. The GameController is responsible for input handling and game event updates, as per the following:

        public void update()
        {
            long delta = getTimeManager().getDeltaTime(System.currentTimeMillis());
            long timeChunk;
            if (isGameOver == false)
            {
                for (long i = 0; i < delta; i += 20)
                {
                    long iNext = i + 20;
                    if (iNext > delta)
                    {
                        timeChunk = delta - i;
                    }
                    else
                    {
                        timeChunk = iNext - i;
                    }
                    // ...update game entities based on the miliseconds provided in timeChunk
        }
    

    And to get the delta amount of time (the time that passed by since the previous update) is the following:

        long temp = currentTimeMillis - oldTime;
        this.oldTime = currentTimeMillis;
        return temp;
    

    I hope that will be helpful to you later :)

    0 讨论(0)
提交回复
热议问题