Android Snake Game(如何添加音频)?

时间:2017-02-15 00:30:42

标签: java android android-studio audio

我试图修改Snake游戏代码以触发以下声音:

  • 从瓷砖移动到瓷砖
  • 当它吃苹果时
  • 当它碰到自己或墙壁时

我认为代码与GitHub上提供的代码相同或几乎相同。我尝试使用MediaPlayer类为每个声音创建一个MediaPlayer对象:

MediaPlayer mpOver = new MediaPlayer();

然后通过以下方式将对象设置为原始文件:

mpOver.create(Snake.this, R.raw.bump);

然后通过以下方法触发声音:

mpOver.start();

但是,我只能在Snake类的onCreate方法中使用它。

截至目前,我只有一个MediaPlayer对象被声明为mpOver。当蛇从一块瓷砖移动到另一块瓷砖时,最终会发出声音。我在onCreate方法的Snake类中使用它的原因只是为了测试我是否可以让它发出声音,它在启动时会发出声音。我只是不知道如何将它发送到其余部分该应用程序。有人可以提供任何建议吗?

Snake类的更新代码如下:

感谢您的帮助,我仍然遇到问题。我使用了你的代码,它运行良好,没有音频,直到我添加

MediaPlayer mpOver = activity.getMpOver();
mpOver.start();

当调用应该播放声音的方法时,应用程序崩溃了。

我在android监视器中收到此错误:

  

02-15 09:25:51.576 4163-4163 / com.example.android.snake E / AndroidRuntime:FATAL EXCEPTION:main                                                                            处理:com.example.android.snake,PID:4163                                                                            java.lang.NullPointerException:尝试调用虚方法' void android.media.MediaPlayer.start()'在null对象引用上                                                                                在com.example.android.snake.SnakeView.updateSnake(SnakeView.java:501)                                                                                在com.example.android.snake.SnakeView.update(SnakeView.java:427)                                                                                在com.example.android.snake.SnakeView $ RefreshHandler.handleMessage(SnakeView.java:131)                                                                                在android.os.Handler.dispatchMessage(Handler.java:102)                                                                                在android.os.Looper.loop(Looper.java:148)                                                                                在android.app.ActivityThread.main(ActivityThread.java:5417)                                                                                at java.lang.reflect.Method.invoke(Native Method)                                                                                在com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:726)                                                                                在com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

抱歉,我的更新代码低于......并且非常感谢您的帮助。

package com.example.android.snake;

import android.content.Context;
import android.content.res.Resources;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Random;

 /**
 * SnakeView: implementation of a simple game of Snake
 * 
 * 
 */


public class SnakeView extends TileView {



private static final String TAG = "SnakeView";

private Snake activity;

public void setActivity(Snake activity) {
    this.activity = activity;
}


/**
 * Current mode of application: READY to run, RUNNING, or you have already
 * lost. static final ints are used instead of an enum for performance
 * reasons.
 */
private int mMode = READY;
public static final int PAUSE = 0;
public static final int READY = 1;
public static final int RUNNING = 2;
public static final int LOSE = 3;

/**
 * Current direction the snake is headed.
 */
private int mDirection = NORTH;
private int mNextDirection = NORTH;
private static final int NORTH = 1;
private static final int SOUTH = 2;
private static final int EAST = 3;
private static final int WEST = 4;

/**
 * Labels for the drawables that will be loaded into the TileView class
 */
private static final int RED_STAR = 1;
private static final int YELLOW_STAR = 2;
private static final int GREEN_STAR = 3;

/**
 * mScore: used to track the number of apples captured mMoveDelay: number of
 * milliseconds between snake movements. This will decrease as apples are
 * captured.
 */
private long mScore = 0;
private long mMoveDelay = 600;
/**
 * mLastMove: tracks the absolute time when the snake last moved, and is used
 * to determine if a move should be made based on mMoveDelay.
 */
private long mLastMove;

/**
 * mStatusText: text shows to the user in some run states
 */
private TextView mStatusText;

/**
 * mSnakeTrail: a list of Coordinates that make up the snake's body
 * mAppleList: the secret location of the juicy apples the snake craves.
 */
private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();

/**
 * Everyone needs a little randomness in their life
 */
private static final Random RNG = new Random();

/**
 * Create a simple handler that we can use to cause animation to happen.  We
 * set ourselves as a target and we can use the sleep()
 * function to cause an update/invalidate to occur at a later date.
 */
private RefreshHandler mRedrawHandler = new RefreshHandler();

class RefreshHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        SnakeView.this.update();
        SnakeView.this.invalidate();

    }

    public void sleep(long delayMillis) {
        this.removeMessages(0);
        sendMessageDelayed(obtainMessage(0), delayMillis);
    }
   };


