se.anyro.snr.GameThread.java Source code

Java tutorial

Introduction

Here is the source code for se.anyro.snr.GameThread.java

Source

/*
 * Copyright (C) 2010 Adam Nybck
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package se.anyro.snr;

import java.util.ArrayList;

import se.anyro.snr.bodies.Ball;
import se.anyro.snr.bodies.Body;
import se.anyro.snr.bodies.Bridge;
import se.anyro.snr.bodies.Circle;
import se.anyro.snr.bodies.Goal;
import se.anyro.snr.bodies.Laser;
import se.anyro.snr.bodies.SquareHole;
import se.anyro.snr.bodies.Wall;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.PaintDrawable;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactListener;

public class GameThread implements Runnable, ContactListener {

    // Events sent to the activity
    public static final int START = 0;
    public static final int WIN = 1;
    public static final int GAME_OVER = 2;
    public static final int COMPLETED = 3;
    public static final int PAUSED = 4;

    private SurfaceHolder mSurfaceHolder;
    private Handler mHandler;

    private PaintDrawable mBackground;
    private ArrayList<Body> mBodies = new ArrayList<Body>();
    private ArrayList<Wall> mWalls = new ArrayList<Wall>();
    private ArrayList<Laser> mLasers = new ArrayList<Laser>();
    private Ball mBall;
    private Wall mSwipee; // The wall being swiped

    private Physics mPhysics;
    private Collision mCollision;

    private Thread mThread;
    // mThreadSuspended is increased each time we pause and decreased when we resume
    private volatile int mThreadSuspended = 1;

    private volatile Point mTouchEvent = null;
    private Point mTouchStart = new Point();
    private Point mTouchMove = new Point();
    private Point mTouchEnd = new Point();
    private Object mTouchLock = new Object();

    // Temporary swipe limits
    private float mLeftLimit = -Physics.HALF_WIDTH;
    private float mRightLimit = Physics.HALF_WIDTH;
    private float mTopLimit = Physics.HALF_HEIGHT;
    private float mBottomLimit = -Physics.HALF_HEIGHT;

    private State mState = State.INIT;
    private int mLevel = 1;

    private enum State {
        INIT, RUNNING, PAUSED, WIN, GAME_OVER,
    }

    private class Collision {

        public Body ball, collider;

        public Collision(Body ball, Body collider) {
            this.ball = ball;
            this.collider = collider;
        }
    }

    public GameThread(Handler handler) {
        mHandler = handler;

        mPhysics = new Physics(this);
    }

    public void start(SurfaceHolder surfaceHolder, int width, int height) {
        mSurfaceHolder = surfaceHolder;
        if (mThread == null) {
            Level.setupLevel(this, mLevel);
            createBackground(width, height);
            mThread = new Thread(this, "GameThread");
            mThread.start();
        } else {
            synchronized (this) {
                --mThreadSuspended;
                notify();
            }
        }
    }

    private void createBackground(int width, int height) {
        // Adjust width/height to game ratio to avoid invisible walls
        Rect bounds = new Rect(0, 0, width, height);
        if (width * 3 > height * 2) {
            bounds.left = (width - height * 2 / 3) / 2;
            bounds.right -= bounds.left;
        } else if (width * 3 < height * 2) {
            bounds.top = (height - width * 3 / 2) / 2;
            bounds.bottom -= bounds.top;
        }

        mBackground = new PaintDrawable(0xff464646);
        mBackground.setBounds(bounds);
    }

    public void pause() {
        //        synchronized(this) {
        ++mThreadSuspended;
        //        }
    }

    public void resume(SurfaceHolder holder) {
        if (holder != null) {
            mSurfaceHolder = holder;
        }
        synchronized (this) {
            --mThreadSuspended;
            if (mThreadSuspended == 0)
                notify();
        }
    }

    public void reset() {
        for (Body body : mBodies)
            body.destroy();
        mBodies.clear();
        mWalls.clear();
        mLasers.clear();
    }

    public void add(Body body) {
        mBodies.add(body);

        if (body instanceof Wall)
            mWalls.add((Wall) body);
        else if (body instanceof Laser)
            mLasers.add((Laser) body);
        else if (body instanceof Ball)
            mBall = (Ball) body;
    }

    public void addTouchEvent(MotionEvent event) {
        synchronized (mTouchLock) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                // Make sure we don't overwrite a touch start
                // before it's consumed by this thread
                if (mTouchEvent == mTouchStart)
                    return;
                mTouchEvent = mTouchMove;
                break;
            case MotionEvent.ACTION_DOWN:
                mTouchEvent = mTouchStart;
                break;
            case MotionEvent.ACTION_UP:
                mTouchEvent = mTouchEnd;
                break;
            default:
                return;
            }
            mTouchEvent.x = (int) event.getX();
            mTouchEvent.y = (int) event.getY();
        }
    }

    private void resetGame() {
        reset();
        Level.setupLevel(this, mLevel);
    }

    @Override
    public void run() {
        mState = State.RUNNING;
        while (true) {
            synchronized (this) {
                while (mThreadSuspended > 0) {
                    if (mState == State.RUNNING) {
                        mState = State.PAUSED;
                        mHandler.sendEmptyMessage(PAUSED);
                    }
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
                Canvas canvas = mSurfaceHolder.lockCanvas();
                if (canvas != null) {
                    try {
                        updateInput();
                        if (mState == State.RUNNING) {
                            updatePhysics();
                        }
                        // updateAnimations();
                        // updateSound();
                        updateGraphics(canvas);
                    } finally {
                        mSurfaceHolder.unlockCanvasAndPost(canvas);
                    }
                }
            }
        }
    }

    /**
     * Take care of the touch events received from the main thread
     */
    private void updateInput() {
        if (mTouchEvent != null) {
            synchronized (mTouchLock) {
                if (mTouchEvent == mTouchMove) {
                    touchMove();
                } else if (mTouchEvent == mTouchStart) {
                    touchStart();
                } else {
                    touchEnd();
                }
                mTouchEvent = null;
            }
        }
    }

    private void touchStart() {

        mSwipee = null;

        // Resume/restart game if not already running
        if (mState != State.RUNNING) {
            if (mState != State.PAUSED)
                resetGame();
            mState = State.RUNNING;
            mHandler.sendEmptyMessage(START);
            return;
        }

        int x = mTouchStart.x;
        int y = mTouchStart.y;

        // Check if a swipeable body is being touched
        for (Wall wall : mWalls) {
            if (wall.contains(x, y)) {
                mSwipee = wall;
                mSwipee.onTouchStart();
                switch (mSwipee.getOrientation()) {
                case HORIZONTAL:
                    calculateHorizontalSwipeLimits();
                    break;
                case VERTICAL:
                    calculateVerticalSwipeLimits();
                    break;
                }
                return;
            }
        }
    }

    private void calculateHorizontalSwipeLimits() {
        float halfWidth = mSwipee.getWidth() / 2f;
        mLeftLimit = -Physics.HALF_WIDTH + halfWidth;
        mRightLimit = Physics.HALF_WIDTH - halfWidth;

        Rect swipeeBounds = mSwipee.getScreenBounds();

        for (Wall wall : mWalls) {
            if (wall == mSwipee)
                continue;

            Rect bounds = wall.getScreenBounds();

            // Check vertical intersection
            if (bounds.top < swipeeBounds.bottom && bounds.bottom > swipeeBounds.top) {
                if (bounds.left > swipeeBounds.right) {
                    // Calculate new right limit
                    int diff = bounds.left - swipeeBounds.right;
                    float newRightLimit = mSwipee.getPosition().x + SizeUtil.fromScreen(diff - 1);
                    if (newRightLimit < mRightLimit)
                        mRightLimit = newRightLimit;
                } else if (bounds.right < swipeeBounds.left) {
                    // Calculate new left limit
                    int diff = swipeeBounds.left - bounds.right;
                    float newLeftLimit = mSwipee.getPosition().x - SizeUtil.fromScreen(diff - 1);
                    if (newLeftLimit > mLeftLimit)
                        mLeftLimit = newLeftLimit;
                }
            }
        }
    }

    private void calculateVerticalSwipeLimits() {
        float halfHeight = mSwipee.getHeight() / 2f;
        mTopLimit = Physics.HALF_HEIGHT - halfHeight;
        mBottomLimit = -Physics.HALF_HEIGHT + halfHeight;

        Rect swipeeBounds = mSwipee.getScreenBounds();

        for (Wall wall : mWalls) {
            if (wall == mSwipee)
                continue;

            Rect bounds = wall.getScreenBounds();

            // Check Horizontal intersection
            if (bounds.left < swipeeBounds.right && bounds.right > swipeeBounds.left) {
                if (bounds.top > swipeeBounds.bottom) {
                    // Calculate new bottom limit
                    int diff = bounds.top - swipeeBounds.bottom;
                    float newBottomLimit = mSwipee.getPosition().y - SizeUtil.fromScreen(diff - 1);
                    if (newBottomLimit > mBottomLimit)
                        mBottomLimit = newBottomLimit;
                } else if (bounds.bottom < swipeeBounds.top) {
                    // Calculate new top limit
                    int diff = swipeeBounds.top - bounds.bottom;
                    float newtopLimit = mSwipee.getPosition().y + SizeUtil.fromScreen(diff - 1);
                    if (newtopLimit < mTopLimit)
                        mTopLimit = newtopLimit;
                }
            }
        }
    }

    private void touchMove() {

        if (mSwipee == null)
            return;

        switch (mSwipee.getOrientation()) {
        case HORIZONTAL:
            moveHorizontal();
            break;
        case VERTICAL:
            moveVertical();
            break;
        }
    }

    private void moveHorizontal() {
        // User moved finger to a new position
        // Calculate how far the finger moved
        float diffX = SizeUtil.fromScreen(mTouchMove.x - mTouchStart.x);

        // Move one of the walls
        Vector2 pos = mSwipee.getPosition();
        pos.x += diffX;

        // Check swipe limits
        if (pos.x < mLeftLimit)
            pos.x = mLeftLimit;
        else if (pos.x > mRightLimit)
            pos.x = mRightLimit;

        mSwipee.setPosition(pos);

        // Remember the new position
        mTouchStart.x = mTouchMove.x;
    }

    private void moveVertical() {
        // User moved finger to a new position
        // Calculate how far the finger moved
        float diffY = SizeUtil.fromScreen(mTouchMove.y - mTouchStart.y);

        // Move one of the walls
        Vector2 pos = mSwipee.getPosition();
        pos.y -= diffY;

        // Check swipe limits
        if (pos.y > mTopLimit)
            pos.y = mTopLimit;
        else if (pos.y < mBottomLimit)
            pos.y = mBottomLimit;

        mSwipee.setPosition(pos);

        // Remember the new position
        mTouchStart.y = mTouchMove.y;

        if (mLasers.size() > 0) {
            float top = pos.y + mSwipee.getHeight() / 2f;
            float bottom = pos.y - mSwipee.getHeight() / 2f;
            for (Laser laser : mLasers) {
                float laserY = laser.getPosition().y;
                if (top > laserY && bottom < laserY)
                    laser.block(pos.x + mSwipee.getWidth() / 2f);
                else
                    laser.unBlock();
            }
        }
    }

    private void touchEnd() {
        if (mSwipee == null)
            return;

        mSwipee.onTouchEnd();

        mSwipee = null;
    }

    private void updatePhysics() {

        mPhysics.update();

        if (mCollision == null)
            return;

        Body collider = mCollision.collider;
        Body ball = mCollision.ball;

        // Visualize the ball going into a hole
        if (collider instanceof Circle) {
            ball.setPosition(collider.getPosition());
        } else if (collider instanceof SquareHole) {
            SquareHole hole = (SquareHole) collider;

            // Calculate the nearest x-position inside the hole
            float x = ball.getPosition().x;
            float minX = hole.getPosition().x - hole.getWidth() / 2f + 1f;
            if (x < minX) {
                x = minX;
            } else {
                float maxX = hole.getPosition().x + hole.getWidth() / 2f - 1f;
                if (x > maxX)
                    x = maxX;
            }

            // Calculate the nearest y-position inside the hole
            float y = ball.getPosition().y;
            float minY = hole.getPosition().y - hole.getHeight() / 2f + 1f;
            if (y < minY) {
                y = minY;
            } else {
                float maxY = hole.getPosition().y + hole.getHeight() / 2f - 1f;
                if (y > maxY)
                    y = maxY;
            }

            ball.setPosition(new Vector2(x, y));
        }

        if (collider instanceof Bridge) {
            Bridge bridge = (Bridge) collider;
            bridge.collision(ball);
        } else if (collider instanceof Goal) {
            mState = State.WIN;
            if (mLevel < Level.count()) {
                ++mLevel;
                mHandler.sendEmptyMessage(WIN);
            } else {
                mLevel = 1;
                mHandler.sendEmptyMessage(COMPLETED);
            }
            mCollision = null;
        } else {
            mState = State.GAME_OVER;
            mHandler.sendEmptyMessage(GAME_OVER);
            mCollision = null;
        }
    }

    private void updateGraphics(Canvas canvas) {

        // Draw background
        mBackground.draw(canvas);

        // Draw the bodies
        for (Body body : mBodies) {
            body.draw(canvas);
        }
    }

    // Collision between two bodies started
    @Override
    public void beginContact(Contact contact) {

        Vector2 ballSpeed = mBall.getSpeed();
        if (ballSpeed.len2() > 25) {
            Vector2 normal = contact.GetWorldManifold().getNormal();
            float collisionSpeed = Math.abs(normal.dot(ballSpeed));
            if (collisionSpeed > 10)
                SwipeNRoll.sVibrator.vibrate((long) collisionSpeed);
        }

        Body body1 = (Body) contact.getFixtureA().getBody().getUserData();
        if (body1 == null)
            return;

        Body body2 = (Body) contact.getFixtureB().getBody().getUserData();
        if (body2 == null)
            return;

        if (body1.isCollider()) {
            mCollision = new Collision(body2, body1);
        } else if (body2.isCollider()) {
            mCollision = new Collision(body1, body2);
        }
    }

    // Collision ended
    @Override
    public void endContact(Contact contact) {
        Body body1 = (Body) contact.getFixtureA().getBody().getUserData();
        if (body1 == null)
            return;

        Body body2 = (Body) contact.getFixtureB().getBody().getUserData();
        if (body2 == null)
            return;

        if (body1.isCollider() || body2.isCollider()) {
            mCollision = null;
        }
    }
}