ゲームサイクル

どのタイプのゲームを作成するかは非常に重要です。 ゲームが動的な場合、デバイスの画像を常に更新する必要があります。 つまり、最初にオブジェクトの新しい座標を適用して作成し、それを画面に表示する必要があります。 私たちのビジョンは、0.04秒以内にそれを行うと、オブジェクトの動きが連続的に見えるように設計されています。 ただし、オブジェクトは描画の複雑さが異なる場合があり、プレイするデバイスの速度は異なる場合があります。



一部のタブレットや携帯電話では、アプリケーションが「飛ぶ」ため、ユーザーはプレイする時間すらありませんが、他のデバイスでは速度が低下し、ユーザーがデバイスから削除する可能性が高くなります。 すべてのデバイスで0.04秒(1秒あたり25フレーム(サイクル))で1ゲームサイクルを実行するというアイデアがあります。 すべてのデバイスで実行できれば、すべてうまくいきます。 ゲーム内に相互作用する10個の動的オブジェクトがあり、衝突の爆発などの新しいオブジェクトを作成するとします。 また、音を鳴らすことを忘れずに、ゲームへのユーザーの参加に対応してください。 私たちの周りの世界の現実的なグラフィックは言うまでもありません。



一部のシーンでゲームサイクルを作成する時間がデバイスにない場合はどうすればよいですか? 解決策はいくつかありますが、そのうちの1つを以下で検討します。



ゲームループを作成する



主なアイデアは、フレームを描画するためのリアルタイムがフレームを更新するための推定時間よりも長い場合(つまり、システムに時間がない場合)、このフレームを犠牲にして次のフレームを描画するための時間を増やすということです。 システムがすぐにフレームを描画することに成功した場合、可能な限り迅速にそれを行いますが、フローはリードタイムの​​間中断され、フレーム数、つまりプログラム実行速度が設定され、デバイス自体の速度に依存しません。



このアプローチにより、デバイスのエネルギー消費を削減し、異なるデバイスでほぼ同じゲームをプレイできます。 毎秒25フレームのリフレッシュレートで、Android 2.2.1を使用して携帯電話上のオブジェクトが不快にぴくぴく動くことに気付きました(特に高速で移動する場合)が、毎秒30フレームではすべてがうまくいきました。 Android 4.2.2を搭載したタブレットでは毎秒50フレームで、すべてが完全に機能しましたが、電話では、特にオブジェクトの高速で、顕著なフレームドロップが発生することがありました。



コードをさらに詳しく考えてみましょう。



Threadクラスから継承するMainThreadクラスを作成します。 将来的には、GameViewゲームクラスで呼び出します。



public class MainThread extends Thread {
      
      





 public class MainThread extends Thread {
      
      





1秒あたりのフレーム数(MAX_FPS)を30に設定します。



  private final static int MAX_FPS = 30; // desired fps
      
      





ディスプレイで犠牲にする(スキップする)ことのできる1秒あたりの最大フレーム数を4とします。この数は実際に私が実験的に見つけたもので、これらの数で試してみてください。



  // maximum number of frames to be skipped
      
      





  private final static int MAX_FRAME_SKIPS = 4;
      
      





表示フレームの期間をミリ秒で計算します。



  private final static int FRAME_PERIOD = 1000 / MAX_FPS; // the frame period
      
      





オブジェクトが描画されるサーフェスを作成するメソッドを宣言します。



  private SurfaceHolder surfaceHolder; // Surface holder that can access the physical surface
      
      





オブジェクトを描画し、その座標を更新するクラスを宣言します。



  private GameView gameView;// The actual view that handles inputs and draws to the surface
      
      





ゲーム状態変数を宣言し、trueの場合、ストリームが再生されます。



  private boolean running; // flag to hold game state public void setRunning(boolean running) { this.running = running; }
      
      





クラスコンストラクターを作成します。



