Browse Source

Merge pull request #428 from aaronfranke/splitscreen

Add a splitscreen mode to the 2D platformer.
Aaron Franke 5 years ago
parent
commit
9b6109e44d

+ 60 - 0
2d/platformer/project.godot

@@ -148,6 +148,66 @@ toggle_pause={
 , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
  ]
 }
+jump_p1={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+move_left_p1={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
+ ]
+}
+move_right_p1={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":15,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
+ ]
+}
+shoot_p1={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":90,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":2,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+jump_p2={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+move_left_p2={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":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)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":1,"axis":0,"axis_value":-1.0,"script":null)
+ ]
+}
+move_right_p2={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":1,"button_index":15,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":1,"axis":0,"axis_value":1.0,"script":null)
+ ]
+}
+shoot_p2={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777350,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":1,"button_index":2,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+splitscreen={
+"deadzone": 0.5,
+"events": [ Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777218,"unicode":0,"echo":false,"script":null)
+ ]
+}
 
 [layer_names]
 

+ 18 - 5
2d/platformer/src/Actors/Player.gd

@@ -4,6 +4,8 @@ extends Actor
 
 const FLOOR_DETECT_DISTANCE = 20.0
 
+export(String) var action_suffix = ""
+
 onready var platform_detector = $PlatformDetector
 onready var sprite = $Sprite
 onready var animation_player = $AnimationPlayer
@@ -11,6 +13,17 @@ onready var shoot_timer = $ShootAnimation
 onready var gun = $Sprite/Gun
 
 
+func _ready():
+	# Static types are necessary to avoid
+	var camera: Camera2D = $Camera
+	if action_suffix == "_p1":
+		camera.custom_viewport = $"../.."
+	elif action_suffix == "_p2":
+		var viewport: Viewport = $"../../../../ViewportContainer2/Viewport"
+		viewport.world_2d = ($"../.." as Viewport).world_2d
+		camera.custom_viewport = viewport
+
+
 # Physics process is a built-in loop in Godot.
 # If you define _physics_process on a node, Godot will call it every frame.
 
@@ -32,7 +45,7 @@ onready var gun = $Sprite/Gun
 func _physics_process(_delta):
 	var direction = get_direction()
 
-	var is_jump_interrupted = Input.is_action_just_released("jump") and _velocity.y < 0.0
+	var is_jump_interrupted = Input.is_action_just_released("jump" + action_suffix) and _velocity.y < 0.0
 	_velocity = calculate_move_velocity(_velocity, direction, speed, is_jump_interrupted)
 
 	var snap_vector = Vector2.DOWN * FLOOR_DETECT_DISTANCE if direction.y == 0.0 else Vector2.ZERO
@@ -44,14 +57,14 @@ func _physics_process(_delta):
 	# When the character’s direction changes, we want to to scale the Sprite accordingly to flip it.
 	# This will make Robi face left or right depending on the direction you move.
 	if direction.x != 0:
-		sprite.scale.x = direction.x
+		sprite.scale.x = 1 if direction.x > 0 else -1
 
 	# We use the sprite's scale to store Robi’s look direction which allows us to shoot
 	# bullets forward.
 	# There are many situations like these where you can reuse existing properties instead of
 	# creating new variables.
 	var is_shooting = false
-	if Input.is_action_just_pressed("shoot"):
+	if Input.is_action_just_pressed("shoot" + action_suffix):
 		is_shooting = gun.shoot(sprite.scale.x)
 
 	var animation = get_new_animation(is_shooting)
@@ -63,8 +76,8 @@ func _physics_process(_delta):
 
 func get_direction():
 	return Vector2(
-		Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
-		-Input.get_action_strength("jump") if is_on_floor() and Input.is_action_just_pressed("jump") else 0.0
+		Input.get_action_strength("move_right" + action_suffix) - Input.get_action_strength("move_left" + action_suffix),
+		-1 if is_on_floor() and Input.is_action_just_pressed("jump" + action_suffix) else 0
 	)
 
 

+ 1 - 0
2d/platformer/src/Actors/Player.tscn

@@ -236,6 +236,7 @@ anims/standing_weapon_ready = SubResource( 10 )
 [node name="Camera" type="Camera2D" parent="."]
 position = Vector2( 0, -28 )
 current = true
+zoom = Vector2( 0.5, 0.5 )
 process_mode = 0
 
 [node name="CollisionShape2D" type="CollisionShape2D" parent="."]

+ 15 - 0
2d/platformer/src/Level/Level.gd

