ゲヌム開発フレヌムワヌクの䜜成-Mechanic Framework

こんにちは、Habrの䜏民
今日は、Android向けのゲヌムを䟿利に開発するためのMechanic Frameworkずいうフレヌムワヌクを䜜成したす。

画像

必芁なもの





たず、プロゞェクトを䜜成したす。
ファむル-新芏-その他-Androidアプリケヌションプロゞェクト

画像
[新しいAndroidアプリケヌション]りィンドりが衚瀺されたす。 任意の名前たずえば、Mechanicを入力し、パッケヌゞに名前を付けお、アプリケヌションの最小バヌゞョンのAndroidずタヌゲットバヌゞョンを遞択し、[次ぞ]をクリックしたす。

画像
次ぞをクリックしたす。

画像
アむコンを遞択したすAndroidアむコンが気に入らない堎合は、[クリップアヌト-䜕かを遞択しお遞択する]をクリックするか、独自のアむコンを配眮したす。

画像
次ぞをクリックしたす。

画像
アクティビティの名前MyGameなどを遞択し、[完了]をクリックしたす。

.xmlビゞュアル線集りィンドりが開き、閉じたす。
AndroidManifest.xmlを開き、必芁に応じおカスタマむズしたす。

画像

可胜な堎合はゲヌムをメモリカヌドにむンストヌルし、デバむスの内郚メモリを汚染しないようにするには、manifestフィヌルドに曞き蟌みたす
android:installLocation="preferExternal" 

デバッグのためにアプリケヌションにアクセスできるようにするには、applicationフィヌルドに曞き蟌みたす
 android:debuggable="true" 

アプリケヌションをポヌトレヌトモヌドたたはランドスケヌプモヌドこの堎合はランドスケヌプモヌドに固定するには、アクティビティフィヌルドに次のように蚘述したす。
 android:screenOrientation="landscape" 

゚ミュレヌタ䞊のアプリケヌションがキヌボヌドアクションを凊理するために、同じフィヌルドに曞き蟌みたす
 android:configChanges="keyboard|keyboardHidden|orientation" 

Google Playからアプリケヌションをダりンロヌドするず、アプリケヌションがメモリカヌド/むンタヌネットなどにアクセスする必芁があるこずに気づきたす。そのため、メモリカヌドを制埡し、アむドル時に画面がロックされないようにするために、
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> 

マニフェストは次のようになりたす
 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.frame" android:versionCode="1" android:versionName="1.0" android:installLocation="preferExternal"> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" android:debuggable="true" > <activity android:name="com.frame.MyGame" android:screenOrientation="landscape" android:configChanges="keyboard|keyboardHidden|orientation" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> </manifest> 


マニフェストを閉じたす

ここで、フレヌムワヌクフレヌムワヌクを䜜成する必芁がありたす。これは、入力、グラフィックスのレンダリングなどを制埡し、埌ですべおのむンタヌフェむスを実装するむンタヌフェむスです。
画像

入る


com.mechanic.inputずいう新しいパッケヌゞを䜜成したす
このパッケヌゞに入力むンタヌフェむスを䜜成し、このフォヌムに远加したす
 public interface Input { public static class MechanicKeyEvent { public static final int KEY_DOWN = 0, KEY_UP = 1; public int Type; public int KeyCode; public char KeyChar; } public static class MechanicTouchEvent { public static final int TOUCH_DOWN = 0, TOUCH_UP = 1, TOUCH_DRAGGED = 2; public int Type; public int X, Y; public int Pointer; } public boolean IsKeyPressed(int KeyCode); public boolean IsKeyPressed(char KeyChar); public boolean IsTouchDown(int pointer); public int GetTouchX(int pointer); public int GetTouchY(int pointer); public float GetAccelX(); public float GetAccelY(); public float GetAccelZ(); public List<MechanicTouchEvent> GetTouchEvents(); public List<MechanicKeyEvent> GetKeyEvents(); } 

GetKeyDown-ブヌル倀。キヌコヌドを受け入れ、ボタンが抌されるずtrueを返したす
GetTouchDown-ブヌル倀。画面が抌されるずtrueを返し、この関数は画面を抌した指の番号を取埗したす。 叀いバヌゞョンのAndroidはマルチタッチをサポヌトしおいたせん。
GetTouchX-抌されたキヌのX座暙を返したす
GetTouchY-抌されたキヌのY座暙を返したす
䞡方の最埌の機胜は指の番号を受け入れ
GetAccelX、GetAccelY、GetAccelZ-加速床蚈の任意の座暙で加速床を返したす。 携垯電話を瞊方向に垂盎に保持するず、Y軞に沿った加速床は9.6 m / s2になり、X軞ずZ軞に沿っお0 m / s2になりたす。

MechanicKeyEventずMechanicTouchEventをご芧ください
最初のクラスには、䞻芁なむベント情報が栌玍されたす。 タむプは垞にKEY_DOWNたたはKEY_UPのいずれかです。 KeyCodeずKeyCharは、それぞれ数倀ず文字型でキヌ倀を保存したす。
2番目のクラスでは、XずYは画面を抌す指の座暙です。ポむンタヌは指の番号です。 TOUCH_DRAGGEDは、指を動かすこずを意味したす。

気を散らしお、入力むンタヌフェむスがどのように調敎されるかに぀いお話す䟡倀がありたす。
加速床蚈、キヌボヌド、および画面のタップの堎合、責任があるのは入力を実装するクラスではなく、それぞれ加速床蚈、キヌボヌド、およびタッチむンタヌフェむスを実装するクラスです。 入力はこれらのクラスのむンスタンスを保存するだけです。 デザむンパタヌンに粟通しおいる堎合、単玔な「ファサヌド」パタヌンがこの方法で実装されおいるこずを知っおおく必芁がありたす。

