Снова привет!
Вот тутта мы с вами начали писать змею для Android. А сегодня продолжим это благородное занятие. Итак, у нас имеется класс, определяющий логику самой игры и есть занятная activity — которая с одной стороны — главное меню, а с другой отображает результаты последней игры.
Продолжим! Следующий класс у нас будет наследоваться от SurfaceView — в этом классе мы будем производить прорисовку. Ничего сложного в нем нет. Вообще ничего! Просто конструктор, в котором мы создаем новое игровое поле и загружаем ресурсы (читай картинки) а так же простой метод для отрисовки. Собственно вот его код:
package ru.davidmd.simpleSnake; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.SurfaceView; public class GameSurface extends SurfaceView { SnakeGame mField; Bitmap mHead, mTill, mBody, mBg, mFruite; String someText = "123"; float x, y; // Установка новых кординат телефона в пространстве // для того чтобы правильно нарисовать кружки на фоне public void setXY(float x, float y) { this.x = x; this.y = y; } // Собственно конструктор в котором мы загружаем // из ресурсов битмапы и добавляем метод обратного вызова // для нашей Surface public GameSurface(Context context) { super(context); //this.getHolder().addCallback(this); mField = new SnakeGame(); mHead = BitmapFactory.decodeResource(context.getResources(), R.drawable.head); mTill = BitmapFactory.decodeResource(context.getResources(), R.drawable.till); mBody = BitmapFactory.decodeResource(context.getResources(), R.drawable.body); mBg = BitmapFactory.decodeResource(context.getResources(), R.drawable.bg); mFruite = BitmapFactory.decodeResource(context.getResources(), R.drawable.fruite); } // метод в котором устанавливаем текст public void setSomeText(String someText) { this.someText = someText; } // Рисуем здесь void drawSnake(Canvas c) { int width = c.getWidth(); int height = c.getHeight(); int mx = width / SnakeGame.mFieldX; int my = height / SnakeGame.mFieldY; // стрейчим битмапы Bitmap head = Bitmap.createScaledBitmap(mHead, mx, my, true); Bitmap body = Bitmap.createScaledBitmap(mBody, mx, my, true); Bitmap till = Bitmap.createScaledBitmap(mTill, mx, my, true); Bitmap bg = Bitmap.createScaledBitmap(mBg, mx, my, true); // создаем на всякий кисточку Paint paint = new Paint(); paint.setColor(Color.CYAN); // рисуем кружки c.drawCircle(width / 2, height / 2, width / 4, paint); paint.setColor(Color.BLUE); c.drawCircle(width / 2 - x * 5, height / 2 + y * 5, width / 10, paint); paint.setColor(Color.BLACK); paint.setAlpha(128); Bitmap fruite = Bitmap.createScaledBitmap(mFruite, mx, my, true); // рисуем игровое поле с фруктами на нем for (int i = 0; i < SnakeGame.mFieldX; i++) { for (int j = 0; j < SnakeGame.mFieldY; j++) { c.drawBitmap(bg, mx * i, my * j, paint); if (mField.getmField()[i][j] > 1) { c.drawBitmap(fruite, mx * i, my * j, paint); } } } paint.setAlpha(0); // рисуем змею for (int i = 0; i < mField.getSnakeLength(); i++) { c.drawBitmap(body, mField.getmSnake().get(i).x * mx, mField .getmSnake().get(i).y * my, new Paint()); if (i == 0) { c.drawBitmap(till, mField.getmSnake().get(i).x * mx, mField .getmSnake().get(i).y * my, new Paint()); } if (i == mField.getSnakeLength() - 1) { c.drawBitmap(head, mField.getmSnake().get(i).x * mx, mField .getmSnake().get(i).y * my, new Paint()); } } // рисуем текст paint.setColor(Color.WHITE); paint.setAlpha(255); paint.setTextSize(15); c.drawText(someText, 50, 50, paint); } }
Идем дальше?
Следующим шагом будет создание самой игровой активити. Именно на ней у нас будет располагаться экземпляр GameSurface именно она будет обрабатывать показания акселерометра и определять куда пользователь решил двинуть змею. Ну а вот и сам код:
package ru.davidmd.simpleSnake; import java.util.List; import java.util.Timer; import android.app.Activity; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.view.WindowManager; public class GameActivity extends Activity implements SensorEventListener { GameSurface surf; Timer t; int width, height; SensorManager mSensorManager; Sensor mAccelerometerSensor; float SSX = 0, SSY = 0; float SX = 0, SY = 0; boolean firstTime; // Ну тут обрабатываем создание активити @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); surf = new GameSurface(this); this.setContentView(surf); t = new Timer(); height = this.getWindowManager().getDefaultDisplay().getHeight(); width = this.getWindowManager().getDefaultDisplay().getWidth(); // Инициализируем акселерометр mSensorManager = (SensorManager) getSystemService(Activity.SENSOR_SERVICE); List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL); if (sensors.size() > 0) { for (Sensor sensor : sensors) { if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) { if (mAccelerometerSensor == null) mAccelerometerSensor = sensor; } } } } // Запуск активити @Override public void onStart() { super.onStart(); // Запускаем таймер обновления картинки на экране t.scheduleAtFixedRate(new GraphUpdater(surf), 0, 100); // Запускаем таймер обновления положения змейки t.scheduleAtFixedRate(new StepUpdater(this), 0, 500); // регистрируем нашу форму как объект слушающий // изменения датчика - акселерометра mSensorManager.registerListener(this, mAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME); this.firstTime = true; getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } // Обрабатываем остановку активити @Override public void onStop() { super.onStop(); // Останавливаем таймеры t.cancel(); t.purge(); // Отписываемся от получения сообщений об изменении // от датчика mSensorManager.unregisterListener(this); } // @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // Do nothing! } // метод, который определяет по показаниям акселерометра // передаваемым ему как параметры (х и у) // в каком направлении должна двигаться змея private int getDirection(float x, float y) { if (Math.abs(x) > Math.abs(y)) { if (x > 0) { return SnakeGame.DIR_WEST; } else { return SnakeGame.DIR_EAST; } } else { if (y > 0) { return SnakeGame.DIR_SOUTH; } else { return SnakeGame.DIR_NORTH; } } } // А вот так мы обрабатываем изменение ориентации // телефона в пространстве // @Override public void onSensorChanged(SensorEvent event) { surf.setSomeText("Your score is: "+SimpleSnakeActivity.GAME_SCORE); // получаем показания датчика SX = event.values[0]; SY = event.values[1]; // Если игра уже идет, то if (!this.firstTime) { // получаем положение телефона в пространстве // с поправкой на его начальное положение float dirX = SX - SSX; float dirY = SY - SSY; // Устанавливаем для змеи новое направление surf.mField.setDirection(this.getDirection(dirX, dirY)); // передаем в нашу повержность координаты телефона в пространстве surf.setXY(dirX, dirY); } else { // Если игра только началась делаем поправку на начальное // положение телефона this.firstTime = false; SSX = SX; SSY = SY; } } // Этот метод вызывается из потока одного из таймеров // именно в этом методе происходит движение змейки в // зависимости от ее направления установленного в предидущем // методе public void Step() { // Если ход не удался то закрываем текущую активити if (!surf.mField.nextMove()) { SimpleSnakeActivity.GAME_MODE=1; this.finish(); } // Если все впорядке то обновляем очки // в стартовой активити else{ SimpleSnakeActivity.GAME_SCORE=this.surf.mField.mScore; } } }
Теперь давайте смотреть, что же тут к чему! В переменных имеется surf — экземпляр GameSurface, таймер t, SensorManager и Sensor. Все это и еще немного другого пригодится нам для того чтобы наша змея начала жить своей жизнью 🙂 (под нашим чутким руководством). Посмотрим что у нас имеется в методе onCreate() : первым делом мы создаем игровую поверхность (surf) и размещаем ее на нашей активити. Затем инициализируем таймер. Следующим шагом получаем ширину и высоту экрана. А вот дальше самое на мой взгляд интересное. С помощью метода getSystemService() мы получаем значение для переменной mSensorManager, а у нее уже запрашиваем список всех датчиков, установленных на устройстве. Затем в цикле перебираем все датчики до тех пор, пока не найдем акселерометр! Ура, сделано!
Теперь идем в метод onStart() который как вы помните, отвечает за то что будет происходить во время запуска активити. Здесь мы планируем два пока что непонятных действия в таймере (мы еще к этому вернемся) и здесь же регистрируем нашу форму (реализующую интерфейс SensorEventListener) как слушателя событий связанных с изменением датчиков. ну а затем указываем системе чтоб не глушила подсветку во время работы нашей activity.
Ну, думаю в методе onStop() ничего сложного нет, там думаю и так все понятно. В методе getDirection() мы определяем в какую сторону должна двигаться змея в зависимости от отклонения телефона по оси x или y. Сами отклонения передаются сюда как параметры. ну и следующий интересный метод — onSensorChanged(). Он вызывается тогда, когда меняется положение телефона в пространстве. Прежде всего мы получаем сырые данные от датчика, затем проверяем, если игра только что открылась, и это первый раз, когда мы считываем координаты, то принимаем текущее положение телефона за эталонное. Это нужно для того, чтобы пользователь мог играть не только расположив телефон горизонтально. В таком случае текущие координаты становятся поправкой. Если мы уже не первый раз считываем положение датчика, тогда высчитываем отклонение смарта в ту или иную сторону исходя из начальной поправки и текущих показаний датчика. И устанавливаем новое направление движения змеи.
Последний метод в этом классе — step(). В его задачи входит подать команду змее на новый ход и посмотреть, что ответит змея. Если змея может туда идти, то все нормально, а если нет, то тут мы закрываем нашу активити и автоматом попадаем на предыдущую, о которой я писал в прошлый раз.
Ну и самое последнее — собственно то что и оживляет всю-всю нашу игру! Это два очень схожих класса: GraphUpdater и StepUpdater. Оба класса являются наследниками TimerTask. Т.е. — это задания таймера, которые мы можем запланировать. (Что мы и сделали в методе onStart()). Каждый из них занят своим очень важным делом. GraphUpdater при создании должен получить экземпляр GameSurface. Само задание заключается всего лишь в вызове метода отрисовки! Вот код этого класса:
package ru.davidmd.simpleSnake; import java.util.TimerTask; import android.graphics.Canvas; import android.graphics.Color; public class GraphUpdater extends TimerTask { GameSurface surf; GraphUpdater(GameSurface surf){ this.surf = surf; } @Override public void run() { Canvas c = surf.getHolder().lockCanvas(); if (c!=null){ c.drawColor(Color.BLACK); surf.drawSnake(c); surf.getHolder().unlockCanvasAndPost(c); } } }
А что же делает StepUpdater? Ну тут все еще проще! Он просто с вызывает метод step() из переданной ему в конструктор GameActivity. Сам код:
package ru.davidmd.simpleSnake; import java.util.TimerTask; public class StepUpdater extends TimerTask { GameActivity act; StepUpdater(GameActivity s){ this.act = s; } @Override public void run() { act.Step(); } }
Таким образом задавая интервал для выполнения этих заданий мы можем регулировать скорость движения змейки и скорость отрисовки.
На этом у меня все! Игра готова!
UPD вот готовый проект по просьбам трудящихся.
Напишите про то, как вставлять баннеры в свои приложения и получать проценты от рекламодателя
О! Хорошо! Действительно тема интересная 🙂
на android.com есть инфа про рекламу)
Имеется аккаунт разработчика на paly.google.com и интересные идеи…. Также опыт работы с рекламой… Если что пиши)))
И да, спасибо за очень полезные(а главное понятные) статьи
Да незачто! 🙂 рад что было полезно!
А на github нельзя проект выложить с рисунками и оформлением? Было бы хорошо.
Спасибо
Да как-то времени нет. Проект сам могу потом тут выложить 🙂 чтоб не заморачиваться.
Пожалуйста выложите либо весь проект, либо картинки к нему, я не знаю и не смог найти статьи в каком формате надо картинки.
Вот код ваш:
mField = new SnakeGame();
mHead = BitmapFactory.decodeResource(context.getResources(),
R.drawable.head);
mTill = BitmapFactory.decodeResource(context.getResources(),
R.drawable.till);
mBody = BitmapFactory.decodeResource(context.getResources(),
R.drawable.body);
mBg = BitmapFactory.decodeResource(context.getResources(),
R.drawable.bg);
mFruite = BitmapFactory.decodeResource(context.getResources(),
R.drawable.fruite);
У меня тут постоянно ошибка из-за того что нет картинок, а те которые я сам сделал в формате bmp не подходят.
Выложите либо сам проект пожалуйста либо картинки что-ли.. можно на мейл.
Сейчас выложу. Подождите.
Собственно вот, смотрите в конце статьи 🙂
Спасибо огромное))))
У вас случайно apk данной игры не закалялся ?
Подскажите — запустил игру на машине, но при нажатии на кнопки змейка не двигается…корректно работает только на устройстве?
Ну там управление только с акселерометра
У меня ваш проект при импорте в eclipse выдает ошибку в файле StepUpdater.
Плюс не один .xml не открывается. В консоле ошибка: Unable to resolve target ‘android-7’
Может сталкивались с этой проблемой кто. Как это убрать?
Выдавало аналогичную ошибку помог следующий комментарий на форуме: «right-click on your project and select «Properties -> Java Compiler», check «Enable project specific settings» and select 1.5 or 1.6 from «Compiler compliance settings» select box.»
После выбора версии 1.6 всё удачно запустилось.
Доброе время суток.
Хотел добавить паузу, можете подсказать как это можно сделать?
Заранее спасибо =)
Ссылка на полный проект активна или нет? У меня ничего не происходит при нажатии
Оппа! Ссылка почему-то не работает постараюсь в ближайшие пару дней найти и загрузить приложение заново
Ссылку ссылку… дайте ссылку на готовый проект!!!
Спасибо.
Да я в общем-то и не помню где у меня этот проект 🙁 Разве что могу попробовать заново собрать его 🙂
Если не трудно, буду очень признателен!
И если можно пришлите на kovtyn.dima@gmail.com
Еще раз благодарю!
Уважаемый davidmd, так что на счет ссылки или готового проекта, а что то у меня по мануалу ну никак собрать не получается…
Буду очень-очень признателен.
Так я не понимаю, почему нет препятсвий в готовом проекте?
Подскажите пожалуйста, как сделать чтобы скорость менялась от угла наклона телефона (как мне высчитать этот угол акселератора)?
Если не затруднить, помогите с данным вопросом)
А не подскажете, как сделать так, чтобы счет не сбрасывался при перезагрузке игры?
Самый простой вариант хранить счет в SharedPreferences
А если делать управление змейки с помощью нажатия на экран? сильно ли придется изменять код?
Ну наверно не очень. Просто вместо слушателя событий датчика нужен слушатель экранных нажатий
В этой части кода Eclipse подчеркивает getHolder(). / drawSnake(c), / getHolder() снова. В чем может быть дело из подсказок эклипса не понимаю 🙁 советует три варианта: 1. изменить на getHandler() 2. создать метод getHolder() в GameSurface() 3. Добавить cast в ‘surf’. =(
@Override
public void run() {
Canvas c = surf.getHolder().lockCanvas();
if (c!=null){
c.drawColor(Color.BLACK);
surf.drawSnake(c);
surf.getHolder().unlockCanvasAndPost(c);
}
}
А какая подсказка? А скриншот можно?
Программа работоспособная. Нашел только одну ошибочку: перепутана координата «Y» на «X» когда двигаемся на юг (т.е. вниз).
А так, очень хороший пример работы акселерометра.
Ну и хорошо было бы добавить паузу в игре. И голову со стрелкой хотя бы! А то не понятно куда она движется))
Автору респект!
Если вы это исправите и добавите — с удовольствием выложу здесь, безусловно указав Вас ))
Скоро доделаю рабочий вариант и дам Вам ссылочку! 🙂
Вот и ссылка на готовую первую версию:
https://yadi.sk/d/yl4T1y98dDVmX
Там можно управлять как при помощи акселерометра, так и при помощи касаний по полям. Переключение режимов производится нажатием на монетку!
Удачи и спасибо за скелет программы!
Счас посмотрим )))
Прикольно! Не хотите здесь сорс выложить и статейку написать?
Спасибо, конечно, но я не сижу на месте и уже пишу решение пятнашек! Предварительно за 4 минуты находит решение, если ходов меньше 50. )))
открыаю готовый проект в eclipce , а он пустой
как открыть?
Какой проект?
этот
«UPD вот готовый проект по просьбам трудящихся.»
Добрый день!
Подскажите, пожалуйста, как исходник можно запустить? Для какой версии предназначен данный исходник?
В eclipse, idea и android studio не запускается с такой проблемой (на картинке):
https://pp.vk.me/c625129/v625129090/204c6/6vsxRVuuyG8.jpg
Не подскажете, как убрать само изображение акселерометра в центре экрана?
Там просто кружочки рисовать не надо и все 🙂
В Android Studio построил .apk. При запуске на устройстве (Android Marshmallow 6.0.1) моментально вылетает. Что подскажете?
О, наверно вам надо написать с какой ошибкой и на какой строке 🙂 Посмотрите в логе 🙂
Доброго времени, как добавить паузу и как сохранять очки, точнее самое большое кол-во очко и выводить их в отдельном окне ?