From 10eb2807b864753ab1ea3ee573f00ee2106c65ab Mon Sep 17 00:00:00 2001 From: Markus Sauermann <6299227+Sauermann@users.noreply.github.com> Date: Thu, 2 Oct 2025 07:56:48 +0200 Subject: [PATCH] Add Split Screen Demo showing input handling (#1023) * Add Split Screen Demo showing input handling * Style fixes, layout improvements, update to Godot 4.5 --------- Co-authored-by: Aaron Franke --- viewport/split_screen_input/README.md | 17 ++++ viewport/split_screen_input/icon.svg | 1 + viewport/split_screen_input/icon.svg.import | 43 +++++++++ viewport/split_screen_input/player.gd | 28 ++++++ viewport/split_screen_input/player.gd.uid | 1 + viewport/split_screen_input/project.godot | 88 ++++++++++++++++++ viewport/split_screen_input/root.gd | 46 +++++++++ viewport/split_screen_input/root.gd.uid | 1 + .../split_screen_input/screenshots/.gdignore | 0 .../screenshots/split_screen_input.webp | Bin 0 -> 10234 bytes viewport/split_screen_input/split_screen.gd | 40 ++++++++ .../split_screen_input/split_screen.gd.uid | 1 + viewport/split_screen_input/split_screen.tscn | 42 +++++++++ .../split_screen_input/split_screen_demo.tscn | 73 +++++++++++++++ .../sub_viewport_container.gd | 27 ++++++ .../sub_viewport_container.gd.uid | 1 + 16 files changed, 409 insertions(+) create mode 100644 viewport/split_screen_input/README.md create mode 100644 viewport/split_screen_input/icon.svg create mode 100644 viewport/split_screen_input/icon.svg.import create mode 100644 viewport/split_screen_input/player.gd create mode 100644 viewport/split_screen_input/player.gd.uid create mode 100644 viewport/split_screen_input/project.godot create mode 100644 viewport/split_screen_input/root.gd create mode 100644 viewport/split_screen_input/root.gd.uid create mode 100644 viewport/split_screen_input/screenshots/.gdignore create mode 100644 viewport/split_screen_input/screenshots/split_screen_input.webp create mode 100644 viewport/split_screen_input/split_screen.gd create mode 100644 viewport/split_screen_input/split_screen.gd.uid create mode 100644 viewport/split_screen_input/split_screen.tscn create mode 100644 viewport/split_screen_input/split_screen_demo.tscn create mode 100644 viewport/split_screen_input/sub_viewport_container.gd create mode 100644 viewport/split_screen_input/sub_viewport_container.gd.uid diff --git a/viewport/split_screen_input/README.md b/viewport/split_screen_input/README.md new file mode 100644 index 00000000..7c6bd52d --- /dev/null +++ b/viewport/split_screen_input/README.md @@ -0,0 +1,17 @@ +# Split Screen Input + +A demo showing a Split Screen GUI and input handling for local multiplayer using viewports. + +It demonstrates: +- Single World2D, that is shared among many Viewports +- Simplified Input Map, that uses the same Actions for all Split Screens +- Input event routing to different viewports based on joypad device id and dedicated keyboard keys +- Dynamic keybinding adjustment for each Split Screen + +Language: GDScript + +Renderer: Compatibility + +## Screenshots + +![Screenshot](screenshots/split_screen_input.webp) diff --git a/viewport/split_screen_input/icon.svg b/viewport/split_screen_input/icon.svg new file mode 100644 index 00000000..b370ceb7 --- /dev/null +++ b/viewport/split_screen_input/icon.svg @@ -0,0 +1 @@ + diff --git a/viewport/split_screen_input/icon.svg.import b/viewport/split_screen_input/icon.svg.import new file mode 100644 index 00000000..fee5a484 --- /dev/null +++ b/viewport/split_screen_input/icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ci5b7o7h2bmj0" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/viewport/split_screen_input/player.gd b/viewport/split_screen_input/player.gd new file mode 100644 index 00000000..985be5d0 --- /dev/null +++ b/viewport/split_screen_input/player.gd @@ -0,0 +1,28 @@ +class_name Player +extends CharacterBody2D +## Player implementation. + +const factor: float = 200.0 # Factor to multiply the movement. + +var _movement: Vector2 = Vector2(0, 0) # Current movement rate of node. + + +# Update movement variable based on input that reaches this SubViewport. +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed("ux_up") or event.is_action_released("ux_down"): + _movement.y -= 1 + get_viewport().set_input_as_handled() + elif event.is_action_pressed("ux_down") or event.is_action_released("ux_up"): + _movement.y += 1 + get_viewport().set_input_as_handled() + elif event.is_action_pressed("ux_left") or event.is_action_released("ux_right"): + _movement.x -= 1 + get_viewport().set_input_as_handled() + elif event.is_action_pressed("ux_right") or event.is_action_released("ux_left"): + _movement.x += 1 + get_viewport().set_input_as_handled() + + +# Move the node based on the content of the movement variable. +func _physics_process(delta: float) -> void: + move_and_collide(_movement * factor * delta) diff --git a/viewport/split_screen_input/player.gd.uid b/viewport/split_screen_input/player.gd.uid new file mode 100644 index 00000000..bcad9705 --- /dev/null +++ b/viewport/split_screen_input/player.gd.uid @@ -0,0 +1 @@ +uid://b7p2tpqjka6jq diff --git a/viewport/split_screen_input/project.godot b/viewport/split_screen_input/project.godot new file mode 100644 index 00000000..134bcca3 --- /dev/null +++ b/viewport/split_screen_input/project.godot @@ -0,0 +1,88 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Split Screen Input" +config/tags=PackedStringArray("demo", "input", "official", "rendering") +run/main_scene="res://split_screen_demo.tscn" +config/features=PackedStringArray("4.5") +config/icon="res://icon.svg" + +[display] + +window/size/viewport_width=800 +window/size/viewport_height=800 +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" + +[input] + +ui_left={ +"deadzone": 0.2, +"events": [] +} +ui_right={ +"deadzone": 0.2, +"events": [] +} +ui_up={ +"deadzone": 0.2, +"events": [] +} +ui_down={ +"deadzone": 0.2, +"events": [] +} +ux_left={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null) +, 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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +, 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":74,"key_label":0,"unicode":106,"location":0,"echo":false,"script":null) +, 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":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, 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":4194442,"key_label":0,"unicode":52,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null) +] +} +ux_right={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null) +, 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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +, 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":76,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null) +, 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":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, 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":4194444,"key_label":0,"unicode":54,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null) +] +} +ux_up={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null) +, 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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +, 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":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null) +, 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":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, 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":4194446,"key_label":0,"unicode":56,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null) +] +} +ux_down={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null) +, 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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +, 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":75,"key_label":0,"unicode":107,"location":0,"echo":false,"script":null) +, 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":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, 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":4194443,"key_label":0,"unicode":53,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null) +] +} + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" diff --git a/viewport/split_screen_input/root.gd b/viewport/split_screen_input/root.gd new file mode 100644 index 00000000..cb2bffe9 --- /dev/null +++ b/viewport/split_screen_input/root.gd @@ -0,0 +1,46 @@ +## Set up different Split Screens +## Provide Input configuration +## Connect Split Screens to Play Area +extends Node + + +const KEYBOARD_OPTIONS: Dictionary[String, Dictionary] = { + "wasd": {"keys": [KEY_W, KEY_A, KEY_S, KEY_D]}, + "ijkl": {"keys": [KEY_I, KEY_J, KEY_K, KEY_L]}, + "arrows": {"keys": [KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN]}, + "numpad": {"keys": [KEY_KP_4, KEY_KP_5, KEY_KP_6, KEY_KP_8]}, +} # 4 keyboard sets for moving players around. + +const PLAYER_COLORS: Array[Color] = [ + Color.WHITE, + Color("ff8f02"), + Color("05ff5a"), + Color("ff05a0") +] # Modulate Colors of each Player. + + +var config: Dictionary = { + "keyboard": KEYBOARD_OPTIONS, + "joypads": 4, + "world": null, + "position": Vector2(), + "index": -1, + "color": Color(), +} # Split Screen configuration Dictionary. + +@onready var play_area: SubViewport = $PlayArea # The central Viewport, all Split Screens are sharing. + + +# Initialize each Split Screen and each player node. +func _ready() -> void: + config["world"] = play_area.world_2d + var children: Array[Node] = get_children() + var index: int = 0 + for child: Node in children: + if child is SplitScreen: + config["position"] = Vector2(index % 2, floor(index / 2.0)) * 132 + Vector2(132, 0) + config["index"] = index + config["color"] = PLAYER_COLORS[index] + var split_child: SplitScreen = child as SplitScreen + split_child.set_config(config) + index += 1 diff --git a/viewport/split_screen_input/root.gd.uid b/viewport/split_screen_input/root.gd.uid new file mode 100644 index 00000000..c42d47f5 --- /dev/null +++ b/viewport/split_screen_input/root.gd.uid @@ -0,0 +1 @@ +uid://bdikev0kwlu1g diff --git a/viewport/split_screen_input/screenshots/.gdignore b/viewport/split_screen_input/screenshots/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/viewport/split_screen_input/screenshots/split_screen_input.webp b/viewport/split_screen_input/screenshots/split_screen_input.webp new file mode 100644 index 0000000000000000000000000000000000000000..4e9abe71fbb153b5638cf2b35cd4b501f2402fbc GIT binary patch literal 10234 zcma)hRa6|#&vubQaV_qy#VN%p?(XigxVv_7mmgBx-CKiR&7zpaBjkkO0^vCz}Y z=bJ5Gxx-rXeUpR!SEKh&bwJq5-Nrb3{n6*Pj$zaQ_GSeMM2(+Pw}w!Wz@Q}nrkL#C zMvBHQN`4tkIG8OSVOZdo9TEbZM12XlPu3x90joWV`Ffbl_22#32$YA~182*sXKawn zPN&TTxv){gcFnUfSK=cF=DDpfuMzh=t#VzmNYGo9udlKte-W!%z>MnRdD$@5>~U$R=kBylYs~9Igso#zrD- z-GIqvTPfN0uP*<4SM$aFjrF7Rgc3Mf5`nzpU|1#3reWkcQ`>m{$saq8#Ua6f9FvDqmLFEc1#Q^!R+Hx} zvxFMq(1D+fHd9Kt+}}XBj2v*&5HvpfpM8gefl#a*j^p0p0SOrw$)WxGyOS<%1ye90 z@i0t&y$j|{kWRCMq(kcgVdSVBRICy2?~=dwQtPP8n*AC{#wW~tQJbc^%LI*ZY*NHe zb$7;W3Le07-XDEw?d|*$w|RK>`kJS9tP(IhC&GN?`3|&IH)1QR_pJ`8XDo(gSy_%L zM-E8)Z%#3fFe<4QyTyS)+wao1#@IBnu50t3{NTt2r(h}Yoe#4koYq=T=35<@QIGL& z^}E0;L<_Oc<|w!C@1Xbz$vq8t2MCQRzT|BlHnnW%8iQY{b*nUu^NC~(Z2EvvRr#>; zShvM{K+lDrO**`2@How=*Y{pNFeX+zn{^X19bbyYy0XvIGMrH^(|Mo6p5bmA}B z3uFsk^wQ_k{234|k9qm}8ELKkkRC&#|Mk3hNO?VBOmz5~ChTAp*Ig>_wA6cXjm|2RG{LwEIoiS zad2?m8u3nh);0^rqP~IOibLIdto>D)X!@`<=1D|O{pQV zdO_^*9i<48TiA_VRP=hnL;wj@E}S;jFzJfDh^HXj5SsoV4P1cobl)}-PR$HE|1{?6 zh0aFtf5xd@9y#Lrp82P+*MxaAyQ#uZF0lW3Q00L@vdODwpy0-Y-}o06Qpo-lA9~~a z{|~)gh>+9$K6ZSb(^W^?{VM{q=!fzJQpmG*)$X7i+|o)2yNCmbEYS>NJ4i)Nw?>=I z6=&hN7tAW4t9?tT@a0!&3QxhcGgAcx!YcmjxpNHw_xJ}}q0MpON*U;r$ep2>I$WLA zj6ST(Yg?^p^?@eV@B8ZK*oJ2ocUJI6+8SQ_B~9 zdyH`?A@i}R_PrpYE^>anWnF@W2D`iyKG)9K>5iR+*&&RpPW`-* zfIYTkkZ6s;4;E}m0o--a5&=pDqbYZv_L*BiY!p|X09?TmZbhe5R3D##DsD9Fybw>` z@z(V0tpr_CrvNB}Grqb$RFjk!s?Zz3VokYz{Q^t&d zCi{RvTe#v($$aKg#q9zo*6I;NW-Y9`VVw_BiVSJWzl$vCN>bc8l{MaBWJ+{oE(6-Dbt{TJg|#u1iY!O zTzeDBGr~G4JeQOUy__+_&h6n(w{T_2p{gfx%vtMk%G#E3AsIZ$O?o{lg!Q6Bqlijg zF!Vjg)>ZVG#C4siZ4IP>F$%{pws%dMy})BDL) z+fRcA77iGeUO3S^%a<#f%`6#@RrO*k&sL&~huT;;*tN0LE9>yZ6Mw6*xFSR3&H5;* z{4Qcgh_I};ds|#i3|Daa@m<1#aho}wG(SVfsDb<8HYq$4)J4qQH~PE8J5-=82CPK1rUKVYntcA`3Azq1olH%gUp=o^5v zQW0ARWIxj~&)&u&>!w%ic(>5KTt)mQboCN z^lwT>R?9Y0B!AJ}4jPSpUM^AA4fCXBRaGAdq#1L}2qzQ_)|;4yT3TAUQ!=`IhVNU4 zP~nk{s1SLcTX96&$EC#5$`wXro=x0az$qAp9qbCeXCxS8c@!ow@@1`XCcJRqt7yX9 zh!~b{%Stf& z=M&+5l|7K67TKtXwz=CusFU|`)WT$`2rtR7RM@^!!m7Je88Av+Y9ho;Ka>iivWpA{ z@JU;vg~Y2Em<_Rm`x!nX`*FtGbX>8Q{uEF{s}wd1F_Z0AiIT_l5(O8Tc2I%7aBr7* zTD>7JY<;zC4;g#Y&<&V^kcVn~^@juSP^;3YaEkTBbM4WkxjSBOx+ebC;2bt&?<%4+sT6+G>q>;U5}mc(ZF&m-3~q)e89DG8$b+k%?qkGM7&)CVXY9ElkB zAS5u{`Ln;}NCDH3jF#66)({iN&Yp2)`u@$BV<2~;ffPPQ;kjOBqx%8haC&>&nc#F%>kvBU-u4V<)mQ_!&P-GVt?=* zeGuJxN;!@%zp^CN6J2pzAe|ih`wMwtl_e0ei1^xG-m_!d9O7XW4gKFEW#{Gszh|0^;lE~Ki#A}7B z9$;5BB{_v-nLx$4UdxKVh1YMq2N7KqMnn#0gA*`e)mhm1U__wf`d6QwG{T`2kiUEz zD~0+uU*j{?HFw_yn(ob8K`eiZJ%fPqN^u;P*@kzy`ile?L1^YzJ>9CG9mtlV0bdFl zWDCd13B+YI>GsromQ8LKrKxZCOvuSmHsdfKP{}s@k`GRQ(;v?LVt zeA&l_>woqxcPeA?1%N2=0!KEgYkrK0<&j|y z-&6L&9{P1#!PUNlB|Oz(M7uT}T>^wVzj)sM%H-L>_*H%ZWi6FO=$a$Xb69qwEk&a8 zV$GFB&H3~AVhLwG1K&4)@s2l+7kVfl;@`U4{!WbqZ#R$fpsKKw@KpF{FSi(J=A;w% z)c4tHr^pIzox+9iN+z|V8F}5?xDY~%!mt~FmGhz-LJUnm{Pj6~=KKr7sq*EJFAaeS zj8nDr^MMeepdAdJjTCooYa|@mB9B(~@J--BnGJsl+@%aygUCyriyA9$IHJj2HKSv9 z!hkM+;HJ*(fm$Di`}bRS1ToU(4uSaFbj)UV3VGsbV0~MN5`K*qe)Nk53)7ud)yZ$N ziE6Qu!R23d8d)KlmEu-$^qFX9x|QlRDijrJwoesmq|02m<@%dHRnm?d)eCz3I{4%8 zwCHYDtCRAMxrGDMMo&*}mf06fR;mP8Yg1(F zb-R8DtUqU6A^(Cp&*rFicpD;g8&^%E=T@hV?baR2EuCoO34Ko+R)%(iL*@3bZf^AH z|H7Xv_rAZikY0~Z(ys&UV_qe)a1#vbO@U11pQ|6>R5q~Eg-b2tOJ5v#1lE^u#gCGEF|7laxmbcVRQQ5W=4U- zWH5^e_Bv4lq*qN@*p+u?!Fvx~#5NBuhxKkGr6-&RZT`?exbZxsOmaN!TAGn0(LIid z*=KK>ns6b;(?CiZn8rOz!fEC9Fj7%<%e5HG8TyQJT{lH$_GL#op%UT{3_@8Bfk-3+Z0z<5+lKj!4Ety`Winrd#)jsm;`NG-$t|l| zRA2D$W~S!AmUj$+C+#xyk^u(2cwRPJ|i$EUR;0A%ub zL{CM`AY2qld=-W4|9NF~Et;UVTA*nut{$9HW>B;DvlkSyv-pNAU-z_x-=_39cO+1w zCv@L04y{UjMUP9(NPzXLG7+O!$F+;1uIuKQ=wiuYgwO#fmisE> z@k@)MCg(gLm-GBZSXyQoWYJXsEF zJL?BxBAxG%OC_6RllKyC7KG`Kk@?UDZz9RL2)T!wK!ME3)|XPo3v`os?NYg3=Ub(T zWA5ni!}P7!e#@z+W`1}*l&ggdsR#}91rVm)Vl!B38@w0g-RPb+bHmYfb+|53XW zvLL88O3^;YVMrj6Zp${p$K|WWJEspi_eaDYqv|*F>66hMuAnb-%VkTbHY<9(;i_mn z5bL4(WfBvZCfz9`Wm!pYg>Yb|F`A7>9$GX>y{4c~6f6+-q(}hlN??pMxlTbQKvXFl z)c{N_LToPiLRCXm=8Q~Ojy)JHt&o8fTii=i?6Ax|x?M)%E=W1&pu0KdK9qLbrU+!a zp3Fh#g{U)0H%5fV)j+*QGHyh&l3E)(a-0YY=e^pcV-+V2DT(8=mkOzt6boqdIW!V{ z*w&VBlp^)DI9$X+{k_J4C{`%Z;$G?e;B~j1KIeC){qJF--@8l3BN$b4j-=ADWf7qO zAHE_BSgK|Z9X+aU*l9vo)c(&WVfy-ky(ZS99i|e3eM5Jfz3$Hk+6-*`G_xCqfUtsr zo7JcTN`HKD*^WZ0i=P}^*3DjDPgb9#VJ5`vMX=;qu3t-YrC%DW)GeWppz2lS^{#ZT z4o1~5ujfnzkSgo><{6=>9HcirlE5xz!t5SK*{kx6;9k-*P%-8_;b6*`4fiY$ovOw= z{DmK?V{EA601`4ceDj;%|C6|d%AyJrPkK) z0}KiF^!Y6k+_}zUES`df+T5@W*gxk?r@6T1OfntH)qWNJ9$?qIl*%1jbu$!oAr@$R zMDgcV4kQOZF&^h4g-_e%B7ZVg7zcCenmFJsZb{dBsy5WIIKtZ7n?0mzrQvuybcdzS zgaYkv(cNXt;Sr=s;&5F!GdPdKKJetxwlFZFp2b#qqkUk0b{ba}snhEUy6v<}dwnoX zc}>+S*^Wqtza}Kta;G+g4p+5ZM*cYb)@`ThR-$EV=EC}5F-m{<*RxZr06qQBRh1SK zg%_K?=w$5nf2AsIt_v#mDsA)G7+Ubf9WRxdlWBS-vfFBFDKUK!|LwMzwMEKpK3*|l z{km_@3?sp?M94ZTvST<{_8ma)q=9fP>sl(CA8lubx9_&B&X}8QK58-}m7Du; zN*0>4=V_^NOr0Nx7g|9IrB6jkD&Rf|HR^)P1iZCiH{3h5(Br1Ab~TA`1ITYPq_1!1 zou0P5q|ty(rln~71Ly8$hUep zLZ*HIi~$1LFhEiyW7N+|+BN>R9gp~LK6@%qEa^|{G*GE5w8mGvuLU<+L5cl zGbzAvUvll#+Dpr=y8Wh_X`L^Q1pJZc6JA-ctki@MFhmsX`AmuzZC;%pwE?W zqao!Jh*t510pp+?Y~(@$v%kv*+^Fkzd+vJJ5Xjc{@BZmh^WXD5oHStu`QAo(rXi@6 z1gy4%2f&NnoF>|&v!Lfp?IB*yD840C1Gnz;IeH4Pd9@P;{mSK-0FugJx3&ZG;kj+*s+OGZ)@>8B273Ha3}#w$$b zOjv?mhnam4EO81BpqbaDIAqqK=$B)F$@upII^ZSrhb zTa@cNQs??@^&Q6FDF9ZO^vVn*u=t6EbF{UZ6Xh$6X3u$_>HDV*mS1TFL=h!hC$ol_ z!VSOgnq*g}x1O~*+$IGrQ}fDDorAva$+Y4b=Y zCXMk;IuGvC1|^|NXsvdRn51%LR=ZCt%%J zs$B&^Y)J%uI6gLo(ZSKYHytpOGOS2cQAtZ{Sm$kujd_0Kr|m0aSCGJOOFahbg|n*Z zq!-)PDR){*U_w0_ToPSLam#eTQuz80=u@(LA=FIj-{MVEiU6PD#`y@!%uF35I67Fb zNX7BHR@>_`G#W(HZeU9<_@g%cY0tb4WR^_E&%GFWjTn%2(v!88hm*TT|C_oRQq6Jz z3EaE8O5t-b)TI&L-X&b8D0{qx1R6UFg2??~mi1nY`77l0Y}XpWE`)#Go^jLM%@y@d zT)g&~utf4)C`wk&fBHHaYpgnOi0NFc6~@T#9A-ikptB(8{b=OXQrIKZmw*wK)_6|Y zzI!Eh8yv82K6C{U{^Xk{wFqB;TH;aDp$N$=1;0)?=L#Ct*PngIJlsw|qWS9wAP`ax zIK4(#cM*Kp1;57oItJA$JCZjI2rPeu)bHtPIzOUd=>3gaN$wh3sr(^0;O+qYN;4If zf|{-GYhKx|vG3z(EE^@7AKFktZ#KdG3wl4Cgr7q4u#3>L1z{*
1n8NhLpz~{K2 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 void: + var text: String = opt.get_item_text(index) + if text.begins_with(JOYPAD_PREFIX): + input_router.set_input_config({"joypad": text.substr(text.length() - 1, -1).to_int(), "keyboard": []}) + else: + input_router.set_input_config({"keyboard": _keyboard_options[text]["keys"], "joypad": -1}) diff --git a/viewport/split_screen_input/split_screen.gd.uid b/viewport/split_screen_input/split_screen.gd.uid new file mode 100644 index 00000000..36be3bef --- /dev/null +++ b/viewport/split_screen_input/split_screen.gd.uid @@ -0,0 +1 @@ +uid://crrvxnm6s4ssm diff --git a/viewport/split_screen_input/split_screen.tscn b/viewport/split_screen_input/split_screen.tscn new file mode 100644 index 00000000..f4203add --- /dev/null +++ b/viewport/split_screen_input/split_screen.tscn @@ -0,0 +1,42 @@ +[gd_scene load_steps=6 format=3 uid="uid://dqailbm8vcpf5"] + +[ext_resource type="Script" uid="uid://crrvxnm6s4ssm" path="res://split_screen.gd" id="1_4fp0b"] +[ext_resource type="Script" uid="uid://mplfqw0th285" path="res://sub_viewport_container.gd" id="2_v8t84"] +[ext_resource type="Texture2D" uid="uid://ci5b7o7h2bmj0" path="res://icon.svg" id="4_787wn"] +[ext_resource type="Script" uid="uid://b7p2tpqjka6jq" path="res://player.gd" id="5_1qhfw"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_m48mh"] +size = Vector2(128, 128) + +[node name="Split" type="VBoxContainer"] +auto_translate_mode = 1 +offset_right = 350.0 +offset_bottom = 374.0 +script = ExtResource("1_4fp0b") + +[node name="OptionButton" type="OptionButton" parent="."] +auto_translate_mode = 1 +layout_mode = 2 + +[node name="InputRoutingViewportContainer" type="SubViewportContainer" parent="."] +auto_translate_mode = 1 +layout_mode = 2 +script = ExtResource("2_v8t84") + +[node name="SubViewport" type="SubViewport" parent="InputRoutingViewportContainer"] +handle_input_locally = false +size = Vector2i(350, 350) +render_target_update_mode = 4 + +[node name="Player" type="CharacterBody2D" parent="InputRoutingViewportContainer/SubViewport"] +script = ExtResource("5_1qhfw") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="InputRoutingViewportContainer/SubViewport/Player"] +shape = SubResource("RectangleShape2D_m48mh") + +[node name="Sprite2D" type="Sprite2D" parent="InputRoutingViewportContainer/SubViewport/Player"] +texture = ExtResource("4_787wn") + +[node name="Camera2D" type="Camera2D" parent="InputRoutingViewportContainer/SubViewport/Player"] + +[connection signal="item_selected" from="OptionButton" to="." method="_on_option_button_item_selected"] diff --git a/viewport/split_screen_input/split_screen_demo.tscn b/viewport/split_screen_input/split_screen_demo.tscn new file mode 100644 index 00000000..ecd9fca0 --- /dev/null +++ b/viewport/split_screen_input/split_screen_demo.tscn @@ -0,0 +1,73 @@ +[gd_scene load_steps=3 format=3 uid="uid://ccutmhshaoqih"] + +[ext_resource type="Script" uid="uid://bdikev0kwlu1g" path="res://root.gd" id="1_2itit"] +[ext_resource type="PackedScene" uid="uid://dqailbm8vcpf5" path="res://split_screen.tscn" id="1_mcbdt"] + +[node name="Node" type="Node"] +script = ExtResource("1_2itit") + +[node name="Panel" type="Panel" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="SplitScreen1" parent="." instance=ExtResource("1_mcbdt")] +anchors_preset = -1 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -375.0 +offset_top = -387.0 +offset_right = -25.0 +offset_bottom = -13.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="SplitScreen2" parent="." instance=ExtResource("1_mcbdt")] +anchors_preset = -1 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = 25.0 +offset_top = -387.0 +offset_right = 375.0 +offset_bottom = -13.0 +grow_horizontal = 2 +grow_vertical = 2 +init_position = Vector2(132, 0) + +[node name="SplitScreen3" parent="." instance=ExtResource("1_mcbdt")] +anchors_preset = -1 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -375.0 +offset_top = 13.0 +offset_right = -25.0 +offset_bottom = 387.0 +grow_horizontal = 2 +grow_vertical = 2 +init_position = Vector2(0, 132) + +[node name="SplitScreen4" parent="." instance=ExtResource("1_mcbdt")] +anchors_preset = -1 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = 25.0 +offset_top = 13.0 +offset_right = 375.0 +offset_bottom = 387.0 +grow_horizontal = 2 +grow_vertical = 2 +init_position = Vector2(132, 132) + +[node name="PlayArea" type="SubViewport" parent="."] +render_target_update_mode = 4 diff --git a/viewport/split_screen_input/sub_viewport_container.gd b/viewport/split_screen_input/sub_viewport_container.gd new file mode 100644 index 00000000..cf98dcf5 --- /dev/null +++ b/viewport/split_screen_input/sub_viewport_container.gd @@ -0,0 +1,27 @@ +## Input Routing for different SubViewports. +## Based on the provided input configuration, ensures only the correct +## events reaching the SubViewport. +class_name InputRoutingViewportContainer +extends SubViewportContainer + + +var _current_keyboard_set: Array = [] # Currently used keyboard set. +var _current_joypad_device: int = -1 # Currently used joypad device id. + + +# Make sure, that only the events are sent to the SubViewport, +# that are allowed via the OptionButton selection. +func _propagate_input_event(input_event: InputEvent) -> bool: + if input_event is InputEventKey: + if _current_keyboard_set.has(input_event.keycode): + return true + elif input_event is InputEventJoypadButton: + if _current_joypad_device > -1 and input_event.device == _current_joypad_device: + return true + return false + + +# Set new config for input handling. +func set_input_config(config_dict: Dictionary): + _current_keyboard_set = config_dict["keyboard"] + _current_joypad_device = config_dict["joypad"] diff --git a/viewport/split_screen_input/sub_viewport_container.gd.uid b/viewport/split_screen_input/sub_viewport_container.gd.uid new file mode 100644 index 00000000..337418c9 --- /dev/null +++ b/viewport/split_screen_input/sub_viewport_container.gd.uid @@ -0,0 +1 @@ +uid://mplfqw0th285