From 607f6a80593b3db02e6758d7af56a4c1803d0335 Mon Sep 17 00:00:00 2001 From: frank <420@shampoo.ooo> Date: Thu, 24 Feb 2022 01:16:45 -0500 Subject: [PATCH] new pad graphics and glow --- NS.py | 208 +++++++++++++++++++++++++++---- config | 1 + resource/pad/pad_0.png | Bin 0 -> 2148 bytes resource/pad_mask/pad_mask_0.png | Bin 0 -> 338 bytes resource/pad_mask/pad_mask_1.png | Bin 0 -> 318 bytes resource/pad_mask/pad_mask_2.png | Bin 0 -> 421 bytes resource/pad_mask/pad_mask_3.png | Bin 0 -> 392 bytes 7 files changed, 184 insertions(+), 25 deletions(-) create mode 100644 resource/pad/pad_0.png create mode 100644 resource/pad_mask/pad_mask_0.png create mode 100644 resource/pad_mask/pad_mask_1.png create mode 100644 resource/pad_mask/pad_mask_2.png create mode 100644 resource/pad_mask/pad_mask_3.png diff --git a/NS.py b/NS.py index 95ff76c..b726e64 100644 --- a/NS.py +++ b/NS.py @@ -37,7 +37,7 @@ from lib.pgfw.pgfw.Animation import Animation from lib.pgfw.pgfw.extension import ( get_step, get_step_relative, get_delta, reflect_angle, render_box, get_hsla_color, get_hue_shifted_surface, - get_color_swapped_surface + get_color_swapped_surface, load_frames ) from lib.pgfw.pgfw.gfx_extension import aa_filled_polygon @@ -56,10 +56,10 @@ class NS(Game, Animation): # four sides of the square and the two diagonals. N, NE, E, NW, S, W = range(6) - FRONT_WIDTH = 230 - BACK_WIDTH = 500 - LENGTH = 150 - FRONT = 300 + FRONT_WIDTH = 156 + BACK_WIDTH = 271 + LENGTH = 94 + FRONT = 330 STEP = .4 IDLE_TIMEOUT = 60000 * 5 CHANNEL_COUNT = 8 @@ -101,6 +101,10 @@ class NS(Game, Animation): "system": { "bool": "minimize-load-time" + }, + "pads": + { + "int-list": "center" } }) # If a serial port was passed on the command line, override the config file setting @@ -497,6 +501,7 @@ class Tony(Sprite): self.effect.update(flags=BLEND_RGBA_SUB) self.get_display_surface().blit(intermediate_surface, self.location.topleft) if self.get_game().title.active: + self.get_game().title.video.update() self.get_game().platform.update() self.get_game().chemtrails.update() frameset = self.get_current_frameset() @@ -718,7 +723,7 @@ class Title(Animation): self.get_game().tony.set_frameset("static") self.halt() self.play(self.show_video, delay=self.get_configuration("time", "attract-reset-countdown"), play_once=True) - self.video.update() + # self.video.update() self.draw_scores() @@ -1000,21 +1005,56 @@ class Wipe(Animation): class Platform(GameChild): + """ + This class contains methods for manipulating and getting information about the platform the player is standing on, + both the real one and on-screen representation. It initializes four Light objects, one for each pad on the platform. + It can set lights to glowing, return the states of individual lights or pairs of lights, reset lights, and draw the + on-screen representation. + """ def __init__(self, parent): + """ + Initialize four lights, one for each pad on the platform. Initialize a Sprite for the pad graphics with one + frameset per six possible combinations of lights. Initialize masks for creating a glow effect on the pads. + """ GameChild.__init__(self, parent) - dsr = self.get_display_surface().get_rect() - self.border = Sprite(self) - self.border.load_from_path(self.get_resource("DancePadClear.png"), True) - self.border.location.midbottom = dsr.centerx, dsr.bottom self.lights = [ Light(self, self.get_configuration("pads", "nw_color"), NS.LNW), Light(self, self.get_configuration("pads", "ne_color"), NS.LNE), Light(self, self.get_configuration("pads", "se_color"), NS.LSE), Light(self, self.get_configuration("pads", "sw_color"), NS.LSW) ] + self.view = Sprite(self) + self.view.load_from_path("pad", True) + self.view.add_frameset([0], name="neutral") + self.view.add_frameset([1], name=str(NS.N)) + self.view.add_frameset([2], name=str(NS.E)) + self.view.add_frameset([3], name=str(NS.NW)) + self.view.add_frameset([4], name=str(NS.NE)) + self.view.add_frameset([5], name=str(NS.W)) + self.view.add_frameset([6], name=str(NS.S)) + self.view.location.center = self.get_configuration("pads", "center") + self.glow_masks = [] + base_images = load_frames(self.get_resource("pad_mask"), True) + for image in base_images: + self.glow_masks.append([image]) + for mask in self.glow_masks: + intensity_resolution = 12 + for intensity in range(1, intensity_resolution): + copy = mask[0].copy() + pixels = pygame.PixelArray(copy) + color = pygame.Color(0, 0, 0) + h, s, l, a = color.hsla + l = int(intensity / intensity_resolution * 100) + color.hsla = h, s, l, a + pixels.replace(pygame.Color(0, 0, 0), color) + del pixels + mask.append(copy) def reset(self): + """ + Deactivate this object and reset each light + """ self.deactivate() self.reset_lights() @@ -1023,19 +1063,37 @@ class Platform(GameChild): light.reset() def deactivate(self): + """ + This will stop the platform from being drawn and lights from updating + """ self.active = False def activate(self): + """ + This will cause the platform to get drawn and lights to update when this object's update method is called + """ self.active = True def unpress(self): + """ + Set the state of each light to unpressed + """ for light in self.lights: light.pressed = False def get_pressed(self): + """ + Returns a list of light positions pressed (NS.LNW, NS.LNE, NS.LSE, NS.LSW) + """ return [light.position for light in self.lights if light.pressed] def get_edge_pressed(self): + """ + Gets the edge (2 light combination) currently pressed. This only returns one edge since there should only + be one able to be pressed at a time. If no edge is pressed, returns None. + + @return NS.N | NS.NE | NS.E | NS.NW | NS.S | NS.W | None + """ pressed = self.get_pressed() if NS.LNW in pressed and NS.LNE in pressed: return NS.N @@ -1050,7 +1108,32 @@ class Platform(GameChild): elif NS.LSW in pressed and NS.LNW in pressed: return NS.W + def get_glowing_edge(self): + """ + Return the edge currently glowing or None + + @return NS.N | NS.NE | NS.E | NS.NW | NS.S | NS.W | None + """ + if self.lights[NS.LNW].glowing() and self.lights[NS.LNE].glowing(): + return NS.N + elif self.lights[NS.LNE].glowing() and self.lights[NS.LSW].glowing(): + return NS.NE + elif self.lights[NS.LNE].glowing() and self.lights[NS.LSE].glowing(): + return NS.E + elif self.lights[NS.LNW].glowing() and self.lights[NS.LSE].glowing(): + return NS.NW + elif self.lights[NS.LSE].glowing() and self.lights[NS.LSW].glowing(): + return NS.S + elif self.lights[NS.LSW].glowing() and self.lights[NS.LNW].glowing(): + return NS.W + def get_buttons_from_edges(self, edges): + """ + Get a list of light positions contained by a list of edges. For example, [NS.N, NS.E] would give [NS.LNW, NS.LNE, NS.LSE]. + + @param edges list of edges [NS.N | NS.NE | NS.E | NS.NW | NS.S | NS.W] + @return list of light positions [NS.LNW | NS.LNE | NS.LSE | NS.LSW] + """ buttons = set() for edge in edges: if edge == NS.N: @@ -1068,6 +1151,13 @@ class Platform(GameChild): return list(buttons) def get_steps_from_edge(self, edge): + """ + Get the edges that are one step away from a given edge. For example, NS.N would give (NS.NE, NS.NW) because those + are the edges that only require a pivot move of one step from NS.N. + + @param edge one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W + @return pair of edges that are one step away + """ if edge == NS.N: return NS.NE, NS.NW elif edge == NS.NE: @@ -1082,6 +1172,13 @@ class Platform(GameChild): return NS.NE, NS.NW def get_right_angles_from_edge(self, edge): + """ + Get the pair of angles that are at a right angle to a given edge. For example, NS.N would return (NS.E, NW.W). For + diagonals, this returns None. + + @param edge one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W + @return pair of edges that are at a right angle to given edge or None + """ if edge == NS.N: return NS.E, NS.W elif edge == NS.NE: @@ -1096,6 +1193,13 @@ class Platform(GameChild): return NS.N, NS.S def get_opposite_of_edge(self, edge): + """ + Get the edge opposite to a given edge. For example, NS.N would return NS.S. For diagonals, the opposite is the + reverse diagonal. + + @param edge one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W + @return edge opposite to given edge, one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W + """ if edge == NS.N: return NS.S elif edge == NS.NE: @@ -1110,6 +1214,12 @@ class Platform(GameChild): return NS.E def get_color_pair_from_edge(self, edge): + """ + Return the pair of pygame color objects that make up a given edge + + @param edge one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W + @return tuple of pygame color objects + """ if edge == NS.N: return self.lights[NS.LNW].color, self.lights[NS.LNE].color elif edge == NS.NE: @@ -1124,6 +1234,11 @@ class Platform(GameChild): return self.lights[NS.LNW].color, self.lights[NS.LSW].color def set_glowing(self, selected): + """ + Set the given light IDs to glowing and other indices to not glowing. + + @param selected list of light IDs (NS.LNW, NS.LNE, NS.LSE, NS.LSW) + """ for ii, light in enumerate(self.lights): light.glow_index = 0 light.halt(light.glow) @@ -1131,20 +1246,44 @@ class Platform(GameChild): light.play(light.glow) def update(self): + """ + Update each light and draw the platform and glow effect + """ if self.active: for light in self.lights: light.update() - # self.border.update() - for light in self.lights: - light.draw_glow() + # draw the pad based on which pads are glowing + glowing = self.get_glowing_edge() + if glowing is None: + self.view.set_frameset("neutral") + self.view.update() + else: + self.view.set_frameset(str(glowing)) + self.view.update() + for light in self.lights: + if light.glowing(): + self.get_display_surface().blit(self.glow_masks[light.position][light.glow_index], self.view.location, None, BLEND_RGBA_ADD) + # for light in self.lights: + # light.draw_glow() class Light(Animation): + """ + This class represents a pad on the platform. Typically there are four instances for a platform, one for each corner of the + platform. Each light stores its color and position on the platform. This class contains methods for glowing the light and + getting its properties. + """ - MAX_GLOW_INDEX = 25 TITLE_OFFSET = 0 def __init__(self, parent, color, position): + """ + Initialize a new Light object, providing color and position on the platform. + + @param parent PGFW game object that instantiated this object + @param color pygame color object + @param position the light's position on the platform, one of NS.LNW, NS.LNE, NS.LSE, NS.LSW + """ Animation.__init__(self, parent) self.color = Color(color) self.color.a = 225 @@ -1170,38 +1309,43 @@ class Light(Animation): self.points = mid, midright, backright, backmid elif self.position == NS.LSW: self.points = midleft, mid, backmid, backleft - self.register(self.blink, interval=300) self.register(self.glow) def reset(self): + """ + Unhide, halt glow animation + """ self.hidden = False - self.halt(self.blink) self.halt(self.glow) self.reset_timer() self.glow_index = 0 - def blink(self): - self.hidden = not self.hidden - def glow(self): + """ + Moves the glow animation forward a frame by incrementing an index + """ self.glow_index += 1 - if self.glow_index > self.MAX_GLOW_INDEX: + if self.glow_index >= len(self.parent.glow_masks[0]): self.glow_index = 0 def update(self): + """ + Checks the attack state to determine whether to start or stop glowing + """ Animation.update(self) if not self.get_game().title.active: boss = self.get_game().boss chemtrails = self.get_game().chemtrails + # checks the boss attack queue and chameleon queue index to see if the glow should be started now if boss.queue and boss.brandish_complete and not self.is_playing(self.glow) \ and self.in_orientation(boss.queue[chemtrails.queue_index]): self.play(self.glow) - elif self.is_playing(self.glow) and (not boss.queue or - not self.in_orientation(boss.queue[chemtrails.queue_index])): + # turns off the glow + elif self.is_playing(self.glow) and (not boss.queue or not self.in_orientation(boss.queue[chemtrails.queue_index])): self.reset() - if not self.hidden: - ds = self.get_display_surface() - aa_filled_polygon(ds, self.get_points(), self.color) + # if not self.hidden: + # ds = self.get_display_surface() + # aa_filled_polygon(ds, self.get_points(), self.color) def get_points(self): if self.get_game().title.active: @@ -1234,6 +1378,12 @@ class Light(Animation): ) def in_orientation(self, orientation): + """ + Returns True if this light is contained in the given edge + + @param orientation edge to check, one of NS.N, NS.NW, NS.W, NS.NE, NS.E, NS.S + @return True | False + """ if self.position == NS.LNW: return orientation in (NS.N, NS.NW, NS.W) elif self.position == NS.LNE: @@ -1243,6 +1393,14 @@ class Light(Animation): elif self.position == NS.LSW: return orientation in (NS.S, NS.NE, NS.W) + def glowing(self): + """ + Returns True if this light is glowing, False otherwise + + @return True | False + """ + return self.is_playing(self.glow) + class Chemtrails(Sprite): diff --git a/config b/config index a4c3db7..6ebc4f5 100644 --- a/config +++ b/config @@ -70,3 +70,4 @@ nw_color = #00FF88 ne_color = #FF88FF se_color = #2222FF sw_color = #FF2222 +center = 319, 376 diff --git a/resource/pad/pad_0.png b/resource/pad/pad_0.png new file mode 100644 index 0000000000000000000000000000000000000000..02a6ce542542589b57d8dee2a7b18f5a0bb8b189 GIT binary patch literal 2148 zcmV-q2%GnbP)^YHTGA-edlVlR$ z$x-ZYcTEZnfYTD6ka3;kH&R1(0wiuXGAG6B>%XV#^`i(-v?>NBsZi)Kmc9kRmph{Y zWf}n_sSsIe21?0^P}8k!O^8^SXa$3GJl8p~%!@ssj8O6n2a~;$#+5KWs7Pz?Z4D@`lo;zFZT9QfC_|``g8R0L4t1vnD$rsjry3W6)#& z#J2<43s5?PNmzM9k3^q4L67dl6|0_s(h*EJQ_{0Lq15pM#fppT{yvl3Z{75!+)24i z;L&c~*E)Xd8`{=)p8%5v<&ET|i(OS}fg*R}@4YJ`RigN=FdPluvEw&iedz*<_B&*! zo?+6;mAEEcC|vfEv-Jd&`T*C~!NK0tVodhNLE}yL$6i6wa-g?9YfvjOU4Kuz6By|u zW~Fs^DSCjS+?i~HB((8rA&J)sD*3^6SE811CL@8P?sueh0Hte~#09)>_1KUbVV6zA zxM%^2VwlK)1R?9v0uydpL&$_XYJZ?jX#*BlQls1;#@5vK8$6T7fRN>fC#kPpT(2L=acgy;O_4L%cO{k*QZn&E5>sl`iz##F z_e@5S^!WK+#0(v$yOXa2m7^Ar#MIzc613KRE9<}GdvjH)5&OF`{s9Vi%cPB)fJ1qz zwacTX%;73Vg#18}0DEaEIJ6di=@B=m^(!t%k}He1u6Xn>)a4JgsXYNZI}&yU>LhB( zvnGt8OqrSklmVQH|DF<8(+Nj{Mh8k>8=HyXL7rJI(KdWNuR>))G2o~HDepV#P3RI)KTJ~e#@Q8FriP4)An(3I3)o$}4x~MR@>c692JrRMr&`XIte?ET~JklY`bEq4KVTeu=%Y zZRt)_!GzVajw@-M@bpc}xRaDJRMMRkhRO4HOo@XlspCr6#lf9)=1hJdLemwYF@Vyn z%vrh;KvKTMVJBs*QYREBlBG@|n8a-ZBZS&X3Md6&vj3yjy%JoBZLwgCQSJ!CWWP`1 zdN{iKc1s*rF0i9hzok&aoe07tytH8lr>#q1C&!+ATf{XN5+RtReT9$4D?)n8om_WP zbvI|dgM-FSa7vvz?j!~%jh#unD*+@3>2xSL?xdPCaqZwx;viJdoh;{K37}-cB+Zoo z5k#p|R$Jwc!ZIfuBz(vkp}Ov54=5$v$$`+L(pq1VgxUioblhTqk_Z!A36itks4P%Y z-AOD^5@3>cri5g|o%lrGq%2T!w~Ir85($&I8p?I9gyt(+Cp5C5XlIgh6dRBr+-aZg zB*aVOs6_&y+=U3014`W3`GFDvldxXNOgYLMg!*uXalMmD&IFL?5{*sdYBTs8 zN^UbY5GGtN&?`vR-z1{c04O2zHbr92gpXWP5lzr0z|XEZoc~RGd53t#`0_0fyqyKlM9orI@y9P)U0L9wKZN{ zc_R&!mVFovP+UL%UcS!p7lujPi+ou1IzUM6QD~HJ5n4ZQ$fWc^qh&t&#w`UhIZzCZ zK?+FLK$%h<31F#{j8?aDw|Z@{FzqGRau$XX&$#|Qskt_f($Q*yhne!Mj0MKAf)I%d1?=AeF5Dw)2>4}=gx ab@d+$;C@UHX>LjY000033Wg(IFBkg=gmi*erwxWcib3(((C%!qbI|H{UbN7nLnoU8~2YMQ~p*^~9uc(Ukuk0@C= z_aXt`WbOLk^WWUvZrVBceCzdmTlI`_-OSr|M^c(2cFJ{d9xdGX4CY*l=mW;$YAa=p Ti!PT6fz0uA^>bP0l+XkKDe`F- literal 0 HcmV?d00001 diff --git a/resource/pad_mask/pad_mask_1.png b/resource/pad_mask/pad_mask_1.png new file mode 100644 index 0000000000000000000000000000000000000000..ce39f7028a9be886f06681469b0c905e823d9a15 GIT binary patch literal 318 zcmeAS@N?(olHy`uVBq!ia0y~yU=#+jlQ`IbWbp#?44?q3r;B4q#jUq@P6{?C2)H^P zGx|TpkdgM?Eszy=--d7P!oW`6Q;)6P9oB^UJh=CK?u>@n5orNOSe9>n-Zvv*2TO?0 zfns+b21h~RnBT0fK06Xt+yTl8h!`FWb_X*2Wq~pZTC0+pmYC@RT`8Kt?y{qLK>898Xt2mvv4FO#r#YU9SKD literal 0 HcmV?d00001 diff --git a/resource/pad_mask/pad_mask_2.png b/resource/pad_mask/pad_mask_2.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec0b53b0968326b07d215bc033a90bbbfd7e7a6 GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0y~yU=#+jlQ`IbWbp#?44^=cr;B4q#jUqDFZLcb;Bj%( z{`=jh{6Rm%rs-FFSjAOqgbJ^-0#y?Y{^VOS-B{%})7P-^{b9o-yQ3}Zn$0TDt$N(k z#hEn8GC0-mVWq<Ef)wf7ZD(9u|3SO9;?VQe_x|lZ z(!yu{f~)`7xt*)^KI^IZ)JkyE-^#_%Iw{5fFCpsoQrHXr+{pe!Qd4 zE>6yl&evzz)|hS-ZhGzO>$1i)&`@yFEYCwx##hh8ADFjk=j!zmN={FXWgncqx0B&Y mZ1EcH8($77NuhKX{weK(h$#}Z@xvX