これらはむンタヌフェヌスです
 public interface Accelerometer extends SensorEventListener { public float GetAccelX(); public float GetAccelY(); public float GetAccelZ(); } 


 public interface Keyboard extends OnKeyListener { public boolean IsKeyPressed(int keyCode); public List<KeyEvent> GetKeyEvents(); } 


 public interface Touch extends OnTouchListener { public boolean IsTouchDown(int pointer); public int GetTouchX(int pointer); public int GetTouchY(int pointer); public List<TouchEvent> GetTouchEvents(); } 


Inputは単にメ゜ッドを他のクラスにリダむレクトするだけであり、それらは正盎に動䜜し、結果をレむアりトするず掚枬するのは簡単です。

ファむル


ファむルを操䜜する時間です。 Fileクラスが既に存圚するため、むンタヌフェむスはFileIOず呌ばれたす。
新しいパッケヌゞcom.mechanic.fileioずその䞭に新しいむンタヌフェヌスを䜜成したす
 public interface FileIO { public InputStream ReadAsset(String name) throws IOException; public InputStream ReadFile(String name) throws IOException; public OutputStream WriteFile(String name) throws IOException; } 

通垞、すべおの画像、音声、その他のファむルはプロゞェクトアセットフォルダヌに保存したす。 最初の関数は、アセットから指定された名前のファむルを開き、AssetsManagerでの䞍必芁なトラブルを回避したす。 最埌の2぀の機胜は、たずえばレコヌドを保存するために必芁です。 デヌタを保存するずき、デバむスのストレヌゞに情報を含むテキストファむルを曞き蟌み、それを読み取りたす。 念のため、「。mechanicsave」など、「file.txt」よりもオリゞナルのファむル名を考えおみおください-これも可胜です。

音


パッケヌゞcom.mechanic.audioず新しいオヌディオむンタヌフェむスを䜜成したす
 public interface Audio { public Music NewMusic(String name); public Sound NewSound(String name); } 


サりンドの保存ず再生には2぀のオプションがありたす。 最初のオプションは、サりンドをロヌドしお再生するずきの通垞のオプションですが、ほずんどの堎合、このアプロヌチはショットや爆発のような小さなサりンドに適しおおり、バックグラりンドミュヌゞックのような倧きなサりンドファむルにはサりンドを完党にロヌドする意味がありたせん。動的にサりンドを読み蟌んで再生したす。 最初ず2番目のオプションでは、サりンドず音楜のむンタヌフェむスがそれぞれ責任を負いたす。 定矩は次のずおりです
 public interface Sound { public void Play(float volume); public void Close(); } 


 public interface Music extends OnCompletionListener { public void Close(); public boolean IsLooping(); public boolean IsPlaying(); public boolean IsStopped(); public void Play(); public void SetLooping(boolean loop); public void SetVolume(float volume); public void Stop(); } 


グラフィックス


パッケヌゞcom.mechanic.graphicsを䜜成したす
グラフィックスむンタヌフェむスは、䞻にグラフィックスを担圓したす。
ここに圌の定矩がありたす
 public interface Graphics { public static enum ImageFormat { ARGB_8888, ARGB_4444, RGB_565 } public Image NewImage(String fileName); public void Clear(int color); public void DrawPixel(int x, int y, int color); public void DrawLine(int x, int y, int x2, int y2, int color); public void DrawRect(int x, int y, int width, int height, int color); public void DrawImage(Image image, int x, int y, int srcX, int srcY, int srcWidth, int srcHeight); public void DrawImage(Image image, int x, int y); public int GetWidth(); public int GetHeight(); } 

ImageFormatは、画像のロヌド方法を簡単に遞択できる列挙型です。 実際、特別なこずは䜕もしたせんが、圢匏を枡す堎所の列挙には、䞍芁なメ゜ッドず䞍芁な名前Configがたくさんあるので、そうしたす。
NewImageは新しい画像を返したす。倉数に保存しお描画したす
Draw ...ずいう名前のメ゜ッドはそれ自䜓を衚しおおり、最初のDrawImageメ゜ッドは画像の䞀郚のみを描画し、2番目のメ゜ッドは画像党䜓を描画したす。
GetWidthずGetHeightは、画像を描く「キャンバス」のサむズを返したす

別のむンタヌフェむスがありたす-写真甚
 public interface Image { public int GetWidth(); public int GetHeight(); public ImageFormat GetFormat(); public void Dispose(); } 

すべおが十分に雄匁です

䞀元化されたゲヌム管理


パッケヌゞcom.mechanic.gameを䜜成したす
アプリケヌション党䜓の操䜜をサポヌトする最埌から2番目の重芁なむンタヌフェヌスがありたす-ゲヌム
 public interface Game { public Input GetInput(); public FileIO GetFileIO(); public Graphics GetGraphics(); public Audio GetAudio(); public void SetScreen(Screen screen); public Screen GetCurrentScreen(); public Screen GetStartScreen(); } 

過去の章のテヌマ-私たちはちょうどそこにむンタヌフェむスを突き出したす。
しかし、スクリヌンずは䜕ですか

気を取らせおください。 ほずんどすべおのゲヌムは、メむンメニュヌ、蚭定メニュヌ、ハむスコア画面、すべおのレベルなど、いく぀かの「状態」で構成されおいたす。 など 少なくずも5぀の状態をサポヌトするこずで、コヌドの深byに浞るこずができるのも䞍思議ではありたせん。 画面抜象クラスは私たちを救いたす
 public abstract class Screen { protected final Game game; public Screen(Game game) { this.game = game; } public abstract void Update(float deltaTime); public abstract void Present(float deltaTime); public abstract void Pause(); public abstract void Resume(); public abstract void Dispose(); } 

各画面の子孫MainMenuScreen、SettingsScreenは、この「状態」を担圓したす。 いく぀かの機胜がありたす。
曎新-曎新
珟圚-グラフィックの衚瀺䟿宜䞊、導入されたもので、実際、この関数は前のものず同様に呌び出されたす
䞀時停止-ゲヌムが䞀時停止されるたびに呌び出されたす画面ブロック
再開-䞀時停止埌のゲヌムの継続
砎棄-ダりンロヌドした画像など、すべおのリ゜ヌスを解攟したす

2぀の関数で枡されるdeltaTimeに぀いお少し話す䟡倀がありたす。
より掗緎されたゲヌム開発者は、ゲヌムの速床たずえば、プレむダヌの動きがデバむスの速床に盎接䟝存する堎合、぀たり サむクルごずに倉数xを1ず぀増やすず、ネットブックず巚倧なRAMを搭茉したコンピュヌタヌでゲヌムが同じように動䜜するこずはありたせん。

したがっお、truバリアントは次のずおりです。
  @Override public void Update(float deltaTime) { x += 150 * deltaTime; } 


truバリアントではありたせん
  @Override public void Update(float deltaTime) { x += 150; } 

基本゚ラヌが1぀ありたす-非垞に倚くの堎合、xを1.0f * deltaTime増やしたすが、0から1の非敎数に敎数を远加しおも結果が埗られないこずに気付かない堎合がありたす。

画面をどのように倉曎したすか ゲヌムむンタヌフェヌスに戻る
SetScreen関数はすべおに責任がありたす。 珟圚の画面ず開始画面を取埗する機胜もありたす。

このコレクション党䜓を実装する時が来たした

入力しお開始



Inputむンタヌフェむスには、むベントのリストを返すGetKeyEventsおよびGetTouchEvents関数がありたす。぀たり、むベントが発生するず、プログラムは倚くのオブゞェクトを䜜成し、ガベヌゞコレクタヌがクリヌンアップしたす。 Androidアプリケヌションのブレヌキがかかる䞻な理由は䜕ですか そうです-ガベヌゞコレクタヌが過負荷になっおいたす 䜕ずか問題を制埡する必芁がありたす。 続行する前に、Poolクラスを䜜成し、「オブゞェクトプヌリング」を実装したす。これは、Mario Tsechnerの優れた曞籍「Programming Games for Android」で提案されおいたす。

その意味は、ガベヌゞコレクタヌがアプリケヌションに干枉するこずを蚱可せず、必芁なリ゜ヌスを無駄にしないこずです。
 public class Pool<T> { public interface PoolFactory<T> { public T Create(); } private final List<T> Objects; private final PoolFactory<T> Factory; private final int MaxSize; public Pool(PoolFactory<T> Factory, int MaxSize) { this.Factory = Factory; this.MaxSize = MaxSize; Objects = new ArrayList<T>(MaxSize); } public T NewObject() { T obj = null; if (Objects.size() == 0) obj = Factory.Create(); else obj = Objects.remove(Objects.size() - 1); return obj; } public void Free(T object) { if (Objects.size() < MaxSize) Objects.add(object); } } 

プヌルプヌルオブゞェクトがあるずしたす。 それが私たちの䜿い方です
  PoolFactory<MechanicTouchEvent> factory = new PoolFactory<MechanicTouchEvent>() { @Override public MechanicTouchEvent Create() { return new MechanicTouchEvent(); } }; TouchEventPool = new Pool<MechanicTouchEvent>(factory, 100); 

プヌル宣蚀
 TouchEventPool.Free(event); 

むベントをプヌルに保存する
 event = TouchEventPool.NewObject(); 

プヌルからむベントを取埗したす。 リストが空の堎合、むベントを䜿甚した埌、次の呌び出したでプヌルに戻すので、これは怖いこずではありたせん。
ずおも良いこずです

メカニック加速床蚈
 package com.mechanic.input; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; public class MechanicAccelerometer implements Accelerometer { float accelX, accelY, accelZ; public MechanicAccelerometer(Context context) { SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); if(manager.getSensorList(Sensor.TYPE_ACCELEROMETER).size() > 0) { Sensor accelerometer = manager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0); manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @Override public void onSensorChanged(SensorEvent event) { accelX = event.values[0]; accelY = event.values[1]; accelZ = event.values[2]; } @Override public float GetAccelX() { return accelX; } @Override public float GetAccelY() { return accelY; } @Override public float GetAccelZ() { return accelZ; } } 

加速床蚈に加えお、このクラスはSensorEventListenerも実装したす。加速床蚈だけでなく、コンパス、懐䞭電灯、その他のおもちゃも制埡する必芁がありたす。 これたでのずころ、加速床蚈のみを実行しおいたす。
コンストラクタヌで、センサヌマネヌゞャヌを取埗し、加速床蚈ぞのアクセスがあるかどうかを確認したす。 䞀般に、理論的には、1぀ではなく耇数の加速床蚈これはリストであり、1぀のオブゞェクトではありたせん、実際には垞に1぀です。 加速床蚈の数が0より倧きい堎合、最初の加速床蚈を取埗しお登録し、このクラスをリスナヌずしお公開したす。 onAccuracyChangedが必芁です。センサヌの粟床が倱われた堎合、䜿甚したせん。 onSensorChangedは、加速床蚈の倀が倉化するず垞に呌び出されたす。ここでは、読み取り倀を取埗したす。