@@ -0,0 +1,15 @@
+extends Node2D
+
+const LIMIT_LEFT = -315
+const LIMIT_TOP = -250
+const LIMIT_RIGHT = 955
+const LIMIT_BOTTOM = 690
+
+func _ready():
+	for child in get_children():
+		if child is Player:
+			var camera = child.get_node("Camera")
+			camera.limit_left = LIMIT_LEFT
+			camera.limit_top = LIMIT_TOP
+			camera.limit_right = LIMIT_RIGHT
+			camera.limit_bottom = LIMIT_BOTTOM

+ 5 - 19
2d/platformer/src/Level/Level.tscn

@@ -1,13 +1,14 @@
-[gd_scene load_steps=11 format=2]
+[gd_scene load_steps=12 format=2]
 
 [ext_resource path="res://assets/art/tileset/tileset.tres" type="TileSet" id=1]
 [ext_resource path="res://src/Actors/Enemy.tscn" type="PackedScene" id=2]
 [ext_resource path="res://src/Platforms/Platform.tscn" type="PackedScene" id=3]
 [ext_resource path="res://src/Objects/Coin.tscn" type="PackedScene" id=4]
-[ext_resource path="res://src/Actors/Player.tscn" type="PackedScene" id=5]
+[ext_resource path="res://src/Level/Level.gd" type="Script" id=5]
 [ext_resource path="res://assets/art/platforms/moving_platform.png" type="Texture" id=6]
 [ext_resource path="res://src/Level/ParallaxBackground.tscn" type="PackedScene" id=7]
 [ext_resource path="res://assets/audio/music/music.ogg" type="AudioStream" id=8]
+[ext_resource path="res://src/Level/Music.gd" type="Script" id=9]
 
 [sub_resource type="Animation" id=1]
 resource_name = "move"
@@ -44,6 +45,7 @@ tracks/0/keys = {
 
 [node name="Level" type="Node2D"]
 pause_mode = 1
+script = ExtResource( 5 )
 
 [node name="TileMap" type="TileMap" parent="."]
 tile_set = ExtResource( 1 )
@@ -257,25 +259,9 @@ position = Vector2( 632.725, 78.5545 )
 [node name="Enemy4" parent="Enemies" instance=ExtResource( 2 )]
 position = Vector2( 828.515, 77.262 )
 
-[node name="Player" parent="." instance=ExtResource( 5 )]
-position = Vector2( 86.647, 546.51 )
-
-[node name="Sprite" parent="Player" index="2"]
-frame = 22
-
-[node name="Camera" parent="Player" index="4"]
-zoom = Vector2( 0.5, 0.5 )
-limit_left = -315
-limit_top = -250
-limit_right = 955
-limit_bottom = 690
-limit_smoothed = true
-editor_draw_limits = true
-
 [node name="ParallaxBackground" parent="." instance=ExtResource( 7 )]
 
 [node name="Music" type="AudioStreamPlayer" parent="."]
 stream = ExtResource( 8 )
 autoplay = true
-
-[editable path="Player"]
+script = ExtResource( 9 )

+ 16 - 0
2d/platformer/src/Level/Music.gd

@@ -0,0 +1,16 @@
+extends AudioStreamPlayer
+
+const DOUBLE_VOLUME_DB = 6 # Do not change. Represents doubling of sound pressure.
+
+export(int) var base_volume_db = -4
+
+func _ready():
+	# To avoid AudioStreamPlayer2D sounds playing on top of each other and
+	# being very loud, let's decrease the volume for splitscreen mode, but
+	# increase the music volume to keep the music at the same volume.
+	if get_parent().get_owner().name == "Splitscreen":
+		AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), base_volume_db - DOUBLE_VOLUME_DB)
+		volume_db = DOUBLE_VOLUME_DB
+	else:
+		AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), base_volume_db)
+		volume_db = 0

+ 19 - 0
2d/platformer/src/Main/Game.gd

@@ -13,6 +13,14 @@ func _init():
 	OS.max_window_size = OS.get_screen_size()
 
 
+func _notification(what):
+	if what == NOTIFICATION_WM_QUIT_REQUEST:
+		# We need to clean up a little bit first to avoid Viewport errors.
+		if name == "Splitscreen":
+			$Black/SplitContainer/ViewportContainer1.free()
+			$Black.queue_free()
+
+
 func _input(event):
 	if event.is_action_pressed("toggle_fullscreen"):
 		OS.window_fullscreen = not OS.window_fullscreen
@@ -29,3 +37,14 @@ func _input(event):
 		else:
 			_pause_menu.close()
 		get_tree().set_input_as_handled()
+	
+	elif event.is_action_pressed("splitscreen"):
+		if name == "Splitscreen":
+			# We need to clean up a little bit first to avoid Viewport errors.
+			$Black/SplitContainer/ViewportContainer1.free()
+			$Black.queue_free()
+			# warning-ignore:return_value_discarded
+			get_tree().change_scene("res://src/Main/Game.tscn")
+		else:
+			# warning-ignore:return_value_discarded
+			get_tree().change_scene("res://src/Main/Splitscreen.tscn")

+ 5 - 1
2d/platformer/src/Main/Game.tscn

@@ -1,8 +1,9 @@
-[gd_scene load_steps=4 format=2]
+[gd_scene load_steps=5 format=2]
 
 [ext_resource path="res://src/UserInterface/PauseMenu.tscn" type="PackedScene" id=1]
 [ext_resource path="res://src/Main/Game.gd" type="Script" id=2]
 [ext_resource path="res://src/Level/Level.tscn" type="PackedScene" id=3]
+[ext_resource path="res://src/Actors/Player.tscn" type="PackedScene" id=4]
 
 [node name="Game" type="Node"]
 pause_mode = 2
@@ -10,6 +11,9 @@ script = ExtResource( 2 )
 
 [node name="Level" parent="." instance=ExtResource( 3 )]
 
+[node name="Player" parent="Level" instance=ExtResource( 4 )]
+position = Vector2( 90, 546 )
+
 [node name="InterfaceLayer" type="CanvasLayer" parent="."]
 layer = 100
 

+ 80 - 0
2d/platformer/src/Main/Splitscreen.tscn

@@ -0,0 +1,80 @@
+[gd_scene load_steps=6 format=2]
+
+[ext_resource path="res://src/UserInterface/PauseMenu.tscn" type="PackedScene" id=1]
+[ext_resource path="res://src/Main/Game.gd" type="Script" id=2]
+[ext_resource path="res://src/Level/Level.tscn" type="PackedScene" id=3]
+[ext_resource path="res://src/Actors/Player.tscn" type="PackedScene" id=4]
+[ext_resource path="res://src/Level/ParallaxBackground.tscn" type="PackedScene" id=5]
+
+[node name="Splitscreen" type="Node"]
+pause_mode = 2
+script = ExtResource( 2 )
+
+[node name="InterfaceLayer" type="CanvasLayer" parent="."]
+layer = 100
+
+[node name="PauseMenu" parent="InterfaceLayer" instance=ExtResource( 1 )]
+
+[node name="Black" type="ColorRect" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_right = 6.10352e-05
+margin_bottom = 3.05176e-05
+color = Color( 0, 0, 0, 1 )
+
+[node name="SplitContainer" type="HSplitContainer" parent="Black"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+dragger_visibility = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ViewportContainer1" type="ViewportContainer" parent="Black/SplitContainer"]
+margin_right = 394.0
+margin_bottom = 480.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+stretch = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Viewport" type="Viewport" parent="Black/SplitContainer/ViewportContainer1"]
+size = Vector2( 394, 480 )
+size_override_stretch = true
+handle_input_locally = false
+usage = 0
+render_target_update_mode = 3
+audio_listener_enable_2d = true
+
+[node name="Level" parent="Black/SplitContainer/ViewportContainer1/Viewport" instance=ExtResource( 3 )]
+
+[node name="Player1" parent="Black/SplitContainer/ViewportContainer1/Viewport/Level" instance=ExtResource( 4 )]
+position = Vector2( 90, 546 )
+action_suffix = "_p1"
+
+[node name="Player2" parent="Black/SplitContainer/ViewportContainer1/Viewport/Level" instance=ExtResource( 4 )]
+position = Vector2( 120, 546 )
+action_suffix = "_p2"
+
+[node name="ViewportContainer2" type="ViewportContainer" parent="Black/SplitContainer"]
+margin_left = 406.0
+margin_right = 800.0
+margin_bottom = 480.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+stretch = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Viewport" type="Viewport" parent="Black/SplitContainer/ViewportContainer2"]
+size = Vector2( 394, 480 )
+size_override_stretch = true
+handle_input_locally = false
+usage = 0
+render_target_update_mode = 3
+audio_listener_enable_2d = true
+
+[node name="ParallaxBackground" parent="Black/SplitContainer/ViewportContainer2/Viewport" instance=ExtResource( 5 )]

+ 3 - 0
2d/platformer/src/UserInterface/PauseMenu.gd

@@ -23,4 +23,7 @@ func _on_ResumeButton_pressed():
 
 
 func _on_QuitButton_pressed():
+	if get_parent().get_parent().name == "Splitscreen":
+		# We need to clean up a little bit first to avoid Viewport errors.
+		$"../../Black/SplitContainer/ViewportContainer1".free()
 	get_tree().quit()