-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Split Screen Demo showing input handling
- Loading branch information
Showing
12 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
[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/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 | ||
svg/scale=1.0 | ||
editor/scale_with_editor_scale=false | ||
editor/convert_colors_with_editor_theme=false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
; 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" | ||
run/main_scene="res://split_screen_demo.tscn" | ||
config/features=PackedStringArray("4.2") | ||
config/icon="res://icon.svg" | ||
|
||
[display] | ||
|
||
window/size/viewport_width=900 | ||
window/size/viewport_height=900 | ||
|
||
[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,"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,"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,"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,"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,"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,"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,"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,"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,"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,"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,"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,"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,"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,"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,"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,"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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
extends Node | ||
## Set up different Split Screens | ||
# | ||
## Provide Input configuration | ||
## Connect Split Screens to Play Area | ||
|
||
|
||
const keyboard_options: 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 c: Array[Node] = get_children() | ||
var i = 0 | ||
for n: Node in c: | ||
if n is SplitScreen: | ||
config["position"] = Vector2(i % 2, floor(i / 2.0)) * 132 + Vector2(132, 0) | ||
config["index"] = i | ||
config["color"] = player_colors[i] | ||
var s: SplitScreen = n as SplitScreen | ||
s.set_config(config) | ||
i += 1 |
Empty file.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
class_name SplitScreen | ||
extends Node | ||
## Interface for a SplitScreen | ||
|
||
|
||
const keypad_string:String = "Joypad" # Prefix for joypads. | ||
|
||
@export var init_position: Vector2 | ||
|
||
var _keyboard_options: Dictionary # Copy of all keyboard options. | ||
|
||
@onready var opt: OptionButton = $OptionButton | ||
@onready var v: SubViewport = $SubViewportContainer/SubViewport | ||
@onready var svc: MySV = $SubViewportContainer | ||
@onready var play: Player = $SubViewportContainer/SubViewport/Player | ||
|
||
|
||
# Set the configuration of this split screen and perform OptionButton initialization. | ||
func set_config(c: Dictionary): | ||
_keyboard_options = c["keyboard"] | ||
play.position = c["position"] | ||
var local_index = c["index"] | ||
play.modulate = c["color"] | ||
opt.clear() | ||
for k in _keyboard_options: | ||
opt.add_item(k) | ||
for i in c["joypads"]: | ||
opt.add_item("%s %s" % [keypad_string, i+1]) | ||
opt.select(local_index) | ||
_on_option_button_item_selected(local_index) | ||
v.world_2d = c["world"] # Connect all Split Screens to the same World2D. | ||
|
||
|
||
# Update Keyboard Settings after selecting them in the OptionButton. | ||
func _on_option_button_item_selected(index: int) -> void: | ||
var txt: String = opt.get_item_text(index) | ||
if txt.begins_with(keypad_string): | ||
svc.set_input_config({"joypad": txt.substr(txt.length()-1, -1).to_int(), "keyboard": []}) | ||
else: | ||
svc.set_input_config({"keyboard": _keyboard_options[txt]["keys"], "joypad": -1}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
[gd_scene load_steps=6 format=3 uid="uid://dqailbm8vcpf5"] | ||
|
||
[ext_resource type="Script" path="res://split_screen.gd" id="1_4fp0b"] | ||
[ext_resource type="Script" 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" 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="SubViewportContainer" type="SubViewportContainer" parent="."] | ||
auto_translate_mode = 1 | ||
layout_mode = 2 | ||
script = ExtResource("2_v8t84") | ||
|
||
[node name="SubViewport" type="SubViewport" parent="SubViewportContainer"] | ||
handle_input_locally = false | ||
size = Vector2i(350, 350) | ||
render_target_update_mode = 4 | ||
|
||
[node name="Player" type="CharacterBody2D" parent="SubViewportContainer/SubViewport"] | ||
script = ExtResource("5_1qhfw") | ||
|
||
[node name="CollisionShape2D" type="CollisionShape2D" parent="SubViewportContainer/SubViewport/Player"] | ||
shape = SubResource("RectangleShape2D_m48mh") | ||
|
||
[node name="Sprite2D" type="Sprite2D" parent="SubViewportContainer/SubViewport/Player"] | ||
texture = ExtResource("4_787wn") | ||
|
||
[node name="Camera2D" type="Camera2D" parent="SubViewportContainer/SubViewport/Player"] | ||
|
||
[connection signal="item_selected" from="OptionButton" to="." method="_on_option_button_item_selected"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
[gd_scene load_steps=3 format=3 uid="uid://ccutmhshaoqih"] | ||
|
||
[ext_resource type="Script" 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="."] | ||
offset_right = 900.0 | ||
offset_bottom = 900.0 | ||
mouse_filter = 2 | ||
|
||
[node name="SplitScreen1" parent="." instance=ExtResource("1_mcbdt")] | ||
offset_left = 25.0 | ||
offset_top = 25.0 | ||
offset_right = 375.0 | ||
offset_bottom = 399.0 | ||
|
||
[node name="SplitScreen2" parent="." instance=ExtResource("1_mcbdt")] | ||
offset_left = 425.0 | ||
offset_top = 25.0 | ||
offset_right = 775.0 | ||
offset_bottom = 399.0 | ||
init_position = Vector2(132, 0) | ||
|
||
[node name="SplitScreen3" parent="." instance=ExtResource("1_mcbdt")] | ||
offset_left = 25.0 | ||
offset_top = 425.0 | ||
offset_right = 375.0 | ||
offset_bottom = 799.0 | ||
init_position = Vector2(0, 132) | ||
|
||
[node name="SplitScreen4" parent="." instance=ExtResource("1_mcbdt")] | ||
offset_left = 425.0 | ||
offset_top = 425.0 | ||
offset_right = 775.0 | ||
offset_bottom = 799.0 | ||
init_position = Vector2(132, 132) | ||
|
||
[node name="PlayArea" type="SubViewport" parent="."] | ||
render_target_update_mode = 4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
class_name MySV | ||
extends SubViewportContainer | ||
## Input Routing for different SubViewports. | ||
# | ||
## Based on the provided input configuration, ensures only the correct | ||
## events reaching the SubViewport- | ||
|
||
|
||
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(event: InputEvent) -> bool: | ||
if event is InputEventKey: | ||
if _current_keyboard_set.has(event.keycode): | ||
return true | ||
elif event is InputEventJoypadButton: | ||
if _current_joypad_device > -1 and event.device == _current_joypad_device: | ||
return true | ||
return false | ||
|
||
|
||
# Set new config for input handling. | ||
func set_input_config(config: Dictionary): | ||
_current_keyboard_set = config["keyboard"] | ||
_current_joypad_device = config["joypad"] |