/**
 * Constructs a SnakeView based on inflation from XML
 * 
 * @param context
 * @param attrs
 */
public SnakeView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initSnakeView();


 }

public SnakeView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    initSnakeView();
 }

private void initSnakeView() {
    setFocusable(true);

    Resources r = this.getContext().getResources();

    resetTiles(4);
    loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
    loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
    loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));

  }


private void initNewGame() {
    mSnakeTrail.clear();
    mAppleList.clear();


    // For now we're just going to load up a short default eastbound snake
    // that's just turned north


    mSnakeTrail.add(new Coordinate(7, 7));
    mSnakeTrail.add(new Coordinate(6, 7));
    mSnakeTrail.add(new Coordinate(5, 7));
    mSnakeTrail.add(new Coordinate(4, 7));
    mSnakeTrail.add(new Coordinate(3, 7));
    mSnakeTrail.add(new Coordinate(2, 7));
    mNextDirection = NORTH;

    // Two apples to start with
    addRandomApple();
    addRandomApple();

    mMoveDelay = 600;
    mScore = 0;
}


/**
 * Given a ArrayList of coordinates, we need to flatten them into an array of
 * ints before we can stuff them into a map for flattening and storage.
 * 
 * @param cvec : a ArrayList of Coordinate objects
 * @return : a simple array containing the x/y values of the coordinates
 * as [x1,y1,x2,y2,x3,y3...]
 */
private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {
    int count = cvec.size();
    int[] rawArray = new int[count * 2];
    for (int index = 0; index < count; index++) {
        Coordinate c = cvec.get(index);
        rawArray[2 * index] = c.x;
        rawArray[2 * index + 1] = c.y;
    }
    return rawArray;
}

/**
 * Save game state so that the user does not lose anything
 * if the game process is killed while we are in the 
 * background.
 * 
 * @return a Bundle with this view's state
 */
public Bundle saveState() {
    Bundle map = new Bundle();

    map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
    map.putInt("mDirection", Integer.valueOf(mDirection));
    map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
    map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));
    map.putLong("mScore", Long.valueOf(mScore));
    map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));

    return map;
}

/**
 * Given a flattened array of ordinate pairs, we reconstitute them into a
 * ArrayList of Coordinate objects
 * 
 * @param rawArray : [x1,y1,x2,y2,...]
 * @return a ArrayList of Coordinates
 */
private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {
    ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();

    int coordCount = rawArray.length;
    for (int index = 0; index < coordCount; index += 2) {
        Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
        coordArrayList.add(c);
    }
    return coordArrayList;
}

/**
 * Restore game state if our process is being relaunched
 * 
 * @param icicle a Bundle containing the game state
 */
public void restoreState(Bundle icicle) {
    setMode(PAUSE);

    mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
    mDirection = icicle.getInt("mDirection");
    mNextDirection = icicle.getInt("mNextDirection");
    mMoveDelay = icicle.getLong("mMoveDelay");
    mScore = icicle.getLong("mScore");
    mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
}

/*
 * handles key events in the game. Update the direction our snake is traveling
 * based on the DPAD. Ignore events that would cause the snake to immediately
 * turn back on itself.
 * 
 * (non-Javadoc)
 * 
 * @see android.view.View#onKeyDown(int, android.os.KeyEvent)
 */
@Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {

    if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
        if (mMode == READY | mMode == LOSE) {
            /*
             * At the beginning of the game, or the end of a previous one,
             * we should start a new game.
             */
            initNewGame();
            setMode(RUNNING);
            update();
            return (true);
        }

        if (mMode == PAUSE) {
            /*
             * If the game is merely paused, we should just continue where
             * we left off.
             */
            setMode(RUNNING);
            update();
            return (true);
        }

        if (mDirection != SOUTH) {
            mNextDirection = NORTH;
        }
        return (true);
    }

    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
        if (mDirection != NORTH) {
            mNextDirection = SOUTH;
        }
        return (true);
    }

    if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
        if (mDirection != EAST) {
            mNextDirection = WEST;
        }
        return (true);
    }

    if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
        if (mDirection != WEST) {
            mNextDirection = EAST;
        }
        return (true);
    }

    return super.onKeyDown(keyCode, msg);
}

/**
 * Sets the TextView that will be used to give information (such as "Game
 * Over" to the user.
 * 
 * @param newView
 */
public void setTextView(TextView newView) {
    mStatusText = newView;
}

/**
 * Updates the current mode of the application (RUNNING or PAUSED or the like)
 * as well as sets the visibility of textview for notification
 * 
 * @param newMode
 */
public void setMode(int newMode) {
    int oldMode = mMode;
    mMode = newMode;

    if (newMode == RUNNING & oldMode != RUNNING) {
        mStatusText.setVisibility(View.INVISIBLE);
        update();
        return;
    }

    Resources res = getContext().getResources();
    CharSequence str = "";
    if (newMode == PAUSE) {
        str = res.getText(R.string.mode_pause);
    }
    if (newMode == READY) {
        str = res.getText(R.string.mode_ready);
    }
    if (newMode == LOSE) {
        str = res.getString(R.string.mode_lose_prefix) + mScore
              + res.getString(R.string.mode_lose_suffix);
    }

    mStatusText.setText(str);
    mStatusText.setVisibility(View.VISIBLE);
}

/**
 * Selects a random location within the garden that is not currently covered
 * by the snake. Currently _could_ go into an infinite loop if the snake
 * currently fills the garden, but we'll leave discovery of this prize to a
 * truly excellent snake-player.
 * 
 */
private void addRandomApple() {
    Coordinate newCoord = null;
    boolean found = false;
    while (!found) {
        // Choose a new location for our apple
        int newX = 1 + RNG.nextInt(mXTileCount - 2);
        int newY = 1 + RNG.nextInt(mYTileCount - 2);
        newCoord = new Coordinate(newX, newY);

        // Make sure it's not already under the snake
        boolean collision = false;
        int snakelength = mSnakeTrail.size();
        for (int index = 0; index < snakelength; index++) {
            if (mSnakeTrail.get(index).equals(newCoord)) {
                collision = true;
            }
        }
        // if we're here and there's been no collision, then we have
        // a good location for an apple. Otherwise, we'll circle back
        // and try again
        found = !collision;
    }
    if (newCoord == null) {
        Log.e(TAG, "Somehow ended up with a null newCoord!");
    }
    mAppleList.add(newCoord);
}

/**
 * Handles the basic update loop, checking to see if we are in the running
 * state, determining if a move should be made, updating the snake's location.
 */
public void update() {
    if (mMode == RUNNING) {
        long now = System.currentTimeMillis();

        if (now - mLastMove > mMoveDelay) {
            clearTiles();
            updateWalls();
            updateSnake();
            updateApples();
            mLastMove = now;
        }
        mRedrawHandler.sleep(mMoveDelay);
    }

}

/**
 * Draws some walls.
 * 
 */
private void updateWalls() {
    for (int x = 0; x < mXTileCount; x++) {
        setTile(GREEN_STAR, x, 0);
        setTile(GREEN_STAR, x, mYTileCount - 1);
    }
    for (int y = 1; y < mYTileCount - 1; y++) {
        setTile(GREEN_STAR, 0, y);
        setTile(GREEN_STAR, mXTileCount - 1, y);
    }
}

