Программирование игр под Android для чайников

в 16:00, , рубрики: android, game development, Разработка под android, метки: ,

Эта маленькая статья для новичков в мире Android, которые решили создать свою первую игру, но столкнулись с определенными трудностями или не знают с чего начать. Это не урок как создать игру от А до Я. Я просто рассмотрю разные аспекты создания игры в форме «вопрос — ответ».

image

Содержание

1. Первые шаги
2. Создание игрового цикла
3. Как растянуть карту на весь экран
4. Проверка на столкновение или Hit Testing
5. Внедряем оплату PayPal

Первые шаги

Вы создали свой новый проект и первое окно (Activity) в игре. Наверняка тут располагаются кнопки меню (начать игру, настройки и т.д.).
Пример:

public class FirstPage extends Activity implements OnClickListener {
	private Button new_game_btn;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
                // Убираем верхнюю панель с названием приложения
                requestWindowFeature(Window.FEATURE_NO_TITLE);
                // Переходим в режим FULLSCREEN
                getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
                // Экран будет всегда активен
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                setContentView(R.layout.main);
                // Инициализируем кнопку для запуска игры
                new_game_btn = (Button) findViewById(R.id.new_game_btn);
	}  
 
        // Обработчик события нажатия на кнопку
	public void onClick(View v) {
		if (v.getId() == new_game_btn.getId()) {
			Intent intent = new Intent(FirstPage.this, Main.class);
			startActivity(intent);
		}
	}  
}

Создадим новый класс, который и будет сердцем нашей игры. Назовем его GameView.

public class GameView extends SurfaceView {
       private Bitmap bmp;
       private SurfaceHolder holder;
 
       public GameView(Context context) {
             super(context);
             holder = getHolder();
             holder.addCallback(new SurfaceHolder.Callback() {
 
                    @Override
                    public void surfaceDestroyed(SurfaceHolder holder) {
                    }
 
                    @Override
                    public void surfaceCreated(SurfaceHolder holder) {
                           Canvas c = holder.lockCanvas(null);
                           onDraw(c);
                           holder.unlockCanvasAndPost(c);
                    }
 
                    @Override
                    public void surfaceChanged(SurfaceHolder holder, int format,
                                  int width, int height) {
                    }
             });
             bmp = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
       }
 
       @Override
       protected void onDraw(Canvas canvas) {
             canvas.drawColor(Color.BLACK);
             canvas.drawBitmap(bmp, 10, 10, null);
       }
}

Если Вы не забыли, то когда пользователь нажимает на кнопку «Начать игру», у нас выполняется этот код:

Intent intent = new Intent(FirstPage.this, Main.class);
startActivity(intent);

Создаем новый класс:

public class Main extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         // Не забываем убрать Title и перейти в Fullscreen
        setContentView(new GameView(this));
    }
}

Каждый раз, когда создаем Activity, не забывает прописать ее в AndroidManifest.xml.
Пример:

<activity android:name=".Main" android:noHistory="true" android:label="@string/app_name" android:screenOrientation="landscape" android:configChanges="orientation|keyboardHidden"  ></activity>

Если Вы хотите изменить Activity которая запускается по умолчанию при старте приложения, это делается так:

      <activity 
            android:name=".pages.FirstPage"
            android:label="@string/app_name" android:screenOrientation="landscape" android:configChanges="orientation|keyboardHidden"  >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

Если Вы все сделали правильно, то запустив приложение, увидим примерно такую картину:

image

Создание игрового цикла

Теперь нам нужно создать игровой цикл или так называемый game loop. Его нужно запускать в отдельном потоке. Создадим новый класс:

public class GameLoopThread extends Thread {
       private GameView view;
       private boolean running = false;
      
       public GameLoopThread(GameView view) {
             this.view = view;
       }
 
       public void setRunning(boolean run) {
             running = run;
       }
 
       @Override
       public void run() {
             while (running) {
                    Canvas c = null;
                    try {
                           c = view.getHolder().lockCanvas();
                           synchronized (view.getHolder()) {
                                  view.onDraw(c);
                           }
                    } finally {
                           if (c != null) {
                                  view.getHolder().unlockCanvasAndPost(c);
                           }
                    }
             }
       }
}  

Далее нужно запустить наш поток из SurfaceView.

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
       boolean retry = true;
       gameLoopThread.setRunning(false);
       while (retry) {
              try {
                    gameLoopThread.join();
                    retry = false;
              } catch (InterruptedException e) {
              }
       }
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
       gameLoopThread.setRunning(true);
       gameLoopThread.start();
}