 public MainThread(SurfaceHolder surfaceHolder, GameView gameView) { super(); this.surfaceHolder = surfaceHolder; this.gameView = gameView; } @ Override public void run() { Canvas canvas;</code> long beginTime; // the time when the cycle begun long timeDiff; // the time it took for the cycle to execute int sleepTime;// ms to sleep (<0 if we're behind) int framesSkipped;</font>// number of frames being skipped sleepTime = 0; while (running) { canvas = null; // try locking the canvas for exclusive pixel editing in the surface try { canvas = this.surfaceHolder.lockCanvas(); synchronized (surfaceHolder) { beginTime = System.currentTimeMillis(); //Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC. framesSkipped = 0; // resetting the frames skipped // update game state this.gameView.update(); // render state to the screen draws the canvas on the panel this.gameView.render(canvas); timeDiff = System.currentTimeMillis() - beginTime;</font> // calculate how long did the cycle take // calculate sleep time sleepTime = (int)(FRAME_PERIOD - timeDiff); if (sleepTime > 0) {</font> // if sleepTime > 0 we're OK try {</font> // send the thread to sleep for a short period // very useful for battery saving Thread.sleep(sleepTime); } catch (InterruptedException e) {} } while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) { // we need to catch up this.gameView.update();</font> // update without rendering sleepTime += FRAME_PERIOD; // add frame period to check if in next frame framesSkipped++; } } } finally { // in case of an exception the surface is not left in // an inconsistent state if (canvas != null) { surfaceHolder.unlockCanvasAndPost(canvas); } } // end finally } } }
      
      





それでは、GameViewクラスの機能を詳しく見ていきましょう。



 public class GameView extends SurfaceView implements SurfaceHolder.Callback { private final Drawable mAsteroid; private int widthAsteroid; private int heightAsteroid; private int leftAsteroid; private int xAsteroid1 = 30; private int rightAsteroid; private int topAsteroid; private int yAsteroid = -30; private int bottomAsteroid; private int centerAsteroid; private int height; private int width; private int speedAsteroid = 5; private int xAsteroid; private MainThread thread; public GameView(Context context) { super(context); // adding the callback (this) to the surface holder to intercept events getHolder().addCallback(this); // create mAsteroid where adress of picture asteroid is located mAsteroid = context.getResources().getDrawable(R.drawable.asteroid); // create the game loop thread thread = new MainThread(getHolder(), this); }
      
      





このメソッドを使用すると、たとえば画面を回転させるときに描画面を変更できます。



 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) { }
      
      





ペイント用の表面を作成するメソッド。



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





このメソッドは、フローが停止した場合に描画されるサーフェスを削除します。



 @Override public void surfaceDestroyed(SurfaceHolder holder) { thread.setRunning(false); boolean retry = true; while (retry) { try { thread.join(); retry = false; } catch (InterruptedException e) { // try again shutting down the thread } } }
      
      





このメソッドでは、画面の表面にオブジェクトを描画します。



 public void render(Canvas canvas) {
      
      





濃い青色の背景を作成します。



 canvas.drawColor(Color.argb(255, 2, 19, 151));
      
      





後で画面サイズに応じてオブジェクトのサイズを拡大縮小するために、デバイスの画面の高さと幅を調べます。



 height = canvas.getHeight(); width = canvas.getWidth();
      
      





小惑星の主要な寸法(写真の幅と高さ)を設定します。 左端をXに沿った座標にアタッチし、画像の上部をYに沿った座標にアタッチします。したがって、画像の左上隅の座標を決定しました。 これらの座標を変更すると、画像が動き始めます。 png形式の小惑星の描画を描画可能フォルダに入れることを忘れないでください。



 //Work with asteroid widthAsteroid = 2 * width / 13;//set width asteroid heightAsteroid = widthAsteroid; leftAsteroid = xAsteroid; //the left edge of asteroid rightAsteroid = leftAsteroid + widthAsteroid;//set right edge of asteroid topAsteroid = yAsteroid; bottomAsteroid = topAsteroid + heightAsteroid; centerAsteroid = leftAsteroid + widthAsteroid / 2; mAsteroid.setBounds(leftAsteroid, topAsteroid, rightAsteroid, bottomAsteroid); mAsteroid.draw(canvas); }
      
      





画像の座標を更新し、上から下に強制的に移動します。 画像の左上隅のY座標が画面の高さよりも大きくなった場合、Yをゼロにすると、小惑星が画面の上部にジャンプします。 小惑星を飛行させるために、座標にspeedAsteroidの数値を追加します。これは、5から15までランダムに決定されます(speedAsteroid = 5+ rnd.nextInt(10);)。



 public void update() { if (yAsteroid > height) { yAsteroid = 0; // find by random function Asteroid & speed Asteroid Random rnd = new Random(); xAsteroid = rnd.nextInt(width - widthAsteroid); speedAsteroid = 5+ rnd.nextInt(10); } else { yAsteroid +=speedAsteroid; } } }
      
      





小惑星が毎回同じ場所から飛び出さないように、X座標は画面幅から小惑星の幅を引いた範囲内でランダムな法則に従って決定されます。 アプリケーションの起動時に、変数private int xAsteroid = 30;を宣言したときに指定した初期座標で小惑星がクラッシュします。 private int yAsteroid = -30;。 将来、飛行速度と初期座標はランダムに変化します。



現在のアプリケーションファイル



AhdroidManifest.xml



 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.adc2017gmail.moonbase" > <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" android:screenOrientation="portrait" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SecondActivity" android:label="@string/title_activity_second" android:screenOrientation="portrait" > </activity> </application> </manifest>
      
      





Gameview.java



 package com.adc2017gmail.moonbase; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.util.Random; public class GameView extends SurfaceView implements SurfaceHolder.Callback { private final Drawable mAsteroid; private int widthAsteroid; private int heightAsteroid; private int leftAsteroid; private int rightAsteroid; private int topAsteroid; private int yAsteroid = -30; private int bottomAsteroid; private int centerAsteroid; private int height; private int width; private int speedAsteroid = 5; private int xAsteroid = 30; private MainThread thread; public GameView(Context context) { super(context); // adding the callback (this) to the surface holder to intercept events getHolder().addCallback(this); // create mAsteroid where adress picture asteroid mAsteroid = context.getResources().getDrawable(R.drawable.asteroid); // create the game loop thread thread = new MainThread(getHolder(), this); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { thread.setRunning(true); thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { thread.setRunning(false); boolean retry = true; while (retry) { try { thread.join(); retry = false; } catch (InterruptedException e) { // try again shutting down the thread } } } public void render(Canvas canvas) { canvas.drawColor(Color.argb(255, 2, 19, 151)); height = canvas.getHeight(); width = canvas.getWidth(); //Work with asteroid widthAsteroid = 2 * width / 13;//set width asteroid heightAsteroid = widthAsteroid; leftAsteroid = xAsteroid;//the left edge of asteroid rightAsteroid = leftAsteroid + widthAsteroid;//set right edge of asteroid topAsteroid = yAsteroid; bottomAsteroid = topAsteroid + heightAsteroid; centerAsteroid = leftAsteroid + widthAsteroid / 2; mAsteroid.setBounds(leftAsteroid, topAsteroid, rightAsteroid, bottomAsteroid); mAsteroid.draw(canvas); } public void update() { if (yAsteroid > height) { yAsteroid = 0; // find by random function Asteroid & speed Asteroid Random rnd = new Random(); xAsteroid = rnd.nextInt(width - widthAsteroid); speedAsteroid = 5+ rnd.nextInt(10); } else { yAsteroid +=speedAsteroid; } } }
      
      





MainThread.java



 package com.adc2017gmail.moonbase; import android.graphics.Canvas; import android.view.SurfaceHolder; public class MainThread extends Thread { private final static int MAX_FPS = 30;// desired fps private final static int MAX_FRAME_SKIPS = 4;// maximum number of frames to be skipped private final static int FRAME_PERIOD = 1000 / MAX_FPS; // the frame period // Surface holder that can access the physical surface private SurfaceHolder surfaceHolder; // The actual view that handles inputs // and draws to the surface private GameView gameView; // flag to hold game state private boolean running; public void setRunning(boolean running) { this.running = running; } public MainThread(SurfaceHolder surfaceHolder, GameView gameView) { super(); this.surfaceHolder = surfaceHolder; this.gameView = gameView; } @Override public void run() { Canvas canvas; long beginTime;// the time when the cycle begun long timeDiff; // the time it took for the cycle to execute int sleepTime;// ms to sleep (<0 if we're behind) int framesSkipped;// number of frames being skipped sleepTime = 0; while (running) { canvas = null; // try locking the canvas for exclusive pixel editing in the surface try { canvas = this.surfaceHolder.lockCanvas(); synchronized (surfaceHolder) { beginTime = System.currentTimeMillis();//Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC. framesSkipped = 0; // resetting the frames skipped // update game state this.gameView.update(); // render state to the screen draws the canvas on the panel this.gameView.render(canvas); // calculate how long did the cycle take timeDiff = System.currentTimeMillis() - beginTime; // calculate sleep time sleepTime = (int)(FRAME_PERIOD - timeDiff); if (sleepTime > 0) { if sleepTime > 0 //we're OK try { // send the thread to sleep for a short period // very useful for battery saving Thread.sleep(sleepTime); } catch (InterruptedException e) {} } while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) { // we need to catch up this.gameView.update(); // update without rendering sleepTime += FRAME_PERIOD; // add frame period to check if in next frame framesSkipped++; } } } finally { // in case of an exception the surface is not left in // an inconsistent state if (canvas != null) { surfaceHolder.unlockCanvasAndPost(canvas); } } // end finally } } }
      
      





MainActivity.java



 package com.adc2017gmail.moonbase; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.MenuItem; import android.view.View; import android.widget.ImageButton; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ImageButton imgbtn8 = (ImageButton)findViewById(R.id.image_button8); imgbtn8.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { imgbtn8.setImageResource(R.drawable.btn2); Intent intent8 = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent8); } }); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
      
      





SecondActivity.java



 package com.adc2017gmail.moonbase; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class SecondActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new GameView(this)); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_second, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
      
      






All Articles