آشنایی با کتابخانه AndEngine (موتور بازی سازی) + آموزش

Nicolas Gramlich
نصب APK از مثال ها (این نسخه قدیمی هست و آپدیت نشده !)
تعداد از بازی های ساخته شده با AndEngine از جمله عنکبوت حریص و Noogra Nuts
Blog (پیشنهاد میکنم تغییر آیپی بدید < که بتونید کلیپ های یوتیوب توی این آدرس رو ببینید)
این بازی هم با AndEngine نوشته شده
گفتوگوی جام جم با میلاد دورباش که از AndEngine استفاده کرده (لینک بازی)
استخدام برنامه نویس AndEngine در ایران ! ! ! (لینک های بیشتر)
منابع آموزشی :
//
از تمام دوستانی که این موضوع براشون جالب هست و علاقه به یادگیری دارند دعوت میکنم در تولید مقاله های آموزشی کمک کنند < ، سعی میکنم انجمنی رو برای یادگیری AndEngine شروع کنیم ، باید ببینم شرایط چطور پیش میره ، به احتمال زیاد تاپیک پرسش و پاسخ AndEngine و آموزش ها در همین Uncocoder ادامه پیدا میکنه و از اینجا خارج نشیم
# بر چسب :
AndEngine ، آموزش AndEngine ، فارسی AndEngine ، اندروید ، بازی سازی ، بازی اندروید ، ساخت بازی ، اندانجین ، AndEngine Toturial ، android game ، game maker android
AndEngine for Android Game Development Cookbook
Learning Android Game Programming
ساخت یک بازی ساده
توی مثال سعی میکنیم یک بازه ساده بسازیم : قرار هست در این بازی یک Sprite داشته باشیم که هرجایی که روی صفحه کلیک میکنیم به اون نقطه حرکت کنه
همچین اگر روی خودش کلیک کنیم یک Modifier رو نمایش میدیم که sprite به مرور غیب میشه و دوباره ظاهر میشه :
اکتیویتی به شکل زیر باید ساخت بشه:
public class TouchGame extends BaseGameActivity implements IOnSceneTouchListener, IOnAreaTouchListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private BitmapTextureAtlas atlas;
private TiledTextureRegion faceTileRegion;
private AnimatedSprite faceSprite;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
faceTileRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
Scene scene = new Scene();
scene.setBackground(new Background(0.7f, 0.8f, 0.1f));
scene.setOnAreaTouchListener(this);
scene.setOnSceneTouchListener(this);
faceSprite = new AnimatedSprite(0, 0, faceTileRegion, getVertexBufferObjectManager());
faceSprite.animate(100);
scene.registerTouchArea(faceSprite);
scene.attachChild(faceSprite);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, ITouchArea pTouchArea, float pTouchAreaLocalX, float pTouchAreaLocalY) {
if (pSceneTouchEvent.isActionDown()) {
AnimatedSprite sprite = (AnimatedSprite) pTouchArea;
SequenceEntityModifier seModifier = new SequenceEntityModifier(new FadeOutModifier(1),
new DelayModifier(0.5f),
new FadeInModifier(1));
sprite.registerEntityModifier(seModifier);
return true;
}
return false;
}
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if (pSceneTouchEvent.isActionDown()) {
MoveModifier move = new MoveModifier(2, faceSprite.getX(), pSceneTouchEvent.getX(), faceSprite.getY(), pSceneTouchEvent.getY());
faceSprite.registerEntityModifier(move);
return true;
}
return false;
}
}
تمام کد بالا رو بلد هستید و تنها نکته ایی که در کد وجود داره مربوط به متد onAreaTouched هست در خط زیر
AnimatedSprite sprite = (AnimatedSprite) pTouchArea;
اینقدر ساده و شفاف هست که که فکر میکنم نیاز به توضیح نداشته باشه ، در واقع با استفاده از pTouchArea میتونیم اون Sprite که روش کلیک رو شده کست کنیم و بگیریم (هنوز در مورد اینکه چطور میشه بصورت داینامیک Sprite , AnimateSprite در یافت کرد چیزی نمیدونم)
IPathModifierListener ، آموزش های تکمیلی از PathModifier و AnimateSprite های حرفه ایی تر !
خب با دیدین تصویر بالا میدونید که برای نمایش دادنش به TiledTuxtureRegion و AnimateSprite نیاز داریم . مشکلی که وجود داره این هست که این Sprite مثل تصویر Banana نیست و نمیشه فریم های index صفر تا index یازدهم رو به پشت سر هم نمایش داد و حتی اگر اونهارو پشت سر هم اجرا کنیم اصلا جالب نیست و انیمیشن خروجی اصلا جالب نمیشه
برای SpriteSheet که در بالا میبینید ما به ROW 1 (سطر اول) نیاز داریم که تشکیل شده از سه فریم هست برای زمانی که به سمت بالا حرکت میکنه و بقیه ROW ها هم برای زمانی هست که Sprite در جهات دیگر در حال حرکت باشه (قبلا بهتون یاد دادم که چطور نمایش فریم هارو محدود کنید)
در ادامه به معرفی IPathModifierListene میپردازیم که یک لیستنر برای PathModifier هست و همچنین یادمیگیریم که چطور از روش دوم animate شدن یک AnimateSprite به که قبلا هم باهاش آشنا شدیم به شیوه درست استفاده کنیم
همونطور که میدونید برای استفاده از PathModifier نیاز بود که یک Path از قبل براش مشخص کنیم ، این Path از دو بخش تشکیل میشد بخش اول که تعداد و اندازه (Lenght) بود و بخش دوم به همون اندازه نقاط X,Y
کاری که IPathModifierListene انجام میده این هست که به ما خبر میده در حال حاضر کدوم یکی از Pathدر حال اجرا هست
Path path = new Path(5).to(0, 0).to(400, 0).to(400, 240).to(800, 240).to(800 - (face.getWidth()), 480 - (face.getHeight()));
اگر یک path به شکل بالا داشته باشیم در لحظه اجرا IPathModifierListene به ما Index 0 رو خروجی میشه یعنی 0و0 که در بالا میبینید بعد از اون Index1 که مربوط به 400,0 هست و به همین ترتیب که در آخرین نقطه 4 رو به ما خروجی میده
از اونجایی که PathModifierیک شیوه ساده برای حرکت دادن Sprite هست و مسیر های مشخصی داره پس ما از قبل میدونیم که مسیر حرکت این Sprite به چه شکل هست یعنی وقتی IPathModifierListene داره خروجی های 0 تا 4 رو به ما نشون میده دقیقا میدونیم که Sprite داره در کدوم جهت حرکت میکنه و این به ما کمک میکنه که در این لحظه تعیین کنید که کدون ROW باید روی Sprite باید animate بشه
خب دیگه خودتون همه چیز رو بلد هست یک Path بسازید که Sprite شروع کنه چهار گوشه Scene رو بصورت نامحدود حرکت کنه
فایل player.png در آدرس gfx در asset از پروژه AndEngineExample وجود داره همونجا برش دارید تست کنید < کد هارو به کلاس Modifiere که قبلا آموزش داده بودیدم اضاف کنید و تست کنید : (دیگه خودتون باید بدونید جای هرکدوم از کدهای زیر کجای اکتیویتی هست)
private TiledTextureRegion playerRegion;
playerRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "player.png", 0, 70, 3, 4);
AnimatedSprite spritePlayer= new AnimatedSprite(50, 50, playerRegion, getVertexBufferObjectManager());
spritePlayer.animate(170);
scene.attachChild(spritePlayer);
خب بعد از اجرا کردن کدهای بالا میبینید که این player در حال قدم زدن هست و بدنش رو در جهت های مختلف تغییر جهت میده در حالی که حرکت نمیکنه و داره درجا میزنه ، بصورت زیر کاری میکنیم که player در چهار طرف Scene بصورت نامحدود در حال حرکت باشه
Path pathPlayer = new Path(5).to(50, 50).to(750, 50).to(750, 430).to(50, 430).to(50, 50);
spritePlayer.registerEntityModifier(new LoopEntityModifier(new PathModifier(12, pathPlayer)));
همونطور که در کد بالا میبینید بدون استفاده از EaseFunction ها Sprite موقع تغییر جهت دادن به یک باره تغییر جهت میده و اصلا جالب نیست و حالت مصنوعی بودن اون زیاد هست با استفاده از EaseSineInOut.getInstance() این حالت رو کمی طبیعی تر میکنیم
spritePlayer.registerEntityModifier(new LoopEntityModifier(new PathModifier(12, pathPlayer, EaseSineInOut.getInstance())));
در ادامه میریم که از IPathModifierListene استفاده کنیم یک لیستنر خشک و خالی بصورت زیر ایجاد میکنیم
IPathModifierListener listener = new IPathModifierListener() {
@Override
public void onPathStarted(final PathModifier pPathModifier, final IEntity pEntity) {
Debug.d("onPathStarted");
}
@Override
public void onPathWaypointStarted(final PathModifier pPathModifier, final IEntity pEntity, final int pWaypointIndex) {
Log.i("LOG", "onPathWaypointStarted: " + pWaypointIndex);
}
@Override
public void onPathWaypointFinished(final PathModifier pPathModifier, final IEntity pEntity, final int pWaypointIndex) {
Debug.d("onPathWaypointFinished: " + pWaypointIndex);
}
@Override
public void onPathFinished(final PathModifier pPathModifier, final IEntity pEntity) {
Debug.d("onPathFinished");
}
};
این لیستنر رو باید کجا استفاده کرد ؟ طریقه استفاده از اون رو در زیر میبینید :
این لیستنر رو باید کجا استفاده کرد ؟ طریقه استفاده از اون رو در زیر میبینید :
spritePlayer.registerEntityModifier(new LoopEntityModifier(new PathModifier(12, pathPlayer, listener, EaseSineInOut.getInstance())));
تنها نکته جدیدی که به چشم میاد Debug هست که این برای AndEngine هست برای نمایش LOG با تگ پیشفرض AndEngine ، خودتون هم میدتوید براش تگ تعریف کنید و هم میتونید لولش رو تغییر بدید < بهتر کم کم راه بیوفتید و این چیزهای ساده رو خودتون تست کنید و سرو ته قضیه رو متوجه بشید .
خب در ادامه باید باتوجه به حالت هایی که Listener به ما خبر میده تصمیم بگیریم حتما خودتون دیگه اون LOG ایی که توی لیستنر گذاشته بودم رو چک کردید و متوجه موضوع شدید ، قبل از هر چیز باید خط زیر رو کامنت کنید
//spritePlayer.animate(170);
کد به شکل زیر میشه :
IPathModifierListener listener = new IPathModifierListener() {
@Override
public void onPathStarted(final PathModifier pPathModifier, final IEntity pEntity) {
Debug.d("onPathStarted");
}
@Override
public void onPathWaypointStarted(final PathModifier pPathModifier, final IEntity pEntity, final int pWaypointIndex) {
Log.i("LOG", "onPathWaypointStarted: " + pWaypointIndex);
switch (pWaypointIndex) {
case 0:
spritePlayer.animate(new long[]{ 200, 200, 200 }, 3, 5, true); //RIGHT
break;
case 1:
spritePlayer.animate(new long[]{ 200, 200, 200 }, 6, 8, true); //DOWN
break;
case 2:
spritePlayer.animate(new long[]{ 200, 200, 200 }, 9, 11, true); //LEFT
break;
case 3:
spritePlayer.animate(new long[]{ 200, 200, 200 }, 0, 2, true); //UP
break;
}
}
@Override
public void onPathWaypointFinished(final PathModifier pPathModifier, final IEntity pEntity, final int pWaypointIndex) {
Debug.d("onPathWaypointFinished: " + pWaypointIndex);
}
@Override
public void onPathFinished(final PathModifier pPathModifier, final IEntity pEntity) {
Debug.d("onPathFinished");
}
};
Path pathPlayer = new Path(5).to(50, 50).to(750, 50).to(750, 430).to(50, 430).to(50, 50);
spritePlayer.registerEntityModifier(new LoopEntityModifier(new PathModifier(12, pathPlayer, listener, EaseSineInOut.getInstance())));
خب حتما متوجه زیبایی این موضوع شدید ، باید بگم که این روش کاملا خشک و استاتیک هست و به هنوز راهی برای داینامیک و انعطاف پذیری اون رو بلد نیستم !
خب یک نکته دیگه هم میتونم اینجا بهتون یاد بدم ، اون هم اینکه ، اندازه spritePlayer ما توی صفحه کمی کوچیک دیده میشه ، شاید فکر کنیم که میشه بهش Scale داد ولی این موضوع رو به روشی دیگه و ساده تر هم میشه انجام داد : دو خط زیر رو با هم مقایسه کنید پارامترهای ورودی متدهارو بخونید که چی هستند ، متوجه موضوع میشید : دیگه بیشتر توضیح نمیدم
//final AnimatedSprite spritePlayer = new AnimatedSprite(50, 50, playerRegion, getVertexBufferObjectManager());
final AnimatedSprite spritePlayer = new AnimatedSprite(50, 50, 48, 64, playerRegion, getVertexBufferObjectManager());
بعد از تست خط بالا حتما متوجه شدید که این Sprite ما داره از نقطه 0 و 0 ترسیم میشه و کمی از صفحه بیرون میزنه و یا دقیقا روی نقاط تعریف شده حرکت نمیکنه ، خودتون باید مسیر های Path رو ویرایش کنید و به اندازه نصف ارتفاع و عرض Region تصمیم بگیرید
ساخت یک بک گراند تکرار شونده در بازی (ساخت زمینی پر از چمن)
خب این آموزش یک چالش یادگیری واقعی هست ، چون چیز خاصی برای توضیح دادن نداره و خیلی راحت هست ، اگر تا اینچا تمام کدهارو کپی میکردید و کد نمیزدید که خیلی بد هست ، بهتره این رو خودتون کد بزنید ماهی گرفتن رو ادامه ندید ، باید ماهیگیری رو یاد بگیرید .
برای یاد گیری این موضوع میتونید کلاس های زیر رو در AndEngineExamples پیدا کنید و سورس هارو نیگاه کنید پارامترهای ورودی متدهارو چک کنید و یاد بگیرید
PathModifierExample.java
RepeatingSpriteBackgroundExample.java
در نهایت باید بتونید خروجی زیر رو تولید کنید
ساخت یک بازی دیگر
قبل از اینکه وارد بحث فیزیک بشیم همین الان یه مورد به ذهنم رسید گفتم برای اینکه هم خودم بهتر یاد بگیرم و هم شما ، این رو هم تست کنم ، قراره وقتی روی Scene کلیک میکنیم یک Sprite اضافه بشه و وقتی روی Sprite کلیک میکنیم از بازی حذف بشه ، من برم ببینم چ میکنم ، نتیجه رو اینجا میزارم
در حال کد زدن . . . ;
خب نتیجه حاصل شد : (دیگه سعی میکنم مسائل ساده رو توضیح ندم)
public class TouchGameSpriteAdd extends BaseGameActivity implements IOnSceneTouchListener, IOnAreaTouchListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private Scene scene;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
scene = new Scene();
scene.setBackground(new Background(0.8f, 0.7f, 0.1f));
scene.setOnAreaTouchListener(this);
scene.setOnSceneTouchListener(this);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, ITouchArea pTouchArea, float pTouchAreaLocalX, float pTouchAreaLocalY) {
if (pSceneTouchEvent.isActionDown()) {
AnimatedSprite sprite = (AnimatedSprite) pTouchArea;
scene.unregisterTouchArea(sprite);
scene.detachChild(sprite);
return true;
}
return false;
}
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if (pSceneTouchEvent.isActionDown()) {
float x = pSceneTouchEvent.getX();
float y = pSceneTouchEvent.getY();
AnimatedSprite sprite = new AnimatedSprite(x - (regionSprite.getWidth() / 2), y - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
sprite.animate(100);
scene.registerTouchArea(sprite);
scene.attachChild(sprite);
return true;
}
return false;
}
}
اگر قصد دارید موقع کلیک شدن روی Sprite ها صدا هم داشته باشید ، به پروژه AndEngineExamples برید و پروژه مربوط به تولید صدا رو پیدا کنید و خودتون قبل از اینکه اینجا آموزش بدیم ، یادش بگیرید
فکر کنم تا همینجا میشه با کمی خلاقیت بازی هایی ساخت که قابل قرار گرفتن در مارکت باشه
خودم قصد دارم موقع یاد گرفتن Phisycs و Velocity یه بازی بدم بازار (شایدم فقط بزارم اینجا) ، برم ببینم این Velocity چی هست
حرکت طبیعی اجسام استفاده از Physics و Velocity
تا به اینجای کار هرچیزی رو که یاد گرفتیم ، کاملا مبتدی بود و اصلا نیازی به AndEngine نداره حتی میشه تمام اینهارو بدون استفاده از AndEngine هم توی اکلیپس ساخت همونطور که توی این تاپیک میبینید ، اما AndEngine برای همین کارهای مبتدی هم ، سختی کار رو ساده کرده ، در ادامه میریم که کم کم وارد بحث فیزیک بشیم ، و اینجا هست که قابلیت های AndEngine مشخص میشه ، پیشنهاد میکنم برای فهم و درک بهتر در مورد فیزیک این لینک رو مطالعه کنید
استفاده از Modifier ها حالت مصنوعی داره و اصلا طبیعی نیست چون معلوم نیست با چه سرعتی حرکت میکنند و سرعت حرکت کردن اونها بستگی به فاصله بین دو نقطه و زمان تعیین شده داره ، اگر اولین بازی که در بالا ساختیم رو تست کرده باشید بهتر متوجه میشید دارم چی میگم ، ولی هرچی که باشه Modifiere ها بهترین هستند برای ساخت موانع متحرک سر راه کاراکترهای اصلی در بازی .
این رو هم بگم که Velocity یعنی سرعت حدود 10 سال بعد دارم میفهمم چرا V توی فیزیک سرعت بود و چرا S یعنی speed برای سرعت نبود ! و یجای دیگه V ولت بود حالا volt (اختلاف پتانسیل) رو میشد فهمید به هرحال ولت با V شروع میشد ، ولی سرعت (speed) با S (به هر حال من از این نظام آموزشی متنفرم و علم واقعی رو باید از گوگل یاد گرفت ، مثل همین الان که جهت یاد آوری V دوم یعنی اختلاف پتانسیل از گوگل و ویکی کمک گرفتم )
خب ساده ترین روش ممکن برای اینکه یک جسم با سرعت ثابت حرکت کنه به شکل زیر هست :
public class PhysicsSimpleVelocity extends BaseGameActivity implements IOnSceneTouchListener, IOnAreaTouchListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private Scene scene;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
private AnimatedSprite sprite;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
scene = new Scene();
scene.setBackground(new Background(0.9f, 0.7f, 0.2f));
sprite = new AnimatedSprite(50 - (regionSprite.getWidth() / 2), 240 - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
sprite.animate(150);
scene.attachChild(sprite);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
PhysicsHandler mPhysicsHandler;
mPhysicsHandler = new PhysicsHandler(sprite);
sprite.registerUpdateHandler(mPhysicsHandler);
mPhysicsHandler.setVelocity(100, 0);
//mPhysicsHandler.setAngularVelocity(50);
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, ITouchArea pTouchArea, float pTouchAreaLocalX, float pTouchAreaLocalY) {
// TODO Auto-generated method stub
return false;
}
باید بگم که اگر تا به اینجا تمام کد هارو در onCreateScene میزدیم شیوی درستی نبوده و من برای راحتی کار اینکارو میکردم و اینکه میخواستم ذهنتون اصلا طرف متد onPopulateScene نره و اینکه در زمان مشخصی بعد از اینکه از AndEngine به درک کافی رسیدید با استفاده از یک مثال مفهوم onPopulateScene رو بهتون بفهمونم .
تمام آپدیت ها و فعالیت های روی Scene بعد از اجرای خط زیر معنی پیدا میکنه : که توی متد onPopulateScene هست
pOnPopulateSceneCallback.onPopulateSceneFinished();
کافیه توی تمام مثال هایی که تا به الان داشتیم خط زیر رو بردارید میبینید که هیچکدوم از AnimatedSprite ها انیمیشن نمیشوند ، فکر کنم همین مثال کمک کرده باشه که به کاربرد onPopulateScene پی ببرید ، توی این کد بالا هم همچنان شما میتونستید کدهای استفاده شده رو در onCreateScene هم بکار ببرید ، به این خاطر که CallBack های این دو متد صدا زده شده هستند .
اگر ذهنتون با این مورد جدیدی که طرح کردم به چالش کشیده شده که کدهارو از کجا باید زد ؟ و با یک چیز جدید آشنا شدید که براتون عجیبه کافیه به این موضوع هم دقت کنید که از معنی اسم onCreateScene هم مشخصه که این متد فقط برای ساخت Scene (صحنه نمایش) هست و نه برای رندر کردن فعالیت های رو اون در واقع خط زیر که در onCreateScene صدا زده میشه اتمام کار رو به onPopulateScene خبر میده و این متد شروع به کار میکنه (تمام چیزی که راجب onPopulateScene میدونستم رو با تمام صحیح و غلط و یا ناقص بودنش رو بهتون گفتم)
pOnCreateSceneCallback.onCreateSceneFinished(scene);
حتما متوجه شدید که Sprite ما از اونور Scene میزنه بیرون ؟ بلاخره بحث فیزیک هست و همه چیز رو طبیعی میکنه وقتی شا به یک جسم قوانین فیزیک وارد میکنید باتوجه به شرایط پیش رو رفتار میکنه (برم ببینم چطور میتونم این مشکل رو حل نم و از توش یه آموزش در بیارم !)
ادامه در پست بعد <
ادامه حرکت طبیعی اجسام استفاده از Physics و Velocity (رفع مشکل بیرون رفتن از Scene)
( آموزش این قسمت با توجه به اطلاعات ناقص داده میشه ! البته تمام آموزش هااز بالا تا اینجا با توجه به اطلاعات ناقص بود :D ولی خودم نسبت به این پست احساس خوبی ندارم )
این چیزی که در بالا دیدید با استفاده از سورس های موجود در کلاس MovingBallExample.java آموزش داده شده بود ، باید این رو بگم که حتی سورس های موجود در این کلاس هم با این که حرکت رو طبیعی تر کرده اما همچنان با استفاده از فیزیک میشه این حرکت رو طبیعی تر هم جلوه داد ، دلیل این حرفم اینه که برای مدیریت Sprite که از صفحه خارج نشه فقط سرعتی که به فیزیک داده شده رو به جهت عکس تبدیل میکنند و به Sprite وارد میکنند بنابر این Sprite موقع خروج از صفحه در جهت عکس حرکت میکنه ، در حالی که در حالت طبیعی باید یک جسم با توجه به وزن ، سرعت حرکت ، سطح اصطکاک و مهمتر از همه چه شکلی بودن Body که داره (شکل دایره ایی ، چهارگوش ، مثلثی ، بیضی ، خطی ، شش گوش و . . .) به یک سطح برخورد کنه و به توجه به قوانین فیزیک رفتار کنه
خب ، برای مدیریت کردن حرکت یک Sprite باید از onManagedUpdate استفاده کرد ، onManagedUpdate یک متد Override شده از زیر کلاس Entitiy هست (دقیقا نمیتونم Entity رو بهتون توضیح بدم اما AnimatedSprite یک Entity هست) اگر بیشتر دقت کرده باشید میبینید که متد زیر یک پارامتر Entitiy میگیره که ما بهش یک AnimatedSprite دادیم
mPhysicsHandler = new PhysicsHandler(sprite);
زیر کلاس هایی از Entitiy ها به ما کمک میکنند که روی Sprite ها مدیریت داشته باشیم ، فرض کنید قرار باشه به یک Sprite مقدار Life رو 10 بدید ، خب این Sprite باید بعد از 10 ضربه بمیره و از بازی حذف بشه ، مدیریت تعداد Life های یک کارکاتر کار سختی هست ، اینجاست که باید از زیر کلاس های Entitiy ها استفاده کرد ، ظاهرا این زیر کلاس های دنیایی دارند که من هیچ اطلاعی ازشون ندارم
یک زیر کلاس به شکل زیر بسازید :
private class mySprite extends AnimatedSprite {
}
میبینید که باید Constractor هارو اضاف کنید : بعد از اضافه شدن میبیند که این ورودی ها شبیه به ورودی هایی هست که یک AnimateSprite میساختید ، خب بخاطر این هست که این کلاس از AnimatedSprite مشتق شده دیگه ! :D
private class mySprite extends AnimatedSprite {
public mySprite(float pX, float pY, ITiledTextureRegion pTiledTextureRegion, VertexBufferObjectManager vertexBufferObjectManager) {
super(pX, pY, pTiledTextureRegion, vertexBufferObjectManager);
// TODO Auto-generated constructor stub
}
}
حالا متد onManagedUpdate رو Override کنید :
private class mySprite extends AnimatedSprite {
public mySprite(float pX, float pY, ITiledTextureRegion pTiledTextureRegion, VertexBufferObjectManager vertexBufferObjectManager) {
super(pX, pY, pTiledTextureRegion, vertexBufferObjectManager);
// TODO Auto-generated constructor stub
}
@Override
protected void onManagedUpdate(float pSecondsElapsed) {
// TODO Auto-generated method stub
super.onManagedUpdate(pSecondsElapsed);
}
}
قبل از این یک AnimatedSprite رو به شکل زیر میساختید :
sprite = new AnimatedSprite(50 - (regionSprite.getWidth() / 2), 240 - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
یعنی یک شی جدید از AnimatedSprite میساختید ، باز هم قراره یک شی از AnimatedSprite ساخته بشه اما اینبار از mySprite ساخته میشه (امیدارم قضیه رو گرفته باشید)
قضیه این هست که برای مدیریت یک Sprite روی Scene یک new AnimatedSprite ساده و خشک و خالی نمیتونه به ما کمک کنه ، برای این چنین قابلیت هایی ما باید از زیر کلاس های ما باید خودمون یک کلاس از AnimatedSprite رو بسازیم و اون رو کامل تر از new AnimatedSprite قرار بدیم که بشه ازش قابلیت های بیشتری استفاده کرد و اینجاست که میشه Sprite روی Scene رو مدیریت کرد و قبالیت های بیشتری رو داشت
خب در ادامه خط زیر رو به این شکل ویرایش میکنیم : (خودم برم یه F5 بزنم ببینم این همه که حرف زدم درست گفتم یا نه ! ، یعنی اطلاعاتی که در اختیارتون میزارم در همین حد اعتبار داره :D)
//sprite = new AnimatedSprite(50 - (regionSprite.getWidth() / 2), 240 - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
sprite = new mySprite(50 - (regionSprite.getWidth() / 2), 240 - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
در کمال تعجب درست داره کار میکنه :D
ادامه در پست بعد <
ادامه ! ادامه حرکت طبیعی اجسام استفاده از Physics و Velocity ( پایان رفع مشکل بیرون رفتن از Scene)
خب تا به اینجای کار کد ما به شکل زیر شد و کمی هم تغییر کرد :
تغییرات : رنگ بک گراند عوض شد + قوانین فیزیک رفت یجایی دیگه استفاده شد با توجه به شرایط هم ورودی خودش رو گرفت < همچنین private AnimatedSprite sprite دیگه لازم نیست fild باشه
public class PhysicsSimpleVelocity extends BaseGameActivity implements IOnSceneTouchListener, IOnAreaTouchListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private Scene scene;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
scene = new Scene();
scene.setBackground(new Background(0.3f, 0.3f, 0.1f));
//sprite = new AnimatedSprite(50 - (regionSprite.getWidth() / 2), 240 - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
mySprite sprite = new mySprite(50 - (regionSprite.getWidth() / 2), 240 - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
sprite.animate(150);
scene.attachChild(sprite);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, ITouchArea pTouchArea, float pTouchAreaLocalX, float pTouchAreaLocalY) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
// TODO Auto-generated method stub
return false;
}
private class mySprite extends AnimatedSprite {
public mySprite(float pX, float pY, ITiledTextureRegion pTiledTextureRegion, VertexBufferObjectManager vertexBufferObjectManager) {
super(pX, pY, pTiledTextureRegion, vertexBufferObjectManager);
mPhysicsHandler = new PhysicsHandler(this);
this.registerUpdateHandler(mPhysicsHandler);
mPhysicsHandler.setVelocity(100, 100);
//mPhysicsHandler.setAngularVelocity(50);
}
@Override
protected void onManagedUpdate(float pSecondsElapsed) {
// TODO Auto-generated method stub
super.onManagedUpdate(pSecondsElapsed);
}
}
}
خب بریم بررسی کنیم onManagedUpdate چی هست و چیا داره ! (من برم کد بزنم برگردم . . .)
خب چیزی متوجه نشدم ، کد های کلاس MovingBallExample.java رو کپی کردم که مشخصه داره چیکار میکنه :
@Override
protected void onManagedUpdate(float pSecondsElapsed) {
float mySpriteX = mX;
float mySpriteY = mY;
if (mySpriteX < 0) {
this.mPhysicsHandler.setVelocityX(100);
} else if (mySpriteX + this.getWidth() > WIDTH) {
this.mPhysicsHandler.setVelocityX( -100);
}
if (mySpriteY < 0) {
this.mPhysicsHandler.setVelocityY(100);
} else if (this.mY + this.getHeight() > HEIGHT) {
this.mPhysicsHandler.setVelocityY( -100);
}
super.onManagedUpdate(pSecondsElapsed);
}
در آخر هم احساس میکنم شاید از خط زیر چیزی متوجه نشده باشید
mPhysicsHandler.setAngularVelocity(50);
خب این باعث میشه یک جسم چرخش داشته باشه ، کاربردش چی هست ؟ درواقع برخورد یک جسم به در و دیوار توی حالتی که داره دور خودش میچرخه با حالتی که مستقیم با کله داره به سمت دیوار میره فرق داره ، که اینجا فرقش برای ما مشخص نیست چون از فیزیک بصورت کامل استفاده نکردیم !
در حال ویرایش . . .
ساخت یک بازی با استفاده از آموزش های قبلی
کلیک کنید روی صفحه Sprite ها شروع میکنند که حرکت کردن ، روی خود Sprite ها بزنید از صفحه روزگار حذف میشوند ! اشتباه کنید کارتون اضاف میشه
public class PhysicsVelocity extends BaseGameActivity implements IOnSceneTouchListener, IOnAreaTouchListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private Scene scene;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
scene = new Scene();
scene.setBackground(new Background(0.3f, 0.3f, 0.1f));
scene.setOnAreaTouchListener(this);
scene.setOnSceneTouchListener(this);
//sprite = new AnimatedSprite(50 - (regionSprite.getWidth() / 2), 240 - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
mySprite sprite = new mySprite(50 - (regionSprite.getWidth() / 2), 240 - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
sprite.animate(150);
scene.attachChild(sprite);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, ITouchArea pTouchArea, float pTouchAreaLocalX, float pTouchAreaLocalY) {
if (pSceneTouchEvent.isActionDown()) {
AnimatedSprite sprite = (AnimatedSprite) pTouchArea;
scene.unregisterTouchArea(sprite);
scene.detachChild(sprite);
return true;
}
return false;
}
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if (pSceneTouchEvent.isActionDown()) {
float x = pSceneTouchEvent.getX();
float y = pSceneTouchEvent.getY();
mySprite sprite = new mySprite(x - (regionSprite.getWidth() / 2), y - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
sprite.animate(100);
scene.registerTouchArea(sprite);
scene.attachChild(sprite);
return true;
}
return false;
}
private class mySprite extends AnimatedSprite {
PhysicsHandler mPhysicsHandler;
public mySprite(float pX, float pY, ITiledTextureRegion pTiledTextureRegion, VertexBufferObjectManager vertexBufferObjectManager) {
super(pX, pY, pTiledTextureRegion, vertexBufferObjectManager);
mPhysicsHandler = new PhysicsHandler(this);
this.registerUpdateHandler(mPhysicsHandler);
mPhysicsHandler.setVelocity(100, 100);
//mPhysicsHandler.setAngularVelocity(50);
}
@Override
protected void onManagedUpdate(float pSecondsElapsed) {
float mySpriteX = mX;
float mySpriteY = mY;
if (mySpriteX < 0) {
this.mPhysicsHandler.setVelocityX(100);
} else if (mySpriteX + this.getWidth() > WIDTH) {
this.mPhysicsHandler.setVelocityX( -100);
}
if (mySpriteY < 0) {
this.mPhysicsHandler.setVelocityY(100);
} else if (this.mY + this.getHeight() > HEIGHT) {
this.mPhysicsHandler.setVelocityY( -100);
}
super.onManagedUpdate(pSecondsElapsed);
}
}
}
ایندفعه که دیگه واغعا این تصویر زیر جا داره استفاده بشه :D :)) ، عجب چیزی ساخته شد تا اینجا :D
همین جا اعلام میکنم هرگونه کپی برداری از ایده بالا نامردیه :D ولی شما راحت باشید ( بالاتر که گفته بودم قراره بازی بدم ) با همین میشه یه بازی با 150 تا مرحله ساخت بازار که هیچی گوگل پلی هم میشه داد ، فوقالعاده هم اعتیاد آور می باشد :D ، فقط یه باگ کوچیک داره که میشه تو بازی تقلب کرد :D
آموزش های کاربردی
*** آموزش مهم ***
( استفاده کاربردی از Camera ! )
پیدا کردن متد جدید <
زیر سوال رفتن آموزش قبلی :D
مگه میشه Camera که همیشه اون بالا تعریف میشد و هیچ کار هم باهاش نداشتیم ! ، خب عاره همین الان یه چیز جدید ازش یاد گرفتم که چیز خوبیه یه آموزش داشتیم بالاتر به اسم حرکت طبیعی اجسام استفاده از Physics و Velocity ، اونجا گفتیم که ساده ترین روش ممکن برای اینکه یک جسم با سرعت ثابت حرکت کنه به شکل زیر هست : ( با همون سورس کار مکنیم )
برم ببینم این چیزی که میگ جواب میده یا نه ! جواب داد پست رو ادامه میدم ، جواب نداد پست رو ویرایش میکنم یه طور دیگه این قابلیت رو توضیح میدم :D (فک کنم توانایی این رو پیدا کردم که یک چیز اشتباه رو به صحیح ترین شکل ممکن به شما توضیح بدم و شما هم قبولش کنید دیگه :D چون خروجی کدش صحیح کار میکنه :)) ، یادش بخیر یه زمانی Emoji های یاهو معروف بود )
خب ظاهرا نتیجه داره حاصل میشه ، باید بگم که اون آموزشی که اولش گفته بودم اطلاعات ناقص هست ، اینجا ثابت میشه که واقعا اطلاعات ناقص بود ولی شیوه کار بالا کاملا صحیح بود دیگه خدارو شکر تا خوردم اعتراف نکنم شما هم چیزی متوجه نمیشید :)) ، یه جای دیگه هم عین جمله زیر گفته بودم که انتظار داشتم چیزی رو پیدا کنم که بشه ازش توی متد onCreateScene یا توی onPopulateScene ازش استفاده کرد که الان اون چیزی که میخواستم رو پیدا کردم :D
خب بریم بررسی کنیم onManagedUpdate چی هست و چیا داره ! (من برم کد بزنم برگردم . . .)
خب چیزی متوجه نشدم ، کد های کلاس MovingBallExample.java رو کپی کردم که مشخصه داره چیکار میکنه :
خب بریم ببینیم چی پیدا کردم ^_^ ، متد زیر رو پیدا کردم :D
scene.registerUpdateHandler(new IUpdateHandler() {
@Override
public void reset() {
// TODO Auto-generated method stub
}
@Override
public void onUpdate(float pSecondsElapsed) {
Log.i("X", "" + sprite.getX());
}
});
همونطور که میبینید یه Log هم وسط متد هست که همون تمام فعالیت های خیر خواهانه مارو زیر سوال برد ، متد جدید این بالایی بود
قابلیت کاربردی هم که از Camera پیدا کردیم بصورت زیر هست :
camera.isRectangularShapeVisible(pRectangularShape)
خب اون کدی که بالا داشتیم و قبلا آموزش داده بودیم به شکل زیر تغییر میکنه :
public class PhysicsSimpleVelocity extends BaseGameActivity implements IOnSceneTouchListener, IOnAreaTouchListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private Scene scene;
private Camera camera;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
private AnimatedSprite sprite;
@Override
public EngineOptions onCreateEngineOptions() {
camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
scene = new Scene();
scene.setBackground(new Background(0.9f, 0.7f, 0.2f));
sprite = new AnimatedSprite(50 - (regionSprite.getWidth() / 2), 240 - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
sprite.animate(150);
scene.attachChild(sprite);
scene.registerUpdateHandler(new IUpdateHandler() {
@Override
public void reset() {
// TODO Auto-generated method stub
}
@Override
public void onUpdate(float pSecondsElapsed) {
Log.i("X", "" + sprite.getX());
if ( !camera.isRectangularShapeVisible(sprite)) {
scene.setBackground(new Background(0.9f, 0.0f, 0.2f));
}
}
});
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
PhysicsHandler mPhysicsHandler;
mPhysicsHandler = new PhysicsHandler(sprite);
sprite.registerUpdateHandler(mPhysicsHandler);
mPhysicsHandler.setVelocity(100, 0);
mPhysicsHandler.setAngularVelocity(50);
// TODO Auto-generated method stub
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, ITouchArea pTouchArea, float pTouchAreaLocalX, float pTouchAreaLocalY) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
// TODO Auto-generated method stub
return false;
}
}
خب دیگه چون آموزش ها فی البداهه هست این چیزها هم داره دیگه ، من یه چیزی میگم باید کدش هم زده بشه بعدا میریم جلوتر میبینیم پیوستگی آموزش ها حفظ نشده ، خب تا اینجا خیلی از آموزش هارو از قبل بلد بودم میشد پیوسته وار گذاشت پست سر هم ، ولی از اینجا به بعد دیگه کلا هرچی یاد میگیرم جدید هست ، مجبورا بزاریم اینجا که امکان داره به موضوعات قبلی ربطی نداشته باشه !
در حال کد زدن . . .
آموزش جدید (تشخیص Touch روی Sprite)
توی این روش نیازی به استفاده از Interface ها نیست و Override کردن متد جدید نیست :
همچنین توی این متد یاد میگیرد چطور یک Toast رو نمایش بدید ، یک روش جدید نمایش animate هم رو میبینید که انتظار دارم این رو خودتون از قبل بلد باشید
AnimatedSprite spritebanana = new AnimatedSprite(100, 100, regionBanana, getVertexBufferObjectManager()) {
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, float X, float Y) {
if (pSceneTouchEvent.isActionUp()) {
this.animate(100, false);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "Banana", Toast.LENGTH_SHORT).show();
}
});
}
return true;
};
};
scene.registerTouchArea(spritebanana);
scene.attachChild(spritebanana);
کد هارو به آموزش قبلی مربوط به تشخیص TOUCH روی Sprite اضاف کنید ؛ در کل کار به شکل زیر میشه :
public class TouchArea extends BaseGameActivity implements IOnAreaTouchListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
TiledTextureRegion faceRegion;
TiledTextureRegion regionBanana;
BitmapTextureAtlas atlas;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 256, 256);
faceRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
regionBanana = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "banana_tiled.png", 32, 32, 4, 2);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
Scene scene = new Scene();
scene.setBackground(new Background(0.6f, 0.8f, 0.2f));
scene.setOnAreaTouchListener(this);
AnimatedSprite faceSprite = new AnimatedSprite((WIDTH / 2) - (faceRegion.getWidth() / 2), (HEIGHT / 2) - (faceRegion.getHeight() / 2), faceRegion, getVertexBufferObjectManager());
faceSprite.animate(100);
scene.attachChild(faceSprite);
scene.registerTouchArea(faceSprite);
AnimatedSprite spritebanana = new AnimatedSprite(100, 100, regionBanana, getVertexBufferObjectManager()) {
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, float X, float Y) {
if (pSceneTouchEvent.isActionUp()) {
this.animate(100, false);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "Banana", Toast.LENGTH_SHORT).show();
}
});
}
return true;
};
};
scene.registerTouchArea(spritebanana);
scene.attachChild(spritebanana);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, ITouchArea pTouchArea, float pTouchAreaLocalX, float pTouchAreaLocalY) {
Log.i("TouchArea", "New Event On Sprite");
return false;
}
}
اصلاح و توضیحات صحیحتر از آموزش های قبلی
درواقع اصلاح نیست ، آموزش های قبلی کاملا درست بود اما با چیزهایی که تا قبل از این توضیح دادم ظاهرا شما فکر میکنید برای حذف کردن یک Sprite کد های زیر کافی باشه
scene.unregisterTouchArea(sprite);
scene.detachChild(sprite);
در حالی که در واقعیت کدهای زیر صحیح هستند و یک Entity به روش زیر کاملا حذف میشه :
scene.unregisterTouchArea(sprite);
scene.detachChild(sprite);
sprite.detachSelf();
sprite.dispose();
ساخت یک بازی ( دشمن هوشمند )
قبل از آموزش دادن بخش جدید ، به ذهنم رسید که میشه با توجه به چیزهایی که تا به الان یاد گرفتیم یک نمونه دشمن هوشمند بسازیم : کار این کارکاتر دشمن ، این هست که کاراکتر اصلی مارو تعقیب میکنه و هرجا که اون باشه دنبالش میره
ساده ترین کد که میتونستم بزنم به شکل زیر بود :
public class FollowSprite extends BaseGameActivity implements IOnSceneTouchListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
private AnimatedSprite sprite;
private Rectangle fallowRect;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
Scene scene = new Scene();
scene.setBackground(new Background(0.7f, 0.9f, 0.1f));
scene.setOnSceneTouchListener(this);
sprite = new AnimatedSprite(50, (HEIGHT / 2) - (regionSprite.getHeight() / 2), regionSprite, getVertexBufferObjectManager());
sprite.animate(150);
fallowRect = new Rectangle(750, (HEIGHT / 2) - 8, 16, 16, getVertexBufferObjectManager());
fallowRect.setColor(Color.RED);
scene.attachChild(sprite);
scene.attachChild(fallowRect);
scene.registerUpdateHandler(new IUpdateHandler() {
@Override
public void reset() {}
@Override
public void onUpdate(float pSecondsElapsed) {
MoveModifier moveModifer = new MoveModifier(1, fallowRect.getX(), sprite.getX() + 8, fallowRect.getY(), sprite.getY() + 8);
fallowRect.registerEntityModifier(moveModifer);
}
});
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if (pSceneTouchEvent.isActionDown()) {
MoveModifier moveModifer = new MoveModifier(2, sprite.getX(), pSceneTouchEvent.getX() - 16, sprite.getY(), pSceneTouchEvent.getY() - 16);
moveModifer.setAutoUnregisterWhenFinished(true);
sprite.registerEntityModifier(moveModifer);
}
return false;
}
}
تنها نکته جدید توی کد های بالا Rectangle هست که اگر اولین آموزش ها رو دیده باشید ، در اونجا یاد گرفته بودید که چطور یک Line رو رسم کنید < این هم رسم یگ Rectangle (چهارگوش) هست ، اگر دارید فکر میکنید که چرا به atlas اضافه نشده و . . . ، باید بگم که این یک Rectangle هست و یک تصویر ثابت و یا یک تصویر متحرک نیست ! پس آموزشهارو قاطی نکنید :D ، مستقیم میشه به Scene اضاف کرد و باهاش رفتار کرد .توی آموزش بعد از ساخت Rectangle چندین بار از عدد 8 و 16 استفاده شده که بهتره ارتفاع و عرض Sprite رو بگیریم و تقسیم کنیم که مرکز رو بدست بیاریم
من قصد دارم کمی دشمنون رو هوشمند کنم ، و همچنین از فیزیک بجای MoveModifire برای حرکت تعیین کنم ، و تعیین کنم که اگر فاصله بین کاراکتر اصلی بازی و این دشمن از یک فاصله کمتر شد تعقیب صورت بگیره و اگر ما موفق شدیم فاصلمون رو بیشتر کنیم دشمن کاراکتر رو گم کنه و تعقیب رو ادامه نده !
خب خودم که فکر میکنم با توجه آموزش های قبلی این چیزی که گفتم ممکن باشه ! باید برم تست کنم
ادامه در دپست بعد
ساخت دشمن هوشمند - استفاده از فیزیک
خب در این بخش همون سناریوی بالارو پیاده میکنیم ، فقط بجای استفاده از Modifier از Physics استفاده میکنیم و هنوز اون قسمت جالب تعقیب و گریز رو ننوشتیم ! هوشمندتر شدن رو ننوشتیم
چالشی که پیش رو داریم این هست که در فیزیک ما فعلا فقط میتونیم سرعت ست کنیم و بگیم که در جهت X , Y با چه سرعتی حرکت کنه باید دقت کنید که در اینجا که از فیزیک استفاده میکنیم سرعت متر بر ثانیه (m/s) هست ، مشکل ما از اونجا هستش که چطور با توجه به این تعیین کنیم که Sprite ما از یک نقطه به یک نقطه دلخواه حرکت کنه ، نقطه دلخواه نقطه ایی هست که روی Scene کلیک میکنیم ، احتمالا استفاده از رابطه فیثاغورث به ما کمک میکنه