Теперь наш отдельный поток будет вызывать метод onDraw, в котором мы можем создавать анимацию. Но скорость анимации будет не постоянной. Чтобы это исправить, нам нужно определить скорость игры, задав число FPS, «frames per second». Сколько фреймов в секунду мы хотим показывать. Чтобы установить скорость потока 10 FPS, внесем изменения в GameLoopThread.

 static final long FPS = 10;

 @Override
       public void run() {
             long ticksPS = 1000 / FPS;
             long startTime;
             long sleepTime;
             while (running) {
                    Canvas c = null;
                    startTime = System.currentTimeMillis();
                    try {
                           c = view.getHolder().lockCanvas();
                           synchronized (view.getHolder()) {
                                  view.onDraw(c);
                           }
                    } finally {
                           if (c != null) {
                                  view.getHolder().unlockCanvasAndPost(c);
                           }
                    }
                    sleepTime = ticksPS-(System.currentTimeMillis() - startTime);
                    try {
                           if (sleepTime > 0)
                                  sleep(sleepTime);
                           else
                                  sleep(10);
                    } catch (Exception e) {}
             }
       }

Как растянуть карту на весь экран

Допустим у Вас есть карта (изображение) и Вы хотите, чтобы она занимала всю площадь экрана, не зависимо от размеров устройства.
Добавим новые переменные в GameView.

public static Bitmap _map; // game map
public static double _ratioWidth; // ratio for scale map
public static double _ratioHeight; // ratio for scale map
public static float _scale;

Инициализируем карту:

	public void initMap() {
		if (_map == null) {
			_scale = getResources().getDisplayMetrics().density;
			_map = BitmapFactory.decodeResource(getResources(), R.drawable.map_1);
			_ratioWidth = (double)_map.getWidth() / (double)this.getWidth();
			_ratioHeight = (double)_map.getHeight() / (double)this.getHeight();
		}
       }

Создадим дополнительный класс для изменения размеров изображения:

public class MapHelper {
	public static Bitmap RescaleBitmap(Bitmap bmp) {
		Bitmap rescaled = Bitmap.createScaledBitmap(bmp, (int)(bmp.getWidth() / GameView._ratioWidth), (int)(bmp.getHeight() /  GameView._ratioHeight), true);
		return rescaled;
	}
}

Остается только вывести нашу карту на экран, для этого добавляем код в метод onDraw класса GameView:

initMap(); // initialize map 
// Clear canvas
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
canvas.drawPaint(paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC));
// Scale map to fullscreen size and draw it
Bitmap rescaledMap = MapHelper.RescaleBitmap(_map);
canvas.drawBitmap(rescaledMap, 0, 0, null);

Проверка на столкновение или Hit Testing

Реализуем простую проверку на столкновение. Представим, что у Вас есть некий объект на экране и вы хотите проверить, нажал ли на него игрок пальцем.
Сделать это очень просто:

@Override
public boolean onTouchEvent(MotionEvent event) {
     float x = event.getX();
     float y = event.getY();
     if (x >= xOfYourBitmap && x < (xOfYourBitmap + yourBitmap.getWidth())  
     && y >= yOfYourBitmap && y < (yOfYourBitmap + yourBitmap.getHeight())) {
            // Игрок кликнул на объект
     }
     return super.onTouchEvent(event);
}

Внедряем оплату PayPal

Скачиваем PayPal SDK и подключаем к нашему проекту библиотеку PayPal_MPL.jar.
Добавляем следующие строчки в AndroidManifest.xml:

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <activity android:name="com.paypal.android.MEP.PayPalActivity"
    android:theme="@android:style/Theme.Translucent.NoTitleBar"  
    android:configChanges="keyboardHidden|orientation"/>

В Activity из которой мы хотим производить оплату, добавляем две переменные:

private static final String PAYPAL_APP_ID = "APP-59X48022DA021071J";
PayPal mPayPal;

PAYPAL_APP_ID можно получить в аккаунте разработчика после регистрации на X.com.
В метод onCreate добавляем следующий код:

mPayPal = PayPal.initWithAppID(Advertisment.this.getBaseContext(), PAYPAL_APP_ID, PayPal.ENV_LIVE);
mPayPal.setLanguage("en_US"); 
CheckoutButton payButton = mPayPal.getCheckoutButton(this, PayPal.BUTTON_194x37,  PayPal.PAYMENT_TYPE_GOODS);
    
    payButton.setOnClickListener(new OnClickListener() {
		public void onClick(View v) {
			PayPalPayment newPayment = new PayPalPayment();

			 newPayment.setSubtotal(BigDecimal.valueOf(0.99));
                         newPayment.setCurrencyType("USD");
                         newPayment.setRecipient("myemail@hotmail.com");
                         newPayment.setMerchantName("My app name");
    
                         Intent checkoutIntent = PayPal.getInstance().checkout(newPayment, Advertisment.this);
                         startActivityForResult(checkoutIntent, 1);
 		}
    }); 

И создаем последний метод для обработки результата оплаты:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch(resultCode) {
        case Activity.RESULT_OK:
            // Оплата прошла успешно
            break;
        case Activity.RESULT_CANCELED:
            // Пользователь отказался оплачивать
            break;
        case PayPalActivity.RESULT_FAILURE:
            // Ошибка при оплате
            break;
    }
}

На этом позвольте завершить мой маленький доклад. Я не Android Guru, но надеюсь, что моя статья поможет кому-то в разработке приложения или уменьшит время «гуугления».

Автор: MMW


  1. maks:

    Спасибо дельная статья

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js