/**
 * Draws some apples.
 * 
 */
private void updateApples() {
    for (Coordinate c : mAppleList) {
        setTile(YELLOW_STAR, c.x, c.y);
    }
}

/**
 * Figure out which way the snake is going, see if he's run into anything (the
 * walls, himself, or an apple). If he's not going to die, we then add to the
 * front and subtract from the rear in order to simulate motion. If we want to
 * grow him, we don't subtract from the rear.
 * 
 */

private void updateSnake() {
    boolean growSnake = false;

    // grab the snake by the head
    Coordinate head = mSnakeTrail.get(0);
    Coordinate newHead = new Coordinate(1, 1);

    mDirection = mNextDirection;

    switch (mDirection) {
    case EAST: {
        newHead = new Coordinate(head.x + 1, head.y);
        break;
    }
    case WEST: {
        newHead = new Coordinate(head.x - 1, head.y);
        break;
    }
    case NORTH: {
        newHead = new Coordinate(head.x, head.y - 1);
        break;
    }
    case SOUTH: {
        newHead = new Coordinate(head.x, head.y + 1);
        break;
    }
    }

    // Collision detection
    // For now we have a 1-square wall around the entire arena
    if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)
            || (newHead.y > mYTileCount - 2)) {
        MediaPlayer mpOver = activity.getMpOver();
        mpOver.start();
        setMode(LOSE);




        return;

    }

    // Look for collisions with itself
    int snakelength = mSnakeTrail.size();
    for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
        Coordinate c = mSnakeTrail.get(snakeindex);
        if (c.equals(newHead)) {
            setMode(LOSE);
            //mpOver.start();
            return;
        }
    }

    // Look for apples
    int applecount = mAppleList.size();
    for (int appleindex = 0; appleindex < applecount; appleindex++) {
        Coordinate c = mAppleList.get(appleindex);
        if (c.equals(newHead)) {
            mAppleList.remove(c);
            addRandomApple();

            mScore++;
            mMoveDelay *= 0.9;

            growSnake = true;
        }
    }

    // push a new head onto the ArrayList and pull off the tail
    mSnakeTrail.add(0, newHead);
    // except if we want the snake to grow
    if (!growSnake) {
        mSnakeTrail.remove(mSnakeTrail.size() - 1);
    }

    int index = 0;
    for (Coordinate c : mSnakeTrail) {
        if (index == 0) {
            setTile(YELLOW_STAR, c.x, c.y);
        } else {
            setTile(RED_STAR, c.x, c.y);
        }
        index++;
    }

}

/**
 * Simple class containing two integer values and a comparison function.
 * There's probably something I should use instead, but this was quick and
 * easy to build.
 * 
 */
private class Coordinate {
    public int x;
    public int y;

    public Coordinate(int newX, int newY) {
        x = newX;
        y = newY;
    }

    public boolean equals(Coordinate other) {
        if (x == other.x && y == other.y) {
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        return "Coordinate: [" + x + "," + y + "]";
    }
}

}

Snake视图代码如下:

