From 1c5222fecf7b2580c850cfec4e4c53e778d0141e Mon Sep 17 00:00:00 2001 From: Rob Kelly Date: Wed, 20 Nov 2024 19:22:11 -0700 Subject: [PATCH] Ball selector --- asset_dev/balls/ball_icons.xcf | Bin 0 -> 16269 bytes assets/ui/ball_icons/basic_icon.png | 3 + assets/ui/ball_icons/basic_icon.png.import | 34 ++++++++ project.godot | 10 +++ src/equipment/balls/physics_ball/game_ball.gd | 7 ++ src/player/debug_player.tres | 7 +- src/player/shot_setup/ball_point.gd | 15 +--- src/player/shot_setup/shot_setup.gd | 20 ++++- src/player/shot_setup/shot_setup.tscn | 3 +- src/player/world_player.gd | 41 +++++++++ src/ui/main_theme.tres | 5 ++ src/ui/shot_hud/ball_selector/ball_icon.gd | 51 +++++++++++ src/ui/shot_hud/ball_selector/ball_icon.tscn | 68 +++++++++++++++ .../shot_hud/ball_selector/ball_selector.gd | 54 ++++++++++++ .../shot_hud/ball_selector/ball_selector.tscn | 66 ++++++++++++++ src/ui/shot_hud/shot_hud.gd | 2 + src/ui/shot_hud/shot_hud.tscn | 81 +++++++++++------- 17 files changed, 418 insertions(+), 49 deletions(-) create mode 100644 asset_dev/balls/ball_icons.xcf create mode 100644 assets/ui/ball_icons/basic_icon.png create mode 100644 assets/ui/ball_icons/basic_icon.png.import create mode 100644 src/ui/shot_hud/ball_selector/ball_icon.gd create mode 100644 src/ui/shot_hud/ball_selector/ball_icon.tscn create mode 100644 src/ui/shot_hud/ball_selector/ball_selector.gd create mode 100644 src/ui/shot_hud/ball_selector/ball_selector.tscn diff --git a/asset_dev/balls/ball_icons.xcf b/asset_dev/balls/ball_icons.xcf new file mode 100644 index 0000000000000000000000000000000000000000..d706fed2d82bf39c244765f1c9fc206c784564ea GIT binary patch literal 16269 zcmeHOdu)_fcE8_iW;}T88P6DO!eh)MK#Um%LogWIV{fEwguqBmx+!)^co=ZkK#IMa zL{aMOKdBOR|5zdQkG3LiaH{^%tZ;UtYMW)lb6cgs7-)A}r5hz;(`}3&5MK7YZhyaf zzt0{vB)f`Ki4ZP2bI*OBbMCq49N+JM$GAWQ!E)f4KKm=eI_=y4n z-#`@sDF(0sPXlJUIQz{4d>_EXa^Ug(`?v4w*rh5=^@~d67JwbLF>ihqOWy=0gjtBE z(n2)q>)7AJ77|eQje{NAdpdT-pXuz`8~^5`o3^keth#pY+rFoxX3xP+ML&E0l8CU<<{A~Br+jn;E z-xGg$HA>IjR&on9-`U^U6Yt!=b8kmCM;zF_ySt+&zLNb5aE;#HnS2x^vsD*nP!vef zB|2ZgO-5SOuJa{I075KZq{}PZy!LHfuIto!<7S=T_M1Y9Kv@-U-`~@@y{mJ3x88pr z>TG}nR1t4_5x-g;SkF`h@bCo%_?!YfSb)b1@EHYoaRDAGz)OKs{k-G{!u2@+Vi!jy z77>?7$QKsip5E_AT|u(`u%M6vPH_3K4gmL33J3wo`U{=&aOLM+h|2thd5ek7A{Hk$ zm)IO3=T%8LuaY$jSu+zgOHiZ6En`g>H8rRyL7qTfj68{ahL9JKUkIXHi*f+@8svV| zBfsE7c`eE|@^#29)FZ!OiF#?Gu3l#632~ub<}QeN4H5)2ptaW#_(kyo>T>hgp1mLh zddqnxMo!P7XXWf%1*@r5vAUvy)Q~Drr>F$AifU3%F31hJ3X^liXB0K$eCCuaDp5n} z%$bsxlAMyElBSYt^uCPm%lMwq<6iOCLN*jh*^m&jHX>#1GL*|vUMzz6#o22J$}0T& zQNoE*`8h!&hsG+7wt zgLyFId{|c;b95%j2I{hPZ(O`sp+?P6opLZWj>yfvc5Rj#8U#U>FhO+wMrIbq7oj5C zB18l0a)qMtWPt=*8k9xLV)Su)1mt3@V@g;yvT&&gWv*h088X)*p10&8bhfx7Ev7rQykSYM z&06eIoyE~d98!pxSZkvdwz0*M5l}8OCg#-j>`{c)Qb6t^q8?Q*TWnxKK#7=uq1;k~ zsD@250AxETa*#ZPn2{OvTgw5KC?=6uMm`_JLLdVhDl@jo%L#JCF3_ktNC8BP$#<{S zirO33ERnw$A&r)pMLLh5Zx;q#y9ig&;;07M;FIzvrBIFh?2AQM3|avl$UP>or`)U- z8*TCpZj%Yh9GTsS9mHOM7Z6kzfn)-}9P~FKdLM9XL=S7QK{5+%f*xT5xvkGvpo^^a zsm>VQ;-ik%Mldf31TFc;UgjJ@XeK&huS>{o%MAYF805F0xk!|Hq(Lm8CHoB42t#@^ zF(bDssFrQH${)BxkL+Jautky88yW$tCc^9Y@g zm`y1!Ckj(h9z$irTy>~y@c>R6k2C6iQi-!TL*_R=hKft5fVNej>nxdT7wurT;Q>*l zj@Mk^RTxU$q(y*bP!3` z3|t2Sw+96w)Z3WTLLYj|V$99555V$t6hPmug8)Va+SglH7v3Lb(p91AOI)136+Qqw zu#FDihcaC|$3B3v=jtDv!UH%J+D%FDN*`~-6w{-7{+)Dk9?!GWi`k%=YkNK&E=2JY zL7nH*9pKudlLp*aOzn5pxateM@KuOg*&FP9MwpxJF3fbn)8LvW`GE zYND?Vu+TSxvJLCTIMs8hw$Sd>P<2J1a~8$~zR(iF8KhjG4|Mi)9YBPD-Qf?C=Qwu* zR~H+N@4a^xI4>ILWWGv{lNWjgDYm?zkOEF{9cSzhfO{zggn$%#N@e(|5FuHE%~}GX zFA(!_@T+jp7UOU%k>#$t{5)4lH(BT+J@;se1}MukNYQJ(2o;{LVXMA|sK>TXJ6JkO z&q?0NLDD&vgR%-xs6(pxpt=&&mxGHC>hJS29m?E|`-3Yo!}8mC%Sebx!?K-7qTbms zS)YhFwq-06w+tIKjcxrWoKv?>^tUyl#Wog+Ylh`SlBhmCG&VV5-MTh0IW}|}9g-2p zGUDPxGn{B>>mNj;YB_aEbp$@x-`0=_n{&k#!;Wq~a&l-aoz_FGrYm|vR-8PtIcgiT z#U(SGY#SI!XLDE2z0T#!$`Z-)sG@0b%$!V*474S~<}C3GPS-y;p2Oc|=k~55{Z>`a zp-mAROo7sqL*tq8!G6vf6Ib-CnS4H*zTBVm8K(G+5!rgIFM)ei%#XjhZQH=` z@p{|DriQBP)^j6^#SP5eIxwCdY>Q&($Dm|jJTEhYZRO?dXOC}ylY~O&ZaWFjwCg54> zPo!n$%p=XG&@%)6qNXL%kXj87#YZG?@&qI!oW|1`Yyzjm`kBnw0CW$RNQPw@72-p3 zJpoAymwiQOZhYv>`SB5I9j-DA%cP+w7aw7+2}M82l#&9-jlbF6+<9)2N(T+Y@vq-d z56zE>>mU(ANLVD*lD~#zL1rwl>UEb}GqUm3W5!#q17F^-j>%27~%YJ)rA;|x&Yv2M;A~ete)c>Wj5Q_Dz1n5${90)Uh+mHub&T zhwX+g^c`y}k5rXS)9aU4bE5<8srEO=Gv_;3;UL@Mzn#W+j@8>>uJ718$2ihnf3z=Y z8&;Vs8;IYQqYytV$IzLf@m!uJ6(++H?>id?Ud5TH`N83>sjaUb-V}D~4t)!nNJZpM zD1|iTKVz$}&`@QvxojpqF$#kU5`|Iv==U~4NvgXyMt#xEhYzjrRd?1v>%6_!S_Lcg z;}?E3ic|XrNH|TGpp;8sj~XKgzC%@|2=}8;9IA6d@el;*X_{7FnLYpHqAfHLIGgQ_ z*i>!h1U9V1G(`@=H^sZox|i14Y9FSwTAve)-xiC}98v?-4v$W%6NlbKFe%l0t6>;^ zY51U=`q(JOC9Pb5XPv63w|8vx^;Ld#AOg@2QfrEjV0rhRb5ADL#zHiuF=%I&BTaFY z)??yaXVV6p4(uDQVHM3cuy6h6uBzj2x^@Lj4zyOZoYAKmV`?baMhRlSNWAcb&lWc--Z=r8fJ6a9A{zM@}~R`>L)hn z&2bur6eEFKgL|70NSq!1w}l5CnJ~7 zBKE+3k7cs3%k)6FMpvFC2$hC8%fe~FOp>ovdxs{?%-9$LgNWmlZv&0og{X;*f>9XB z1!RUA3^G5|XIB`3W|&2)2fQymJI3T_%8Fo1l}q%=U&8sH85)CESBKWIm0a{7ha!1% z+$AmOlID~D3IE>$<`I=Bfxr%-mvq%vHz?AIUD6ym0v)f2BIZFrKxrUw8qZlc@cO`D zc|}-A5r(M2mdc340EhqCC6_te9Sp_|oyp==XwC%|sU&*6y?t@oan_FM`05naq9_PPI zOnC8>$qB~JpC8ZpurF8Mdg_s<-U3w;v#K}^KGBQQ;Mk}u0SOgR<_M0&4bW!Mf#|R= zUjqKPS%}p16tfXDxRGNfUn*~6JvUmME3SKCXBs2?@;XMQy@)d|t``KYX(w~tjXPDy zy6AI4-DP(OixX2pGhR~M{+DiK`?HhaMU!#?o=?~ZJ9s@Zr9J%OP{sPj zfBX4AAouP{fPQ5@GPniUQ}`vg+Ors6=B82#7y)VVU+Ns1V;xlbV#p1+ zKC2g5iQ;;Z#xVm{Cngmliu2Cpkq+AW)H6QU>0I&9luX1s=tpwmR zmgPzqPao*+?CJb&M|>9^o^^F}bFc3j^NGhzaAoon(DY?rLG2_%(`hN0_mU?F z_W&$?K{qU~@H!y(`T$+}hOV#vUpJr7`EnWtm3sY$aW7nlyV^ClyIt+V8kE=K?zdO` z6K;Ih0oLBJ2it7<=K!w{Z`FN(g_wyq&b&ph!fmvHId}`b0QC}cv0U{z_yX0g`z%C1 z-XydCB8;~b?#QJ$0yc22kx)0&Ccd%Zp@9G?CN8b-GY08=c2Do>9s4_cT4@9{0BEn* z<1@!PK(Cm@FIzld58aDB4cTinVe_>TPJb3!f>EWYUxK;x=N}2hnf~On95@w}pLvKE ze%_&-F8r)RTg^{7w7rB)qSu%DVle+q^cDcl8}I$SyZ;n$0&QCH?(cMUJ*n*(ig%AW zBQb9JvgWMBn?UC^OAE>=-~`v4)jR^+ODP}(q#E}Tt$@5~t~_H?sNT1mF0JOhzF*y? zBfwP31@3>T-uhkf?Oi)McB!YWca28_d0zUmDo_~0VXj2AHzD$r3$6+zzIlf>R3)DD z>U(it$WYEiq(%!DaPuP9dljK>8mJbl@KyJA2q9D}DS%SB0$)Z~;GIT|f-;x`LR3Mu zh{=cWxoR1nE-l5EO@4S=tR53dd^Md5XJaC-QeR?2VkJz?3iV#adlNFt{rojsy$y-W zIL0i*91miyhp=7(pTXA9+ZulYgK?ibZ~wl!d$%6WL_07Cu@&DPn24aXgYoi*QKsWr zgElYcn~T+OTFh4=;^8<-p%(r4#$vU4zc8J%-oOc1g?@ZXkwhQ94Ooe>d^5nvh;JuW z6y$u*z_QK*XlJ=djY}x)kK)A=p8et7z(Z*B-YhHw?!8r5hIa2A;Zoq!?-O_;_}0LC zpJ1TO_bE1_LcSMdIfS*n_X!fGC8Xxn?-OR=yhQNF58o#kcwH3+uAU&` zjYPPoFUM)4gOB3G@l6QdESNZX5sWcEbNXCcltNCk z9MtAGzEJ%)o_on6SAn~NwE9>8$1AOxh8mYEj06i*}gHiobC_y{2ixRXVmi+;bK zMZV9Swng{%?)Qro{=YR7=P%^~tyR&U16{j!Z13s%~>Z$>Jep>_Jlh*`*Pg)ZIznk;Fo+JT$@~U$a zp#Y!9>YU+fEr8*l&P^2}GbCoH{JV2+oX#2U70y4su3n$?6w((!f6_h8hvqAYe&mz< zl5d8<T${%7yZwoFb^B-1sz?a-=*d*O{)ID}ht)bFc^82R;Mmb8udaGVJ~B|1bc% zm2!bb-rBJTzxR}RFEr<1t?5xR=%y5Mf@|KJ)PJc#nJMK0vL^Q)T=0{~wu; DCQNIg literal 0 HcmV?d00001 diff --git a/assets/ui/ball_icons/basic_icon.png b/assets/ui/ball_icons/basic_icon.png new file mode 100644 index 0000000..4675760 --- /dev/null +++ b/assets/ui/ball_icons/basic_icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f31c37793ded0f1d51ec4dba68a5d50602af6e0b18a2f6d7e50735cd29600f70 +size 2402 diff --git a/assets/ui/ball_icons/basic_icon.png.import b/assets/ui/ball_icons/basic_icon.png.import new file mode 100644 index 0000000..3b82c5b --- /dev/null +++ b/assets/ui/ball_icons/basic_icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tancoet1lih5" +path="res://.godot/imported/basic_icon.png-bc904292cc126e1d3e1fd0eb1ba5acc2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/ui/ball_icons/basic_icon.png" +dest_files=["res://.godot/imported/basic_icon.png-bc904292cc126e1d3e1fd0eb1ba5acc2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/project.godot b/project.godot index 6382ac2..2e9c7e3 100644 --- a/project.godot +++ b/project.godot @@ -182,6 +182,16 @@ pause={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +ball_next={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +ball_previous={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} [layer_names] diff --git a/src/equipment/balls/physics_ball/game_ball.gd b/src/equipment/balls/physics_ball/game_ball.gd index 86a2b65..aaec4f7 100644 --- a/src/equipment/balls/physics_ball/game_ball.gd +++ b/src/equipment/balls/physics_ball/game_ball.gd @@ -4,6 +4,13 @@ class_name GameBall extends RigidBody3D ## Fired as soon as this ball enters a water hazard signal entered_water +## Types of game balls +enum Type { + NONE, + BASIC, + PLASMA, +} + const TERRAIN_DAMPING_EPSILON := 1e-6 const IRON_DAMPING := 9999.0 diff --git a/src/player/debug_player.tres b/src/player/debug_player.tres index 8eaef6f..9083234 100644 --- a/src/player/debug_player.tres +++ b/src/player/debug_player.tres @@ -8,9 +8,14 @@ [resource] script = ExtResource("4_8ybyj") -name = "DEBUG Gfolfer" life = 100.0 +name = "DEBUG Gfolfer" +color = Color(1, 0.439216, 0.439216, 1) driver = ExtResource("1_sn8fd") iron = ExtResource("2_piku2") wedge = ExtResource("5_wm4ae") putter = ExtResource("3_tytwr") +_balls = { +1: -1, +2: 5 +} diff --git a/src/player/shot_setup/ball_point.gd b/src/player/shot_setup/ball_point.gd index a82e423..318f2d6 100644 --- a/src/player/shot_setup/ball_point.gd +++ b/src/player/shot_setup/ball_point.gd @@ -4,17 +4,10 @@ class_name BallPoint extends Node3D ## Emitted when a new ball is placed. signal ball_changed(ball: GameBall) -## Types of game balls -enum Type { - NONE, - BASIC, - PLASMA, -} - ## Scenes for each type of ball. const SCENE_MAP: Dictionary = { - Type.BASIC: preload("res://src/equipment/balls/physics_ball/physics_ball.tscn"), - Type.PLASMA: preload("res://src/equipment/balls/plasma_ball/plasma_ball.tscn"), + GameBall.Type.BASIC: preload("res://src/equipment/balls/physics_ball/physics_ball.tscn"), + GameBall.Type.PLASMA: preload("res://src/equipment/balls/plasma_ball/plasma_ball.tscn"), } @export var ball: GameBall: @@ -25,7 +18,7 @@ const SCENE_MAP: Dictionary = { ## Get a new instance of a ball of the given type. ## Returns null if the type can't be instantiated (e.g. NONE type) -func get_instance(type: Type) -> GameBall: +func get_instance(type: GameBall.Type) -> GameBall: if type in SCENE_MAP: var scene: PackedScene = SCENE_MAP.get(type) return scene.instantiate() as GameBall @@ -33,7 +26,7 @@ func get_instance(type: Type) -> GameBall: ## Clear any existing ball, instantiate a new one of the given type, and place it at the ball point. -func spawn_ball(type: Type) -> void: +func spawn_ball(type: GameBall.Type) -> void: # Clear existing ball if is_instance_valid(ball): ball.queue_free() diff --git a/src/player/shot_setup/shot_setup.gd b/src/player/shot_setup/shot_setup.gd index 3c19d36..e4a4082 100644 --- a/src/player/shot_setup/shot_setup.gd +++ b/src/player/shot_setup/shot_setup.gd @@ -42,7 +42,7 @@ const WATER_DAMAGE := 10.0 @export var initial_club: Club.Type = Club.Type.DRIVER ## Initially-selected ball type -@export var initial_ball: BallPoint.Type = BallPoint.Type.BASIC +@export var initial_ball: GameBall.Type = GameBall.Type.BASIC @export_category("Shot Parameters") @export var base_power := 2.5 @@ -89,9 +89,10 @@ var club: Club.Type: _on_club_change(value) club = value -var ball_type: BallPoint.Type: +var ball_type: GameBall.Type: set(value): if value != ball_type: + hud.ball_selector.value = value ball_point.spawn_ball(value) ball_type = value @@ -237,6 +238,10 @@ func take_shot() -> void: game_ball.freeze = false game_ball.apply_central_impulse(impulse) + # Use a ball if a limited type is selected + if player.get_balls(ball_type) > 0: + player.mutate_balls(ball_type, -1) + ## Make the shot projection widget visible, with animated transition func _show_shot_projection() -> void: @@ -486,6 +491,12 @@ func _process(delta: float) -> void: if Input.is_action_just_pressed("select_putter"): club = Club.Type.PUTTER + # Ball select + if Input.is_action_just_pressed("ball_next"): + ball_type = player.next_ball(ball_type) + if Input.is_action_just_pressed("ball_previous"): + ball_type = player.prev_ball(ball_type) + # Switch to free cam if ( Input.is_action_just_pressed("camera_back") @@ -497,7 +508,10 @@ func _process(delta: float) -> void: # Advance to next phase if Input.is_action_just_pressed("shot_accept"): - phase = Phase.POWER_ADJUST + if player.get_balls(ball_type) != 0: + # Check that player has enough of the selected ball (<0 means unlimited) + phase = Phase.POWER_ADJUST + # TODO play UI bonk if player doesn't have balls (lmao) Phase.POWER_ADJUST: if Input.is_action_just_pressed("shot_accept"): # TODO set power gauge parameters if needed diff --git a/src/player/shot_setup/shot_setup.tscn b/src/player/shot_setup/shot_setup.tscn index 632bea7..8ba1607 100644 --- a/src/player/shot_setup/shot_setup.tscn +++ b/src/player/shot_setup/shot_setup.tscn @@ -323,14 +323,13 @@ visible = false transform = Transform3D(0.2, 0, 0, 0, -1.74846e-08, 0.2, 0, -0.4, -8.74228e-09, 0, 0, -1) loop_animation = 1 -[node name="ShotProjection" parent="ArrowPivot" node_paths=PackedStringArray("excluded_bodies") instance=ExtResource("4_ry2ho")] +[node name="ShotProjection" parent="ArrowPivot" instance=ExtResource("4_ry2ho")] unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 0.707107, -0.707107, 0, 0.707107, 0.707107, 0, -0.02, 0) visible = false initial_speed = 50.0 time_step = 0.01 max_steps = 800 -excluded_bodies = [null] [node name="ProjectedTarget" parent="ArrowPivot/ShotProjection" instance=ExtResource("6_mynqj")] diff --git a/src/player/world_player.gd b/src/player/world_player.gd index ace2d5d..0b6df77 100644 --- a/src/player/world_player.gd +++ b/src/player/world_player.gd @@ -2,6 +2,7 @@ class_name WorldPlayer extends Resource ## Container for the state of the player within the world. signal on_life_changed(new_value: float) +signal on_balls_changed(type: GameBall.Type, new_value: int) @export_range(0, 100) var life: float = 100.0: set(value): @@ -21,6 +22,13 @@ signal on_life_changed(new_value: float) @export var putter: Club @export var special: Club +## Count of each type of ball the player possesses. +## A count of less than zero indicates unlimited quantity. +@export var _balls := { + GameBall.Type.BASIC: -1, + GameBall.Type.PLASMA: -1, +} + # TODO balls, pickups, etc var shot_setup: ShotSetup: @@ -48,6 +56,39 @@ func get_club(type: Club.Type) -> Club: return null +## Get the quantity of a ball type +func get_balls(type: GameBall.Type) -> int: + return _balls.get(type, 0) + + +## Set the quantity of a ball type +func set_balls(type: GameBall.Type, value: int) -> void: + _balls[type] = value + on_balls_changed.emit(type, value) + + +## Change the quantity of a ball type +func mutate_balls(type: GameBall.Type, delta: int) -> void: + _balls[type] = _balls.get(type, 0) + delta + on_balls_changed.emit(type, _balls[type]) + + +## Get next slotted ball type +func next_ball(type: GameBall.Type) -> GameBall.Type: + var keys := _balls.keys() + var i := keys.find(type) + var j := (i + 1) % len(keys) if i >= 0 else 0 + return keys[j] + + +## Get previous slotted ball type +func prev_ball(type: GameBall.Type) -> GameBall.Type: + var keys := _balls.keys() + var i := keys.find(type) + var j := (i - 1) % len(keys) if i >= 0 else 0 + return keys[j] + + ## Create a debug player instance static func create_debug() -> WorldPlayer: var instance := WorldPlayer.new() diff --git a/src/ui/main_theme.tres b/src/ui/main_theme.tres index 5d28770..3adf3ae 100644 --- a/src/ui/main_theme.tres +++ b/src/ui/main_theme.tres @@ -28,6 +28,11 @@ PauseMenuButton/colors/font_outline_color = Color(0, 0, 0, 1) PauseMenuButton/constants/outline_size = 6 PauseMenuButton/font_sizes/font_size = 32 PauseMenuButton/fonts/font = ExtResource("2_5ty6u") +QuantityLabel/base_type = &"Label" +QuantityLabel/colors/font_color = Color(0.819608, 0.196078, 0.196078, 1) +QuantityLabel/colors/font_outline_color = Color(1, 0.901961, 0.509804, 1) +QuantityLabel/constants/outline_size = 6 +QuantityLabel/font_sizes/font_size = 22 ShotFeedback/base_type = &"RichTextLabel" ShotFeedback/colors/font_shadow_color = Color(0, 0, 0, 1) ShotFeedback/constants/shadow_offset_x = 6 diff --git a/src/ui/shot_hud/ball_selector/ball_icon.gd b/src/ui/shot_hud/ball_selector/ball_icon.gd new file mode 100644 index 0000000..e363aaf --- /dev/null +++ b/src/ui/shot_hud/ball_selector/ball_icon.gd @@ -0,0 +1,51 @@ +@tool +class_name BallIcon extends Control +## HUD icon for a ball type + +const BASE_MODULATE := Color.WHITE +const EMPTY_MODULATE := Color.DIM_GRAY + +@export var text: String: + set(value): + text = value + if ball_label: + ball_label.text = value + +@export var quantity: int: + set = _set_quantity +@export var empty: bool: + set(value): + if value: + quantity = 0 + elif not quantity: + quantity = 1 + get: + return quantity == 0 +@export var unlimited: bool: + set(value): + quantity = -1 if value else 0 + get: + return quantity < 0 + +@onready var ball_label: Label = %BallLabel +@onready var quantity_container: HBoxContainer = %QuantityContainer +@onready var quantity_label: Label = %QuantityLabel + + +func _ready() -> void: + text = text + quantity = quantity + + +func _set_quantity(value: int) -> void: + # TODO play effect on gain/loss + quantity = value + if not quantity_label: + return + quantity_label.text = str(quantity) + + # Quantity is not visible if we're empty or unlimited + quantity_container.visible = quantity > 0 + + # Grey out when empty + ball_label.modulate = EMPTY_MODULATE if quantity == 0 else BASE_MODULATE diff --git a/src/ui/shot_hud/ball_selector/ball_icon.tscn b/src/ui/shot_hud/ball_selector/ball_icon.tscn new file mode 100644 index 0000000..7a92367 --- /dev/null +++ b/src/ui/shot_hud/ball_selector/ball_icon.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=3 format=3 uid="uid://cc8a55ly7ybhy"] + +[ext_resource type="Script" path="res://src/ui/shot_hud/ball_selector/ball_icon.gd" id="1_p0p8j"] +[ext_resource type="FontFile" uid="uid://b6gxwgomstkgu" path="res://assets/fonts/Geo/Geo-Italic.ttf" id="2_bu42i"] + +[node name="BallIcon" type="Control"] +custom_minimum_size = Vector2(0, 27) +layout_mode = 3 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 4 +script = ExtResource("1_p0p8j") +quantity = -1 +unlimited = true + +[node name="BallLabel" type="Label" parent="."] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -46.5 +offset_top = -13.5 +offset_right = 46.5 +offset_bottom = 13.5 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/outline_size = 10 +theme_override_fonts/font = ExtResource("2_bu42i") +theme_override_font_sizes/font_size = 25 +horizontal_alignment = 2 + +[node name="QuantityContainer" type="HBoxContainer" parent="BallLabel"] +unique_name_in_owner = true +visible = false +layout_mode = 1 +anchors_preset = -1 +anchor_left = 1.0 +anchor_top = 0.8 +anchor_right = 1.0 +anchor_bottom = 0.8 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 0 + +[node name="Label" type="Label" parent="BallLabel/QuantityContainer"] +layout_mode = 2 +size_flags_horizontal = 8 +size_flags_vertical = 8 +theme_type_variation = &"QuantityLabel" +theme_override_font_sizes/font_size = 22 +text = "×" + +[node name="QuantityLabel" type="Label" parent="BallLabel/QuantityContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 8 +size_flags_vertical = 8 +theme_type_variation = &"QuantityLabel" +theme_override_font_sizes/font_size = 22 +text = "-1" diff --git a/src/ui/shot_hud/ball_selector/ball_selector.gd b/src/ui/shot_hud/ball_selector/ball_selector.gd new file mode 100644 index 0000000..bd7d292 --- /dev/null +++ b/src/ui/shot_hud/ball_selector/ball_selector.gd @@ -0,0 +1,54 @@ +@tool +class_name BallSelector extends Control + +const TWEEN_TIME := 0.2 + +const Y_OFFSET := { + GameBall.Type.NONE: 50, + GameBall.Type.BASIC: 18, + GameBall.Type.PLASMA: -14, +} + +@export var value: GameBall.Type: + set = _set_value + +@onready var ball_list: VBoxContainer = %BallList +@onready var basic_icon: BallIcon = %BasicIcon +@onready var plasma_icon: BallIcon = %PlasmaIcon + + +func _ready() -> void: + value = value + + +## Set ball quantities based on player's state +func set_state_for_player(player: WorldPlayer) -> void: + basic_icon.quantity = player.get_balls(GameBall.Type.BASIC) + plasma_icon.quantity = player.get_balls(GameBall.Type.PLASMA) + if player.shot_setup: + value = player.shot_setup.ball_type + player.on_balls_changed.connect(_set_quantity) + + +func _get_icon(type: GameBall.Type) -> BallIcon: + match type: + GameBall.Type.BASIC: + return basic_icon + GameBall.Type.PLASMA: + return plasma_icon + return null + + +func _set_value(new_value: GameBall.Type) -> void: + if not ball_list: + return + + var tween := get_tree().create_tween() + tween.tween_property(ball_list, "position:y", Y_OFFSET[new_value], TWEEN_TIME).set_trans( + Tween.TRANS_EXPO + ) + value = new_value + + +func _set_quantity(type: GameBall.Type, new_value: int) -> void: + _get_icon(type).quantity = new_value diff --git a/src/ui/shot_hud/ball_selector/ball_selector.tscn b/src/ui/shot_hud/ball_selector/ball_selector.tscn new file mode 100644 index 0000000..d8336f8 --- /dev/null +++ b/src/ui/shot_hud/ball_selector/ball_selector.tscn @@ -0,0 +1,66 @@ +[gd_scene load_steps=5 format=3 uid="uid://b0yr0w0xv8cm5"] + +[ext_resource type="PackedScene" uid="uid://cc8a55ly7ybhy" path="res://src/ui/shot_hud/ball_selector/ball_icon.tscn" id="1_1v8xg"] +[ext_resource type="Script" path="res://src/ui/shot_hud/ball_selector/ball_selector.gd" id="1_b7gkp"] + +[sub_resource type="Gradient" id="Gradient_3vqut"] +interpolation_mode = 2 +offsets = PackedFloat32Array(0, 0.382, 0.618, 1) +colors = PackedColorArray(0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0) + +[sub_resource type="GradientTexture2D" id="GradientTexture2D_tm61r"] +gradient = SubResource("Gradient_3vqut") +fill_to = Vector2(0, 1) +metadata/_snap_enabled = true + +[node name="BallSelector" type="Control"] +custom_minimum_size = Vector2(120, 27) +layout_mode = 3 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -60.0 +offset_top = -13.5 +offset_right = 60.0 +offset_bottom = 13.5 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_b7gkp") + +[node name="GradientMask" type="TextureRect" parent="."] +clip_children = 1 +custom_minimum_size = Vector2(120, 32) +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = SubResource("GradientTexture2D_tm61r") + +[node name="BallList" type="VBoxContainer" parent="GradientMask"] +unique_name_in_owner = true +custom_minimum_size = Vector2(120, 0) +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 50.0 +offset_bottom = 50.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 5 + +[node name="BasicIcon" parent="GradientMask/BallList" instance=ExtResource("1_1v8xg")] +unique_name_in_owner = true +custom_minimum_size = Vector2(120, 27) +layout_mode = 2 +text = "BASIC" + +[node name="PlasmaIcon" parent="GradientMask/BallList" instance=ExtResource("1_1v8xg")] +unique_name_in_owner = true +custom_minimum_size = Vector2(120, 27) +layout_mode = 2 +text = "PLASMA" diff --git a/src/ui/shot_hud/shot_hud.gd b/src/ui/shot_hud/shot_hud.gd index 463e126..4098c8e 100644 --- a/src/ui/shot_hud/shot_hud.gd +++ b/src/ui/shot_hud/shot_hud.gd @@ -13,6 +13,7 @@ var player: WorldPlayer @onready var life_bar: TextureProgressBar = %LifeBar @onready var club_selector: ClubSelector = %ClubSelector +@onready var ball_selector: BallSelector = %BallSelector @onready var _curve_animation: AnimationPlayer = %CurveAnimation @onready var _power_animation: AnimationPlayer = %PowerAnimation @@ -32,6 +33,7 @@ static var scene: PackedScene = preload("res://src/ui/shot_hud/shot_hud.tscn") func _ready() -> void: club_selector.set_state_for_player(player) + ball_selector.set_state_for_player(player) _player_name.text = player.name life_bar.value = player.life life_bar.tint_progress = player.color diff --git a/src/ui/shot_hud/shot_hud.tscn b/src/ui/shot_hud/shot_hud.tscn index ef91dfc..c0359af 100644 --- a/src/ui/shot_hud/shot_hud.tscn +++ b/src/ui/shot_hud/shot_hud.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=32 format=3 uid="uid://c4ifdiohng830"] +[gd_scene load_steps=34 format=3 uid="uid://c4ifdiohng830"] [ext_resource type="Script" path="res://src/ui/shot_hud/shot_hud.gd" id="1_x5b4c"] [ext_resource type="Shader" path="res://src/shaders/canvas_retro.gdshader" id="1_ybxxp"] @@ -7,7 +7,9 @@ [ext_resource type="Texture2D" uid="uid://b5812y3pmmgg5" path="res://assets/ui/gauge_patch.png" id="4_5kcpe"] [ext_resource type="Texture2D" uid="uid://76fjx2ukavqe" path="res://assets/ui/power_gauge_fill.png" id="5_3i1yq"] [ext_resource type="Texture2D" uid="uid://4a8tvjgwegv3" path="res://assets/ui/power_gauge_tab.png" id="6_sw48q"] +[ext_resource type="PackedScene" uid="uid://b0yr0w0xv8cm5" path="res://src/ui/shot_hud/ball_selector/ball_selector.tscn" id="8_b2302"] [ext_resource type="FontFile" uid="uid://dsa0oh7c0h4pu" path="res://assets/fonts/Racing_Sans_One/RacingSansOne-Regular.ttf" id="8_bejx4"] +[ext_resource type="Texture2D" uid="uid://tancoet1lih5" path="res://assets/ui/ball_icons/basic_icon.png" id="8_tt8i3"] [ext_resource type="PackedScene" uid="uid://dmciuk3pbjsae" path="res://src/ui/shot_hud/life_bar/life_bar.tscn" id="9_w1fiw"] [sub_resource type="Animation" id="Animation_3xds6"] @@ -490,35 +492,6 @@ tracks/1/keys = { "values": [false] } -[sub_resource type="Animation" id="Animation_nicro"] -resource_name = "show_life_bar" -length = 0.4 -step = 0.02 -tracks/0/type = "value" -tracks/0/imported = false -tracks/0/enabled = true -tracks/0/path = NodePath("SouthWest:position") -tracks/0/interp = 2 -tracks/0/loop_wrap = true -tracks/0/keys = { -"times": PackedFloat32Array(0, 0.34, 0.4), -"transitions": PackedFloat32Array(1.618, 1.618, 1), -"update": 0, -"values": [Vector2(0, 1100), Vector2(0, 960), Vector2(0, 982)] -} -tracks/1/type = "value" -tracks/1/imported = false -tracks/1/enabled = true -tracks/1/path = NodePath("SouthWest:visible") -tracks/1/interp = 1 -tracks/1/loop_wrap = true -tracks/1/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [true] -} - [sub_resource type="Animation" id="Animation_jugqx"] resource_name = "peek" length = 2.4 @@ -548,6 +521,35 @@ tracks/1/keys = { "values": [true, false] } +[sub_resource type="Animation" id="Animation_nicro"] +resource_name = "show_life_bar" +length = 0.4 +step = 0.02 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("SouthWest:position") +tracks/0/interp = 2 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.34, 0.4), +"transitions": PackedFloat32Array(1.618, 1.618, 1), +"update": 0, +"values": [Vector2(0, 1100), Vector2(0, 960), Vector2(0, 982)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("SouthWest:visible") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [true] +} + [sub_resource type="AnimationLibrary" id="AnimationLibrary_c3i4w"] _data = { "RESET": SubResource("Animation_3cn2c"), @@ -756,15 +758,30 @@ libraries = { "": SubResource("AnimationLibrary_5nauw") } +[node name="TextureRect" type="TextureRect" parent="ClubSelector"] +texture_filter = 1 +layout_mode = 0 +offset_right = 128.0 +offset_bottom = 128.0 +texture = ExtResource("8_tt8i3") + +[node name="BallSelector" parent="ClubSelector/TextureRect" instance=ExtResource("8_b2302")] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = -1 +anchor_left = 0.7 +anchor_right = 0.7 +offset_right = 108.0 +grow_horizontal = 1 + [node name="SouthWest" type="MarginContainer" parent="."] layout_mode = 1 anchors_preset = -1 anchor_top = 1.0 anchor_right = 0.333 anchor_bottom = 1.0 -offset_top = 20.0 +offset_top = -98.0 offset_right = 40.0 -offset_bottom = 118.0 grow_vertical = 0 theme_override_constants/margin_left = 16 theme_override_constants/margin_bottom = 16