MechanicTouch
 package com.mechanic.input; import java.util.ArrayList; import java.util.List; import com.mechanic.input.Input.MechanicTouchEvent; import com.mechanic.input.Pool.PoolFactory; import android.os.Build.VERSION; import android.view.MotionEvent; import android.view.View; public class MechanicTouch implements Touch { boolean EnableMultiTouch; final int MaxTouchers = 20; boolean[] IsTouched = new boolean[MaxTouchers]; int[] TouchX = new int[MaxTouchers]; int[] TouchY = new int[MaxTouchers]; Pool<MechanicTouchEvent> TouchEventPool; List<MechanicTouchEvent> TouchEvents = new ArrayList<MechanicTouchEvent>(); List<MechanicTouchEvent> TouchEventsBuffer = new ArrayList<MechanicTouchEvent>(); float ScaleX; float ScaleY; public MechanicTouch(View view, float scaleX, float scaleY) { if(Integer.parseInt(VERSION.SDK) < 5) EnableMultiTouch = false; else EnableMultiTouch = true; PoolFactory<MechanicTouchEvent> factory = new PoolFactory<MechanicTouchEvent>() { @Override public MechanicTouchEvent Create() { return new MechanicTouchEvent(); } }; TouchEventPool = new Pool<MechanicTouchEvent>(factory, 100); view.setOnTouchListener(this); this.ScaleX = scaleX; this.ScaleY = scaleY; } @Override public boolean onTouch(View v, MotionEvent event) { synchronized (this) { int action = event.getAction() & MotionEvent.ACTION_MASK; @SuppressWarnings("deprecation") int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT; int pointerId = event.getPointerId(pointerIndex); MechanicTouchEvent TouchEvent; switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: TouchEvent = TouchEventPool.NewObject(); TouchEvent.Type = MechanicTouchEvent.TOUCH_DOWN; TouchEvent.Pointer = pointerId; TouchEvent.X = TouchX[pointerId] = (int)(event.getX(pointerIndex) * ScaleX); TouchEvent.Y = TouchY[pointerId] = (int)(event.getY(pointerIndex) * ScaleY); IsTouched[pointerId] = true; TouchEventsBuffer.add(TouchEvent); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_CANCEL: TouchEvent = TouchEventPool.NewObject(); TouchEvent.Type = MechanicTouchEvent.TOUCH_UP; TouchEvent.Pointer = pointerId; TouchEvent.X = TouchX[pointerId] = (int)(event.getX(pointerIndex) * ScaleX); TouchEvent.Y = TouchY[pointerId] = (int)(event.getY(pointerIndex) * ScaleY); IsTouched[pointerId] = false; TouchEventsBuffer.add(TouchEvent); break; case MotionEvent.ACTION_MOVE: int pointerCount = event.getPointerCount(); for (int i = 0; i < pointerCount; i++) { pointerIndex = i; pointerId = event.getPointerId(pointerIndex); TouchEvent = TouchEventPool.NewObject(); TouchEvent.Type = MechanicTouchEvent.TOUCH_DRAGGED; TouchEvent.Pointer = pointerId; TouchEvent.X = TouchX[pointerId] = (int)(event.getX(pointerIndex) * ScaleX); TouchEvent.Y = TouchY[pointerId] = (int)(event.getY(pointerIndex) * ScaleY); TouchEventsBuffer.add(TouchEvent); } break; } return true; } } @Override public boolean IsTouchDown(int pointer) { synchronized(this) { if(pointer < 0 || pointer >= MaxTouchers) return false; else return IsTouched[pointer]; } } @Override public int GetTouchX(int pointer) { synchronized(this) { if (pointer < 0 || pointer >= MaxTouchers) return 0; else return TouchX[pointer]; } } @Override public int GetTouchY(int pointer) { synchronized(this) { if (pointer < 0 || pointer >= 20) return 0; else return TouchY[pointer]; } } @Override public List<MechanicTouchEvent> GetTouchEvents() { synchronized (this) { for (int i = 0; i < TouchEvents.size(); i++) TouchEventPool.Free(TouchEvents.get(i)); TouchEvents.clear(); TouchEvents.addAll(TouchEventsBuffer); TouchEventsBuffer.clear(); return TouchEvents; } } } 

Touchに加えお、OnTouchListenerも実装したす
デバむスが耇数の指の抌䞋をサポヌトしおいるかどうかを刀断するには、EnableMultiTouchが必芁です。 VERSION.SDKが5未満の堎合䜕らかの理由でこの倉数は文字列ずしお衚瀺されたす、サポヌトされたせん。
MaxTouchers-指の​​最倧数。 それらの20がありたす、倚かれ少なかれ。
onTouch関数では、指の番号ずアクション抌す、持ち䞊げる、移動を取埗したす。これらをむベントに蚘録し、むベントをリストに远加したす。
GetTouchEventsでは、むベントのリストを返したす。むベントのリストはその埌クリアしたす。 別のリストがむベントのリストを返したす。
あなたは、ScaleXずScaleYは䜕に責任があるのでしょうか これに぀いおは、グラフィックセクションで埌ほど説明したす。

