Skip to content

Commit 10eb280

Browse files
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 <[email protected]>
1 parent 0cff177 commit 10eb280

16 files changed

+409
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Split Screen Input
2+
3+
A demo showing a Split Screen GUI and input handling for local multiplayer using viewports.
4+
5+
It demonstrates:
6+
- Single World2D, that is shared among many Viewports
7+
- Simplified Input Map, that uses the same Actions for all Split Screens
8+
- Input event routing to different viewports based on joypad device id and dedicated keyboard keys
9+
- Dynamic keybinding adjustment for each Split Screen
10+
11+
Language: GDScript
12+
13+
Renderer: Compatibility
14+
15+
## Screenshots
16+
17+
![Screenshot](screenshots/split_screen_input.webp)
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://ci5b7o7h2bmj0"
6+
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
7+
metadata={
8+
"vram_texture": false
9+
}
10+
11+
[deps]
12+
13+
source_file="res://icon.svg"
14+
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
15+
16+
[params]
17+
18+
compress/mode=0
19+
compress/high_quality=false
20+
compress/lossy_quality=0.7
21+
compress/uastc_level=0
22+
compress/rdo_quality_loss=0.0
23+
compress/hdr_compression=1
24+
compress/normal_map=0
25+
compress/channel_pack=0
26+
mipmaps/generate=false
27+
mipmaps/limit=-1
28+
roughness/mode=0
29+
roughness/src_normal=""
30+
process/channel_remap/red=0
31+
process/channel_remap/green=1
32+
process/channel_remap/blue=2
33+
process/channel_remap/alpha=3
34+
process/fix_alpha_border=true
35+
process/premult_alpha=false
36+
process/normal_map_invert_y=false
37+
process/hdr_as_srgb=false
38+
process/hdr_clamp_exposure=false
39+
process/size_limit=0
40+
detect_3d/compress_to=1
41+
svg/scale=1.0
42+
editor/scale_with_editor_scale=false
43+
editor/convert_colors_with_editor_theme=false
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
class_name Player
2+
extends CharacterBody2D
3+
## Player implementation.
4+
5+
const factor: float = 200.0 # Factor to multiply the movement.
6+
7+
var _movement: Vector2 = Vector2(0, 0) # Current movement rate of node.
8+
9+
10+
# Update movement variable based on input that reaches this SubViewport.
11+
func _unhandled_input(event: InputEvent) -> void:
12+
if event.is_action_pressed("ux_up") or event.is_action_released("ux_down"):
13+
_movement.y -= 1
14+
get_viewport().set_input_as_handled()
15+
elif event.is_action_pressed("ux_down") or event.is_action_released("ux_up"):
16+
_movement.y += 1
17+
get_viewport().set_input_as_handled()
18+
elif event.is_action_pressed("ux_left") or event.is_action_released("ux_right"):
19+
_movement.x -= 1
20+
get_viewport().set_input_as_handled()
21+
elif event.is_action_pressed("ux_right") or event.is_action_released("ux_left"):
22+
_movement.x += 1
23+
get_viewport().set_input_as_handled()
24+
25+
26+
# Move the node based on the content of the movement variable.
27+
func _physics_process(delta: float) -> void:
28+
move_and_collide(_movement * factor * delta)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://b7p2tpqjka6jq
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
; Engine configuration file.
2+
; It's best edited using the editor UI and not directly,
3+
; since the parameters that go here are not all obvious.
4+
;
5+
; Format:
6+
; [section] ; section goes between []
7+
; param=value ; assign values to parameters
8+
9+
config_version=5
10+
11+
[application]
12+
13+
config/name="Split Screen Input"
14+
config/tags=PackedStringArray("demo", "input", "official", "rendering")
15+
run/main_scene="res://split_screen_demo.tscn"
16+
config/features=PackedStringArray("4.5")
17+
config/icon="res://icon.svg"
18+
19+
[display]
20+
21+
window/size/viewport_width=800
22+
window/size/viewport_height=800
23+
window/stretch/mode="canvas_items"
24+
window/stretch/aspect="expand"
25+
26+
[input]
27+
28+
ui_left={
29+
"deadzone": 0.2,
30+
"events": []
31+
}
32+
ui_right={
33+
"deadzone": 0.2,
34+
"events": []
35+
}
36+
ui_up={
37+
"deadzone": 0.2,
38+
"events": []
39+
}
40+
ui_down={
41+
"deadzone": 0.2,
42+
"events": []
43+
}
44+
ux_left={
45+
"deadzone": 0.2,
46+
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
47+
, 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)
48+
, 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)
49+
, 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)
50+
, 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)
51+
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
52+
]
53+
}
54+
ux_right={
55+
"deadzone": 0.2,
56+
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
57+
, 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)
58+
, 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)
59+
, 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)
60+
, 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)
61+
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
62+
]
63+
}
64+
ux_up={
65+
"deadzone": 0.2,
66+
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
67+
, 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)
68+
, 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)
69+
, 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)
70+
, 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)
71+
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
72+
]
73+
}
74+
ux_down={
75+
"deadzone": 0.2,
76+
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
77+
, 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)
78+
, 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)
79+
, 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)
80+
, 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)
81+
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
82+
]
83+
}
84+
85+
[rendering]
86+
87+
renderer/rendering_method="gl_compatibility"
88+
renderer/rendering_method.mobile="gl_compatibility"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
## Set up different Split Screens
2+
## Provide Input configuration
3+
## Connect Split Screens to Play Area
4+
extends Node
5+
6+
7+
const KEYBOARD_OPTIONS: Dictionary[String, Dictionary] = {
8+
"wasd": {"keys": [KEY_W, KEY_A, KEY_S, KEY_D]},
9+
"ijkl": {"keys": [KEY_I, KEY_J, KEY_K, KEY_L]},
10+
"arrows": {"keys": [KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN]},
11+
"numpad": {"keys": [KEY_KP_4, KEY_KP_5, KEY_KP_6, KEY_KP_8]},
12+
} # 4 keyboard sets for moving players around.
13+
14+
const PLAYER_COLORS: Array[Color] = [
15+
Color.WHITE,
16+
Color("ff8f02"),
17+
Color("05ff5a"),
18+
Color("ff05a0")
19+
] # Modulate Colors of each Player.
20+
21+
22+
var config: Dictionary = {
23+
"keyboard": KEYBOARD_OPTIONS,
24+
"joypads": 4,
25+
"world": null,
26+
"position": Vector2(),
27+
"index": -1,
28+
"color": Color(),
29+
} # Split Screen configuration Dictionary.
30+
31+
@onready var play_area: SubViewport = $PlayArea # The central Viewport, all Split Screens are sharing.
32+
33+
34+
# Initialize each Split Screen and each player node.
35+
func _ready() -> void:
36+
config["world"] = play_area.world_2d
37+
var children: Array[Node] = get_children()
38+
var index: int = 0
39+
for child: Node in children:
40+
if child is SplitScreen:
41+
config["position"] = Vector2(index % 2, floor(index / 2.0)) * 132 + Vector2(132, 0)
42+
config["index"] = index
43+
config["color"] = PLAYER_COLORS[index]
44+
var split_child: SplitScreen = child as SplitScreen
45+
split_child.set_config(config)
46+
index += 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://bdikev0kwlu1g

viewport/split_screen_input/screenshots/.gdignore

Whitespace-only changes.
9.99 KB
Loading

0 commit comments

Comments
 (0)