خب از رابطه فیثافورث هم نتونستم کمک بگیرم ، هنوز هیچ کدوم از این چیزهایی که گفتم رو نمیشه با استفاده از Physics راه انداخت کار خیلی پیچیده میشه و نیاز به کد نویسی های زیاد و خشک داره که فقط روی دوتا کارکاتر ست میشه ! و در آینده به هیچ درد ما نمیخوره !
شاید این لینک ها به ما کمک کنه :
این پروژه رو میزاریم برای آینده . . .
قسمت دوم هوشمندی هم که قرار بود بنویسیم باید فاصله بین دو کاراکتر رو به دست میاوردیم ، در حالت عادی مقدار X,Y رو محاسبه میکردیم اما در حالت واقعی و صحیح باید شعاعی رو از مرکز کاراکتر در نظر بگیریم ، البته روش های دیگه ایی هم وجود داره !
ساخت دشمن هوشمند در همینجا شکست خورد ، به دلیل اینکه هنوز به دانسته های بیشتری نیاز داریم
نمونه این دشمن هوشمند که قرار بود بسازیم رو میتونید در بازی AttackWave که با AndEngine ساخته شده در GoolePlay ببینید
تشخیص برخورد دو جسم به یک دیگر
collisions (برخورد ، تصادف ، تلاقی)
نیاز به توضیح نداره ، چیز ساده ایی هست : خروجی این متد boolean هست
if (sprite1.collidesWith(sprite2)) {
}
public class Collision extends BaseGameActivity {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
final Scene scene = new Scene();
scene.setBackground(new Background(0.2f, 0.4f, 0.1f));
float centerX = (WIDTH / 2) - (regionSprite.getWidth() / 2);
float centerY = (HEIGHT / 2) - (regionSprite.getWidth() / 2);
final AnimatedSprite sprite1 = new AnimatedSprite(centerX - 250, centerY, regionSprite, getVertexBufferObjectManager());
final AnimatedSprite sprite2 = new AnimatedSprite(centerX + 250, centerY, regionSprite, getVertexBufferObjectManager());
scene.attachChild(sprite1);
scene.attachChild(sprite2);
Path path1 = new Path(3).to(centerX - 250, centerY).to(centerX + 250, centerY).to(centerX - 250, centerY);
Path path2 = new Path(3).to(centerX + 250, centerY).to(centerX - 250, centerY).to(centerX + 250, centerY);
sprite1.registerEntityModifier(new LoopEntityModifier(new PathModifier(2, path1, EaseBackIn.getInstance())));
sprite2.registerEntityModifier(new LoopEntityModifier(new PathModifier(2, path2, EaseBackIn.getInstance())));
scene.registerUpdateHandler(new IUpdateHandler() {
@Override
public void reset() {}
boolean bool = true;
@Override
public void onUpdate(float pSecondsElapsed) {
if (sprite1.collidesWith(sprite2)) {
if (bool) {
bool = false;
scene.setBackground(new Background(0.4f, 0.2f, 0.1f));
} else {
bool = true;
scene.setBackground(new Background(0.2f, 0.4f, 0.1f));
}
}
}
});
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
}
implementsIOnSceneTouchListener,IOnAreaTouchListener
public class TouchAndDrag extends BaseGameActivity {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("res/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
Scene scene = new Scene();
scene.setBackground(new Background(0.6f, 0.8f, 0.1f));
scene.setOnSceneTouchListener(new IOnSceneTouchListener() {
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if (pSceneTouchEvent.isActionUp()) {
AnimatedSprite sprite = new AnimatedSprite(pSceneTouchEvent.getX() - (regionSprite.getWidth() / 2), pSceneTouchEvent.getY() - (regionSprite.getHeight() / 2), regionSprite,
getVertexBufferObjectManager());
sprite.animate(150);
pScene.registerTouchArea(sprite);
pScene.attachChild(sprite);
return true;
}
return false;
}
});
scene.setOnAreaTouchListener(new IOnAreaTouchListener() {
@Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, ITouchArea pTouchArea, float pTouchAreaLocalX, float pTouchAreaLocalY) {
if (pSceneTouchEvent.isActionMove()) {
AnimatedSprite sprite = (AnimatedSprite) pTouchArea;
sprite.setPosition(pSceneTouchEvent.getX() - (regionSprite.getWidth() / 2), pSceneTouchEvent.getY() - (regionSprite.getHeight() / 2));
return true;
}
return false;
}
});
scene.setTouchAreaBindingOnActionDownEnabled(true);
scene.setTouchAreaBindingOnActionMoveEnabled(true);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
}
scene.setTouchAreaBindingOnActionDownEnabled(true);
scene.setTouchAreaBindingOnActionMoveEnabled(true);
استفاده از AnalogOnScreenControl جهت حرکت دادن Sprite
کد :
public class Analog extends BaseGameActivity {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private Camera camera;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
private ITextureRegion regionAnalogBase;
private ITextureRegion regionAnalogKnob;
@Override
public EngineOptions onCreateEngineOptions() {
camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
atlas = new BitmapTextureAtlas(getTextureManager(), 260, 260, TextureOptions.BILINEAR);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
regionAnalogBase = BitmapTextureAtlasTextureRegionFactory.createFromAsset(atlas, this, "onscreen_control_base.png", 33, 33);
regionAnalogKnob = BitmapTextureAtlasTextureRegionFactory.createFromAsset(atlas, this, "onscreen_control_knob.png", 162, 162);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
Scene scene = new Scene();
scene.setBackground(new Background(0.5f, 0.6f, 0.1f));
final AnimatedSprite sprite = new AnimatedSprite(250, 0, regionSprite, getVertexBufferObjectManager());
sprite.animate(150);
final PhysicsHandler physicsHandler = new PhysicsHandler(sprite);
sprite.registerUpdateHandler(physicsHandler);
AnalogOnScreenControl alanogLeft = new AnalogOnScreenControl(0, HEIGHT - regionAnalogBase.getHeight(), camera,
regionAnalogBase, regionAnalogKnob, 0.1f, 200, this.getVertexBufferObjectManager(),
new IAnalogOnScreenControlListener() {
@Override
public void onControlChange(final BaseOnScreenControl pBaseOnScreenControl, final float pValueX, final float pValueY) {
physicsHandler.setVelocity(pValueX * 100, pValueY * 100);
}
@Override
public void onControlClick(final AnalogOnScreenControl pAnalogOnScreenControl) {
sprite.registerEntityModifier(new SequenceEntityModifier(new ScaleModifier(0.25f, 1, 1.5f), new ScaleModifier(0.25f, 1.5f, 1)));
}
});
alanogLeft.getControlBase().setAlpha(0.2f);
alanogLeft.getControlKnob().setAlpha(0.2f);
alanogLeft.getControlBase().setScaleCenter(0, 128);
alanogLeft.refreshControlKnobPosition();
scene.setChildScene(alanogLeft);
scene.attachChild(sprite);
scene.attachChild(alanogLeft);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
}
توضیحات :
تعریف AnalogOnScreenControl به لیستنر هم نیاز داره ، همون لحظه تعریف باید براش لیستنر تعریف کنید که این لیستنر دارای دو متد onControlChange و onControlClick هست اولی برای زمانی که اهرم رو حرکت میدیم و دومی برای زمانی که روی اهرم کلیک میکنیم
alanogLeft.getControlBase().setAlpha(0.2f);
alanogLeft.getControlKnob().setAlpha(0.2f);
alanogLeft.getControlBase().setScaleCenter(0, 128);
alanogLeft.refreshControlKnobPosition();
در مورد خطوط بالا ، خط اول و دوم شفافیت رو تنظیم میکنیم ، هرچی شفاف تر بهتر ، خط سوم مرکز و وسط اهرم رو مشخص میکنیم ، کاربردش رو نمیدونم ! ، خط آخر که از همه مهمتر هست کار رفرش رو انجام میده یعنی زمانی که شما دستتون رو از روی اهرم برمیدارید این دسته اهرم به جای اولش بر میگرده
برای دیدن نمونه هایی دیگه از بکارگیری اهرم میتونید کلاس های زیر رو ببینید : مخصوصا AnalogOnScreenControlsExample که یک نمونه استفاده کاربردی رو به شما نشون میده که میتونید مختصات اهرم رو به درجه تبدیل کنید و استفاده کاربردی اون در اینجا میشه setRotate روی sprite ولی استفاده کاربردی واقعی از تبدیل مقادیر به درجه رو میتونید در RacerGameActivity ببینید
AnalogOnScreenControlExample
AnalogOnScreenControlsExample
RacerGameActivity
استفاده از فیزیک واقعی
گرانش ، جرم ، چگالی ، اصطکاک ، سرعت ، اجسام dynamic , اجسام static ، نوع Body و خیلی چیزهای دیگه ایی که هنوز بلد نیستیم
آاشنایی با مفاهیم جدید : FixtureDef و Body و FixedStepPhysicsWorld و Vector2 و PhysicsWorld
PhysicsWorld یک نمونه ساده از FixedStepPhysicsWorld هست که در ادامه ازش استفاده نمیکنیم و FixedStepPhysicsWorld همیشه کار مارو راه میندازه
FixedStepPhysicsWorld همون PhysicsWorld هست با این تفاوت که بهینه هست و میشه تعیین کرد هر گام از اجرای فیزیک رو در چند گام نمایش بده ، درواقع کمک میکنه که منابع سخت افزاری کمتری مصرف بشه ، همونطور که میدونید حداقل 33 فریم بر ثانیه مقدار مناسبی هست برای اینکه کاربر رضایت داشته باشه ، پس ما همیشه مقدار PhysicsStep رو روی 60 قرار میدیم این کمک میکنه که در هر ثانیه فقط 60 مختصات محاسبه شده و استفاده بشه در حالی که شاید در حالت عادی 300 بار نمایش بکار برده میشد و این باعث هدر رفتن منابع و استفاده بی مورد از CPU میشه ، (دیگه از این بهتر نمیتونستم توضیح بدم ، اطلاعات دقیقی ندارم و با اینکه ممکنه بعضی چیزهارو اشتباه و غلط گفته باشم ولی امیدوارم مفهوم رو به شما رسونده باشم)
Vector2 یک مختصات دو بعدی هست در ساخت گرانش کمک میکنه و با استفاده از مختصات X , Y که میگیره مشخص میشه از کدوم طرف باید گرانش اعمال بشه زمانی که از سنسور های گوشی استفاده میکنیم ، میتونیم با چرخش گوشی و تغییر سنسور ها جهت گرانش تغییر کنه
Body خیلی مهمه فرض کنید که شما یک در بازی یک سطح با شیب دلخواه دارید در واقعیت قرار گرفتن یک جسم دایره و قرار گرفتن یک جسم چهار گوش روی این سطح شیب دار رفتار های متفاوتی از خودش نشون میده ، در AndEngine هم به همین صورت هست شما اگر دو جسم دایره و چهار گوش روی یک سطح شیب دار داشته باشید جسم دایره به سمت پایین حرکت میکنه ، Body میتونه توی برخورد کردن دو جسم به هم و خیلی چیزهای دیگه تعیین کننه باشه ، در مورد Body باید گم که در AndEngine کاملا دست شما باز هست و شما میتونید هر Body رو بسازید حتی Body به شکل T , W
طبق آموزش های اقای رمضان زاده سه نوع Body داریم :
انواع Body :
سه نوع Body در AndEngine داریم که عبارتند از BodyType.DynamicBody و BodyType.StaticBody و BodyType.KinematicBody .
BodyType.DynamicBody : این نوع اجسام در مقابل نیروها (جاذبه و غیره) و اشیای دیگر واکنش نشان میدهند.
BodyType.StaticBody : این نوع اجسام در مقابل سایر نیرو ها و اجسام واکنشی نشان نمی دهند و حرکت نمی کنند.تعیین FixtureDef برای این نوع اشیا چندان دارای اهمیت نیست پس در این مثال لازم نیست نگران مقادیر به کار رفته برای FixtureDef باشید. این نوع اشیا بیشتر برای تعریف زمین ، دیوار و مرز ها به کار می روند. اگرچه این نوع اجسام را می توان با استفاده از setTransform جا به جا کرد ولی توصیه می شود که از این عمل بپرهیزیم.
BodyType.KinematicBody : این نوع اجسام در مقابل سایر اجسام و نیرو ها واکنش نشان نمی دهند ولی می توانند دارای حرکت باشند.
FixtureDef نقش مهمی داره برای استفاده از فیزیک واقعی هر جسم باید حداقل یک Body داشه باشه که Body به وسیله FixtureDef ساخته میشه ، FixtureDef تعیین میکنه که یک جسم خاصیت های چگالی ، الاستیکی و اصطکاک رو به چه صورت در بر داشته باشه
اجسام dynamic , اجسام static برای ساخت یک جسم static مثل دیوار باید نوع خاصی از Body و همچنین خاصیت های FixtrueDef رو که برای اجسامی مثل دیوار تعریف میکنیم آنچنان کاربردی نداره ، چون یک دیوار که همیشه ثابت هست چه فرقی میکنه که جرمش چقدر باشه ! ، برای ساخت یک جسم Dynamic و متحرک که روی Scene حرکت میکنه و جاذبه روی اون تاثیر میذاره قضیه متفاوت هست
در آخر برای آشنایی بهتر با این مفاهیم بهتره این آموزش رو مطالعه کنید
قدم اول ساخت ظاهر و دیوار در چهار طرف بازی
توی آموزش های قبلی دیدیم که وقتی به یک جسم با استفاده از فیزیک سرعت ست میکردیم تا بینهایت حرکت میکرد و از Camera خارج میشد ، بنابر این اگر قراره جاذبه داشتیم حتما باید زمین هم داشته باشیم که جسم در اثر گرانش به زمین برخورد کنه و همونجا به بایسته
کد :
public class PhysicsBodys extends BaseGameActivity {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
Scene scene = new Scene();
scene.setBackground(new Background(0.4f, 0.5f, 0.1f));
int CenterX = WIDTH / 2;
int CenterY = HEIGHT / 2;
AnimatedSprite sprite = new AnimatedSprite(CenterX - regionSprite.getWidth() / 2, CenterY - regionSprite.getHeight() / 2, regionSprite, getVertexBufferObjectManager());
sprite.animate(150);
Rectangle ground = new Rectangle(0, HEIGHT - 6, WIDTH, 6, getVertexBufferObjectManager());
ground.setColor(Color.RED);
Rectangle left = new Rectangle(0, 0, 6, HEIGHT - 6, getVertexBufferObjectManager());
left.setColor(Color.BLUE);
Rectangle roof = new Rectangle(6, 0, WIDTH, 6, getVertexBufferObjectManager());
roof.setColor(Color.GREEN);
Rectangle right = new Rectangle(WIDTH - 6, 6, 6, HEIGHT - 12, getVertexBufferObjectManager());
right.setColor(Color.YELLOW);
scene.attachChild(sprite);
scene.attachChild(ground);
scene.attachChild(left);
scene.attachChild(roof);
scene.attachChild(right);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
}
همه ی کدهای بالا رو بلد هستید
در این شرایط اگر جاذبه زمین رو بسازیم ، هیچ چیزی رو هس نمیکنیم چه هیچ کدوم از اشیا ما در بازی خاصیت های فیزیکی براشون تعریف نشده و نسبت به قوانین فیزیک بی معنی هستند ، در این شرایط اگر به sprite شرایط فیزیکی (جرم و . . .) ست کنیم و جاذبه زمین رو شبیه سازی کنیم Sprite از روی دیوار های چهار گوشه بازی رد میشه ، و به اونها برخورد نمیکنه چون تنها فیزیک رو برای Sprite تعریف کردیم و برای اونها نه ! اگر هم فیزیک رو فقط یه دیوار ها بدیم هیچ چیزی رو حس نمیکنیم ، چون چیزی نیست که جاذبه روش تاثیر بزاره و در ظاهر پیدا باشه
قدم دوم ساخت جاذبه و ساخت Body به واسطه FixtureDef
public class PhysicsBodys extends BaseGameActivity {
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private BitmapTextureAtlas atlas;
private TiledTextureRegion regionSprite;
@Override
public EngineOptions onCreateEngineOptions() {
Camera camera = new Camera(0, 0, WIDTH, HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), camera);
}
@Override
public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
atlas = new BitmapTextureAtlas(getTextureManager(), 64, 32);
regionSprite = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(atlas, this, "face_box_tiled.png", 0, 0, 2, 1);
atlas.load();
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {
Scene scene = new Scene();
scene.setBackground(new Background(0.4f, 0.5f, 0.1f));
int CenterX = WIDTH / 2;
int CenterY = HEIGHT / 2;
AnimatedSprite sprite = new AnimatedSprite(CenterX - regionSprite.getWidth() / 2, CenterY - regionSprite.getHeight() / 2, regionSprite, getVertexBufferObjectManager());
sprite.animate(150);
Rectangle ground = new Rectangle(0, HEIGHT - 6, WIDTH, 6, getVertexBufferObjectManager());
ground.setColor(Color.RED);
Rectangle left = new Rectangle(0, 0, 6, HEIGHT - 6, getVertexBufferObjectManager());
left.setColor(Color.BLUE);
Rectangle roof = new Rectangle(6, 0, WIDTH, 6, getVertexBufferObjectManager());
roof.setColor(Color.GREEN);
Rectangle right = new Rectangle(WIDTH - 6, 6, 6, HEIGHT - 12, getVertexBufferObjectManager());
right.setColor(Color.YELLOW);
FixedStepPhysicsWorld myPhysicsWorld = new FixedStepPhysicsWorld(60, new Vector2(0f, SensorManager.GRAVITY_EARTH), false, 8, 3);
scene.registerUpdateHandler(myPhysicsWorld);
FixtureDef WALL_FIXTURE_DEF = PhysicsFactory.createFixtureDef(0, 0.1f, 0.5f);
FixtureDef BOX_BODY_FIXTURE_DEF = PhysicsFactory.createFixtureDef(20f, 0.35f, 0.5f);
PhysicsFactory.createBoxBody(myPhysicsWorld, ground, BodyType.StaticBody, WALL_FIXTURE_DEF);
PhysicsFactory.createBoxBody(myPhysicsWorld, roof, BodyType.StaticBody, WALL_FIXTURE_DEF);
PhysicsFactory.createBoxBody(myPhysicsWorld, left, BodyType.StaticBody, WALL_FIXTURE_DEF);
PhysicsFactory.createBoxBody(myPhysicsWorld, right, BodyType.StaticBody, WALL_FIXTURE_DEF);
Body dynamicBoxBody = PhysicsFactory.createBoxBody(myPhysicsWorld, sprite, BodyType.DynamicBody, BOX_BODY_FIXTURE_DEF);
myPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(sprite, dynamicBoxBody, PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT));
scene.attachChild(sprite);
scene.attachChild(ground);
scene.attachChild(left);
scene.attachChild(roof);
scene.attachChild(right);
pOnCreateSceneCallback.onCreateSceneFinished(scene);
}
@Override
public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
}
توضیحات :
FixedStepPhysicsWorld(int pStepsPerSecond, Vector2 pGravity, boolean pAllowSleep, int pVelocityIterations, int pPositionIterations)
FixedStepPhysicsWorld myPhysicsWorld =newFixedStepPhysicsWorld(60,newVector2(0f,SensorManager.GRAVITY_EARTH),false,8,3);
scene.registerUpdateHandler(myPhysicsWorld);
پارامتر های ورودی 60 در هر ثانیه 60 گام از فیزیک نمایش داده میشه ، Vector2 که بهش گفتیم جاذبه فقط به سمت پایین باشه همون طور که میبیند شما میتونید جاذبه زمین ، مریخ ، مشتری ، ماه و غیره رو ست کنید پارامتد بعدی این هست که اجازه نمیدیم این جاذبه بخواب بره !! در دو مقدار آخر که سرعت تکرار و تعداد تکرار موقعیت ها هست ، هرچی این مقادیر بیشتر باشه فیزیک نرم تری رو دارریم که بیشتر کردن این دو مقدار مصرف CPU رو افزایش میده پس بهتره بهینه عمل کنید ، بعد از اتمام این مورد فیزیک رو به Scene اضاف میکنیم حالا جاذبه وجود داره اما هنوز هیچ عنصر فیزیکی وجود نداره !
FixtureDef WALL_FIXTURE_DEF = PhysicsFactory.createFixtureDef(0, 0.1f, 0.5f);
FixtureDef BOX_BODY_FIXTURE_DEF = PhysicsFactory.createFixtureDef(20f, 0.35f, 0.5f);
در مورد FixtureDef توضیح دادیم که چی هست برای Wall ها که در چهار طرف بازی قرار دارند تعریف جرم معنایی نداره پس 0 قرار میدیم تعریف الاستیکی هم معنایی نداره (البته آنچنان مطمئن نیستم !)، تنها تعریف اصطکاک میتونه معنی داشته باشه (اما کاربردش رو هنوز بهش شک دارم) ، چرا تعریف جرم معنایی نداره ؟ در ادامه که Body میسازیم متوجه این موضوع میشید ، در واقع چون Body این دیوار ها از نوع static تعریف میکنیم پس گرانش بر اونها تاثیری نداره پس تعریف جرم هم معنایی نداره
PhysicsFactory.createBoxBody(myPhysicsWorld, ground, BodyType.StaticBody, WALL_FIXTURE_DEF);
PhysicsFactory.createBoxBody(myPhysicsWorld, roof, BodyType.StaticBody, WALL_FIXTURE_DEF);
PhysicsFactory.createBoxBody(myPhysicsWorld, left, BodyType.StaticBody, WALL_FIXTURE_DEF);
PhysicsFactory.createBoxBody(myPhysicsWorld, right, BodyType.StaticBody, WALL_FIXTURE_DEF);
ساخت body از نوع Box (چهارگوش) برای دیوار ها نوع body از نوع ثابت (static) و مقدار FixtureDef هم که مشخص هست ، خروجی متد های createBoxBody برابر با Body هست ولی چون به خروجی اونها نیازی نداریم خروجی هم نمیگیریم اما میبینید که برای جسم محترک (Dynamic) مقدار Body رو دریافت میکنیم چون بهش نیاز داریم
Body dynamicBoxBody = PhysicsFactory.createBoxBody(myPhysicsWorld, sprite, BodyType.DynamicBody, BOX_BODY_FIXTURE_DEF);
در بالا هم یک Body.dynamic تعریف میکنیم
myPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(sprite, dynamicBoxBody, PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT));
PhysicsConnector شکل ساخته شده در Scene را به Body مورد نظر لینک می دهد و از این طریق با جا به جا شدن Body شکل مرتبط با آن نیز جا به جا می شود. (طبق گفته های این لینک)، گزینه آخر هم تبدیل pixel به متر هست
پیشنهاد میکنم مقادیر جرم و الاستیکی و اصطکاک رو در FixtureDef برای BOX_BODY_FIXTURE_DEF تغییر بدید و تست کنید
پاسخگویی و مشاهده پاسخ های این سوال تنها برای اعضای ویژه سایت امکان پذیر است .
چنانچه تمایل دارید به همه بخش ها دسترسی داشته باشید میتوانید از این بخش لایسنس این آموزش را خریداری نمایید .