From 9f30f656bc99bc7c6da90ca5a1575f64e6dab6d7 Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 18 Jul 2013 15:13:58 +0800 Subject: [PATCH] 46 tests with assets and javascript code. --- Tests/assets/buttons/arrow-button.png | Bin 0 -> 2341 bytes Tests/assets/buttons/baddie-buttons.png | Bin 2514 -> 4447 bytes Tests/assets/buttons/follow-style-button.png | Bin 8461 -> 10151 bytes Tests/assets/buttons/number-buttons-90x90.png | Bin 0 -> 4364 bytes Tests/assets/buttons/number-buttons.png | Bin 0 -> 8982 bytes Tests/assets/buttons/revive-button.png | Bin 0 -> 1547 bytes Tests/assets/buttons/spacebar.png | Bin 0 -> 3620 bytes Tests/assets/sprites/robot/arm-l.png | Bin 0 -> 2751 bytes Tests/assets/sprites/robot/arm-r.png | Bin 0 -> 2773 bytes Tests/assets/sprites/robot/body.png | Bin 0 -> 10524 bytes Tests/assets/sprites/robot/eye.png | Bin 0 -> 2949 bytes Tests/assets/sprites/robot/leg-l.png | Bin 0 -> 3789 bytes Tests/assets/sprites/robot/leg-r.png | Bin 0 -> 4175 bytes Tests/assets/sprites/space-baddie-purple.png | Bin 0 -> 559 bytes Tests/assets/sprites/x.png | Bin 0 -> 2559 bytes Tests/assets/tests/blue-circle.png | Bin 7481 -> 9165 bytes Tests/assets/tests/cloud-big-2x.png | Bin 2465 -> 4149 bytes Tests/assets/tests/cloud-big.png | Bin 1548 -> 3232 bytes Tests/assets/tests/cloud-narrow-2x.png | Bin 1210 -> 2894 bytes Tests/assets/tests/cloud-narrow.png | Bin 1000 -> 2684 bytes Tests/assets/tests/cloud-small-2x.png | Bin 1517 -> 3201 bytes Tests/assets/tests/cloud-small.png | Bin 1176 -> 2860 bytes Tests/assets/tests/ground-2x.png | Bin 1227 -> 2911 bytes Tests/assets/tests/ground.png | Bin 593 -> 2277 bytes Tests/assets/tests/magenta-circle.png | Bin 7225 -> 8909 bytes Tests/assets/tests/radar-surface.png | Bin 9225 -> 10909 bytes Tests/assets/tests/river-2x.png | Bin 794 -> 2478 bytes Tests/assets/tests/river.png | Bin 346 -> 2030 bytes Tests/assets/tests/sky-2x.png | Bin 2939 -> 4623 bytes Tests/assets/tests/sky.png | Bin 1078 -> 2762 bytes Tests/assets/tests/tween/PHASER.png | Bin 0 -> 3967 bytes Tests/assets/tests/tween/ribbon.png | Bin 0 -> 1165 bytes Tests/assets/tests/tween/shadow.png | Bin 0 -> 1251 bytes Tests/assets/tests/yellow-circle.png | Bin 7459 -> 9143 bytes Tests/assets/tiles/ground-tile.png | Bin 1007 -> 2697 bytes Tests/cameras/camera fade.js | 42 ----- Tests/cameras/camera fx 1.js | 43 +++++ Tests/cameras/camera fx 2.js | 44 +++++ Tests/cameras/camera fx 3.js | 44 +++++ Tests/cameras/camera scale.js | 72 +++++++++ Tests/cameras/hide from camera.js | 53 ++++++ Tests/cameras/multi camera.js | 17 +- Tests/cameras/radar template.js | 47 ------ Tests/groups/add to group 1.js | 40 +++++ Tests/groups/add to group 2.js | 37 +++++ Tests/groups/bring to top.js | 30 ++++ Tests/groups/call all.js | 37 +++++ Tests/groups/direct render.js | 38 +++++ Tests/groups/for each.js | 35 ++++ Tests/groups/get first 1.js | 44 +++++ Tests/groups/get first 2.js | 49 ++++++ Tests/groups/get first 3.js | 58 +++++++ Tests/groups/group as layer.js | 74 +++++++++ Tests/groups/group texture.js | 21 +++ Tests/groups/group transform 1.js | 65 ++++++++ Tests/groups/group transform 2.js | 48 ++++++ Tests/groups/group transform 3.js | 51 ++++++ Tests/groups/recycle 1.js | 46 ++++++ Tests/groups/recycle 2.js | 49 ++++++ Tests/groups/remove.js | 65 ++++++++ Tests/groups/replace.js | 66 ++++++++ Tests/groups/set all.js | 31 ++++ Tests/groups/sort 1.js | 46 ++++++ Tests/groups/sort 2.js | 57 +++++++ Tests/groups/sub groups.js | 60 +++++++ Tests/input/drop limitation.js | 46 ++++++ Tests/input/keyboard 1.js | 79 +++++++++ Tests/input/keyboard 2.js | 106 ++++++++++++ Tests/misc/color utils 1.js | 49 ++++++ Tests/misc/color utils 2.js | 57 +++++++ Tests/misc/color utils 3.js | 151 ++++++++++++++++++ Tests/misc/dynamic texture 1.js | 23 +++ Tests/misc/dynamic texture 2.js | 27 ++++ Tests/misc/dynamic texture 3.js | 32 ++++ Tests/misc/rectangle utils 1.js | 10 ++ Tests/sprites/out of screen.js | 29 ++++ Tests/sprites/rotate around.js | 40 +++++ Tests/tweens/easing example 1.js | 22 +++ Tests/tweens/easing example 2.js | 27 ++++ Tests/tweens/easing example 3.js | 27 ++++ Tests/tweens/easing example 4.js | 37 +++++ Tests/tweens/easing example 5.js | 38 +++++ Tests/tweens/easing example 6.js | 53 ++++++ 83 files changed, 2168 insertions(+), 94 deletions(-) create mode 100644 Tests/assets/buttons/arrow-button.png create mode 100644 Tests/assets/buttons/number-buttons-90x90.png create mode 100644 Tests/assets/buttons/number-buttons.png create mode 100644 Tests/assets/buttons/revive-button.png create mode 100644 Tests/assets/buttons/spacebar.png create mode 100644 Tests/assets/sprites/robot/arm-l.png create mode 100644 Tests/assets/sprites/robot/arm-r.png create mode 100644 Tests/assets/sprites/robot/body.png create mode 100644 Tests/assets/sprites/robot/eye.png create mode 100644 Tests/assets/sprites/robot/leg-l.png create mode 100644 Tests/assets/sprites/robot/leg-r.png create mode 100644 Tests/assets/sprites/space-baddie-purple.png create mode 100644 Tests/assets/sprites/x.png create mode 100644 Tests/assets/tests/tween/PHASER.png create mode 100644 Tests/assets/tests/tween/ribbon.png create mode 100644 Tests/assets/tests/tween/shadow.png delete mode 100644 Tests/cameras/camera fade.js create mode 100644 Tests/cameras/camera fx 1.js create mode 100644 Tests/cameras/camera fx 2.js create mode 100644 Tests/cameras/camera fx 3.js create mode 100644 Tests/cameras/camera scale.js create mode 100644 Tests/cameras/hide from camera.js delete mode 100644 Tests/cameras/radar template.js create mode 100644 Tests/groups/add to group 1.js create mode 100644 Tests/groups/add to group 2.js create mode 100644 Tests/groups/bring to top.js create mode 100644 Tests/groups/call all.js create mode 100644 Tests/groups/direct render.js create mode 100644 Tests/groups/for each.js create mode 100644 Tests/groups/get first 1.js create mode 100644 Tests/groups/get first 2.js create mode 100644 Tests/groups/get first 3.js create mode 100644 Tests/groups/group as layer.js create mode 100644 Tests/groups/group texture.js create mode 100644 Tests/groups/group transform 1.js create mode 100644 Tests/groups/group transform 2.js create mode 100644 Tests/groups/group transform 3.js create mode 100644 Tests/groups/recycle 1.js create mode 100644 Tests/groups/recycle 2.js create mode 100644 Tests/groups/remove.js create mode 100644 Tests/groups/replace.js create mode 100644 Tests/groups/set all.js create mode 100644 Tests/groups/sort 1.js create mode 100644 Tests/groups/sort 2.js create mode 100644 Tests/groups/sub groups.js create mode 100644 Tests/input/drop limitation.js create mode 100644 Tests/input/keyboard 1.js create mode 100644 Tests/input/keyboard 2.js create mode 100644 Tests/misc/color utils 1.js create mode 100644 Tests/misc/color utils 2.js create mode 100644 Tests/misc/color utils 3.js create mode 100644 Tests/misc/dynamic texture 1.js create mode 100644 Tests/misc/dynamic texture 2.js create mode 100644 Tests/misc/dynamic texture 3.js create mode 100644 Tests/misc/rectangle utils 1.js create mode 100644 Tests/sprites/out of screen.js create mode 100644 Tests/sprites/rotate around.js create mode 100644 Tests/tweens/easing example 1.js create mode 100644 Tests/tweens/easing example 2.js create mode 100644 Tests/tweens/easing example 3.js create mode 100644 Tests/tweens/easing example 4.js create mode 100644 Tests/tweens/easing example 5.js create mode 100644 Tests/tweens/easing example 6.js diff --git a/Tests/assets/buttons/arrow-button.png b/Tests/assets/buttons/arrow-button.png new file mode 100644 index 0000000000000000000000000000000000000000..48dedb62256e3f950e23f97b7b7aba19836806e8 GIT binary patch literal 2341 zcmV+=3EK9FP)a(=+a>uI{eBRMr1WEk|)OkgD_foT~FzHDeG2LAEK1qP}>>7>8&~V}Xc( znhLlj@$5vgFdoOEFZP!=tRFJ4FC{vQ$By*GUTpc=qroM_Bw>=YWJEM<&) z9y|l_WhkD^K-hh7#5R9=!VaFldh>U_ximk3LcRkyHy+>KKmK*_-rMZv7iZbvf|Ro( z@!SwYxC;RN3YvZlUS=t@|MXvWyEU2}0%P0Kn1!k_bL3OE)f^QI)0JVpu}}=uWx0LrapZ?~fk;EwbL@Y1ygu zc>u5yfQhIa9bP^s%F|P_KSEh>-Sx7RI~*863|>ETcAvkRf_LY)XT?4j02%=>(b^~P zPeG&&8;M3Sh)$NJJqH6~DK0lBGWe*}T>b9X!tg=nMRP!NL31Jz&EdpVHw||Tz5$zU zvIMfprmUB4D-`~3EM!1vA)pFoz_cg}QZ}Tls3=P}U20o!N5G?Y!cbLiuHSu>*;W96 z-2w18<()gtcCMVyD@&b@cOKzi^Yj@JLd<0X#lz+ zSKTz+BJcuM(ZvD)?U!qA8g2=EAv54&0f27EB{vQ4ICxZU8gsD#K)2+An}&B3ynxl< zVgZ0|%4RoBuVdgTT`Wmk0WKCOEt}mmyhHE}nBs7(2^R}8p!?Er({$<(Fz*0(jk#C= zpg*$3P19=;yy+G1OyOdoj0YDBR7Q!LhGHnkP1EX?`n)ChT5+);1Nx~hyJ>jC;Psip z#X@ZbT1X?~0l>P-QtBTn+7^wwX?R261u=M;7qj}}VgZ2NkcOM)P7LCK^=rVmtuU6s zM=qA-TMqyq)R-vNDCQ{kBwCBthoQ9@FusPIvR)u7H`nhz^uDct4A`z{mvraKM|o}< z%7%BW01z$~0BEDya?@~w;L#RSvb&kN&fEU^=``(QcM|}1QM4u`gZK4|XH@IMO769l&S(@XfIba120do!T0tOcg0EFgb%1y&n>pV7VD@gBRp^S&N0s!>6 zETy&uWkf|;y3G`A3od~VE*1a?X{w5whG&$UaIpYDsD#S6X?jVy8DH_Pyo-glWC0%p zNfv#A4UH@HC+REodAz%MTei+axrq!2)l!zyn(*shyj{8-|HesV#Rpu!_-TIiU6QL(tI0Jq5ESBod={37SA%99m_vDxez_Y#$SCl{hvhJ0&=O`q?o4ImcW-B zMOrR;c~j{V@n~Bh1Nxw%ZH1z$d?yUr768yiHT6zS1BT0;0AS1N(yPfD1uz9- zpP_9502@|QZH1z0{2lSrLx`NO(m$4ExM^sB_P+cy?@Dk8DzMHwKaD8~e938r)u!QV zB?D%x57D+j2CPMGv=yuZFa;vo768zg+H5OW31A9Dv@HN&#d5RVwu03Frpi*>$PECs zW#Wcv0C*C_fGOG*lm!Vy-`W-ca9-3_An+9BW}|HdX8=rrNb5r7TbX=11PlOY(mWiy z-p0;m5%s!0&~e*>4gqJjEpQ0fzj9OKaO{ovaO|k_@om*l|JnMG%F;pR$a~rtB^{k%k z*}&8Ka7$LQOs-u}x!$3Uisp>wP5~dy`LfOMS)TzHvSF^9X8rD?%4I1sV7(cT%FP@f z@Q8K9#o~PiT*!v|;<+k*xz1)=&~u^*B}4A*s~^MU-9gcu(A>}*N#=*QAnHJ}rgdrm0Ufwlu~x)~2s`M+UU3EG=#F zMg}aG0c|%;13>eE(`O}2-T<%|fUcOPQG%K;OJVY^x!iQgG!51JQ+!E0JCQ_(FJ4E3 zA09?*_uh%`VeA&hLyQrP6^)rfJ8`QBg67Lo@r-3rGyd}*0GI<{))=L~9hd$PqztMJ zN_>%qFjiVRjNhIGL50fFApool;1J~|R3*Sx#Ipwg3j??p8{{F#NT3a}%2JF=X1O^) z#6WeGr6B;60vMv)gqjGtASp{R7RTjgfXINF4!$*v0mh(;{uf{XHk&8>D+`sU00000 LNkvXXu0mjfk-9Tf literal 0 HcmV?d00001 diff --git a/Tests/assets/buttons/baddie-buttons.png b/Tests/assets/buttons/baddie-buttons.png index a16940fcb62d2721bca22d1efa7cefe9e64535e7..278274186c89cf94c6030c47e629146a56e066fa 100644 GIT binary patch delta 1957 zcmb_d&649Z5avd)S8kky=K=+`Y{yPwmp!{P$!zUZVc41(YNm>lA}fh%{D-7WGP4KQ zz5#E*E3jt{yaO-5Bk&5e?8G=BEKr3{mel>V`s+{Kzkd9i@xw{|;ah%lr|y!kLR#h) zNy{0tHhJ}d{-WobiqLze(2Om35gWh%{ELBj8XLEMVkgyvE%~b_!LFaazM)U|G)Rr- zr_Z8I2mm=#1a0!Hkl`ja8r%^6^)@z8W1{Y3qdwCS(OvQyO-jL#Z}}#*9S@BH%kiDD z?SF|}+ZkZn!**agZfK7~*FoFQIDLj9kC0_2SjEOtsVc;Hy7K&`lqLwEbqXn5VNd>PIFAJnW$*f$d*f8K6?Q+ShE52AN*~KX2mNnoH zZ%Bn5%f_Oy!3Um4eCnRlfpwA`vG1AZ`5wMh7Y}gRZRSUBC2t-phOd|`SAw#O2Ue*1 zq#re<9YD1bSwmb(G0RvEwh~fv_F`UEih59NUAoQ_2&9dtR4MLTG|V)TXhFd7s&0r1 z6C&;z=;|fOBx`O2hhYL@Nr@s;q5xjYz_drEdy<3{WDbkH&|MeD1sCqsaIU8Qam zLdTLmRAFcQ-lTgu*Z(%@2Jm&Lqn9>=~7zsL#CZc9gT4 delta 11 ScmcbwbV+!E^5zZf>p1}*YXrOi diff --git a/Tests/assets/buttons/follow-style-button.png b/Tests/assets/buttons/follow-style-button.png index 74a0481ca5a766ebfab09e1f559aad8f58fa08ee..e6ba55626067be5a6db1f1e709580c41c84cb0a3 100644 GIT binary patch delta 1712 zcmb7F&2Hm15bmjmyhBk|L4g*nM9Hy}l^}bY*jcQBs=G*1Bv+ao%S7~7l8&8R^aNe( zTlCy>4}GBmeSsc2qAb_;2D_+EB02NT$B!R}zh3mtn;kU(R%1u+z9`hY3dLgV(y1decn_gcgb5aE)|f0H*mQh^vPN11%u#)Klq;X z{NRN8ed>p9(2M*RQ7<5y&lw$&Sc!RbHF>qEhNGdgFs6zqU9DE$>clHmN`o*Aso$f$ zo{JH#ek=@IyG5;!8;llY0-7sX8Ce#j0cEptX@-u2>!jODUR}v_Vf3~}p*O0bess+$ z8hAccHiqtI3cgLLmMXKP1g|M%kV9cK<^_8R)5`EZD%t3=O+|LxrfMVTZiFps_1*)) z{B7=YOzXqwB>8AO&GWqsZ7v^BqK)3qRiOIju>yJpx?Cy_mk+qvV=D}DyQ-~FQNq+c zps!xBOhdOAC1@7G44W{63N$uw{WCY{-!vZZqm#jR_~ZLse3C@dLK{|am`;Wc!h2GR zsPDtstOuuV5Cows!v4SwPtgWvry=SOAVermb}%L-Up6PwVTey*dN#4F3qw3wH6cl$B&GG{WoEfqGEO0FTRBri1z^F!J!cz8SQ8I%3A zTK=pySsM>4Hk(OkK=Jit4`ld6Un4yM~J-|5i-VhGITV1 z0MRn&mL!_B{$FnG+_6_-wE)Eb`0YS5wRChjH!G&#JVl2Oo%7nZ-wP#03S>Mc-`vVV zlq>!1K2?t1N}CHxn+D$8$&D)H&|@E{)CTOdR+O%t!=63*`Y5K`_X6LJ?TZ5iR#W(% OOuzW&%iq8Hi~I|?UJ$zg delta 11 ScmZ4P-|I9%dGlVjR|)_gjs*Dt diff --git a/Tests/assets/buttons/number-buttons-90x90.png b/Tests/assets/buttons/number-buttons-90x90.png new file mode 100644 index 0000000000000000000000000000000000000000..f4179877ce3b6be2c9bff6f666eab8e4ff3be95b GIT binary patch literal 4364 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 BFAx9# literal 0 HcmV?d00001 diff --git a/Tests/assets/buttons/number-buttons.png b/Tests/assets/buttons/number-buttons.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf84b8fbca9707c72e079a0172a6786d34ec6ac GIT binary patch literal 8982 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 Bvibl3 literal 0 HcmV?d00001 diff --git a/Tests/assets/buttons/revive-button.png b/Tests/assets/buttons/revive-button.png new file mode 100644 index 0000000000000000000000000000000000000000..13ce5d7a59aa85d679402d56c71d4f7ceb668eaf GIT binary patch literal 1547 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 literal 0 HcmV?d00001 diff --git a/Tests/assets/sprites/robot/arm-r.png b/Tests/assets/sprites/robot/arm-r.png new file mode 100644 index 0000000000000000000000000000000000000000..a10b33511dd9ad2c268d675e057f78ca4d6beb0d GIT binary patch literal 2773 zcmZ9OdpwivAICk+VvC00FAnlXpaZ(@#fK4s>x)-WOxrk*Jk!z!nBa26x#XQWIf z%R{A%oJKjH>S04l&$Bs%=y&V+?{~jm*L_{D@AbVt*ZcFmuGjs#Z+f~rLZmdLL_|a& zPG~!Cpw9u?)*dkM_Th5ufKHuck0trUgpn>0;)o)s(3oH%)G3+}PV^=cLdmgRM5Ks_ z)Fmf7)Tuwlzub4bIF43IvoF&MRY}Y%R1XVdiq0yh*1lYkZJ}@*dOBs%nClIqol5FJt;>9Bhou3HktM?wqtd4e@CVV zs9GZ&n-jh28Gll}GPsnP7~q0XDU(m^Gw=RP%vIZCNh~2(J=~AdXv{LMS+S!z$c15a z4x-nugb^b(Q%L<;KA7`%wBbI!khf#sDN-i4_e=QYvW?5e)=BGN>$B98TW%l;0OebNIMPWOAW?BT+Ptk>sauj zf+HL*_Bbl#nQMOngT^}yNg<``1dM_rrV7^LSrvyw>3Z^<(bo6 zOVkOgU69>83W=+8IfZl;#dd|EL+C;OH~&LMspS`q^dRao1#5L1Iz_y@Ei3jmoK6RJ zMit2@mETM`J{DM8_MCV2ZN#pxbW~BiH7H^h3Sl2S?g_|KfYIJPg9x)RN*%~mskZAh zLwVtyFRP*Ep?J3h#1_a7cBodLGNN@fmn{se zO5|tG89KS|c$ z+fmGSXR@EKjrbRLOrAP<3;w)=b=bFu0l&XzJ7Pxa zviV4_!Sf4W*yVTV3GUN(S1Ea|jqqH8CA1%AO$@eFv)_t6aq3ExBw<>g-vJE}{ z(vSggYQVP9rdi z0_VO}nV{F~BUy|E?~7Vh#6{IZegP}}Tgrcp?4A^f=$V9}OBWV}=V`ECVvydg%A&iq zW~1_>f+tLYP{jiK`E&D4jVxJP?vO62YnhWaq0d68E*MP*Uxu)(N7~KaW9>7OoKg7@ zcG#88UAqAQgFbgi>O4;P^jUsyY3CwFSnxFO}azP5*h>F9?rs`(6Zm zqGhPg_Pj_#wJRV0n~umH-aLQuSr9x2qfO3APd`;^O@4G0%Mom$#qU_^h5q>d&+N`Q zQ}(SKZ7W7=FSXt6W@iH#Ui0HwC`;Mlk@}8XgJUwjLo=%R2{KOF#Xu!P^M?7Hd#}$b zFGl&>b^Rtnv)^mv@`o3VSwH6rsF3dB-iP0JwlBxV#d&8sJlG!5xhu&p7ii{BivHLT z&Oe$tgPbng$UV?+58SjZM?^pf>uj7H2N{JuX=OFzr@hZKmw}jA8hP@qclb)y7$$%~ ze*C>$V&nce5D%ohyGU$DP8B<iZwG16r1QP%*F38=8mdpTUjApfr-76(z+5WV z6OI4)bTX)9#ozwn`|5%9zWMmC-##Hdtc99O4fR7$@NFqO0|gv6EauKvn6%I{fr*gk z*N+_KqnxNbF8}t!q#iL=riq$N9l}G0&^*xF3mL8W{05(7=;1(67s}@$Gvy*YWe9Ui|z*2_7 zil<$8HYdY_3OGKz2rAM42L0GZk7a?bHI{`t1s$HGkdbLT7TE?yP9Yni^5_-!P1&_b z!DaM?4y0r{PYoj!_$C?V%ss1reL`1)I3!ut){=7$U;^?= zBFM_4&M?@ylG#_tR33HiW*72goZRlhst`{vE88Cmbv1JLQ)SzkU%na^_b&farBLt$ z;nCmDRC7%43O#a9nsE1M#`h??O)0_~pu=z}kU(T@f u8yd&!8a~*fzz-yaV28Bm0{m}a5%>kd?@eDK4S@et5hr_hyE>aQDgOhLEIE(> literal 0 HcmV?d00001 diff --git a/Tests/assets/sprites/robot/body.png b/Tests/assets/sprites/robot/body.png new file mode 100644 index 0000000000000000000000000000000000000000..527a84b9decbba05654e2c560577433b7bdb1377 GIT binary patch literal 10524 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 obNd#*OKaXj;sQ>@~ literal 0 HcmV?d00001 diff --git a/Tests/assets/sprites/robot/eye.png b/Tests/assets/sprites/robot/eye.png new file mode 100644 index 0000000000000000000000000000000000000000..d872e67647fca3b9c94d3f21179b2d1c922b7b96 GIT binary patch literal 2949 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 literal 0 HcmV?d00001 diff --git a/Tests/assets/sprites/robot/leg-l.png b/Tests/assets/sprites/robot/leg-l.png new file mode 100644 index 0000000000000000000000000000000000000000..b1c8529eff2bdb75f8afd893481a2fe7cb550fd1 GIT binary patch literal 3789 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 DVnIUL literal 0 HcmV?d00001 diff --git a/Tests/assets/sprites/robot/leg-r.png b/Tests/assets/sprites/robot/leg-r.png new file mode 100644 index 0000000000000000000000000000000000000000..10f0d0bbe5e2c501ec9b92c7467e4cac42f2a000 GIT binary patch literal 4175 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 N)$A{L|J~Q$d;;B<4*~!H delta 11 ScmX@>zSC-g^5*SqVzK}qX#`#X diff --git a/Tests/assets/tests/cloud-big-2x.png b/Tests/assets/tests/cloud-big-2x.png index bdc5d1eae39295e87ef0932fcc0d2c804abf5208..e3ec533f575832f7f6cf9789e9f88b6e935cf507 100644 GIT binary patch delta 1706 zcmb7F%W~T`6zwY0WZMrYN^ja}NszJ>n;g=qBRLb#lu5>R$KHYfL}AQV0LYTPVU}5D zCSQ#o`h?!?v=82jvkQHZ{uR->^a!gn z?k&sa2lK^GmJOkoT%(NVqDq{9KmX$(!4l_ul=^8i<}>m9M)K2}7iaY5l7`HAa_}f# zMgUN9P0+F|Ditjg$Kpot-%Mi%*${o1IPIaSh%VAsXk1Hj-SE~f46=rp}I9+%}-hSEn@AW^UPT@|o8yKXo>;i{fX%8#yKvpZH8;C9klp`w(? zOAfwzMheAOi%|+@VMLP&(V&2_f$Q(N!SKv@VE1?U_5D?FnnuT!(xjsNc#=4P?Fqr6 zESv`6a5``q2?F==-f-aVQ<}Mtvy2Tx#@f;T?r;NRQq#FPjui&`4vbq6FX8Y-MT+l& zYH#teSjLbjW;9}74Qb~(n;sUr8+F4%7;i=esdCu2%lMSc3+)`7Hd1JgL`ib4(6pB5 z)r=@E5wCzBN17|GS}(@N>V@bF*wLy&jPD>}h*8OjuH^=)Q~r9J)WIny6?$6FNOf0< zKE6e^#wK38Y3pd*RT|gOuT&pb)7p09c8oYGi73u%2{%e3mb_>LtrgV&GdBw!w5B~{ zbSv#|+4fnzfhEZ@5RGU`w8_E_w!4bKPvEfULcy-D@7uOzhmh@y2pGd%k~oT7an>H;<{RnV=QgWDS32@J2m&)rJ_qxDg5i5bdQsEPCj*r*f4`i&mnf*hwr%*=Fr5HbB)bk`&36rp7W6eU+qRCl?)C zpj}{p!v2Mxd*~19p?{%6N_Oou2(~&HCOPxw@$n7$+o$h8f4A3u@{72<*4OEG5wA;+ zW%U%iRoQ%GzWBweVf+R(nL;kA#QFP=KOG{n#JL)#e%g#-E?(bDxVZoRlHcF(QRcij zeipAH1Sml>vMP&8MXSWIxDoz0)6^k0MBgM%dtxe*>+}s7*AmFk8@k*N2IO$$1;gOT zAAUnZKiH@Kfchgh2qXU}3Io!A&hax6%WM{%PhR!aaFjT6t(%C_<#OpQ_q|%?G#HIW z)DLMGx)|ZAyGpZ_TeWJ>V00i8P+W>ei@G8Pluhe}P8QzJi z=!!Ko@O&z54BgJm_$H-U$ikA!cuS#x5-P1QFW5;qYcwCAl17(JDzf7?Rq2dwN7%Af z?;Q}#-{d~Vw0#(zrccJxvfRl~`t%kh>h*rC0#%oH4bXE?^+Iwuy~WM$Sz(add25A= zQYLQzef5eJ3f7BJie|}}W)r4S0b>K#KXik^rSZW2*ZAxE>)Yz$UWeL!(n(dZD$AmU<+eX^MyH%HAeOU7!M$Rz{3|6 zD?SLSeZa?J8AFmd<5BjnA?;je)00AXqi$FT$7TB+iu*A5vL^+#Z@iwMrp(fibn8SVf}A&v*2NC z+A}5((tea}pVb>!vgs5>W12H-|& z)mOVbIi6o`T9cZq+jL_u2_Bp=&idSA`X7n9fUQdvb?fHvw#IjY;w$h)_fp_!^VUFx S)g-3F+(Tdv;T?V{E{y_RCHU>R7h>20d3fhG7Q1t#7qi$ zpOPE2tw|TERm~a69$Q2@8w_x)>B_KPO<9^$(TB~v8lxzBAiU#Tf9)HF>l%N4J1L;WY=|Ir8nxB z?ZcKfY)Ux3fy{M*>y&t_WKu{Xa|n}?T7mKwp1_18sS_F5D28k@e_8^5uEQS;r|J5) zJxQJ%HzAx%3JKM+kt*RtEXokm0Y#C8Y*@X!v#L}rT@wuqkc=T)LN--m8P=Us z#N87@k7AKIZ@kx}-QXZ}cZ5I!CTB>LvCsxAR|1%Tl>bwwq&6xJMg}o4e~(QuDQRh0 zdjy$S%#q5TIBl1kik!o+yBjWEtEjRzVm(nCr9{>)NB1O4k88ROs|kUX6}_{DcO|GI z;5kZ3)+#)5M3cLLoxB_fF?qdUl{hoW<#D8`c`CW2ZuYK9$H4&r4jiBl^2*R*hBk-% zGLAzZ?u!h`Rn^+sN+#{|`FNg>$K$C~3L#_|#`f*oM@L8Z@84f8mrtEKb>YGVSKbkU zQv%L0K$J;8=75p|L}JJbc)0a}Qpwp@a*map^Cicj{k&5;=9Kn1wSb3PA1IfdlV#^z z*|}VH9NI5C<#SH?q*Ducxb*=5jCsJ|fsp}*L3_pkyaDDJwSb3PAK-Z-&KnutK)hkl zjto9y@NuIS@Nnw`sg$ukWuTOyqzr?0#YiC|wce-&Jly&K0AdReArLtrinMbgfFfuS zYXJ|pJ^-P(7>XPec_@ms^CIL#xLB+OJly(#&nI#|G3*l~K2fASBKpFjj}vPF54S!* zzp?IWq&{CBJtGl*gWb2fA1wRf>ntS(o?aOPJEzU>A;kT9&w2}h8FT2r0qlMKVZv;* z6Ulhv`1F~L(`PTb^~gBU=}g4Lbn)z!@nrLdT+6o9)iZOh-`=A?KK0V!w!%|~H=bWH z_4%>iAM8$VIl3@0+FG7^Bm|m3!&q+C)|pcph)vR>){UW16CE{-{rSSIDi?q+y3W72 zg0e~D8n^xN(PJ~13I$F#9v5d<$AA5zw=MncE#}im*QQ5#%4`@jPL1c9uAtAq!Q70o zQmJa(00$q4?0BJG9IeSPuei(fF#-6@KNB2r?lupD~#||!kZu{R+@Vfy3 delta 11 ScmX>nwu^Iu^5*Sq(^&u<^aN%A diff --git a/Tests/assets/tests/cloud-narrow.png b/Tests/assets/tests/cloud-narrow.png index b2f112308f64912800158120f9a783f271b83fad..fb0428edc2b41a1766a1751563f279a563786eb9 100644 GIT binary patch literal 2684 zcmbtVZHOC17#`cI$Cbhr>JPQ*xGlC~lHH^^vRRUArm|F{ z-ML=wXs{f#w*EY;h|v3Szsf93LLqy{`MAZUDf#!GY8Wdmm>1p03qwymIu#_W`)?m_9a6$1{5r)hvau zX68w#QYteXfW-Dn8LLx-!aOPHMpC%`!C3+7T2k2Gn~^f*eln>K%-LjgZfH!Mn^NPN zu)VK4QBjzI5}_DYN=3s_DoMe!tFUj*76s^u(5a+=xI;Le8HW9)O<-@RH>gVCD2&BJ z;oh(;^#))>3U3pos3^sQ;fNy1N+b*$kI>f*6Sg*?jHGrooUvI_n5492?g%A0ZRYWbQ6rDz%w+X!;;4P@~u#9XMmNWg?Cc z$DFoRl0L|C-Q+oC-s+W9GT5FXtfhA0qC=XPl3{(KVTx0jvix}q1f^Io93A89zwIeD zN>WoelTii@hhjq|gQ=vzOowz`Q*cC%5hBNf@ra}au^Np8aZJU*7{M`|&tnqH_xenv zOm&*CMbm`#?@jDw3$Giv_@7PjUW5rvRkYi^=<(_uii)&8t51tMZ%d_!jRMO$sF4Xg zU8F+aXxY{&fqDrShyy1~8xBumhuDx9EIa}!ai|kYh`h;*dc(dqYdT1X9b({dsW!pX zw0$v+601F?zEOe=xYL}(Mkg0ddC!I4T*BYbN`Ku^zsdHgN(YSz)7ysaGJ3j%_2Pch zW(P@GuaII{S51ex-%;Q|i0w&lf#Dr$IlH0MXkDAN1FIOODyCdX6htqlgtsS@0o`Fa z&)zL*FE|Lj9igy**%^`)9DI;y^#GcX{(t(E^+w&n%p_sv@3JXcl8!k+XRu9BfpvEC zwB2dzT7kg+ez6a>~+^!6J5qd;{5-%-k{R_C!J zn%xaz>vCX|*7ZUR=FDc7$KLVS5&)~g@Sd@>cLgnjWdN3!`M_Q|{+Z*C%YHf6<%9QP zpGcYSmq=x+_)pQq^@iU2>}{ZgtUZ2YlSlK&|GUueld%?zNiha=PZ$ zF1oezZad)Pb_M{jHUdiqRt{Jer`Faz^@+Uu$n!nx&m7lBH(z=1?CZZjx^evT z7uM`sN}hSE=hMaY3%8uE#JOMf*TAVSKFO}SzZ`h!%Cj_wbi+wYlj}*{n@jpu3ueQGr#NR1FO#6*pqwu%OhQz|2q03?*H@McP_ex pxjf&V)_IcE{dA07zlXR{_Y9z2S7% zWq+mXF8Txcl}r}-fnI==BDo$<%7sP%?mhS5;9TJS*S~-J=U)5OAL8OlU!|`jT9+PS z^^AL~viZV%@$*$f=rz}9#&c06&WFGMagbn%b2&`?w3+a^IJuMj{O*?vdUs97%z1wJ zBwj@TP;yPssw^rMtrExLM)2QEV+Yv~eVsV%k*SET(zj?*OOA%#(4~GbKnG(l7zQJM z_!NbHu#f!#_Q!4zM*b)Y1Jr-c;S&@~mPKdNmwh!DCC*&yCc=2RTzboWua-Fu#^W*e zLmY-KM7Zj<(q!dUt=cmf9mtd`Dn+A3T_FQXX7xfRjsxpxy_d2%6Zu@LO^r(Rs)2rZ zMH(D?qn!^d;=2odOuVFtBc!)<1?=6g{1uW1~$8Ag#m77traRt ziM;0EtCyrue7zW@U=~I+nGy{O7#q0$fg21ij0g6Af?wZX2d8OtS}9E`%1@_>1K6Gr zETVMAMj@kaFw8b%|+hsf>i$Xhx=ZzGaBT(Y4$nb+>H^(B@xACE#XFK#EKV*|S1q6iv diff --git a/Tests/assets/tests/cloud-small.png b/Tests/assets/tests/cloud-small.png index 14f5f8ae8a8319eda908910ad7d5338b0d22b099..d046639080d77db99d59917402821907f7818e99 100644 GIT binary patch literal 2860 zcmbtWYitxn9G{}{D1t-~5h08V#DaIXd&iY-*E{I8chCb4d%d2t1S8Ykx$CBPySI;W zrFt=7F!F^&d_;|+K`>aOVkGB-q+oa{e5jER7*QM3C?<#`e(@1?o!M*4o?J-Oz5hPu z|NFn^KQp(xZR6S*wexEM05f8(k#_QKqW9FB$n)j*cl-#z3D{=lHY-sfP z>-{`e?|}i%zk=n0EZ6Au2Lz560)9AlF{^4}$dJ=Qdt}X+GcpP@J(iUhShi3o_zEk0 znvr4sjg5^g7hnScFOl$?+f@todsTBWHBq(^!KP#=c}vk$NNu8St#tBsj^@G%cBROE_MgVpkX;`V+cU19eA8gyZoWi|29`=1nWQjTC6C*i{{v9ozGm zZO5k8XGl1@jpVx6aZ0?ED=Ea0(Thn-twC86k7Gid^ofit6hRirpSFOPYw-Gm9d!Oz z+((`qH!d8H3o+HSkSgI=B+L-gK1GoQf0|3nIEcLUT)^+;P%!A_r8Mtt@HaO2(@2&B zLBGpHM3egHT#TEL|GkL^$iyou%Km3lv=?DWmIV1)FFL$(hl*LNJSq>f3T;avi_{Fs z+AHHU>dRWps$||!EDV(#%3u?wH3PQwAQKx9t0X)OEo@q*FT~O&&njc~ozb|15L+b% z8kb~XWNC&grX4^peS7?*ckxXp#!4K1{k9kpm=ok~_rO>53Ftdvor zIizGaX$ILy@=8C><`qdZiTkDu1$<;pIui{4p_Venl*U@uK;7M>V#tz^MU_~Fb#e+h zYeHyMOp^2V8zt=o2cfef1QIaWL&A)Swqdy(z&ND*pFSnMQFc)1!NmLx7R7|5simy~ zGO(B-ojtzWHXDkZ!LX$T)@@Q$Su2>!Dx;Lh+-2yRWa)NIm*H4JV0lKb&Ea(gDhs%_ zQc|@tk8IK8Y+xr22QrvAUa(4>8RYQT^6c#^0hsh>+lG!P0E6TQ1_$Ybyi)WtMK7ED zQnpPW&Wn7K(`wF~IV5k7$HVh{G#X7L5(pt(*LUvRxp(j0{rmTqN~Lq>&W(>{v-08u71WP_p&L}JJdxVY7UV$t4Lw1P*HgTXcwMK2 zbUvl?QN0pyajOG~gx;0VQ9@S|x=u;a6G%^V>6L(sTO9yEoCQP(L=K1|B~AoT1hd3S zz{RZ&Kq%Hhk%J-+MUfIOLQaHrVkO|>RtG#Dk@JW_j~McZBBhY%35p(0tOQ)#>Hz(l zb=^h!a~=EZI>N89+BYUa)AwJb>7aZ4+6Z{^mW8ckFfHHOWdbmLHoa@Wi!aS1%qdnZ z9-Z>_tq`Cl(PqI!|1AZvE{i;H@p@p#n2v&x)UAZ|=Uk(@GpsUQaF? z%x3pUqsNZ#x?^a|)~6m@e)#k&E8kt7A01wH{BYAlN1h&jzRCCHrb|I?UbAnrJXM-` z_MK0ci=#gWp6Qyqm8p5-lMU0_F5Fg_{n?UP`~J9h$DK9$&rkn0xa-)%zi&Q_=1!kE zu;ER1>kII&OAG^}#28n6c~1J=R@o?qioe z73UtFe)7SlMZ34nTd*&AO1}TVFR%4p?39tV*u?{}n&Hl) z$DSWp(KxyHeB{|_7v!IYKi|!-K;IAGvz^^y%4) Y(j#9PUEP;Xk&=N}bYtZ0=B+#b21&cllmGw# delta 11 ScmZ1@HiL75^5*Sqr7Qp%2m}BC diff --git a/Tests/assets/tests/ground-2x.png b/Tests/assets/tests/ground-2x.png index 9588e6b2cb4e8c5aced055e693879a92b2653f5b..981a78b8de81b34d4579c2cea096cff127545970 100644 GIT binary patch literal 2911 zcmds3drT8|96ybK;+QU)#ThfsBhfi+uLTP9Yh6QdCXiLqpx79hxoFX1?SWF&k>cW*d_AdzdB{6ewNI+vh0 zr<$ZW8&G?kF1U^$hBS|hq{@JZ+JK#5jab_;ABHlt5i8JHHC9(9C}py%c#u~$FQ2L^ zqx3YEmKtsF5D>r#L=yEl9jrijjF^l|z_BF8F;rF&%ZyltSOnP(0%j8IDCIOwz`ci$)BC z>u5L$x*VUAkz3i4cV)l>_H&VzRz|@q5~O1Wvlhs>Q3h%ctJE*zyyVo zJ){F6ztRQp1wi1+c?y^-V61VnQ|L{}Bw;1_G5}j@Ht7&Rc#W{aKA}mGG?5~VUuprB zCRwFT%$Mq)FdaTMny|9fN?2GyBv}erOhybss~LtSbV)irse{E%($R@33M8heY$-sm zvX#WsNhKO83D~Fs5GIZ)mueA)p#M2UE-XC5l8%23CG{d|pecgx??qXrbZCY{RA$Q5 z!ANaMI7rqGqgK(Nge-T6SZbb&XGDN9PSOqpw1nf)`K6=)coeXZ9zaDPh=SUHOHJ-z zLiqA5?7)Bz!9n6h@qiRLegLOD@aAAnsd<28(OFz6$qwZrtS`G52xl1B&_t%9iW++=qm*bOLJ~v+F+IxtesujlelzTOWQD% z5LBsXe+~bVAVp!oQ3|uB$lwtT?*_c@a)6V*>jkjT84oXysXLx-ffrAtEho!_1f}ne z#@(BsWRxp=k$@nh$4S3o$ky!>pfF0bSj|zlB4eV)j=So-Fa=(3g%(przUPPgH@|-E zM*4(^V(00I&qr*~-#PmcwlQ_?P!8Zjq3>g?oddtu?i!|!^wtn|NUq3b(Z zuH4aciWLOS1;@8m&Mk{ zE^TURuJYzIvfL-lf!yc0w)U%~Nk95;WOek4buC?6dzy|2?&d&aKcIx|$Gn$nTXSV6 ztvB6a;6P(B8`p8~>15%jg0ADMj`;UnFLK=O^!79@UtGELV4X}T%*_XSYtk;&$}-=n zQ8OfNJ9W9mkDFGc6a-3 z+}gB#?z?{ULnYJ@a>IFldu!(=NfzvQ4#y?CG&}jgj@&j$aGhG+Zd+>YCj% ziUPG0Ph{F_W?v~tUfa4ndHYFo*PfO|d-c_S3z*Xnc=Ybf2F9_3+m{#g_OvafD%k{m zgZ8)kdwYXDx7y9g$6LOyJLD7E)(sBZ+#mL`jlNK4O;7gMo$gqwX}G>Pr>*_oY4)z$ zy!n9;xYYQ2YV(~3MSbOzVhoSh(c{Re8DF8%-j delta 11 ScmcaFcA9g7^5*SqD_8&?Y6O`8 diff --git a/Tests/assets/tests/ground.png b/Tests/assets/tests/ground.png index c4e678ec640255d089d7538eabb44924f5d2b2fb..52debe6064e07df1314ca3f0daf093faaea2bfef 100644 GIT binary patch literal 2277 zcmbtV3v3f*9KX$^j4dOEDWD*nw}=pFuh+HPdbF#fEwo|>bVG*dw(H%u?$Y+|xVx_H z;>H4ruc(lSSu#b0un`_IkSzEBOX6c8gBW>)OnHRN4RH=+0tN@n@2(FzT}agSdg=H7 z{{P?q_kVrg8_O$}imA9H{I7^{3{_R+fE6oO9HP~p<*1z(0cz5jw6vZuq9tY> zVIoTPrUj@$PZZ&LBd#}V2?MDwB@G0c{9t9ds6}MzNv~sZ(ivP?u?ATdNF0wuBDzSC zju-tnVK$p_y#Y5Ev`|AU#WoWbnjQA)rGogNoe9X!*Lq0=TpSPZJ17#asb5CWVmK{H~UzFUxKBTS=` zm9!-!N2`{S%;1?Cs#i7H<3ON&+IB*snGB(_$MazIP-q;yBs&{nf|ALe(1BDH69Dc7 z5+4?6;B17k7OGC6w?rmM4R zeCzcoWsiq+bCOJPG;lkt7=+fbEJG5-rBrc=u~b`B46RK@qe<)27n`(zHWilwU|<+? zCWM2h!%8hu5X`@aSOyEva#Zj?Ln*z8S{RyS?)Rc9OLS;?P)@88%OIf-6lPWRv*nD&@d}PryOp zMT>xvc`<`iC0IM2Q&}aTICK%;KyeefNa?FCGT|(l4Nck;j_f>~Q(AU&^}ITU<8@eP zh+>1Qco7~X0viQEfu(r~x_{QMZ0O)jCOZ6YW-G;{QL=SK%IAX_qhy+r6(bzO)tD^m zOpq>Cf-%P)6tqeXMb(KQAz^riSTTue1WbZJYK#3peF}Rc;ULlgAp8S{BCRR$^>Ty~ zLAf7xcIvcUB(jVjpmsYtZxzcid_{bBfAZz!91SW|~@Z{2*9=y}QP zJ?YK4QaHbL>aF&v^PYTS(^Ac}gWnErbGR-(?L79$g~!GQ^)c6#Tcgd{t$`D>vJg#9 z>T$h)_2B5)`0=5x_`N@e#(Eyw(Y^N2zP&TKyj_Z*YwY(MThE{T`NK_TeuFD0vN^|e z%{w~R-<{j>$Ia%U*((aK?mYd%(2AehYFlm(*i0KweEn+0zP9-{zq+!af0RB@TwBy) zZ8`jA``y7CEj9f?TVMEe`{9x^3)i$=R889A*0H{>{hgi1FKxSYpg`NP`Q*r_eY z3v+G{D4<=vwvB_|*X@q&T^;)F?0a)-zPuhiGNso}_QN6AyR{oXCrl|Ke-vjWEy z^xkzZ--`u5f28RZYfV9I&*eLH&%H)Gx}Ymu`m5(QGE&mnQN!X1BDGuNYmVq{b$_EB zx$L96cfGhLHgvGtj)Tog_-*lzuk|5Wt<&yJpO(t7n(7%SlKIrNBAHKXR`${UJIFf) W8@8|P%YPddA8|V?9Qzj4t^W&l9ubNF delta 11 ScmaDVc#&m-^5*SqdQ1Qvk^~(nz1vO0Hb!0q)3*fNm?c%Q7TEtPBz+h z*>zXF!Yl`~m=p9Yy+S`J*|pOkm}+5|5Fwk=nXe$#tR`c@9z&EJtSD>U5)ZEZzg;pUf(NzasT5by}zL`^IjZ3 zOVu1cwsb>=zT1peD;>>(FoZZfYuu@%vE{uWJY#nC7j`814%=qL`NQ8W%m z-=jeo9pZ3^!`P1oNjOdh5$Zqh_!&wSn_gGU*0twpL1O=73HV5u-OAA3~)Pdolr?m z)C~t;y&|RNo5dstvoK=Flo(LJ+Q1Kw{AhS-J#hFP{D$EsIM0(asSS~opG`9lumd4j zGAH9G91fY!<|Ovx!7%p6b2j#m=<_&=4r$AuACVo5X-${*I5rsU6EGe?`~-(DBq=`$ zs(rx6VOc|>l+lF!Ye+ZO+4Q6|-KZOu!g@0)i7a5>KI3z;ERA=3(I{a!5)~=9M)O*s zw+o`VLRz_P7yQ?KQpXpZNc6H^5cyb% zKE6e+#x7pEYwKjvRhrb$uhg8$dF{G!KSrEZM3h&xf*Yj~YhE^j)*9;nP}qe4TGO2| zdXV3UfAQ~~07@LJX>~@ucpTJ?Sg@#?y3=BU^5*SqVln_9LH6}chTSJuD_5AkWwVa<4L*D2*ADP9vqwt{C@Js`;U9gli$R}mA*>ehP)~~mR3{n zRz>}d`Jz{=n(=GUWD1!mW9PrW|8a;&W9M?1_(?s6xj4C#aDMmW1;4xIqttnI_%vFD z2vC4#WL4y)3RkgXaYOuXrl~`0h`x@U=EzheSIIjvt|XA5H*~ol49Nb-3x>ggKm4Bb z{Gd<$0rf|2&DU z2A)r)jiK9_Dc_{j3z=I|DQ_s`P(Y~_<^?+mr?ut-RMP0ONkw+trYfD%?Fd`e>b(Pk z`J3E_m^SyLljPBOS`<4ON*~{#M4jFbRiNtPwg!3zs#-`6$2YjyJu3`yJ8P^^VZ!7! zps(JrT)}!VOwcST(`>>tDqw8j`ulD$xG)~*zrbJLUk4{icv>pWN)D%!*gk2QK(5aC_-s=(6cca1Z#9^HCp~K8J%Xj7h~8<~Y_E=~rOfgZK##Uz9BWDyZfj zAB$xSNurd8>1RXQxz?sfxo$`8Fc-#~Vb017_wA-IV~bonhv&5v8i*)Z28zroN#4zw z0!g4men2!Rtr{<)#_GA~3fR%QLPQ@RVTf@FOjmM?)H!_EC3ScXtR%0iIV&G3(Zx5! z*4W02wrw4b+e+gK`<3g{a#q=H+>H^(1rzyYCGke7#R~FT@JeC*A2PGxVQbnmCil|b z%cjff3@q7nilQ;inKoI}p>|gh`UxF&A{6fW_OWeSc7)iz2$3<~C9$K}4WuoD^^$1K z+WarKZk&exXe%t|fcTHJ#wJo#GreRIjxw}&?6mjp>qd$+12P_y=a-^PtEKvGmnX;b z%S>xhb9I|;>?Fa1Gsan;dqlsGs14YN~|XFCHeK| MU;g~-+kf{y0f#^l2LJ#7 delta 11 ScmbOm+UYStdGmI*XUYH`O9b!$ diff --git a/Tests/assets/tests/river-2x.png b/Tests/assets/tests/river-2x.png index abed28ac6fb34bc9697cc16a6604a9e83b0d807e..1e287c238bf6e740ee8d154ccd30970354aaeb39 100644 GIT binary patch literal 2478 zcmd^AU1%Id9N!pvX;ULe3ymT;Y^$hsZ}+}>x%Do+%wzWRk%5hv%W9 zjrjwSK!+505(K3{NR+~&6cYkLS?Z930Z{$$ovk2lYk9dpeV{5vM+v@2h$V|+xm@;_ zLw?gPh=EuvCQ3mu7!)W%a3&1`D}v$dW)vPr8ab+sErLx0Fiw~=$4P?c={l(Gq-6Eu zLXkLi3B#$zj7CqaKuZkxCDC>SgKU5d>K+0FImGeDMONfGuJXI1nm}CKzz61+Y9mNYyTPXc z^x|5dItLImK(|?h#zqn~yerX27jGta?lM-_&WHrCdamev_uCBONnO%Fsq#1zOp))3)2jS^jLBf8jeyLHE)g{(e!Q*>z4x^ z)UOw0P%)cc9`F8iYlvPvzTDFX)7)+LeQROKSo3Zu<`QYvNviqn%E+kgSqv z`OeqYvU8^&y?Kn|?wQY~y9O&4uV4H~`SfsmX7Q5#$qPESu=nPNP47>h>}%g~f6_P0 zFDB+^=Wac^dLha#!vFMhyC6?5{TlwFefipQyZXwHm#?lazjn1e^Y*vbR;Ct4KWKQY zuB?=sUGd{*M;1?ie&n=xPXGV_ delta 11 ScmZ1{Jd16D^5*Sq-4NQ+5BHM}gXvgtLSF09QI&9!ASWl8(S z2hvR)0BTea+^pFS*P9tB#MR+H5GxW6O~i6Wng{~oC1Va}U7zA)EGe6+mcXN_n3mMW z)a0W$u4==Ino!h~ti^S8Opj~0bxGp`IPF_yeLlCp6%0lhsVao0D@vo$h&6^|u3u5K zR4S#YaU~v?!9wP%jv!6h;d=sz2qQZnq+9=(gmf3Of>b@OvvP^~Av_>7l!An@0<|%jUzDe^dFxNd{ zP>2>~Qa6adOrfOq6PwevPB)-VEFwsb2$+9R0 z!ZU~1{~0Q%MVz)w-MU_jp)9J<30p*?XlS#bEOnbW6_~Yb(K4yqLKpalyFYzuqX6s#$nE@+f6zohjG{uIuM3EBqMRM zLam5ETgCoQokDFy0UA{b@Y^&+$CA5c(I7sZs6b`6ciTyySrv-2S-f|VIhNbt5BIV( zVeKkGPb$H79kgL9Avjvm>udP01Vsj2TPb8MlED@YXM@r?9AMBnUZ?}Xd^kL+pFj2m z96bG{C#G`f@8Ev#%!hA*j|R`FcjYf^*yj$v-#GHX$X74ib7f%(-+KEU@9?90Py4SHXJ^2^_ZoZq z<3FD}a^i!{)2HD1di~N%({G-={N?5w_NC38TLLN~t0)^8b_y^Q)! R?l1!Wh5StJ)Z~Ff{{U>8dv5>$ delta 11 ScmaFIe~W2?^5*Sq=8OOyGXymN diff --git a/Tests/assets/tests/sky-2x.png b/Tests/assets/tests/sky-2x.png index d7aae77da62c73fac44c6d3ae2f228b96de3d817..a86d96b03d0c07de82bdac0e2f9617db25eaa216 100644 GIT binary patch literal 4623 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 diff --git a/Tests/assets/tests/tween/PHASER.png b/Tests/assets/tests/tween/PHASER.png new file mode 100644 index 0000000000000000000000000000000000000000..09178f50c377ac9aeee42997ed545c9c3236a55a GIT binary patch literal 3967 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 AD*ylh literal 0 HcmV?d00001 diff --git a/Tests/assets/tests/tween/ribbon.png b/Tests/assets/tests/tween/ribbon.png new file mode 100644 index 0000000000000000000000000000000000000000..674a55aed6a0bf970571b25aface04a5206c0a18 GIT binary patch literal 1165 zcmV;81akX{P)>Q{B@+}+5HdmO1SK2vu0CpofI%R^Nqj!<-9y;?5a2w2b3Vj% z0RR9OXfl~p;iODs5ym`xZQka6TzQ%S02pvkuVQ)>#+xw4>3rOthVeOEX4=*LUwa7v z0O)Z{zo~_EJiS>vzitu6hZx%7y34NjPizAKkZVX+X|o>A<8)f4v9CCL{W09;?39l| zYy$vrs(7wPbA9`!w%s(Mcli{?hj8DEb3A6T4FJF);k+KjRc-riis>%mxfhWwwgCVx zJC--CX|C4Jk89i8)Hw_*wm}FFPx?0U*Di zEsFhTV|T^v%D(ptxr(3mc-Q4etBY*_D0Z&1-KJ`I`Ei5G9{lGbd_d{{DM5K@qizjpCtgGO~3SgW;@2wd#@t^02z7=u^pF^Ec>|g zKY9HC0LWNe{`t_?ofq2x0Oam=GMPMzZ2$le++rI5fL;dopXumgVjBPeT2_VeB?R}V z4?8Qi0RZTmG6na&*aiTAZak!AC8gK~0D$&YMaHw-VjBQ}zKd^|jI-Qd(x;<~Z2$mh zS$xxETvqa!%hD3D4FJH<+!oJEOD5+&OrY2X0APq>WWNvNuX9GX*aiSVbc<~O0B9w; zA5OKVTWkXWaB0zfpQ8JZu8&@F?fC`(;6lBNBLw#%oPG@BQ`la%c?V{;0RXskWf%`( z{F7$8Z`-aIRM~v z(qZ$;4sLps!uA+%54eq{k7ty|eJ#G@_!>{c`Tu{fU&SMP7thBWgmXJI?*IyB;wLV#z*&eoi{{+78ENIghp0|DY8~}hb#`bFM_BKC!Pi%K%k;ZEg z{Oy=XHlqpvkiScd?QJu&F^vCiaQXkPV?8MV0DX?_O|iRaR(Q7Ix|T2i05JI2UKHOi zsd#$jduEFP0J!+rp4DP`wfv&K&e*In003h9m6(pP{Iz&l%CStx5Ci}K_KfXWEwC-) f004lN{42l!ny4un;|CTv00000NkvXXu0mjfJt!>b literal 0 HcmV?d00001 diff --git a/Tests/assets/tests/tween/shadow.png b/Tests/assets/tests/tween/shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..60096788772d7f8d969793d35cfdd2a83bc59938 GIT binary patch literal 1251 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 I_8gu23vv60i2wiq delta 11 ScmeAaea}8YdGlVjGt2-SK?IEe diff --git a/Tests/cameras/camera fade.js b/Tests/cameras/camera fade.js deleted file mode 100644 index b86cbcc2..00000000 --- a/Tests/cameras/camera fade.js +++ /dev/null @@ -1,42 +0,0 @@ -/// -(function () { - var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); - - var circle1, circle2, circle3; - var fx; - - function init() { - game.world.setSize(800, 600, true); - game.load.image('blue', 'assets/tests/blue-circle.png'); - game.load.image('yellow', 'assets/tests/yellow-circle.png'); - game.load.image('magenta', 'assets/tests/magenta-circle.png'); - - game.load.start(); - } - function create() { - circle1 = game.add.sprite(114, 34, 'blue'); - circle2 = game.add.sprite(426, 86, 'yellow'); - circle3 = game.add.sprite(221, 318, 'magenta'); - - circle1.input.start(0, false, true); - circle1.events.onInputUp.add(fade1, this); - - fx = game.camera.fx.add(Phaser.FX.Camera.Fade); - } - function update() { - } - function render() { - game.camera.fx.render(game.camera, - game.camera.x, game.camera.y, - game.camera.width, game.camera.height); - game.camera.fx.postRender(game.camera, - game.camera.x, game.camera.y, - game.camera.width, game.camera.height); - } - function fade1(pointer) { - console.log('pressed'); - fx.start(0.05, 0.5, function() { - console.log('fin'); - }); - } -})(); diff --git a/Tests/cameras/camera fx 1.js b/Tests/cameras/camera fx 1.js new file mode 100644 index 00000000..7b8cd30f --- /dev/null +++ b/Tests/cameras/camera fx 1.js @@ -0,0 +1,43 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var btn1, btn2, btn3; + var fx; + + function init() { + game.world.setSize(800, 600, true); + game.load.image('blue', 'assets/tests/blue-circle.png'); + game.load.image('yellow', 'assets/tests/yellow-circle.png'); + game.load.image('magenta', 'assets/tests/magenta-circle.png'); + + game.load.start(); + } + function create() { + btn1 = game.add.button(114, 34, 'blue', simpleFade, this); + btn2 = game.add.button(426, 86, 'yellow', forceFade, this); + btn3 = game.add.button(221, 318, 'magenta', fadeWithCallback, this); + + fx = game.camera.fx.add(Phaser.FX.Camera.Fade); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Press to fade.', 114 + 90, 34 + 130); + Phaser.DebugUtils.context.fillText('Force to fade every time you press it.', 426 + 20, 86 + 130); + Phaser.DebugUtils.context.fillText('Popup a window when fade finished.', 221 + 30, 318 + 130); + } + function simpleFade() { + // Simply fade to black in 0.5 seconds. + fx.start(0x330033, 0.5); + } + function forceFade() { + // Force restart fade effect each time you pressed the button. + fx.start(0x003333, 0.5, null, true); + } + function fadeWithCallback() { + // Popup a alert window when fade finished. + fx.start(0x333300, 0.5, function() { + alert('Fade finished!'); + }); + } +})(); diff --git a/Tests/cameras/camera fx 2.js b/Tests/cameras/camera fx 2.js new file mode 100644 index 00000000..f700830e --- /dev/null +++ b/Tests/cameras/camera fx 2.js @@ -0,0 +1,44 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var btn1, btn2, btn3; + var fx; + + function init() { + game.world.setSize(800, 600, true); + game.load.image('blue', 'assets/tests/blue-circle.png'); + game.load.image('yellow', 'assets/tests/yellow-circle.png'); + game.load.image('magenta', 'assets/tests/magenta-circle.png'); + + game.load.start(); + } + function create() { + btn1 = game.add.button(114, 34, 'blue', simpleFlash, this); + btn2 = game.add.button(426, 86, 'yellow', forceFlash, this); + btn3 = game.add.button(221, 318, 'magenta', flashWithCallback, this); + + // Usage of flash fx is the same as fade. + fx = game.camera.fx.add(Phaser.FX.Camera.Flash); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Press to flash.', 114 + 90, 34 + 130); + Phaser.DebugUtils.context.fillText('Force to flash every time you press it.', 426 + 20, 86 + 130); + Phaser.DebugUtils.context.fillText('Popup a window when flash finished.', 221 + 30, 318 + 130); + } + function simpleFlash() { + // Simply flash to black in 0.5 seconds. + fx.start(0x330033, 0.5); + } + function forceFlash() { + // Force restart flash effect each time you pressed the button. + fx.start(0x003333, 0.5, null, true); + } + function flashWithCallback() { + // Popup a alert window when flash finished. + fx.start(0x333300, 0.5, function() { + alert('Flash finished!'); + }); + } +})(); diff --git a/Tests/cameras/camera fx 3.js b/Tests/cameras/camera fx 3.js new file mode 100644 index 00000000..b15a0652 --- /dev/null +++ b/Tests/cameras/camera fx 3.js @@ -0,0 +1,44 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var btn1, btn2, btn3; + var fx; + + function init() { + game.world.setSize(800, 600, true); + game.load.image('blue', 'assets/tests/blue-circle.png'); + game.load.image('yellow', 'assets/tests/yellow-circle.png'); + game.load.image('magenta', 'assets/tests/magenta-circle.png'); + + game.load.start(); + } + function create() { + btn1 = game.add.button(114, 34, 'blue', simpleShake, this); + btn2 = game.add.button(426, 86, 'yellow', forceShake, this); + btn3 = game.add.button(221, 318, 'magenta', shakeWithCallback, this); + + // Usage of shake fx is the same as fade and flash. + fx = game.camera.fx.add(Phaser.FX.Camera.Shake); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Press to shake.', 114 + 90, 34 + 130); + Phaser.DebugUtils.context.fillText('Force to shake every time you press it.', 426 + 20, 86 + 130); + Phaser.DebugUtils.context.fillText('Popup a window when shake finished.', 221 + 30, 318 + 130); + } + function simpleShake() { + // Simply shake to black in 0.5 seconds. + fx.start(0x330033, 0.5); + } + function forceShake() { + // Force restart shake effect each time you pressed the button. + fx.start(0x003333, 0.5, null, true); + } + function shakeWithCallback() { + // Popup a alert window when shake finished. + fx.start(0x333300, 0.5, function() { + alert('Shake finished!'); + }); + } +})(); diff --git a/Tests/cameras/camera scale.js b/Tests/cameras/camera scale.js new file mode 100644 index 00000000..dd132d85 --- /dev/null +++ b/Tests/cameras/camera scale.js @@ -0,0 +1,72 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var zombieCamera; + + var zombie; + var walkSpeed = 2, + direction = 1; + + function init() { + game.world.setSize(1280, 600, true); + game.load.image('ground', 'assets/tests/ground-2x.png'); + game.load.image('river', 'assets/tests/river-2x.png'); + game.load.image('sky', 'assets/tests/sky-2x.png'); + + game.load.spritesheet('zombie', 'assets/sprites/metalslug_monster39x40.png', 39, 40); + + game.load.start(); + } + function create() { + // Add background images. + game.add.sprite(0, 0, 'sky'); + game.add.sprite(0, 360, 'ground'); + game.add.sprite(0, 400, 'river'); + + // Create zombie spirte + zombie = game.add.sprite(480, 336, 'zombie'); + zombie.animations.add('walk', null, 30, true); + zombie.animations.play('walk'); + + // Create a small camera which looks at the zombie. + // Use the same settings as the default camera. + zombieCamera = game.add.camera(0, 0, 800, 600); + // Use x and y properties to set the target area. + zombieCamera.x = 420; + zombieCamera.y = 240; + // Resize the camera so that it will only look at 200x200 area. + zombieCamera.setSize(200, 200); + // Scale the camera to 2.0, now its target will be 100x100. + zombieCamera.transform.scale.setTo(2.0, 2.0); + // Use setPosition() method to set where the camera rendered + // on the screen. + zombieCamera.setPosition(0, 0); + } + function update() { + if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { + zombieCamera.x -= 2; + } + else if (game.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) { + zombieCamera.x += 2; + } + if (game.input.keyboard.isDown(Phaser.Keyboard.UP)) { + zombieCamera.y -= 2; + } + else if (game.input.keyboard.isDown(Phaser.Keyboard.DOWN)) { + zombieCamera.y += 2; + } + // zombie wandering update + zombie.x += walkSpeed * direction; + if (zombie.x > 540 || zombie.x < 440) { + // Change walk direction. + direction *= -1; + // Flip zombie's animation. + zombie.texture.flippedX = !zombie.texture.flippedX; + } + } + function render() { + game.camera.renderDebugInfo(32, 32); + zombieCamera.renderDebugInfo(32, 128); + } +})(); diff --git a/Tests/cameras/hide from camera.js b/Tests/cameras/hide from camera.js new file mode 100644 index 00000000..231d1125 --- /dev/null +++ b/Tests/cameras/hide from camera.js @@ -0,0 +1,53 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var radar; + var ships = []; + + var enemyCamera; + + function init() { + game.world.setSize(800, 600, true); + game.load.image('radar-surface', 'assets/tests/radar-surface.png'); + game.load.image('ship', 'assets/sprites/asteroids_ship_white.png'); + game.load.image('enemy-ship', 'assets/sprites/asteroids_ship.png'); + + game.load.start(); + } + function create() { + // Add enemies and our ship the the world. + for (var i = 0; i < 4; i++) { + ships.push(game.add.sprite(100 + i * 10, 280 + i * 16, 'enemy-ship')); + } + var ourShip = game.add.sprite(160, 300, 'ship'); + ships.push(ourShip); + + // Radar sprite is a HUD. + radar = game.add.sprite(0, 0, 'radar-surface'); + radar.transform.scrollFactor.setTo(0, 0); + + // Make the default camera rendered on the left half screen. + game.camera.setSize(400, 600); + // Add a new camera and render it on the right half screen. + // The newly created is the enemies' camera, which cannot "see" our ship. + enemyCamera = game.add.camera(0, 0, 800, 600); + enemyCamera.setSize(400, 600); + enemyCamera.setPosition(400, 0); + + // Hide our ship on the enemies' camera. + enemyCamera.hide(ourShip); + } + function update() { + for (var i = 0; i < ships.length; i++) { + ships[i].x += 1; + if (ships[i].x > 400) { + ships[i].x = 40; + } + } + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Left is the player\'s camera, and right is the enemies\' camera.', 32, 100); + } +})(); diff --git a/Tests/cameras/multi camera.js b/Tests/cameras/multi camera.js index 7554e1f3..d98d8244 100644 --- a/Tests/cameras/multi camera.js +++ b/Tests/cameras/multi camera.js @@ -19,22 +19,27 @@ game.load.start(); } function create() { - // background images + // Add background images. game.add.sprite(0, 0, 'sky'); game.add.sprite(0, 360, 'ground'); game.add.sprite(0, 400, 'river'); - // zombie spirte + // Create zombie spirte zombie = game.add.sprite(480, 336, 'zombie'); zombie.animations.add('walk', null, 30, true); zombie.animations.play('walk'); - // create a small camera which looks at the zombie + // Create a small camera which looks at the zombie. + // Use the same settings as the default camera. zombieCamera = game.add.camera(0, 0, 800, 600); + // Use x and y properties to set the target area. zombieCamera.x = 420; zombieCamera.y = 240; - zombieCamera.setPosition(0, 0); + // Resize the camera so that it will only look at 200x200 area. zombieCamera.setSize(200, 200); + // Use setPosition() method to set where the camera rendered + // on the screen. + zombieCamera.setPosition(0, 0); } function update() { if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { @@ -52,8 +57,10 @@ // zombie wandering update zombie.x += walkSpeed * direction; if (zombie.x > 540 || zombie.x < 440) { + // Change walk direction. direction *= -1; - zombie.transform.scale.setTo(direction, 1); + // Flip zombie's animation. + zombie.texture.flippedX = !zombie.texture.flippedX; } } function render() { diff --git a/Tests/cameras/radar template.js b/Tests/cameras/radar template.js deleted file mode 100644 index 5608b2e6..00000000 --- a/Tests/cameras/radar template.js +++ /dev/null @@ -1,47 +0,0 @@ -/// -(function () { - var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); - - var radar; - var ships = []; - - var button; - - function init() { - game.world.setSize(800, 600, true); - game.load.image('radar-surface', 'assets/tests/radar-surface.png'); - game.load.image('ship', 'assets/sprites/asteroids_ship_white.png'); - game.load.image('enemy-ship', 'assets/sprites/asteroids_ship.png'); - - game.load.image('button', 'assets/tests/320x200.png'); - - game.load.start(); - } - function create() { - for (var i = 0; i < 4; i++) { - ships.push(game.add.sprite(100 + i * 10, 300 + i * 16, 'ship')); - } - ships.push(game.add.sprite(160, 320, 'enemy-ship')); - radar = game.add.sprite(0, 0, 'radar-surface'); - - game.camera.setSize(400, 600); - var camera2 = game.add.camera(0, 0, 400, 600); - camera2.x = 400; - - button = game.add.sprite(500, 100, 'button'); - button.input.start(0, false, true); - } - function update() { - if (button.input.justReleased(0, 20)) { - } - - for (var i = 0; i < ships.length; i++) { - ships[i].x += 1; - if (ships[i].x > 400) { - ships[i].x = 40; - } - } - } - function render() { - } -})(); diff --git a/Tests/groups/add to group 1.js b/Tests/groups/add to group 1.js new file mode 100644 index 00000000..44bff9a3 --- /dev/null +++ b/Tests/groups/add to group 1.js @@ -0,0 +1,40 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var friendAndFoe, + enemies; + + function init() { + game.load.image('ufo', 'assets/sprites/ufo.png'); + game.load.image('baddie', 'assets/sprites/space-baddie.png'); + game.load.start(); + } + function create() { + // Create some local groups for later use. + friendAndFoe = game.add.group(); + enemies = game.add.group(); + + // Use game.add (GameObjectFactory) to create sprites, those + // newly created ones will be added to game.world.group + // automatically. While you can still use new to allocate and + // only add them to your own groups. + var ufo = game.add.sprite(200, 240, 'ufo'); + friendAndFoe.add(ufo); + + // Create some enemies using new keyword. + // (Don't forget to pass game as the first parameter.) + var enemy; + for (var i = 0; i < 16; i++) { + enemy = new Phaser.Sprite(game, + 360 + Math.random() * 200, 120 + Math.random() * 200, + 'baddie'); + enemies.add(enemy); + } + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('ufo added to game.world.group and "friendAndFoe" group', 16, 24); + Phaser.DebugUtils.context.fillText('others ONLY added to "enemies" group', 16, 40); + } +})(); diff --git a/Tests/groups/add to group 2.js b/Tests/groups/add to group 2.js new file mode 100644 index 00000000..1d7b0ead --- /dev/null +++ b/Tests/groups/add to group 2.js @@ -0,0 +1,37 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + function init() { + game.load.image('ufo', 'assets/sprites/ufo.png'); + game.load.image('baddie', 'assets/sprites/space-baddie.png'); + game.load.start(); + } + function create() { + // Create some local groups for later use. + friendAndFoe = game.add.group(); + enemies = game.add.group(); + + // You can directly create sprite and add it to a group + // using just one line. (One thing you should know is, the body type + // of this sprite is set to BODY_DINAMIC by default, while it's + // BODY_DISABLED by default using other creating methods.) + friendAndFoe.addNewSprite(200, 240, 'ufo', null, Phaser.Types.BODY_DISABLED); + + // Create some enemies. + for (var i = 0; i < 8; i++) { + createBaddie(); + } + + // Tap to create new baddie sprites. + game.input.onTap.add(createBaddie, this); + } + function createBaddie() { + enemies.addNewSprite(360 + Math.random() * 200, 120 + Math.random() * 200, + 'baddie', null, + Phaser.Types.BODY_DISABLED); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Tap screen or click to create new baddies.', 16, 24); + } +})(); diff --git a/Tests/groups/bring to top.js b/Tests/groups/bring to top.js new file mode 100644 index 00000000..c28b68a9 --- /dev/null +++ b/Tests/groups/bring to top.js @@ -0,0 +1,30 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var container; + + function init() { + game.load.spritesheet('button', 'assets/buttons/number-buttons.png', 160, 160); + game.load.start(); + } + function create() { + // Container for sorting the buttons, which we'll use to make buttons + // to the top later. + container = game.add.group(); + + // Add buttons to container. + container.add(game.add.button(200, 100, 'button', bringMeToTop, this, 0, 0, 0)); + container.add(game.add.button(300, 100, 'button', bringMeToTop, this, 1, 1, 1)); + container.add(game.add.button(100, 200, 'button', bringMeToTop, this, 2, 2, 2)); + container.add(game.add.button(400, 200, 'button', bringMeToTop, this, 3, 3, 3)); + container.add(game.add.button(300, 300, 'button', bringMeToTop, this, 4, 4, 4)); + container.add(game.add.button(200, 300, 'button', bringMeToTop, this, 5, 5, 5)); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Tap or click buttons to bring it to the top.', 32, 32); + } + function bringMeToTop(btn) { + container.bringToTop(btn); + } +})(); diff --git a/Tests/groups/call all.js b/Tests/groups/call all.js new file mode 100644 index 00000000..12b78583 --- /dev/null +++ b/Tests/groups/call all.js @@ -0,0 +1,37 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.image('reviveBtn', 'assets/buttons/revive-button.png'); + game.load.start(); + } + function create() { + // Add some items. + var item; + for (var i = 0; i < 3; i++) { + // Give the items a different alpha increase speed. + item = game.add.sprite(290, 98 * (i + 1), 'item', i); + // Enable input. + item.input.start(0, false, true); + item.events.onInputUp.add(kill); + // An item besides the left one. + item = game.add.sprite(388, 98 * (i + 1), 'item', i + 3); + item.input.start(0, false, true); + item.events.onInputUp.add(kill); + } + // Add a button to revive all the items. + game.add.button(270, 400, 'reviveBtn', reviveAll, this, 0, 0, 0); + } + function kill(item) { + item.kill(); + } + function reviveAll() { + game.world.group.callAll('revive'); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Tap or click an item to kill it, and press the revive button to revive them all.', 160, 500); + } +})(); diff --git a/Tests/groups/direct render.js b/Tests/groups/direct render.js new file mode 100644 index 00000000..96457e01 --- /dev/null +++ b/Tests/groups/direct render.js @@ -0,0 +1,38 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + // Left and right group. + var left, right; + // The first selected item. + var selected = null; + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.start(); + } + function create() { + left = game.add.group(); + right = new Phaser.Group(game); + // Add some items to left side, and set a onDragStop listener + // to limit its location when dropped. + var item; + for (var i = 0; i < 3; i++) { + // Directly create sprites from the left group. + item = left.addNewSprite(250, 98 * (i + 1), 'item', i, Phaser.Types.BODY_DISABLED); + // Add another to the right group. + item = right.addNewSprite(348, 98 * (i + 1), 'item', i + 3, Phaser.Types.BODY_DISABLED); + } + + exCamera = game.add.camera(0, 0, 800, 600); + exCamera.setPosition(120, 0); + } + function render() { + right.directRender(exCamera); + + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Left Group', 300, 80); + Phaser.DebugUtils.context.fillText('Right Group', 400, 80); + Phaser.DebugUtils.context.fillText('Left group is normally rendered, while the right one is ONLY rendered directly to another camera.', 120, 480); + } +})(); diff --git a/Tests/groups/for each.js b/Tests/groups/for each.js new file mode 100644 index 00000000..6c4b3821 --- /dev/null +++ b/Tests/groups/for each.js @@ -0,0 +1,35 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var baseAlphaIncSpeed = 0.006; + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.start(); + } + function create() { + // Add some items. + for (var i = 0; i < 3; i++) { + game.add.sprite(290, 98 * (i + 1), 'item', i) + .alphaIncSpeed = baseAlphaIncSpeed * (i + 1); + game.add.sprite(388, 98 * (i + 1), 'item', i + 3) + .alphaIncSpeed = baseAlphaIncSpeed * (i + 4); + } + } + function update() { + // Animating alpha property of each item using forEach() method. + game.world.group.forEach(function(item) { + // Update alpha first. + item.alpha -= item.alphaIncSpeed; + // Check for switch between increasing and descreasing. + if (item.alpha < 0.001 || item.alpha > 0.999) { + item.alphaIncSpeed *= -1; + } + }); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Alpha of items is always changing.', 280, 480); + } +})(); diff --git a/Tests/groups/get first 1.js b/Tests/groups/get first 1.js new file mode 100644 index 00000000..13abf0b4 --- /dev/null +++ b/Tests/groups/get first 1.js @@ -0,0 +1,44 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var timer, cycle; + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.image('reviveBtn', 'assets/buttons/revive-button.png'); + game.load.start(); + } + function create() { + // Add some items. + var item; + for (var i = 0; i < 3; i++) { + // Give the items a different alpha increase speed. + item = game.add.sprite(290, 98 * (i + 1), 'item', i); + // An item besides the left one. + item = game.add.sprite(388, 98 * (i + 1), 'item', i + 3); + } + + // Set a timer so we can perform an action after a delay. + timer = 0; + cycle = 1000; + } + function update() { + // Update timer. + timer += game.time.delta; + if (timer > cycle) { + timer -= cycle; + // Get the first alive item and kill it. + var item = game.world.group.getFirstAlive(); + if (item) { + item.kill(); + } + } + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('One item will be killed each second.', 280, 420); + // Get living and dead number of a group. + Phaser.DebugUtils.context.fillText('Living: ' + game.world.group.countLiving() + ', Dead: ' + game.world.group.countDead(), 330, 440); + } +})(); diff --git a/Tests/groups/get first 2.js b/Tests/groups/get first 2.js new file mode 100644 index 00000000..b83c8161 --- /dev/null +++ b/Tests/groups/get first 2.js @@ -0,0 +1,49 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var timer, cycle; + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.image('reviveBtn', 'assets/buttons/revive-button.png'); + game.load.start(); + } + function create() { + // Add some items. + var item; + for (var i = 0; i < 3; i++) { + // Give the items a different alpha increase speed. + item = game.add.sprite(290, 98 * (i + 1), 'item', i); + // An item besides the left one. + item = game.add.sprite(388, 98 * (i + 1), 'item', i + 3); + } + + // Set a timer so we can perform an action after a delay. + timer = 0; + cycle = 1000; + } + function update() { + // Update timer. + timer += game.time.delta; + if (timer > cycle) { + timer -= cycle; + // Get an alive item from the group randomly, so it may not + // be the first to be killed. + // Also you can specific a range, only items between that range + // will be found and return. + // Set a range of (0, 5), so the first item will not be kill at all. + var item = game.world.group.getRandom(1, 5); + if (item) { + item.kill(); + } + } + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('One item will be killed each second.', 280, 420); + Phaser.DebugUtils.context.fillText('Yet the first one will NEVER be killed since we use a range from 1 to 5 for selection.', 140, 432); + // Get living and dead number of a group. + Phaser.DebugUtils.context.fillText('Living: ' + game.world.group.countLiving() + ', Dead: ' + game.world.group.countDead(), 330, 460); + } +})(); diff --git a/Tests/groups/get first 3.js b/Tests/groups/get first 3.js new file mode 100644 index 00000000..ab09aedf --- /dev/null +++ b/Tests/groups/get first 3.js @@ -0,0 +1,58 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var killTimer, reviveTimer, cycle; + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.image('reviveBtn', 'assets/buttons/revive-button.png'); + game.load.start(); + } + function create() { + // Add some items. + var item; + for (var i = 0; i < 3; i++) { + // Give the items a different alpha increase speed. + item = game.add.sprite(290, 98 * (i + 1), 'item', i); + // An item besides the left one. + item = game.add.sprite(388, 98 * (i + 1), 'item', i + 3); + } + + // Set a timer so we can perform an action after a delay. + killTimer = 0; + // Another timer for reviving. + reviveTimer = 0; + cycle = 1000; + } + function update() { + // Update timers. + killTimer += game.time.delta; + reviveTimer += game.time.delta; + + // Kill first alive item every "cycle" duration. + if (killTimer > cycle) { + killTimer -= cycle; + // Get an alive item from the group and kill it. + var item = game.world.group.getFirstAlive(); + if (item) { + item.kill(); + } + } + // Revive first dead item every 1.5 "cycle" duration. + if (reviveTimer > cycle * 1.5) { + reviveTimer -= cycle * 1.5; + // Get a dead item from the group and revive it. + var item = game.world.group.getFirstDead(); + if (item) { + item.revive(); + } + } + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('One item will be killed each second and revived later.', 240, 420); + // Get living and dead number of a group. + Phaser.DebugUtils.context.fillText('Living: ' + game.world.group.countLiving() + ', Dead: ' + game.world.group.countDead(), 330, 440); + } +})(); diff --git a/Tests/groups/group as layer.js b/Tests/groups/group as layer.js new file mode 100644 index 00000000..1ba93a6f --- /dev/null +++ b/Tests/groups/group as layer.js @@ -0,0 +1,74 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + function init() { + game.world.setSize(1280, 800, true); + + game.load.image('ground', 'assets/tests/ground-2x.png'); + game.load.image('river', 'assets/tests/river-2x.png'); + game.load.image('sky', 'assets/tests/sky-2x.png'); + game.load.image('cloud0', 'assets/tests/cloud-big-2x.png'); + game.load.image('cloud1', 'assets/tests/cloud-narrow-2x.png'); + game.load.image('cloud2', 'assets/tests/cloud-small-2x.png'); + + game.load.spritesheet('ufo', 'assets/sprites/ufo.png', 24, 21); + + game.load.start(); + } + function create() { + // Create the sky layer, behind everything and donot move. + var skyLayer = game.add.group(); + skyLayer.z = 0; + // Create the cloud layer, only beyond the sky. + var cloudLayer = game.add.group(); + cloudLayer.z = 1; + // Create the ground, behind the river and beyond clouds. + var groundLayer = game.add.group(); + groundLayer.z = 2; + // Create the sprite layer. This should behind the river, + // and beyond the ground, cloud and sky layer. + var spriteLayer = game.add.group(); + spriteLayer.z = 3; + // Create the river layer, beyond everything. + var riverLayer = game.add.group(); + riverLayer.z = 4; + + // Add sky background to skyLayer. + var sky = new Phaser.Sprite(game, 0, 0, 'sky'); + sky.transform.scrollFactor.setTo(0, 0); + skyLayer.add(sky); + // Add clouds to cloudLayer. + var cloud0 = new Phaser.Sprite(game, 200, 120, 'cloud0'); + cloud0.transform.scrollFactor.setTo(0.3, 0.1); + var cloud1 = new Phaser.Sprite(game, -60, 120, 'cloud1'); + cloud1.transform.scrollFactor.setTo(0.5, 0.1); + var cloud2 = new Phaser.Sprite(game, 900, 170, 'cloud2'); + cloud2.transform.scrollFactor.setTo(0.7, 0.1); + cloudLayer.add(cloud0); + cloudLayer.add(cloud1); + cloudLayer.add(cloud2); + // Add ground sprite to groundLayer. + var ground = new Phaser.Sprite(game, 0, 360, 'ground'); + ground.transform.scrollFactor.setTo(0.5, 0.1); + groundLayer.add(ground); + // Add river to riverLayer. + var river = new Phaser.Sprite(game, 0, 400, 'river'); + river.transform.scrollFactor.setTo(1.3, 0.16); + riverLayer.add(river); + + // Add animating sprites to spriteLayer. + var ufo = new Phaser.Sprite(game, 360, 240, 'ufo'); + ufo.animations.add('fly', null, 0, false); + ufo.animations.play('fly'); + ufo.transform.origin.setTo(0.5, 0.5); + spriteLayer.add(ufo); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('sky layer: z = 0', 16, 20); + Phaser.DebugUtils.context.fillText('cloud layer: z = 1', 16, 36); + Phaser.DebugUtils.context.fillText('ground layer: z = 2', 16, 52); + Phaser.DebugUtils.context.fillText('sprite layer: z = 3', 16, 68); + Phaser.DebugUtils.context.fillText('river layer: z = 4', 16, 84); + } +})(); diff --git a/Tests/groups/group texture.js b/Tests/groups/group texture.js new file mode 100644 index 00000000..d4acd821 --- /dev/null +++ b/Tests/groups/group texture.js @@ -0,0 +1,21 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var robot; + var eye, body, leftArm, rightArm, leftLeg, rightLeg; + + function init() { + game.load.spritesheet('buttons', 'assets/buttons/number-buttons.png', 90, 90); + game.load.start(); + } + function create() { + // Add 6 groups and childs. + var item = game.add.group(); + item.texture.loadImage('buttons'); + } + function render() { + Phaser.DebugUtils.context.fillStyle = 'rgb(0, 160, 213)'; + Phaser.DebugUtils.context.fillText('Group can have a texture too, so you can use it just like a simple sprite.', 180, 380); + } +})(); diff --git a/Tests/groups/group transform 1.js b/Tests/groups/group transform 1.js new file mode 100644 index 00000000..ceb1f0fe --- /dev/null +++ b/Tests/groups/group transform 1.js @@ -0,0 +1,65 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var robot; + var eye, body, leftArm, rightArm, leftLeg, rightLeg; + + function init() { + game.load.image('eye', 'assets/sprites/robot/eye.png'); + game.load.image('body', 'assets/sprites/robot/body.png'); + game.load.image('arm-l', 'assets/sprites/robot/arm-l.png'); + game.load.image('arm-r', 'assets/sprites/robot/arm-r.png'); + game.load.image('leg-l', 'assets/sprites/robot/leg-l.png'); + game.load.image('leg-r', 'assets/sprites/robot/leg-r.png'); + game.load.start(); + } + function create() { + // Add some items. + var item; + for (var i = 0; i < 3; i++) { + // Give the items a different alpha increase speed. + item = game.add.sprite(290, 98 * (i + 1), 'item', i); + // An item besides the left one. + item = game.add.sprite(388, 98 * (i + 1), 'item', i + 3); + } + // Use groups of sprites to create a big robot. + // Robot itself, you can subclass group class in a real game. + robot = game.add.group(); + // Robot components. + leftArm = robot.addNewSprite(90, 175, 'arm-l', 0, Phaser.Types.BODY_DISABLED); + rightArm = robot.addNewSprite(549, 175, 'arm-r', 0, Phaser.Types.BODY_DISABLED); + leftLeg = robot.addNewSprite(270, 325, 'leg-l', 0, Phaser.Types.BODY_DISABLED); + rightLeg = robot.addNewSprite(410, 325, 'leg-r', 0, Phaser.Types.BODY_DISABLED); + body = robot.addNewSprite(219, 32, 'body', 0, Phaser.Types.BODY_DISABLED); + eye = robot.addNewSprite(335, 173,'eye', 0, Phaser.Types.BODY_DISABLED); + + leftArm.input.start(0, false, true); + leftArm.input.enableDrag(); + rightArm.input.start(0, false, true); + rightArm.input.enableDrag(); + leftLeg.input.start(0, false, true); + leftLeg.input.enableDrag(); + rightLeg.input.start(0, false, true); + rightLeg.input.enableDrag(); + body.input.start(0, false, true); + body.input.enableDrag(); + eye.input.start(0, false, true); + eye.input.enableDrag(); + } + function update() { + } + function render() { + Phaser.DebugUtils.renderSpriteInfo(leftArm, 32, 32); + Phaser.DebugUtils.renderSpriteInfo(rightArm, 32, 152); + Phaser.DebugUtils.renderSpriteInfo(leftLeg, 32, 272); + Phaser.DebugUtils.renderSpriteInfo(rightLeg, 32, 392); + Phaser.DebugUtils.renderSpriteInfo(rightLeg, 450, 32); + Phaser.DebugUtils.renderSpriteInfo(rightLeg, 450, 152); + + Phaser.DebugUtils.context.fillStyle = 'rgb(0, 160, 213)'; + Phaser.DebugUtils.context.fillText('The robot is a group and every component is a sprite.', 240, 580); + // Phaser.DebugUtils.context.fillText('Drag each part to re-position them.', 288, 592); + Phaser.DebugUtils.context.fillText('Drag each part to re-position them. ', 288, 592); + } +})(); diff --git a/Tests/groups/group transform 2.js b/Tests/groups/group transform 2.js new file mode 100644 index 00000000..710b6a04 --- /dev/null +++ b/Tests/groups/group transform 2.js @@ -0,0 +1,48 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var robot; + var eye, body, leftArm, rightArm, leftLeg, rightLeg; + var draggable; + + function init() { + game.load.image('eye', 'assets/sprites/robot/eye.png'); + game.load.image('body', 'assets/sprites/robot/body.png'); + game.load.image('arm-l', 'assets/sprites/robot/arm-l.png'); + game.load.image('arm-r', 'assets/sprites/robot/arm-r.png'); + game.load.image('leg-l', 'assets/sprites/robot/leg-l.png'); + game.load.image('leg-r', 'assets/sprites/robot/leg-r.png'); + + game.load.start(); + } + function create() { + // Add some items. + var item; + for (var i = 0; i < 3; i++) { + // Give the items a different alpha increase speed. + item = game.add.sprite(290, 98 * (i + 1), 'item', i); + // An item besides the left one. + item = game.add.sprite(388, 98 * (i + 1), 'item', i + 3); + } + // Use groups of sprites to create a big robot. + // Robot itself, you can subclass group class in a real game. + robot = game.add.group(); + // Robot components. + leftArm = robot.addNewSprite(90, 175, 'arm-l', 0, Phaser.Types.BODY_DISABLED); + rightArm = robot.addNewSprite(549, 175, 'arm-r', 0, Phaser.Types.BODY_DISABLED); + leftLeg = robot.addNewSprite(270, 325, 'leg-l', 0, Phaser.Types.BODY_DISABLED); + rightLeg = robot.addNewSprite(410, 325, 'leg-r', 0, Phaser.Types.BODY_DISABLED); + body = robot.addNewSprite(219, 32, 'body', 0, Phaser.Types.BODY_DISABLED); + eye = robot.addNewSprite(335, 173,'eye', 0, Phaser.Types.BODY_DISABLED); + } + function update() { + // Change parent's rotation to change all the childs. + // robot.transform.rotation += 2; + game.world.group.transform.rotation += 2; + } + function render() { + Phaser.DebugUtils.context.fillStyle = 'rgb(0, 160, 213)'; + Phaser.DebugUtils.context.fillText('The robot is a group and every component is a sprite.', 240, 580); + } +})(); diff --git a/Tests/groups/group transform 3.js b/Tests/groups/group transform 3.js new file mode 100644 index 00000000..5031c814 --- /dev/null +++ b/Tests/groups/group transform 3.js @@ -0,0 +1,51 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var robot; + var eye, body, leftArm, rightArm, leftLeg, rightLeg; + var draggable; + + function init() { + game.load.image('eye', 'assets/sprites/robot/eye.png'); + game.load.image('body', 'assets/sprites/robot/body.png'); + game.load.image('arm-l', 'assets/sprites/robot/arm-l.png'); + game.load.image('arm-r', 'assets/sprites/robot/arm-r.png'); + game.load.image('leg-l', 'assets/sprites/robot/leg-l.png'); + game.load.image('leg-r', 'assets/sprites/robot/leg-r.png'); + + game.load.start(); + } + function create() { + // Add some items. + var item; + for (var i = 0; i < 3; i++) { + // Give the items a different alpha increase speed. + item = game.add.sprite(290, 98 * (i + 1), 'item', i); + // An item besides the left one. + item = game.add.sprite(388, 98 * (i + 1), 'item', i + 3); + } + // Use groups of sprites to create a big robot. + // Robot itself, you can subclass group class in a real game. + robot = game.add.group(); + // Robot components. + leftArm = robot.addNewSprite(90, 175, 'arm-l', 0, Phaser.Types.BODY_DISABLED); + rightArm = robot.addNewSprite(549, 175, 'arm-r', 0, Phaser.Types.BODY_DISABLED); + leftLeg = robot.addNewSprite(270, 325, 'leg-l', 0, Phaser.Types.BODY_DISABLED); + rightLeg = robot.addNewSprite(410, 325, 'leg-r', 0, Phaser.Types.BODY_DISABLED); + body = robot.addNewSprite(219, 32, 'body', 0, Phaser.Types.BODY_DISABLED); + eye = robot.addNewSprite(335, 173,'eye', 0, Phaser.Types.BODY_DISABLED); + + // Tween the robot's size, so all the components also scaled. + // game.add.tween(robot.transform.scale) + game.add.tween(game.world.group.transform.scale) + .to({x: 1.2, y: 1.2}, 1000, Phaser.Easing.Back.InOut, true, 0, false) + .yoyo(true); + } + function update() { + } + function render() { + Phaser.DebugUtils.context.fillStyle = 'rgb(0, 160, 213)'; + Phaser.DebugUtils.context.fillText('The robot is a group and every component is a sprite.', 240, 580); + } +})(); diff --git a/Tests/groups/recycle 1.js b/Tests/groups/recycle 1.js new file mode 100644 index 00000000..965faeb0 --- /dev/null +++ b/Tests/groups/recycle 1.js @@ -0,0 +1,46 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + function init() { + game.load.image('ufo', 'assets/sprites/ufo.png'); + game.load.image('baddie', 'assets/sprites/space-baddie.png'); + game.load.spritesheet('button', 'assets/buttons/baddie-buttons.png', 224, 70); + game.load.start(); + } + function create() { + // Create some local groups for later use. + friendAndFoe = game.add.group(); + enemies = game.add.group(); + + // Create a ufo. + friendAndFoe.addNewSprite(200, 240, 'ufo', null, Phaser.Types.BODY_DISABLED); + + // Create some enemies. + for (var i = 0; i < 8; i++) { + createBaddie(); + } + + // Create buttons to create and kill baddies. + game.add.button(16, 50, 'button', createBaddie, 0, 0, 0); + game.add.button(16, 130, 'button', killBaddie, 1, 1, 1); + } + function killBaddie() { + var baddie = enemies.getFirstAlive(); + if (baddie) baddie.kill(); + } + function createBaddie() { + // Group's recycle() method will always return a valid object unless + // you did not pass an objectClass parameter. + // It will create new object instance of the given class if no "dead" + // object can be found inside the group. + var enemy = enemies.recycle(Phaser.Sprite); + enemy.texture.loadImage('baddie', false); + enemy.texture.opaque = false; + enemy.x = 360 + Math.random() * 200; + enemy.y = 120 + Math.random() * 200; + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Add new baddies using recyle() instead of allocating new object every time.', 16, 24); + } +})(); diff --git a/Tests/groups/recycle 2.js b/Tests/groups/recycle 2.js new file mode 100644 index 00000000..12d7b220 --- /dev/null +++ b/Tests/groups/recycle 2.js @@ -0,0 +1,49 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + function init() { + game.load.image('ufo', 'assets/sprites/ufo.png'); + game.load.image('baddie', 'assets/sprites/space-baddie.png'); + game.load.spritesheet('button', 'assets/buttons/baddie-buttons.png', 224, 70); + game.load.start(); + } + function create() { + // Create some local groups for later use. + friendAndFoe = game.add.group(); + enemies = game.add.group(); + + // Create a ufo. + friendAndFoe.addNewSprite(200, 240, 'ufo', null, Phaser.Types.BODY_DISABLED); + + // Create some enemies. + for (var i = 0; i < 8; i++) { + // Since the getFirstAvailable() which we'll use for recycling + // cannot allocate new objects, create them manually here. + enemies.addNewSprite(360 + Math.random() * 200, 120 + Math.random() * 200, + 'baddie', null, Phaser.Types.BODY_DISABLED); + } + + // Create buttons to create and kill baddies. + game.add.button(16, 50, 'button', createBaddie, 0, 0, 0); + game.add.button(16, 130, 'button', killBaddie, 1, 1, 1); + } + function killBaddie() { + var baddie = enemies.getFirstAlive(); + if (baddie) baddie.kill(); + } + function createBaddie() { + // Recycle using getFirstAvailable() as an alternative to recycle(). + // Notice that this method will not create new objects if there's no one + // available, and it won't change size of this group. + var enemy = enemies.getFirstAvailable(); + if (enemy) { + enemy.revive(); + } + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Recycle baddies from a group using getFirstAvailable() instead of recycle().', 16, 24); + Phaser.DebugUtils.context.fillText('Notice that you cannot add more than 8 baddies since we only create 8 instance.', 16, 36); + Phaser.DebugUtils.context.fillText('Living baddies: ' + enemies.countLiving(), 340, 420); + } +})(); diff --git a/Tests/groups/remove.js b/Tests/groups/remove.js new file mode 100644 index 00000000..d7d789ff --- /dev/null +++ b/Tests/groups/remove.js @@ -0,0 +1,65 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + // Group contains items. + var items; + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.image('rect', 'assets/tests/200x100corners.png'); + game.load.image('rect2', 'assets/tests/200x100corners2.png'); + game.load.start(); + } + function create() { + // Create item container group. + items = game.add.group(); + + // Add some items and add them to the container group, + // then you can drag and drop them to remove. + var item; + for (var i = 0; i < 6; i++) { + // Directly create sprites from the group. + item = items.addNewSprite(90, 90 * i, 'item', i, Phaser.Types.BODY_DISABLED); + // Enable input detection, then it's possible be dragged. + item.input.start(0, false, true); + // Make this item draggable. + item.input.enableDrag(); + // Then we make it snap to 90x90 grids. + item.input.enableSnap(90, 90, false, true); + // Add a handler to remove it using different options when dropped. + item.events.onDragStop.add(dropHandler); + } + + // Create 2 rectangles, drop it at these rectangle to + // remove it from origin group normally or + // cut it from the group's array entirely. + var rect = game.add.sprite(400, 0, 'rect'); + rect.transform.scale.setTo(2.0, 3.0); + var rect2 = game.add.sprite(400, 300, 'rect2'); + rect2.transform.scale.setTo(2.0, 3.0); + } + function update() { + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Size of group: ' + items.length, 100, 560); + Phaser.DebugUtils.context.fillText('Drop here to cut items from groups entirely.', 450, 24); + Phaser.DebugUtils.context.fillText('Drop here to remove it normally.', 450, 324); + } + function dropHandler(item, pointer) { + if (item.x < 90) { + item.x = 90; + } + else if (item.x > 400) { // So it is dropped in one rectangle. + if (item.y < 300) { + // Remove it from group normally, so the group's size does not change. + items.remove(item, true); + } + else { + // Remove it from group and cut from it, so the group's size decreases. + items.remove(item); + } + } + } +})(); diff --git a/Tests/groups/replace.js b/Tests/groups/replace.js new file mode 100644 index 00000000..fae0ea29 --- /dev/null +++ b/Tests/groups/replace.js @@ -0,0 +1,66 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + // Left and right group. + var left, right; + // The first selected item. + var selected = null; + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.start(); + } + function create() { + left = game.add.group(); + right = game.add.group(); + // Add some items to left side, and set a onDragStop listener + // to limit its location when dropped. + var item; + for (var i = 0; i < 3; i++) { + // Directly create sprites from the left group. + item = left.addNewSprite(290, 98 * (i + 1), 'item', i, Phaser.Types.BODY_DISABLED); + // Enable input. + item.input.start(0, false, true); + item.events.onInputUp.add(select); + // Add another to the right group. + item = right.addNewSprite(388, 98 * (i + 1), 'item', i + 3, Phaser.Types.BODY_DISABLED); + // Enable input. + item.input.start(0, false, true); + item.events.onInputUp.add(select); + } + } + function select(item, pointer) { + // If there's no one selected, mark it as selected. + if (!selected) { + selected = item; + selected.alpha = 0.5; + } + else { + // Items from different group selected, replace with each other; + // Something like a swap action, maybe better done with + // group.swap() method. + if (selected.group !== item.group) { + // Move the later selected to the first selected item's position. + item.x = selected.x; + item.y = selected.y; + // Replace first selected with the second one. + selected.group.replace(selected, item); + } + else { + selected.alpha = 1; + } + + // After checking, now clear the helper var. + selected = null; + } + } + function update() { + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Left Group', 300, 80); + Phaser.DebugUtils.context.fillText('Right Group', 400, 80); + Phaser.DebugUtils.context.fillText('Click an item and one from another group to replace it.', 240, 480); + } +})(); diff --git a/Tests/groups/set all.js b/Tests/groups/set all.js new file mode 100644 index 00000000..b59555ae --- /dev/null +++ b/Tests/groups/set all.js @@ -0,0 +1,31 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var baseIncSpeed = 0.006; + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.start(); + } + function create() { + // Add some items. + for (var i = 0; i < 3; i++) { + // Give the items a different alpha increase speed. + game.add.sprite(290, 98 * (i + 1), 'item', i) + .alphaIncSpeed = baseIncSpeed * (i + 1); + game.add.sprite(388, 98 * (i + 1), 'item', i + 3) + .alphaIncSpeed = baseIncSpeed * (i + 4); + } + + game.input.onTap.add(resetAlpha); + } + function resetAlpha() { + // Set "alpha" value of all the childs. + game.world.group.setAll('alpha', Math.random()); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Tap or click to set random alpha of all the items.', 240, 480); + } +})(); diff --git a/Tests/groups/sort 1.js b/Tests/groups/sort 1.js new file mode 100644 index 00000000..839971ee --- /dev/null +++ b/Tests/groups/sort 1.js @@ -0,0 +1,46 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var xTop, yTop, zTop; + + function init() { + game.load.image('cell', 'assets/sprites/diamond.png'); + game.load.start(); + } + function create() { + // Create 3 groups which will have different sort "index". + xTop = game.add.group(); + yTop = game.add.group(); + zTop = game.add.group(); + + var i; + for (i = 0; i < 64; i++) { + xTop.addNewSprite(160 + 48 * Math.cos(i * Math.PI / 8), 540 - i * 8, + 'cell', 0, + Phaser.Types.BODY_DISABLED); + } + for (i = 0; i < 64; i++) { + yTop.addNewSprite(340 + 48 * Math.cos(i * Math.PI / 8), 540 - i * 8, + 'cell', 0, + Phaser.Types.BODY_DISABLED); + } + for (i = 0; i < 64; i++) { + zTop.addNewSprite(520 + 48 * Math.cos(i * Math.PI / 8), 540 - i * 8, + 'cell', 0, + Phaser.Types.BODY_DISABLED); + } + } + function update() { + // Sort 3 groups using different methods, all of them are + // ascending by default. + xTop.sort('x'); + yTop.sort('y'); + zTop.sort('z'); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Left group sorted by x.', 16, 18); + Phaser.DebugUtils.context.fillText('Middle group sorted by y.', 16, 36); + Phaser.DebugUtils.context.fillText('Right group sorted by z.', 16, 54); + } +})(); diff --git a/Tests/groups/sort 2.js b/Tests/groups/sort 2.js new file mode 100644 index 00000000..9bde00b3 --- /dev/null +++ b/Tests/groups/sort 2.js @@ -0,0 +1,57 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + // Wabbits inside this group is sorted by its "dead" property. + // Dead wabbits behinds the others. + var wabbits; + + function init() { + game.load.image('wabbit', 'assets/sprites/wabbit.png'); + game.load.start(); + } + function create() { + // Create container group. + wabbits = game.add.group(); + + // Create wabbit and add to the container. + var wabbe; + for (var i = 0; i < 64; i++) { + wabbe = wabbits.addNewSprite(Math.random() * 480 + 64, Math.random() * 480 + 32, + 'wabbit', 0, + Phaser.Types.BODY_DISABLED); + wabbe.transform.scale.setTo(2, 2); + wabbe.transform.origin.setTo(0.5, 0.5); + + // Give wabbie a flag of living or not. + wabbe.dead = false; + + wabbe.input.start(0, false, true); + wabbe.events.onInputUp.add(killMe, this); + } + } + function update() { + // sort wabbies by "exists", so killed ones will + wabbits.sort('dead', Phaser.Group.DESCENDING); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Tap or click wabbits to kill them.', 32, 32); + } + function killMe(wabbe) { + // Disable input. + wabbe.input.stop(); + + // Do not call the kill method, set its "dead" property instead. + wabbe.dead = true; + + // Kill effects. + game.add.tween(wabbe) + .to({x: wabbe.x - 48}, 2000, Phaser.Easing.Linear.None, true, 0, false); + game.add.tween(wabbe) + .to({y: 640}, 2000 - wabbe.y, Phaser.Easing.Back.In, true, 0, false); + game.add.tween(wabbe) + .to({rotation: 240}, 1000, Phaser.Easing.Back.In, true, 0, false); + game.add.tween(wabbe.transform.scale) + .to({x: 2, y: 2}, 1000, Phaser.Easing.Bounce.In, true, 0, false); + } +})(); diff --git a/Tests/groups/sub groups.js b/Tests/groups/sub groups.js new file mode 100644 index 00000000..0b069d3c --- /dev/null +++ b/Tests/groups/sub groups.js @@ -0,0 +1,60 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + // Groups for storing friends and enemies, may use for collision later. + var friendAndFoe, + enemies; + + // Groups for teaming up stuff. + var normalBaddies, + purpleBaddies; + + function init() { + game.load.image('ufo', 'assets/sprites/ufo.png'); + game.load.image('baddie', 'assets/sprites/space-baddie.png'); + game.load.image('purple-baddie', 'assets/sprites/space-baddie-purple.png'); + game.load.start(); + } + function create() { + // Create some local groups for later use. + friendAndFoe = game.add.group(); + enemies = game.add.group(); + normalBaddies = game.add.group(); + purpleBaddies = game.add.group(); + + // Add both teams to enemies group. + enemies.add(normalBaddies); + enemies.add(purpleBaddies); + + // Create a ufo as a friend sprite. + friendAndFoe.addNewSprite(200, 240, 'ufo', null, Phaser.Types.BODY_DISABLED); + + // Create some enemies. + for (var i = 0; i < 16; i++) { + createBaddie(); + } + + // Tap to create new baddie sprites. + game.input.onTap.add(createBaddie, this); + } + function createBaddie() { + var baddie; + if (Math.random() > 0.5) { + baddie = purpleBaddies.addNewSprite(360 + Math.random() * 200, 120 + Math.random() * 200, + 'purple-baddie', null, Phaser.Types.BODY_DISABLED); + } + else { + baddie = normalBaddies.addNewSprite(360 + Math.random() * 200, 120 + Math.random() * 200, + 'baddie', null, Phaser.Types.BODY_DISABLED); + } + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Tap screen or click to create new baddies.', 16, 24); + Phaser.DebugUtils.context.fillText('enemies: ' + enemies.length + ' (actually ' + enemies.length + ' groups)', 16, 48); + Phaser.DebugUtils.context.fillText('normal baddies: ' + normalBaddies.length, 16, 60); + Phaser.DebugUtils.context.fillText('purple baddies: ' + purpleBaddies.length, 16, 72); + Phaser.DebugUtils.context.fillText('friends: ' + friendAndFoe.length, 16, 96); + } +})(); diff --git a/Tests/input/drop limitation.js b/Tests/input/drop limitation.js new file mode 100644 index 00000000..69c382fc --- /dev/null +++ b/Tests/input/drop limitation.js @@ -0,0 +1,46 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + function init() { + game.load.spritesheet('item', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.start(); + } + function create() { + // Add some items to left side, and set a onDragStop listener + // to limit its location when dropped. + var item; + for (var i = 0; i < 6; i++) { + // Directly create sprites from the left group. + item = game.add.sprite(90, 90 * i, 'item', i); + // Enable input detection, then it's possible be dragged. + item.input.start(0, false, true); + // Make this item draggable. + item.input.enableDrag(); + // Then we make it snap to left and right side, + // also make it only snaps when released. + item.input.enableSnap(90, 90, false, true); + // Limit drop location to only the 2 columns. + item.events.onDragStop.add(fixLocation); + } + } + function update() { + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Group Left.', 100, 560); + Phaser.DebugUtils.context.fillText('Group Right.', 280, 560); + } + function fixLocation(item) { + // Move the items when it is already dropped. + if (item.x < 90) { + item.x = 90; + } + else if (item.x > 180 && item.x < 270) { + item.x = 180; + } + else if (item.x > 360) { + item.x = 270; + } + } +})(); diff --git a/Tests/input/keyboard 1.js b/Tests/input/keyboard 1.js new file mode 100644 index 00000000..04811a15 --- /dev/null +++ b/Tests/input/keyboard 1.js @@ -0,0 +1,79 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update); + + var ufo, leftBtn, rightBtn; + var speed = 4; + + function init() { + game.world.setSize(1280, 600, true); + game.load.image('ground', 'assets/tests/ground-2x.png'); + game.load.image('river', 'assets/tests/river-2x.png'); + game.load.image('sky', 'assets/tests/sky-2x.png'); + game.load.image('cloud0', 'assets/tests/cloud-big-2x.png'); + game.load.image('cloud1', 'assets/tests/cloud-narrow-2x.png'); + game.load.image('cloud2', 'assets/tests/cloud-small-2x.png'); + + game.load.spritesheet('button', 'assets/buttons/arrow-button.png', 112, 95); + + game.load.spritesheet('ufo', 'assets/sprites/ufo.png', 24, 21); + + game.load.start(); + } + function create() { + // background images + game.add.sprite(0, 0, 'sky') + .transform.scrollFactor.setTo(0, 0); + game.add.sprite(0, 360, 'ground') + .transform.scrollFactor.setTo(0.5, 0.5); + game.add.sprite(0, 400, 'river') + .transform.scrollFactor.setTo(1.3, 1.3); + game.add.sprite(200, 120, 'cloud0') + .transform.scrollFactor.setTo(0.3, 0.3); + game.add.sprite(-60, 120, 'cloud1') + .transform.scrollFactor.setTo(0.5, 0.3); + game.add.sprite(900, 170, 'cloud2') + .transform.scrollFactor.setTo(0.7, 0.3); + + // Create a ufo spirte as player. + ufo = game.add.sprite(320, 240, 'ufo'); + ufo.animations.add('fly', null, 30, false); + ufo.animations.play('fly'); + ufo.transform.origin.setTo(0.5, 0.5); + + // Make the default camera follow the ufo. + game.camera.follow(ufo); + + // Add 2 sprite to display hold direction. + leftBtn = game.add.sprite(160 - 112, 200, 'button', 0); + leftBtn.transform.scrollFactor.setTo(0, 0); + leftBtn.alpha = 0; + rightBtn = game.add.sprite(640 - 112, 200, 'button', 1); + rightBtn.alpha = 0; + rightBtn.transform.scrollFactor.setTo(0, 0); + } + function update() { + // Check key states every frame. + // Move ONLY one of the left and right key is hold. + if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT) && + !game.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) { + ufo.x -= speed; + ufo.rotation = -15; + leftBtn.alpha = 0.6; + } + else if (game.input.keyboard.isDown(Phaser.Keyboard.RIGHT) && + !game.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { + ufo.x += speed; + ufo.rotation = 15; + rightBtn.alpha = 0.6; + } + else { + ufo.rotation = 0; + leftBtn.alpha = rightBtn.alpha = 0; + } + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Hold left/right to move the ufo.'); + } +})(); diff --git a/Tests/input/keyboard 2.js b/Tests/input/keyboard 2.js new file mode 100644 index 00000000..813551b1 --- /dev/null +++ b/Tests/input/keyboard 2.js @@ -0,0 +1,106 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update); + + var ufo, leftBtn, rightBtn, spaceBtn; + var speed = 4; + + function init() { + game.world.setSize(1280, 600, true); + game.load.image('ground', 'assets/tests/ground-2x.png'); + game.load.image('river', 'assets/tests/river-2x.png'); + game.load.image('sky', 'assets/tests/sky-2x.png'); + game.load.image('cloud0', 'assets/tests/cloud-big-2x.png'); + game.load.image('cloud1', 'assets/tests/cloud-narrow-2x.png'); + game.load.image('cloud2', 'assets/tests/cloud-small-2x.png'); + + game.load.spritesheet('button', 'assets/buttons/arrow-button.png', 112, 95); + game.load.image('spacebar', 'assets/buttons/spacebar.png'); + + game.load.spritesheet('ufo', 'assets/sprites/ufo.png', 24, 21); + + game.load.start(); + } + function create() { + // background images + game.add.sprite(0, 0, 'sky') + .transform.scrollFactor.setTo(0, 0); + game.add.sprite(0, 360, 'ground') + .transform.scrollFactor.setTo(0.5, 0.5); + game.add.sprite(0, 400, 'river') + .transform.scrollFactor.setTo(1.3, 1.3); + game.add.sprite(200, 120, 'cloud0') + .transform.scrollFactor.setTo(0.3, 0.3); + game.add.sprite(-60, 120, 'cloud1') + .transform.scrollFactor.setTo(0.5, 0.3); + game.add.sprite(900, 170, 'cloud2') + .transform.scrollFactor.setTo(0.7, 0.3); + + // Create a ufo spirte as player. + ufo = game.add.sprite(320, 240, 'ufo'); + ufo.animations.add('fly', null, 30, false); + ufo.animations.play('fly'); + ufo.transform.origin.setTo(0.5, 0.5); + + // Make the default camera follow the ufo. + game.camera.follow(ufo); + + // Add 2 sprite to display hold direction. + leftBtn = game.add.sprite(160 - 112 / 2, 200, 'button', 0); + leftBtn.transform.scrollFactor.setTo(0, 0); + leftBtn.alpha = 0; + rightBtn = game.add.sprite(640 - 112 / 2, 200, 'button', 1); + rightBtn.alpha = 0; + rightBtn.transform.scrollFactor.setTo(0, 0); + // Add a sprite to display spacebar press. + spaceBtn = game.add.sprite(400 - 112, 100, 'spacebar'); + spaceBtn.transform.scrollFactor.setTo(0, 0); + spaceBtn.alpha = 0; + + // Prevent directions and space key events bubbling up to browser, + // since these keys will make web page scroll which is not + // expected. + game.input.keyboard.addKeyCapture([ + Phaser.Keyboard.LEFT, + Phaser.Keyboard.RIGHT, + Phaser.Keyboard.UP, + Phaser.Keyboard.DOWN, + Phaser.Keyboard.SPACEBAR + ]); + } + function update() { + // Check key states every frame. + // Move ONLY one of the left and right key is hold. + if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT) && + !game.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) { + ufo.x -= speed; + ufo.rotation = -15; + leftBtn.alpha = 0.6; + } + else if (game.input.keyboard.isDown(Phaser.Keyboard.RIGHT) && + !game.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { + ufo.x += speed; + ufo.rotation = 15; + rightBtn.alpha = 0.6; + } + else { + ufo.rotation = 0; + leftBtn.alpha = rightBtn.alpha = 0; + } + + // 50 is a good choice if you are running 60FPS. + if (game.input.keyboard.justPressed(Phaser.Keyboard.SPACEBAR, 50)) { + console.log('space bar pressed'); + spaceBtn.alpha = 1; + } + if (spaceBtn.alpha > 0) { + spaceBtn.alpha -= 0.03; + } + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Hold left/right to move the ufo.', 16, 32); + Phaser.DebugUtils.context.fillText('Direction and Space key events are stopped by Phaser now, which will no longer be sent to the browser.', 16, 48); + Phaser.DebugUtils.context.fillText('Now you can press UP/DOWN or SPACE to see what happened.', 16, 64); + } +})(); diff --git a/Tests/misc/color utils 1.js b/Tests/misc/color utils 1.js new file mode 100644 index 00000000..8943d4d0 --- /dev/null +++ b/Tests/misc/color utils 1.js @@ -0,0 +1,49 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var colorWheel, selected, color = '#CCA22B'; + var offset = { + x: 300 - 578 / 2, + y: 300 - 550 / 2 + }; + var rect, rectSize = 24; + + function init() { + game.load.image('color-wheel', 'assets/pics/color-wheel.png'); + game.load.start(); + } + function create() { + // Create color wheel texture. + colorWheel = game.add.dynamicTexture(578, 550); + colorWheel.pasteImage('color-wheel'); + + // Create a rectangle shows the color you just selected. + rect = new Phaser.Rectangle(0, 0, rectSize, rectSize); + selected = game.add.sprite(600, 430); + selected.width = rectSize; + selected.height = rectSize; + selected.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + selected.texture.dynamicTexture.fillRect(rect, color); + + // Get the color under the position you tapped or clicked. + var pos = {}; + game.input.onTap.add(function(pointer) { + pos.x = pointer.position.x - offset.x; + pos.y = pointer.position.y - offset.y; + color = Phaser.ColorUtils.RGBtoWebstring(colorWheel.getPixel32(pos.x, pos.y)); + + // Set the rectangle's color to new selected one. + selected.texture.dynamicTexture.fillRect(rect, color); + }); + + // Set the background color to white. + game.stage.backgroundColor = '#fff'; +} + function render() { + colorWheel.render(offset.x, offset.y); + + Phaser.DebugUtils.context.fillStyle = '#000'; + Phaser.DebugUtils.context.fillText('Tap or click the color wheel to select a color.', 480, 52); + Phaser.DebugUtils.context.fillText(color, 646, 452); + } +})(); diff --git a/Tests/misc/color utils 2.js b/Tests/misc/color utils 2.js new file mode 100644 index 00000000..08d11e06 --- /dev/null +++ b/Tests/misc/color utils 2.js @@ -0,0 +1,57 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var colorWheel, selected, + color = 0xAACCA22B, colorStr = '#CCA22B'; + var offset = { + x: 300 - 578 / 2, + y: 300 - 550 / 2 + }; + var rect, rectSize = 24; + + function init() { + game.load.image('color-wheel', 'assets/pics/color-wheel.png'); + game.load.start(); + } + function create() { + // Create color wheel texture. + colorWheel = game.add.dynamicTexture(578, 550); + colorWheel.pasteImage('color-wheel'); + + // Create a rectangle shows the color you just selected. + rect = new Phaser.Rectangle(0, 0, rectSize, rectSize); + selected = game.add.sprite(600, 430); + selected.width = rectSize; + selected.height = rectSize; + selected.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + selected.texture.dynamicTexture.fillRect(rect, colorStr); + + // Get the color under the position you tapped or clicked. + var pos = {}; + game.input.onTap.add(function(pointer) { + pos.x = pointer.position.x - offset.x; + pos.y = pointer.position.y - offset.y; + color = colorWheel.getPixel32(pos.x, pos.y); + colorStr = Phaser.ColorUtils.RGBtoWebstring(color); + + // Set the rectangle's color to new selected one. + selected.texture.dynamicTexture.fillRect(rect, colorStr); + }); + + // Set the background color to white. + game.stage.backgroundColor = '#fff'; +} + function render() { + colorWheel.render(offset.x, offset.y); + + Phaser.DebugUtils.context.fillStyle = '#000'; + Phaser.DebugUtils.context.fillText('Tap or click the color wheel to select a color.', 480, 52); + + // Display more color formated infos here. You can also get a + // string contains everything using getColorInfo(); + Phaser.DebugUtils.context.fillText('Web String: ' + colorStr, 600, 492); + Phaser.DebugUtils.context.fillText('Hex String: ' + Phaser.ColorUtils.RGBtoHexstring(color), 600, 508); + var hsv = Phaser.ColorUtils.RGBtoHSV(color); + Phaser.DebugUtils.context.fillText('HSV: (' + hsv.hue.toFixed() + ', ' + hsv.saturation.toFixed(2) + ', ' + hsv.value.toFixed(2) + ')', 600, 524); + } +})(); diff --git a/Tests/misc/color utils 3.js b/Tests/misc/color utils 3.js new file mode 100644 index 00000000..ae638d32 --- /dev/null +++ b/Tests/misc/color utils 3.js @@ -0,0 +1,151 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + var colorWheel, + selected, + compHarmony, + analoHarmony, analoHarmony1, + splitHarmony, splitHarmony1, + triaHarmony, triaHarmony1, + color = 0xAACCA22B, colorStr = '#CCA22B', + compHColor, + analoColor, splitColor, triaColor, + analoColor1, splitColor1, triaColor1; + + var offset = { + x: 300 - 578 / 2, + y: 300 - 550 / 2 + }; + var rect, rectSize = 24; + + function init() { + game.load.image('color-wheel', 'assets/pics/color-wheel.png'); + game.load.start(); + } + function create() { + // Create color wheel texture. + colorWheel = game.add.dynamicTexture(578, 550); + colorWheel.pasteImage('color-wheel'); + + // Create a rectangle shows the color you just selected. + rect = new Phaser.Rectangle(0, 0, rectSize, rectSize); + selected = game.add.sprite(700, 290); + selected.width = rectSize; + selected.height = rectSize; + selected.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + selected.texture.dynamicTexture.fillRect(rect, colorStr); + + // Create rectangles to show the harmony colors to the selected one. + compHColor = Phaser.ColorUtils.getComplementHarmony(color), + analoColor = Phaser.ColorUtils.getAnalogousHarmony(color), + splitColor = Phaser.ColorUtils.getSplitComplementHarmony(color), + triaColor = Phaser.ColorUtils.getTriadicHarmony(color); + + // Complement + compHarmony = game.add.sprite(700, 390); + compHarmony.width = rectSize; + compHarmony.height = rectSize; + compHarmony.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + compHarmony.texture.dynamicTexture.fillRect(rect, Phaser.ColorUtils.RGBtoWebstring(compHColor)); + + // Analogous + analoHarmony = game.add.sprite(700, 434); + analoHarmony.width = rectSize; + analoHarmony.height = rectSize; + analoHarmony.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + analoHarmony.texture.dynamicTexture.fillRect(rect, Phaser.ColorUtils.RGBtoWebstring(analoColor.color2)); + + analoHarmony1 = game.add.sprite(744, 434); + analoHarmony1.width = rectSize; + analoHarmony1.height = rectSize; + analoHarmony1.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + analoHarmony1.texture.dynamicTexture.fillRect(rect, Phaser.ColorUtils.RGBtoWebstring(analoColor.color3)); + + // Split Complement + splitHarmony = game.add.sprite(700, 478); + splitHarmony.width = rectSize; + splitHarmony.height = rectSize; + splitHarmony.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + splitHarmony.texture.dynamicTexture.fillRect(rect, Phaser.ColorUtils.RGBtoWebstring(splitColor.color2)); + + splitHarmony1 = game.add.sprite(744, 478); + splitHarmony1.width = rectSize; + splitHarmony1.height = rectSize; + splitHarmony1.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + splitHarmony1.texture.dynamicTexture.fillRect(rect, Phaser.ColorUtils.RGBtoWebstring(splitColor.color3)); + + // Triadic + triaHarmony = game.add.sprite(700, 522); + triaHarmony.width = rectSize; + triaHarmony.height = rectSize; + triaHarmony.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + triaHarmony.texture.dynamicTexture.fillRect(rect, Phaser.ColorUtils.RGBtoWebstring(triaColor.color2)); + + triaHarmony1 = game.add.sprite(744, 522); + triaHarmony1.width = rectSize; + triaHarmony1.height = rectSize; + triaHarmony1.texture.loadDynamicTexture(game.add.dynamicTexture(rectSize, rectSize)); + triaHarmony1.texture.dynamicTexture.fillRect(rect, Phaser.ColorUtils.RGBtoWebstring(triaColor.color3)); + + // Get the color under the position you tapped or clicked. + var pos = {}; + game.input.onTap.add(function(pointer) { + pos.x = pointer.position.x - offset.x; + pos.y = pointer.position.y - offset.y; + + // Update colors. + color = colorWheel.getPixel32(pos.x, pos.y); + compHColor = Phaser.ColorUtils.getComplementHarmony(color), + analoColor = Phaser.ColorUtils.getAnalogousHarmony(color).color2, + analoColor1 = Phaser.ColorUtils.getAnalogousHarmony(color).color3, + splitColor = Phaser.ColorUtils.getSplitComplementHarmony(color).color2, + splitColor1 = Phaser.ColorUtils.getSplitComplementHarmony(color).color3, + triaColor = Phaser.ColorUtils.getTriadicHarmony(color).color2; + triaColor1 = Phaser.ColorUtils.getTriadicHarmony(color).color3; + + // Calc color strings. + colorStr = Phaser.ColorUtils.RGBtoWebstring(color); + var compStr = Phaser.ColorUtils.RGBtoWebstring(compHColor), + analStr = Phaser.ColorUtils.RGBtoWebstring(analoColor), + analStr1 = Phaser.ColorUtils.RGBtoWebstring(analoColor1), + spliStr = Phaser.ColorUtils.RGBtoWebstring(splitColor), + spliStr1 = Phaser.ColorUtils.RGBtoWebstring(splitColor1), + triaStr = Phaser.ColorUtils.RGBtoWebstring(triaColor), + triaStr1 = Phaser.ColorUtils.RGBtoWebstring(triaColor1); + + // Update color of the rectangles. + selected.texture.dynamicTexture.fillRect(rect, colorStr); + compHarmony.texture.dynamicTexture.fillRect(rect, compStr); + analoHarmony.texture.dynamicTexture.fillRect(rect, analStr); + analoHarmony1.texture.dynamicTexture.fillRect(rect, analStr1); + splitHarmony.texture.dynamicTexture.fillRect(rect, spliStr); + splitHarmony1.texture.dynamicTexture.fillRect(rect, spliStr1); + triaHarmony.texture.dynamicTexture.fillRect(rect, triaStr); + triaHarmony1.texture.dynamicTexture.fillRect(rect, triaStr1); + }); + + // Set the background color to white. + game.stage.backgroundColor = '#fff'; + } + function render() { + colorWheel.render(offset.x, offset.y); + + Phaser.DebugUtils.context.fillStyle = '#000'; + Phaser.DebugUtils.context.fillText('Tap or click the color wheel to select a color.', 480, 52); + Phaser.DebugUtils.context.fillText('All the harmony colors are calculated on the fly.', 480, 590); + + // Display more color formated infos here. You can also get a + // string contains everything using getColorInfo(); + Phaser.DebugUtils.context.fillText('Selected Color: ', 600, 312); + Phaser.DebugUtils.context.fillText('Web String: ' + colorStr, 640, 342); + Phaser.DebugUtils.context.fillText('Hex String: ' + Phaser.ColorUtils.RGBtoHexstring(color), 640, 358); + var hsv = Phaser.ColorUtils.RGBtoHSV(color); + Phaser.DebugUtils.context.fillText('HSV: (' + hsv.hue.toFixed() + ', ' + hsv.saturation.toFixed(2) + ', ' + hsv.value.toFixed(2) + ')', 640, 374); + + // Harmony color types info. + Phaser.DebugUtils.context.fillText('Complement Harmony Color: ', 540, 412); + Phaser.DebugUtils.context.fillText(' Analogous Harmony Color: ', 540, 456); + Phaser.DebugUtils.context.fillText('Split Complement Harmony Color: ', 502, 500); + Phaser.DebugUtils.context.fillText(' Triadic Harmony Color: ', 540, 544); + } +})(); diff --git a/Tests/misc/dynamic texture 1.js b/Tests/misc/dynamic texture 1.js new file mode 100644 index 00000000..579b1e3c --- /dev/null +++ b/Tests/misc/dynamic texture 1.js @@ -0,0 +1,23 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, null, create, null, render); + + // Pattern texture for tiling render to the stage later. + var pattern; + + function create() { + // Create a simple pattern texture. + pattern = game.add.dynamicTexture(96, 96); + pattern.fillRect(new Phaser.Rectangle(0, 0, 48, 48), '#5b35c0'); + pattern.fillRect(new Phaser.Rectangle(48, 48, 48, 48), '#5b35c0'); + pattern.fillRect(new Phaser.Rectangle(48, 0, 48, 48), '#43baf3'); + pattern.fillRect(new Phaser.Rectangle(0, 48, 48, 48), '#43baf3'); + } + function render() { + // Directly render the texture to the stage. + // In screen coordinates. + pattern.render(400 - 48, 300 - 48); + + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('There\'s no sprites here, only a DynamicTexture created on the fly.', 210, 450); + } +})(); diff --git a/Tests/misc/dynamic texture 2.js b/Tests/misc/dynamic texture 2.js new file mode 100644 index 00000000..1e362587 --- /dev/null +++ b/Tests/misc/dynamic texture 2.js @@ -0,0 +1,27 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, null, create, null, render); + + // Pattern texture for tiling render to the stage later. + var pattern; + + function create() { + // Create a simple pattern texture. + pattern = game.add.dynamicTexture(96, 96); + pattern.fillRect(new Phaser.Rectangle(0, 0, 48, 48), '#5b35c0'); + pattern.fillRect(new Phaser.Rectangle(48, 48, 48, 48), '#5b35c0'); + pattern.fillRect(new Phaser.Rectangle(48, 0, 48, 48), '#43baf3'); + pattern.fillRect(new Phaser.Rectangle(0, 48, 48, 48), '#43baf3'); + + // Create a sprite and load to our newly created DynamicTexture. + // Notice that loadDynamicTexture() will destroy sprite's AnimationManager, + // so all the animations already added will no longer exist. + var sprite = game.add.sprite(game.stage.centerX - 48, game.stage.centerY - 48); + sprite.texture.loadDynamicTexture(pattern); + + console.log('Size of the sprite is now: (' + sprite.width + ', ' + sprite.height + ').'); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('This is a sprite whose appearance defined by a DynamicTexture created on the fly.', 160, 450); + } +})(); diff --git a/Tests/misc/dynamic texture 3.js b/Tests/misc/dynamic texture 3.js new file mode 100644 index 00000000..8758bb15 --- /dev/null +++ b/Tests/misc/dynamic texture 3.js @@ -0,0 +1,32 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render); + + // Pattern texture for tiling render to the stage later. + var pattern; + + function init() { + game.load.spritesheet('cat', 'assets/sprites/baddie_cat_1.png', 16, 16); + game.load.start(); + } + function create() { + // Create a simple pattern first. + pattern = game.add.dynamicTexture(64, 16); + for (var i = 0; i < 4; i++) { + pattern.fillRect(new Phaser.Rectangle(i * 16, 0, 8, 8), '#5b35c0'); + pattern.fillRect(new Phaser.Rectangle((i + 1) * 16 - 8, 8, 8, 8), '#5b35c0'); + pattern.fillRect(new Phaser.Rectangle((i + 1) * 16 - 8, 0, 8, 8), '#43baf3'); + pattern.fillRect(new Phaser.Rectangle(i * 16, 8, 8, 8), '#43baf3'); + } + + // Paste cat image to the texture, so the cat is on top of our pattern. + pattern.pasteImage('cat'); + + // Create a sprite with our result texture. + var sprite = game.add.sprite(game.stage.centerX - 32, game.stage.centerY - 8); + sprite.texture.loadDynamicTexture(pattern); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Paste exist spritesheet to a DynamicTexture created on the fly.', 220, 450); + } +})(); diff --git a/Tests/misc/rectangle utils 1.js b/Tests/misc/rectangle utils 1.js new file mode 100644 index 00000000..c3724d29 --- /dev/null +++ b/Tests/misc/rectangle utils 1.js @@ -0,0 +1,10 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create); + + function init() { + // body... + } + function create() { + + } +})(); diff --git a/Tests/sprites/out of screen.js b/Tests/sprites/out of screen.js new file mode 100644 index 00000000..efba6bc9 --- /dev/null +++ b/Tests/sprites/out of screen.js @@ -0,0 +1,29 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + function init() { + game.load.spritesheet('rect', 'assets/buttons/number-buttons-90x90.png', 90, 90); + game.load.start(); + } + function create() { + var rect, factor; + for (var i = 0; i < 16; i++) { + rect = game.add.sprite(10 + Math.random() * 700, 10 + Math.random() * 300, 'rect', Math.round(Math.random() * 5)); + factor = 0.2 + Math.random() * 2; + rect.transform.scale.setTo(factor, factor); + factor = 2 + Math.random() * 6; + rect.speed = factor; + } + console.log(game.world.group.length); + } + function update() { + game.world.group.forEach(function(rect) { + // Apply speed to each rect. + rect.y += rect.speed; + // Move the rect back to screen if it's not. + if (!Phaser.SpriteUtils.onScreen(rect)) { + rect.y = 0; + } + }); + } +})(); diff --git a/Tests/sprites/rotate around.js b/Tests/sprites/rotate around.js new file mode 100644 index 00000000..01efc3fc --- /dev/null +++ b/Tests/sprites/rotate around.js @@ -0,0 +1,40 @@ +(function() { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var rotationSrv, angle = 0; + + function init() { + game.load.spritesheet('x', 'assets/sprites/x.png', 220, 160); + game.load.start(); + } + function create() { + // Create 6 sprites rotating around the center at the beginning. + for (var i = 0; i < 3; i++) { + game.add.sprite(210 - ((i % 2) ? 140 : 0), 120 * (i + 1), 'x', i); + game.add.sprite(370 + ((i % 2) ? 140 : 0), 120 * (i + 1), 'x', i + 3); + } + + // Set a default rotation server pointer. + rotationSrv = new Phaser.Point(game.stage.centerX, game.stage.centerY); + + // Rotate all the sprites around the touch point. + game.input.onTap.add(function(pointer) { + rotationSrv.x = pointer.position.x; + rotationSrv.y = pointer.position.y; + }); + } + function update() { + angle += 0.1; + + game.world.group.forEach(function(obj) { + var resPointer = new Phaser.Point(obj.x, obj.y); + Phaser.PointUtils.rotateAroundPoint(resPointer, rotationSrv, angle); + obj.x = resPointer.x; + obj.y = resPointer.y; + }); + } + function render() { + Phaser.DebugUtils.context.fillStyle = '#fff'; + Phaser.DebugUtils.context.fillText('Tap or click to set new rotation point.', 280, 100); + } +})(); diff --git a/Tests/tweens/easing example 1.js b/Tests/tweens/easing example 1.js new file mode 100644 index 00000000..bc2a3976 --- /dev/null +++ b/Tests/tweens/easing example 1.js @@ -0,0 +1,22 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create); + + function init() { + game.load.spritesheet('shadow', 'assets/tests/tween/shadow.png', 80, 15); + game.load.spritesheet('PHASER', 'assets/tests/tween/PHASER.png', 70, 90); + game.load.start(); + } + function create() { + var item; + for (var i = 0; i < 6; i++) { + item = game.add.sprite(190 + 69 * i, -100, 'PHASER', i); + // Add a simple bounce tween to each character's position. + game.add.tween(item) + .to({y: 240}, 2400, Phaser.Easing.Bounce.Out, true, 1000 + 400 * i, false); + } + + // Set background color to white. + game.stage.backgroundColor = '#fff'; + } +})(); diff --git a/Tests/tweens/easing example 2.js b/Tests/tweens/easing example 2.js new file mode 100644 index 00000000..491ebd49 --- /dev/null +++ b/Tests/tweens/easing example 2.js @@ -0,0 +1,27 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create); + + function init() { + game.load.spritesheet('shadow', 'assets/tests/tween/shadow.png', 80, 15); + game.load.spritesheet('PHASER', 'assets/tests/tween/PHASER.png', 70, 90); + game.load.start(); + } + function create() { + var item; + for (var i = 0; i < 6; i++) { + item = game.add.sprite(190 + 69 * i, -100, 'PHASER', i); + // Add a simple bounce tween to each character's position. + game.add.tween(item) + .to({y: 240}, 2400, Phaser.Easing.Bounce.Out, true, 1000 + 400 * i, false); + // Add another rotation tween to the same character. + // Notice that depends on the default origin setting, characters will + // rotate around the left-top corner which looks a little strange. + game.add.tween(item) + .to({rotation: 360}, 2400, Phaser.Easing.Cubic.In, true, 1000 + 400 * i, false); + } + + // Set background color to white. + game.stage.backgroundColor = '#fff'; + } +})(); diff --git a/Tests/tweens/easing example 3.js b/Tests/tweens/easing example 3.js new file mode 100644 index 00000000..b914e734 --- /dev/null +++ b/Tests/tweens/easing example 3.js @@ -0,0 +1,27 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create); + + function init() { + game.load.spritesheet('shadow', 'assets/tests/tween/shadow.png', 80, 15); + game.load.spritesheet('PHASER', 'assets/tests/tween/PHASER.png', 70, 90); + game.load.start(); + } + function create() { + var item, tween; + for (var i = 0; i < 6; i++) { + item = game.add.sprite(190 + 69 * i, -100, 'PHASER', i); + // Add a simple bounce tween to each character's position. + tween = game.add.tween(item) + .to({y: 240}, 2400, Phaser.Easing.Bounce.Out, true, 1000 + 400 * i, false); + // Chain another tween to the character after it falls down. + tween.chain( + game.add.tween(item) + .to({y: 640}, 1400, Phaser.Easing.Circular.In, false, 0, false) + ); + } + + // Set background color to white. + game.stage.backgroundColor = '#fff'; + } +})(); diff --git a/Tests/tweens/easing example 4.js b/Tests/tweens/easing example 4.js new file mode 100644 index 00000000..b31b6c09 --- /dev/null +++ b/Tests/tweens/easing example 4.js @@ -0,0 +1,37 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create); + + function init() { + game.load.spritesheet('shadow', 'assets/tests/tween/shadow.png', 80, 15); + game.load.spritesheet('PHASER', 'assets/tests/tween/PHASER.png', 70, 90); + game.load.start(); + } + function create() { + var item, tween; + for (var i = 0; i < 6; i++) { + item = game.add.sprite(190 + 69 * i, -100, 'PHASER', i); + // Set origin to the center to make the rotation look better. + item.transform.origin.setTo(0.5, 0.5); + // Add a simple bounce tween to each character's position. + tween = game.add.tween(item) + .to({y: 240}, 2400, Phaser.Easing.Bounce.Out, true, 1000 + 400 * i, false); + // A more complex chain, add a falling and a rotating after + // the first tween done. Notice that tween.chain(t1).chain(t2) + // will not chain the t2 to the t1, they're both chained to + // the tween itself so they'll start the same time. + tween + .chain( + game.add.tween(item) + .to({y: 640}, 1400, Phaser.Easing.Circular.In, false, 0, false) + ) + .chain( + game.add.tween(item) + .to({rotation: 720}, 1600, Phaser.Easing.Cubic.Out, false, 200, false) + ); + } + + // Set background color to white. + game.stage.backgroundColor = '#fff'; + } +})(); diff --git a/Tests/tweens/easing example 5.js b/Tests/tweens/easing example 5.js new file mode 100644 index 00000000..aa0f276d --- /dev/null +++ b/Tests/tweens/easing example 5.js @@ -0,0 +1,38 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create); + + function init() { + game.load.spritesheet('shadow', 'assets/tests/tween/shadow.png', 138, 15); + game.load.spritesheet('PHASER', 'assets/tests/tween/PHASER.png', 70, 90); + game.load.start(); + } + function create() { + var item, shadow, tween; + for (var i = 0; i < 6; i++) { + // Add a shadow to the location which characters will land on. + // And tween their size to make them look like a real shadow. + // Put the following code before items to give shadow a lower + // render order. + shadow = game.add.sprite(190 + 69 * i, 284, 'shadow'); + // Set shadow's size 0 so that it'll be invisible at the beginning. + shadow.transform.scale.setTo(0.0, 0.0); + // Also set the origin to the center since we don't want to + // see the shadow scale to the left top. + shadow.transform.origin.setTo(0.5, 0.5); + game.add.tween(shadow.transform.scale) + .to({x: 1.0, y: 1.0}, 2400, Phaser.Easing.Bounce.Out, true, 1000 + 400 * i, false); + + // Add characters on top of shadows. + item = game.add.sprite(190 + 69 * i, -100, 'PHASER', i); + // Set origin to the center to make the rotation look better. + item.transform.origin.setTo(0.5, 0.5); + // Add a simple bounce tween to each character's position. + tween = game.add.tween(item) + .to({y: 240}, 2400, Phaser.Easing.Bounce.Out, true, 1000 + 400 * i, false); + } + + // Set background color to white. + game.stage.backgroundColor = '#fff'; + } +})(); diff --git a/Tests/tweens/easing example 6.js b/Tests/tweens/easing example 6.js new file mode 100644 index 00000000..023b251b --- /dev/null +++ b/Tests/tweens/easing example 6.js @@ -0,0 +1,53 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create); + + function init() { + game.load.spritesheet('shadow', 'assets/tests/tween/shadow.png', 80, 15); + game.load.spritesheet('PHASER', 'assets/tests/tween/PHASER.png', 70, 90); + game.load.spritesheet('ribbon', 'assets/tests/tween/ribbon.png', 731, 49); + game.load.start(); + } + function create() { + var ribbon = game.add.sprite(-1000, 256, 'ribbon'); + ribbon.transform.scale.setTo(1.2, 1.0); + // Add ribbon appear animation. + var tween = game.add.tween(ribbon) + .to({x: -218}, 2400, Phaser.Easing.Elastic.Out, true, 0, false); + + // Add characters and give them a delay so they'll appear after the + // ribbon already there. + var item; + var letterGroup = game.add.group(); + for (var i = 0; i < 6; i++) { + item = game.add.sprite(80 + 69 * i, -100, 'PHASER', i); + letterGroup.add(item); + // Set origin to the center to make the rotation look better. + item.transform.origin.setTo(0.5, 0.5); + // Add a simple bounce tween to each character's position. + tween = game.add.tween(item) + .to({y: 240}, 2000, Phaser.Easing.Bounce.Out, true, 1600 + 400 * i, false); + } + + // Move the ribbon out after the last letter landded. + // Instead of using delay, we can use the callback of tweens. + tween.onComplete.add(function() { + tween = game.add.tween(ribbon) + .to({x: -1000}, 1600, Phaser.Easing.Elastic.In, true, 500, false); + // Again add falling animations to the letter after ribbon disappeared. + tween.onComplete.add(function() { + var index = 5; + letterGroup.forEach(function(item) { + game.add.tween(item) + .to({y: 640}, 1000, Phaser.Easing.Circular.In, true, index * 100, false); + game.add.tween(item) + .to({rotation: 720}, 1600, Phaser.Easing.Cubic.Out, true, 200 + index * 300, false); + index--; + }); + }); + }); + + // Set background color to white. + game.stage.backgroundColor = '#fff'; + } +})();