メカニックキヌボヌド
 package com.mechanic.input; import java.util.ArrayList; import java.util.List; import android.view.KeyEvent; import android.view.View; import com.mechanic.input.Input.MechanicKeyEvent; import com.mechanic.input.Pool.PoolFactory; import com.mechanic.input.Pool; public class MechanicKeyboard implements Keyboard { boolean[] PressedKeys = new boolean[128]; Pool<MechanicKeyEvent> KeyEventPool; List<MechanicKeyEvent> KeyEventsBuffer = new ArrayList<MechanicKeyEvent>(); List<MechanicKeyEvent> KeyEvents = new ArrayList<MechanicKeyEvent>(); public MechanicKeyboard(View view) { PoolFactory<MechanicKeyEvent> pool = new PoolFactory<MechanicKeyEvent>() { @Override public MechanicKeyEvent Create() { return new MechanicKeyEvent(); } }; KeyEventPool = new Pool<MechanicKeyEvent>(pool,100); view.setOnKeyListener(this); view.setFocusableInTouchMode(true); view.requestFocus(); } public boolean IsKeyPressed(int KeyCode) { if(KeyCode < 0 || KeyCode > 127) return false; return PressedKeys[KeyCode]; } public List<MechanicKeyEvent> GetKeyEvents() { synchronized(this) { for(int i = 0; i < KeyEvents.size(); i++) KeyEventPool.Free(KeyEvents.get(i)); KeyEvents.clear(); KeyEvents.addAll(KeyEventsBuffer); KeyEventsBuffer.clear(); return KeyEvents; } } @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if(event.getAction() == KeyEvent.ACTION_MULTIPLE) return false; synchronized(this) { MechanicKeyEvent key = KeyEventPool.NewObject(); key.KeyCode = keyCode; key.KeyChar = (char)event.getUnicodeChar(); if(event.getAction() == KeyEvent.ACTION_DOWN) { key.Type = MechanicKeyEvent.KEY_DOWN; if(keyCode > 0 && keyCode < 128) PressedKeys[keyCode] = true; } if(event.getAction() == KeyEvent.ACTION_UP) { key.Type = MechanicKeyEvent.KEY_UP; if(keyCode > 0 && keyCode < 128) PressedKeys[keyCode] = false; } KeyEventsBuffer.add(key); } return false; } } 

128個のブヌル倉数の配列を䜜成し、128個のキヌが抌されたかどうかに関する情報を保持したす。 たた、オブゞェクトのプヌルず2぀のリストを䜜成したす。 すべおがシンプルです

MechanicInput
 package com.mechanic.input; import java.util.List; import android.content.Context; import android.view.View; public class MechanicInput implements Input { MechanicKeyboard keyboard; MechanicAccelerometer accel; MechanicTouch touch; public MechanicInput(Context context, View view, float scaleX, float scaleY) { accel = new MechanicAccelerometer(context); keyboard = new MechanicKeyboard(view); touch = new MechanicTouch(view, scaleX, scaleY); } @Override public boolean IsKeyPressed(int keyCode) { return keyboard.IsKeyPressed(keyCode); } @Override public boolean IsKeyPressed(char keyChar) { return keyboard.IsKeyPressed(keyChar); } @Override public boolean IsTouchDown(int pointer) { return touch.IsTouchDown(pointer); } @Override public int GetTouchX(int pointer) { return touch.GetTouchX(pointer); } @Override public int GetTouchY(int pointer) { return touch.GetTouchY(pointer); } @Override public float GetAccelX() { return accel.GetAccelX(); } @Override public float GetAccelY() { return accel.GetAccelY(); } @Override public float GetAccelZ() { return accel.GetAccelZ(); } @Override public List<MechanicTouchEvent> GetTouchEvents() { return touch.GetTouchEvents(); } @Override public List<MechanicKeyEvent> GetKeyEvents() { return keyboard.GetKeyEvents(); } } 

「ファサヌド」パタヌンを実珟したす。

これで、ファむルを操䜜する時間です

ファむルを操䜜する



MechanicFileIO
 package com.mechanic.fileio; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import android.content.res.AssetManager; import android.os.Environment; public class MechanicFileIO implements FileIO { AssetManager assets; String ExternalStoragePath; public MechanicFileIO(AssetManager assets) { this.assets = assets; ExternalStoragePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator; } public InputStream ReadAsset(String name) throws IOException { return assets.open(name); } public InputStream ReadFile(String name) throws IOException { return new FileInputStream(ExternalStoragePath + name); } public OutputStream WriteFile(String name) throws IOException { return new FileOutputStream(ExternalStoragePath + name); } } 

アセットマネヌゞャヌからアセットフォルダヌからファむルを削陀したす。最初の関数はそれを䜿甚し、2番目の2぀の関数はAndroidの特別なデバむスフォルダヌからファむルを取埗したす。 コンストラクタヌでこのフォルダヌぞのパスを取埗したす。

サりンドを䜜成したす

音を扱う



MechanicSound
 package com.mechanic.audio; import android.media.SoundPool; public class MechanicSound implements Sound { int id; SoundPool pool; public MechanicSound(SoundPool pool, int id) { this.pool = pool; this.id = id; } public void Play(float volume) { pool.play(id, volume, volume, 0, 0, 1); } public void Close() { pool.unload(id); } } 


MechanicAudioでは、SoundPoolを䜿甚しお小さな効果音を保持したす。 MechanicSoundでは、サりンド゚フェクト番号ずSoundPoolオブゞェクト自䜓を枡し、そこからサりンドを生成したす

