برنامه Flappy، چرا و چگونه در Power Apps یک بازی بسازیم
Power Apps به وضوح یک پلت فرم تمرکز سازمانی است که برای راهحلهای کاربردی و تجاری طراحی شده است. پس چرا باید از آن برای ساختن یک بازی استفاده کنم، خوب یک کمی دیوانه هستم (حتی یاد گرفتم که چگونه Power Automate flows را کدنویسی کنم، اینجا بخوانید)، اما یک دلیل خوب وجود دارد. بهترین روشی که من یاد میگیرم انجام دادن است، و با توجه به تنوع ملزومات/راهحلهای Power App، شما به مجموعه مهارتهای متنوعی نیاز دارید. از ورود دادهها گرفته تا مدیریت پروژه گرفته تا سیستمهای موجودی، Power Apps میتواند همه این کارها را انجام دهد، و به عنوان توسعهدهنده ممکن است تکرار زیادی برای تمرین نداشته باشید.
بنابراین من اغلب برای خودم پروژههای شخصی تنظیم میکنم تا مهارتهایم را یاد بگیرم/تمرین کنم، و در این مورد این بازی پرنده شل و ول بود. چه مهارت هایی… خوب در این مورد (کاملاً صادقانه برنامه ریزی شده، نه اینکه فقط می خواستم بازی دیگری بسازم) تایمرها و مختصات بود.
تایمرها فوق العاده قدرتمند هستند و نسبت به تایمرهای واقعی حلقه/انتظار بیشتری دارند. میتوان از آنها برای موارد اساسی مانند انیمیشن منوها تا بهروزرسانیهای وضعیت برنامهریزیشده استفاده کرد.
مختصات اغلب توسط توسعه دهندگان جدید فراموش می شوند و به سادگی به کشیدن مؤلفه به مکان مناسب تکیه می کنند. اما استفاده از پارامترهای X و Y میتواند دقت را افزایش دهد و با بهروزرسانی پویا همه مؤلفهها در زمان صرفهجویی کند.
علاوه بر این، کار در LowCode اغلب به راهحلهای نوآورانه نیاز دارد، و از آنجایی که Power Apps برای بازیها ساخته نشدهاند، قدرت نوآوری شما را به حداکثر میرساند.
بنابراین اکنون بخش های خسته کننده به پایان رسیده است، بیایید برنامه Flappy را بسازیم.
من در راهنماها بهترین نیستم، بنابراین در این وبلاگ فقط قصد دارم در مورد 3 حوزه اصلی پیچیدگی و آنچه به من کمک کرد یاد بگیرم صحبت کنم. مطمئنم هر چیز دیگری را میتوانید بفهمید (نمرات، بازی تمام شده، راهاندازی مجدد)، و اگر میخواهید بدانید که چگونه آن را بدانید، یک کپی از برنامه را در Github خود آپلود میکنم، در پایین وبلاگ پیوند دهید.
1. اسکرول بی نهایت
چگونه می توانیم نه تنها انیمیشن، بلکه حرکت ادامه دار را بدون کدگذاری/ایجاد همه چیز ایجاد کنیم. حرکت خیلی بد نبود، با استفاده از تایمرها در یک حلقه میتوانیم x و y لولههای خود را بهروزرسانی کنیم.
برای اسکرول بی نهایت، من یک شی با 4 مقدار ایجاد کردم، اینها اسلات های ما هستند (آن را به عنوان یک تسمه با سطل/شاخه ها در نظر بگیرید). در شروع تایمر، هر یک از شکاف ها را با به روز رسانی مقدار x جابجا می کنیم. هنگامی که یک شکاف از 10- عبور کرد، اسلات به آخرین شکاف + شکاف تنظیم شده به روز می شود.
شکاف 1 = شکاف 4 + شکاف
شکاف 2 = شکاف 1 + شکاف
شکاف 3 = شکاف 2 + شکاف
شکاف 4 = شکاف 3 + شکاف
شیار جابجایی Power FX 1 به بعد از شکاف 4
Set(voSlots,{
vi1:voSlots.vi4+viGap,
vi2:voSlots.vi2,
vi3:voSlots.vi3,
vi4:voSlots.vi4
}
);
2. تشخیص برخورد
تشخیص برخورد همه چیز در مورد دانستن نشانه های اقلام شما و سپس ریاضی برای بررسی اینکه آیا آنها با هم تداخل دارند. نمودار زیر بهترین رویکرد من برای نشان دادن آن است (هنوز بهترین روشی که من می دانم نیست).
من از پارامترهای x و y برای بدست آوردن سمت چپ بالا استفاده می کنم، سپس عرض و ارتفاع را اضافه می کنم تا پایین سمت راست (و 2 گوشه دیگر با و بدون ارتفاع و عرض). اکنون مقادیری داریم که میتوانیم آنها را بررسی کنیم، بنابراین برای لوله پایین اگر:
- عرض اسپرایت X+ از لوله های X بیشتر است
- ارتفاع اسپرایت Y+ از لوله های Y بیشتر است
- sprites X کمتر از لوله X + عرض است
تاثیر داریم
در Power FX یعنی.
imFlap.X+imFlap.Width>pipe1.X&&imFlap.Y+imFlap.Height>pipe1.Y&&imFlap.X<pipe1.X+pipe1.Width
برای لوله بالایی Y به sprites Y و لوله Y+height تغییر می کند
تکرار برای تمام لوله ها
3. تایمر
در برنامه ای که من از 2 تایمر استفاده می کنم، هر دو به صورت پیوسته تنظیم شده اند، بنابراین در یک حلقه بی پایان قرار دارند. من 2 دارم زیرا 1 همیشه مدت زمان یکسانی خواهد داشت (1 میلی ثانیه) و دومی به تدریج از مدت زمان کاهش می یابد (سرعت حرکت لوله ها به صورت افقی).
با استفاده از OnTimerStart می توانیم متغیرها را به روز کنیم / تعاملات را بررسی کنیم.
تایمر 1 (tiSet)
//// if sprite above ground move down 10 px
If(viVertical<App.Height-viBase-imFlap.Height,
Set(viVertical,viVertical+10);
//impact check bottom pipe 1 - pipe past flapEnd & flap below pipe & pipe not past flap start
If((imFlap.X+imFlap.Width>pipe1.X&&imFlap.Y+imFlap.Height>pipe1.Y&&imFlap.X<pipe1.X+pipe1.Width&&!vbCheat)
||
//impact check top pipe 1 (imFlap.X+imFlap.Width>pipeT1.X&&imFlap.Y<pipeT1.Y+pipeT1.Height&&imFlap.X<pipeT1.X+pipeT1.Width) ,
Set(vbGameOver,true);
);
//impact check pipe 2 If((imFlap.X+imFlap.Width>pipe2.X&&imFlap.Y+imFlap.Height>pipe2.Y&&imFlap.X<pipe2.X+pipe2.Width&&!vbCheat)
|| (imFlap.X+imFlap.Width>pipeT2.X&&imFlap.Y<pipeT2.Y+pipeT2.Height&&imFlap.X<pipeT2.X+pipeT2.Width) ,
Set(vbGameOver,true);
);
//impact check pipe 3 If((imFlap.X+imFlap.Width>pipe3.X&&imFlap.Y+imFlap.Height>pipe3.Y&&imFlap.X<pipe3.X+pipe3.Width&&!vbCheat)
|| (imFlap.X+imFlap.Width>pipeT3.X&&imFlap.Y<pipeT3.Y+pipeT3.Height&&imFlap.X<pipeT3.X+pipeT3.Width) ,
Set(vbGameOver,true);
);
//impact check pipe 4 If((imFlap.X+imFlap.Width>pipe4.X&&imFlap.Y+imFlap.Height>pipe4.Y&&imFlap.X<pipe4.X+pipe4.Width&&!vbCheat)
|| (imFlap.X+imFlap.Width>pipeT4.X&&imFlap.Y<pipeT4.Y+pipeT4.Height&&imFlap.X<pipeT4.X+pipeT4.Width) ,
Set(vbGameOver,true);
);
)
تایمر 2 (tiMove)
If(Not(vbGameOver),
//// sets pipe position
Set(voSlots,{
vi1:voSlots.vi1-10,
vi2:voSlots.vi2-10,
vi3:voSlots.vi3-10,
vi4:voSlots.vi4-10
}
);
//// sets score
Set(viScore,viScore+1);
//// if pipe 1 passes off side
If(voSlots.vi1<-10,
////move to after last pipe
Set(voSlots,{
vi1:voSlots.vi4+viGap,
vi2:voSlots.vi2,
vi3:voSlots.vi3,
vi4:voSlots.vi4
}
);
//// randomly positions pipe gap
Set(voPipes,{
viPipe1:RandBetween(130,App.Height-100-(viGap+10)),
viPipe2:voPipes.viPipe2,
viPipe3:voPipes.viPipe3,
viPipe4:voPipes.viPipe4
}
);
);
//// if pipe 2 passes off side (same as pipe 1)
If(voSlots.vi2<-10,
Set(voSlots,{
vi1:voSlots.vi1,
vi2:voSlots.vi1+viGap,
vi3:voSlots.vi3,
vi4:voSlots.vi4
}
);
Set(voPipes,{
viPipe1:voPipes.viPipe1,
viPipe2:RandBetween(130,App.Height-100-(viGap+10)),
viPipe3:voPipes.viPipe3,
viPipe4:voPipes.viPipe4
}
);
);
//// if pipe 3 passes off side (same as pipe 1)
If(voSlots.vi3<-10,
Set(voSlots,{
vi1:voSlots.vi1,
vi2:voSlots.vi2,
vi3:voSlots.vi2+viGap,
vi4:voSlots.vi4
}
);
Set(voPipes,{
viPipe1:voPipes.viPipe1,
viPipe2:voPipes.viPipe2,
viPipe3:RandBetween(130,App.Height-100-(viGap+10)),
viPipe4:voPipes.viPipe4
}
);
);
//// if pipe 4 passes off side (same as pipe 1)
If(voSlots.vi4<-10,
Set(voSlots,{
vi1:voSlots.vi1,
vi2:voSlots.vi2,
vi3:voSlots.vi3,
vi4:voSlots.vi3+viGap
}
);
Set(voPipes,{
viPipe1:voPipes.viPipe1,
viPipe2:voPipes.viPipe2,
viPipe3:voPipes.viPipe3,
viPipe4:RandBetween(130,App.Height-100-(viGap+10))
}
);
);
//// if score is block of 200 next level
If(Mod(viScore,200)=0,
Set(viLevel,viLevel+1);
//// if level block of 2 increase speed
If(Mod(viScore,2)=0 && viSpeed>0,Set(viSpeed,viSpeed-1));
//// decrease gap until 280 px
If(viGap>280,Set(viGap,viGap-10));
);
)
همانطور که می بینید این احتمالا کارآمدترین راه برای انجام این کار نیست، اما کار می کند، و از منظر یادگیری، من تجربیات ارزشمندی در این زمینه به دست آوردم:
- موقعیت نسبی اجزاء
- انیمیشن (به عنوان مثال برای منوی کشویی)
- تکرار اعتبارسنجی (مثلاً بررسی دوره ای برای به روز رسانی خارجی)
- حلقه زدن عمومی (مثلاً کاری را 100 بار انجام دهید)
پیوند به صادرات: https://github.com/wyattdave/Power-Platform