From a62de4262778d91440e7f9e2ae6b9acc4b734695 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Sat, 14 Sep 2024 18:13:54 +0100 Subject: [PATCH 1/6] Lua version of ninja thief episode 1 --- .../episode-1/32blit-lua/assets.yml | 8 +++ .../32blit-lua/assets/background.blim | Bin 0 -> 521 bytes .../32blit-lua/assets/background.png | Bin 0 -> 1050 bytes .../episode-1/32blit-lua/assets/icon.png | Bin 0 -> 138 bytes .../episode-1/32blit-lua/assets/logo.png | Bin 0 -> 1732 bytes .../32blit-lua/assets/spritesheet.blim | Bin 0 -> 1303 bytes .../32blit-lua/assets/spritesheet.png | Bin 0 -> 1113 bytes .../episode-1/32blit-lua/constants.lua | 13 +++++ .../episode-1/32blit-lua/metadata.yml | 12 ++++ .../episode-1/32blit-lua/ninja_thief.lua | 55 ++++++++++++++++++ 10 files changed, 88 insertions(+) create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/assets.yml create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/assets/background.blim create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/assets/background.png create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/assets/icon.png create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/assets/logo.png create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/assets/spritesheet.blim create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/assets/spritesheet.png create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/constants.lua create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/metadata.yml create mode 100644 source-code/ninja-thief/episode-1/32blit-lua/ninja_thief.lua diff --git a/source-code/ninja-thief/episode-1/32blit-lua/assets.yml b/source-code/ninja-thief/episode-1/32blit-lua/assets.yml new file mode 100644 index 0000000..721942b --- /dev/null +++ b/source-code/ninja-thief/episode-1/32blit-lua/assets.yml @@ -0,0 +1,8 @@ +# 32blit pack --force --config assets.yml +# ideally these should be .blim, but the tools don't recognise that + +background.bin: + assets/background.png: + +spritesheet.bin: + assets/spritesheet.png: diff --git a/source-code/ninja-thief/episode-1/32blit-lua/assets/background.blim b/source-code/ninja-thief/episode-1/32blit-lua/assets/background.blim new file mode 100644 index 0000000000000000000000000000000000000000..f06181f75845af32637310c528d42a5576b8c1ae GIT binary patch literal 521 zcmV+k0`~n=P*O=$MN&)&0ssJ@0C)fb15i>{|8;X||2#cK|B94$CQ4Y*M`C25jud7l zN*LirVq~3<6jmll*zrYTWSNi@HYQ1#2}5FJnUIt=CP`TtLSkf_Ibvj8ofDQOMVa9^Vq{gGlZGZm8TmJ2WKEzGb|yrc z5jA3DNuiTgCPZ2xHDY8zqZ39ZL)tksVq`g_lQt$pT2V7%WHF`_CMH7KX)$7CEvFL} zCPJElFk)mSsFMaJLK>MbVq_hv680uRnz1foWEHED<|aWJ!7XBB5v>x&CP3QREMj3M zx=PrXO?Z{DFxUxGbtO&Ml(%9~-N{3EB<}`sqDtP$8+;^f^pQ8x zMBj-Eek3jUkhk(c-^lxcBhCbmI3haWh~tDKP85u|Vld(evX&f*uv-d5*iC{WOg6v~ z1|wfcdlj!lIgHnW7{_a=3uHCSgtBVhLK#(UAk4b85LRVc2pcx6p(b@$f@jr$p;f|- zRti;ADb+x!RKl%N3e`j_)c~ literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-1/32blit-lua/assets/background.png b/source-code/ninja-thief/episode-1/32blit-lua/assets/background.png new file mode 100644 index 0000000000000000000000000000000000000000..b1c66ca74d19334492ee777ca0c39261d2cbd743 GIT binary patch literal 1050 zcmV+#1m*jQP)004Lh1^@s6b)xxi00001b5ch_0Itp) z=>Px&)Ja4^RCt{2-Aig4K@>*e8{-WGB@58N`w1Cj^B#Bsw($~@fvLDvYPI@NRrhty zC=jsG<>N0fd3^iz`_EtL(9`+Uw`bKnFYcO8#-3_3~=TW>K zW<(hg)}Og6z>^aRSi_~VA)J2RVE{|kkE2yKgr(sCMOZ`nN(EpEgqLMn z!SY#E0gBi@l17;jiWmSW!joK=1>w{AWCLKy>Y+5sg3uH|5!P^8288ASmMk7iqv#0D z0u*5lOOX+p23WFmFlw6%fFi76)D?6Cuw>B<~tVKo|mG353TReZx=yMe?p;%SPTW9Kcd0gkJti=3HR`ih%G)qiz=( zph&JK*(1X6081d;SMR%}0VtAZ4Lu=D1+WCd?e)4@N`N9c*3bjOv;a#W+*a#*r3WaI zV-2kaz)4XNT3$f_R)KI`t?mTCnW8?)77+q)ArRj0=#2n)r-*B42_XPi2Eyg`-Uomi zinfLp5CU*Vkq~MJz@0@wsJ(&!JW!M;SrZ`u8wA4Z*4_kwjf${_8VCW{I1pZT?;Zf` zkoPqli4cGt1L0S*`C=Leu- z-qx@OLI4^lGeQ6w$mNQ3~S*+wA*Al+=lll<`Z`XO(xA6>r*`K~9z$MTCB;g6r+yM0sg zS3%hNA18Xe^&5bsxt?F%n$_+H8t2G{8DQ+NnIrsQ}giQoqyy>i{`E04@Z`{sC}9fNUQC z4+O~L0kC0!+#Ucs2FUvXaA1JE4uFaQ5`d=42oivX%K#F9j*9rF#sTO&?<)zwV0i-x zz~EU23BY7o0|~(7X@dkHT{Idb0BNz_AOT35Y7G*Abf}{s0Z2{T3KD?SwR<4{0l*$} Uh)ucPr2qf`07*qoM6N<$f~efR@&Et; literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-1/32blit-lua/assets/icon.png b/source-code/ninja-thief/episode-1/32blit-lua/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..baa43d35e8ff1e0c19a3d7a267d1c309062c708d GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|Ts&PILnNjq zznQo9sXepEmi&FE4gf*G%hkNXNeNEVUD7unV7bwEHqn7`-`^E#514#jlqH2aTwsyV h$a|j-#Oh~R7+OvW#UE@ENCBG1;OXk;vd$@?2>=Q1F6jUO literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-1/32blit-lua/assets/logo.png b/source-code/ninja-thief/episode-1/32blit-lua/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..895b373c9bcc1a6afd57337f592a2a8b6e35b367 GIT binary patch literal 1732 zcmV;#20QtQP)Px*en~_@RCt{2+;40XWgG|a?+`6X&4_HeF#~KR0|E^x21o`tG#FtK#tW-LaA65X z5=1a+2tnV8VIfgOkwl|$W+r2CNEpl}5<|A2jl*mxl>iHM*f2KYP->QJZdzZ=mfl^j zy{GqF+v~ocWXV0x{dsPAe)rsS&pkbWh=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=YY^flf zowl6_MF60yy%o!!@}jG~HRV3e;AsrI)+bCymz}}WLgz6)K7y%Jr(t}2L}fRR=}^y<&1_{u*&IAYm2 zfcx}rMLMwlaKZ|2D|=OuuI+zUvrUoHfnI^Kr9TA}IUNZr`DXu(SiW7Z{&y= z4*>p)Ku0s0N;junj!Ze#2at88_jCu+>iq*q>j@w#)YyQ1>*k`+SD<2cg(6QhEh>8T>%9wTD%}ipG~ZoL)3N!xvFXZTO`iJVadqrOaP<*Q zo_agcK7cI(AQvnv(T)SiX!hNeE*zV`Ta%Y)J6Fffl-uQH3y|*;_V^nM73tdguqB;m z27r!i&IeFeel)MPKJ}OcZvm2IV?KZkqPKvoy$#GHpTAErTPPiNnGYZ;?*kyd;>`v@ zrnpvKFq9{QWw!ut%?3sK*r_iqoerId&)Dmk7yrY`nj`MxcF&6)pDQwx!Z7= z*}y)44!e#6h_X!pRJDB`TlUv()!oih>{PrG_8d4AOM4C+0$|{k@}iAc5;vK&r#pZR zufGKVxbXY1oPQ`s*d_q{wOi5k#ZfGOen-;n%uXOpmDYQ`d}r0;K=^XfbIV220ziFF zmpKkFSkQ){Yh$>7MM=_U?UG0>fV&ezMJJG&nP58o+*x4pOvBsf3R|=QL)XTVreo%| z0U7=Ax?Is2REfp_lC3i%uf_p%1H%U5r_ZUU(j{AGmMvOZ)`qVr z(w@J7f!hlJ0DnHQ8fa<3b;rankiV&X5c9tNR7%=WvJh7b=WEJe8@_^qDHG?YK0gu3 zD%Yxa1HvDPT3Wp_V&yQd`U4Vy-XI>T*3OfUH-PUVk0 zto}&U(C{dFe>i8|N20RfR_Ec=8^y`5p&D!Dd;pkj`b4KlC6dpdt;?&&Kt0>NUTpVz zQ!YoXKD$Sgm#-#80DRPX_e-XiUsARx>hxqYDFPtbmAv4?pk}-HynL5)PXV$`wmyL9 zQ$SiDi(Xp2GLp`IWZa$60!(*AaA#DkibN`(h=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=Y aZ0tW#pQt@QVP=j10000NJfxAf@noA`3SK&! zG@bmQs7Q%gWSfI{k`g@h*fs>?&AXQc5A8qTwd+i}i77}?_wZ)kd*6I-zHfFWIeqWp z#PsCFeujxL8sn16?>^g)o?@FJUv1!qJsMxIW$#rxT1?mjk9FIBJ8G{)Z`sjn!}i{@ zr0tn1+C#@bZRX8)`{ZcOKKXRhW@NDsoed0lVUFkVSRWE)=}eG9X`fdK?T86Aj!MCd zWPLz5@iFe9y8?S7Q%C1 zLOx|9h($Bbv9Z)?V8>iM8;g5b+K~&vtdb^R11BQGziEaAOErFKnQGf;I^-9S}n zP8zsw83A_HZ!WICWwS1x%U(b@3l~9SYNREX@3^cqeIK4%0SK>MH0h|Al= zemu&BH&BfY!^5FHt#+ITExEFdJ0VYZxvuZKn+P8#DhRi(pLdKj|nhW(peI6xcbfhD& zPxoErx&U)-;AjsE-dWMCpb|GCn@_V?$g(q&21Rjzk$8$+T_%F%7t@;Px(6G=otRA_8`IaXfilSSqx*L>C?XXM5&At|E;_1L|NJS_O;jtpCUMYd zx5Udu%{LJswS$Udqv`Io+!gf+s5lBX#^W#{dvm72JCE8HDrLp1_e8r7C5J?7VKbs5(3 z6dgd>5twV|Ti1OnLId>U{OlsT{LYrv`D{15V~~Cr^Xu5( zXotQ}1xqJ2NGcd2@p4fUEwNk!(2jT+%HFte4*8wFi^De&aBM)kd$|VaH#8n3wS$TV zBSdICU>7IU4l3F>Cb>p9kv@QSE?zEbD7W)OaYDRP#y2&}4UY$A0F^#KB|QcxtgmTw ztyTq%0a&O(R3Y>BBF^Ff>;Ah|YNGt%2iKax#o3;HHnqGFOLpt2XA<1)bO14vnaAt9fWdx%u@>Lfcz<1pqLzY2gUKW=YtPd?KeEb&J~% z)_sD;{y9L|zxy6o4wyUfrb2>0mwSxER%XPKJ-zE?8k;tLAE53LVy3LBsq*>q1@l+O+9 zj98M`MSNcCsbGk(@AwCyo8ov@-~}>X>ehx?4>)+GkI=@A7T$rqtgv~i^;9rK9@qbX zy{xc#D)l4#0AA-*>N0qpbE9{MiS@s8>oJqls{i-9!7i+YcyCfptM17N;GNPs%E_%` zd%hRzyeR-VhB$yt48ydC#QN zI6#D3ogSd1>1W1xR~wQ2RpnOa!5WLczZc!>R_9>~gk+6(tILoz-mNapDFs f-Sv@^lUn{ScA1G9?8X+-00000NkvXXu0mjfN?;DR literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-1/32blit-lua/constants.lua b/source-code/ninja-thief/episode-1/32blit-lua/constants.lua new file mode 100644 index 0000000..ec03f95 --- /dev/null +++ b/source-code/ninja-thief/episode-1/32blit-lua/constants.lua @@ -0,0 +1,13 @@ +Constants = { + -- Screen size in pixels + SCREEN_WIDTH = 160, + SCREEN_HEIGHT = 120, + + -- Actual game area in pixels + GAME_WIDTH = 120, + GAME_HEIGHT = 120, + + -- Each sprite on the spritesheet is 8x8 pixels + SPRITE_SIZE = 8, +} + diff --git a/source-code/ninja-thief/episode-1/32blit-lua/metadata.yml b/source-code/ninja-thief/episode-1/32blit-lua/metadata.yml new file mode 100644 index 0000000..f1b6dab --- /dev/null +++ b/source-code/ninja-thief/episode-1/32blit-lua/metadata.yml @@ -0,0 +1,12 @@ +# 32blit metadata --config metadata.yml --metadata-file ninja_thief.blmeta + +title: Ninja Thief +description: A tutorial game for the 32blit and PicoSystem. Collect all the coins while avoiding the enemy ninjas! +author: ThePythonator +splash: + file: assets/logo.png +icon: + file: assets/icon.png +version: v1.0.0 +category: game +url: https://github.com/32blit/32blit-tutorials-contrib diff --git a/source-code/ninja-thief/episode-1/32blit-lua/ninja_thief.lua b/source-code/ninja-thief/episode-1/32blit-lua/ninja_thief.lua new file mode 100644 index 0000000..499ad79 --- /dev/null +++ b/source-code/ninja-thief/episode-1/32blit-lua/ninja_thief.lua @@ -0,0 +1,55 @@ +require "constants" + +-- Outside of our functions, just after the includes +background = nil + +-- Setup your game here +function init() + -- Set the resolution to 160x120 + set_screen_mode(ScreenMode.lores) + + -- Load the background from assets.cpp + background = Surface.load("assets/background.blim") + + -- Load the spritesheet + screen.load_sprites("assets/spritesheet.blim") +end + +-- This function is called to perform rendering of the game. +function render(time) + -- Clear the screen + screen.pen = Pen(0, 0, 0) + screen.clear() + + -- Draw the entire background image onto the screen at (0, 0) + screen.blit(background, Rect(0, 0, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT), Point(0, 0)) + + -- Set the text colour to white + screen.pen = Pen(255, 255, 255) + + -- Render the placeholder score text + screen.text("Score: 0", minimal_font, Point(2, 2)) + + -- Example primitive shape drawing code: + --[[ + -- Draw a red (filled) rectangle + screen.pen = Pen(255, 0, 0) + screen.rectangle(Rect(10, 10, 50, 20)) + + -- Draw a blue (filled) circle + screen.pen = Pen(0, 0, 255) + screen.circle(Point(20, 40), 10) + + -- Draw a green diagonal line from top left to bottom right + screen.pen = Pen(0, 255, 0) + screen.line(Point(0, 0), Point(120, 120)) + ]]-- + + -- Draw the first blue player sprite (index 32) at (50, 50) + screen.sprite(32, Point(50, 50)) +end + +-- This is called to update your game state. +function update(time) + +end From 5cffdc86034f3ab54e62307d4998797f838e5c8f Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Sat, 14 Sep 2024 20:51:25 +0100 Subject: [PATCH 2/6] Lua version of ninja thief episode 2 --- .../episode-2/32blit-lua/assets.yml | 8 +++ .../32blit-lua/assets/background.blim | Bin 0 -> 521 bytes .../32blit-lua/assets/background.png | Bin 0 -> 1050 bytes .../episode-2/32blit-lua/assets/icon.png | Bin 0 -> 138 bytes .../episode-2/32blit-lua/assets/logo.png | Bin 0 -> 1732 bytes .../32blit-lua/assets/spritesheet.blim | Bin 0 -> 1303 bytes .../32blit-lua/assets/spritesheet.png | Bin 0 -> 1113 bytes .../episode-2/32blit-lua/constants.lua | 55 ++++++++++++++++ .../episode-2/32blit-lua/metadata.yml | 12 ++++ .../episode-2/32blit-lua/ninja.lua | 59 ++++++++++++++++++ .../episode-2/32blit-lua/ninja_thief.lua | 56 +++++++++++++++++ .../episode-2/32blit-lua/player_ninja.lua | 30 +++++++++ 12 files changed, 220 insertions(+) create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/assets.yml create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/assets/background.blim create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/assets/background.png create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/assets/icon.png create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/assets/logo.png create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/assets/spritesheet.blim create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/assets/spritesheet.png create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/constants.lua create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/metadata.yml create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/ninja.lua create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/ninja_thief.lua create mode 100644 source-code/ninja-thief/episode-2/32blit-lua/player_ninja.lua diff --git a/source-code/ninja-thief/episode-2/32blit-lua/assets.yml b/source-code/ninja-thief/episode-2/32blit-lua/assets.yml new file mode 100644 index 0000000..721942b --- /dev/null +++ b/source-code/ninja-thief/episode-2/32blit-lua/assets.yml @@ -0,0 +1,8 @@ +# 32blit pack --force --config assets.yml +# ideally these should be .blim, but the tools don't recognise that + +background.bin: + assets/background.png: + +spritesheet.bin: + assets/spritesheet.png: diff --git a/source-code/ninja-thief/episode-2/32blit-lua/assets/background.blim b/source-code/ninja-thief/episode-2/32blit-lua/assets/background.blim new file mode 100644 index 0000000000000000000000000000000000000000..f06181f75845af32637310c528d42a5576b8c1ae GIT binary patch literal 521 zcmV+k0`~n=P*O=$MN&)&0ssJ@0C)fb15i>{|8;X||2#cK|B94$CQ4Y*M`C25jud7l zN*LirVq~3<6jmll*zrYTWSNi@HYQ1#2}5FJnUIt=CP`TtLSkf_Ibvj8ofDQOMVa9^Vq{gGlZGZm8TmJ2WKEzGb|yrc z5jA3DNuiTgCPZ2xHDY8zqZ39ZL)tksVq`g_lQt$pT2V7%WHF`_CMH7KX)$7CEvFL} zCPJElFk)mSsFMaJLK>MbVq_hv680uRnz1foWEHED<|aWJ!7XBB5v>x&CP3QREMj3M zx=PrXO?Z{DFxUxGbtO&Ml(%9~-N{3EB<}`sqDtP$8+;^f^pQ8x zMBj-Eek3jUkhk(c-^lxcBhCbmI3haWh~tDKP85u|Vld(evX&f*uv-d5*iC{WOg6v~ z1|wfcdlj!lIgHnW7{_a=3uHCSgtBVhLK#(UAk4b85LRVc2pcx6p(b@$f@jr$p;f|- zRti;ADb+x!RKl%N3e`j_)c~ literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-2/32blit-lua/assets/background.png b/source-code/ninja-thief/episode-2/32blit-lua/assets/background.png new file mode 100644 index 0000000000000000000000000000000000000000..b1c66ca74d19334492ee777ca0c39261d2cbd743 GIT binary patch literal 1050 zcmV+#1m*jQP)004Lh1^@s6b)xxi00001b5ch_0Itp) z=>Px&)Ja4^RCt{2-Aig4K@>*e8{-WGB@58N`w1Cj^B#Bsw($~@fvLDvYPI@NRrhty zC=jsG<>N0fd3^iz`_EtL(9`+Uw`bKnFYcO8#-3_3~=TW>K zW<(hg)}Og6z>^aRSi_~VA)J2RVE{|kkE2yKgr(sCMOZ`nN(EpEgqLMn z!SY#E0gBi@l17;jiWmSW!joK=1>w{AWCLKy>Y+5sg3uH|5!P^8288ASmMk7iqv#0D z0u*5lOOX+p23WFmFlw6%fFi76)D?6Cuw>B<~tVKo|mG353TReZx=yMe?p;%SPTW9Kcd0gkJti=3HR`ih%G)qiz=( zph&JK*(1X6081d;SMR%}0VtAZ4Lu=D1+WCd?e)4@N`N9c*3bjOv;a#W+*a#*r3WaI zV-2kaz)4XNT3$f_R)KI`t?mTCnW8?)77+q)ArRj0=#2n)r-*B42_XPi2Eyg`-Uomi zinfLp5CU*Vkq~MJz@0@wsJ(&!JW!M;SrZ`u8wA4Z*4_kwjf${_8VCW{I1pZT?;Zf` zkoPqli4cGt1L0S*`C=Leu- z-qx@OLI4^lGeQ6w$mNQ3~S*+wA*Al+=lll<`Z`XO(xA6>r*`K~9z$MTCB;g6r+yM0sg zS3%hNA18Xe^&5bsxt?F%n$_+H8t2G{8DQ+NnIrsQ}giQoqyy>i{`E04@Z`{sC}9fNUQC z4+O~L0kC0!+#Ucs2FUvXaA1JE4uFaQ5`d=42oivX%K#F9j*9rF#sTO&?<)zwV0i-x zz~EU23BY7o0|~(7X@dkHT{Idb0BNz_AOT35Y7G*Abf}{s0Z2{T3KD?SwR<4{0l*$} Uh)ucPr2qf`07*qoM6N<$f~efR@&Et; literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-2/32blit-lua/assets/icon.png b/source-code/ninja-thief/episode-2/32blit-lua/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..baa43d35e8ff1e0c19a3d7a267d1c309062c708d GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|Ts&PILnNjq zznQo9sXepEmi&FE4gf*G%hkNXNeNEVUD7unV7bwEHqn7`-`^E#514#jlqH2aTwsyV h$a|j-#Oh~R7+OvW#UE@ENCBG1;OXk;vd$@?2>=Q1F6jUO literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-2/32blit-lua/assets/logo.png b/source-code/ninja-thief/episode-2/32blit-lua/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..895b373c9bcc1a6afd57337f592a2a8b6e35b367 GIT binary patch literal 1732 zcmV;#20QtQP)Px*en~_@RCt{2+;40XWgG|a?+`6X&4_HeF#~KR0|E^x21o`tG#FtK#tW-LaA65X z5=1a+2tnV8VIfgOkwl|$W+r2CNEpl}5<|A2jl*mxl>iHM*f2KYP->QJZdzZ=mfl^j zy{GqF+v~ocWXV0x{dsPAe)rsS&pkbWh=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=YY^flf zowl6_MF60yy%o!!@}jG~HRV3e;AsrI)+bCymz}}WLgz6)K7y%Jr(t}2L}fRR=}^y<&1_{u*&IAYm2 zfcx}rMLMwlaKZ|2D|=OuuI+zUvrUoHfnI^Kr9TA}IUNZr`DXu(SiW7Z{&y= z4*>p)Ku0s0N;junj!Ze#2at88_jCu+>iq*q>j@w#)YyQ1>*k`+SD<2cg(6QhEh>8T>%9wTD%}ipG~ZoL)3N!xvFXZTO`iJVadqrOaP<*Q zo_agcK7cI(AQvnv(T)SiX!hNeE*zV`Ta%Y)J6Fffl-uQH3y|*;_V^nM73tdguqB;m z27r!i&IeFeel)MPKJ}OcZvm2IV?KZkqPKvoy$#GHpTAErTPPiNnGYZ;?*kyd;>`v@ zrnpvKFq9{QWw!ut%?3sK*r_iqoerId&)Dmk7yrY`nj`MxcF&6)pDQwx!Z7= z*}y)44!e#6h_X!pRJDB`TlUv()!oih>{PrG_8d4AOM4C+0$|{k@}iAc5;vK&r#pZR zufGKVxbXY1oPQ`s*d_q{wOi5k#ZfGOen-;n%uXOpmDYQ`d}r0;K=^XfbIV220ziFF zmpKkFSkQ){Yh$>7MM=_U?UG0>fV&ezMJJG&nP58o+*x4pOvBsf3R|=QL)XTVreo%| z0U7=Ax?Is2REfp_lC3i%uf_p%1H%U5r_ZUU(j{AGmMvOZ)`qVr z(w@J7f!hlJ0DnHQ8fa<3b;rankiV&X5c9tNR7%=WvJh7b=WEJe8@_^qDHG?YK0gu3 zD%Yxa1HvDPT3Wp_V&yQd`U4Vy-XI>T*3OfUH-PUVk0 zto}&U(C{dFe>i8|N20RfR_Ec=8^y`5p&D!Dd;pkj`b4KlC6dpdt;?&&Kt0>NUTpVz zQ!YoXKD$Sgm#-#80DRPX_e-XiUsARx>hxqYDFPtbmAv4?pk}-HynL5)PXV$`wmyL9 zQ$SiDi(Xp2GLp`IWZa$60!(*AaA#DkibN`(h=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=Y aZ0tW#pQt@QVP=j10000NJfxAf@noA`3SK&! zG@bmQs7Q%gWSfI{k`g@h*fs>?&AXQc5A8qTwd+i}i77}?_wZ)kd*6I-zHfFWIeqWp z#PsCFeujxL8sn16?>^g)o?@FJUv1!qJsMxIW$#rxT1?mjk9FIBJ8G{)Z`sjn!}i{@ zr0tn1+C#@bZRX8)`{ZcOKKXRhW@NDsoed0lVUFkVSRWE)=}eG9X`fdK?T86Aj!MCd zWPLz5@iFe9y8?S7Q%C1 zLOx|9h($Bbv9Z)?V8>iM8;g5b+K~&vtdb^R11BQGziEaAOErFKnQGf;I^-9S}n zP8zsw83A_HZ!WICWwS1x%U(b@3l~9SYNREX@3^cqeIK4%0SK>MH0h|Al= zemu&BH&BfY!^5FHt#+ITExEFdJ0VYZxvuZKn+P8#DhRi(pLdKj|nhW(peI6xcbfhD& zPxoErx&U)-;AjsE-dWMCpb|GCn@_V?$g(q&21Rjzk$8$+T_%F%7t@;Px(6G=otRA_8`IaXfilSSqx*L>C?XXM5&At|E;_1L|NJS_O;jtpCUMYd zx5Udu%{LJswS$Udqv`Io+!gf+s5lBX#^W#{dvm72JCE8HDrLp1_e8r7C5J?7VKbs5(3 z6dgd>5twV|Ti1OnLId>U{OlsT{LYrv`D{15V~~Cr^Xu5( zXotQ}1xqJ2NGcd2@p4fUEwNk!(2jT+%HFte4*8wFi^De&aBM)kd$|VaH#8n3wS$TV zBSdICU>7IU4l3F>Cb>p9kv@QSE?zEbD7W)OaYDRP#y2&}4UY$A0F^#KB|QcxtgmTw ztyTq%0a&O(R3Y>BBF^Ff>;Ah|YNGt%2iKax#o3;HHnqGFOLpt2XA<1)bO14vnaAt9fWdx%u@>Lfcz<1pqLzY2gUKW=YtPd?KeEb&J~% z)_sD;{y9L|zxy6o4wyUfrb2>0mwSxER%XPKJ-zE?8k;tLAE53LVy3LBsq*>q1@l+O+9 zj98M`MSNcCsbGk(@AwCyo8ov@-~}>X>ehx?4>)+GkI=@A7T$rqtgv~i^;9rK9@qbX zy{xc#D)l4#0AA-*>N0qpbE9{MiS@s8>oJqls{i-9!7i+YcyCfptM17N;GNPs%E_%` zd%hRzyeR-VhB$yt48ydC#QN zI6#D3ogSd1>1W1xR~wQ2RpnOa!5WLczZc!>R_9>~gk+6(tILoz-mNapDFs f-Sv@^lUn{ScA1G9?8X+-00000NkvXXu0mjfN?;DR literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-2/32blit-lua/constants.lua b/source-code/ninja-thief/episode-2/32blit-lua/constants.lua new file mode 100644 index 0000000..28bb3c9 --- /dev/null +++ b/source-code/ninja-thief/episode-2/32blit-lua/constants.lua @@ -0,0 +1,55 @@ +Constants = { + -- Screen size in pixels + SCREEN_WIDTH = 160, + SCREEN_HEIGHT = 120, + + -- Actual game area in pixels + GAME_WIDTH = 120, + GAME_HEIGHT = 120, + + -- Each sprite on the spritesheet is 8x8 pixels + SPRITE_SIZE = 8, +} + +-- Offset of game area from top left corner +Constants.GAME_OFFSET_X = (Constants.SCREEN_WIDTH - Constants.GAME_WIDTH) / 2 +Constants.GAME_OFFSET_Y = (Constants.SCREEN_HEIGHT - Constants.GAME_HEIGHT) / 2 + +-- Sprite data, including indices to use for rendering +Constants.Sprites = { + -- Offset of the red ninja sprites from the blue ninja sprites + RED_OFFSET = 4, + + -- We'll only be using PLAYER_IDLE for now + PLAYER_IDLE = 32, + PLAYER_CLIMBING_IDLE = 33, + PLAYER_CLIMBING_1 = 34, + PLAYER_CLIMBING_2 = 35, + PLAYER_WALKING_1 = 40, + PLAYER_WALKING_2 = 41, + PLAYER_JUMPING_UP = 42, + PLAYER_JUMPING_DOWN = 43, + + LADDER = 11, + + COIN = 19, + GEM = 18, + + -- These will be used to draw a border either side of the screen, to make the game area 120x120 + BORDER_LEFT = 10, + BORDER_FULL = 9, + BORDER_RIGHT = 8, +} + +-- Player data such as speeds +Constants.Player = { + -- Speeds are measured in pixels per second + MAX_SPEED = 50.0, + + JUMP_SPEED = 125.0, +} + +-- Environment data such as gravity strength +Constants.Environment = { + GRAVITY_ACCELERATION = 375.0, +} \ No newline at end of file diff --git a/source-code/ninja-thief/episode-2/32blit-lua/metadata.yml b/source-code/ninja-thief/episode-2/32blit-lua/metadata.yml new file mode 100644 index 0000000..f1b6dab --- /dev/null +++ b/source-code/ninja-thief/episode-2/32blit-lua/metadata.yml @@ -0,0 +1,12 @@ +# 32blit metadata --config metadata.yml --metadata-file ninja_thief.blmeta + +title: Ninja Thief +description: A tutorial game for the 32blit and PicoSystem. Collect all the coins while avoiding the enemy ninjas! +author: ThePythonator +splash: + file: assets/logo.png +icon: + file: assets/icon.png +version: v1.0.0 +category: game +url: https://github.com/32blit/32blit-tutorials-contrib diff --git a/source-code/ninja-thief/episode-2/32blit-lua/ninja.lua b/source-code/ninja-thief/episode-2/32blit-lua/ninja.lua new file mode 100644 index 0000000..5001994 --- /dev/null +++ b/source-code/ninja-thief/episode-2/32blit-lua/ninja.lua @@ -0,0 +1,59 @@ +require "constants" + +HorizontalDirection = { + LEFT = -1, + RIGHT = 1, +} + +VerticalDirection = { + UP = -1, + DOWN = 1, +} + +Ninja = { + position_x = 0, + posiiton_y = 0, + velocity_x = 0, + velocity_y = 0, + + facing_direction = HorizontalDirection.LEFT, +} + +function Ninja:new(x, y) + local o = {position_x = x, position_y = y} + + setmetatable(o, self) + self.__index = self + return o +end + +function Ninja:update(dt) + -- Before we update the position, update the velocity + self.velocity_y = self.velocity_y + Constants.Environment.GRAVITY_ACCELERATION * dt + + -- Move the ninja + self.position_x = self.position_x + self.velocity_x * dt + self.position_y = self.position_y + self.velocity_y * dt + + -- Update direction the ninja is facing (only if the player is moving) + if self.velocity_x < 0.0 then + self.facing_direction = HorizontalDirection.LEFT + elseif self.velocity_x > 0.0 then + self.facing_direction = HorizontalDirection.RIGHT + end +end + +function Ninja:render() + -- If ninja is travelling left, flip the image horizontally + local transform = SpriteTransform.NONE + if self.facing_direction == HorizontalDirection.LEFT then + transform = SpriteTransform.HORIZONTAL + end + + screen.sprite(Constants.Sprites.PLAYER_IDLE, Point(math.floor(self.position_x + 0.5) + Constants.GAME_OFFSET_X, math.floor(self.position_y + 0.5) + Constants.GAME_OFFSET_Y), transform) +end + +function Ninja:jump(jump_speed) + -- Upwards is negative + self.velocity_y = -jump_speed +end diff --git a/source-code/ninja-thief/episode-2/32blit-lua/ninja_thief.lua b/source-code/ninja-thief/episode-2/32blit-lua/ninja_thief.lua new file mode 100644 index 0000000..46d2dca --- /dev/null +++ b/source-code/ninja-thief/episode-2/32blit-lua/ninja_thief.lua @@ -0,0 +1,56 @@ +require "constants" +require "player_ninja" + +-- Our global variables are defined here +background = nil + +last_time = 1.0 + +-- Temporary player object for testing +player = PlayerNinja:new(50, 50) + +-- Setup your game here +function init() + -- Set the resolution to 160x120 + set_screen_mode(ScreenMode.lores) + + -- Load the background from assets.cpp + background = Surface.load("assets/background.blim") + + -- Load the spritesheet + screen.load_sprites("assets/spritesheet.blim") +end + +-- This function is called to perform rendering of the game. +function render(time) + -- Clear the screen + screen.pen = Pen(0, 0, 0) + screen.clear() + + -- Draw the entire background image onto the screen at (0, 0) + screen.blit(background, Rect(0, 0, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT), Point(0, 0)) + + -- Set the text colour to white + screen.pen = Pen(255, 255, 255) + + -- Render the placeholder score text + screen.text("Score: 0", minimal_font, Point(2, 2)) + + -- Render the player + player:render() +end + +-- This is called to update your game state. +function update(time) + -- Calculate change in time (in seconds) since last frame + local dt = (time - last_time) / 1000.0 + last_time = time + + -- Limit dt + if dt > 0.05 then + dt = 0.05 + end + + -- Update the player + player:update(dt) +end diff --git a/source-code/ninja-thief/episode-2/32blit-lua/player_ninja.lua b/source-code/ninja-thief/episode-2/32blit-lua/player_ninja.lua new file mode 100644 index 0000000..b1ac283 --- /dev/null +++ b/source-code/ninja-thief/episode-2/32blit-lua/player_ninja.lua @@ -0,0 +1,30 @@ +require "ninja" + +PlayerNinja = Ninja:new() + +function PlayerNinja:update(dt) + -- If nothing is pressed, the player shouldn't move + self.velocity_x = 0.0 + + -- Note: "else if" isn't used, because otherwise the sprite will still move when both buttons are pressed + -- Instead, we add/subtract the velocity, so if both are pressed, nothing happens + if buttons.state & Button.LEFT ~= 0 then + self.velocity_x = self.velocity_x - Constants.Player.MAX_SPEED + end + if buttons.state & Button.RIGHT ~= 0 then + self.velocity_x = self.velocity_x + Constants.Player.MAX_SPEED + end + + -- Handle jumping + -- Note that we use buttons.pressed, which only contains the buttons just pressed (since the last frame) + if buttons.pressed & Button.A ~= 0 then + self:jump(Constants.Player.JUMP_SPEED) + end + + Ninja.update(self, dt) + + -- Temporary addition to stop player falling off bottom of screen + if self.position_y > Constants.GAME_HEIGHT - Constants.SPRITE_SIZE then + self.position_y = Constants.GAME_HEIGHT - Constants.SPRITE_SIZE + end +end From c05487a2b31526d728487ab0549a105af926ef25 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Sun, 15 Sep 2024 18:44:28 +0100 Subject: [PATCH 3/6] Lua version of ninja thief episode 3 --- .../episode-3/32blit-lua/assets.yml | 8 + .../32blit-lua/assets/background.blim | Bin 0 -> 521 bytes .../32blit-lua/assets/background.png | Bin 0 -> 1050 bytes .../episode-3/32blit-lua/assets/icon.png | Bin 0 -> 138 bytes .../episode-3/32blit-lua/assets/logo.png | Bin 0 -> 1732 bytes .../32blit-lua/assets/spritesheet.blim | Bin 0 -> 1303 bytes .../32blit-lua/assets/spritesheet.png | Bin 0 -> 1113 bytes .../episode-3/32blit-lua/constants.lua | 97 +++++++++ .../episode-3/32blit-lua/level.lua | 84 ++++++++ .../episode-3/32blit-lua/metadata.yml | 12 ++ .../episode-3/32blit-lua/ninja.lua | 193 ++++++++++++++++++ .../episode-3/32blit-lua/ninja_thief.lua | 51 +++++ .../episode-3/32blit-lua/player_ninja.lua | 30 +++ 13 files changed, 475 insertions(+) create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/assets.yml create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/assets/background.blim create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/assets/background.png create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/assets/icon.png create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/assets/logo.png create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/assets/spritesheet.blim create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/assets/spritesheet.png create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/constants.lua create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/level.lua create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/metadata.yml create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/ninja.lua create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/ninja_thief.lua create mode 100644 source-code/ninja-thief/episode-3/32blit-lua/player_ninja.lua diff --git a/source-code/ninja-thief/episode-3/32blit-lua/assets.yml b/source-code/ninja-thief/episode-3/32blit-lua/assets.yml new file mode 100644 index 0000000..721942b --- /dev/null +++ b/source-code/ninja-thief/episode-3/32blit-lua/assets.yml @@ -0,0 +1,8 @@ +# 32blit pack --force --config assets.yml +# ideally these should be .blim, but the tools don't recognise that + +background.bin: + assets/background.png: + +spritesheet.bin: + assets/spritesheet.png: diff --git a/source-code/ninja-thief/episode-3/32blit-lua/assets/background.blim b/source-code/ninja-thief/episode-3/32blit-lua/assets/background.blim new file mode 100644 index 0000000000000000000000000000000000000000..f06181f75845af32637310c528d42a5576b8c1ae GIT binary patch literal 521 zcmV+k0`~n=P*O=$MN&)&0ssJ@0C)fb15i>{|8;X||2#cK|B94$CQ4Y*M`C25jud7l zN*LirVq~3<6jmll*zrYTWSNi@HYQ1#2}5FJnUIt=CP`TtLSkf_Ibvj8ofDQOMVa9^Vq{gGlZGZm8TmJ2WKEzGb|yrc z5jA3DNuiTgCPZ2xHDY8zqZ39ZL)tksVq`g_lQt$pT2V7%WHF`_CMH7KX)$7CEvFL} zCPJElFk)mSsFMaJLK>MbVq_hv680uRnz1foWEHED<|aWJ!7XBB5v>x&CP3QREMj3M zx=PrXO?Z{DFxUxGbtO&Ml(%9~-N{3EB<}`sqDtP$8+;^f^pQ8x zMBj-Eek3jUkhk(c-^lxcBhCbmI3haWh~tDKP85u|Vld(evX&f*uv-d5*iC{WOg6v~ z1|wfcdlj!lIgHnW7{_a=3uHCSgtBVhLK#(UAk4b85LRVc2pcx6p(b@$f@jr$p;f|- zRti;ADb+x!RKl%N3e`j_)c~ literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-3/32blit-lua/assets/background.png b/source-code/ninja-thief/episode-3/32blit-lua/assets/background.png new file mode 100644 index 0000000000000000000000000000000000000000..b1c66ca74d19334492ee777ca0c39261d2cbd743 GIT binary patch literal 1050 zcmV+#1m*jQP)004Lh1^@s6b)xxi00001b5ch_0Itp) z=>Px&)Ja4^RCt{2-Aig4K@>*e8{-WGB@58N`w1Cj^B#Bsw($~@fvLDvYPI@NRrhty zC=jsG<>N0fd3^iz`_EtL(9`+Uw`bKnFYcO8#-3_3~=TW>K zW<(hg)}Og6z>^aRSi_~VA)J2RVE{|kkE2yKgr(sCMOZ`nN(EpEgqLMn z!SY#E0gBi@l17;jiWmSW!joK=1>w{AWCLKy>Y+5sg3uH|5!P^8288ASmMk7iqv#0D z0u*5lOOX+p23WFmFlw6%fFi76)D?6Cuw>B<~tVKo|mG353TReZx=yMe?p;%SPTW9Kcd0gkJti=3HR`ih%G)qiz=( zph&JK*(1X6081d;SMR%}0VtAZ4Lu=D1+WCd?e)4@N`N9c*3bjOv;a#W+*a#*r3WaI zV-2kaz)4XNT3$f_R)KI`t?mTCnW8?)77+q)ArRj0=#2n)r-*B42_XPi2Eyg`-Uomi zinfLp5CU*Vkq~MJz@0@wsJ(&!JW!M;SrZ`u8wA4Z*4_kwjf${_8VCW{I1pZT?;Zf` zkoPqli4cGt1L0S*`C=Leu- z-qx@OLI4^lGeQ6w$mNQ3~S*+wA*Al+=lll<`Z`XO(xA6>r*`K~9z$MTCB;g6r+yM0sg zS3%hNA18Xe^&5bsxt?F%n$_+H8t2G{8DQ+NnIrsQ}giQoqyy>i{`E04@Z`{sC}9fNUQC z4+O~L0kC0!+#Ucs2FUvXaA1JE4uFaQ5`d=42oivX%K#F9j*9rF#sTO&?<)zwV0i-x zz~EU23BY7o0|~(7X@dkHT{Idb0BNz_AOT35Y7G*Abf}{s0Z2{T3KD?SwR<4{0l*$} Uh)ucPr2qf`07*qoM6N<$f~efR@&Et; literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-3/32blit-lua/assets/icon.png b/source-code/ninja-thief/episode-3/32blit-lua/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..baa43d35e8ff1e0c19a3d7a267d1c309062c708d GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|Ts&PILnNjq zznQo9sXepEmi&FE4gf*G%hkNXNeNEVUD7unV7bwEHqn7`-`^E#514#jlqH2aTwsyV h$a|j-#Oh~R7+OvW#UE@ENCBG1;OXk;vd$@?2>=Q1F6jUO literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-3/32blit-lua/assets/logo.png b/source-code/ninja-thief/episode-3/32blit-lua/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..895b373c9bcc1a6afd57337f592a2a8b6e35b367 GIT binary patch literal 1732 zcmV;#20QtQP)Px*en~_@RCt{2+;40XWgG|a?+`6X&4_HeF#~KR0|E^x21o`tG#FtK#tW-LaA65X z5=1a+2tnV8VIfgOkwl|$W+r2CNEpl}5<|A2jl*mxl>iHM*f2KYP->QJZdzZ=mfl^j zy{GqF+v~ocWXV0x{dsPAe)rsS&pkbWh=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=YY^flf zowl6_MF60yy%o!!@}jG~HRV3e;AsrI)+bCymz}}WLgz6)K7y%Jr(t}2L}fRR=}^y<&1_{u*&IAYm2 zfcx}rMLMwlaKZ|2D|=OuuI+zUvrUoHfnI^Kr9TA}IUNZr`DXu(SiW7Z{&y= z4*>p)Ku0s0N;junj!Ze#2at88_jCu+>iq*q>j@w#)YyQ1>*k`+SD<2cg(6QhEh>8T>%9wTD%}ipG~ZoL)3N!xvFXZTO`iJVadqrOaP<*Q zo_agcK7cI(AQvnv(T)SiX!hNeE*zV`Ta%Y)J6Fffl-uQH3y|*;_V^nM73tdguqB;m z27r!i&IeFeel)MPKJ}OcZvm2IV?KZkqPKvoy$#GHpTAErTPPiNnGYZ;?*kyd;>`v@ zrnpvKFq9{QWw!ut%?3sK*r_iqoerId&)Dmk7yrY`nj`MxcF&6)pDQwx!Z7= z*}y)44!e#6h_X!pRJDB`TlUv()!oih>{PrG_8d4AOM4C+0$|{k@}iAc5;vK&r#pZR zufGKVxbXY1oPQ`s*d_q{wOi5k#ZfGOen-;n%uXOpmDYQ`d}r0;K=^XfbIV220ziFF zmpKkFSkQ){Yh$>7MM=_U?UG0>fV&ezMJJG&nP58o+*x4pOvBsf3R|=QL)XTVreo%| z0U7=Ax?Is2REfp_lC3i%uf_p%1H%U5r_ZUU(j{AGmMvOZ)`qVr z(w@J7f!hlJ0DnHQ8fa<3b;rankiV&X5c9tNR7%=WvJh7b=WEJe8@_^qDHG?YK0gu3 zD%Yxa1HvDPT3Wp_V&yQd`U4Vy-XI>T*3OfUH-PUVk0 zto}&U(C{dFe>i8|N20RfR_Ec=8^y`5p&D!Dd;pkj`b4KlC6dpdt;?&&Kt0>NUTpVz zQ!YoXKD$Sgm#-#80DRPX_e-XiUsARx>hxqYDFPtbmAv4?pk}-HynL5)PXV$`wmyL9 zQ$SiDi(Xp2GLp`IWZa$60!(*AaA#DkibN`(h=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=Y aZ0tW#pQt@QVP=j10000NJfxAf@noA`3SK&! zG@bmQs7Q%gWSfI{k`g@h*fs>?&AXQc5A8qTwd+i}i77}?_wZ)kd*6I-zHfFWIeqWp z#PsCFeujxL8sn16?>^g)o?@FJUv1!qJsMxIW$#rxT1?mjk9FIBJ8G{)Z`sjn!}i{@ zr0tn1+C#@bZRX8)`{ZcOKKXRhW@NDsoed0lVUFkVSRWE)=}eG9X`fdK?T86Aj!MCd zWPLz5@iFe9y8?S7Q%C1 zLOx|9h($Bbv9Z)?V8>iM8;g5b+K~&vtdb^R11BQGziEaAOErFKnQGf;I^-9S}n zP8zsw83A_HZ!WICWwS1x%U(b@3l~9SYNREX@3^cqeIK4%0SK>MH0h|Al= zemu&BH&BfY!^5FHt#+ITExEFdJ0VYZxvuZKn+P8#DhRi(pLdKj|nhW(peI6xcbfhD& zPxoErx&U)-;AjsE-dWMCpb|GCn@_V?$g(q&21Rjzk$8$+T_%F%7t@;Px(6G=otRA_8`IaXfilSSqx*L>C?XXM5&At|E;_1L|NJS_O;jtpCUMYd zx5Udu%{LJswS$Udqv`Io+!gf+s5lBX#^W#{dvm72JCE8HDrLp1_e8r7C5J?7VKbs5(3 z6dgd>5twV|Ti1OnLId>U{OlsT{LYrv`D{15V~~Cr^Xu5( zXotQ}1xqJ2NGcd2@p4fUEwNk!(2jT+%HFte4*8wFi^De&aBM)kd$|VaH#8n3wS$TV zBSdICU>7IU4l3F>Cb>p9kv@QSE?zEbD7W)OaYDRP#y2&}4UY$A0F^#KB|QcxtgmTw ztyTq%0a&O(R3Y>BBF^Ff>;Ah|YNGt%2iKax#o3;HHnqGFOLpt2XA<1)bO14vnaAt9fWdx%u@>Lfcz<1pqLzY2gUKW=YtPd?KeEb&J~% z)_sD;{y9L|zxy6o4wyUfrb2>0mwSxER%XPKJ-zE?8k;tLAE53LVy3LBsq*>q1@l+O+9 zj98M`MSNcCsbGk(@AwCyo8ov@-~}>X>ehx?4>)+GkI=@A7T$rqtgv~i^;9rK9@qbX zy{xc#D)l4#0AA-*>N0qpbE9{MiS@s8>oJqls{i-9!7i+YcyCfptM17N;GNPs%E_%` zd%hRzyeR-VhB$yt48ydC#QN zI6#D3ogSd1>1W1xR~wQ2RpnOa!5WLczZc!>R_9>~gk+6(tILoz-mNapDFs f-Sv@^lUn{ScA1G9?8X+-00000NkvXXu0mjfN?;DR literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-3/32blit-lua/constants.lua b/source-code/ninja-thief/episode-3/32blit-lua/constants.lua new file mode 100644 index 0000000..55ed9db --- /dev/null +++ b/source-code/ninja-thief/episode-3/32blit-lua/constants.lua @@ -0,0 +1,97 @@ +Constants = { + -- Screen size in pixels + SCREEN_WIDTH = 160, + SCREEN_HEIGHT = 120, + + -- Actual game area in pixels + GAME_WIDTH = 120, + GAME_HEIGHT = 120, + + -- Each sprite on the spritesheet is 8x8 pixels + SPRITE_SIZE = 8, +} + +-- Offset of game area from top left corner +Constants.GAME_OFFSET_X = (Constants.SCREEN_WIDTH - Constants.GAME_WIDTH) / 2 +Constants.GAME_OFFSET_Y = (Constants.SCREEN_HEIGHT - Constants.GAME_HEIGHT) / 2 + +-- Game area size in tiles +Constants.GAME_WIDTH_TILES = Constants.GAME_WIDTH / Constants.SPRITE_SIZE +Constants.GAME_HEIGHT_TILES = Constants.GAME_HEIGHT / Constants.SPRITE_SIZE + +-- Sprite data, including indices to use for rendering +Constants.Sprites = { + -- Offset of the red ninja sprites from the blue ninja sprites + RED_OFFSET = 4, + + -- We'll only be using PLAYER_IDLE for now + PLAYER_IDLE = 32, + PLAYER_CLIMBING_IDLE = 33, + PLAYER_CLIMBING_1 = 34, + PLAYER_CLIMBING_2 = 35, + PLAYER_WALKING_1 = 40, + PLAYER_WALKING_2 = 41, + PLAYER_JUMPING_UP = 42, + PLAYER_JUMPING_DOWN = 43, + + LADDER = 11, + + COIN = 19, + GEM = 18, + + -- These will be used to draw a border either side of the screen, to make the game area 120x120 + BORDER_LEFT = 10, + BORDER_FULL = 9, + BORDER_RIGHT = 8, + + -- A blank tile is represented by 0xff in the level arrays + BLANK_TILE = 0xff, +} + +-- Generic ninja data such as size +Constants.Ninja = { + -- The visible width of the ninja sprite + WIDTH = 4, +} + +-- The gap between the edge of the sprite and the edge of the ninja on each side +Constants.Ninja.BORDER = (Constants.SPRITE_SIZE - Constants.Ninja.WIDTH) / 2 + +-- Player data such as speeds +Constants.Player = { + -- Speeds are measured in pixels per second + MAX_SPEED = 50.0, + + JUMP_SPEED = 125.0, +} + +-- Environment data such as gravity strength +Constants.Environment = { + GRAVITY_ACCELERATION = 375.0, +} + +Constants.LEVEL_COUNT = 1 + +Constants.LEVELS = { + -- Level 1 + { + -- Platform data + platforms = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x01, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x01, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + } + } +} \ No newline at end of file diff --git a/source-code/ninja-thief/episode-3/32blit-lua/level.lua b/source-code/ninja-thief/episode-3/32blit-lua/level.lua new file mode 100644 index 0000000..0f6ff30 --- /dev/null +++ b/source-code/ninja-thief/episode-3/32blit-lua/level.lua @@ -0,0 +1,84 @@ +require "constants" +require "player_ninja" + +Level = { + level_data = nil, + level_number = 0, + + player = nil, +} + +function Level:new(level_number) + local o = { + level_number = level_number, + level_data = Constants.LEVELS[level_number], + + -- Temporary player object for testing + player = PlayerNinja:new(50, 50), + } + + setmetatable(o, self) + self.__index = self + return o +end + +function Level:update(dt) + self.player:update(dt, self.level_data) +end + +function Level:render() + -- Render border + self:render_border() + + -- Render platform tiles + for y = 0, Constants.GAME_HEIGHT_TILES - 1 do + for x = 0, Constants.GAME_WIDTH_TILES - 1 do + local tile_id = self.level_data.platforms[y * Constants.GAME_WIDTH_TILES + x + 1] + + if tile_id ~= Constants.Sprites.BLANK_TILE then + -- Offset the tiles since the 32blit version has borders on the screen + screen.sprite(tile_id, Point(x * Constants.SPRITE_SIZE + Constants.GAME_OFFSET_X, y * Constants.SPRITE_SIZE + Constants.GAME_OFFSET_Y)) + end + end + end + + -- Render the player + self.player:render() + + -- Set the text colour to white + screen.pen = Pen(255, 255, 255) + + -- Render the placeholder score text + screen.text("Score: 0", minimal_font, Point(2, 2)) +end + +function Level:render_border() + -- Offset the tiles since the 32blit version has borders on the screen + + -- Each row is the same + for y = 0, Constants.SCREEN_HEIGHT - 1, Constants.SPRITE_SIZE do + -- Left border: + local x = 0 + + -- BORDER_FULL sprites + while x < Constants.GAME_OFFSET_X - Constants.SPRITE_SIZE do + screen.sprite(Constants.Sprites.BORDER_FULL, Point(x, y)) + x = x + Constants.SPRITE_SIZE + end + + -- BORDER_LEFT sprite + screen.sprite(Constants.Sprites.BORDER_LEFT, Point(x, y)) + + -- Right border: + x = Constants.SCREEN_WIDTH + + -- BORDER_FULL sprites + while x > Constants.SCREEN_WIDTH - Constants.GAME_OFFSET_X do + screen.sprite(Constants.Sprites.BORDER_FULL, Point(x, y)) + x = x - Constants.SPRITE_SIZE + end + + -- BORDER_RIGHT sprite + screen.sprite(Constants.Sprites.BORDER_RIGHT, Point(x, y)) + end +end \ No newline at end of file diff --git a/source-code/ninja-thief/episode-3/32blit-lua/metadata.yml b/source-code/ninja-thief/episode-3/32blit-lua/metadata.yml new file mode 100644 index 0000000..f1b6dab --- /dev/null +++ b/source-code/ninja-thief/episode-3/32blit-lua/metadata.yml @@ -0,0 +1,12 @@ +# 32blit metadata --config metadata.yml --metadata-file ninja_thief.blmeta + +title: Ninja Thief +description: A tutorial game for the 32blit and PicoSystem. Collect all the coins while avoiding the enemy ninjas! +author: ThePythonator +splash: + file: assets/logo.png +icon: + file: assets/icon.png +version: v1.0.0 +category: game +url: https://github.com/32blit/32blit-tutorials-contrib diff --git a/source-code/ninja-thief/episode-3/32blit-lua/ninja.lua b/source-code/ninja-thief/episode-3/32blit-lua/ninja.lua new file mode 100644 index 0000000..1235a3d --- /dev/null +++ b/source-code/ninja-thief/episode-3/32blit-lua/ninja.lua @@ -0,0 +1,193 @@ +require "constants" + +HorizontalDirection = { + LEFT = -1, + RIGHT = 1, +} + +VerticalDirection = { + UP = -1, + DOWN = 1, +} + +Ninja = { + position_x = 0, + posiiton_y = 0, + velocity_x = 0, + velocity_y = 0, + + facing_direction = HorizontalDirection.LEFT, +} + +function Ninja:new(x, y) + local o = {position_x = x, position_y = y} + + setmetatable(o, self) + self.__index = self + return o +end + +function Ninja:update(dt, level_data) + -- Before we update the position, update the velocity + self.velocity_y = self.velocity_y + Constants.Environment.GRAVITY_ACCELERATION * dt + + -- Move the ninja + self.position_x = self.position_x + self.velocity_x * dt + self.position_y = self.position_y + self.velocity_y * dt + + + -- Don't allow ninja to go off the sides + if self.position_x < -Constants.Ninja.BORDER then + self.position_x = -Constants.Ninja.BORDER + elseif self.position_x > Constants.GAME_WIDTH - Constants.Ninja.BORDER - Constants.Ninja.WIDTH then + self.position_x = Constants.GAME_WIDTH - Constants.Ninja.BORDER - Constants.Ninja.WIDTH + end + + -- Detect and resolve any collisions with platforms, ladders, coins etc + self:handle_collisions(level_data) + + -- Update direction the ninja is facing (only if the player is moving) + if self.velocity_x < 0.0 then + self.facing_direction = HorizontalDirection.LEFT + elseif self.velocity_x > 0.0 then + self.facing_direction = HorizontalDirection.RIGHT + end +end + +function Ninja:render() + -- If ninja is travelling left, flip the image horizontally + local transform = SpriteTransform.NONE + if self.facing_direction == HorizontalDirection.LEFT then + transform = SpriteTransform.HORIZONTAL + end + + screen.sprite(Constants.Sprites.PLAYER_IDLE, Point(math.floor(self.position_x + 0.5) + Constants.GAME_OFFSET_X, math.floor(self.position_y + 0.5) + Constants.GAME_OFFSET_Y), transform) +end + +function Ninja:check_object_colliding(object_x, object_y, object_size) + return self.position_x + Constants.SPRITE_SIZE - Constants.Ninja.BORDER > object_x and + self.position_x + Constants.Ninja.BORDER < object_x + object_size and + self.position_y + Constants.SPRITE_SIZE > object_y and + self.position_y < object_y + object_size +end + +function Ninja:handle_collisions(level_data) + -- Get position of ninja in "grid" of tiles + -- We're relying on converting to integers to truncate and hence round towards zero + -- FIXME: this is not doing that + local x = self.position_x // Constants.SPRITE_SIZE + local y = self.position_y // Constants.SPRITE_SIZE + + -- Check the four tiles which the ninja might be colliding with (the top left tile is marked by the x and y previously calculated) + + -- We need to check that the player is within the game area + -- If they aren't, we don't need to worry about checking for collisions + if x < Constants.GAME_WIDTH_TILES and y < Constants.GAME_HEIGHT_TILES and self.position_x >= -Constants.Ninja.BORDER and self.position_y >= -Constants.SPRITE_SIZE then + + -- It's possible the ninja is near the edge of the screen and we could end up checking tiles which don't exist (off the edge of the screen) + -- To avoid this issue, we vary the maximum x and y offsets + -- The minimum offset is handled by the trucation, since it will round up (rather than down) if the value is negative + local y_max = 1 + if y == Constants.GAME_HEIGHT_TILES - 1 then + y_max = 0 + end + + for y_offset = 0, y_max do + + local x_max = 1 + if x == Constants.GAME_WIDTH_TILES - 1 then + x_max = 0 + end + + for x_offset = 0, x_max do + -- Calculate grid position of this tile + local new_x = x + x_offset + local new_y = y + y_offset + + -- Handle platforms + self:handle_platform(level_data, new_x, new_y) + end + end + end +end + +function Ninja:handle_platform(level_data, x, y) + -- Get tile's sprite index from level data + local tile_id = level_data.platforms[y * Constants.GAME_WIDTH_TILES + x + 1] + + -- Check the tile actually exists (check that it isn't blank) + if tile_id ~= Constants.Sprites.BLANK_TILE then + + -- Calculate the actual position of the tile from the grid position + local tile_x = x * Constants.SPRITE_SIZE + local tile_y = y * Constants.SPRITE_SIZE + + -- Check if the ninja is colliding with the tile + if self:check_object_colliding(tile_x, tile_y, Constants.SPRITE_SIZE) then + -- Resolve collision by finding the direction with the least intersection + -- The value of the direction variable corresponds to: + -- 0 - left side of tile + -- 1 - top side of tile + -- 2 - right side of tile + -- 3 - bottom side of tile + local direction = 0 + + -- The starting value of least_intersection is at least the maximum possible intersection + -- The width/height of the tile is the maximum intersection possible + local least_intersection = Constants.SPRITE_SIZE + + -- Left side of tile + local intersection = self.position_x + Constants.Ninja.WIDTH + Constants.Ninja.BORDER - tile_x + if intersection < least_intersection then + direction = 0 + least_intersection = intersection + end + + -- Top side of tile + intersection = self.position_y + Constants.SPRITE_SIZE - tile_y + if intersection < least_intersection then + direction = 1 + least_intersection = intersection + end + + -- Right side of tile + intersection = tile_x + Constants.SPRITE_SIZE - self.position_x - Constants.Ninja.BORDER + if intersection < least_intersection then + direction = 2 + least_intersection = intersection + end + + -- Bottom side of tile + intersection = tile_y + Constants.SPRITE_SIZE - self.position_y + if intersection < least_intersection then + direction = 3 + least_intersection = intersection + end + + -- Now resolve collision by moving the ninja in the direction of least intersection, by exactly the amount equal to the least intersection + if direction == 0 then + -- Hit the left side of a platform + self.position_x = self.position_x - least_intersection + self.velocity_x = 0.0 + elseif direction == 1 then + -- Landed on top of a platform + self.position_y = self.position_y - least_intersection + self.velocity_y = 0.0 + elseif direction == 2 then + -- Hit the right side of a platform + self.position_x = self.position_x + least_intersection + self.velocity_x = 0.0 + elseif direction == 3 then + -- Hit the underside of a platform + self.position_y = self.position_y + least_intersection + self.velocity_y = 0.0 + end + end + end +end + +function Ninja:jump(jump_speed) + -- Upwards is negative + self.velocity_y = -jump_speed +end + diff --git a/source-code/ninja-thief/episode-3/32blit-lua/ninja_thief.lua b/source-code/ninja-thief/episode-3/32blit-lua/ninja_thief.lua new file mode 100644 index 0000000..4a73b12 --- /dev/null +++ b/source-code/ninja-thief/episode-3/32blit-lua/ninja_thief.lua @@ -0,0 +1,51 @@ +require "constants" +require "level" + +-- Our global variables are defined here +background = nil + +last_time = 1.0 + +level = nil + +-- Setup your game here +function init() + -- Set the resolution to 160x120 + set_screen_mode(ScreenMode.lores) + + -- Load the background from assets.cpp + background = Surface.load("assets/background.blim") + + -- Load the spritesheet + screen.load_sprites("assets/spritesheet.blim") + + level = Level:new(1) +end + +-- This function is called to perform rendering of the game. +function render(time) + -- Clear the screen + screen.pen = Pen(0, 0, 0) + screen.clear() + + -- Draw the entire background image onto the screen at (0, 0) + screen.blit(background, Rect(0, 0, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT), Point(0, 0)) + + -- Render the level + level:render() +end + +-- This is called to update your game state. +function update(time) + -- Calculate change in time (in seconds) since last frame + local dt = (time - last_time) / 1000.0 + last_time = time + + -- Limit dt + if dt > 0.05 then + dt = 0.05 + end + + -- Update the level + level:update(dt) +end diff --git a/source-code/ninja-thief/episode-3/32blit-lua/player_ninja.lua b/source-code/ninja-thief/episode-3/32blit-lua/player_ninja.lua new file mode 100644 index 0000000..eb1027c --- /dev/null +++ b/source-code/ninja-thief/episode-3/32blit-lua/player_ninja.lua @@ -0,0 +1,30 @@ +require "ninja" + +PlayerNinja = Ninja:new() + +function PlayerNinja:update(dt, level_data) + -- If nothing is pressed, the player shouldn't move + self.velocity_x = 0.0 + + -- Note: "else if" isn't used, because otherwise the sprite will still move when both buttons are pressed + -- Instead, we add/subtract the velocity, so if both are pressed, nothing happens + if buttons.state & Button.LEFT ~= 0 then + self.velocity_x = self.velocity_x - Constants.Player.MAX_SPEED + end + if buttons.state & Button.RIGHT ~= 0 then + self.velocity_x = self.velocity_x + Constants.Player.MAX_SPEED + end + + -- Handle jumping + -- Note that we use buttons.pressed, which only contains the buttons just pressed (since the last frame) + if buttons.pressed & Button.A ~= 0 then + self:jump(Constants.Player.JUMP_SPEED) + end + + Ninja.update(self, dt, level_data) + + -- Temporary addition to stop player falling off bottom of screen + if self.position_y > Constants.GAME_HEIGHT - Constants.SPRITE_SIZE then + self.position_y = Constants.GAME_HEIGHT - Constants.SPRITE_SIZE + end +end From e4c5bf7c9aaa6e918cc0f511d5fa4bca5381823c Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Wed, 18 Sep 2024 20:54:40 +0100 Subject: [PATCH 4/6] Lua version of ninja thief episode 4 --- .../episode-4/32blit-lua/assets.yml | 8 + .../32blit-lua/assets/background.blim | Bin 0 -> 521 bytes .../32blit-lua/assets/background.png | Bin 0 -> 1050 bytes .../episode-4/32blit-lua/assets/icon.png | Bin 0 -> 138 bytes .../episode-4/32blit-lua/assets/logo.png | Bin 0 -> 1732 bytes .../32blit-lua/assets/spritesheet.blim | Bin 0 -> 1303 bytes .../32blit-lua/assets/spritesheet.png | Bin 0 -> 1113 bytes .../episode-4/32blit-lua/constants.lua | 152 ++++++++ .../episode-4/32blit-lua/enemy_ninja.lua | 121 +++++++ .../episode-4/32blit-lua/level.lua | 129 +++++++ .../episode-4/32blit-lua/metadata.yml | 12 + .../episode-4/32blit-lua/ninja.lua | 325 ++++++++++++++++++ .../episode-4/32blit-lua/ninja_thief.lua | 51 +++ .../episode-4/32blit-lua/player_ninja.lua | 45 +++ 14 files changed, 843 insertions(+) create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/assets.yml create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/assets/background.blim create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/assets/background.png create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/assets/icon.png create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/assets/logo.png create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/assets/spritesheet.blim create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/assets/spritesheet.png create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/constants.lua create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/enemy_ninja.lua create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/level.lua create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/metadata.yml create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/ninja.lua create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/ninja_thief.lua create mode 100644 source-code/ninja-thief/episode-4/32blit-lua/player_ninja.lua diff --git a/source-code/ninja-thief/episode-4/32blit-lua/assets.yml b/source-code/ninja-thief/episode-4/32blit-lua/assets.yml new file mode 100644 index 0000000..721942b --- /dev/null +++ b/source-code/ninja-thief/episode-4/32blit-lua/assets.yml @@ -0,0 +1,8 @@ +# 32blit pack --force --config assets.yml +# ideally these should be .blim, but the tools don't recognise that + +background.bin: + assets/background.png: + +spritesheet.bin: + assets/spritesheet.png: diff --git a/source-code/ninja-thief/episode-4/32blit-lua/assets/background.blim b/source-code/ninja-thief/episode-4/32blit-lua/assets/background.blim new file mode 100644 index 0000000000000000000000000000000000000000..f06181f75845af32637310c528d42a5576b8c1ae GIT binary patch literal 521 zcmV+k0`~n=P*O=$MN&)&0ssJ@0C)fb15i>{|8;X||2#cK|B94$CQ4Y*M`C25jud7l zN*LirVq~3<6jmll*zrYTWSNi@HYQ1#2}5FJnUIt=CP`TtLSkf_Ibvj8ofDQOMVa9^Vq{gGlZGZm8TmJ2WKEzGb|yrc z5jA3DNuiTgCPZ2xHDY8zqZ39ZL)tksVq`g_lQt$pT2V7%WHF`_CMH7KX)$7CEvFL} zCPJElFk)mSsFMaJLK>MbVq_hv680uRnz1foWEHED<|aWJ!7XBB5v>x&CP3QREMj3M zx=PrXO?Z{DFxUxGbtO&Ml(%9~-N{3EB<}`sqDtP$8+;^f^pQ8x zMBj-Eek3jUkhk(c-^lxcBhCbmI3haWh~tDKP85u|Vld(evX&f*uv-d5*iC{WOg6v~ z1|wfcdlj!lIgHnW7{_a=3uHCSgtBVhLK#(UAk4b85LRVc2pcx6p(b@$f@jr$p;f|- zRti;ADb+x!RKl%N3e`j_)c~ literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-4/32blit-lua/assets/background.png b/source-code/ninja-thief/episode-4/32blit-lua/assets/background.png new file mode 100644 index 0000000000000000000000000000000000000000..b1c66ca74d19334492ee777ca0c39261d2cbd743 GIT binary patch literal 1050 zcmV+#1m*jQP)004Lh1^@s6b)xxi00001b5ch_0Itp) z=>Px&)Ja4^RCt{2-Aig4K@>*e8{-WGB@58N`w1Cj^B#Bsw($~@fvLDvYPI@NRrhty zC=jsG<>N0fd3^iz`_EtL(9`+Uw`bKnFYcO8#-3_3~=TW>K zW<(hg)}Og6z>^aRSi_~VA)J2RVE{|kkE2yKgr(sCMOZ`nN(EpEgqLMn z!SY#E0gBi@l17;jiWmSW!joK=1>w{AWCLKy>Y+5sg3uH|5!P^8288ASmMk7iqv#0D z0u*5lOOX+p23WFmFlw6%fFi76)D?6Cuw>B<~tVKo|mG353TReZx=yMe?p;%SPTW9Kcd0gkJti=3HR`ih%G)qiz=( zph&JK*(1X6081d;SMR%}0VtAZ4Lu=D1+WCd?e)4@N`N9c*3bjOv;a#W+*a#*r3WaI zV-2kaz)4XNT3$f_R)KI`t?mTCnW8?)77+q)ArRj0=#2n)r-*B42_XPi2Eyg`-Uomi zinfLp5CU*Vkq~MJz@0@wsJ(&!JW!M;SrZ`u8wA4Z*4_kwjf${_8VCW{I1pZT?;Zf` zkoPqli4cGt1L0S*`C=Leu- z-qx@OLI4^lGeQ6w$mNQ3~S*+wA*Al+=lll<`Z`XO(xA6>r*`K~9z$MTCB;g6r+yM0sg zS3%hNA18Xe^&5bsxt?F%n$_+H8t2G{8DQ+NnIrsQ}giQoqyy>i{`E04@Z`{sC}9fNUQC z4+O~L0kC0!+#Ucs2FUvXaA1JE4uFaQ5`d=42oivX%K#F9j*9rF#sTO&?<)zwV0i-x zz~EU23BY7o0|~(7X@dkHT{Idb0BNz_AOT35Y7G*Abf}{s0Z2{T3KD?SwR<4{0l*$} Uh)ucPr2qf`07*qoM6N<$f~efR@&Et; literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-4/32blit-lua/assets/icon.png b/source-code/ninja-thief/episode-4/32blit-lua/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..baa43d35e8ff1e0c19a3d7a267d1c309062c708d GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|Ts&PILnNjq zznQo9sXepEmi&FE4gf*G%hkNXNeNEVUD7unV7bwEHqn7`-`^E#514#jlqH2aTwsyV h$a|j-#Oh~R7+OvW#UE@ENCBG1;OXk;vd$@?2>=Q1F6jUO literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-4/32blit-lua/assets/logo.png b/source-code/ninja-thief/episode-4/32blit-lua/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..895b373c9bcc1a6afd57337f592a2a8b6e35b367 GIT binary patch literal 1732 zcmV;#20QtQP)Px*en~_@RCt{2+;40XWgG|a?+`6X&4_HeF#~KR0|E^x21o`tG#FtK#tW-LaA65X z5=1a+2tnV8VIfgOkwl|$W+r2CNEpl}5<|A2jl*mxl>iHM*f2KYP->QJZdzZ=mfl^j zy{GqF+v~ocWXV0x{dsPAe)rsS&pkbWh=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=YY^flf zowl6_MF60yy%o!!@}jG~HRV3e;AsrI)+bCymz}}WLgz6)K7y%Jr(t}2L}fRR=}^y<&1_{u*&IAYm2 zfcx}rMLMwlaKZ|2D|=OuuI+zUvrUoHfnI^Kr9TA}IUNZr`DXu(SiW7Z{&y= z4*>p)Ku0s0N;junj!Ze#2at88_jCu+>iq*q>j@w#)YyQ1>*k`+SD<2cg(6QhEh>8T>%9wTD%}ipG~ZoL)3N!xvFXZTO`iJVadqrOaP<*Q zo_agcK7cI(AQvnv(T)SiX!hNeE*zV`Ta%Y)J6Fffl-uQH3y|*;_V^nM73tdguqB;m z27r!i&IeFeel)MPKJ}OcZvm2IV?KZkqPKvoy$#GHpTAErTPPiNnGYZ;?*kyd;>`v@ zrnpvKFq9{QWw!ut%?3sK*r_iqoerId&)Dmk7yrY`nj`MxcF&6)pDQwx!Z7= z*}y)44!e#6h_X!pRJDB`TlUv()!oih>{PrG_8d4AOM4C+0$|{k@}iAc5;vK&r#pZR zufGKVxbXY1oPQ`s*d_q{wOi5k#ZfGOen-;n%uXOpmDYQ`d}r0;K=^XfbIV220ziFF zmpKkFSkQ){Yh$>7MM=_U?UG0>fV&ezMJJG&nP58o+*x4pOvBsf3R|=QL)XTVreo%| z0U7=Ax?Is2REfp_lC3i%uf_p%1H%U5r_ZUU(j{AGmMvOZ)`qVr z(w@J7f!hlJ0DnHQ8fa<3b;rankiV&X5c9tNR7%=WvJh7b=WEJe8@_^qDHG?YK0gu3 zD%Yxa1HvDPT3Wp_V&yQd`U4Vy-XI>T*3OfUH-PUVk0 zto}&U(C{dFe>i8|N20RfR_Ec=8^y`5p&D!Dd;pkj`b4KlC6dpdt;?&&Kt0>NUTpVz zQ!YoXKD$Sgm#-#80DRPX_e-XiUsARx>hxqYDFPtbmAv4?pk}-HynL5)PXV$`wmyL9 zQ$SiDi(Xp2GLp`IWZa$60!(*AaA#DkibN`(h=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=Y aZ0tW#pQt@QVP=j10000NJfxAf@noA`3SK&! zG@bmQs7Q%gWSfI{k`g@h*fs>?&AXQc5A8qTwd+i}i77}?_wZ)kd*6I-zHfFWIeqWp z#PsCFeujxL8sn16?>^g)o?@FJUv1!qJsMxIW$#rxT1?mjk9FIBJ8G{)Z`sjn!}i{@ zr0tn1+C#@bZRX8)`{ZcOKKXRhW@NDsoed0lVUFkVSRWE)=}eG9X`fdK?T86Aj!MCd zWPLz5@iFe9y8?S7Q%C1 zLOx|9h($Bbv9Z)?V8>iM8;g5b+K~&vtdb^R11BQGziEaAOErFKnQGf;I^-9S}n zP8zsw83A_HZ!WICWwS1x%U(b@3l~9SYNREX@3^cqeIK4%0SK>MH0h|Al= zemu&BH&BfY!^5FHt#+ITExEFdJ0VYZxvuZKn+P8#DhRi(pLdKj|nhW(peI6xcbfhD& zPxoErx&U)-;AjsE-dWMCpb|GCn@_V?$g(q&21Rjzk$8$+T_%F%7t@;Px(6G=otRA_8`IaXfilSSqx*L>C?XXM5&At|E;_1L|NJS_O;jtpCUMYd zx5Udu%{LJswS$Udqv`Io+!gf+s5lBX#^W#{dvm72JCE8HDrLp1_e8r7C5J?7VKbs5(3 z6dgd>5twV|Ti1OnLId>U{OlsT{LYrv`D{15V~~Cr^Xu5( zXotQ}1xqJ2NGcd2@p4fUEwNk!(2jT+%HFte4*8wFi^De&aBM)kd$|VaH#8n3wS$TV zBSdICU>7IU4l3F>Cb>p9kv@QSE?zEbD7W)OaYDRP#y2&}4UY$A0F^#KB|QcxtgmTw ztyTq%0a&O(R3Y>BBF^Ff>;Ah|YNGt%2iKax#o3;HHnqGFOLpt2XA<1)bO14vnaAt9fWdx%u@>Lfcz<1pqLzY2gUKW=YtPd?KeEb&J~% z)_sD;{y9L|zxy6o4wyUfrb2>0mwSxER%XPKJ-zE?8k;tLAE53LVy3LBsq*>q1@l+O+9 zj98M`MSNcCsbGk(@AwCyo8ov@-~}>X>ehx?4>)+GkI=@A7T$rqtgv~i^;9rK9@qbX zy{xc#D)l4#0AA-*>N0qpbE9{MiS@s8>oJqls{i-9!7i+YcyCfptM17N;GNPs%E_%` zd%hRzyeR-VhB$yt48ydC#QN zI6#D3ogSd1>1W1xR~wQ2RpnOa!5WLczZc!>R_9>~gk+6(tILoz-mNapDFs f-Sv@^lUn{ScA1G9?8X+-00000NkvXXu0mjfN?;DR literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-4/32blit-lua/constants.lua b/source-code/ninja-thief/episode-4/32blit-lua/constants.lua new file mode 100644 index 0000000..dd5d39f --- /dev/null +++ b/source-code/ninja-thief/episode-4/32blit-lua/constants.lua @@ -0,0 +1,152 @@ +Constants = { + -- Screen size in pixels + SCREEN_WIDTH = 160, + SCREEN_HEIGHT = 120, + + -- Actual game area in pixels + GAME_WIDTH = 120, + GAME_HEIGHT = 120, + + -- Each sprite on the spritesheet is 8x8 pixels + SPRITE_SIZE = 8, + + -- The number of pixels by which a ninja can intersect a one-way platform, + -- while still being moved back to the top of the platform during collision resolution + ONE_WAY_PLATFORM_TOLERANCE = 2, +} + +-- Offset of game area from top left corner +Constants.GAME_OFFSET_X = (Constants.SCREEN_WIDTH - Constants.GAME_WIDTH) / 2 +Constants.GAME_OFFSET_Y = (Constants.SCREEN_HEIGHT - Constants.GAME_HEIGHT) / 2 + +-- Game area size in tiles +Constants.GAME_WIDTH_TILES = Constants.GAME_WIDTH / Constants.SPRITE_SIZE +Constants.GAME_HEIGHT_TILES = Constants.GAME_HEIGHT / Constants.SPRITE_SIZE + +-- Sprite data, including indices to use for rendering +Constants.Sprites = { + -- Offset of the red ninja sprites from the blue ninja sprites + RED_OFFSET = 4, + + -- We'll only be using PLAYER_IDLE for now + PLAYER_IDLE = 32, + PLAYER_CLIMBING_IDLE = 33, + PLAYER_CLIMBING_1 = 34, + PLAYER_CLIMBING_2 = 35, + PLAYER_WALKING_1 = 40, + PLAYER_WALKING_2 = 41, + PLAYER_JUMPING_UP = 42, + PLAYER_JUMPING_DOWN = 43, + + LADDER = 11, + + COIN = 19, + GEM = 18, + + -- These will be used to draw a border either side of the screen, to make the game area 120x120 + BORDER_LEFT = 10, + BORDER_FULL = 9, + BORDER_RIGHT = 8, + + -- A blank tile is represented by 0xff in the level arrays + BLANK_TILE = 0xff, +} + +-- Generic ninja data such as size +Constants.Ninja = { + -- The visible width of the ninja sprite + WIDTH = 4, +} + +-- The gap between the edge of the sprite and the edge of the ninja on each side +Constants.Ninja.BORDER = (Constants.SPRITE_SIZE - Constants.Ninja.WIDTH) / 2 + +-- Player data such as speeds +Constants.Player = { + -- Speeds are measured in pixels per second + MAX_SPEED = 50.0, + + JUMP_SPEED = 125.0, + + CLIMBING_SPEED = 40.0, +} + +-- Enemy constants +Constants.Enemy = { + MAX_SPEED = 15.0, + + CLIMBING_SPEED = 20.0, + + -- Hitbox width for detecting the edge of a platform + PLATFORM_DETECTION_WIDTH = 6, + + -- Chance of climbing next ladder + CLIMB_NEXT_LADDER_CHANCE = 0.2, +} + +-- Environment data such as gravity strength +Constants.Environment = { + GRAVITY_ACCELERATION = 375.0, +} + +Constants.LEVEL_COUNT = 1 + +Constants.LEVELS = { + -- Level 1 + { + -- Platform data + platforms = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x01, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x01, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + -- Coin, gem and ladder data + extras = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x12, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + -- Entity spawn data + entity_spawns = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + } +} \ No newline at end of file diff --git a/source-code/ninja-thief/episode-4/32blit-lua/enemy_ninja.lua b/source-code/ninja-thief/episode-4/32blit-lua/enemy_ninja.lua new file mode 100644 index 0000000..d78e059 --- /dev/null +++ b/source-code/ninja-thief/episode-4/32blit-lua/enemy_ninja.lua @@ -0,0 +1,121 @@ +require "ninja" + +AIState = { + PATROLLING = 0, + CLIMBING = 1, +} + +EnemyNinja = Ninja:new() +EnemyNinja.current_direction = 1 +EnemyNinja.climb_next_ladder = false +EnemyNinja.ai_state = AIState.PATROLLING + +function EnemyNinja:new(x, y) + return Ninja.new(EnemyNinja, Colour.RED, x, y) +end + +function EnemyNinja:update(dt, level_data) + if self.ai_state == AIState.PATROLLING then + if not self:platform_ahead(level_data) then + -- No platform ahead, so turn around + self.current_direction = -self.current_direction + end + + self.velocity_x = Constants.Enemy.MAX_SPEED * self.current_direction + + if self.can_climb then + if self.climb_next_ladder then + -- We're allowed to climb - check both directions for a ladder tile + local can_go_up = self:ladder_above_or_below(level_data, VerticalDirection.UP) + local can_go_down = self:ladder_above_or_below(level_data, VerticalDirection.DOWN) + + if can_go_up and can_go_down then + -- If we can go either way, pick one at random + if self:random_bool(0.5) then + self.climbing_state = ClimbingState.UP + else + self.climbing_state = ClimbingState.DOWN + end + elseif can_go_up then + -- Only way is up + self.climbing_state = ClimbingState.UP + elseif can_go_down then + -- Only way is down + self.climbing_state = ClimbingState.DOWN + end + + if self.climbing_state ~= ClimbingState.NONE then + -- We've now decided to climb + self.ai_state = AIState.CLIMBING + + self.climb_next_ladder = false + end + end + else + -- Keep "re-rolling" while we can't climb + + -- Decide if we should climb the next ladder we find + self.climb_next_ladder = self:random_bool(Constants.Enemy.CLIMB_NEXT_LADDER_CHANCE) + end + end + + Ninja.update(self, dt, level_data) + + -- If we're no longer in a climbing state, switch back to patrolling + -- This will happen when the enemy reaches the bottom of a ladder, or if they fall off the ladder + if self.climbing_state == ClimbingState.NONE then + self.ai_state = AIState.PATROLLING + end +end + +-- Returns true if there is a platform tile which is one block below and just in front of the ninja +-- This is used to work out when the ninja reaches the end of a platform +function EnemyNinja:platform_ahead(level_data) + -- Get a position which would be just in front of the ninja (and one tile below them) + local point_x = self.position_x + Constants.SPRITE_SIZE / 2 + self.current_direction * Constants.Enemy.PLATFORM_DETECTION_WIDTH / 2 + local point_y = self.position_y + Constants.SPRITE_SIZE + + -- Get tile at that position + local tile_id = self:tile_at_position(level_data.platforms, point_x, point_y) + + -- Return true if the tile is a platform (i.e. isn't an empty tile) + return tile_id ~= Constants.Sprites.BLANK_TILE +end + + +-- Returns true if there is a ladder tile above or below the ninjas's centre +function EnemyNinja:ladder_above_or_below(level_data, direction) + -- Get a position which would be one tile above/below the ninja + local point_x = self.position_x + local point_y = self.position_y + Constants.SPRITE_SIZE * direction + + -- Get tile at that position + local tile_id = self:tile_at_position(level_data.extras, point_x, point_y) + + -- Return true if the tile is a ladder + return tile_id == Constants.Sprites.LADDER +end + +-- Finds the tile which is at the position provided +function EnemyNinja:tile_at_position(tile_array, x, y) + -- Check that the position is within the game bounds (if it isn't, return an empty tile) + if x < 0.0 or x > Constants.GAME_WIDTH or + y < 0.0 or y > Constants.GAME_HEIGHT then + return Constants.Sprites.BLANK_TILE + end + + -- Get grid position of tile + local grid_x = x // Constants.SPRITE_SIZE + local grid_y = y // Constants.SPRITE_SIZE + + -- If we've not returned yet, then it's safe to get the tile from the level data + return tile_array[grid_y * Constants.GAME_WIDTH_TILES + grid_x + 1] +end + +-- Returns a boolean, with chance of being true equal to probability supplied +function EnemyNinja:random_bool(probability) + return math.random() < probability +end + + + diff --git a/source-code/ninja-thief/episode-4/32blit-lua/level.lua b/source-code/ninja-thief/episode-4/32blit-lua/level.lua new file mode 100644 index 0000000..51d950c --- /dev/null +++ b/source-code/ninja-thief/episode-4/32blit-lua/level.lua @@ -0,0 +1,129 @@ +require "constants" +require "enemy_ninja" +require "player_ninja" + +Level = { + level_data = nil, + level_number = 0, + + player = nil, + enemies = {}, +} + +function Level:new(level_number) + local o = { + level_number = level_number, + level_data = Constants.LEVELS[level_number], + enemies = {}, + } + + setmetatable(o, self) + self.__index = self + + -- Search for player spawn position and create PlayerNinja object + -- Search for enemy spawn positions and create EnemyNinja objects and add them to a vector + + for y = 0, Constants.GAME_HEIGHT_TILES - 1 do + for x = 0, Constants.GAME_WIDTH_TILES - 1 do + + -- Get spritesheet index at point (x,y) + local spawn_id = o.level_data.entity_spawns[y * Constants.GAME_WIDTH_TILES + x + 1] + + -- Calculate actual position from grid position + local position_x = x * Constants.SPRITE_SIZE + local position_y = y * Constants.SPRITE_SIZE + + -- Create the correct instance + if spawn_id == Constants.Sprites.PLAYER_IDLE then + o.player = PlayerNinja:new(position_x, position_y) + + elseif spawn_id == Constants.Sprites.PLAYER_IDLE + Constants.Sprites.RED_OFFSET then + o.enemies[#o.enemies + 1] = EnemyNinja:new(position_x, position_y) + end + end + end + + return o +end + +function Level:update(dt) + -- Update player + self.player:update(dt, self.level_data) + + -- Update enemies + for i, enemy in ipairs(self.enemies) do + enemy:update(dt, self.level_data) + end +end + +function Level:render() + -- Render border + self:render_border() + + -- Render platforms + self:render_tiles(self.level_data.platforms) + + -- Render extras (coins, gems and ladders) + self:render_tiles(self.level_data.extras) + + -- Render enemies + for i, enemy in ipairs(self.enemies) do + enemy:render() + end + + -- Render the player + self.player:render() + + -- Set the text colour to white + screen.pen = Pen(255, 255, 255) + + -- Render the placeholder score text + screen.text("Score: 0", minimal_font, Point(2, 2)) +end + +function Level:render_tiles(tile_ids) + -- Iterate through array of tile ids and render using the correct index in the spritesheet + for y = 0, Constants.GAME_HEIGHT_TILES - 1 do + for x = 0, Constants.GAME_WIDTH_TILES - 1 do + -- Iterate through array of tile ids and render using the correct index in the spritesheet + local tile_id = tile_ids[y * Constants.GAME_WIDTH_TILES + x + 1] + + -- Only render the tile if it isn't a blank tile + if tile_id ~= Constants.Sprites.BLANK_TILE then + -- Offset the tiles since the 32blit version has borders on the screen + screen.sprite(tile_id, Point(x * Constants.SPRITE_SIZE + Constants.GAME_OFFSET_X, y * Constants.SPRITE_SIZE + Constants.GAME_OFFSET_Y)) + end + end + end +end + +function Level:render_border() + -- Offset the tiles since the 32blit version has borders on the screen + + -- Each row is the same + for y = 0, Constants.SCREEN_HEIGHT - 1, Constants.SPRITE_SIZE do + -- Left border: + local x = 0 + + -- BORDER_FULL sprites + while x < Constants.GAME_OFFSET_X - Constants.SPRITE_SIZE do + screen.sprite(Constants.Sprites.BORDER_FULL, Point(x, y)) + x = x + Constants.SPRITE_SIZE + end + + -- BORDER_LEFT sprite + screen.sprite(Constants.Sprites.BORDER_LEFT, Point(x, y)) + + -- Right border: + x = Constants.SCREEN_WIDTH + + -- BORDER_FULL sprites + while x > Constants.SCREEN_WIDTH - Constants.GAME_OFFSET_X do + screen.sprite(Constants.Sprites.BORDER_FULL, Point(x, y)) + x = x - Constants.SPRITE_SIZE + end + + -- BORDER_RIGHT sprite + screen.sprite(Constants.Sprites.BORDER_RIGHT, Point(x, y)) + end +end \ No newline at end of file diff --git a/source-code/ninja-thief/episode-4/32blit-lua/metadata.yml b/source-code/ninja-thief/episode-4/32blit-lua/metadata.yml new file mode 100644 index 0000000..f1b6dab --- /dev/null +++ b/source-code/ninja-thief/episode-4/32blit-lua/metadata.yml @@ -0,0 +1,12 @@ +# 32blit metadata --config metadata.yml --metadata-file ninja_thief.blmeta + +title: Ninja Thief +description: A tutorial game for the 32blit and PicoSystem. Collect all the coins while avoiding the enemy ninjas! +author: ThePythonator +splash: + file: assets/logo.png +icon: + file: assets/icon.png +version: v1.0.0 +category: game +url: https://github.com/32blit/32blit-tutorials-contrib diff --git a/source-code/ninja-thief/episode-4/32blit-lua/ninja.lua b/source-code/ninja-thief/episode-4/32blit-lua/ninja.lua new file mode 100644 index 0000000..6555c65 --- /dev/null +++ b/source-code/ninja-thief/episode-4/32blit-lua/ninja.lua @@ -0,0 +1,325 @@ +require "constants" + +Colour = { + BLUE = 0, + RED = 1, +} + +HorizontalDirection = { + LEFT = -1, + RIGHT = 1, +} + +VerticalDirection = { + UP = -1, + DOWN = 1, +} + +ClimbingState = { + NONE = 0, + IDLE = 1, + UP = 2, + DOWN = 3, +} + +Ninja = { + colour = nil, + + position_x = 0, + posiiton_y = 0, + velocity_x = 0, + velocity_y = 0, + + facing_direction = HorizontalDirection.LEFT, + + can_jump = false, + can_climb = false, + + climbing_state = ClimbingState.NONE +} + +function Ninja:new(colour, x, y) + local o = {colour = colour, position_x = x, position_y = y} + + setmetatable(o, self) + self.__index = self + return o +end + +function Ninja:update(dt, level_data) + + -- This is set to true later in the update stage, but only if the ninja is on a platform + self.can_jump = false + + -- Apply gravity, but only if the ninja isn't climbing a ladder + if self.climbing_state == ClimbingState.NONE then + self.velocity_y = self.velocity_y + Constants.Environment.GRAVITY_ACCELERATION * dt + end + + -- Move the ninja + self.position_x = self.position_x + self.velocity_x * dt + self.position_y = self.position_y + self.velocity_y * dt + + -- Don't allow ninja to go off the sides + if self.position_x < -Constants.Ninja.BORDER then + self.position_x = -Constants.Ninja.BORDER + elseif self.position_x > Constants.GAME_WIDTH - Constants.Ninja.BORDER - Constants.Ninja.WIDTH then + self.position_x = Constants.GAME_WIDTH - Constants.Ninja.BORDER - Constants.Ninja.WIDTH + end + + -- Detect and resolve any collisions with platforms, ladders, coins etc + self:handle_collisions(level_data) + + -- Update direction the ninja is facing (only if the player is moving) + if self.velocity_x < 0.0 then + self.facing_direction = HorizontalDirection.LEFT + elseif self.velocity_x > 0.0 then + self.facing_direction = HorizontalDirection.RIGHT + end +end + +function Ninja:render() + -- If ninja is travelling left, flip the image horizontally + local transform = SpriteTransform.NONE + if self.facing_direction == HorizontalDirection.LEFT then + transform = SpriteTransform.HORIZONTAL + end + + -- Set sprite index (take into account sprite offset based on colour) + local index = 0 + + if self.colour == Colour.RED then + index = Constants.Sprites.RED_OFFSET + end + + if self.climbing_state == ClimbingState.NONE then + index = index + Constants.Sprites.PLAYER_IDLE + else + index = index + Constants.Sprites.PLAYER_CLIMBING_IDLE + end + + screen.sprite(index, Point(math.floor(self.position_x + 0.5) + Constants.GAME_OFFSET_X, math.floor(self.position_y + 0.5) + Constants.GAME_OFFSET_Y), transform) +end + +function Ninja:check_object_colliding(object_x, object_y, object_size) + return self.position_x + Constants.SPRITE_SIZE - Constants.Ninja.BORDER > object_x and + self.position_x + Constants.Ninja.BORDER < object_x + object_size and + self.position_y + Constants.SPRITE_SIZE > object_y and + self.position_y < object_y + object_size +end + +function Ninja:handle_collisions(level_data) + -- Reset can_climb flag (which then gets set by handle_ladders if the ninja is near a ladder) + self.can_climb = false + + -- Get position of ninja in "grid" of tiles + -- We're relying on converting to integers to truncate and hence round towards zero + -- FIXME: this is not doing that + local x = self.position_x // Constants.SPRITE_SIZE + local y = self.position_y // Constants.SPRITE_SIZE + + -- Check the four tiles which the ninja might be colliding with (the top left tile is marked by the x and y previously calculated) + + -- We need to check that the player is within the game area + -- If they aren't, we don't need to worry about checking for collisions + if x < Constants.GAME_WIDTH_TILES and y < Constants.GAME_HEIGHT_TILES and self.position_x >= -Constants.Ninja.BORDER and self.position_y >= -Constants.SPRITE_SIZE then + + -- It's possible the ninja is near the edge of the screen and we could end up checking tiles which don't exist (off the edge of the screen) + -- To avoid this issue, we vary the maximum x and y offsets + -- The minimum offset is handled by the trucation, since it will round up (rather than down) if the value is negative + local y_max = 1 + if y == Constants.GAME_HEIGHT_TILES - 1 then + y_max = 0 + end + + for y_offset = 0, y_max do + + local x_max = 1 + if x == Constants.GAME_WIDTH_TILES - 1 then + x_max = 0 + end + + for x_offset = 0, x_max do + -- Calculate grid position of this tile + local new_x = x + x_offset + local new_y = y + y_offset + + -- Handle platforms + self:handle_platform(level_data, new_x, new_y) + + -- Handle ladders + self:handle_ladder(level_data, new_x, new_y) + end + end + end + + -- If ninja can no longer climb, reset their climbing state + if self.can_climb ~= true then + self.climbing_state = ClimbingState.NONE + end + + if self.climbing_state ~= ClimbingState.NONE then + -- If player is on a ladder, they can jump + self.can_jump = true + + -- Set velocity to 0 + self.velocity_x = 0.0 + self.velocity_y = 0.0 + + -- Get climbing speed, depending on whether ninja is the player or an enemy + local climbing_speed = Constants.Enemy.CLIMBING_SPEED + + if colour == Colour.BLUE then + climbing_speed = Constants.Player.CLIMBING_SPEED + end + + -- If player is actually climbing the ladder, set vertical velocity to be in the right direction + if self.climbing_state == ClimbingState.UP then + self.velocity_y = -climbing_speed + + elseif self.climbing_state == ClimbingState.DOWN then + self.velocity_y = climbing_speed + end + end +end + +function Ninja:handle_platform(level_data, x, y) + -- Get tile's sprite index from level data + local tile_id = level_data.platforms[y * Constants.GAME_WIDTH_TILES + x + 1] + + -- Check the tile actually exists (check that it isn't blank) + if tile_id ~= Constants.Sprites.BLANK_TILE then + + -- Calculate the actual position of the tile from the grid position + local tile_x = x * Constants.SPRITE_SIZE + local tile_y = y * Constants.SPRITE_SIZE + + -- Check if the ninja is colliding with the tile + if self:check_object_colliding(tile_x, tile_y, Constants.SPRITE_SIZE) then + + -- Check if this platform have a ladder in front of it + if level_data.extras[y * Constants.GAME_WIDTH_TILES + x + 1] == Constants.Sprites.LADDER then + + -- Check that the ninja is not on a ladder + if self.climbing_state == ClimbingState.NONE then + + -- Check that the ninja is falling downwards + if self.velocity_y > 0.0 then + + -- Check that the ninja collided with the smaller platform hitbox + if self.position_y + Constants.SPRITE_SIZE - tile_y < Constants.ONE_WAY_PLATFORM_TOLERANCE then + -- Set the ninja's position so that it rests on top of the platform, and reset its vertical velocity to zero + self.position_y = tile_y - Constants.SPRITE_SIZE + self.velocity_y = 0.0 + + -- Allow the ninja to jump again + self.can_jump = true + end + end + end + else + -- Resolve collision by finding the direction with the least intersection + -- The value of the direction variable corresponds to: + -- 0 - left side of tile + -- 1 - top side of tile + -- 2 - right side of tile + -- 3 - bottom side of tile + local direction = 0 + + -- The starting value of least_intersection is at least the maximum possible intersection + -- The width/height of the tile is the maximum intersection possible + local least_intersection = Constants.SPRITE_SIZE + + -- Left side of tile + local intersection = self.position_x + Constants.Ninja.WIDTH + Constants.Ninja.BORDER - tile_x + if intersection < least_intersection then + direction = 0 + least_intersection = intersection + end + + -- Top side of tile + intersection = self.position_y + Constants.SPRITE_SIZE - tile_y + if intersection < least_intersection then + direction = 1 + least_intersection = intersection + end + + -- Right side of tile + intersection = tile_x + Constants.SPRITE_SIZE - self.position_x - Constants.Ninja.BORDER + if intersection < least_intersection then + direction = 2 + least_intersection = intersection + end + + -- Bottom side of tile + intersection = tile_y + Constants.SPRITE_SIZE - self.position_y + if intersection < least_intersection then + direction = 3 + least_intersection = intersection + end + + -- Now resolve collision by moving the ninja in the direction of least intersection, by exactly the amount equal to the least intersection + if direction == 0 then + -- Hit the left side of a platform + self.position_x = self.position_x - least_intersection + self.velocity_x = 0.0 + elseif direction == 1 then + -- Landed on top of a platform + self.position_y = self.position_y - least_intersection + self.velocity_y = 0.0 + + -- Allow the ninja to jump again + self.can_jump = true + + -- Stop the ninja from climbing + self.climbing_state = ClimbingState.NONE + elseif direction == 2 then + -- Hit the right side of a platform + self.position_x = self.position_x + least_intersection + self.velocity_x = 0.0 + elseif direction == 3 then + -- Hit the underside of a platform + self.position_y = self.position_y + least_intersection + self.velocity_y = 0.0 + end + end + end + end +end + +function Ninja:handle_ladder(level_data, x, y) + -- Get tile's sprite index from level data + local tile_id = level_data.extras[y * Constants.GAME_WIDTH_TILES + x + 1] + + -- Check if the tile is a ladder + if tile_id == Constants.Sprites.LADDER then + + -- Calculate the actual position of the tile from the grid position + local tile_x = x * Constants.SPRITE_SIZE + local tile_y = y * Constants.SPRITE_SIZE + + -- Check if ninja is colliding with the tile + if self:check_object_colliding(tile_x, tile_y, Constants.SPRITE_SIZE) then + + -- Check that ninja is sufficiently close to ladder + if math.abs(tile_x - self.position_x) < Constants.Ninja.WIDTH / 2 then + self.can_climb = true + + -- Check if ninja should be climbing or idling on ladder + if self.climbing_state ~= ClimbingState.NONE then + -- Lock position to ladder + self.position_x = tile_x + end + end + end + end +end + +function Ninja:jump(jump_speed) + -- Upwards is negative + self.velocity_y = -jump_speed + + -- Reset climbing state when player jumps + self.climbing_state = ClimbingState.NONE +end + diff --git a/source-code/ninja-thief/episode-4/32blit-lua/ninja_thief.lua b/source-code/ninja-thief/episode-4/32blit-lua/ninja_thief.lua new file mode 100644 index 0000000..4a73b12 --- /dev/null +++ b/source-code/ninja-thief/episode-4/32blit-lua/ninja_thief.lua @@ -0,0 +1,51 @@ +require "constants" +require "level" + +-- Our global variables are defined here +background = nil + +last_time = 1.0 + +level = nil + +-- Setup your game here +function init() + -- Set the resolution to 160x120 + set_screen_mode(ScreenMode.lores) + + -- Load the background from assets.cpp + background = Surface.load("assets/background.blim") + + -- Load the spritesheet + screen.load_sprites("assets/spritesheet.blim") + + level = Level:new(1) +end + +-- This function is called to perform rendering of the game. +function render(time) + -- Clear the screen + screen.pen = Pen(0, 0, 0) + screen.clear() + + -- Draw the entire background image onto the screen at (0, 0) + screen.blit(background, Rect(0, 0, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT), Point(0, 0)) + + -- Render the level + level:render() +end + +-- This is called to update your game state. +function update(time) + -- Calculate change in time (in seconds) since last frame + local dt = (time - last_time) / 1000.0 + last_time = time + + -- Limit dt + if dt > 0.05 then + dt = 0.05 + end + + -- Update the level + level:update(dt) +end diff --git a/source-code/ninja-thief/episode-4/32blit-lua/player_ninja.lua b/source-code/ninja-thief/episode-4/32blit-lua/player_ninja.lua new file mode 100644 index 0000000..5f4ee28 --- /dev/null +++ b/source-code/ninja-thief/episode-4/32blit-lua/player_ninja.lua @@ -0,0 +1,45 @@ +require "ninja" + +PlayerNinja = Ninja:new() + +function PlayerNinja:new(x, y) + return Ninja.new(PlayerNinja, Colour.BLUE, x, y) +end + +function PlayerNinja:update(dt, level_data) + -- If nothing is pressed, the player shouldn't move + self.velocity_x = 0.0 + + -- Note: "else if" isn't used, because otherwise the sprite will still move when both buttons are pressed + -- Instead, we add/subtract the velocity, so if both are pressed, nothing happens + if buttons.state & Button.LEFT ~= 0 then + self.velocity_x = self.velocity_x - Constants.Player.MAX_SPEED + end + if buttons.state & Button.RIGHT ~= 0 then + self.velocity_x = self.velocity_x + Constants.Player.MAX_SPEED + end + + -- Handle climbing + if self.can_climb then + local up = buttons.state & Button.UP ~= 0 + local down = buttons.state & Button.DOWN ~= 0 + + if up ~= down then + -- Only one of up and down are selected + self.climbing_state = up and ClimbingState.UP or ClimbingState.DOWN + elseif self.climbing_state ~= ClimbingState.NONE then + -- Player has already been climbing the ladder, and either none or both of up and down are pressed + self.climbing_state = ClimbingState.IDLE + end + end + + -- Handle jumping + -- Note that we use buttons.pressed, which only contains the buttons just pressed (since the last frame) + if buttons.pressed & Button.A ~= 0 then + if self.can_jump then + self:jump(Constants.Player.JUMP_SPEED) + end + end + + Ninja.update(self, dt, level_data) +end From 73fc077d98366a0d617b528bb1f8cc23f68f1e85 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Thu, 19 Sep 2024 20:34:03 +0100 Subject: [PATCH 5/6] Lua version of ninja thief episode 5 --- .../episode-5/32blit-lua/assets.yml | 8 + .../32blit-lua/assets/background.blim | Bin 0 -> 521 bytes .../32blit-lua/assets/background.png | Bin 0 -> 1050 bytes .../episode-5/32blit-lua/assets/icon.png | Bin 0 -> 138 bytes .../episode-5/32blit-lua/assets/logo.png | Bin 0 -> 1732 bytes .../32blit-lua/assets/spritesheet.blim | Bin 0 -> 1303 bytes .../32blit-lua/assets/spritesheet.png | Bin 0 -> 1113 bytes .../episode-5/32blit-lua/constants.lua | 347 +++++++++++++++++ .../episode-5/32blit-lua/enemy_ninja.lua | 131 +++++++ .../episode-5/32blit-lua/level.lua | 234 ++++++++++++ .../episode-5/32blit-lua/metadata.yml | 12 + .../episode-5/32blit-lua/ninja.lua | 355 ++++++++++++++++++ .../episode-5/32blit-lua/ninja_thief.lua | 65 ++++ .../episode-5/32blit-lua/player_ninja.lua | 108 ++++++ 14 files changed, 1260 insertions(+) create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/assets.yml create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/assets/background.blim create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/assets/background.png create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/assets/icon.png create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/assets/logo.png create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/assets/spritesheet.blim create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/assets/spritesheet.png create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/constants.lua create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/enemy_ninja.lua create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/level.lua create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/metadata.yml create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/ninja.lua create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/ninja_thief.lua create mode 100644 source-code/ninja-thief/episode-5/32blit-lua/player_ninja.lua diff --git a/source-code/ninja-thief/episode-5/32blit-lua/assets.yml b/source-code/ninja-thief/episode-5/32blit-lua/assets.yml new file mode 100644 index 0000000..721942b --- /dev/null +++ b/source-code/ninja-thief/episode-5/32blit-lua/assets.yml @@ -0,0 +1,8 @@ +# 32blit pack --force --config assets.yml +# ideally these should be .blim, but the tools don't recognise that + +background.bin: + assets/background.png: + +spritesheet.bin: + assets/spritesheet.png: diff --git a/source-code/ninja-thief/episode-5/32blit-lua/assets/background.blim b/source-code/ninja-thief/episode-5/32blit-lua/assets/background.blim new file mode 100644 index 0000000000000000000000000000000000000000..f06181f75845af32637310c528d42a5576b8c1ae GIT binary patch literal 521 zcmV+k0`~n=P*O=$MN&)&0ssJ@0C)fb15i>{|8;X||2#cK|B94$CQ4Y*M`C25jud7l zN*LirVq~3<6jmll*zrYTWSNi@HYQ1#2}5FJnUIt=CP`TtLSkf_Ibvj8ofDQOMVa9^Vq{gGlZGZm8TmJ2WKEzGb|yrc z5jA3DNuiTgCPZ2xHDY8zqZ39ZL)tksVq`g_lQt$pT2V7%WHF`_CMH7KX)$7CEvFL} zCPJElFk)mSsFMaJLK>MbVq_hv680uRnz1foWEHED<|aWJ!7XBB5v>x&CP3QREMj3M zx=PrXO?Z{DFxUxGbtO&Ml(%9~-N{3EB<}`sqDtP$8+;^f^pQ8x zMBj-Eek3jUkhk(c-^lxcBhCbmI3haWh~tDKP85u|Vld(evX&f*uv-d5*iC{WOg6v~ z1|wfcdlj!lIgHnW7{_a=3uHCSgtBVhLK#(UAk4b85LRVc2pcx6p(b@$f@jr$p;f|- zRti;ADb+x!RKl%N3e`j_)c~ literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-5/32blit-lua/assets/background.png b/source-code/ninja-thief/episode-5/32blit-lua/assets/background.png new file mode 100644 index 0000000000000000000000000000000000000000..b1c66ca74d19334492ee777ca0c39261d2cbd743 GIT binary patch literal 1050 zcmV+#1m*jQP)004Lh1^@s6b)xxi00001b5ch_0Itp) z=>Px&)Ja4^RCt{2-Aig4K@>*e8{-WGB@58N`w1Cj^B#Bsw($~@fvLDvYPI@NRrhty zC=jsG<>N0fd3^iz`_EtL(9`+Uw`bKnFYcO8#-3_3~=TW>K zW<(hg)}Og6z>^aRSi_~VA)J2RVE{|kkE2yKgr(sCMOZ`nN(EpEgqLMn z!SY#E0gBi@l17;jiWmSW!joK=1>w{AWCLKy>Y+5sg3uH|5!P^8288ASmMk7iqv#0D z0u*5lOOX+p23WFmFlw6%fFi76)D?6Cuw>B<~tVKo|mG353TReZx=yMe?p;%SPTW9Kcd0gkJti=3HR`ih%G)qiz=( zph&JK*(1X6081d;SMR%}0VtAZ4Lu=D1+WCd?e)4@N`N9c*3bjOv;a#W+*a#*r3WaI zV-2kaz)4XNT3$f_R)KI`t?mTCnW8?)77+q)ArRj0=#2n)r-*B42_XPi2Eyg`-Uomi zinfLp5CU*Vkq~MJz@0@wsJ(&!JW!M;SrZ`u8wA4Z*4_kwjf${_8VCW{I1pZT?;Zf` zkoPqli4cGt1L0S*`C=Leu- z-qx@OLI4^lGeQ6w$mNQ3~S*+wA*Al+=lll<`Z`XO(xA6>r*`K~9z$MTCB;g6r+yM0sg zS3%hNA18Xe^&5bsxt?F%n$_+H8t2G{8DQ+NnIrsQ}giQoqyy>i{`E04@Z`{sC}9fNUQC z4+O~L0kC0!+#Ucs2FUvXaA1JE4uFaQ5`d=42oivX%K#F9j*9rF#sTO&?<)zwV0i-x zz~EU23BY7o0|~(7X@dkHT{Idb0BNz_AOT35Y7G*Abf}{s0Z2{T3KD?SwR<4{0l*$} Uh)ucPr2qf`07*qoM6N<$f~efR@&Et; literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-5/32blit-lua/assets/icon.png b/source-code/ninja-thief/episode-5/32blit-lua/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..baa43d35e8ff1e0c19a3d7a267d1c309062c708d GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|Ts&PILnNjq zznQo9sXepEmi&FE4gf*G%hkNXNeNEVUD7unV7bwEHqn7`-`^E#514#jlqH2aTwsyV h$a|j-#Oh~R7+OvW#UE@ENCBG1;OXk;vd$@?2>=Q1F6jUO literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-5/32blit-lua/assets/logo.png b/source-code/ninja-thief/episode-5/32blit-lua/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..895b373c9bcc1a6afd57337f592a2a8b6e35b367 GIT binary patch literal 1732 zcmV;#20QtQP)Px*en~_@RCt{2+;40XWgG|a?+`6X&4_HeF#~KR0|E^x21o`tG#FtK#tW-LaA65X z5=1a+2tnV8VIfgOkwl|$W+r2CNEpl}5<|A2jl*mxl>iHM*f2KYP->QJZdzZ=mfl^j zy{GqF+v~ocWXV0x{dsPAe)rsS&pkbWh=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=YY^flf zowl6_MF60yy%o!!@}jG~HRV3e;AsrI)+bCymz}}WLgz6)K7y%Jr(t}2L}fRR=}^y<&1_{u*&IAYm2 zfcx}rMLMwlaKZ|2D|=OuuI+zUvrUoHfnI^Kr9TA}IUNZr`DXu(SiW7Z{&y= z4*>p)Ku0s0N;junj!Ze#2at88_jCu+>iq*q>j@w#)YyQ1>*k`+SD<2cg(6QhEh>8T>%9wTD%}ipG~ZoL)3N!xvFXZTO`iJVadqrOaP<*Q zo_agcK7cI(AQvnv(T)SiX!hNeE*zV`Ta%Y)J6Fffl-uQH3y|*;_V^nM73tdguqB;m z27r!i&IeFeel)MPKJ}OcZvm2IV?KZkqPKvoy$#GHpTAErTPPiNnGYZ;?*kyd;>`v@ zrnpvKFq9{QWw!ut%?3sK*r_iqoerId&)Dmk7yrY`nj`MxcF&6)pDQwx!Z7= z*}y)44!e#6h_X!pRJDB`TlUv()!oih>{PrG_8d4AOM4C+0$|{k@}iAc5;vK&r#pZR zufGKVxbXY1oPQ`s*d_q{wOi5k#ZfGOen-;n%uXOpmDYQ`d}r0;K=^XfbIV220ziFF zmpKkFSkQ){Yh$>7MM=_U?UG0>fV&ezMJJG&nP58o+*x4pOvBsf3R|=QL)XTVreo%| z0U7=Ax?Is2REfp_lC3i%uf_p%1H%U5r_ZUU(j{AGmMvOZ)`qVr z(w@J7f!hlJ0DnHQ8fa<3b;rankiV&X5c9tNR7%=WvJh7b=WEJe8@_^qDHG?YK0gu3 zD%Yxa1HvDPT3Wp_V&yQd`U4Vy-XI>T*3OfUH-PUVk0 zto}&U(C{dFe>i8|N20RfR_Ec=8^y`5p&D!Dd;pkj`b4KlC6dpdt;?&&Kt0>NUTpVz zQ!YoXKD$Sgm#-#80DRPX_e-XiUsARx>hxqYDFPtbmAv4?pk}-HynL5)PXV$`wmyL9 zQ$SiDi(Xp2GLp`IWZa$60!(*AaA#DkibN`(h=_=Yh=_=Yh=_=Yh=_=Yh=_=Yh=_=Y aZ0tW#pQt@QVP=j10000NJfxAf@noA`3SK&! zG@bmQs7Q%gWSfI{k`g@h*fs>?&AXQc5A8qTwd+i}i77}?_wZ)kd*6I-zHfFWIeqWp z#PsCFeujxL8sn16?>^g)o?@FJUv1!qJsMxIW$#rxT1?mjk9FIBJ8G{)Z`sjn!}i{@ zr0tn1+C#@bZRX8)`{ZcOKKXRhW@NDsoed0lVUFkVSRWE)=}eG9X`fdK?T86Aj!MCd zWPLz5@iFe9y8?S7Q%C1 zLOx|9h($Bbv9Z)?V8>iM8;g5b+K~&vtdb^R11BQGziEaAOErFKnQGf;I^-9S}n zP8zsw83A_HZ!WICWwS1x%U(b@3l~9SYNREX@3^cqeIK4%0SK>MH0h|Al= zemu&BH&BfY!^5FHt#+ITExEFdJ0VYZxvuZKn+P8#DhRi(pLdKj|nhW(peI6xcbfhD& zPxoErx&U)-;AjsE-dWMCpb|GCn@_V?$g(q&21Rjzk$8$+T_%F%7t@;Px(6G=otRA_8`IaXfilSSqx*L>C?XXM5&At|E;_1L|NJS_O;jtpCUMYd zx5Udu%{LJswS$Udqv`Io+!gf+s5lBX#^W#{dvm72JCE8HDrLp1_e8r7C5J?7VKbs5(3 z6dgd>5twV|Ti1OnLId>U{OlsT{LYrv`D{15V~~Cr^Xu5( zXotQ}1xqJ2NGcd2@p4fUEwNk!(2jT+%HFte4*8wFi^De&aBM)kd$|VaH#8n3wS$TV zBSdICU>7IU4l3F>Cb>p9kv@QSE?zEbD7W)OaYDRP#y2&}4UY$A0F^#KB|QcxtgmTw ztyTq%0a&O(R3Y>BBF^Ff>;Ah|YNGt%2iKax#o3;HHnqGFOLpt2XA<1)bO14vnaAt9fWdx%u@>Lfcz<1pqLzY2gUKW=YtPd?KeEb&J~% z)_sD;{y9L|zxy6o4wyUfrb2>0mwSxER%XPKJ-zE?8k;tLAE53LVy3LBsq*>q1@l+O+9 zj98M`MSNcCsbGk(@AwCyo8ov@-~}>X>ehx?4>)+GkI=@A7T$rqtgv~i^;9rK9@qbX zy{xc#D)l4#0AA-*>N0qpbE9{MiS@s8>oJqls{i-9!7i+YcyCfptM17N;GNPs%E_%` zd%hRzyeR-VhB$yt48ydC#QN zI6#D3ogSd1>1W1xR~wQ2RpnOa!5WLczZc!>R_9>~gk+6(tILoz-mNapDFs f-Sv@^lUn{ScA1G9?8X+-00000NkvXXu0mjfN?;DR literal 0 HcmV?d00001 diff --git a/source-code/ninja-thief/episode-5/32blit-lua/constants.lua b/source-code/ninja-thief/episode-5/32blit-lua/constants.lua new file mode 100644 index 0000000..f2f809e --- /dev/null +++ b/source-code/ninja-thief/episode-5/32blit-lua/constants.lua @@ -0,0 +1,347 @@ +Constants = { + -- Screen size in pixels + SCREEN_WIDTH = 160, + SCREEN_HEIGHT = 120, + + -- Actual game area in pixels + GAME_WIDTH = 120, + GAME_HEIGHT = 120, + + -- Each sprite on the spritesheet is 8x8 pixels + SPRITE_SIZE = 8, + + -- The number of pixels by which a ninja can intersect a one-way platform, + -- while still being moved back to the top of the platform during collision resolution + ONE_WAY_PLATFORM_TOLERANCE = 2, +} + +-- Offset of game area from top left corner +Constants.GAME_OFFSET_X = (Constants.SCREEN_WIDTH - Constants.GAME_WIDTH) / 2 +Constants.GAME_OFFSET_Y = (Constants.SCREEN_HEIGHT - Constants.GAME_HEIGHT) / 2 + +-- Game area size in tiles +Constants.GAME_WIDTH_TILES = Constants.GAME_WIDTH / Constants.SPRITE_SIZE +Constants.GAME_HEIGHT_TILES = Constants.GAME_HEIGHT / Constants.SPRITE_SIZE + +-- Sprite data, including indices to use for rendering +Constants.Sprites = { + -- Offset of the red ninja sprites from the blue ninja sprites + RED_OFFSET = 4, + + -- We'll only be using PLAYER_IDLE for now + PLAYER_IDLE = 32, + PLAYER_CLIMBING_IDLE = 33, + PLAYER_CLIMBING_1 = 34, + PLAYER_CLIMBING_2 = 35, + PLAYER_WALKING_1 = 40, + PLAYER_WALKING_2 = 41, + PLAYER_JUMPING_UP = 42, + PLAYER_JUMPING_DOWN = 43, + + LADDER = 11, + + COIN = 19, + GEM = 18, + + WATER = 30, + + -- These will be used to draw a border either side of the screen, to make the game area 120x120 + BORDER_LEFT = 10, + BORDER_FULL = 9, + BORDER_RIGHT = 8, + + -- A blank tile is represented by 0xff in the level arrays + BLANK_TILE = 0xff, +} + +-- Generic ninja data such as size +Constants.Ninja = { + -- The visible width of the ninja sprite + WIDTH = 4, +} + +-- The gap between the edge of the sprite and the edge of the ninja on each side +Constants.Ninja.BORDER = (Constants.SPRITE_SIZE - Constants.Ninja.WIDTH) / 2 + +-- Player data such as speeds +Constants.Player = { + -- Speeds are measured in pixels per second + MAX_SPEED = 50.0, + + JUMP_SPEED = 125.0, + DEATH_JUMP_SPEED = 100.0, + CELEBRATION_JUMP_SPEED = 75.0, + + CELEBRATION_JUMP_COUNT = 3, + + CLIMBING_SPEED = 40.0, +} + +-- Enemy constants +Constants.Enemy = { + MAX_SPEED = 20.0, + MIN_SPEED = 10.0, + + CLIMBING_SPEED = 20.0, + + -- Hitbox width for detecting the edge of a platform + PLATFORM_DETECTION_WIDTH = 6, + + -- Chance of climbing next ladder + CLIMB_NEXT_LADDER_CHANCE = 0.2, +} + +-- Data for "Collectables" (gems and coins), such as value of each +Constants.Collectable = { + -- The coin and gem sprites are smaller in size + SIZE = 4, + + -- Point value of each + COIN_SCORE = 2, + GEM_SCORE = 5, +} + +-- Calculate the gap between the edge of the sprite and the edge of the actual coin/gem +Constants.Collectable.BORDER = (Constants.SPRITE_SIZE - Constants.Collectable.SIZE) / 2 + +-- Environment data such as gravity strength +Constants.Environment = { + GRAVITY_ACCELERATION = 375.0, +} + +Constants.LEVEL_COUNT = 3 + +Constants.LEVELS = { + -- Level 1 + { + -- Platform data + platforms = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x01, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x01, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + -- Coin, gem and ladder data + extras = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x12, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + -- Entity spawn data + entity_spawns = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + -- Background pipe data + pipes = { + 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0x1c, 0x16, 0x17, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0d, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x05, 0x16, 0x16, 0x16, 0x17, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x17, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x05, 0x16, 0x16, 0x16, 0x16, 0x16, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x11, 0x05, 0x06, 0x16, 0x17, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x06, 0x07, 0xff, 0xff, 0xff, 0x19, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0c, 0xff, 0xff, 0xff, 0x19, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + }, + -- Level 2 + { + -- Platform data + platforms = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x01, 0x02, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0x00, 0x01, 0x01 + }, + + -- Coin, gem and ladder data + extras = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x12, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, + 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, + 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, + 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, + 0x0b, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0x0b, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0x0b, + 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + + -- Entity spawn data + entity_spawns = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + + -- Background pipe data + pipes = { + 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0x14, 0x16, 0x1d, 0xff, 0xff, 0x0c, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0x0c, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x06, 0x16, 0x1d, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, + 0x16, 0x16, 0x16, 0x05, 0x17, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, + 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x16, + 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x0d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x04, 0x16, 0x16, 0x15, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0xff, 0xff, 0xff, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0xff, 0xff, 0xff, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x11, 0x06, 0x07, 0xff, + 0x1c, 0x16, 0x06, 0x07, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0xff, 0x1c, 0x16, + 0x0c, 0xff, 0xff, 0x0c, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0xff, 0x0c, 0xff + }, + }, + + -- Level 3 + { + -- Platform data + platforms = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x01, 0x02, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff, 0xff, 0x00, 0x01, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + + -- Coin, gem and ladder data + extras = { + 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0x13, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x12, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0x12, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0x0b, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + + -- Entity spawn data + entity_spawns = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x24, 0xff, 0xff, 0xff, 0xff, 0xff, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0x24, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + + -- Background pipe data + pipes = { + 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0x0c, 0xff, + 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x14, 0x06, 0x1d, 0xff, + 0xff, 0x14, 0x07, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x16, + 0xff, 0xff, 0x1c, 0x16, 0x16, 0x16, 0x17, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x05, 0x16, 0x0e, 0x06, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0xff, 0x0c, 0xff, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x16, 0x16, + 0x14, 0x16, 0x1d, 0xff, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, + 0xff, 0xff, 0x0c, 0xff, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x16, 0x15, 0x16, 0x16, + 0xff, 0xff, 0x0c, 0xff, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0c, 0xff, 0x18, 0xff, 0xff, 0xff, 0x11, 0x06, 0x1d, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0f, 0xff, 0x18, 0xff, 0xff, 0xff, 0x19, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, + 0x16, 0x16, 0x15, 0x07, 0x18, 0xff, 0xff, 0xff, 0x19, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x0c, 0x18, 0xff, 0xff, 0xff, 0x19, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff + }, + }, +} \ No newline at end of file diff --git a/source-code/ninja-thief/episode-5/32blit-lua/enemy_ninja.lua b/source-code/ninja-thief/episode-5/32blit-lua/enemy_ninja.lua new file mode 100644 index 0000000..752051e --- /dev/null +++ b/source-code/ninja-thief/episode-5/32blit-lua/enemy_ninja.lua @@ -0,0 +1,131 @@ +require "ninja" + +AIState = { + PATROLLING = 0, + CLIMBING = 1, +} + +EnemyNinja = Ninja:new() +EnemyNinja.current_direction = 1 +EnemyNinja.climb_next_ladder = false +EnemyNinja.ai_state = AIState.PATROLLING +EnemyNinja.speed = 0.0 + +function EnemyNinja:new(x, y) + local ret = Ninja.new(EnemyNinja, Colour.RED, x, y) + + if math.random() < 0.5 then + ret.current_direction = -1 + else + ret.current_direction = 1 + end + + ret.speed = math.random(Constants.Enemy.MIN_SPEED, Constants.Enemy.MAX_SPEED) + return ret +end + +function EnemyNinja:update(dt, level_data) + if self.ai_state == AIState.PATROLLING then + if not self:platform_ahead(level_data) then + -- No platform ahead, so turn around + self.current_direction = -self.current_direction + end + + self.velocity_x = self.speed * self.current_direction + + if self.can_climb then + if self.climb_next_ladder then + -- We're allowed to climb - check both directions for a ladder tile + local can_go_up = self:ladder_above_or_below(level_data, VerticalDirection.UP) + local can_go_down = self:ladder_above_or_below(level_data, VerticalDirection.DOWN) + + if can_go_up and can_go_down then + -- If we can go either way, pick one at random + if self:random_bool(0.5) then + self.climbing_state = ClimbingState.UP + else + self.climbing_state = ClimbingState.DOWN + end + elseif can_go_up then + -- Only way is up + self.climbing_state = ClimbingState.UP + elseif can_go_down then + -- Only way is down + self.climbing_state = ClimbingState.DOWN + end + + if self.climbing_state ~= ClimbingState.NONE then + -- We've now decided to climb + self.ai_state = AIState.CLIMBING + + self.climb_next_ladder = false + end + end + else + -- Keep "re-rolling" while we can't climb + + -- Decide if we should climb the next ladder we find + self.climb_next_ladder = self:random_bool(Constants.Enemy.CLIMB_NEXT_LADDER_CHANCE) + end + end + + Ninja.update(self, dt, level_data) + + -- If we're no longer in a climbing state, switch back to patrolling + -- This will happen when the enemy reaches the bottom of a ladder, or if they fall off the ladder + if self.climbing_state == ClimbingState.NONE then + self.ai_state = AIState.PATROLLING + end +end + +-- Returns true if there is a platform tile which is one block below and just in front of the ninja +-- This is used to work out when the ninja reaches the end of a platform +function EnemyNinja:platform_ahead(level_data) + -- Get a position which would be just in front of the ninja (and one tile below them) + local point_x = self.position_x + Constants.SPRITE_SIZE / 2 + self.current_direction * Constants.Enemy.PLATFORM_DETECTION_WIDTH / 2 + local point_y = self.position_y + Constants.SPRITE_SIZE + + -- Get tile at that position + local tile_id = self:tile_at_position(level_data.platforms, point_x, point_y) + + -- Return true if the tile is a platform (i.e. isn't an empty tile) + return tile_id ~= Constants.Sprites.BLANK_TILE +end + + +-- Returns true if there is a ladder tile above or below the ninjas's centre +function EnemyNinja:ladder_above_or_below(level_data, direction) + -- Get a position which would be one tile above/below the ninja + local point_x = self.position_x + local point_y = self.position_y + Constants.SPRITE_SIZE * direction + + -- Get tile at that position + local tile_id = self:tile_at_position(level_data.extras, point_x, point_y) + + -- Return true if the tile is a ladder + return tile_id == Constants.Sprites.LADDER +end + +-- Finds the tile which is at the position provided +function EnemyNinja:tile_at_position(tile_array, x, y) + -- Check that the position is within the game bounds (if it isn't, return an empty tile) + if x < 0.0 or x > Constants.GAME_WIDTH or + y < 0.0 or y > Constants.GAME_HEIGHT then + return Constants.Sprites.BLANK_TILE + end + + -- Get grid position of tile + local grid_x = x // Constants.SPRITE_SIZE + local grid_y = y // Constants.SPRITE_SIZE + + -- If we've not returned yet, then it's safe to get the tile from the level data + return tile_array[grid_y * Constants.GAME_WIDTH_TILES + grid_x + 1] +end + +-- Returns a boolean, with chance of being true equal to probability supplied +function EnemyNinja:random_bool(probability) + return math.random() < probability +end + + + diff --git a/source-code/ninja-thief/episode-5/32blit-lua/level.lua b/source-code/ninja-thief/episode-5/32blit-lua/level.lua new file mode 100644 index 0000000..b078edc --- /dev/null +++ b/source-code/ninja-thief/episode-5/32blit-lua/level.lua @@ -0,0 +1,234 @@ +require "constants" +require "enemy_ninja" +require "player_ninja" + +LevelState = { + PLAYING = 0, + PLAYER_DEAD = 1, + PLAYER_WON = 2, + FAILED = 3, + COMPLETE = 4, +} + +Level = { + level_data = nil, + level_number = 0, + + player = nil, + enemies = {}, + + level_state = LevelState.PLAYING, +} + +function Level:new(level_number) + local o = { + level_number = level_number, + level_data = { + platforms = Constants.LEVELS[level_number].platforms, + extras = Constants.LEVELS[level_number].extras, + entity_spawns = Constants.LEVELS[level_number].entity_spawns, + pipes = Constants.LEVELS[level_number].pipes, + }, + enemies = {}, + } + + setmetatable(o, self) + self.__index = self + + -- Copy the extras table as we'll modify it later + local extras = {} + for i, v in ipairs(o.level_data.extras) do + extras[i] = v + end + + o.level_data.extras = extras + + -- Search for player spawn position and create PlayerNinja object + -- Search for enemy spawn positions and create EnemyNinja objects and add them to a vector + + for y = 0, Constants.GAME_HEIGHT_TILES - 1 do + for x = 0, Constants.GAME_WIDTH_TILES - 1 do + + -- Get spritesheet index at point (x,y) + local spawn_id = o.level_data.entity_spawns[y * Constants.GAME_WIDTH_TILES + x + 1] + + -- Calculate actual position from grid position + local position_x = x * Constants.SPRITE_SIZE + local position_y = y * Constants.SPRITE_SIZE + + -- Create the correct instance + if spawn_id == Constants.Sprites.PLAYER_IDLE then + o.player = PlayerNinja:new(position_x, position_y) + + elseif spawn_id == Constants.Sprites.PLAYER_IDLE + Constants.Sprites.RED_OFFSET then + o.enemies[#o.enemies + 1] = EnemyNinja:new(position_x, position_y) + end + end + end + + return o +end + +function Level:update(dt) + if self.level_state == LevelState.PLAYING then + -- Update player + self.player:update(dt, self.level_data) + + if self:coins_left() == 0 then + self.level_state = LevelState.PLAYER_WON + self.player:set_won() + end + + -- Update enemies + for i, enemy in ipairs(self.enemies) do + enemy:update(dt, self.level_data) + + if self.player:check_ninja_colliding(enemy) then + -- Player touched an enemy, so they're dead + self.level_state = LevelState.PLAYER_DEAD + + -- Trigger "jump and fall" animation before restarting level + self.player:set_dead() + end + end + + if self.player:get_y() > Constants.GAME_HEIGHT then + -- Player has gone off the bottom of the screen, so they're dead + self.level_state = LevelState.FAILED + end + + elseif self.level_state == LevelState.PLAYER_DEAD then + -- Update player + self.player:update(dt, self.level_data) + + if self.player:get_y() > Constants.GAME_HEIGHT then + -- Player has gone off the bottom of the screen, so we can reset the level + self.level_state = LevelState.FAILED + end + + elseif self.level_state == LevelState.PLAYER_WON then + -- Update player + self.player:update(dt, self.level_data) + + if self.player:finished_celebrating() or self.player:get_y() > Constants.GAME_HEIGHT then + self.level_state = LevelState.COMPLETE + end + end +end + +function Level:render() + -- Render border + self:render_border() + + -- Render background pipes + screen.alpha = 0x80 + self:render_tiles(self.level_data.pipes) + screen.alpha = 0xff + + -- Render water + self:render_water() + + -- Render platforms + self:render_tiles(self.level_data.platforms) + + -- Render extras (coins, gems and ladders) + self:render_tiles(self.level_data.extras) + + -- Render enemies + for i, enemy in ipairs(self.enemies) do + enemy:render() + end + + -- Render the player + self.player:render() + + -- Render UI text + + -- Set the text colour to white + screen.pen = Pen(255, 255, 255) + + -- Render level number in top left corner + local level_string = "Level: " .. self.level_number + screen.text(level_string, minimal_font, Point(2, 2)) + + -- Render score in top right corner + local score_string = "Score: " .. self.player:get_score() + screen.text(score_string, minimal_font, Point(Constants.SCREEN_WIDTH - 2, 2), true, TextAlign.top_right) +end + +function Level:render_tiles(tile_ids) + -- Iterate through array of tile ids and render using the correct index in the spritesheet + for y = 0, Constants.GAME_HEIGHT_TILES - 1 do + for x = 0, Constants.GAME_WIDTH_TILES - 1 do + -- Iterate through array of tile ids and render using the correct index in the spritesheet + local tile_id = tile_ids[y * Constants.GAME_WIDTH_TILES + x + 1] + + -- Only render the tile if it isn't a blank tile + if tile_id ~= Constants.Sprites.BLANK_TILE then + -- Offset the tiles since the 32blit version has borders on the screen + screen.sprite(tile_id, Point(x * Constants.SPRITE_SIZE + Constants.GAME_OFFSET_X, y * Constants.SPRITE_SIZE + Constants.GAME_OFFSET_Y)) + end + end + end +end + +function Level:render_border() + -- Offset the tiles since the 32blit version has borders on the screen + + -- Each row is the same + for y = 0, Constants.SCREEN_HEIGHT - 1, Constants.SPRITE_SIZE do + -- Left border: + local x = 0 + + -- BORDER_FULL sprites + while x < Constants.GAME_OFFSET_X - Constants.SPRITE_SIZE do + screen.sprite(Constants.Sprites.BORDER_FULL, Point(x, y)) + x = x + Constants.SPRITE_SIZE + end + + -- BORDER_LEFT sprite + screen.sprite(Constants.Sprites.BORDER_LEFT, Point(x, y)) + + -- Right border: + x = Constants.SCREEN_WIDTH + + -- BORDER_FULL sprites + while x > Constants.SCREEN_WIDTH - Constants.GAME_OFFSET_X do + screen.sprite(Constants.Sprites.BORDER_FULL, Point(x, y)) + x = x - Constants.SPRITE_SIZE + end + + -- BORDER_RIGHT sprite + screen.sprite(Constants.Sprites.BORDER_RIGHT, Point(x, y)) + end +end + +function Level:render_water() + for i = 0, Constants.GAME_WIDTH_TILES - 1 do + screen.sprite(Constants.Sprites.WATER, Point(Constants.GAME_OFFSET_X + i * Constants.SPRITE_SIZE, Constants.GAME_OFFSET_Y + Constants.GAME_HEIGHT - Constants.SPRITE_SIZE)) + end +end + +function Level:level_failed() + return self.level_state == LevelState.FAILED +end + +function Level:level_complete() + return self.level_state == LevelState.COMPLETE +end + +function Level:get_level_number() + return self.level_number +end + +function Level:coins_left() + local total = 0 + + for i = 1, Constants.GAME_WIDTH_TILES * Constants.GAME_HEIGHT_TILES do + if self.level_data.extras[i] == Constants.Sprites.COIN then + total = total + 1 + end + end + + return total +end \ No newline at end of file diff --git a/source-code/ninja-thief/episode-5/32blit-lua/metadata.yml b/source-code/ninja-thief/episode-5/32blit-lua/metadata.yml new file mode 100644 index 0000000..f1b6dab --- /dev/null +++ b/source-code/ninja-thief/episode-5/32blit-lua/metadata.yml @@ -0,0 +1,12 @@ +# 32blit metadata --config metadata.yml --metadata-file ninja_thief.blmeta + +title: Ninja Thief +description: A tutorial game for the 32blit and PicoSystem. Collect all the coins while avoiding the enemy ninjas! +author: ThePythonator +splash: + file: assets/logo.png +icon: + file: assets/icon.png +version: v1.0.0 +category: game +url: https://github.com/32blit/32blit-tutorials-contrib diff --git a/source-code/ninja-thief/episode-5/32blit-lua/ninja.lua b/source-code/ninja-thief/episode-5/32blit-lua/ninja.lua new file mode 100644 index 0000000..d0191ba --- /dev/null +++ b/source-code/ninja-thief/episode-5/32blit-lua/ninja.lua @@ -0,0 +1,355 @@ +require "constants" + +Colour = { + BLUE = 0, + RED = 1, +} + +HorizontalDirection = { + LEFT = -1, + RIGHT = 1, +} + +VerticalDirection = { + UP = -1, + DOWN = 1, +} + +ClimbingState = { + NONE = 0, + IDLE = 1, + UP = 2, + DOWN = 3, +} + +Ninja = { + colour = nil, + + position_x = 0, + posiiton_y = 0, + velocity_x = 0, + velocity_y = 0, + + facing_direction = HorizontalDirection.LEFT, + + can_jump = false, + can_climb = false, + + climbing_state = ClimbingState.NONE, + + -- If a ninja is dead, they don't collide with any tiles, but are still affected by gravity + dead = false, +} + +function Ninja:new(colour, x, y) + local o = {colour = colour, position_x = x, position_y = y} + + setmetatable(o, self) + self.__index = self + return o +end + +function Ninja:update(dt, level_data) + + -- This is set to true later in the update stage, but only if the ninja is on a platform + self.can_jump = false + + -- Apply gravity, but only if the ninja isn't climbing a ladder + if self.climbing_state == ClimbingState.NONE then + self.velocity_y = self.velocity_y + Constants.Environment.GRAVITY_ACCELERATION * dt + end + + -- Move the ninja + self.position_x = self.position_x + self.velocity_x * dt + self.position_y = self.position_y + self.velocity_y * dt + + -- Don't allow ninja to go off the sides + if self.position_x < -Constants.Ninja.BORDER then + self.position_x = -Constants.Ninja.BORDER + elseif self.position_x > Constants.GAME_WIDTH - Constants.Ninja.BORDER - Constants.Ninja.WIDTH then + self.position_x = Constants.GAME_WIDTH - Constants.Ninja.BORDER - Constants.Ninja.WIDTH + end + + -- Detect and resolve any collisions with platforms, ladders, coins etc, only if the ninja isn't dead + if not self.dead then + self:handle_collisions(level_data) + end + + -- Update direction the ninja is facing (only if the player is moving) + if self.velocity_x < 0.0 then + self.facing_direction = HorizontalDirection.LEFT + elseif self.velocity_x > 0.0 then + self.facing_direction = HorizontalDirection.RIGHT + end +end + +function Ninja:render() + -- If ninja is travelling left, flip the image horizontally + local transform = SpriteTransform.NONE + if self.facing_direction == HorizontalDirection.LEFT then + transform = SpriteTransform.HORIZONTAL + end + + -- Set sprite index (take into account sprite offset based on colour) + local index = 0 + + if self.colour == Colour.RED then + index = Constants.Sprites.RED_OFFSET + end + + if self.climbing_state == ClimbingState.NONE then + index = index + Constants.Sprites.PLAYER_IDLE + else + index = index + Constants.Sprites.PLAYER_CLIMBING_IDLE + end + + screen.sprite(index, Point(math.floor(self.position_x + 0.5) + Constants.GAME_OFFSET_X, math.floor(self.position_y + 0.5) + Constants.GAME_OFFSET_Y), transform) +end + +function Ninja:check_object_colliding(object_x, object_y, object_size) + return self.position_x + Constants.SPRITE_SIZE - Constants.Ninja.BORDER > object_x and + self.position_x + Constants.Ninja.BORDER < object_x + object_size and + self.position_y + Constants.SPRITE_SIZE > object_y and + self.position_y < object_y + object_size +end + +function Ninja:check_ninja_colliding(ninja) + local ninja_x = ninja:get_x() + local ninja_y = ninja:get_y() + + return self.position_x + Constants.SPRITE_SIZE - Constants.Ninja.BORDER > ninja_x + Constants.Ninja.BORDER and + self.position_x + Constants.Ninja.BORDER < ninja_x + Constants.SPRITE_SIZE - Constants.Ninja.BORDER and + self.position_y + Constants.SPRITE_SIZE > ninja_y and + self.position_y < ninja_y + Constants.SPRITE_SIZE +end + +function Ninja:get_x() + return self.position_x +end + +function Ninja:get_y() + return self.position_y +end + +function Ninja:handle_collisions(level_data) + -- Reset can_climb flag (which then gets set by handle_ladders if the ninja is near a ladder) + self.can_climb = false + + -- Get position of ninja in "grid" of tiles + -- We're relying on converting to integers to truncate and hence round towards zero + -- FIXME: this is not doing that + local x = self.position_x // Constants.SPRITE_SIZE + local y = self.position_y // Constants.SPRITE_SIZE + + -- Check the four tiles which the ninja might be colliding with (the top left tile is marked by the x and y previously calculated) + + -- We need to check that the player is within the game area + -- If they aren't, we don't need to worry about checking for collisions + if x < Constants.GAME_WIDTH_TILES and y < Constants.GAME_HEIGHT_TILES and self.position_x >= -Constants.Ninja.BORDER and self.position_y >= -Constants.SPRITE_SIZE then + + -- It's possible the ninja is near the edge of the screen and we could end up checking tiles which don't exist (off the edge of the screen) + -- To avoid this issue, we vary the maximum x and y offsets + -- The minimum offset is handled by the trucation, since it will round up (rather than down) if the value is negative + local y_max = 1 + if y == Constants.GAME_HEIGHT_TILES - 1 then + y_max = 0 + end + + for y_offset = 0, y_max do + + local x_max = 1 + if x == Constants.GAME_WIDTH_TILES - 1 then + x_max = 0 + end + + for x_offset = 0, x_max do + -- Calculate grid position of this tile + local new_x = x + x_offset + local new_y = y + y_offset + + -- Handle platforms + self:handle_platform(level_data, new_x, new_y) + + -- Handle ladders + self:handle_ladder(level_data, new_x, new_y) + + -- Handle scoring + self:handle_scoring(level_data, new_x, new_y) + end + end + end + + -- If ninja can no longer climb, reset their climbing state + if self.can_climb ~= true then + self.climbing_state = ClimbingState.NONE + end + + if self.climbing_state ~= ClimbingState.NONE then + -- If player is on a ladder, they can jump + self.can_jump = true + + -- Set velocity to 0 + self.velocity_x = 0.0 + self.velocity_y = 0.0 + + -- Get climbing speed, depending on whether ninja is the player or an enemy + local climbing_speed = Constants.Enemy.CLIMBING_SPEED + + if colour == Colour.BLUE then + climbing_speed = Constants.Player.CLIMBING_SPEED + end + + -- If player is actually climbing the ladder, set vertical velocity to be in the right direction + if self.climbing_state == ClimbingState.UP then + self.velocity_y = -climbing_speed + + elseif self.climbing_state == ClimbingState.DOWN then + self.velocity_y = climbing_speed + end + end +end + +function Ninja:handle_platform(level_data, x, y) + -- Get tile's sprite index from level data + local tile_id = level_data.platforms[y * Constants.GAME_WIDTH_TILES + x + 1] + + -- Check the tile actually exists (check that it isn't blank) + if tile_id ~= Constants.Sprites.BLANK_TILE then + + -- Calculate the actual position of the tile from the grid position + local tile_x = x * Constants.SPRITE_SIZE + local tile_y = y * Constants.SPRITE_SIZE + + -- Check if the ninja is colliding with the tile + if self:check_object_colliding(tile_x, tile_y, Constants.SPRITE_SIZE) then + + -- Check if this platform have a ladder in front of it + if level_data.extras[y * Constants.GAME_WIDTH_TILES + x + 1] == Constants.Sprites.LADDER then + + -- Check that the ninja is not on a ladder + if self.climbing_state == ClimbingState.NONE then + + -- Check that the ninja is falling downwards + if self.velocity_y > 0.0 then + + -- Check that the ninja collided with the smaller platform hitbox + if self.position_y + Constants.SPRITE_SIZE - tile_y < Constants.ONE_WAY_PLATFORM_TOLERANCE then + -- Set the ninja's position so that it rests on top of the platform, and reset its vertical velocity to zero + self.position_y = tile_y - Constants.SPRITE_SIZE + self.velocity_y = 0.0 + + -- Allow the ninja to jump again + self.can_jump = true + end + end + end + else + -- Resolve collision by finding the direction with the least intersection + -- The value of the direction variable corresponds to: + -- 0 - left side of tile + -- 1 - top side of tile + -- 2 - right side of tile + -- 3 - bottom side of tile + local direction = 0 + + -- The starting value of least_intersection is at least the maximum possible intersection + -- The width/height of the tile is the maximum intersection possible + local least_intersection = Constants.SPRITE_SIZE + + -- Left side of tile + local intersection = self.position_x + Constants.Ninja.WIDTH + Constants.Ninja.BORDER - tile_x + if intersection < least_intersection then + direction = 0 + least_intersection = intersection + end + + -- Top side of tile + intersection = self.position_y + Constants.SPRITE_SIZE - tile_y + if intersection < least_intersection then + direction = 1 + least_intersection = intersection + end + + -- Right side of tile + intersection = tile_x + Constants.SPRITE_SIZE - self.position_x - Constants.Ninja.BORDER + if intersection < least_intersection then + direction = 2 + least_intersection = intersection + end + + -- Bottom side of tile + intersection = tile_y + Constants.SPRITE_SIZE - self.position_y + if intersection < least_intersection then + direction = 3 + least_intersection = intersection + end + + -- Now resolve collision by moving the ninja in the direction of least intersection, by exactly the amount equal to the least intersection + if direction == 0 then + -- Hit the left side of a platform + self.position_x = self.position_x - least_intersection + self.velocity_x = 0.0 + elseif direction == 1 then + -- Landed on top of a platform + self.position_y = self.position_y - least_intersection + self.velocity_y = 0.0 + + -- Allow the ninja to jump again + self.can_jump = true + + -- Stop the ninja from climbing + self.climbing_state = ClimbingState.NONE + elseif direction == 2 then + -- Hit the right side of a platform + self.position_x = self.position_x + least_intersection + self.velocity_x = 0.0 + elseif direction == 3 then + -- Hit the underside of a platform + self.position_y = self.position_y + least_intersection + self.velocity_y = 0.0 + end + end + end + end +end + +function Ninja:handle_ladder(level_data, x, y) + -- Get tile's sprite index from level data + local tile_id = level_data.extras[y * Constants.GAME_WIDTH_TILES + x + 1] + + -- Check if the tile is a ladder + if tile_id == Constants.Sprites.LADDER then + + -- Calculate the actual position of the tile from the grid position + local tile_x = x * Constants.SPRITE_SIZE + local tile_y = y * Constants.SPRITE_SIZE + + -- Check if ninja is colliding with the tile + if self:check_object_colliding(tile_x, tile_y, Constants.SPRITE_SIZE) then + + -- Check that ninja is sufficiently close to ladder + if math.abs(tile_x - self.position_x) < Constants.Ninja.WIDTH / 2 then + self.can_climb = true + + -- Check if ninja should be climbing or idling on ladder + if self.climbing_state ~= ClimbingState.NONE then + -- Lock position to ladder + self.position_x = tile_x + end + end + end + end +end + +-- Only implemented by PlayerNinja +function Ninja:handle_scoring(level_data, x, y) +end + +function Ninja:jump(jump_speed) + -- Upwards is negative + self.velocity_y = -jump_speed + + -- Reset climbing state when player jumps + self.climbing_state = ClimbingState.NONE +end + diff --git a/source-code/ninja-thief/episode-5/32blit-lua/ninja_thief.lua b/source-code/ninja-thief/episode-5/32blit-lua/ninja_thief.lua new file mode 100644 index 0000000..b868c2a --- /dev/null +++ b/source-code/ninja-thief/episode-5/32blit-lua/ninja_thief.lua @@ -0,0 +1,65 @@ +require "constants" +require "level" + +-- Our global variables are defined here +background = nil + +last_time = 1.0 + +level = nil + +-- Setup your game here +function init() + -- Set the resolution to 160x120 + set_screen_mode(ScreenMode.lores) + + -- Load the background from assets.cpp + background = Surface.load("assets/background.blim") + + -- Load the spritesheet + screen.load_sprites("assets/spritesheet.blim") + + level = Level:new(1) +end + +-- This function is called to perform rendering of the game. +function render(time) + -- Clear the screen + screen.pen = Pen(0, 0, 0) + screen.clear() + + -- Draw the entire background image onto the screen at (0, 0) + screen.blit(background, Rect(0, 0, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT), Point(0, 0)) + + -- Render the level + level:render() +end + +-- This is called to update your game state. +function update(time) + -- Calculate change in time (in seconds) since last frame + local dt = (time - last_time) / 1000.0 + last_time = time + + -- Limit dt + if dt > 0.05 then + dt = 0.05 + end + + -- Update the level + level:update(dt) + + if level:level_failed() then + -- Restart the same level + local level_number = level:get_level_number() + + level = Level:new(level_number) + elseif level:level_complete() then + -- Start the next level + + local level_number = level:get_level_number() + level_number = (level_number % Constants.LEVEL_COUNT) + 1 + + level = Level:new(level_number) + end +end diff --git a/source-code/ninja-thief/episode-5/32blit-lua/player_ninja.lua b/source-code/ninja-thief/episode-5/32blit-lua/player_ninja.lua new file mode 100644 index 0000000..4621663 --- /dev/null +++ b/source-code/ninja-thief/episode-5/32blit-lua/player_ninja.lua @@ -0,0 +1,108 @@ +require "ninja" + +PlayerNinja = Ninja:new() +PlayerNinja.score = 0 +PlayerNinja.won = false +PlayerNinja.celebration_jumps_remaining = Constants.Player.CELEBRATION_JUMP_COUNT + +function PlayerNinja:new(x, y) + return Ninja.new(PlayerNinja, Colour.BLUE, x, y) +end + +function PlayerNinja:update(dt, level_data) + -- If nothing is pressed, the player shouldn't move + self.velocity_x = 0.0 + + if self.won then + -- Jump in celebration! + + if self.can_jump and self.celebration_jumps_remaining > 0 then + self:jump(Constants.Player.CELEBRATION_JUMP_SPEED) + self.celebration_jumps_remaining = self.celebration_jumps_remaining - 1 + end + elseif not self.dead then + -- Handle any buttons the user has pressed + + -- Note: "else if" isn't used, because otherwise the sprite will still move when both buttons are pressed + -- Instead, we add/subtract the velocity, so if both are pressed, nothing happens + if buttons.state & Button.LEFT ~= 0 then + self.velocity_x = self.velocity_x - Constants.Player.MAX_SPEED + end + if buttons.state & Button.RIGHT ~= 0 then + self.velocity_x = self.velocity_x + Constants.Player.MAX_SPEED + end + + -- Handle climbing + if self.can_climb then + local up = buttons.state & Button.UP ~= 0 + local down = buttons.state & Button.DOWN ~= 0 + + if up ~= down then + -- Only one of up and down are selected + self.climbing_state = up and ClimbingState.UP or ClimbingState.DOWN + elseif self.climbing_state ~= ClimbingState.NONE then + -- Player has already been climbing the ladder, and either none or both of up and down are pressed + self.climbing_state = ClimbingState.IDLE + end + end + + -- Handle jumping + -- Note that we use buttons.pressed, which only contains the buttons just pressed (since the last frame) + if buttons.pressed & Button.A ~= 0 then + if self.can_jump then + self:jump(Constants.Player.JUMP_SPEED) + end + end + end + + -- Call parent update method + Ninja.update(self, dt, level_data) +end + +function PlayerNinja:handle_scoring(level_data, x, y) + -- Calculate position of tile in array + local array_position = y * Constants.GAME_WIDTH_TILES + x + 1 + + -- Get tile's sprite index from level data + local tile_id = level_data.extras[array_position] + + -- Check the tile is a coin or gem + if tile_id == Constants.Sprites.COIN or tile_id == Constants.Sprites.GEM then + + -- Calculate the actual position of the tile from the grid position + local tile_x = x * Constants.SPRITE_SIZE + local tile_y = y * Constants.SPRITE_SIZE + + -- Check if the ninja is colliding with the tile + -- We use a smaller object_size since the coins and gems are smaller, which also means we have to offset the tile_position + if self:check_object_colliding(tile_x + Constants.Collectable.BORDER, tile_y + Constants.Collectable.BORDER, Constants.Collectable.SIZE) then + + -- Add the correct amount of score if it's a coin or gem tile + if tile_id == Constants.Sprites.COIN then + self.score = self.score + Constants.Collectable.COIN_SCORE + elseif tile_id == Constants.Sprites.GEM then + self.score = self.score + Constants.Collectable.GEM_SCORE + end + + -- Remove item from level data + level_data.extras[array_position] = Constants.Sprites.BLANK_TILE + end + end +end + +function PlayerNinja:get_score() + return self.score +end + +function PlayerNinja:set_dead() + self.dead = true + self:jump(Constants.Player.DEATH_JUMP_SPEED) +end + +function PlayerNinja:set_won() + self.won = true +end + +function PlayerNinja:finished_celebrating() + return self.can_jump and self.celebration_jumps_remaining == 0 +end \ No newline at end of file From e1cecbdedd48e495a490804eecb31128c6efcd90 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Thu, 19 Sep 2024 20:49:32 +0100 Subject: [PATCH 6/6] Update lua assets.yml to output the right thing in the right place Turns out that this is possible --- source-code/ninja-thief/episode-1/32blit-lua/assets.yml | 9 +++++---- source-code/ninja-thief/episode-2/32blit-lua/assets.yml | 9 +++++---- source-code/ninja-thief/episode-3/32blit-lua/assets.yml | 9 +++++---- source-code/ninja-thief/episode-4/32blit-lua/assets.yml | 9 +++++---- source-code/ninja-thief/episode-5/32blit-lua/assets.yml | 9 +++++---- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/source-code/ninja-thief/episode-1/32blit-lua/assets.yml b/source-code/ninja-thief/episode-1/32blit-lua/assets.yml index 721942b..a4d890e 100644 --- a/source-code/ninja-thief/episode-1/32blit-lua/assets.yml +++ b/source-code/ninja-thief/episode-1/32blit-lua/assets.yml @@ -1,8 +1,9 @@ -# 32blit pack --force --config assets.yml -# ideally these should be .blim, but the tools don't recognise that +# 32blit pack --force --config assets.yml --output assets -background.bin: +background.blim: + type: raw_binary assets/background.png: -spritesheet.bin: +spritesheet.blim: + type: raw_binary assets/spritesheet.png: diff --git a/source-code/ninja-thief/episode-2/32blit-lua/assets.yml b/source-code/ninja-thief/episode-2/32blit-lua/assets.yml index 721942b..a4d890e 100644 --- a/source-code/ninja-thief/episode-2/32blit-lua/assets.yml +++ b/source-code/ninja-thief/episode-2/32blit-lua/assets.yml @@ -1,8 +1,9 @@ -# 32blit pack --force --config assets.yml -# ideally these should be .blim, but the tools don't recognise that +# 32blit pack --force --config assets.yml --output assets -background.bin: +background.blim: + type: raw_binary assets/background.png: -spritesheet.bin: +spritesheet.blim: + type: raw_binary assets/spritesheet.png: diff --git a/source-code/ninja-thief/episode-3/32blit-lua/assets.yml b/source-code/ninja-thief/episode-3/32blit-lua/assets.yml index 721942b..a4d890e 100644 --- a/source-code/ninja-thief/episode-3/32blit-lua/assets.yml +++ b/source-code/ninja-thief/episode-3/32blit-lua/assets.yml @@ -1,8 +1,9 @@ -# 32blit pack --force --config assets.yml -# ideally these should be .blim, but the tools don't recognise that +# 32blit pack --force --config assets.yml --output assets -background.bin: +background.blim: + type: raw_binary assets/background.png: -spritesheet.bin: +spritesheet.blim: + type: raw_binary assets/spritesheet.png: diff --git a/source-code/ninja-thief/episode-4/32blit-lua/assets.yml b/source-code/ninja-thief/episode-4/32blit-lua/assets.yml index 721942b..a4d890e 100644 --- a/source-code/ninja-thief/episode-4/32blit-lua/assets.yml +++ b/source-code/ninja-thief/episode-4/32blit-lua/assets.yml @@ -1,8 +1,9 @@ -# 32blit pack --force --config assets.yml -# ideally these should be .blim, but the tools don't recognise that +# 32blit pack --force --config assets.yml --output assets -background.bin: +background.blim: + type: raw_binary assets/background.png: -spritesheet.bin: +spritesheet.blim: + type: raw_binary assets/spritesheet.png: diff --git a/source-code/ninja-thief/episode-5/32blit-lua/assets.yml b/source-code/ninja-thief/episode-5/32blit-lua/assets.yml index 721942b..a4d890e 100644 --- a/source-code/ninja-thief/episode-5/32blit-lua/assets.yml +++ b/source-code/ninja-thief/episode-5/32blit-lua/assets.yml @@ -1,8 +1,9 @@ -# 32blit pack --force --config assets.yml -# ideally these should be .blim, but the tools don't recognise that +# 32blit pack --force --config assets.yml --output assets -background.bin: +background.blim: + type: raw_binary assets/background.png: -spritesheet.bin: +spritesheet.blim: + type: raw_binary assets/spritesheet.png: