【问题标题】:Android Snake Game (How to add audio)?Android 贪吃蛇游戏(如何添加音频)?
【发布时间】:2017-02-15 00:30:42
【问题描述】:

我正在尝试修改 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: 致命异常: main 进程:com.example.android.snake,PID:4163 java.lang.NullPointerException:尝试在空对象引用上调用虚拟方法“void android.media.MediaPlayer.start()” 在 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) 在 java.lang.reflect.Method.invoke(本机方法) 在 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/? I/art: 不延迟启用 -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:Instant Run Runtime 已启动。 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 中设置字幕锚 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)

我添加了 活动.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 开始调用

【问题讨论】:

    标签: java android android-studio audio


    【解决方案1】:

    尝试将您的 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();
    

    【讨论】:

    • 感谢您的帮助,但是我仍然遇到问题。我使用了你的代码,它运行良好,没有音频,直到我添加 MediaPlayer mpOver = activity.getMpOver(); mpOver.start();当调用应该播放声音的方法时,应用程序就会崩溃。
    • 请查看我编辑的主帖以查看 Android 监视器错误。
    • activity.getMpOver();返回空值。你必须解决这个问题。如果您不发布编辑后的代码,我无法帮助您
    • 我已经添加了更新的代码。由于字符限制,我不得不删除原始代码。非常感谢您的建议和帮助。
    • 好的,我补充了,现在它不会崩溃,但不会播放音频。请在编辑后的帖子中查看 Android 监视器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多