MechanicMusic
 package com.mechanic.audio; import java.io.IOException; import android.content.res.AssetFileDescriptor; import android.media.MediaPlayer; public class MechanicMusic implements Music { MediaPlayer Player; boolean IsPrepared = false; public MechanicMusic(AssetFileDescriptor descriptor) { Player = new MediaPlayer(); try { Player.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength()); Player.prepare(); IsPrepared = true; } catch(Exception ex) { throw new RuntimeException("   "); } } public void Close() { if(Player.isPlaying()) Player.stop(); Player.release(); } public boolean IsLooping() { return Player.isLooping(); } public boolean IsPlaying() { return Player.isPlaying(); } public boolean IsStopped() { return !IsPrepared; } public void Play() { if(Player.isPlaying()) return; try { synchronized(this) { if(!IsPrepared) Player.prepare(); Player.start(); } } catch(IllegalStateException ex) { ex.printStackTrace(); } catch(IOException ex) { ex.printStackTrace(); } } public void SetLooping(boolean loop) { Player.setLooping(loop); } public void SetVolume(float volume) { Player.setVolume(volume, volume); } public void Stop() { Player.stop(); synchronized(this) { IsPrepared = false; } } @Override public void onCompletion(MediaPlayer player) { synchronized(this) { IsPrepared = false; } } } 

サりンドファむルをストリヌムに配眮しお再生したす。
IsPreparedは、サりンドが䜜業の準備ができおいるかどうかを瀺したす。
このクラスを理解するこずをお勧めしたす。

MechanicAudioに行きたした
 package com.mechanic.audio; import java.io.IOException; import android.app.Activity; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.media.AudioManager; import android.media.SoundPool; public class MechanicAudio implements Audio { AssetManager assets; SoundPool pool; public MechanicAudio(Activity activity) { activity.setVolumeControlStream(AudioManager.STREAM_MUSIC); this.assets = activity.getAssets(); pool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0); } public Music NewMusic(String name) { try { AssetFileDescriptor descriptor = assets.openFd(name); return new MechanicMusic(descriptor); } catch(IOException ex) { throw new RuntimeException("    " + name); } } public Sound NewSound(String name) { try { AssetFileDescriptor descriptor = assets.openFd(name); int id = pool.load(descriptor, 0); return new MechanicSound(pool, id); } catch(IOException ex) { throw new RuntimeException("    " + name); } } } 

コンストラクタヌでは、デバむスごずに音楜を調敎し、アセットマネヌゞャヌを䜿甚しお、䞀床に20以䞋のサりンド゚フェクトしか再生できないSoundPoolを䜜成できたす。ほずんどのゲヌムではこれで十分だず思いたす。
Musicの䜜成では、ファむル蚘述子をMechanicMusicコンストラクタヌに枡したす。Soundでは、サりンドをsoundPoolに読み蟌み、プヌルずサりンド番号をMechanicSoundコンストラクタヌに枡したす。䜕か問題が発生した堎合、䟋倖が䜜成されたす。

補図工を䜜りたす

グラフィックを䜿甚する



MechanicImage
 package com.mechanic.graphics; import com.mechanic.graphics.Graphics.ImageFormat; import android.graphics.Bitmap; public class MechanicImage implements Image { Bitmap bitmap; ImageFormat format; public MechanicImage(Bitmap bitmap, ImageFormat format) { this.bitmap = bitmap; this.format = format; } @Override public int GetWidth() { return bitmap.getWidth(); } @Override public int GetHeight() { return bitmap.getHeight(); } @Override public ImageFormat GetFormat() { return format; } @Override public void Dispose() { bitmap.recycle(); } } 

このクラスはむメヌゞホルダヌです。圌は特別なこずは䜕もせず、䟿宜䞊導入されたした。

MechanicGraphics
 package com.mechanic.graphics; import java.io.IOException; import java.io.InputStream; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; public class MechanicGraphics implements Graphics { AssetManager assets; Bitmap buffer; Canvas canvas; Paint paint; Rect srcRect = new Rect(), dstRect = new Rect(); public MechanicGraphics(AssetManager assets, Bitmap buffer) { this.assets = assets; this.buffer = buffer; this.canvas = new Canvas(buffer); this.paint = new Paint(); } @Override public Image NewImage(String fileName) { ImageFormat format; InputStream file = null; Bitmap bitmap = null; try { file = assets.open(fileName); bitmap = BitmapFactory.decodeStream(file); if (bitmap == null) throw new RuntimeException("   '" + fileName + "'"); } catch (IOException e) { throw new RuntimeException("   '" + fileName + "'"); } finally { try { if(file != null) file.close(); } catch(IOException e) { } } if (bitmap.getConfig() == Config.RGB_565) format = ImageFormat.RGB_565; else if (bitmap.getConfig() == Config.ARGB_4444) format = ImageFormat.ARGB_4444; else format = ImageFormat.ARGB_8888; return new MechanicImage(bitmap, format); } @Override public void Clear(int color) { canvas.drawRGB((color & 0xff0000) >> 16, (color & 0xff00) >> 8, (color & 0xff)); } @Override public void DrawPixel(int x, int y, int color) { paint.setColor(color); canvas.drawPoint(x, y, paint); } @Override public void DrawLine(int x, int y, int x2, int y2, int color) { paint.setColor(color); canvas.drawLine(x, y, x2, y2, paint); } @Override public void DrawRect(int x, int y, int width, int height, int color) { paint.setColor(color); paint.setStyle(Style.FILL); canvas.drawRect(x, y, x + width - 1, y + width - 1, paint); } @Override public void DrawImage(Image image, int x, int y, int srcX, int srcY, int srcWidth, int srcHeight) { srcRect.left = srcX; srcRect.top = srcY; srcRect.right = srcX + srcWidth - 1; srcRect.bottom = srcY + srcHeight - 1; dstRect.left = x; dstRect.top = y; dstRect.right = x + srcWidth - 1; dstRect.bottom = y + srcHeight - 1; canvas.drawBitmap(((MechanicImage)image).bitmap, srcRect, dstRect, null); } @Override public void DrawImage(Image image, int x, int y) { canvas.drawBitmap(((MechanicImage)image).bitmap, x, y, null); } @Override public int GetWidth() { return buffer.getWidth(); } @Override public int GetHeight() { return buffer.getHeight(); } } 

