From a370ec7fd0d1a9429b2325e0965a73ee0556f576 Mon Sep 17 00:00:00 2001 From: dani Date: Thu, 20 Jul 2023 04:39:34 +0000 Subject: [PATCH] all the things and even documentation... <_< --- .gitignore | 2 + .idea/.gitignore | 8 + .idea/gsa.iml | 12 ++ .idea/inspectionProfiles/Project_Default.xml | 10 ++ .idea/modules.xml | 8 + .idea/vcs.xml | 6 + Cargo.toml | 25 ++++ examples/basic.rs | 23 +++ examples/gfx.gif | Bin 0 -> 19809 bytes src/buttons.rs | 47 ++++++ src/gsa.rs | 56 +++++++ src/input.rs | 18 +++ src/lib.rs | 47 ++++++ src/rgb.rs | 10 ++ src/run.rs | 57 ++++++++ src/sprite.rs | 17 +++ src/state.rs | 146 +++++++++++++++++++ src/tilemap.rs | 22 +++ todo.md | 12 ++ 19 files changed, 526 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/gsa.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 100644 examples/basic.rs create mode 100644 examples/gfx.gif create mode 100644 src/buttons.rs create mode 100644 src/gsa.rs create mode 100644 src/input.rs create mode 100644 src/lib.rs create mode 100644 src/rgb.rs create mode 100644 src/run.rs create mode 100644 src/sprite.rs create mode 100644 src/state.rs create mode 100644 src/tilemap.rs create mode 100644 todo.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/gsa.iml b/.idea/gsa.iml new file mode 100644 index 0000000..7025ac1 --- /dev/null +++ b/.idea/gsa.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..146ab09 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..45f7941 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..889b28c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ + +[package] +name = "gsa" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lazy_static = "1.4.0" +glam = "0.24.0" +ascii = "1.1.0" +gilrs = "0.10.2" + +[dependencies.skunk2d] +path = "../skunk2d" + +[profile.release-dani] +inherits = "release" +opt-level = "z" +lto = true +codegen-units = 1 +strip = true +panic = "abort" + diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 0000000..8e71710 --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,23 @@ +use glam::IVec2; +use gsa::{run, Gsa}; + +struct Game {} + +fn init(gsa: &mut Gsa) -> Game { + gsa.sprites[0].tile = 0x0300; + gsa.sprites[1].tile = 0x0200; + gsa.maps[0].tiles[0][0] = 0x0300; + gsa.maps[1].half_tile = true; + gsa.write_string(1, IVec2::ONE, "Hello world nyaa~"); + Game {} +} + +fn update(_game: &mut Game, gsa: &mut Gsa) { + gsa.sprites[0].pos.x = (gsa.sprites[0].pos.x + 1) % 300; + gsa.sprites[1].pos += gsa.input.dir; + if gsa.input.pressed.face_down { + gsa.sprites[1].tile += 1; + } +} + +run!(init, update); diff --git a/examples/gfx.gif b/examples/gfx.gif new file mode 100644 index 0000000000000000000000000000000000000000..dbd91c9c29fa9a7267c229438187a740147085da GIT binary patch literal 19809 zcmeEtof+Iyd8owd*N=;&!H$h*+pq`P@UN7uy7O{1fu`@fw= zNBiFv{Juo3A-?3>dxa#4hU577_s$9?bn)>?_e#9gbiI#{k30H)J3B|`HcU8|Oy~wz zPyAl)Q!`n96|ANp;ca8{zx(`uY zmiK0GYa~C?YpVE?C1hUf@TIA;KS#ptYk^*K)nL9t*y{9`X7q5e+Oz8%`YqMp%XPCv z9KW{IjG|4-^$Yb|Yk$_+H2cncZLJ%}Id>;>7_?!2HG7ZNI`+5KPqhcne=Rg<$IjrR z_f}{6+i`PbXWBJR!;XeO-ASyXumKy1-@UK+42ldpn-=?XrTu0HI-4oOB^oK5MqMo{ zqt)i)=L~NT-hZfd>n}3Gw{1*ygssgE;@h|8-aos+1toOsOk?eD7#5v(>`}gz8x%u{ zT?cCu&3ytg(tOeX(_8}vfI zn%x^E)tqgX6>>5o33)WJfERPFTC(dlg(%0wXR?%Tg>&5+_E%@2giTo zEO@U@y)o(1{gbU9xw4q!oFKfE>z?hil;>SlQ{`whW;353%%WGN-G#|QA%Qd`(3RcPepa%HY(?MhWablMhAK8$Pl5tvB`w)~z=UJiRYw zHY_K)(elH}Z=-d>vu>kp`gC%l{da;WsAwkJZ?ki`s&2Duy=`q1zuhCcMc5zn+aew> z)oqc^57)NHbPQtK@2{}?Z&z$;t<`G0<^8e!;iit*&Zj%p{yU%ViJxUas6qs1$VD_E zq?a*GOsbdD3$y!GtbKjAU!qrR56M@)&e_MUV=w*X+6G3tS8iBr|C=Uz!2Wk#K)nnM zJeRsRtfs-y(yg`!f91*y`R{y(sEvbh=S1M)fXJjnO|l{%WBC`~9HZS|TX`G5AqwLc zE$ZQD)x%l%5pCmWE}j9T(CCk!arhHrPb`^E5iOFhHJ4AXo$dcw*uMC$D)i)cLDc36 zr6dt_y8J+HCi+_y8YT;`vqm1RsG!qNdb6iELYEXr3#C>FN1JC`?-|6;x8Kxo9)#9+ z51sCO#beuds)vy=o00N)drP0Za2JQuQCk;BziH1q7C=%%hki>s)Z@w3eB+b-HaFw5 zjX&c-w2Na3jz*n+I2-x*wcW(&Z*A~D>BE*b_;+K+z3=Ijwx8TgExE$usZ=XD?9$xq zys({3xE8zpjGM9as*di*NXu5-)=WEcc9_}+e+D8eKX%IO&EWWd+zgpO|z;ED@Cloe6_ls zEK!Fabru5{eE4+xaYL#d5Fv;@mTk&X4X>0))EjVpu$Rj-Y5Ox1ZTJcp7Sj)}+B>~v z^uuzLYou;=e~GOru;_N)Z$`92uij9o;Oe^pO~Bq8TchKN`ob~?&x5M_QpdK&QTx(p z@X;6AF!FS-IM>BXrt~HBDQvVb;u&B)UT*lrF(|*In<9Tn|9i6Ce%ZajYO^aMgD*r{ z@_x8`K8C~&`k(IR?B!Q$TYnwN?%J=E_^AsrZR@w^49qsJs{w^5-pl8ekPJ?(F-g?_ zQTE_xb%Exb(3ATE(Fsj4I%%~QX#JnHB$*oM&pZIF!=m!~NUbwdolUR)n1Sov+IyP9 z{N&`Zrmlnf;Iul2&f^}r}zx%S$RmuYWybPu$ zX_JR-C0{XWuA+3)r-$wDnXti$2Gf5Z9Ch>=VMBBJr}nm-4S8g2p2ovKYQ49MBGlp!6^oFr;|sd<3SwqsDF-ndPJsU zZir_v{Qb|vIP1)@Oi=eoZerM+9++M>asr-`xZ)o$o@qr~d{0VI^ zIK8=mVn|V%IvI){YA!k&pn%U#hU1xAN*IimA3r?(mIQ4nV;@}BRzCgyD!ruwV6>ud ze>##o)KVosxB`tk9W7yQt=2JGHOoK!Q4MXawH{ox!k_+ZNN>e>8Lin(osM-3wPK?N z*I;L-BIBi zSB*P9vJY)1D4+kinbGkH0NqKpKmU7gxT8mYXeT}He4d-7vrh-Qo0)&UAVf3n{AxY4 zn~gtTl*s5D@Ph8Wn>t@o7*^cU|HZ9RzbYG3X#Zu&-CFh(o+I;DF+F@}ziKTm@VD8p z(LuO%W!-93T$)885?j`)c(LYOd=w`XE}&~m%(fk_`{D6JNb91-v_j_W=ls3HuBn+R z6Pneqz2^Q$0>aFZq3f5xLR392@nsz3wf@%7agTi5c9S9Dhpk0)(H4g~jQxBOM~xaV z_`0PUMOfBB1!xTxZd^V0shMm!{ARpjQA815(?G?HOi>SqhwzK2w?{4cxA%2u`24S+ zvpXJ~M}5Y~#^IrH*{?O&+Hp}=-;C(m8#88Khe?NCg;E%e*Sk7FBpQX*ezBX+wPhDa z9W(um6#5vl+N^Va%+XV+U`ste9;TtyAu*rBkO3PyabyOhN~PMSJSx>JZWN7ToQThr z18643)R;xmO=$YITVLda!_jdM?;-zE;<@iVP3S>Y@T0ETJaJ%v^9#j|MaBD$Mk0=$ z@G3l$nec+kMsanB=#Go*>L3~!Pz{-Uw{)XqZJui@c+JSi%q2#AV2@JOMe5vpVOH&@ znHzDvIo>??g`KXSQ0CKkisrxG@t}#@mK`rVqZ1rG!!NygN@IBDcJ+n9al(Dm1aF(f zNL>%#W1)x9Pd+Ece;9f3nsO^VI`O$pk|CRp|J8_^b~P4h_%+%-GO#5&LE+^aA@|q1 zPo0F+41Od9+)j8Xne_Hva#^*zQf}hilS34c;gpx<$H@%}?n(F5@~+0cK7PJ_ zDZ%b`3SlDU4Sh1{Uh2nXSJ8==+w9@ciP)FD&*WWiwR^wlzLz$#?6O8dunNHm@^Rx= zqcGJ;Y#qs?_tO6gxx4`+Uf~y@Mn&6R225W~gdA%#T~3`>$k_LGdZ3Un78OlNdg;{n zlvOJ+-X`N8#aEZx5+NPhA?P$)vlL2h%FSD^uH1ipyT&QtUcywL=G1aBAW4ELCjJPJ z!FoTF*A^zS{G^*boH`z}r4YfMlrmhM%y&Oa>cov(F-tBcYmH`^`aLmib^OV~$aBE8 zv;wbW^l^;r%PifzH+7b2W8SgL%ShY5ZvcHS>#iofdK_m{_QtOB?Wc&0Yq@XX-h75h z88`Ia8h2)XVvn!xd+Yxz8@>3}^w;y6tH?qIdCi*SsLs@N%8S5XImi>+;FB!ZytH7c z>^7ThBR%2OBoVaklf9#ySNHQ$TQg3V-)JfF%|vHExSzAW{9>&=JM;d#GDVxGUqr-g zo?csdCUE=~|0DLna&C@~NV#Hui*0^eOn!$j2Yx(Dy)(7YC$I5Zl%x&dTdrbfOhMm? zmGennO=isfFL^2d6g+&HY?W6q7E@@^TJTmlUeG%aQWtk$Ji1dAWmq zks2!q%NpsMu_brxN*Avn(CEa7nkt(CfKG$4Lpi9mvxL;a-zXEz%;kuBt zE)?_ee$u*LfxcZz%}SBUWF^c_>zpC`US9gam$bS2<=a=ES1MFGO;!bpXnJ*3Sw1Mg zU`S`{)r*Vj4kli)k8eq&ej zIF=QdTk*`dreLxrc(t6{v|>RoqR6hcYE}73S51w6rN!6ElwkjOOsNUCrhe-6>=s&@$#)csjirbLQ(8+hq03Plxjq7)f`RY zs3HnH$*E921^9?9kIbZ$gi8%x4YfA1f#IvGcX%OlLQ3CbgTdH$IwbJbe7z zXEGmLonZAPmCrAy2K|x~-z1hIj_rE&eKqac!|bryN>$qO%kW=KO8DmG%=#-=pRXm? z2Xtf&P?9AdA`*I=_3-(r*B`uT6tbEO7>m(TuA*6-9{ zSKrmAU8}XrZ}Y7aw#MH+?P?2t*nX7xj5C+}nL@>1k@zc9<=&$0$g}o}%of@FnyY7+ z#g*4j@o24U9jQ|tIQ|wZ9ago@*gtxRhl=&-`JM0V`B!Rl+zpzh6g#GBIskr|tY+2u z`CYXS1xxCR%K9Dp*2UpF^7(Jx$K$5(nA-S|Ur&}5I`8M>d*TS-ru^=c zPNR3J`w!auKeaBLyva==jKvY-KjJb^(+=AkCeDbzX?PxQfcfW%M0UT%fjH71JZb#~ z|IR6X?YQT;Q}{nCx8jolsEHV6vHBzBIUUJ%yqa$~{lN>~ z8$H18gXwY8fg3M>`n39i<9a=#UrP+UFA39YOPlU>7-&!zZKJb^QMK)R1&G3(yt|&? z_m1$zzp*zJ6D_Jz#Gu%N(IG!sPet^9sU>`czV0b@=)I&=SLB0l#^he)f5jB^cZuC8 z8t8Q}{2XuiMPB)H+xg25LVsVufU;!kUHr`tk9vFopOy7f+%W@V@Ilar0oJtv*rS4x z^TBT4<_VRd1;#s*VtrGo@0ty(!1tfUDk4%$hjvwl6+(u}rU%L$s_0UO_os)c{&(g- z=Km>csCm$7dQ_6OGZb_-%sBH+z-9PhUGX2rq0gzWey)GJSNOdeKD;teJbM29+d8%v z^PS&uL@_z#so3{v#*t{H?|cCx5`*7@{GTMy7(f428Qd4Cwn{3KoEZhHj%q}W$_4b9 z6^y#4v`nsz>Zg5=@c(gluvTy8$Hdn`C`Vap;}4sF5jEn7rYcU}@yEIE&z*vw?la#m z4SuZC`Q}wP>IWQScN_~Q_KT?A3L|#Z>vqO^S%(nE^&Q6n7oYL5 z`8~11L^(HKJ{p}iJytl~sQUB!^hau6-M_TsT8vMogu(l9$63h0A)$W*w*lS`nC97G&Q*-p`GckI79~1}l`pcWsMi@B% zSe+9r)+Ze}M&vF&)57L%&;CLB{W=n>-T(FB`$Nqt#$TL8e`U_+^1jUa9A$Fo59kE+ ziooW341QhP{gK!4jg1&B6F9FHIOok#m;UiD*Ur3p(L$X+N$kVCK6N4UC7oFjomo2F zb(2L4*rE+=(HgvHM_M$SUA+2g(TqyxiCpq7T4JGF^e^gn0WCQ(FI}gjgn*ZVU=+I| zx?9|r;IkCuCgs+*rKdw*+`yD*>f$w;^KxSPVlrv@mc*r3oGY22l_%ikY-C>|l#+s7 z$>XGCFt6OXxmw1#`Wm*9gIx6_EJtBi>xNd7sVjF(E;WJInxL0n1+G>Xt?9s*ZD*IG zXV(gfD0S53M%X2`;7fhTOKc`hxBC<^Y$gDRkn;B{$bM3 zz)cP=iZE_5gY!}gb@%SKT>%9Nh>$g5r*h zi;qXs>0Uri=#Br2+odG&gETT-y79ryS9D&+i*F#O_aTcn<4=@~4aysi_A?2V>3uwn!+CFp1UKH+hfK1m> zOxKIL7)zn+m^&rVGL8r5&R?>eM`oN9XIv~5V4V3g^BO;I30Zv20qQ_ZH+a`D4Yr z+O!f{Y4>y05@XvMxH$Q9&AP$mbGm^2q0p79HyC)`-^Z7{e-d&7!j zzUB1($-gXu4inohpI+V-_ne;CaqD|6U|i+!Yu95SPc~?2`o8Z!SA%}FMW~%_JRM;) zlQvP1$9YXOdJI?59H$NgraL3HmS(1of_{H^!2*CyABQY_dE2YKZRN81v;1zGrO|`Z zwZ&hpL6q5zO0;$%DicJ<3}Ecy;!y5)B#RF^kJW>=R#%OX@yFh6`(PpDih1pMT- zosiCjiYAcf=~C9NfkgM6Fhi++nLR_9QHwAmxhXLg9_D&OUGCF@?wwB1%#Sc*@Y(*J zvD)Rk;UbzZRoKC@dquyN0_jI{O4k2tjT zE`NKd@NML4L$@EqFrwb?`}I(};O@Io4x#)K9zG0lgzOZjV4+#1X#XAf*DzK2@+wTxQD1++~?o&|JVh$*yG z5NG2r6ZSasJ-c|K6$0O1W`_rVydi%c{3%PLWFE`Xr5r>!!s6C5S=-M;2c*a0VMEGC z(?b8ULEf!D+3pDr&AfGd7B*%dbrCV)mV-E~wqzmNy;$3A05UU3*+=~JVMj*)O_8TY zFJz5dMvyOhR6#Q_8EZ#R>#V7<>&@fHxJ~@gtk&-*EZ@6DY(?w!J3d+ycekdZXo%za z92D|&t(}HE-y27vsAot2Wgxvt{AGUUOH9#fR|HL-+;Dfk%yF!CRomn#YlZW_z-VkX#6HK1-@w;3zihg{j%H)NpyUPv7nxtKNzhoQ)Qaw=kE4!=2%;y$22{x!i{4iT1REcG$U@ojqBoAM~&X>)iZu~9;O&w zO+mBQDemrk94ETE+Gd$q748DS7(GKzv#k6DcYx}No=Jk)n{s{+VWSv*iz>6Xb?zRb zjwkvyJ!aX>6&}EV7z2kTvmE?_hdAQIz?t4Wx0~NnGA+i?UC=zQ-`!KX@Wjwt+x*>V zg{N#iEyl>-(>#A_!Bd`iVicTUUNFz^r8pP^4X-jUTyytQo;iU=_m~&$Rd}gx#2CYu z%!|(!ydW1R#_{wPC6@)f)tO>Vo(o!(-th3&;5ao&(zYmLt@PFc#+s&jT9iLr^wv>5 zHGP#}QNbtRqh}Or_NL0BQq;r8!12^9x5uJNy3)reAlAHK$pWps=wpmHH7}vJtkw|l zHBF1Ps1UTQG4Sv;FFduV*0!uQuk^L7kF~^jTGrVw`dSlDEgKRnF>V5Wwu7-&EmfBF zeja}IGpAM^J(k$8N!!rSY7p6FyPl8sBDIWfA9A`GP zK5eU}tV(|mV4UrMr&V+QqQ95wneDd(tCn(s03V|`yB}3nt#uv&evW5$6FpXK&6NQG z0de-zOIGdp#eg8hnf-5i>yB=Lz>u^!hXq0F&VG-;u);HkWo_%O(aOMx`Z&jRPiy?t zVqg^U%yB!xnlLXA6f+nH+pn@Fu6YE-&78rGd#p)&l|hJ&IH&U^Yx3D*5bEO0iH^bM z{bj-6CrohXD*&4hH#~!%ahy9})v@`=S{3{P2zO!hvgv-f6r8Af?s7BH<`bV_$V(%* z>m9VsXHn0P6vuPdd%ZS2(p4d80dTiR6q{bFucxwXezNdJGmvdZr>+0BzWL1S%0uerj zUUsATOaJ}6T=aUi;a-swmkfuc-_8AedUN*ElMH2|^S(~#j60LK*ruh`K-YIwDd1M7CsHFC5*^pw0CB$tk&!KE#|=NPxTzUxWJj;#W*9ndW&;^Tpg3;DQR3z< zs8R13VB60C@IOqb=uZIHPKpG8Jv#00mz*+6np{TbkgsI z+$*HPmvx*@N72ZGdK6;a%js;2f;=M95Zj4P=koy6$sh{3k9N9P^Fp1?(2&QyPSia# z>S6J+)NT;e z7zfmtrq!6&)mYBeZZfLhdZd0^Or2Fl{f?pfT?chGe|2`a`n^>3`vvL`FzOEp>W>E0 zIi}S)*VVbs)wvlT^E`UYEB2UAY;4K=kKG`0OTb>N!1shWBPn)(<`1A?aEfTq#3CUjlX_*~P3 zQOop^mYJBAxr&yBp_ZkCmX*JjHC)RkRm-+O%MPPuPtbB0&~lvCf~{*goohKWYP&qr zb`{fhQ_*%e)b?=D_Vm~Gf@^!HYWoyu`(m{H2-^Mw+5ywrf$Q2q=i0%HIw6mALdA5# zRCK}(bs`*eBK>uu;5yN%Ixz)0u^62=f)0E@2QjUKT-QOJ>%=qaK6#}3R804oitckm z-4_nJ3I4i?aNVR--IoQr$rxQ)3PCq@KsRk#H+@|<<6QR@qu%RBdYNK+St@#O4E5eR z=wsWz6*JybG2S;cK5#HT^fx|&8y}|{pA;CMVvNrS#^(dZ7t_Yn zbz|DOF&&f1B@UB+fF_q!O|BT3{Of2!A7H|OFu9s$a;?zhdcDaFq6y=m3Db-T^M(n_ zg~?4O(_0*-w}Gats-}00Oz%3HvIUs3BTVn5ncgoneNb=ukZAg7(3E4wlyk$B>%x?q z$&81?j2CFer)tJ;WG3KfCKzA_K$r=onF$w~iPW2k63xU0&44pzpbaze3o{8Ob4dbeyq-ZCE;8SUNLVxo}vy0X?5fl3Y6|UY>+R}@ zc9=oC`WZXyh8^z0u7SzEk;A?TXy2@A-(qCn>S*5cx5pFh34`{; z8GF)(J^8}^J(I%+4u_9Ghi+AePeu-(9UXcC9C{HBeQ6F~3LU=IJMt$ z9EL9(zA-s|=WrYWI*zJ3{xEX<>F78X;5d$OoJe#0Rp>Zb?>I$toE~(XnQ@%maHP#$ zIR0jW{o#Q91;XZ4VGBmEMMv0D0E~iwEvLa&3Sq1Dur(rVeGs-W1KZqyZC${&nVfbw zoOXdud#XS6LrIhcHuA2Bh=VbmwbD&eyTdH%QKmL(WXI&di(6EL7*4%r3V$U2cP1SivrL zpe}b|E^L7=>`0e;=`Qz+TpnOu9+F%h4Y_d4x^Ql~a8X^jnO%7}U3o#Sd|+38sH*_X zRWQ&MfOHi~cNH#j6~VfSl3c}xT!FK$piNhCs;dOEn_(G;y2--a2N?12#lAFqqo9e6^c+(9+byH(@SLbwp406{1yK6$-wP5brf$lm;cinV% zy&`vgth)ip-Ehd=Xx1IN>26GQH(~ZL<@7KEd6jB&JaH4uRGkdyldb)x<-N2siP)`q-r)Qw27t+%^-P5PY(--UMNAmO^ z@(h^u4BYe#qIw21dxda%g@U}oz+T}{uLzh|WT00R(knXME2hXR7V8y9@`4X}A!fah zn_eiYS3I-#6Hf1^An#{j@8?kO7clRHK<`APcT&1H?PZa7GS)kVDL7E zYXb=_erpFp5#Xu@*~drkv9FvRKNGk{vSB~KZ5+b!Tz71 z{-0s~J%RqcNdLZc|1U-UU$OrEB>#aS|G`=Rp-un)s>I)z1HN+xjDP}0!2v&@0Y6~@ zV}SwV$bgCTfL}!clh}YMQo!_3z|3p_ZFVzYjvDZrIq(l>;9pSSJUDOx8n_4xTnY@N zAOn}v16PUySFwR>q`>u|z>V3!&CS3qYT!0=&<s2 zjtjm)4rUwa-|xy2Q7TRemn5^~2lt*EQ}*8og%D)BCJsnHW?AN#SwP62zzpb!*GP-Tm)<@!ig5)%o6Fs z73nG-=>~~(H;(jhiu4SM^g=~?XGHoGNBZI-{m7C2!;t}Vk%3#0LA1zVmZ%V}s8I2! zFi2Fmaa4p;RAf+86e=n@BPymiDi#+NM~;FIM<&qM~U@8PP9`qmyybDdgzX;pnuv==81V3|jOnmYCOEF`42qS&*1F#xZZ5 zVzPr`a!@h388LaqG4F6O`Q(^_;h4g?n4+ziVp>cIOKd4uY?*j$IV85iIJVL$wkjwV zjf$<#h^;A(t;NOGkz+B#vGsGY*sWL`Ew+Iru8}LQNj$C@64zoJ*Xk7478KWxitEUT z>nx7z!o}gqafIPG;#?eQD~?Qyd(Q&@zy<#(4)2D*KN-V6JHdN`;Jqk#Uk3b3G5jkI z-cN=P48sTK;6q#RVH*4!3*tK$VniG<3PJoZM*MU_j0GXaQHY5Q#IIt+Bn~k}MobSQ zXftz&*)7Bz4e^@=`G*VnR~$JHK`t007oCtxK}ZS;xtxJqDMqg1kZWY*`Y>{14!OC7 z+@c}3Sx`G%s9kZ?9t5>-j5=^a9R{I}P^jY!)JZYw6o)z^qt1s>7jr1;7K+AjmF^#g z+hX3^+#jD`XP5F{-sbs~bQ@rX@!sL>Nqrz6y1u-_*Y}E7CsWL4m;dV<5$h)Zm0f{> zTq&;+jL)9nP=Qj^?)u6e;9JS##M?mMeW8&G{T!)))qUX~)n;h3dfx+)F^pY%=*H@S z=tP5SZzj<1P;9crcf2WJ?GQNK5lR`U_d5d35@L^bH`b2Cf4_gqzzXuG9ZUTEl*}$2 zxPC0T(3c4?$NHa0Ee*Vr58GTnkzW2*rjrEL{Xh^lhB zzdGI=v_(}tKi;K`;(};k>iOx>-qsck5`XEs0{4@DA4GFMy(ZGZ{ftTJnEUxHeFdHu zckQBi67KtU@Fa4?9`hveBrEW~6nGcSn=D-0!J7i?I_6E4{Hnm0COa9;m#(LJ*U47T72&VC?l4yPFWUMH* zbG1tpM?5_hZTN6qS*-E%gE+CKFCuuc=0T-1v6k=p%D~p2c5%SAU%q%?`%LT^u;Wj% zGN^O$T^y)ur4|puZ*-l32s>Yu#fb-#apI(tRlGR);`B`X{iPc!5+DA32$%SHO_U(f z&7^!T@#&UQd}cQ538P?OMg?pp(^uT>mfpBL{F3`Gis!KA@jr3Kvnjqr9DD+%+`-6JMI{F zAv@ucqAK^xBOf6*=~G9Pn+m{R$W4dztIE$rOd;fFW7dfBbBMDG`QJ}&fEE6{c!*T^ z`%;voFrTJORakg!09IUlYmZc1%JU;BQVQd!ip!-bV5ODHe5BH9O&v*TtsYNRT5s$J zD{r(;A(b~f*GS4+#51b$_J4aWF+gL8vEdWC-ozj0T~*EcE`;?S^=I z74CmntKa`C)$oKF&V7YX=)*O$hNo-TZtRhD7i=&&_8-pLlLJCTXki-p;E2Bp}uJ5(?+N zQ!eyb*sL+xiona(T>Tjs+L!`6=e^f0)FYYMnChv*_h7WTN4BXkEfCK4XkMsSailRl zlEBBgSKX_++n9ko=i|OC+^2rK=~aRXKQC)dpO#e9>vT9jKcDayJ+r3FYy!Wabj=r| z(59@SbABNW;jgBdO>e4H1VqehzFIaly~V->#At59{k9`b*=+;?P*_dB<8D(9>0ChK znec$i?dIGb6+x-2ngI`~=DZ=epiH^&ppRMeyD@^GTyxD}KxlLR?75&qxA0I%W^=)k z3P5SJW+P?;AVju~k#IwSzVdo{y|-R5HIIY8~Q$hRlATT19vg&woketRL+ zQp$`F(&Q8Q{?e?ajEyLyEnWLPEwrVa^Fl~hLuBN2W=n;js<6I!?a14vmP!yp*w9U6 zG;gG(N{%QD4XYh3+-*UFFN95=iTo(N-CC`!Dq@yZ`=e5-wFZh1u_zb$S!32(Yef{X zYOeiRAKF?6yAZMI78z^IY{hu0irS6V(#BewTI&N5q7L&SeonHTN@D=7*;nmv)k5Ax&Q`06P^BZyS?MTDt~BJ z-SnbVd*=`W6kaYmvtriXHAVzQHrLH;gtp^nFF?`VqO&`h?Sv&&@z~M2*@LEb;wC~I zJ})|VGSW^uB#I;V>gFzX+sV`m@%YPPzb~!z(wIL?VIAF^REZ=FvA?%ySskAQ!IH`5n7?0y|8 zj=hdvFjex^GqD98*3Ld{uvBIiWl{W>rAsaYm(^Ljz9oQV z%2?}{wWPbgrz2%5_<$>V=3OJ%B$+Dd`W2(FuF)c@Otl7Z)ikT?M-^DM*1UezvbpOg z7AcEy1FqSQc8#@>WU*oOYmR$e<0PtV!!zKz3oCx22Q1f=Rln{bjsG=-lxrymZupqv zC&x%~ZO!!?0bzLB)GSr5qZ_yxl7*jM0?T)e)^A2M<7YOJ@`QQdR?H}V_K+k`+N<9} z?BVCARQdOpLEBGQ3BTzf3Ljaq+b^UEf0$7UpZGvKFU<*m*~kh#(%7A}Fv2`1O`%T% zwEH@XupkIg{A!NfecMb}1fdiM+(3JIql6_nvf@w}cCT=cKmpSfzdZx(m$DLcfa@Fq+atH|Vf2i@5FyQJx&d9=0|UHv&=0 z)AOLC&QaoKBw2ZO4|_!1BW@vS%D*p*AAevaZ6`of{<7kZKTDH#(orf4eBvix%t^c1 zG_uN)H11?DjI>uoQ(4v!KmDFX+OLABuA1Xce>Rg2uqf4aH}SJyqol(&vg&3S?rdg{ zbVQ=5Za))0|HDc??ty@JvvB8&(&UpN6nMW}{9?tNd^$!3A2#DIHp0kfvo!E=w>Wht zi+sKWft-%ws0YpDi%k^dd|sS(GD@Z%k|ESR9PMI{Or!n>!t^C{%-omQN-lA7|07uP z4~YA+T*+lH_Z97uD^Tu#txEodanpO2&8Jrm+&HS#IW~66Q^AmctSjD)&wL(wodYx7bQ=aq`?2q?O(V@vzF3 zvVwW;XqVoB^4zs5y$j=E^DJcxSdODMgU&T~Jz^nMY~gR0U8Se}P%r4LCw zk9tZU4e@Y{m2%ATa4wZ{Zt`#)mU2;fxarHdnR$8G%6K?=c?HXOLA-o&Wqe>>e(f@T zD6fE3nE;Ge(6dZ1kQWeH20-!(C6o!J^9pB|2^aB-RF#Qfc}3gGL`l42J!N7;yuh(C z;4Cj_sSLEqD}GoePUV%LFPC8ElVmHGgZX5%%VnT^vR37?Fg`iY za=Ac0`N(p4B%eY;xk5UhVs^P=5uZ|3xe}I7xvgB8#HZ3zt};a9QynW;o#g{Bm4i3= zAcy4;DxVsCg&H%zI$MQ0C;wx?ipL;+4Y>*pFu$gDg(j3=%c?>P#;@&Jp&iJt6Ir2y ziWKf?kodDlep&Q)z6_qFRZFx zz=RS!s}ll+5+kbFSDy(76~O+RVQPGQrfChNJ6PS)u}^5X=BxCvqI@h z)#;l;8Hd#wRH0Y&HLsY3U$fP`=A;Q{3f5$TgtO#ovcSS`v}@i#h2L7$yoCv8d)8zJ z3g<-De_1SNFtb?TFj70{a9`NtO$0g7P~2eJFNYW2--kj*T5{= z$X3_LDcU4h*8~!6maA(9i?(RjwLnE%t?F7~qHUgaZGocgk#+4z(T;?=j&#w^?7Ge( z(XOhxF03fNtqxBTCG^nh2t%U8u{z?cC~2vVv?)qHtRquJ-_v8>GmCv-!+hWr`zVO{ z2omd-!*qkiK51hVLrpedOR^bfnvRpm|mn(1RHm5*r-D49#fA?t!&I?v^!4AEf#2EczjFdd1nWmYz)`vSQ84g_ zcKr`1@TXP%PZ)5_vwkcPI38I)js#95)K8=Xe`VMIDgsVc)lXu9Q~${aNxXKVZ;961pO7n{sn>N<*@T$(1JF00Sa2Q!Y;x< zOP<)JKpKb=iKQSx%L&;3Xu2!e*p(vCY87@B3tDT#u8~0NJ=paj(8d^cV-~czgx%Z( zZ5?8_sGx0n+%~iL4jXQVQ+!ttw+j;Alf&(S#rL&w`%v)%E8GE0{LmA37$|-ei914y zA1C0B)5TA+aVJIMr&YL9toT_Q?u;aU-h(?I62BP3UCfG8mvGcgaoXX3x-W4$h6Xwo ziA(Gam$)SU0W|z0E^%4D;W9+xicZ57V~KyQ8~$~Yp!aH^50YSrYG6P~Tup4anjvv5 zr{P+$#C3GTb)3YF_J$i|3C7+A#$gGj@dl+p9A(D4=8t)iO-nDML>mpyO=81R!0{&FoD_)C1lp1kKWY-ENl7p?ORz{wvNua|NlO8mrNpJB<(s7;(lR>D zGRD%f*3Gg`(sEwSazWDaQO)uwX@$gQg$!xMoMy#hX(e>C5>8sVy;+$|lUC_%RvDI7 z9dA~hlLk|o!CTUhqh<(AT8*JajYUSCy+xf%<}skb$Ta4yppp+Kq zmW=UHi!n{cgrU`hMb?zP)s#!t4A5#OE^98|Y7UXL&}p?WmbJ8QwRDoT@@lmTlC_R% zwMNO>B(~aQ$lB(#+7`>&pvYuWM3Z%9 zXme(fb760D;gWL&w7H7QxyiS=LFC+Z+T4xhJgnP1oa8*cXlQV)WMa!Vd{)Q+Ob$1`-qvnV`a?|8zc@D$MTR9xYi ze8)40!gHOD=f(;ztUF#fDI|DxBm^lWMs*~j6p|7dX#O%!%sEK`G`YcIIX%=H+ze6)V0&cfP|Z=C^m|lNAekI}3&t z3&%SP=M;-5okd%U#YdgRG{q8zt`ZidQueM=E~PR+SDCm{xqMeSM5#ijtHM~R(z>hC zNvY~TtwN9zI;sneQmRhus?JcV$?2*oR;opJ)#8-u+Pmt=N|@d*%&=1ZcvtV1A4Gj1O7Uf2Ed?S}~69C^NuG}n-Z-ywh=-^w7m0PXxtxn2qUih{k<@P9i zJ4(4D5#N!a+?j*#ELQG9MOK~eB~A~k&Wsah=2T}X#Mv#?xg+8nP4zbe={F1b4?F1(7x*uL^j91_FHf3> zfERQ~3&!9@Yto_<4ZP$|RA*~dHSJ9+Z9C)prv_=N6_mb9! z!5ibGjXCfpg|xW^-Z~;}(ZJgb?D@T5BE7mC{{wLaB&yRCbZwo_N4o zYmK!Qq(G#?T5FB9)>@fkrDSGBWkHIFa(FA=x1z%P^?!UH3coA^*t_;zFuWpV9N4#B ziV2Iop8+Oryhw^j8y*MuZ0>@hcE&)nQ57zs=46g^*^1Ks5*l4Xylg{%U~rsKG>vKf?Y;K|JS1+piz7Ci#WXD_$Q%I8E*0Z)MqWcgF{_Elg7 zHij#&!1J=fXM_U<@@EL~*dy>cIn^$IPR*GDUwm+meEG$P7gwQ;A1Sz(jUN|gqhEe< zqu}z(Pj5eZKWU5XmtTEWGKGHqc?CK5^%s?^FqO+J+}z3)j%>`EFIx+8-+a~c2=jK; zh&}ghly?eaS{+EqGp!AY#G2RRczNcHlpO54&GQTM-fdY_g00%N+>uwcBhrhlW^SM; zs&{RV#95+acnV7_F9%l>cc4&F6Mw7(XXU0k6jpwY7gxLI97S2X?_wn0wqLp<$2hHpZm2#w$3_*WXgqlgJj-_O@vY5HMNDWUnt<<2Y3KSlZoEwT+% zb<5@LQE+Qs3}4-<VG~k&p*p%-0qSzFcG=54~RQDj0ebiI9g) z8)${Y@3zw^Bh@j2!jT%Dgfd!tKwCIkcg#Q;v!}WW$DBC`WxU}WO*7tfkxuorCo}rMJ~-`ZwW%3>aU<_rv@wOH1DuQp!JSABsAZ6t5)lq=rPcc zsS%eJ@p%#2*Z$ZBKUCc604i_>72Plmu3a4z1$+bMHB1kSTNBI&&cf9VGa_o&go=T0 z=VBXYGUC=^G{CvA=*C%`+O<$A@ZFNU#@WKSbtETnKBBsDj;MAW%?EtH2HONkK6w6IxxnenAxY11qlytBc#PkPlwqR5#)EwHp?T!5{Zxn+e9ajY~D)MPYO^(N?=L zycGQDR9-XL9k*$v6TBp)vo*nqR_Uu- zKNH!u3Vi4*W!SdQrJQYvRE*jf-S&mrw(U?9=BhcbZJCar|YM5!1O#T^D^b3ik&~(HX6a zkGaLi-Qrj}WA$}0cf`0q_u{(ZjPbGeG`QQsn67wRU2JhF?ypmdF0MPC{m_ZKBeHby zJ#}n@5BK*OT=yPSLR>i&e^(mQy$`OBdme@VN3Q7JAC|y*#mC=MTe=TM)N|g7@%OLc zdJZ!Z;;S_HB3(?+5l(%)wG{v0uA=9tFd?Ddi7(b$dX9_g6I?$0zh$`IlhOokGZoSs zV|q`k>$&Yw(0^t{Z;CE~*Tsh(+AO_k`g&fU7<$x*>&q}E@P{-|i94n*(^k(PD}_o2 z6n)w51i_>eGI%U~Vo$vQL443-e|-PfDDIvB8lemo+y4!0-xEY9JOP#c--dDb1`7z~ za83Vr5%#^I62jBD_<`>k+uz4)OkM()9- zTDVdeJ9O1%KNxO+-<(no6}Y*FR=VJ~qM9L%$9{;8z@{_!;h#~w!|P~7vov=27ua!l z6P@@@t{na~j3?YGAXceshJTB22zN?|)z|PNzcYA8c58_iUF^so9LEv1fmm}_Ir68F zmz3Z_h*o{g$X_Byk^mvrmf=VLmhz4!(nvOA?C3vg$I(M{Qk_{jdSAyomLwq6+iFH1 z=pDySNJ#cZ{Mf%n-tlBD$>EM2`_JY$o^BvH2b5!v+`JQ6E|SYrGgj(xoRA=-27hS$ zF^YfkEREcVVvj$8ohQ%J$xWbY{An2f)CB>#8Mcl;i*TO0Bq6uVg*-19{L{Hwa%&jd z^OEB{tum0?mZ&_hh5Y1v7r8yc>Ukq_CTkIL#~NtDB;}`Er%~Jt_QX52Gvy|o(#cXy zRO|Suw*-_fj&-6&?@YZTp>*$sCTorSw0l}gkB~iCXLF_%8z{ZVDb=Li%};;mqV$QZ zlTME_-GET~&p=ZRC_zR!jXEG@Pc^}=jOTRfpjSWK)~U7#m*}m8I(!ZC zb}$5)Ra)wZj_vK_xH7E<>gZjSw_7O4s&`Sx^j2@L$d%^=Q(icQXUovcLpWpa7VW$eeLVHY3S2;{*_t%nVAG1!b9n zB*4tG%$et9GcTBCUII|LOq5E7$~U33z^v=cSvO^~ZkcA?0fO%_gNtRs4^6=aV0Jlk z_H)_nSEku-fsiU@h*cI+Zwhe%bDEiR+GTUPOmq5x&>?2%m@IVC6p8>q01F6`1Hon> z6a+CW5R!u=Ge`r`^H}Hwa`ZwodNGJu%EE-pF)Pg&I*47z!fujdx0we}!j5cK`qY literal 0 HcmV?d00001 diff --git a/src/buttons.rs b/src/buttons.rs new file mode 100644 index 0000000..dd89038 --- /dev/null +++ b/src/buttons.rs @@ -0,0 +1,47 @@ +/// State of all GSA buttons +#[derive(Default, Copy, Clone)] +pub struct Buttons { + /// UP on dpad + pub dpad_up: bool, + /// DOWN on dpad + pub dpad_down: bool, + /// LEFT on dpad + pub dpad_left: bool, + /// RIGHT on dpad + pub dpad_right: bool, + /// X on nintendo, Y on microsoft, TRIANGLE on sony + pub face_up: bool, + /// B on nintendo, A on microsoft, CROSS on sony + pub face_down: bool, + /// Y on nintendo, X on microsoft, SQUARE on sony + pub face_left: bool, + /// A on nintendo, B on microsoft, CIRCLE on sony + pub face_right: bool, + /// left shoulder button + pub l: bool, + /// right shoulder button + pub r: bool, + /// start button + pub start: bool, + /// select button + pub select: bool, +} + +impl Buttons { + pub(crate) fn pressed(old: &Buttons, new: &Buttons) -> Buttons { + Buttons { + dpad_up: !old.dpad_up && new.dpad_up, + dpad_down: !old.dpad_down && new.dpad_down, + dpad_left: !old.dpad_left && new.dpad_left, + dpad_right: !old.dpad_right && new.dpad_right, + face_up: !old.face_up && new.face_up, + face_down: !old.face_down && new.face_down, + face_left: !old.face_left && new.face_left, + face_right: !old.face_right && new.face_right, + l: !old.l && new.l, + r: !old.r && new.r, + start: !old.start && new.start, + select: !old.select && new.select, + } + } +} diff --git a/src/gsa.rs b/src/gsa.rs new file mode 100644 index 0000000..afc895d --- /dev/null +++ b/src/gsa.rs @@ -0,0 +1,56 @@ +use crate::input::Input; +use crate::rgb::Rgb; +use crate::sprite::Sprite; +use crate::tilemap::Tilemap; +use crate::{MAX_SPRITES, MAX_TILEMAPS, TILEMAP_MAX_SIZE}; +use ascii::{AsciiChar, AsciiStr}; +use glam::IVec2; + +/// Complete state of GSA +pub struct Gsa { + /// Sprites available + pub sprites: [Sprite; MAX_SPRITES], + + /// Palette used to draw graphics, initially loaded from gfx.gif + pub palette: [Rgb; 256], + + /// Tilemap layers available + pub maps: [Tilemap; MAX_TILEMAPS], + + /// Currently selected font + /// + /// Chosen as half-size tile index, extends 16x16 half tiles in x and y + pub font: u16, + + /// Current input state + pub input: Input, +} + +impl Gsa { + /// Clears all tiles of given map to 0 + pub fn clear_map(&mut self, map: usize) { + self.fill_map(map, 0); + } + + /// Sets all tiles of map to val + pub fn fill_map(&mut self, map: usize, val: u16) { + for x in 0..TILEMAP_MAX_SIZE { + for y in 0..TILEMAP_MAX_SIZE { + self.maps[map].tiles[x][y] = val; + } + } + } + + /// Write given string on given map, at given position, with font [Gsa::font] + pub fn write_string(&mut self, map: usize, pos: IVec2, str: &str) { + let str = AsciiStr::from_ascii(str).unwrap(); + for (i, ch) in str.into_iter().enumerate() { + self.maps[map].tiles[pos.x as usize + i][pos.y as usize] = self.get_char_tile(*ch); + } + } + + fn get_char_tile(&self, ch: AsciiChar) -> u16 { + let ch = ch as u16; + self.font + (ch % 0x10) + (ch / 0x10) * 0x100 + } +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..7d722a8 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,18 @@ +use crate::buttons::Buttons; +use glam::IVec2; + +/// Input State +/// +/// GSA pretends all input is a snes-like gamepad +/// d-pad, 4 face buttons, l and r shoulder buttons, start and select +#[derive(Default)] +pub struct Input { + /// Buttons that are currentle held down + pub down: Buttons, + + /// Buttons that have been pressed in the current frame + pub pressed: Buttons, + + /// Directional vector (-1 to 1) from current dpad state + pub dir: IVec2, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..50aaf08 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,47 @@ +#![deny(missing_docs)] + +//! # Game Skunk Advance +//! Game development library modelled after an imaginary console +//! +//! ## Specs +//! - Resolution: 304x176 (19x11 tiles) +//! - Colors: 256 (indexed out of a possible 24-bit) +//! - Tilesize: 16x16 (or 8x8 for half-tiles) +//! - Tileset: 65536 tiles, indexed via 0xYYXX +//! - Sprites: 256 of size 16x16 (pondering allowing larger sprites) +//! - Tilemaps: 4 of size 1024x1024, scrollable +//! +//! ## Features not yet implemented +//! - Sound (no samples +//! - Synth +//! - Speech +//! - Savegames +//! - Helpers +//! - Gamepad text keyboard input +//! - Menus + +mod buttons; +mod gsa; +mod input; +mod rgb; +mod run; +mod sprite; +mod state; +mod tilemap; + +pub use crate::buttons::Buttons; +pub use crate::gsa::Gsa; +pub use crate::input::Input; +pub use crate::rgb::Rgb; +pub use crate::run::run; +pub use crate::sprite::Sprite; +pub use crate::tilemap::Tilemap; + +/// Amount of sprites in [Gsa::sprites] +pub const MAX_SPRITES: usize = 0xff; + +/// X and y dimensions of maps in [Gsa::maps] +pub const TILEMAP_MAX_SIZE: usize = 1024; + +/// Amount of tile maps in [Gsa::maps] +pub const MAX_TILEMAPS: usize = 4; diff --git a/src/rgb.rs b/src/rgb.rs new file mode 100644 index 0000000..6f540d0 --- /dev/null +++ b/src/rgb.rs @@ -0,0 +1,10 @@ +/// RGB Color +#[derive(Copy, Clone)] +pub struct Rgb { + /// Red component 0-255 + pub r: u8, + /// Green component 0-255 + pub g: u8, + /// Blue component 0-255 + pub b: u8, +} diff --git a/src/run.rs b/src/run.rs new file mode 100644 index 0000000..6c78c6b --- /dev/null +++ b/src/run.rs @@ -0,0 +1,57 @@ +use crate::state::State; +use crate::{Gsa, Rgb, Sprite, MAX_SPRITES}; +use gilrs::Gilrs; +use skunk2d::Image8; + +/// Creates main function, includes gfx.gif, and calls run +/// +/// Pass the following two functions to it: +/// - fn init(gsa: &mut Gsa) -> TGame +/// - fn update(game: &mut Game, gsa: &mut Gsa) +/// +/// TGame can be any type, usually a struct that contains your game's state +#[macro_export] +macro_rules! run { + ($init: ident, $update: ident) => { + fn main() { + run($init, $update, include_bytes!("gfx.gif")); + } + }; +} + +/// This is called by [run!] +pub fn run( + init_fn: fn(gsa: &mut Gsa) -> TGame, + update_fn: fn(game: &mut TGame, gsa: &mut Gsa), + image_data: &[u8], +) { + let tileset = Image8::load_data(image_data); + let pal = Image8::load_data_palette(image_data); + let mut palette = [Rgb { r: 0, g: 0, b: 0 }; 256]; + + for i in 0..pal.len() / 3 { + palette[i] = Rgb { + r: pal[i * 3 + 2], + g: pal[i * 3 + 1], + b: pal[i * 3], + }; + } + + let mut gsa = Gsa { + sprites: [Sprite::default(); MAX_SPRITES], + maps: Default::default(), + palette, + font: 0x1010, + input: Default::default(), + }; + let game = init_fn(&mut gsa); + let state = State:: { + gsa, + game, + tileset, + update_fn, + first: true, + gilrs: Gilrs::new().unwrap(), + }; + skunk2d::run_with::>(304, 176, 60, state); +} diff --git a/src/sprite.rs b/src/sprite.rs new file mode 100644 index 0000000..c5bb194 --- /dev/null +++ b/src/sprite.rs @@ -0,0 +1,17 @@ +use glam::IVec2; +use std::fmt::{Display, Formatter}; + +/// Sprite which will be displayed on screen, unless tile=0 +#[derive(Default, Copy, Clone)] +pub struct Sprite { + /// Position on screen + pub pos: IVec2, + /// Tile index + pub tile: u16, +} + +impl Display for Sprite { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "[pos: {}, tile: {}]", self.pos, self.tile) + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..5f75783 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,146 @@ +use crate::buttons::Buttons; +use crate::{Gsa, TILEMAP_MAX_SIZE}; +use gilrs::{Button, EventType, Gilrs}; +use glam::IVec2; +use skunk2d::{Event, IRect, Image8, WindowState}; +use std::rc::Rc; + +pub struct State { + pub(crate) gsa: Gsa, + pub(crate) game: TGame, + pub(crate) update_fn: fn(game: &mut TGame, gsa: &mut Gsa), + pub(crate) tileset: Rc, + pub(crate) first: bool, + pub(crate) gilrs: Gilrs, +} + +impl skunk2d::Game for State { + fn update(&mut self, window_state: &mut WindowState) { + if self.first { + window_state.toggle_fullscreen(); + self.first = false; + } + let mut new_buttons = self.gsa.input.down; + while let Some(event) = self.gilrs.next_event() { + match event { + gilrs::Event { + event: EventType::ButtonPressed(button, _), + .. + } => match button { + Button::South => new_buttons.face_down = true, + Button::East => new_buttons.face_left = true, + Button::North => new_buttons.face_up = true, + Button::West => new_buttons.face_right = true, + Button::LeftTrigger | Button::LeftTrigger2 => new_buttons.l = true, + Button::RightTrigger | Button::RightTrigger2 => new_buttons.r = true, + Button::Select => new_buttons.select = true, + Button::Start => new_buttons.start = true, + Button::DPadUp => new_buttons.dpad_up = true, + Button::DPadDown => new_buttons.dpad_down = true, + Button::DPadLeft => new_buttons.dpad_left = true, + Button::DPadRight => new_buttons.dpad_right = true, + _ => {} + }, + gilrs::Event { + event: EventType::ButtonReleased(button, _), + .. + } => match button { + Button::South => new_buttons.face_down = false, + Button::East => new_buttons.face_left = false, + Button::North => new_buttons.face_up = false, + Button::West => new_buttons.face_right = false, + Button::LeftTrigger | Button::LeftTrigger2 => new_buttons.l = false, + Button::RightTrigger | Button::RightTrigger2 => new_buttons.r = false, + Button::Select => new_buttons.select = false, + Button::Start => new_buttons.start = false, + Button::DPadUp => new_buttons.dpad_up = false, + Button::DPadDown => new_buttons.dpad_down = false, + Button::DPadLeft => new_buttons.dpad_left = false, + Button::DPadRight => new_buttons.dpad_right = false, + _ => {} + }, + _ => (), + } + } + self.gsa.input.pressed = Buttons::pressed(&self.gsa.input.down, &new_buttons); + self.gsa.input.down = new_buttons; + self.gsa.input.dir = IVec2 { + x: if self.gsa.input.down.dpad_left { -1 } else { 0 } + + if self.gsa.input.down.dpad_right { 1 } else { 0 }, + y: if self.gsa.input.down.dpad_up { -1 } else { 0 } + + if self.gsa.input.down.dpad_down { 1 } else { 0 }, + }; + + //todo: don't if not updated... how check? <_< + for i in 0..=255 { + window_state.set_palette( + i, + self.gsa.palette[i as usize].r, + self.gsa.palette[i as usize].g, + self.gsa.palette[i as usize].b, + ); + } + (self.update_fn)(&mut self.game, &mut self.gsa); + } + + fn on_event(&mut self, _window_state: &mut WindowState, _event: Event) {} + + fn draw(&self, target: &mut Image8) { + target.clear(); + + for map in &self.gsa.maps { + let tcmult = if map.half_tile { 2 } else { 1 }; + let tilesize = if map.half_tile { 8 } else { 16 }; + let mut startx = map.scroll.x / tilesize; + let mut starty = map.scroll.y / tilesize; + let endx = (TILEMAP_MAX_SIZE as i32).min(startx + 20 * tcmult); + let endy = (TILEMAP_MAX_SIZE as i32).min(starty + 12 * tcmult); + startx = 0.max(startx); + starty = 0.max(starty); + for x in startx..endx { + for y in starty..endy { + let tile = map.tiles[x as usize][y as usize]; + if tile > 0 { + let ty = tile / 0x100; + let tx = tile % 0x100; + target.draw_image_partial( + IVec2 { + x: x * tilesize - map.scroll.x, + y: y * tilesize - map.scroll.y, + }, + &self.tileset, + IRect { + pos: IVec2 { + x: tx as i32 * tilesize, + y: ty as i32 * tilesize, + }, + size: IVec2 { + x: tilesize, + y: tilesize, + }, + }, + ) + } + } + } + } + + for sprite in &self.gsa.sprites { + if sprite.tile > 0 { + let ty = sprite.tile / 0x100; + let tx = sprite.tile % 0x100; + target.draw_image_partial( + sprite.pos, + &self.tileset, + IRect { + pos: IVec2 { + x: tx as i32 * 16, + y: ty as i32 * 16, + }, + size: IVec2 { x: 16, y: 16 }, + }, + ); + } + } + } +} diff --git a/src/tilemap.rs b/src/tilemap.rs new file mode 100644 index 0000000..21152ef --- /dev/null +++ b/src/tilemap.rs @@ -0,0 +1,22 @@ +use crate::TILEMAP_MAX_SIZE; +use glam::IVec2; + +/// Tilemap which will be rendered on screen +pub struct Tilemap { + /// Tiles in idx, accessible via \[x\]\[y\], x and y 0..[TILEMAP_MAX_SIZE] + pub tiles: Box<[[u16; TILEMAP_MAX_SIZE]; TILEMAP_MAX_SIZE]>, + /// Camera scroll (negative draw offset) for rendering + pub scroll: IVec2, + /// Are tiles indices half-tile indices? + pub half_tile: bool, +} + +impl Default for Tilemap { + fn default() -> Self { + Self { + tiles: [[0u16; TILEMAP_MAX_SIZE]; TILEMAP_MAX_SIZE].into(), + scroll: IVec2::ZERO, + half_tile: false, + } + } +} diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..f50ca28 --- /dev/null +++ b/todo.md @@ -0,0 +1,12 @@ +# TODO + +- Input (mapped minimal controller) + - Gamepad doooone :) + - Keyboard still gotta +- Sound + - Something synth'y would be nice... + - Speech synthesis...? :) +- Savegames +- Helpers + - Menu + - Fake Keyboard Text Input \ No newline at end of file