public class Snake extends Activity {

private SnakeView mSnakeView;


private static String ICICLE_KEY = "snake-view";

MediaPlayer mpOver;

public MediaPlayer getMpOver() {

    return mpOver;
}

/**
 * Called when Activity is first created. Turns off the title bar, sets up
 * the content views, and fires up the SnakeView.
 * 
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.snake_layout);
    mpOver.create(Snake.this, R.raw.bump);


    mSnakeView = (SnakeView) findViewById(R.id.snake);
    mSnakeView.setTextView((TextView) findViewById(R.id.text));
    mSnakeView.setActivity(this);

    if (savedInstanceState == null) {
        // We were just launched -- set up a new game
        mSnakeView.setMode(SnakeView.READY);
    } else {
        // We are being restored
        Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
        if (map != null) {
            mSnakeView.restoreState(map);
        } else {
            mSnakeView.setMode(SnakeView.PAUSE);
        }
    }
}

@Override
protected void onPause() {
    super.onPause();
    // Pause the game along with the activity
    mSnakeView.setMode(SnakeView.PAUSE);
}

@Override
public void onSaveInstanceState(Bundle outState) {
    //Store the game state
    outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
}



}

我们现在正在某个地方......它并没有崩溃,但也没有播放音频。这就是我在Android显示器中看到的内容。

  

02-15 10:18:13.409 17511-17511 /?我/艺术:不迟到-Xcheck:jni(已经开启)   02-15 10:18:13.478 17511-17511 / com.example.android.snake W / System:ClassLoader引用未知路径:/data/app/com.example.android.snake-1/lib/x86   02-15 10:18:13.486 17511-17511 / com.example.android.snake I / InstantRun:即时运行运行时启动。 Android包是com.example.android.snake,真正的应用程序类是null。   02-15 10:18:13.671 17511-17511 / com.example.android.snake W / System:ClassLoader引用未知路径:/data/app/com.example.android.snake-1/lib/x86   02-15 10:18:13.787 17511-17511 / com.example.android.snake D / MediaPlayer:MediaPlayer中的setSubtitleAnchor   02-15 10:18:18.037 17511-17511 / com.example.android.snake D / gralloc_ranchu:gralloc_unregister_buffer:退出HostConnection(是缓冲区处理线程)   02-15 10:18:28.095 17511-17511 / com.example.android.snake E / MediaPlayer:在状态1中调用   02-15 10:18:28.095 17511-17511 / com.example.android.snake E / MediaPlayer:错误(-38,0)   02-15 10:18:28.096 17511-17511 / com.example.android.snake E / MediaPlayer:错误(-38,0)

我添加了      activity.startMpOver(); 它希望我将以下方法添加到Snake视图中。我添加了它,没有任何改变。所以我将mpOver.start()添加到方法中。仍然没有去。

public void startMpOver() {
    mpOver.start();
}

我还是得到了这个

02-15 11:11:56.049 23934-23934 / com.example.android.snake E / MediaPlayer:在状态1中调用 02-15 11:11:56.049 23934-23934 / com.example.android.snake E / MediaPlayer:错误(-38,0) 02-15 11:11:56.057 23934-23934 / com.example.android.snake E / MediaPlayer:错误(-38,0) 02-15 11:12:17.878 23934-23934 / com.example.android.snake E / MediaPlayer:在状态0中调用 02-15 11:12:43.165 23934-23934 / com.example.android.snake E / MediaPlayer:在状态0中调用 02-15 11:12:52.872 23934-23934 / com.example.android.snake E / MediaPlayer:在状态0中调用

1 个答案:

答案 0 :(得分:0)

尝试将您的Snake活动类更改为:

public class Snake extends Activity {

    private SnakeView mSnakeView;

    private static String ICICLE_KEY = "snake-view";

    MediaPlayer mpOver; 
    boolean mpOverPrepared = false;

    public MediaPlayer getMpOver() {

        return mpOver;
    }

    /**
     * Called when Activity is first created. Turns off the title bar, sets up
     * the content views, and fires up the SnakeView.
     *
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.snake_layout);
        mpOver = new MediaPlayer();
        mpOver.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mpOverPrepared = true;
            }
        });
        mpOver.create(Snake.this, R.raw.bump);


        mSnakeView = (SnakeView) findViewById(R.id.snake);
        mSnakeView.setTextView((TextView) findViewById(R.id.text));
        mSnakeView.setActivity(this);

        if (savedInstanceState == null) {
            // We were just launched -- set up a new game
            mSnakeView.setMode(SnakeView.READY);
        } else {
            // We are being restored
            Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
            if (map != null) {
                mSnakeView.restoreState(map);
            } else {
                mSnakeView.setMode(SnakeView.PAUSE);
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // Pause the game along with the activity
        mSnakeView.setMode(SnakeView.PAUSE);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        //Store the game state
        outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
    }

    public void startMpOver() {
        if(mpOverPrepared) {
            mpOver.start();
        }
    }
}

然后,当你想在SnakeView中播放mpOver时,你可以这样做

activity.startMpOver();