From 898a67c6fa383ace6d4e3fd9f4a314d5e6603b4b Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Fri, 14 Jul 2023 22:51:29 -0500 Subject: [PATCH] feat: [#2688] Add actor level graphics flipping as a QOL improvement (#2694) Closes #2688 ## Changes: - Adds new convenience properties on the `ex.GraphicsComponent` to flip all the graphics on an `ex.Actor` * `ex.Actor.graphics.flipHorizontal` - Flips all the graphics horizontally * `ex.Actor.graphics.flipVertical` - Flips all the graphics vertically --- CHANGELOG.md | 4 + src/engine/Graphics/GraphicsComponent.ts | 10 ++ src/engine/Graphics/GraphicsSystem.ts | 25 +++- src/engine/Graphics/Text.ts | 15 +- src/spec/GraphicsSystemSpec.ts | 139 ++++++++++++++++++ .../sword-flip-both-offset.png | Bin 0 -> 770 bytes .../GraphicsSystemSpec/sword-flip-both.png | Bin 0 -> 771 bytes .../sword-flip-horizontal.png | Bin 0 -> 760 bytes .../sword-flip-vertical.png | Bin 0 -> 772 bytes src/spec/images/GraphicsSystemSpec/sword.png | Bin 0 -> 1468 bytes 10 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 src/spec/images/GraphicsSystemSpec/sword-flip-both-offset.png create mode 100644 src/spec/images/GraphicsSystemSpec/sword-flip-both.png create mode 100644 src/spec/images/GraphicsSystemSpec/sword-flip-horizontal.png create mode 100644 src/spec/images/GraphicsSystemSpec/sword-flip-vertical.png create mode 100644 src/spec/images/GraphicsSystemSpec/sword.png diff --git a/CHANGELOG.md b/CHANGELOG.md index e415e62c8..0f09ef5ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Added new convenience properties for flipping all the graphics on an Actor + * `ex.Actor.graphics.flipHorizontal` - Flips all the graphics horizontally + * `ex.Actor.graphics.flipVertical` - Flips all the graphics vertically - Added new `ex.Scene.transfer(actor)` method for transferring actors between scenes, useful if you want to only have an actor in 1 scene at a time. - Added new `ex.Material` to add custom shaders per `ex.Actor`! * This feature cant be applied using the `ex.Actor.graphics.material = material` property or by setting the material property on the `ex.ExcaliburGraphicsContext.material = material` with `.save()/.restore()` @@ -135,6 +138,7 @@ are returned ### Fixed +- Fixed issue where `ex.Text.flipHorizontal` or `ex.Text.flipVertical` would not work - Fixed issue where overriding existing components did not work properly because of deferred component removal - Fixed issue where `ex.ScreenElement` pointer events were not working by default. - Fixed issue where setting lineWidth on `ex.Circle` was not accounted for in the bitmap diff --git a/src/engine/Graphics/GraphicsComponent.ts b/src/engine/Graphics/GraphicsComponent.ts index aa0ebe90e..037c3ee81 100644 --- a/src/engine/Graphics/GraphicsComponent.ts +++ b/src/engine/Graphics/GraphicsComponent.ts @@ -307,6 +307,16 @@ export class GraphicsComponent extends Component<'ex.graphics'> { */ public anchor: Vector = Vector.Half; + /** + * Flip all graphics horizontally along the y-axis + */ + public flipHorizontal: boolean = false; + + /** + * Flip all graphics vertically along the x-axis + */ + public flipVertical: boolean = false; + /** * If set to true graphics added to the component will be copied. This can affect performance */ diff --git a/src/engine/Graphics/GraphicsSystem.ts b/src/engine/Graphics/GraphicsSystem.ts index 9e89a6d0e..07b21e6fe 100644 --- a/src/engine/Graphics/GraphicsSystem.ts +++ b/src/engine/Graphics/GraphicsSystem.ts @@ -154,11 +154,15 @@ export class GraphicsSystem extends System { await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) .toEqualImage('src/spec/images/GraphicsSystemSpec/graphics-context-opacity.png'); }); + + it('can flip graphics horizontally', async () => { + const sut = new ex.GraphicsSystem(); + const offscreenSystem = new ex.OffscreenSystem(); + engine.currentScene.camera.update(engine, 1); + engine.currentScene._initialize(engine); + engine.screen.setCurrentCamera(engine.currentScene.camera); + offscreenSystem.initialize(engine.currentScene); + sut.initialize(engine.currentScene); + + const sword = new ex.ImageSource('src/spec/images/GraphicsSystemSpec/sword.png'); + await sword.load(); + + const actor = new ex.Actor({ + x: 50, + y: 50, + height: 100, + width: 100 + }); + actor.graphics.use(sword.toSprite()); + actor.graphics.flipHorizontal = true; + + sut.notify(new ex.AddedEntity(actor)); + + offscreenSystem.update([actor]); + + engine.graphicsContext.clear(); + sut.preupdate(); + sut.update([actor], 1); + + engine.graphicsContext.flush(); + await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + .toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-horizontal.png'); + }); + + it('can flip graphics vertically', async () => { + const sut = new ex.GraphicsSystem(); + const offscreenSystem = new ex.OffscreenSystem(); + engine.currentScene.camera.update(engine, 1); + engine.currentScene._initialize(engine); + engine.screen.setCurrentCamera(engine.currentScene.camera); + offscreenSystem.initialize(engine.currentScene); + sut.initialize(engine.currentScene); + + const sword = new ex.ImageSource('src/spec/images/GraphicsSystemSpec/sword.png'); + await sword.load(); + + const actor = new ex.Actor({ + x: 50, + y: 50, + height: 100, + width: 100 + }); + actor.graphics.use(sword.toSprite()); + actor.graphics.flipVertical = true; + + sut.notify(new ex.AddedEntity(actor)); + + offscreenSystem.update([actor]); + + engine.graphicsContext.clear(); + sut.preupdate(); + sut.update([actor], 1); + + engine.graphicsContext.flush(); + await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + .toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-vertical.png'); + }); + + it('can flip graphics both horizontally and vertically', async () => { + const sut = new ex.GraphicsSystem(); + const offscreenSystem = new ex.OffscreenSystem(); + engine.currentScene.camera.update(engine, 1); + engine.currentScene._initialize(engine); + engine.screen.setCurrentCamera(engine.currentScene.camera); + offscreenSystem.initialize(engine.currentScene); + sut.initialize(engine.currentScene); + + const sword = new ex.ImageSource('src/spec/images/GraphicsSystemSpec/sword.png'); + await sword.load(); + + const actor = new ex.Actor({ + x: 50, + y: 50, + height: 100, + width: 100 + }); + actor.graphics.use(sword.toSprite()); + actor.graphics.flipVertical = true; + actor.graphics.flipHorizontal = true; + + sut.notify(new ex.AddedEntity(actor)); + + offscreenSystem.update([actor]); + + engine.graphicsContext.clear(); + sut.preupdate(); + sut.update([actor], 1); + + engine.graphicsContext.flush(); + await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + .toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-both.png'); + }); + + it('can flip graphics both horizontally and vertically with an offset', async () => { + const sut = new ex.GraphicsSystem(); + const offscreenSystem = new ex.OffscreenSystem(); + engine.currentScene.camera.update(engine, 1); + engine.currentScene._initialize(engine); + engine.screen.setCurrentCamera(engine.currentScene.camera); + offscreenSystem.initialize(engine.currentScene); + sut.initialize(engine.currentScene); + + const sword = new ex.ImageSource('src/spec/images/GraphicsSystemSpec/sword.png'); + await sword.load(); + + const actor = new ex.Actor({ + x: 50, + y: 50, + height: 100, + width: 100 + }); + actor.graphics.use(sword.toSprite()); + actor.graphics.flipVertical = true; + actor.graphics.flipHorizontal = true; + actor.graphics.offset = ex.vec(25, 25); + + sut.notify(new ex.AddedEntity(actor)); + + offscreenSystem.update([actor]); + + engine.graphicsContext.clear(); + sut.preupdate(); + sut.update([actor], 1); + + engine.graphicsContext.flush(); + await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + .toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-both-offset.png'); + }); }); diff --git a/src/spec/images/GraphicsSystemSpec/sword-flip-both-offset.png b/src/spec/images/GraphicsSystemSpec/sword-flip-both-offset.png new file mode 100644 index 0000000000000000000000000000000000000000..3e677b7dde739b112e188703ae8e6ff8ced28062 GIT binary patch literal 770 zcmeAS@N?(olHy`uVBq!ia0vp^DImVA|v9;uum9 z_jcA=KW9e)w}YQ}BuqkmS=rhTBy>M#+`!SKw1<)9ICFyIgtis|o-Tz0W*j2>m{>VE z?RP6qPc3`)ExWp4`flUjh3DqxEPenGWJF+&jghoKT_s!+ESz7a)#YWV3C`H z4u3j_(-Dr2qlr!eNXcb`Iazd!Z<&x7IHE_HVp(it%cxD=Xn5}E+u|bodjFr5lZ}rgh$*sVOjPCQT$25wSAf~% z{G_+7tCz$szwEiL{cvK@-CA3-`I>VSS)U06F|0hR%8@y_>|h*+W`oGiC;OQ`>F8hI zeRBP$?^Bk27dkf~{*02!r$Td(yP{G!os?dReOV`JAi*;!_T8@PlcoQZPNqlJdpLR= z48HwLsAY@i?gV|H8@pnJbQh@o_Sx62u;kf9&K0g63|D4N<5;=ydBAENh5w(^9`QJK z91ebalSy%&iENeP5ym9nZciZtVPJe_$vtm~YGH9$o7p)*s)}#xWSs=9^8zgvHdp8A z3Ow?>yinXpMxkm;M77ngjLYx8PcHjep8I+4zhCk@+|9E4JOAl$D~7#wEal*2%FgW; z(B8RVm%rlPxb=nXPmPmT-E$TY`m!YYw$c$Vy%l_ljtOgXcbETmT?#a#yX)AN7qYzj XXCFVEcUB*ms2Mz6{an^LB{Ts5T5mJ7 literal 0 HcmV?d00001 diff --git a/src/spec/images/GraphicsSystemSpec/sword-flip-both.png b/src/spec/images/GraphicsSystemSpec/sword-flip-both.png new file mode 100644 index 0000000000000000000000000000000000000000..3dfe9fdc84f2fe2a3a08c06282e6411f51827ad9 GIT binary patch literal 771 zcmeAS@N?(olHy`uVBq!ia0vp^DImVA|{H;uum9 z_jb-gKW0Y}*XY~%2NtZ%(lXf5CH3afPX?W)1swGZ2R|}K2rS8T;$XX~aKMa1WZxpL zNopOE8!umI2^WAFu3%vJ1{JI z)-Xfvn?OT{rGi>}3xmrUMng*#7KJ3fj+2Ut41&cDK75>v9W$7diaarg1c6MS= z0_u?wViGW9Kk{V41dbCg4_j2EZj4~L9`3Hcj)Rd&;5uvAPD{>}If?h!7*4)UfBqvm z12kZ4;^2Mlz)F{x#;^ooweWZ z89zE1UC+np@lG{vqr8Kb)QoBiha&4)JL7pW_UsR~I{f%`_=mb0zmq!pH`jfw_NZxR z@HAS#ok8L2ueo!06+F9Zxc#d953>BNv)?iA{PTlf6DpPWF5gne&)D(4$-JDs>}oZa)^w5bm`1@5!vzvC0woVt&h;nC~-O}ot-mNc|+KRE{swcqDl8yJ>6Y!9)h zecZn$*WYE26ob-K{>FS&RYjXkM~p5S2nYr4lgqmA%)lh@(mmwy{?v&R+Ju=Hq8;vj z7ieHO@~k1kl=G&T!s7F@SXJX!z% literal 0 HcmV?d00001 diff --git a/src/spec/images/GraphicsSystemSpec/sword-flip-horizontal.png b/src/spec/images/GraphicsSystemSpec/sword-flip-horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..ab79738b263848241b62bc4ce2f6353a759949d1 GIT binary patch literal 760 zcmeAS@N?(olHy`uVBq!ia0vp^DImVA|yA;uum9 z_jc|=KVwG`x0P>=6E1|9ib}+|@@}h;W9U}UI3(>*V8bBBlIg*5aDf!d4YtNZ;USKf zmL$2odG}`S?%!K&EiTQxnScJ~dh^rz|IXdJdi*}0y%CEQ$Hc;oJWT?+@*IMn8wFZc z2nw7&%;VT0!MM@Fgherl<;bH80xcba0^QEe0!nTUI()oDNapdr^w-zE`)W&lVCLOz zI!+xkGR+kazn}edEgxI+q3C%&Y6qX+z9-RgVZsCkUe3x36LPapRM_z4RhrE<4Cdn4 zVa}qMDeB@OrT_gZN1^+iGDdU7)e_6^PFDzAe?58G7DH!&N!NO=W=RG0&%UkKvSZ=e z@(sHuScn-fjSz8qWqTrTx4@&eZTAjxtUP`;H~g{F49z5C7DbN2-&G$iW~|rYU)5$D zEq*9G!h=DvZTZb)Z6})*Qs-Pc80Q@ivsCmwKV|az#DbH%|EBYwn|j`b!%53t=XC46 zdf8=)nRWt`POSd;^xCmM`{uFFbZ6gv@V)IlZ|!ZL!s+TE zxvVA|*D;uum9 z_jcA=KW0Y}x0S0_xGOFY3lVJ-5W1vr;1h!d)A|M_(Fi#Pf8Aw^_?k1MTq>F8Efkx? zW_)>r_3fR;zu$g)aO>Rp?a$BLovr&;rpKRU zV(nax9TJQiElz4b`SiL)yf{yCGbd_*SmP7_Dx<8(XgodWh7|HnM4-)Hm6DnB-U=Xt|_ zKR({x{WeN7jqyfI;pqh&2F9l|0SD$E;dfJklD6txq`~` zkNhZRT-8%iSZuuHtu3o!QoNfIi(+Vg(7TGf$ihF9&gacuaP01;n!_BPbW(%Ev>f@`v1;?|Yr#?BioWkPIX?1S`>QS1qOc@O ze1oj~?cigNKWbel`G3yr#{I0d>jhrVYZ7=|?9nR0+#AcMxRFKp{Ccenv)LD&gRC4q zymm>i>7Bs4VCkHu2~$dVuYF_vyZuSchlCBce+fl@mkDwcR7`Ez_NG|N#m9n-m=D31 bXg+giRi~I2h(B2YOx6sZu6{1-oD!M<<{?I4 literal 0 HcmV?d00001 diff --git a/src/spec/images/GraphicsSystemSpec/sword.png b/src/spec/images/GraphicsSystemSpec/sword.png new file mode 100644 index 0000000000000000000000000000000000000000..0809aa483ca2bd27701130824fd056ec2d67fa1e GIT binary patch literal 1468 zcmbVMeM}o=7%z-zhau_kBaXm!J=jFu^?Jwk<2vfrQYw{|@zDtShuQV+fD2r&++Ar) zqG&yV8KQGYjM2I!EO92x4-_jIWPeBmG(pBTC7aW*Fd+^$WHQ_m7T*G8{6Y4Im%Dr4 z=e_6meEgp0xg(_|&!?qkq^eY^G*^+M9IQtqvATc5bp2jzz>zyQz66cpm?eUc?)g;(ra zz&?JA!qAF}TxW$>gQ{|uLIr}zLI#}?p|Lz1GLbsWK;mX%FNEtcBdRA*j6|?J3%ChA zw06P3o5)mK${o%%UqG_LwX*EDplC1{)CKc&f>;BvB#G*A6vq*ufkxO;O8c~2UG(`DPOs7YAKAzW#wj`Ic_rus! zTk?ecELzS=LO`TJJ*u-4V8Gpj9mN$vG?qgm2Z}<~I|Mqwvp(77u)^Srj^P*!W}?h_ zv=KvS1~VXLoG>6>!bBsq*GQWwy`D5O#)O^C@C4zs*)R+zOc;(~g+|g~!wL%vag)u7 zJ8cF$fpz&Lnex$W!Z!zeuVJwVVl4$COUZ)h5rq0g7nIftvLMw8eyE@zUUdVMTgmwt zAt*f^FVkw)4p!tES;i>}JhZ~Ih1(y1^i&0(#GZGYHCd~$fCcS_flF*w7 z6TF6J{x5$h2nvli%ZBDj9D$CEZ`Vfw2=kRV zoyu%!IHXQclfR7bEK`WS*n~&Yq_n&-9v+On za#9+-nf&nLiA?my^lSITu~V1l{+w^Fn%}NTwyTr0%Ig~Y0ksxNQ8vnIZHkf;?SB_4 z>3uo+m%X?8>m9B(g+aG>zQE4@efq`X*4%(5(%v}S9W8sNQ*PV+hyrzGEu0^%ja{D9 zzNZwaon^WY0&U7ndtabMnYbO%WF5OVdg|Bf>E4lKZBeN8!+Xv8xXHqn=p^KnJf^q1cR-k$(g&UBjx1o7;`KMOxZl$oPRpMLZc{l|PD z`;!xIE=RxmBG?eN$|Xb7Kc@pCY^jYeby&Jea|ie3EbZKyg?%;!Z?iQjkL|j3JobBk z`sm)SnYqEcLr;ZUcWd`7PL4i#F=_dFPe$+6!FRsv8`za8jM#e~#h*QYyRCfaxaLl* ocH4b!70pI&SGbORct>+s)jSt|oQ%CS9si%Y3QHWH+Yd+n1DY}sssI20 literal 0 HcmV?d00001