Главная страница
Навигация по странице:

  • Шаг 6. Создаём класс GameView

  • Шаг 7. Создаём класс SpaceBody

  • Шаг 8. Создаём класс Ship

  • Шаг 9. Создаём класс Asteroid

  • Шаг 10. Добавляем астероиды в GameView

  • Инструкция по разработке 2d игры в Android Studio. Шаг Придумываем идею игры


    Скачать 0.5 Mb.
    НазваниеШаг Придумываем идею игры
    АнкорИнструкция по разработке 2d игры в Android Studio
    Дата13.10.2019
    Размер0.5 Mb.
    Формат файлаdocx
    Имя файлаShooter_Androidapp.docx
    ТипДокументы
    #89940
    страница4 из 4
    1   2   3   4

    Шаг 5. Редактируем MainActivity класс

    В первую очередь в определение класса добавляем implements View.OnTouchListener. Определение класса теперь будет таким:
    public class MainActivity extends AppCompatActivity implements View.OnTouchListener {


    Добавим в класс нужные нам статические переменные (переменные класса):
    public static boolean isLeftPressed = false; // нажата левая кнопка

    public static boolean isRightPressed = false; // нажата правая кнопка


    В процедуру protected void onCreate(Bundle savedInstanceState) {
    добавляем строки:


    GameView gameView = new GameView(this); // создаём gameView
    LinearLayout gameLayout = (LinearLayout) findViewById(R.id.gameLayout); // находим gameLayout

    gameLayout.addView(gameView); // и добавляем в него gameView
    Button leftButton = (Button) findViewById(R.id.leftButton); // находим кнопки

    Button rightButton = (Button) findViewById(R.id.rightButton);
    leftButton.setOnTouchListener(this); // и добавляем этот класс как слушателя (при нажатии сработает onTouch)

    rightButton.setOnTouchListener(this);


    Классы LinearLayout, Button и т.д. подсвечены красным потому что ещё не добавлены в Import.
    Чтобы добавить в Import и убрать красную подсветку нужно для каждого нажать Alt+Enter.
    GameView будет подсвечено красным потому-что этого класса ещё нет. Мы создадим его позже.

    Теперь добавляем процедуру:
    public boolean onTouch(View button, MotionEvent motion) {

    switch(button.getId()) { // определяем какая кнопка

    case R.id.leftButton:

    switch (motion.getAction()) { // определяем нажата или отпущена

    case MotionEvent.ACTION_DOWN:

    isLeftPressed = true;

    break;

    case MotionEvent.ACTION_UP:

    isLeftPressed = false;

    break;

    }

    break;

    case R.id.rightButton:

    switch (motion.getAction()) { // определяем нажата или отпущена

    case MotionEvent.ACTION_DOWN:

    isRightPressed = true;

    break;

    case MotionEvent.ACTION_UP:

    isRightPressed = false;

    break;

    }

    break;

    }

    return true;

    }


    Если кто-то запутался ― вот так в результате должен выглядеть MainActivity класс:
    package com.spaceavoider.spaceavoider;

    import android.support.v7.app.AppCompatActivity;

    import android.os.Bundle;

    import android.view.MotionEvent;

    import android.view.View;

    import android.widget.Button;

    import android.widget.LinearLayout;

    public class MainActivity extends AppCompatActivity implements View.OnTouchListener {

    public static boolean isLeftPressed = false; // нажата левая кнопка

    public static boolean isRightPressed = false; // нажата правая кнопка

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    GameView gameView = new GameView(this); // создаём gameView

    LinearLayout gameLayout = (LinearLayout) findViewById(R.id.gameLayout); // находим gameLayout

    gameLayout.addView(gameView); // и добавляем в него gameView

    Button leftButton = (Button) findViewById(R.id.leftButton); // находим кнопки

    Button rightButton = (Button) findViewById(R.id.rightButton);

    leftButton.setOnTouchListener(this); // и добавляем этот класс как слушателя (при нажатии сработает onTouch)

    rightButton.setOnTouchListener(this);

    }

    public boolean onTouch(View button, MotionEvent motion) {

    switch(button.getId()) { // определяем какая кнопка

    case R.id.leftButton:

    switch (motion.getAction()) { // определяем нажата или отпущена

    case MotionEvent.ACTION_DOWN:

    isLeftPressed = true;

    break;

    case MotionEvent.ACTION_UP:

    isLeftPressed = false;

    break;

    }

    break;

    case R.id.rightButton:

    switch (motion.getAction()) { // определяем нажата или отпущена

    case MotionEvent.ACTION_DOWN:

    isRightPressed = true;

    break;

    case MotionEvent.ACTION_UP:

    isRightPressed = false;

    break;

    }

    break;

    }

    return true;

    }

    }


    Итак, класс MainActivity готов! В нём инициирован ещё не созданный класс GameView. И когда нажата левая кнопка — статическая переменная isLeftPressed = true, а когда правая — isRightPressed = true. Это в общем то и всё что он делает.

    Для начала сделаем чтобы на экране отображался космический корабль, и чтобы он двигался по нажатию управляющих кнопок. Астероиды оставим на потом.

    Шаг 6. Создаём класс GameView

    Теперь наконец-то создадим тот самый недостающий класс GameView. Итак приступим. В определение класса добавим extends SurfaceView implements Runnable. Мобильные устройства имею разные разрешения экрана. Это может быть старенький маленький телефон с разрешением 480x800, или большой планшет 1800x2560. Для того чтобы игра выглядела на всех устройствах одинаково я поделил экран на 20 частей по горизонтали и 28 по вертикали. Полученную единицу измерения я назвал юнит. Можно выбрать и другие числа. Главное чтобы отношение между ними примерно сохранялось, иначе изображение будет вытянутым или сжатым. 
    public static int maxX = 20; // размер по горизонтали

    public static int maxY = 28; // размер по вертикали

    public static float unitW = 0; // пикселей в юните по горизонтали

    public static float unitH = 0; // пикселей в юните по вертикали


    unitW и unitW мы вычислим позже. Также нам понадобятся и другие переменные:
    private boolean firstTime = true;

    private boolean gameRunning = true;

    private Ship ship;

    private Thread gameThread = null;

    private Paint paint;

    private Canvas canvas;

    private SurfaceHolder surfaceHolder;


    Конструктор будет таким:


    public GameView(Context context) {

    super(context);

    //инициализируем обьекты для рисования

    surfaceHolder = getHolder();

    paint = new Paint();
    // инициализируем поток

    gameThread = new Thread(this);

    gameThread.start();

    }


    Метод run() будет содержать бесконечный цикл. В начале цикла выполняется метод update()
    который будет вычислять новые координаты корабля. Потом метод draw() рисует корабль на экране. И в конце метод control() сделает паузу на 17 миллисекунд. Через 17 миллисекунд run() запустится снова. И так до пока переменная gameRunning == true. Вот эти методы:


    @Override

    public void run() {

    while (gameRunning) {

    update();

    draw();

    control();

    }

    }
    private void update() {

    if(!firstTime) {

    ship.update();

    }

    }
    private void draw() {

    if (surfaceHolder.getSurface().isValid()) { //проверяем валидный ли surface
    if(firstTime){ // инициализация при первом запуске

    firstTime = false;

    unitW = surfaceHolder.getSurfaceFrame().width()/maxX; // вычисляем число пикселей в юните

    unitH = surfaceHolder.getSurfaceFrame().height()/maxY;
    ship = new Ship(getContext()); // добавляем корабль

    }
    canvas = surfaceHolder.lockCanvas(); // закрываем canvas

    canvas.drawColor(Color.BLACK); // заполняем фон чёрным
    ship.drow(paint, canvas); // рисуем корабль
    surfaceHolder.unlockCanvasAndPost(canvas); // открываем canvas

    }

    }
    private void control() { // пауза на 17 миллисекунд

    try {

    gameThread.sleep(17);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    }


    Обратите внимание на инициализацию при первом запуске. Там мы вычисляем количество пикселей в юните и добавляем корабль. Корабль мы ещё не создали. Но прежде мы создадим его родительский класс.

    Шаг 7. Создаём класс SpaceBody

    Он будет родительским для класса Ship (космический корабль) и Asteroid (астероид). В нём будут содержаться все переменные и методы общие для этих двух классов. Добавляем переменные:


    protected float x; // координаты

    protected float y;

    protected float size; // размер

    protected float speed; // скорость

    protected int bitmapId; // id картинки

    protected Bitmap bitmap; // картинка


    и методы


    void init(Context context) { // сжимаем картинку до нужных размеров

    Bitmap cBitmap = BitmapFactory.decodeResource(context.getResources(), bitmapId);

    bitmap = Bitmap.createScaledBitmap(

    cBitmap, (int)(size * GameView.unitW), (int)(size * GameView.unitH), false);

    cBitmap.recycle();

    }
    void update(){ // тут будут вычисляться новые координаты

    }
    void drow(Paint paint, Canvas canvas){ // рисуем картинку

    canvas.drawBitmap(bitmap, x*GameView.unitW, y*GameView.unitH, paint);

    }


    Шаг 8. Создаём класс Ship

    Теперь создадим класс Ship (космический корабль). Он наследует класс SpaceBody поэтому в определение класа добавим extends SpaceBody.

    Напишем конструктор:
    public Ship(Context context) {

    bitmapId = R.drawable.ship; // определяем начальные параметры

    size = 5;

    x=7;

    y=GameView.maxY - size - 1;

    speed = (float) 0.2;
    init(context); // инициализируем корабль

    }


    и переопределим метод update()
    @Override

    public void update() { // перемещаем корабль в зависимости от нажатой кнопки

    if(MainActivity.isLeftPressed && x >= 0){

    x -= speed;

    }

    if(MainActivity.isRightPressed && x <= GameView.maxX - 5){

    x += speed;

    }

    }


    На этом космический корабль готов! Всё компилируем и запускаем. На экране должен появиться космический корабль. При нажатии на кнопки он должен двигаться вправо и влево. Теперь добавляем сыплющиеся сверху астероиды. При столкновении с кораблём игра заканчивается.

    Шаг 9. Создаём класс Asteroid

    Добавим класс Asteroid (астероид). Он тоже наследует класс SpaceBody поэтому в определение класса добавим extends SpaceBody.

    Добавим нужные нам переменные:
    private int radius = 2; // радиус

    private float minSpeed = (float) 0.1; // минимальная скорость

    private float maxSpeed = (float) 0.5; // максимальная скорость


    Астероид должен появляться в случайной точке вверху экрана и лететь вниз с случайной скоростью. Для этого x и speed задаются при помощи генератора случайных чисел в его конструкторе.
    public Asteroid(Context context) {

    Random random = new Random();
    bitmapId = R.drawable.asteroid;

    y=0;

    x = random.nextInt(GameView.maxX) - radius;

    size = radius*2;

    speed = minSpeed + (maxSpeed - minSpeed) * random.nextFloat();
    init(context);

    }


    Астероид должен двигаться с определённой скорость вертикально вниз. Поэтому в методе update() прибавляем к координате x скорость.
    @Override

    public void update() {

    y += speed;

    }


    Так же нам нужен будет метод определяющий столкнулся ли астероид с кораблём.
    public boolean isCollision(float shipX, float shipY, float shipSize) {

    return !(((x+size) < shipX)||(x > (shipX+shipSize))||((y+size) < shipY)||(y > (shipY+shipSize)));

    }


    Рассмотрим его поподробнее. Для простоты считаем корабль и астероид квадратами. Тут я пошёл от противного. То есть определяю когда квадраты НЕ пересекаются.

    ((x+size) < shipX) — корабль слева от астероида.
    (x > (shipX+shipSize)) — корабль справа от астероида.
    ((y+size) < shipY) — корабль сверху астероида.
    (y > (shipY+shipSize)) — корабль снизу астероида.

    Между этими четырьмя выражениями стоит || (или). То есть если хоть одно выражение правдиво (а это значит что квадраты НЕ пересекаются) — результирующие тоже правдиво.

    Всё это выражение я инвертирую знаком!. В результате метод возвращает true когда квадраты пересекаются. Что нам и надо.

    Про определение пересечения более сложных фигур можно почитать тут

    Шаг 10. Добавляем астероиды в GameView

    В GameView добавляем переменные:
    private ArrayList asteroids = new ArrayList<>(); // тут будут харанится астероиды

    private final int ASTEROID_INTERVAL = 50; // время через которое появляются астероиды (в итерациях)

    private int currentTime = 0;


    также добавляем 2 метода:


    private void checkCollision(){ // перебираем все астероиды и проверяем не касается ли один из них корабля

    for (Asteroid asteroid : asteroids) {

    if(asteroid.isCollision(ship.x, ship.y, ship.size)){

    // игрок проиграл

    gameRunning = false; // останавливаем игру

    // TODO добавить анимацию взрыва

    }

    }

    }
    private void checkIfNewAsteroid(){ // каждые 50 итераций добавляем новый астероид

    if(currentTime >= ASTEROID_INTERVAL){

    Asteroid asteroid = new Asteroid(getContext());

    asteroids.add(asteroid);

    currentTime = 0;

    }else{

    currentTime ++;

    }

    }


    И в методе run() добавляем вызовы этих методов перед вызовоом control().
    @Override

    public void run() {

    while (gameRunning) {

    update();

    draw();

    checkCollision();

    checkIfNewAsteroid();

    control();

    }

    }


    Далее в методе update() добавляем цикл который перебирает все астероиды и вызывает у них метод update().
    private void update() {

    if(!firstTime) {

    ship.update();

    for (Asteroid asteroid : asteroids) {

    asteroid.update();

    }

    }

    }


    Такой же цикл добавляем и в метод draw().
    private void draw() {

    if (surfaceHolder.getSurface().isValid()) { //проверяем валидный ли surface
    if(firstTime){ // инициализация при первом запуске

    firstTime = false;

    unitW = surfaceHolder.getSurfaceFrame().width()/maxX; // вычисляем число пикселей в юните

    unitH = surfaceHolder.getSurfaceFrame().height()/maxY;
    ship = new Ship(getContext()); // добавляем корабль

    }
    canvas = surfaceHolder.lockCanvas(); // закрываем canvas

    canvas.drawColor(Color.BLACK); // заполняем фон чёрным
    ship.drow(paint, canvas); // рисуем корабль
    for(Asteroid asteroid: asteroids){ // рисуем астероиды

    asteroid.drow(paint, canvas);

    }
    surfaceHolder.unlockCanvasAndPost(canvas); // открываем canvas

    }

    }


    Вот и всё! Простейшая 2D игра готова. Компилируем, запускаем и смотрим что получилось!
    Если кто-то запутался или что-то не работает можно скачать исходник.

    Игра, конечно, примитивна. Но её можно усовершенствовать, добавив новые функции. В первую очередь следует реализовать удаление вылетевших за пределы экрана астероидов. Можно сделать чтобы корабль мог стрелять в астероиды, чтобы игра постепенно ускорялась, добавить таймер, 
    1   2   3   4


    написать администратору сайта