泚意しおくださいこれはガベヌゞコレクタヌに察する犯眪であるため、描画するたびにPaintおよびRectオブゞェクトを䜜成するわけではありたせん。
コンストラクタヌでは、Bitmapを䜿甚したす。これは、すべおを描画するバッファヌであり、キャンバスによっお䜿甚されたす。
画像を読み蟌むず、アセットから画像を読み取り、ビットマップでデコヌドしたす。ダりンロヌドしたファむルが画像でない堎合、たたはファむルが存圚しない堎合は、䟋倖がスロヌされ、ファむルは閉じられたす。最埌に、むメヌゞ圢匏を取埗しお新しいMechanicImageを返し、BitmapずImageFormatをコンストラクタヌに枡したす。たた、画像の䞀郚を描画する最初のDrawImageメ゜ッドも泚目に倀したす。これは、アトラスず呌ばれる画像のグルヌプがゲヌム内の個々の画像の代わりに䜿甚される堎合に適甚されたす。このようなアトラスの䟋は
画像
次のずおりですinteresnoe.info Webリ゜ヌスからの画像
䜍眮1.1で32.32から48.48の画像の䞀郚を描画する必芁があるずしたす。それからそうする
 DrawImage(image, 1, 1, 32, 32, 16, 16); 

残りの方法は簡単に理解でき、興味の察象ではありたせん。

ゲヌムず画面のむンタヌフェむスの時間です

続行する前に、グラフィックを別のストリヌムに描画し、ナヌザヌストリヌムを読み蟌たないようにする必芁がありたす。
別のスレッドでグラフィックスを描画できるSurfaceViewクラスをご芧ください。ランナヌクラスを䜜成する
 package com.mechanic.game; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.view.SurfaceHolder; import android.view.SurfaceView; public class Runner extends SurfaceView implements Runnable { MechanicGame game; Canvas canvas; Bitmap buffer; Thread thread = null; SurfaceHolder holder; volatile boolean running = false; public Runner(Object context, MechanicGame game, Bitmap buffer) { super(game); this.game = game; this.buffer = buffer; this.holder = getHolder(); } public void Resume() { running = true; thread = new Thread(this); thread.start(); } public void run() { Rect dstRect = new Rect(); long startTime = System.nanoTime(); while(running) { if(!holder.getSurface().isValid()) continue; float deltaTime = (System.nanoTime()-startTime) / 1000000000.0f; startTime = System.nanoTime(); game.GetCurrentScreen().Update(deltaTime); game.GetCurrentScreen().Present(deltaTime); canvas = holder.lockCanvas(); canvas.getClipBounds(dstRect); canvas.drawBitmap(buffer, null, dstRect, null); holder.unlockCanvasAndPost(canvas); } } public void Pause() { running = false; while(true) { try { thread.join(); break; } catch (InterruptedException e) { } } } } 

MechanicGameクラスは近日公開予定です。心配しないでください。
ナヌザヌむンタヌフェむスにないグラフィックを描画するには、SurfaceHolderオブゞェクトが必芁です。䞻な機胜は、lockCanvasずunlockCanvasAndPostです。最初の関数はSurfaceをブロックし、䜕かを描画するCanvasこの堎合、キャンバスずしお機胜するBitmapバッファヌを返したす。
Resume関数では、このクラスで新しいスレッドを開始したす。
run関数では、アプリケヌションの実行䞭に、最埌のサむクルからの経過時間が取埗されSystem.nanoTimeはナノ秒を返したす、アプリケヌションの珟圚の画面の曎新および珟圚の関数が呌び出され、その埌バッファヌが描画されたす。

これがMechanicGameクラスです
 package com.mechanic.game; import com.mechanic.audio.Audio; import com.mechanic.audio.MechanicAudio; import com.mechanic.fileio.FileIO; import com.mechanic.fileio.MechanicFileIO; import com.mechanic.graphics.Graphics; import com.mechanic.graphics.MechanicGraphics; import com.mechanic.input.Input; import com.mechanic.input.MechanicInput; import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.os.Bundle; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.view.Window; import android.view.WindowManager; public abstract class MechanicGame extends Activity implements Game { Runner runner; Graphics graphics; Audio audio; Input input; FileIO fileIO; Screen screen; WakeLock wakeLock; static final int SCREEN_WIDTH = 80; static final int SCREEN_HEIGHT = 128; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); boolean IsLandscape = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); int frameBufferWidth = IsLandscape ? SCREEN_HEIGHT : SCREEN_WIDTH; int frameBufferHeight = IsLandscape ? SCREEN_WIDTH : SCREEN_HEIGHT; Bitmap frameBuffer = Bitmap.createBitmap(frameBufferWidth, frameBufferHeight, Config.RGB_565); float scaleX = (float) frameBufferWidth / getWindowManager().getDefaultDisplay().getWidth(); float scaleY = (float) frameBufferHeight / getWindowManager().getDefaultDisplay().getHeight(); runner = new Runner(null, this, frameBuffer); graphics = new MechanicGraphics(getAssets(), frameBuffer); fileIO = new MechanicFileIO(getAssets()); audio = new MechanicAudio(this); input = new MechanicInput(this, runner, scaleX, scaleY); screen = GetStartScreen(); setContentView(runner); PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "Game"); } @Override public Input GetInput() { return input; } @Override public FileIO GetFileIO() { return fileIO; } @Override public Graphics GetGraphics() { return graphics; } @Override public Audio GetAudio() { return audio; } @Override public void SetScreen(Screen screen) { if (screen == null) throw new IllegalArgumentException("Screen    null"); this.screen.Pause(); this.screen.Dispose(); screen.Resume(); screen.Update(0); this.screen = screen; } @Override public Screen GetCurrentScreen() { return screen; } @Override public Screen GetStartScreen() { return null; } @Override public void onResume() { super.onResume(); wakeLock.acquire(); screen.Resume(); runner.Resume(); } @Override public void onPause() { super.onPause(); wakeLock.release(); runner.Pause(); screen.Pause(); if(isFinishing()) screen.Dispose(); } } 


このクラスには、Runnerオブゞェクト、すべおのむンタヌフェむスずクラス、およびWakeLockオブゞェクトゲヌムの実行䞭に携垯電話がスリヌプ状態にならないようにするために必芁

があり、2぀の定数がありたす。
デバむスには倚くの解像床があり、各デバむスの画像サむズの調敎、堎所の蚈算などはほずんど䞍可胜で無意味です。など䞊蚘の2぀の定数から80x128ピクセルを枬定するりィンドりがあるず想像しおください。このりィンドりに小さな絵を描きたす。しかし、突然、デバむスの画面サむズがこのりィンドりのサむズに適合したせん。どうするすべおが非垞に簡単です。りィンドりの幅ず長さずデバむスの幅ず長さの比を取り、この比が䞎えられるずすべおの絵を描きたす。
その結果、アプリケヌション自䜓がデバむスの画面䞋で画像を拡倧したす。

このクラスにはActivityが含たれ、onCreate、onResume、およびonPauseのメ゜ッドがありたす。
onCreateでは、アプリケヌションは最初にフルスクリヌンモヌドになりたすそのため、䞊郚に充電ず時間が衚瀺されなくなりたす。次に、電話の向き暪向きたたは瞊向きが刀明したす蚘事の冒頭で.xmlファむルに既に蚘述されおいたす。次に、このりィンドりのサむズが80x128ピクセルの埅望のバッファヌが䜜成されたす。このりィンドりずデバむスのサむズの比率は、MechanicInputコンストラクタヌに枡され、それがリレヌションをMechanicTouchに枡したす。そしおここ-ビンゎ結果の画面䞊のタッチポむントはこの比率で乗算されるため、クリックの座暙はデバむスのサむズに䟝存したせん。
次に、むンタヌフェむスを䜜成し、RunnerずWakeLockを登録したす。
SetScreenメ゜ッドでは、珟圚のScreenを解攟し、別のScreenを䜜成したす。
興味のある他の方法は提䟛したせん。

それだけですか

はい、玳士、フレヌムワヌクは準備ができおいたす
終わったら。

そしお、たずえばMyGameなどのメむンクラスにフレヌムワヌクを接続する方法を教えおください。

「メむン」クラスは次のようになりたす
 public class MyGame extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_game); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.my_game, menu); return true; } } 

このクラスに倉曎したす
 package com.mechanic; import com.mechanic.game.MechanicGame; import com.mechanic.game.Screen; public class MyGame extends MechanicGame { @Override public Screen GetStartScreen() { return new GameScreen(this); } } 

MechanicGame自䜓がActivityから継承するため、JavaはこのクラスをActivityからの継承者ずしお認識したす。onCreateはすでに登録されおいたす。このメ゜ッドはMechanicGameでnullを返し、゚ラヌをスロヌするため、GetStartScreenをオヌバヌラむドするだけです。
GameScreenクラスを実装するこずを忘れないでください:)
 package com.mechanic; import com.mechanic.game.Game; import com.mechanic.game.Screen; import com.mechanic.graphics.Graphics; import com.mechanic.graphics.Image; public class GameScreen extends Screen { Graphics g = game.GetGraphics(); Image wikitan; float x = 0.0f; public GameScreen(Game game) { super(game); wikitan = g.NewImage("wikipetan.png"); } @Override public void Update(float deltaTime) { if(game.GetInput().IsTouchDown(0)) x += 1.0f * deltaTime; } @Override public void Present(float deltaTime) { g.Clear(0); g.DrawImage(wikitan, (int)x, 0); } @Override public void Pause() { } @Override public void Resume() { } @Override public void Dispose() { wikitan.Dispose(); } } 


これは、Wikipe-tanむメヌゞをダりンロヌドし、画面をクリックしお移動するScreen実装の簡単な䟋です。
画像
ru.wikipedia.org Webリ゜ヌスからの画像

結果
画像

倉数xは、0から1の数倀を远加しおも䜕も埗られないため、浮動小数点数ずしお衚されたす。
キャンバスのサむズは80x128ピクセルなので、Wikipe-tanは増加しお描画されたす

質問ず回答


-画像が正しく描画されたせん-90床回転したした
-これは、xmlファむルでコマンドを暪モヌドでのみ動䜜するように指定したためです。モヌドを切り替えるには、キヌボヌドの右偎にあるキヌ7を抌したす
-正盎にx + = 1.0f * deltaTimeを倉曎したすが、画像が動かないか、ゆっくり動きたす。 どうする
-゚ミュレヌタヌは非垞に遅いものです。デバむス䞊のアプリケヌションの状態を確認したす。

楜しんでください

出兞
rghost.ru/49052713
github.com/Izaron/MechanicFramework
文献
developer.alexanderklimov.ru/android
habrahabr.ru/post/109944
マリオ・テシュナヌの「Android向けゲヌムのプログラミング」

Source: https://habr.com/ru/post/J195830/


All Articles