Selaa lähdekoodia

Merge pull request #269 from PetePete1984/space-shooter-rework

Update Space Shooter demo to 2.1.5
Aaron Franke 5 vuotta sitten
vanhempi
commit
238057d534
67 muutettua tiedostoa jossa 1557 lisäystä ja 647 poistoa
  1. 39 0
      2d/space_shooter/README.md
  2. 2 1
      2d/space_shooter/assets/tileset_edit.tscn
  3. 0 43
      2d/space_shooter/asteroid.gd
  4. 0 0
      2d/space_shooter/effects/background/bg_gradient.png
  5. 2 0
      2d/space_shooter/effects/background/bg_gradient.png.flags
  6. 0 0
      2d/space_shooter/effects/background/big_star.png
  7. 2 0
      2d/space_shooter/effects/background/big_star.png.flags
  8. 5 3
      2d/space_shooter/effects/background/parallax.tscn
  9. 0 0
      2d/space_shooter/effects/background/small_star.png
  10. 2 0
      2d/space_shooter/effects/background/small_star.png.flags
  11. 2 1
      2d/space_shooter/effects/particles/explosion.tscn
  12. 0 0
      2d/space_shooter/effects/particles/fire.png
  13. 2 0
      2d/space_shooter/effects/particles/fire.png.flags
  14. 0 0
      2d/space_shooter/effects/sounds/sound_explode.wav
  15. 0 0
      2d/space_shooter/effects/sounds/sound_shoot.wav
  16. 59 0
      2d/space_shooter/enemies/asteroid/asteroid.gd
  17. 46 9
      2d/space_shooter/enemies/asteroid/asteroid.tscn
  18. 0 0
      2d/space_shooter/enemies/asteroid/meteorite.png
  19. 2 0
      2d/space_shooter/enemies/asteroid/meteorite.png.flags
  20. 77 0
      2d/space_shooter/enemies/shooter/enemy2.gd
  21. 0 0
      2d/space_shooter/enemies/shooter/enemy2.png
  22. 2 0
      2d/space_shooter/enemies/shooter/enemy2.png.flags
  23. 40 12
      2d/space_shooter/enemies/shooter/enemy2.tscn
  24. 7 6
      2d/space_shooter/enemies/shooter/enemy_shot.gd
  25. 0 0
      2d/space_shooter/enemies/shooter/enemy_shot.png
  26. 2 0
      2d/space_shooter/enemies/shooter/enemy_shot.png.flags
  27. 37 8
      2d/space_shooter/enemies/shooter/enemy_shot.tscn
  28. 41 0
      2d/space_shooter/enemies/ufo/enemy1.gd
  29. 0 0
      2d/space_shooter/enemies/ufo/enemy1.png
  30. 2 0
      2d/space_shooter/enemies/ufo/enemy1.png.flags
  31. 57 20
      2d/space_shooter/enemies/ufo/enemy1.tscn
  32. 33 0
      2d/space_shooter/enemies/ufo/enemy1_rail.gd
  33. 0 36
      2d/space_shooter/enemy1.gd
  34. 0 47
      2d/space_shooter/enemy2.gd
  35. 5 2
      2d/space_shooter/engine.cfg
  36. 25 0
      2d/space_shooter/game_screen/hud/game_hud.gd
  37. 77 0
      2d/space_shooter/game_screen/hud/game_hud.tscn
  38. 284 0
      2d/space_shooter/game_screen/level/enemies.tscn
  39. 33 0
      2d/space_shooter/game_screen/level/level.gd
  40. 29 0
      2d/space_shooter/game_screen/level/level.tscn
  41. 12 0
      2d/space_shooter/game_screen/level/level_map.gd
  42. 18 0
      2d/space_shooter/game_screen/level/level_map.tscn
  43. 0 0
      2d/space_shooter/game_screen/level/tile.png
  44. 2 0
      2d/space_shooter/game_screen/level/tile.png.flags
  45. 21 0
      2d/space_shooter/game_screen/level/tilemap_level.tscn
  46. 5 1
      2d/space_shooter/game_screen/level/tileset.tres
  47. 65 7
      2d/space_shooter/game_state.gd
  48. 0 27
      2d/space_shooter/level.tscn
  49. 0 11
      2d/space_shooter/main_menu.gd
  50. 0 64
      2d/space_shooter/main_menu.tscn
  51. 18 0
      2d/space_shooter/main_menu/main_menu.gd
  52. 106 0
      2d/space_shooter/main_menu/main_menu.tscn
  53. BIN
      2d/space_shooter/main_menu/space.png
  54. 2 0
      2d/space_shooter/main_menu/space.png.flags
  55. 176 0
      2d/space_shooter/player/player_ship.tscn
  56. 32 0
      2d/space_shooter/player/player_ship_on_rail.tscn
  57. 27 0
      2d/space_shooter/player/rail.gd
  58. 105 0
      2d/space_shooter/player/ship.gd
  59. 0 0
      2d/space_shooter/player/ship.png
  60. 2 0
      2d/space_shooter/player/ship.png.flags
  61. 0 0
      2d/space_shooter/player/shoot.png
  62. 2 0
      2d/space_shooter/player/shoot.png.flags
  63. 7 9
      2d/space_shooter/player/shot.gd
  64. 43 12
      2d/space_shooter/player/shot.tscn
  65. 0 19
      2d/space_shooter/rail.gd
  66. 0 80
      2d/space_shooter/ship.gd
  67. 0 229
      2d/space_shooter/ship.tscn

+ 39 - 0
2d/space_shooter/README.md

@@ -0,0 +1,39 @@
+# Space Shooter
+
+## Introduction
+In this on-rails shoot-em-up demo, the player gets to control a Space ship flying through a 2D version of Space, while firing their lasers by hitting the Space bar.  
+Various enemies will enter the screen from the right and try their hardest to destroy the player's ship.  
+Shooting these enemies will award points and the highest score achieved is kept in a one-entry leaderboard.  
+Avoiding the blocky obstacles and the enemies is key to survival and high scores, so good luck and have fun!
+
+## Controls
+* WSAD or Arrow Keys to move the ship
+* Space to fire lasers
+* Escape / ESC to stop playing and return to the main menu
+
+---
+## Godot Concepts presented in the demo
+### Editor Workflow
+* Importing assets (images, sounds)
+* Using Scenes to group Nodes into small, mostly self-contained units of functionality
+* Using a TileMap and TileSet to place obstacles in the level
+* Use of AnimationPlayer nodes to both animate properties (position, rotation) as well as trigger functions
+* Using a Parallax Background to give an impression of speed and distance traveled
+
+### Scripting
+* Using signals to communicate between Nodes that are created in different Scenes
+* Using groups to tag and identify Nodes
+* Interactions between KinematicBody2D and Area2D nodes for hit detection / collision
+* Use of VisibilityNotifier2D to remove Nodes that move off screen
+* Dynamically instancing loaded Scenes as Nodes
+
+### GUI
+* GUI Containers for organization and positioning
+* GUI Controls to start and stop gameplay
+* Use of a CanvasLayer to keep GUI always on top of the gameplay screen
+
+### Miscellaneous
+* Persisting a "savegame" in the user directory for the highscore
+
+### Interactivity
+* Player input for movement and firing

+ 2 - 1
2d/space_shooter/tileset_edit.tscn → 2d/space_shooter/assets/tileset_edit.tscn

@@ -1,6 +1,7 @@
 [gd_scene load_steps=3 format=1]
 
-[ext_resource path="res://tile.png" type="Texture" id=1]
+[ext_resource path="res://game_screen/level/tile.png" type="Texture" id=1]
+
 
 [sub_resource type="RectangleShape2D" id=1]
 

+ 0 - 43
2d/space_shooter/asteroid.gd

@@ -1,43 +0,0 @@
-
-extends Area2D
-
-# Member variables
-const SPEED = -200
-const Y_RANDOM = 10
-
-var points = 1
-var speed_y = 0.0
-var destroyed = false
-
-
-func _fixed_process(delta):
-	translate(Vector2(SPEED, speed_y)*delta)
-
-
-func _ready():
-	speed_y = rand_range(-Y_RANDOM, Y_RANDOM)
-
-
-func destroy():
-	if (destroyed):
-		return
-	destroyed = true
-	get_node("anim").play("explode")
-	set_fixed_process(false)
-	get_node("sfx").play("sound_explode")
-	# Accumulate points
-	get_node("/root/game_state").points += 1
-
-
-func is_enemy():
-	return not destroyed
-
-
-func _on_visibility_enter_screen():
-	set_fixed_process(true)
-	# Make it spin!
-	get_node("anim").play("spin")
-
-
-func _on_visibility_exit_screen():
-	queue_free()

+ 0 - 0
2d/space_shooter/bg_gradient.png → 2d/space_shooter/effects/background/bg_gradient.png


+ 2 - 0
2d/space_shooter/effects/background/bg_gradient.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 0 - 0
2d/space_shooter/big_star.png → 2d/space_shooter/effects/background/big_star.png


+ 2 - 0
2d/space_shooter/effects/background/big_star.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 5 - 3
2d/space_shooter/parallax.tscn → 2d/space_shooter/effects/background/parallax.tscn

@@ -1,8 +1,8 @@
 [gd_scene load_steps=4 format=1]
 
-[ext_resource path="res://bg_gradient.png" type="Texture" id=1]
-[ext_resource path="res://small_star.png" type="Texture" id=2]
-[ext_resource path="res://big_star.png" type="Texture" id=3]
+[ext_resource path="res://effects/background/bg_gradient.png" type="Texture" id=1]
+[ext_resource path="res://effects/background/small_star.png" type="Texture" id=2]
+[ext_resource path="res://effects/background/big_star.png" type="Texture" id=3]
 
 [node name="parallax" type="ParallaxBackground"]
 
@@ -20,6 +20,7 @@ scroll/ignore_camera_zoom = true
 [node name="bg_layer" type="ParallaxLayer" parent="."]
 
 motion/scale = Vector2( 0.2, 1 )
+motion/offset = Vector2( 0, 0 )
 motion/mirroring = Vector2( 1024, 0 )
 
 [node name="gradient" type="Sprite" parent="bg_layer"]
@@ -136,6 +137,7 @@ texture = ExtResource( 2 )
 [node name="bg_layer2" type="ParallaxLayer" parent="."]
 
 motion/scale = Vector2( 0.5, 1 )
+motion/offset = Vector2( 0, 0 )
 motion/mirroring = Vector2( 1024, 0 )
 
 [node name="Sprite" type="Sprite" parent="bg_layer2"]

+ 0 - 0
2d/space_shooter/small_star.png → 2d/space_shooter/effects/background/small_star.png


+ 2 - 0
2d/space_shooter/effects/background/small_star.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 2 - 1
2d/space_shooter/explosion.tscn → 2d/space_shooter/effects/particles/explosion.tscn

@@ -1,6 +1,7 @@
 [gd_scene load_steps=3 format=1]
 
-[ext_resource path="res://fire.png" type="Texture" id=1]
+[ext_resource path="res://effects/particles/fire.png" type="Texture" id=1]
+
 
 [sub_resource type="ColorRamp" id=1]
 

+ 0 - 0
2d/space_shooter/fire.png → 2d/space_shooter/effects/particles/fire.png


+ 2 - 0
2d/space_shooter/effects/particles/fire.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 0 - 0
2d/space_shooter/sound_explode.wav → 2d/space_shooter/effects/sounds/sound_explode.wav


+ 0 - 0
2d/space_shooter/sound_shoot.wav → 2d/space_shooter/effects/sounds/sound_shoot.wav


+ 59 - 0
2d/space_shooter/enemies/asteroid/asteroid.gd

@@ -0,0 +1,59 @@
+extends Area2D
+
+# horizontal movement speed
+const SPEED = -200
+
+# how many points does the player get for destroying this enemy type?
+const POINTS = 1
+
+# used for a slight vertical drift, defines the range
+const Y_RANDOM = 10
+
+# vertical movement for this asteroid instance
+var speed_y = 0.0
+
+# used to store this asteroid's motion direction
+var motion = Vector2()
+
+var destroyed = false
+
+# notifies listeners about death and sends the point value along with it
+signal enemy_died(score)
+
+func _fixed_process(delta):
+	# constant movement
+	translate(motion * delta)
+
+func _ready():
+	# determine this asteroid's vertical drift
+	speed_y = rand_range(-Y_RANDOM, Y_RANDOM)
+	# store the movement direction because it doesn't change for this asteroid
+	motion = Vector2(SPEED, speed_y)
+
+func destroy():
+	# skip if already destroyed
+	if (destroyed):
+		return
+	
+	# set the state to destroyed
+	destroyed = true
+	# stop processing
+	set_fixed_process(false)
+	# play on-death effects
+	get_node("anim").play("explode")
+	get_node("sfx").play("sound_explode")
+	# inform listeners about death, while sending the point value along with it
+	emit_signal("enemy_died", POINTS)
+	# disable physics interactions
+	call_deferred("set_enable_monitoring", false)
+	call_deferred("set_monitorable", false)
+
+func _on_visibility_enter_screen():
+	# start moving once the asteroid enters the screen
+	set_fixed_process(true)
+	# Make it spin!
+	get_node("anim").play("spin")
+
+func _on_visibility_exit_screen():
+	# remove the asteroid when it leaves the screen
+	queue_free()

+ 46 - 9
2d/space_shooter/asteroid.tscn → 2d/space_shooter/enemies/asteroid/asteroid.tscn

@@ -1,8 +1,11 @@
 [gd_scene load_steps=9 format=1]
 
-[ext_resource path="res://asteroid.gd" type="Script" id=1]
-[ext_resource path="res://meteorite.png" type="Texture" id=2]
-[ext_resource path="res://sound_explode.wav" type="Sample" id=3]
+[ext_resource path="res://enemies/asteroid/asteroid.gd" type="Script" id=1]
+[ext_resource path="res://enemies/asteroid/meteorite.png" type="Texture" id=2]
+[ext_resource path="res://effects/sounds/sound_explode.wav" type="Sample" id=3]
+
+
+
 
 [sub_resource type="CircleShape2D" id=1]
 
@@ -18,15 +21,35 @@ step = 0.1
 tracks/0/type = "value"
 tracks/0/path = NodePath("particles:config/emitting")
 tracks/0/interp = 1
-tracks/0/keys = { "cont":false, "times":FloatArray( 0, 0.1 ), "transitions":FloatArray( 1, 1 ), "values":[ true, false ] }
+tracks/0/imported = false
+tracks/0/keys = {
+"times": FloatArray( 0, 0.1 ),
+"transitions": FloatArray( 1, 1 ),
+"update": 1,
+"values": [ true, false ]
+}
 tracks/1/type = "value"
 tracks/1/path = NodePath("sprite:visibility/visible")
 tracks/1/interp = 1
-tracks/1/keys = { "cont":false, "times":FloatArray( 0 ), "transitions":FloatArray( 1 ), "values":[ false ] }
+tracks/1/imported = false
+tracks/1/keys = {
+"times": FloatArray( 0 ),
+"transitions": FloatArray( 1 ),
+"update": 1,
+"values": [ false ]
+}
 tracks/2/type = "method"
 tracks/2/path = NodePath(".")
 tracks/2/interp = 1
-tracks/2/keys = { "times":FloatArray( 0.7 ), "transitions":FloatArray( 1 ), "values":[ { "args":[  ], "method":"queue_free" } ] }
+tracks/2/imported = false
+tracks/2/keys = {
+"times": FloatArray( 0.7 ),
+"transitions": FloatArray( 1 ),
+"values": [ {
+"args": [  ],
+"method": "queue_free"
+} ]
+}
 
 [sub_resource type="Animation" id=3]
 
@@ -36,7 +59,13 @@ step = 0.1
 tracks/0/type = "value"
 tracks/0/path = NodePath("sprite:transform/rot")
 tracks/0/interp = 1
-tracks/0/keys = { "cont":true, "times":FloatArray( 0, 3 ), "transitions":FloatArray( 1, 1 ), "values":[ 0.0, 360.0 ] }
+tracks/0/imported = false
+tracks/0/keys = {
+"times": FloatArray( 0, 3 ),
+"transitions": FloatArray( 1, 1 ),
+"update": 0,
+"values": [ 0.0, 360.0 ]
+}
 
 [sub_resource type="ColorRamp" id=4]
 
@@ -45,9 +74,16 @@ colors = ColorArray( 1, 1, 1, 1, 1, 1, 1, 0 )
 
 [sub_resource type="SampleLibrary" id=5]
 
-samples/sound_explode = { "db":0.0, "pitch":1.0, "sample":ExtResource( 3 ) }
+samples/sound_explode = {
+"db": 0.0,
+"pitch": 1.0,
+"priority": 0,
+"sample": ExtResource( 3 )
+}
 
-[node name="asteroid" type="Area2D"]
+[node name="asteroid" type="Area2D" groups=[
+"enemy",
+]]
 
 input/pickable = true
 shapes/0/shape = SubResource( 1 )
@@ -90,6 +126,7 @@ rect = Rect2( -10, -10, 20, 20 )
 config/amount = 32
 config/lifetime = 0.5
 config/emitting = false
+config/process_mode = 1
 config/half_extents = Vector2( 20, 20 )
 config/explosiveness = 0.1
 config/texture = ExtResource( 2 )

+ 0 - 0
2d/space_shooter/meteorite.png → 2d/space_shooter/enemies/asteroid/meteorite.png


+ 2 - 0
2d/space_shooter/enemies/asteroid/meteorite.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 77 - 0
2d/space_shooter/enemies/shooter/enemy2.gd

@@ -0,0 +1,77 @@
+extends Area2D
+
+# horizontal movement speed
+const SPEED = -220
+
+# how many points does the player get for destroying this ship?
+const POINTS = 10
+
+# time between shots in seconds
+const SHOOT_INTERVAL = 1
+
+# the enemy's projectile scene
+const Shot = preload("res://enemies/shooter/enemy_shot.tscn")
+
+# used to store this enemy's motion direction
+var motion = Vector2()
+
+var destroyed = false
+
+# remaining timeout until the enemy can fire again
+var shoot_timeout = 0
+
+# the node in the tree where this enemy should spawn its bullets into
+var projectile_container
+
+# the Position2D that defines where the bullets will be spawned
+onready var shoot_from = get_node("shoot_from")
+
+# used to notify listeners about this enemy's death
+signal enemy_died(score)
+
+func _ready():
+	motion = Vector2(SPEED, 0)
+
+func _fixed_process(delta):
+	# the enemy constantly moves
+	translate(motion * delta)
+	
+	# count down the time until the next shot
+	if shoot_timeout > 0.0:
+		shoot_timeout -= delta
+	
+	if (shoot_timeout <= 0):
+		shoot_timeout = SHOOT_INTERVAL
+		
+		if projectile_container != null:
+			# Instance a shot
+			var shot = Shot.instance()
+			# Set position to "shoot_from" Position2D node's global position
+			shot.set_pos(shoot_from.get_global_pos())
+			# Add it to the projectile container, making its movement independent from ours
+			projectile_container.add_child(shot)
+
+func set_projectile_container(container):
+	projectile_container = container
+
+func destroy():
+	# skip if already destroyed
+	if (destroyed):
+		return
+	destroyed = true
+	
+	# stop processing
+	set_fixed_process(false)
+	# play on-death effects
+	get_node("sfx").play("sound_explode")
+	get_node("anim").play("explode")
+	# inform listeners about death, while sending the point value along with it
+	emit_signal("enemy_died", POINTS)
+	call_deferred("set_enable_monitoring", false)
+	call_deferred("set_monitorable", false)
+
+func _on_visibility_enter_screen():
+	set_fixed_process(true)
+
+func _on_visibility_exit_screen():
+	queue_free()

+ 0 - 0
2d/space_shooter/enemy2.png → 2d/space_shooter/enemies/shooter/enemy2.png


+ 2 - 0
2d/space_shooter/enemies/shooter/enemy2.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 40 - 12
2d/space_shooter/enemy2.tscn → 2d/space_shooter/enemies/shooter/enemy2.tscn

@@ -1,9 +1,12 @@
 [gd_scene load_steps=8 format=1]
 
-[ext_resource path="res://enemy2.gd" type="Script" id=1]
-[ext_resource path="res://enemy2.png" type="Texture" id=2]
-[ext_resource path="res://explosion.tscn" type="PackedScene" id=3]
-[ext_resource path="res://sound_explode.wav" type="Sample" id=4]
+[ext_resource path="res://enemies/shooter/enemy2.gd" type="Script" id=1]
+[ext_resource path="res://enemies/shooter/enemy2.png" type="Texture" id=2]
+[ext_resource path="res://effects/particles/explosion.tscn" type="PackedScene" id=3]
+[ext_resource path="res://effects/sounds/sound_explode.wav" type="Sample" id=4]
+
+
+
 
 [sub_resource type="ConvexPolygonShape2D" id=1]
 
@@ -18,21 +21,48 @@ step = 0.1
 tracks/0/type = "value"
 tracks/0/path = NodePath("explosion:config/emitting")
 tracks/0/interp = 1
-tracks/0/keys = { "cont":false, "times":FloatArray( 0, 0.1 ), "transitions":FloatArray( 1, 1 ), "values":[ true, false ] }
+tracks/0/imported = false
+tracks/0/keys = {
+"times": FloatArray( 0, 0.1 ),
+"transitions": FloatArray( 1, 1 ),
+"update": 1,
+"values": [ true, false ]
+}
 tracks/1/type = "value"
 tracks/1/path = NodePath("sprite:visibility/visible")
 tracks/1/interp = 1
-tracks/1/keys = { "cont":false, "times":FloatArray( 0 ), "transitions":FloatArray( 1 ), "values":[ false ] }
+tracks/1/imported = false
+tracks/1/keys = {
+"times": FloatArray( 0 ),
+"transitions": FloatArray( 1 ),
+"update": 1,
+"values": [ false ]
+}
 tracks/2/type = "method"
 tracks/2/path = NodePath(".")
 tracks/2/interp = 1
-tracks/2/keys = { "times":FloatArray( 0.9 ), "transitions":FloatArray( 1 ), "values":[ { "args":[  ], "method":"queue_free" } ] }
+tracks/2/imported = false
+tracks/2/keys = {
+"times": FloatArray( 0.9 ),
+"transitions": FloatArray( 1 ),
+"values": [ {
+"args": [  ],
+"method": "queue_free"
+} ]
+}
 
 [sub_resource type="SampleLibrary" id=3]
 
-samples/sound_explode = { "db":0.0, "pitch":1.0, "sample":ExtResource( 4 ) }
+samples/sound_explode = {
+"db": 0.0,
+"pitch": 1.0,
+"priority": 0,
+"sample": ExtResource( 4 )
+}
 
-[node name="enemy2" type="Area2D"]
+[node name="enemy2" type="Area2D" groups=[
+"enemy",
+]]
 
 input/pickable = true
 shapes/0/shape = SubResource( 1 )
@@ -57,9 +87,7 @@ texture = ExtResource( 2 )
 
 [node name="explosion" parent="." instance=ExtResource( 3 )]
 
-transform/rot = -91.1436
-config/explosiveness = 0.1
-params/gravity_strength = 9.8
+config/process_mode = 1
 
 [node name="anim" type="AnimationPlayer" parent="."]
 

+ 7 - 6
2d/space_shooter/enemy_shot.gd → 2d/space_shooter/enemies/shooter/enemy_shot.gd

@@ -1,4 +1,3 @@
-
 extends Area2D
 
 # Member variables
@@ -6,19 +5,18 @@ const SPEED = -800
 
 var hit = false
 
+var motion = Vector2()
 
 func _process(delta):
-	translate(Vector2(delta*SPEED, 0))
-
+	translate(motion * delta)
 
 func _ready():
+	motion = Vector2(SPEED, 0)
 	set_process(true)
 
-
 func is_enemy():
 	return true
 
-
 func _hit_something():
 	if (hit):
 		return
@@ -26,6 +24,9 @@ func _hit_something():
 	set_process(false)
 	get_node("anim").play("splash")
 
-
 func _on_visibility_exit_screen():
 	queue_free()
+
+func _on_enemy_shot_area_enter(area):
+	if area.is_in_group("player"):
+		area.take_damage()

+ 0 - 0
2d/space_shooter/enemy_shot.png → 2d/space_shooter/enemies/shooter/enemy_shot.png


+ 2 - 0
2d/space_shooter/enemies/shooter/enemy_shot.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 37 - 8
2d/space_shooter/enemy_shot.tscn → 2d/space_shooter/enemies/shooter/enemy_shot.tscn

@@ -1,7 +1,8 @@
 [gd_scene load_steps=6 format=1]
 
-[ext_resource path="res://enemy_shot.gd" type="Script" id=1]
-[ext_resource path="res://enemy_shot.png" type="Texture" id=2]
+[ext_resource path="res://enemies/shooter/enemy_shot.gd" type="Script" id=1]
+[ext_resource path="res://enemies/shooter/enemy_shot.png" type="Texture" id=2]
+
 
 [sub_resource type="RectangleShape2D" id=1]
 
@@ -21,15 +22,35 @@ step = 0.1
 tracks/0/type = "value"
 tracks/0/path = NodePath("hit_splash:config/emitting")
 tracks/0/interp = 1
-tracks/0/keys = { "cont":false, "times":FloatArray( 0, 0.1 ), "transitions":FloatArray( 1, 1 ), "values":[ true, false ] }
+tracks/0/imported = false
+tracks/0/keys = {
+"times": FloatArray( 0, 0.1 ),
+"transitions": FloatArray( 1, 1 ),
+"update": 1,
+"values": [ true, false ]
+}
 tracks/1/type = "method"
 tracks/1/path = NodePath(".")
 tracks/1/interp = 1
-tracks/1/keys = { "times":FloatArray( 0.9 ), "transitions":FloatArray( 1 ), "values":[ { "args":[  ], "method":"queue_free" } ] }
+tracks/1/imported = false
+tracks/1/keys = {
+"times": FloatArray( 0.9 ),
+"transitions": FloatArray( 1 ),
+"values": [ {
+"args": [  ],
+"method": "queue_free"
+} ]
+}
 tracks/2/type = "value"
 tracks/2/path = NodePath("sprite:visibility/visible")
 tracks/2/interp = 1
-tracks/2/keys = { "cont":false, "times":FloatArray( 0 ), "transitions":FloatArray( 1 ), "values":[ false ] }
+tracks/2/imported = false
+tracks/2/keys = {
+"times": FloatArray( 0 ),
+"transitions": FloatArray( 1 ),
+"update": 1,
+"values": [ false ]
+}
 
 [node name="enemy_shot" type="Area2D"]
 
@@ -45,9 +66,8 @@ script/script = ExtResource( 1 )
 
 [node name="visibility" type="VisibilityNotifier2D" parent="."]
 
-transform/pos = Vector2( 1.8353, -0.0742126 )
-transform/scale = Vector2( 1.54149, 0.770745 )
-rect = Rect2( -10, -10, 20, 20 )
+transform/pos = Vector2( 1, 0 )
+rect = Rect2( -13, -5, 24, 10 )
 
 [node name="sprite" type="Sprite" parent="."]
 
@@ -58,12 +78,16 @@ texture = ExtResource( 2 )
 shape = SubResource( 1 )
 trigger = false
 _update_shape_index = -1
+__meta__ = {
+"_edit_lock_": true
+}
 
 [node name="hit_splash" type="Particles2D" parent="."]
 
 config/amount = 32
 config/lifetime = 0.5
 config/emitting = false
+config/process_mode = 1
 config/explosiveness = 0.1
 params/direction = 0.0
 params/spread = 180.0
@@ -82,6 +106,9 @@ params/hue_variation = 0.0
 params/anim_speed_scale = 1.0
 params/anim_initial_pos = 0.0
 color/color_ramp = SubResource( 2 )
+__meta__ = {
+"_edit_lock_": true
+}
 
 [node name="anim" type="AnimationPlayer" parent="."]
 
@@ -94,6 +121,8 @@ playback/speed = 1.0
 blend_times = [  ]
 autoplay = ""
 
+[connection signal="area_enter" from="." to="." method="_on_enemy_shot_area_enter"]
+
 [connection signal="exit_screen" from="visibility" to="." method="_on_visibility_exit_screen"]
 
 

+ 41 - 0
2d/space_shooter/enemies/ufo/enemy1.gd

@@ -0,0 +1,41 @@
+extends Area2D
+
+# how many points does the player get for destroying this ship?
+const POINTS = 5
+
+var destroyed = false
+
+# used to notify listeners about this enemy's death
+signal enemy_died(score)
+
+func _ready():
+	# the ship will start its zigzag movement at a random offset in its animation path
+	# this skip is visible for about one frame
+	# as a workaround, we hide the node until the animation seeking is later completed
+	hide()
+
+func destroy():
+	# skip if already destroyed
+	if (destroyed):
+		return
+	
+	# set the state to destroyed
+	destroyed = true
+	# play on-death effects
+	# take note of how the explode animation also frees the parent node after 0.9 seconds
+	get_node("anim").play("explode")
+	get_node("sfx").play("sound_explode")
+	# inform listeners about death, while sending the point value along with it
+	emit_signal("enemy_died", POINTS)
+	# disable physics interactions
+	call_deferred("set_enable_monitoring", false)
+	call_deferred("set_monitorable", false)
+
+# this signal is connected in the editor
+func _on_visibility_enter_screen():
+	get_node("anim").play("zigzag")
+	 # randomly offset the animation's start point
+	get_node("anim").seek(randf()*2.0)
+	# as mentioned in _ready, show the node after seeking to the random start point in the animation
+	show()
+	

+ 0 - 0
2d/space_shooter/enemy1.png → 2d/space_shooter/enemies/ufo/enemy1.png


+ 2 - 0
2d/space_shooter/enemies/ufo/enemy1.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 57 - 20
2d/space_shooter/enemy1.tscn → 2d/space_shooter/enemies/ufo/enemy1.tscn

@@ -1,9 +1,13 @@
-[gd_scene load_steps=9 format=1]
+[gd_scene load_steps=10 format=1]
+
+[ext_resource path="res://enemies/ufo/enemy1_rail.gd" type="Script" id=1]
+[ext_resource path="res://enemies/ufo/enemy1.gd" type="Script" id=2]
+[ext_resource path="res://enemies/ufo/enemy1.png" type="Texture" id=3]
+[ext_resource path="res://effects/particles/explosion.tscn" type="PackedScene" id=4]
+[ext_resource path="res://effects/sounds/sound_explode.wav" type="Sample" id=5]
+
+
 
-[ext_resource path="res://enemy1.gd" type="Script" id=1]
-[ext_resource path="res://enemy1.png" type="Texture" id=2]
-[ext_resource path="res://explosion.tscn" type="PackedScene" id=3]
-[ext_resource path="res://sound_explode.wav" type="Sample" id=4]
 
 [sub_resource type="ConvexPolygonShape2D" id=1]
 
@@ -18,15 +22,35 @@ step = 0.1
 tracks/0/type = "value"
 tracks/0/path = NodePath("sprite:visibility/visible")
 tracks/0/interp = 1
-tracks/0/keys = { "cont":false, "times":FloatArray( 0 ), "transitions":FloatArray( 1 ), "values":[ false ] }
+tracks/0/imported = false
+tracks/0/keys = {
+"times": FloatArray( 0 ),
+"transitions": FloatArray( 1 ),
+"update": 1,
+"values": [ false ]
+}
 tracks/1/type = "value"
 tracks/1/path = NodePath("explosion:config/emitting")
 tracks/1/interp = 1
-tracks/1/keys = { "cont":false, "times":FloatArray( 0, 0.1 ), "transitions":FloatArray( 1, 1 ), "values":[ true, false ] }
+tracks/1/imported = false
+tracks/1/keys = {
+"times": FloatArray( 0, 0.1 ),
+"transitions": FloatArray( 1, 1 ),
+"update": 1,
+"values": [ true, false ]
+}
 tracks/2/type = "method"
 tracks/2/path = NodePath("..")
 tracks/2/interp = 1
-tracks/2/keys = { "times":FloatArray( 0.9 ), "transitions":FloatArray( 1 ), "values":[ { "args":[  ], "method":"queue_free" } ] }
+tracks/2/imported = false
+tracks/2/keys = {
+"times": FloatArray( 0.9 ),
+"transitions": FloatArray( 1 ),
+"values": [ {
+"args": [  ],
+"method": "queue_free"
+} ]
+}
 
 [sub_resource type="Animation" id=3]
 
@@ -36,15 +60,32 @@ step = 0.1
 tracks/0/type = "value"
 tracks/0/path = NodePath(".:transform/pos")
 tracks/0/interp = 1
-tracks/0/keys = { "cont":true, "times":FloatArray( 0, 1 ), "transitions":FloatArray( -1.86607, -1.86607 ), "values":[ Vector2( 0, -100 ), Vector2( 0, 100 ) ] }
+tracks/0/imported = false
+tracks/0/keys = {
+"times": FloatArray( 0, 1 ),
+"transitions": FloatArray( -1.86607, -1.86607 ),
+"update": 0,
+"values": [ Vector2( 0, -100 ), Vector2( 0, 100 ) ]
+}
 
 [sub_resource type="SampleLibrary" id=4]
 
-samples/sound_explode = { "db":0.0, "pitch":1.0, "sample":ExtResource( 4 ) }
+samples/sound_explode = {
+"db": 0.0,
+"pitch": 1.0,
+"priority": 0,
+"sample": ExtResource( 5 )
+}
 
-[node name="enemy1" type="Node2D"]
+[node name="enemy1" type="Node2D" groups=[
+"enemy",
+]]
 
-[node name="area" type="Area2D" parent="."]
+script/script = ExtResource( 1 )
+
+[node name="area" type="Area2D" parent="." groups=[
+"enemy",
+]]
 
 transform/pos = Vector2( 0, -100 )
 input/pickable = true
@@ -55,7 +96,7 @@ gravity_vec = Vector2( 0, 1 )
 gravity = 98.0
 linear_damp = 0.1
 angular_damp = 1.0
-script/script = ExtResource( 1 )
+script/script = ExtResource( 2 )
 
 [node name="collision" type="CollisionPolygon2D" parent="area"]
 
@@ -66,13 +107,11 @@ trigger = false
 
 [node name="sprite" type="Sprite" parent="area"]
 
-texture = ExtResource( 2 )
+texture = ExtResource( 3 )
 
-[node name="explosion" parent="area" instance=ExtResource( 3 )]
+[node name="explosion" parent="area" instance=ExtResource( 4 )]
 
-transform/rot = -91.1436
-config/explosiveness = 0.1
-params/gravity_strength = 9.8
+config/process_mode = 1
 
 [node name="anim" type="AnimationPlayer" parent="area"]
 
@@ -103,6 +142,4 @@ config/pitch_random = 0.0
 
 [connection signal="enter_screen" from="area/visibility" to="area" method="_on_visibility_enter_screen"]
 
-[connection signal="exit_screen" from="area/visibility" to="area" method="_on_visibility_exit_screen"]
-
 

+ 33 - 0
2d/space_shooter/enemies/ufo/enemy1_rail.gd

@@ -0,0 +1,33 @@
+extends Node2D
+
+# the rail's horizontal movement speed
+const SPEED = -200
+
+# used to store this enemy rail's motion direction
+var motion = Vector2()
+
+# keep references to child nodes
+onready var ship = get_node("area")
+onready var visibility = get_node("area/visibility")
+
+signal enemy_died(score)
+
+func _ready():
+	# connect to the actual ship's death signal so it can be relayed further up the tree
+	# the relay is required because the player collides with the ship's area node, but the level connects to each enemy scene's root node
+	ship.connect("enemy_died", self, "on_ship_died")
+	# once the ship is in range, start moving the rail
+	visibility.connect("enter_screen", self, "set_fixed_process", [true])
+	# once the ship leaves the screen, remove the entire node
+	visibility.connect("exit_screen", self, "queue_free")
+	motion = Vector2(SPEED, 0)
+	
+func _fixed_process(delta):
+	# constant movement
+	translate(motion * delta)
+
+func on_ship_died(score):
+	# stop moving the rail
+	set_fixed_process(false)
+	# relay the death signal upwards
+	emit_signal("enemy_died", score)

+ 0 - 36
2d/space_shooter/enemy1.gd

@@ -1,36 +0,0 @@
-
-extends Area2D
-
-# Member variables
-const SPEED = -200
-
-var destroyed=false
-
-
-func _fixed_process(delta):
-	get_parent().translate(Vector2(SPEED*delta, 0))
-
-
-func is_enemy():
-	return not destroyed
-
-
-func destroy():
-	if (destroyed):
-		return
-	destroyed = true
-	get_node("anim").play("explode")
-	set_fixed_process(false)
-	get_node("sfx").play("sound_explode")
-	# Accumulate points
-	get_node("/root/game_state").points += 5
-
-
-func _on_visibility_enter_screen():
-	set_fixed_process(true)
-	get_node("anim").play("zigzag")
-	get_node("anim").seek(randf()*2.0) # Make it start from any pos
-
-
-func _on_visibility_exit_screen():
-	queue_free()

+ 0 - 47
2d/space_shooter/enemy2.gd

@@ -1,47 +0,0 @@
-
-extends Area2D
-
-# Member variables
-const SPEED = -220
-const SHOOT_INTERVAL = 1
-
-var shoot_timeout = 0
-var destroyed=false
-
-
-func _fixed_process(delta):
-	translate(Vector2(SPEED*delta, 0))
-	shoot_timeout -= delta
-	
-	if (shoot_timeout < 0):
-		shoot_timeout = SHOOT_INTERVAL
-		
-		# Instance a shot
-		var shot = preload("res://enemy_shot.tscn").instance()
-		# Set pos as "shoot_from" Position2D node
-		shot.set_pos(get_node("shoot_from").get_global_pos())
-		# Add it to parent, so it has world coordinates
-		get_parent().add_child(shot)
-
-
-func is_enemy():
-	return not destroyed
-
-
-func destroy():
-	if (destroyed):
-		return
-	destroyed = true
-	get_node("anim").play("explode")
-	set_fixed_process(false)
-	get_node("sfx").play("sound_explode")
-	# Accumulate points
-	get_node("/root/game_state").points += 10
-
-
-func _on_visibility_enter_screen():
-	set_fixed_process(true)
-
-
-func _on_visibility_exit_screen():
-	queue_free()

+ 5 - 2
2d/space_shooter/engine.cfg

@@ -1,15 +1,18 @@
 [application]
 
 name="Space Shooter"
-main_scene="res://main_menu.tscn"
+main_scene="res://main_menu/main_menu.tscn"
 icon="res://icon.png"
 
 [autoload]
 
-game_state="res://game_state.gd"
+game_state="*res://game_state.gd"
 
 [display]
 
+resizable=true
+stretch_mode="2d"
+stretch_aspect="keep"
 width=1024
 height=600
 

+ 25 - 0
2d/space_shooter/game_screen/hud/game_hud.gd

@@ -0,0 +1,25 @@
+extends CanvasLayer
+
+onready var score_label = get_node("score_points")
+onready var return_button = get_node("back_to_menu")
+onready var game_over_label = get_node("game_over")
+
+signal return_to_menu
+
+func _ready():
+	return_button.connect("pressed", self, "_on_return_to_menu")
+	set_process(true)
+	set_process_input(true)
+
+func _process(delta):
+	if Input.is_action_pressed("ui_cancel"):
+		_on_return_to_menu()
+		
+func update_score(score):
+	score_label.set_text(str(score))
+	
+func _on_return_to_menu():
+	emit_signal("return_to_menu")
+	
+func game_over():
+	game_over_label.show()

+ 77 - 0
2d/space_shooter/game_screen/hud/game_hud.tscn

@@ -0,0 +1,77 @@
+[gd_scene load_steps=2 format=1]
+
+[ext_resource path="res://game_screen/hud/game_hud.gd" type="Script" id=1]
+
+
+[node name="hud" type="CanvasLayer"]
+
+layer = 1
+offset = Vector2( 0, 0 )
+rotation = 0.0
+scale = Vector2( 1, 1 )
+script/script = ExtResource( 1 )
+
+[node name="score" type="Label" parent="."]
+
+focus/ignore_mouse = true
+focus/stop_mouse = true
+size_flags/horizontal = 2
+size_flags/vertical = 0
+margin/left = 15.0
+margin/top = 13.0
+margin/right = 66.0
+margin/bottom = 26.0
+text = "SCORE:"
+percent_visible = 1.0
+lines_skipped = 0
+max_lines_visible = -1
+
+[node name="score_points" type="Label" parent="."]
+
+focus/ignore_mouse = true
+focus/stop_mouse = true
+size_flags/horizontal = 2
+size_flags/vertical = 0
+margin/left = 70.0
+margin/top = 13.0
+margin/right = 121.0
+margin/bottom = 26.0
+text = "0"
+align = 1
+percent_visible = 1.0
+lines_skipped = 0
+max_lines_visible = -1
+
+[node name="back_to_menu" type="Button" parent="."]
+
+focus/ignore_mouse = false
+focus/stop_mouse = true
+size_flags/horizontal = 2
+size_flags/vertical = 2
+margin/left = 911.0
+margin/top = 10.0
+margin/right = 1006.0
+margin/bottom = 31.0
+toggle_mode = false
+enabled_focus_mode = 2
+shortcut = null
+text = "Back to Menu"
+flat = false
+
+[node name="game_over" type="Label" parent="."]
+
+visibility/visible = false
+focus/ignore_mouse = true
+focus/stop_mouse = true
+size_flags/horizontal = 2
+size_flags/vertical = 0
+margin/left = 482.0
+margin/top = 286.0
+margin/right = 564.0
+margin/bottom = 299.0
+text = "GAME_OVER"
+percent_visible = 1.0
+lines_skipped = 0
+max_lines_visible = -1
+
+

+ 284 - 0
2d/space_shooter/game_screen/level/enemies.tscn

@@ -0,0 +1,284 @@
+[gd_scene load_steps=4 format=1]
+
+[ext_resource path="res://enemies/asteroid/asteroid.tscn" type="PackedScene" id=1]
+[ext_resource path="res://enemies/ufo/enemy1.tscn" type="PackedScene" id=2]
+[ext_resource path="res://enemies/shooter/enemy2.tscn" type="PackedScene" id=3]
+
+
+
+
+[node name="enemies" type="Node2D"]
+
+[node name="asteroid" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 1797.52, 105.736 )
+
+[node name="asteroid1" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 1666.61, 304.621 )
+
+[node name="asteroid2" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 1988.85, 443.086 )
+
+[node name="asteroid3" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 2595.58, 103.219 )
+
+[node name="asteroid4" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 3229.99, 299.586 )
+
+[node name="asteroid5" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 3592.52, 541.269 )
+
+[node name="asteroid6" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 4571.84, 216.508 )
+
+[node name="asteroid7" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 4571.84, 284.481 )
+
+[node name="asteroid8" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 4571.84, 360.007 )
+
+[node name="asteroid9" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 5140.8, 108.254 )
+
+[node name="asteroid10" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 5168.5, 475.814 )
+
+[node name="asteroid11" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 5767.67, 113.289 )
+
+[node name="asteroid12" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 6107.53, 480.849 )
+
+[node name="asteroid13" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 6364.32, 105.736 )
+
+[node name="asteroid14" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 6731.88, 573.997 )
+
+[node name="asteroid15" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 7033.99, 166.157 )
+
+[node name="asteroid16" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 6424.74, 352.454 )
+
+[node name="asteroid17" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 7263.08, 80.5608 )
+
+[node name="asteroid18" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 7177.49, 541.269 )
+
+[node name="asteroid19" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 7066.71, 344.902 )
+
+[node name="asteroid20" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 7655.82, 118.324 )
+
+[node name="asteroid21" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 7540.01, 324.762 )
+
+[node name="asteroid22" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 7764.07, 566.445 )
+
+[node name="asteroid23" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 7872.33, 216.508 )
+
+[node name="asteroid24" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 8458.91, 95.666 )
+
+[node name="asteroid25" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 8786.19, 231.613 )
+
+[node name="asteroid26" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 8599.89, 551.339 )
+
+[node name="asteroid27" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 8353.17, 289.516 )
+
+[node name="asteroid28" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 8995.14, 95.6658 )
+
+[node name="asteroid29" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 9294.73, 579.032 )
+
+[node name="asteroid30" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 9392.91, 140.981 )
+
+[node name="asteroid31" parent="." instance=ExtResource( 1 )]
+
+transform/pos = Vector2( 9644.67, 281.963 )
+
+[node name="enemy1" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 2920.34, 365.042 )
+
+[node name="enemy2" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 3894.62, 506.024 )
+
+[node name="enemy3" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 4325.12, 302.104 )
+
+[node name="enemy4" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 4753.1, 506.024 )
+
+[node name="enemy5" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 5158.43, 211.473 )
+
+[node name="enemy6" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 5490.74, 349.937 )
+
+[node name="enemy7" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 5765.15, 546.305 )
+
+[node name="enemy8" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 6142.78, 244.201 )
+
+[node name="enemy9" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 6701.67, 221.543 )
+
+[node name="enemy10" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 6701.67, 352.455 )
+
+[node name="enemy11" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 6706.71, 500.989 )
+
+[node name="enemy12" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 6711.74, 566.445 )
+
+[node name="enemy13" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 7157.35, 332.314 )
+
+[node name="enemy14" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 7421.69, 511.059 )
+
+[node name="enemy15" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 7887.43, 239.166 )
+
+[node name="enemy16" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 8463.95, 382.665 )
+
+[node name="enemy17" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 9065.64, 244.201 )
+
+[node name="enemy18" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 8967.46, 566.445 )
+
+[node name="enemy19" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 9483.55, 422.946 )
+
+[node name="enemy20" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 9687.47, 234.131 )
+
+[node name="enemy21" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 9815.86, 579.033 )
+
+[node name="enemy22" parent="." instance=ExtResource( 2 )]
+
+transform/pos = Vector2( 9815.86, 579.033 )
+
+[node name="enemy2 2" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 4759.97, 278.527 )
+
+[node name="enemy23" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 6277.15, 559.36 )
+
+[node name="enemy24" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 7136.77, 100.438 )
+
+[node name="enemy25" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 7766.93, 370.996 )
+
+[node name="enemy26" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 7890.23, 309.35 )
+
+[node name="enemy27" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 8006.67, 237.43 )
+
+[node name="enemy28" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 8664.23, 257.978 )
+
+[node name="enemy29" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 8660.8, 357.297 )
+
+[node name="enemy30" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 8657.38, 453.191 )
+
+[node name="enemy31" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 9475.9, 189.483 )
+
+[node name="enemy32" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 9564.95, 234.005 )
+
+[node name="enemy33" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 9674.54, 281.952 )
+
+[node name="enemy34" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 9575.22, 391.545 )
+
+[node name="enemy35" parent="." instance=ExtResource( 3 )]
+
+transform/pos = Vector2( 9458.78, 446.342 )
+
+

+ 33 - 0
2d/space_shooter/game_screen/level/level.gd

@@ -0,0 +1,33 @@
+extends Node2D
+
+onready var hud = get_node("hud")
+onready var player_rail = get_node("player_ship_on_rail")
+onready var player_ship = player_rail.player_ship
+onready var level_map = get_node("level_map")
+onready var projectiles = get_node("projectiles")
+
+func _ready():
+	# tell the player ship where to instance its projectiles
+	player_ship.set_projectile_container(projectiles)
+	# connect to the player's death signal
+	player_ship.connect("player_died", self, "on_player_died")
+	# find all enemies in the currently loaded level and connect to their death signals for scoring
+	for enemy in level_map.enemy_container.get_children():
+		# for instances of enemy1, this check is being done against their root "rail" Node2D
+		# the actual collision later checks their area's group
+		if enemy.is_in_group("enemy"):
+			enemy.connect("enemy_died", self, "on_enemy_died")
+	hud.connect("return_to_menu", self, "on_return_to_menu")
+
+# notifies the hud and game state about the player's death
+func on_player_died():
+	game_state.game_over()
+	hud.game_over()
+
+# notifies the game_state and hud about enemy deaths, which report the enemy's point value
+func on_enemy_died(score):
+	game_state.points += score
+	hud.update_score(game_state.points)
+	
+func on_return_to_menu():
+	game_state.abort_game()

+ 29 - 0
2d/space_shooter/game_screen/level/level.tscn

@@ -0,0 +1,29 @@
+[gd_scene load_steps=6 format=1]
+
+[ext_resource path="res://game_screen/level/level.gd" type="Script" id=1]
+[ext_resource path="res://player/player_ship_on_rail.tscn" type="PackedScene" id=2]
+[ext_resource path="res://effects/background/parallax.tscn" type="PackedScene" id=3]
+[ext_resource path="res://game_screen/hud/game_hud.tscn" type="PackedScene" id=4]
+[ext_resource path="res://game_screen/level/level_map.tscn" type="PackedScene" id=5]
+
+
+
+
+
+[node name="space_shooter_game" type="Node2D"]
+
+script/script = ExtResource( 1 )
+
+[node name="player_ship_on_rail" parent="." instance=ExtResource( 2 )]
+
+[node name="parallax" parent="." instance=ExtResource( 3 )]
+
+[node name="hud" parent="." instance=ExtResource( 4 )]
+
+[node name="level_map" parent="." instance=ExtResource( 5 )]
+
+[node name="projectiles" type="Node2D" parent="." groups=[
+"projectile_container",
+]]
+
+

+ 12 - 0
2d/space_shooter/game_screen/level/level_map.gd

@@ -0,0 +1,12 @@
+extends Node2D
+
+onready var enemy_container = get_node("enemies")
+onready var enemy_projectile_container = get_node("enemy_projectiles")
+
+func _ready():
+	var all_enemies = enemy_container.get_children()
+	for enemy in all_enemies:
+		# find all enemies who need a projectile container because they can shoot
+		if enemy.has_method("set_projectile_container"):
+			# tell those enemies about the container
+			enemy.set_projectile_container(enemy_projectile_container)

+ 18 - 0
2d/space_shooter/game_screen/level/level_map.tscn

@@ -0,0 +1,18 @@
+[gd_scene load_steps=4 format=1]
+
+[ext_resource path="res://game_screen/level/level_map.gd" type="Script" id=1]
+[ext_resource path="res://game_screen/level/enemies.tscn" type="PackedScene" id=2]
+[ext_resource path="res://game_screen/level/tilemap_level.tscn" type="PackedScene" id=3]
+
+
+[node name="LevelMap" type="Node2D"]
+
+script/script = ExtResource( 1 )
+
+[node name="enemies" parent="." instance=ExtResource( 2 )]
+
+[node name="TileMap" parent="." instance=ExtResource( 3 )]
+
+[node name="enemy_projectiles" type="Node2D" parent="."]
+
+

+ 0 - 0
2d/space_shooter/tile.png → 2d/space_shooter/game_screen/level/tile.png


+ 2 - 0
2d/space_shooter/game_screen/level/tile.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 21 - 0
2d/space_shooter/game_screen/level/tilemap_level.tscn


+ 5 - 1
2d/space_shooter/tileset.tres → 2d/space_shooter/game_screen/level/tileset.tres

@@ -1,6 +1,7 @@
 [gd_resource type="TileSet" load_steps=3 format=1]
 
-[ext_resource path="res://tile.png" type="Texture" id=1]
+[ext_resource path="res://game_screen/level/tile.png" type="Texture" id=1]
+
 
 [sub_resource type="RectangleShape2D" id=1]
 
@@ -12,9 +13,12 @@ extents = Vector2( 16, 16 )
 0/name = "block"
 0/texture = ExtResource( 1 )
 0/tex_offset = Vector2( 0, 0 )
+0/modulate = Color( 1, 1, 1, 1 )
 0/region = Rect2( 0, 0, 0, 0 )
 0/occluder_offset = Vector2( 16, 16 )
 0/navigation_offset = Vector2( 16, 16 )
 0/shape_offset = Vector2( 16, 16 )
 0/shapes = [ SubResource( 1 ) ]
+0/one_way_collision_direction = Vector2( 0, 0 )
+0/one_way_collision_max_depth = 0.0
 

+ 65 - 7
2d/space_shooter/game_state.gd

@@ -1,22 +1,80 @@
-
+# Script has to extend any Node class because it's an Autoload
+# Autoloads are put into the Scene Tree, and only Nodes can live there
 extends Node
 
-# Member variables
+# points in current round
 var points = 0
+# maximum points ever achieved
 var max_points = 0
 
+# file path to the highscore file, stored in the user directory
+# see the docs for the actual path, which depends on operating system / platform
+# http://docs.godotengine.org/en/2.1/learning/features/misc/data_paths.html
+const HIGHSCORE_PATH = "user://highscore"
+
+# preloading the game's menu and actual game screen
+const main_menu_scene = preload("res://main_menu/main_menu.tscn")
+const main_level_scene = preload("res://game_screen/level/level.tscn")
+
+var menu
+var game
 
 func _ready():
-	var f = File.new()
 	# Load high score
-	if (f.open("user://highscore", File.READ) == OK):
-		max_points = f.get_var()
+	_load_high_score()
+
+func start_game():
+	# reset the points
+	points = 0
+	_reset_game()
 
+	# instance the game scene
+	game = main_level_scene.instance()
+	# tell the scene tree to switch the current scene
+	get_tree().get_root().add_child(game)
+
+func abort_game():
+	_reset_game()
+	
+	menu = main_menu_scene.instance()
+	get_tree().get_root().add_child(menu)
 
 func game_over():
 	if (points > max_points):
 		max_points = points
 		# Save high score
-		var f = File.new()
-		f.open("user://highscore", File.WRITE)
+		_save_high_score()
+
+func _reset_game():
+	if game != null:
+		game.hide()
+		game.queue_free()
+		game = null
+		
+	if menu != null:
+		menu.hide()
+		menu.queue_free()
+		menu = null
+
+func _load_high_score():
+	# start off with 0 max points in a fresh game
+	max_points = 0
+	# initialize a file handler
+	var f = File.new()
+	# check for existing highscore file
+	if f.file_exists(HIGHSCORE_PATH):
+		# if it exists, try to open the file in READ mode
+		if f.open(HIGHSCORE_PATH, File.READ) == OK:
+			# read the current high score as a godot Variant and store it in max_points
+			max_points = f.get_var()
+	# always close the file handle
+	f.close()
+	
+func _save_high_score():
+	var f = File.new()
+	# try to open the highscore file in WRITE mode, which creates a new file if it doesn't exist
+	if f.open(HIGHSCORE_PATH, File.WRITE) == OK:
+		# store the max points as a godot Variant
 		f.store_var(max_points)
+	# always close the file handle
+	f.close()

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 27
2d/space_shooter/level.tscn


+ 0 - 11
2d/space_shooter/main_menu.gd

@@ -1,11 +0,0 @@
-
-extends Control
-
-
-func _ready():
-	get_node("score").set_text("HIGH SCORE: " + str(get_node("/root/game_state").max_points))
-
-
-func _on_play_pressed():
-	get_node("/root/game_state").points = 0
-	get_tree().change_scene("res://level.tscn")

+ 0 - 64
2d/space_shooter/main_menu.tscn

@@ -1,64 +0,0 @@
-[gd_scene load_steps=2 format=1]
-
-[ext_resource path="res://main_menu.gd" type="Script" id=1]
-
-[node name="main_screen" type="Control"]
-
-anchor/right = 1
-anchor/bottom = 1
-focus/ignore_mouse = false
-focus/stop_mouse = true
-size_flags/horizontal = 2
-size_flags/vertical = 2
-margin/left = 0.0
-margin/top = 0.0
-margin/right = 0.0
-margin/bottom = 0.0
-script/script = ExtResource( 1 )
-
-[node name="title" type="Label" parent="."]
-
-focus/ignore_mouse = true
-focus/stop_mouse = true
-size_flags/horizontal = 2
-margin/left = 405.0
-margin/top = 86.0
-margin/right = 547.0
-margin/bottom = 99.0
-text = "S P A C E  S H O O T E R"
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[node name="score" type="Label" parent="."]
-
-focus/ignore_mouse = true
-focus/stop_mouse = true
-size_flags/horizontal = 2
-margin/left = 349.0
-margin/top = 204.0
-margin/right = 585.0
-margin/bottom = 218.0
-text = "HIGH SCORE:"
-align = 1
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[node name="play" type="Button" parent="."]
-
-focus/ignore_mouse = false
-focus/stop_mouse = true
-size_flags/horizontal = 2
-size_flags/vertical = 2
-margin/left = 412.0
-margin/top = 390.0
-margin/right = 535.0
-margin/bottom = 442.0
-toggle_mode = false
-text = "PLAY"
-flat = false
-
-[connection signal="pressed" from="play" to="." method="_on_play_pressed"]
-
-

+ 18 - 0
2d/space_shooter/main_menu/main_menu.gd

@@ -0,0 +1,18 @@
+extends Control
+
+# get a reference to the score label
+onready var score_label = get_node("VBoxContainer/score")
+# set up a string format template for the high score display
+const HIGH_SCORE_TEXT = "HIGH SCORE: %d"
+
+func _ready():
+	# game_state is an Autoloaded Singleton (see Project Settings), making it globally available and persistent
+	game_state.menu = self
+	# grab the current highscore from game_state and update the score label
+	score_label.set_text(HIGH_SCORE_TEXT % game_state.max_points)
+
+# response function for the "play" button's "pressed" signal
+# the connection is set up on the "play" node, using the "Signals" sub-tab in the "Node" dock
+func _on_play_pressed():
+	# tell the game_state to start a new game, which resets the current score to 0 and switches to the level scene
+	game_state.start_game()

+ 106 - 0
2d/space_shooter/main_menu/main_menu.tscn

@@ -0,0 +1,106 @@
+[gd_scene load_steps=4 format=1]
+
+[ext_resource path="res://main_menu/main_menu.gd" type="Script" id=1]
+[ext_resource path="res://main_menu/space.png" type="Texture" id=2]
+[ext_resource path="res://effects/background/parallax.tscn" type="PackedScene" id=3]
+
+[node name="main_screen" type="Control"]
+
+anchor/right = 1
+anchor/bottom = 1
+focus/ignore_mouse = false
+focus/stop_mouse = true
+size_flags/horizontal = 2
+size_flags/vertical = 2
+margin/left = 0.0
+margin/top = 0.0
+margin/right = 0.0
+margin/bottom = 0.0
+script/script = ExtResource( 1 )
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+
+anchor/right = 1
+anchor/bottom = 1
+focus/ignore_mouse = false
+focus/stop_mouse = false
+size_flags/horizontal = 3
+size_flags/vertical = 3
+margin/left = 0.0
+margin/top = 0.0
+margin/right = 0.0
+margin/bottom = 0.0
+custom_constants/separation = 16
+alignment = 1
+
+[node name="TextureFrame" type="TextureFrame" parent="VBoxContainer"]
+
+focus/ignore_mouse = true
+focus/stop_mouse = true
+size_flags/horizontal = 2
+size_flags/vertical = 2
+margin/left = 0.0
+margin/top = 62.0
+margin/right = 1024.0
+margin/bottom = 230.0
+texture = ExtResource( 2 )
+stretch_mode = 6
+
+[node name="title" type="Label" parent="VBoxContainer"]
+
+rect/min_size = Vector2( 1024, 100 )
+focus/ignore_mouse = true
+focus/stop_mouse = true
+size_flags/horizontal = 3
+size_flags/vertical = 0
+margin/left = 0.0
+margin/top = 246.0
+margin/right = 1024.0
+margin/bottom = 346.0
+text = "S P A C E  S H O O T E R"
+align = 1
+valign = 1
+percent_visible = 1.0
+lines_skipped = 0
+max_lines_visible = -1
+
+[node name="score" type="Label" parent="VBoxContainer"]
+
+rect/min_size = Vector2( 0, 100 )
+focus/ignore_mouse = true
+focus/stop_mouse = true
+size_flags/horizontal = 3
+size_flags/vertical = 0
+margin/left = 0.0
+margin/top = 362.0
+margin/right = 1024.0
+margin/bottom = 462.0
+text = "HIGH SCORE:"
+align = 1
+valign = 1
+percent_visible = 1.0
+lines_skipped = 0
+max_lines_visible = -1
+
+[node name="play" type="Button" parent="VBoxContainer"]
+
+rect/min_size = Vector2( 120, 60 )
+focus/ignore_mouse = false
+focus/stop_mouse = true
+size_flags/horizontal = 0
+size_flags/vertical = 0
+margin/left = 452.0
+margin/top = 478.0
+margin/right = 572.0
+margin/bottom = 538.0
+toggle_mode = false
+enabled_focus_mode = 2
+shortcut = null
+text = "PLAY"
+flat = false
+
+[node name="parallax" parent="." instance=ExtResource( 3 )]
+
+[connection signal="pressed" from="VBoxContainer/play" to="." method="_on_play_pressed"]
+
+

BIN
2d/space_shooter/main_menu/space.png


+ 2 - 0
2d/space_shooter/main_menu/space.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 176 - 0
2d/space_shooter/player/player_ship.tscn

@@ -0,0 +1,176 @@
+[gd_scene load_steps=12 format=1]
+
+[ext_resource path="res://player/ship.gd" type="Script" id=1]
+[ext_resource path="res://player/ship.png" type="Texture" id=2]
+[ext_resource path="res://effects/particles/fire.png" type="Texture" id=3]
+[ext_resource path="res://effects/particles/explosion.tscn" type="PackedScene" id=4]
+[ext_resource path="res://effects/sounds/sound_shoot.wav" type="Sample" id=5]
+[ext_resource path="res://effects/sounds/sound_explode.wav" type="Sample" id=6]
+
+
+
+
+[sub_resource type="ConvexPolygonShape2D" id=1]
+
+custom_solver_bias = 0.0
+points = Vector2Array( 25.9104, 1.3603, -20.5637, 14.8656, -20.5637, -15.3227 )
+
+[sub_resource type="ColorRamp" id=2]
+
+offsets = FloatArray( 0, 0.474062, 0.653631, 1 )
+colors = ColorArray( 0.154794, 0.413313, 0.991004, 1, 0.555474, 0.971578, 0, 1, 0.82934, 0.989088, 0.616085, 0.383915, 1, 1, 1, 0 )
+
+[sub_resource type="Animation" id=3]
+
+length = 1.0
+loop = false
+step = 0.1
+tracks/0/type = "value"
+tracks/0/path = NodePath("sprite:visibility/visible")
+tracks/0/interp = 1
+tracks/0/imported = false
+tracks/0/keys = {
+"times": FloatArray( 0, 0.1 ),
+"transitions": FloatArray( 1, 1 ),
+"update": 1,
+"values": [ true, false ]
+}
+tracks/1/type = "value"
+tracks/1/path = NodePath("thruster:config/emitting")
+tracks/1/interp = 1
+tracks/1/imported = false
+tracks/1/keys = {
+"times": FloatArray( 0 ),
+"transitions": FloatArray( 1 ),
+"update": 1,
+"values": [ false ]
+}
+tracks/2/type = "value"
+tracks/2/path = NodePath("explosion:config/emitting")
+tracks/2/interp = 1
+tracks/2/imported = false
+tracks/2/keys = {
+"times": FloatArray( 0, 0.1 ),
+"transitions": FloatArray( 1, 1 ),
+"update": 1,
+"values": [ true, false ]
+}
+
+[sub_resource type="ColorRamp" id=4]
+
+offsets = FloatArray( 0, 0.364725, 0.77494, 1 )
+colors = ColorArray( 1, 1, 1, 1, 1, 0, 0, 1, 0.184473, 0.181601, 0.181345, 1, 1, 1, 1, 0 )
+
+[sub_resource type="SampleLibrary" id=5]
+
+samples/shoot = {
+"db": 0.0,
+"pitch": 1.0,
+"priority": 0,
+"sample": ExtResource( 5 )
+}
+samples/sound_explode = {
+"db": 0.0,
+"pitch": 1.0,
+"priority": 0,
+"sample": ExtResource( 6 )
+}
+
+[node name="ship" type="Area2D" groups=[
+"player",
+]]
+
+transform/pos = Vector2( 253.607, 282.275 )
+input/pickable = true
+shapes/0/shape = SubResource( 1 )
+shapes/0/transform = Matrix32( 1, 0, 0, 1, 0, 0 )
+shapes/0/trigger = false
+gravity_vec = Vector2( 0, 1 )
+gravity = 98.0
+linear_damp = 0.1
+angular_damp = 1.0
+script/script = ExtResource( 1 )
+
+[node name="sprite" type="Sprite" parent="."]
+
+texture = ExtResource( 2 )
+
+[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="."]
+
+build_mode = 0
+polygon = Vector2Array( -20.5637, -15.3227, 25.9104, 1.3603, -20.5637, 14.8656 )
+shape_range = Vector2( -1, -1 )
+trigger = false
+
+[node name="thruster" type="Particles2D" parent="."]
+
+visibility/blend_mode = 1
+transform/pos = Vector2( -26.528, -0.358481 )
+transform/rot = -91.1436
+config/amount = 32
+config/lifetime = 2.0
+config/time_scale = 5.0
+config/emitting = false
+config/process_mode = 1
+config/local_space = false
+config/texture = ExtResource( 3 )
+params/direction = 0.0
+params/spread = 10.0
+params/linear_velocity = 20.0
+params/spin_velocity = 0.0
+params/orbit_velocity = 0.0
+params/gravity_direction = 0.0
+params/gravity_strength = 0.0
+params/radial_accel = 0.0
+params/tangential_accel = 0.0
+params/damping = 0.0
+params/initial_angle = 0.0
+params/initial_size = 1.0
+params/final_size = 1.0
+params/hue_variation = 0.0
+params/anim_speed_scale = 1.0
+params/anim_initial_pos = 0.0
+color/color_ramp = SubResource( 2 )
+
+[node name="anim" type="AnimationPlayer" parent="."]
+
+playback/process_mode = 1
+playback/default_blend_time = 0.0
+root/root = NodePath("..")
+anims/explode = SubResource( 3 )
+playback/active = true
+playback/speed = 1.0
+blend_times = [  ]
+autoplay = ""
+
+[node name="shoot_from" type="Position2D" parent="."]
+
+transform/pos = Vector2( 35.3307, 0.875969 )
+
+[node name="explosion" parent="." instance=ExtResource( 4 )]
+
+config/process_mode = 1
+color/color_ramp = SubResource( 4 )
+
+[node name="sfx" type="SamplePlayer" parent="."]
+
+config/polyphony = 1
+config/samples = SubResource( 5 )
+default/volume_db = 0.0
+default/pitch_scale = 1.0
+default/pan = 0.0
+default/depth = 0.0
+default/height = 0.0
+default/filter/type = 0
+default/filter/cutoff = 0.0
+default/filter/resonance = 0.0
+default/filter/gain = 0.0
+default/reverb_room = 2
+default/reverb_send = 0.0
+default/chorus_send = 0.0
+
+[connection signal="area_enter" from="." to="." method="_on_ship_area_enter"]
+
+[connection signal="body_enter" from="." to="." method="_on_ship_body_enter"]
+
+

+ 32 - 0
2d/space_shooter/player/player_ship_on_rail.tscn

@@ -0,0 +1,32 @@
+[gd_scene load_steps=3 format=1]
+
+[ext_resource path="res://player/rail.gd" type="Script" id=1]
+[ext_resource path="res://player/player_ship.tscn" type="PackedScene" id=2]
+
+
+[node name="rail" type="Node2D"]
+
+script/script = ExtResource( 1 )
+
+[node name="ship" parent="." instance=ExtResource( 2 )]
+
+[node name="camera" type="Camera2D" parent="."]
+
+anchor_mode = 0
+rotating = false
+current = true
+zoom = Vector2( 1, 1 )
+limit/left = -10000000
+limit/top = -10000000
+limit/right = 10000000
+limit/bottom = 10000000
+drag_margin/h_enabled = true
+drag_margin/v_enabled = true
+smoothing/enable = false
+smoothing/speed = 5.0
+drag_margin/left = 0.2
+drag_margin/top = 0.2
+drag_margin/right = 0.2
+drag_margin/bottom = 0.2
+
+

+ 27 - 0
2d/space_shooter/player/rail.gd

@@ -0,0 +1,27 @@
+extends Node2D
+
+# Member variables
+# on-rails movement speed of the game area
+const SPEED = 200
+# current offset of the game area
+var offset = 0
+
+var motion = Vector2()
+
+# reference to the actual ship that is controlled by the player
+onready var player_ship = get_node("ship")
+
+# the rail is moved during _fixed_process and should stop on player death
+func stop():
+	set_fixed_process(false)
+
+func _fixed_process(delta):
+	# move the rail at SPEED pixels per second
+	translate(motion * delta)
+
+func _ready():
+	motion = Vector2(SPEED, 0)
+	# connect the stop method to the player_ship's player_died signal
+	player_ship.connect("player_died", self, "stop")
+	# start processing
+	set_fixed_process(true)

+ 105 - 0
2d/space_shooter/player/ship.gd

@@ -0,0 +1,105 @@
+extends Area2D
+
+# Member variables
+const SPEED = 200
+const SHOT_COOLDOWN = 0.16
+
+const Shot = preload("res://player/shot.tscn")
+
+var screen_size
+var killed = false
+var can_shoot = true
+
+var shot_timer = 0
+
+var projectile_container
+
+onready var shot_anchor = get_node("shoot_from")
+
+signal player_died
+
+func _fixed_process(delta):
+	var motion = Vector2()
+	if Input.is_action_pressed("move_up"):
+		motion += Vector2(0, -1)
+	if Input.is_action_pressed("move_down"):
+		motion += Vector2(0, 1)
+	if Input.is_action_pressed("move_left"):
+		motion += Vector2(-1, 0)
+	if Input.is_action_pressed("move_right"):
+		motion += Vector2(1, 0)
+	var shooting = Input.is_action_pressed("shoot")
+	
+	var pos = get_pos()
+	
+	# normally you would normalize the motion vector using motion.normalized(), so diagonal movement isn't faster
+	# in this case, the base speed would make dodging the tilemap impossible in some places
+	# additionally, it could be explained as the ship using both horizontal and vertical thrusters at once
+	# the better solution in the long run would be to playtest the level and make sure that every passage is playable
+	# pos += motion.normalized() * delta * SPEED
+	pos += motion * delta * SPEED
+	
+	# limit the resulting position to the screen's dimensions, so the player can't fly off screen
+	pos.x = clamp(pos.x, 0, screen_size.x)
+	pos.y = clamp(pos.y, 0, screen_size.y)
+	
+	set_pos(pos)
+	
+	# tick down the shot cooldown
+	if shot_timer > 0.0:
+		shot_timer -= delta
+	
+	# the player can shoot if the timer is back to zero
+	can_shoot = shot_timer <= 0.0
+	
+	# if the player is alive, allowed to shoot and pressing space to shoot..
+	if (can_shoot and shooting and not killed):
+		# instance a shot
+		var shot = Shot.instance()
+		# Use the Position2D named "shoot_from" as reference
+		shot.set_pos(shot_anchor.get_global_pos())
+		# add the shot to projectile container so it moves independently from the ship
+		if projectile_container != null:
+			projectile_container.add_child(shot)
+			# Play sound
+			get_node("sfx").play("shoot")
+			# delay the next shot
+			shot_timer = SHOT_COOLDOWN
+
+func _ready():
+	screen_size = get_viewport().get_rect().size
+	set_fixed_process(true)
+
+func _hit_something():
+	if (killed):
+		return
+	killed = true
+	# disable the collider
+	call_deferred("set_enable_monitoring", false)
+	call_deferred("set_monitorable", false)
+	# play on-death effects
+	get_node("anim").play("explode")
+	get_node("sfx").play("sound_explode")
+	# notify listeners that the player died
+	emit_signal("player_died")
+	# disable processing 
+	set_fixed_process(false)
+
+# the block tiles in a level have StaticBody2D colliders, touching them kills the player ship
+func _on_ship_body_enter(body):
+	_hit_something()
+
+# colliding with the area of an enemy (asteroid, enemy1, enemy2 scenes) kills the player ship
+func _on_ship_area_enter(area):
+	# check if the colliding node is in the "enemy" node group
+	if area.is_in_group("enemy"):
+		_hit_something()
+
+# setup function to obtain a reference to the bullet container node
+func set_projectile_container(container):
+	projectile_container = container
+
+# other objects (enemy projectiles) use this to tell the player ship that it was hit
+func take_damage():
+	_hit_something()
+	

+ 0 - 0
2d/space_shooter/ship.png → 2d/space_shooter/player/ship.png


+ 2 - 0
2d/space_shooter/player/ship.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 0 - 0
2d/space_shooter/shoot.png → 2d/space_shooter/player/shoot.png


+ 2 - 0
2d/space_shooter/player/shoot.png.flags

@@ -0,0 +1,2 @@
+filter=false
+gen_mipmaps=false

+ 7 - 9
2d/space_shooter/shot.gd → 2d/space_shooter/player/shot.gd

@@ -1,40 +1,38 @@
-
 extends Area2D
 
 # Member variables
 const SPEED = 800
 
 var hit = false
-
+var motion = Vector2()
 
 func _process(delta):
-	translate(Vector2(delta*SPEED, 0))
-
+	translate(motion * delta)
 
 func _ready():
+	motion = Vector2(SPEED, 0)
 	set_process(true)
 
-
 func _hit_something():
 	if (hit):
 		return
 	hit = true
 	set_process(false)
 	get_node("anim").play("splash")
-
+	# disable collisions
+	call_deferred("set_enable_monitoring", false)
+	call_deferred("set_monitorable", false)
 
 func _on_visibility_exit_screen():
 	queue_free()
 
-
 func _on_shot_area_enter(area):
 	# Hit an enemy or asteroid
 	if (area.has_method("destroy")):
-		# Duck typing at it's best
+		# Duck typing at its best
 		area.destroy()
 		_hit_something()
 
-
 func _on_shot_body_enter(body):
 	# Hit the tilemap
 	_hit_something()

+ 43 - 12
2d/space_shooter/shot.tscn → 2d/space_shooter/player/shot.tscn

@@ -1,7 +1,8 @@
 [gd_scene load_steps=6 format=1]
 
-[ext_resource path="res://shot.gd" type="Script" id=1]
-[ext_resource path="res://shoot.png" type="Texture" id=2]
+[ext_resource path="res://player/shot.gd" type="Script" id=1]
+[ext_resource path="res://player/shoot.png" type="Texture" id=2]
+
 
 [sub_resource type="RectangleShape2D" id=1]
 
@@ -21,17 +22,39 @@ step = 0.1
 tracks/0/type = "value"
 tracks/0/path = NodePath("hit_splash:config/emitting")
 tracks/0/interp = 1
-tracks/0/keys = { "cont":false, "times":FloatArray( 0, 0.1 ), "transitions":FloatArray( 1, 1 ), "values":[ true, false ] }
+tracks/0/imported = false
+tracks/0/keys = {
+"times": FloatArray( 0, 0.1 ),
+"transitions": FloatArray( 1, 1 ),
+"update": 1,
+"values": [ true, false ]
+}
 tracks/1/type = "method"
 tracks/1/path = NodePath(".")
 tracks/1/interp = 1
-tracks/1/keys = { "times":FloatArray( 1 ), "transitions":FloatArray( 1 ), "values":[ { "args":[  ], "method":"queue_free" } ] }
+tracks/1/imported = false
+tracks/1/keys = {
+"times": FloatArray( 1 ),
+"transitions": FloatArray( 1 ),
+"values": [ {
+"args": [  ],
+"method": "queue_free"
+} ]
+}
 tracks/2/type = "value"
 tracks/2/path = NodePath("sprite:visibility/visible")
 tracks/2/interp = 1
-tracks/2/keys = { "cont":false, "times":FloatArray( 0 ), "transitions":FloatArray( 1 ), "values":[ false ] }
-
-[node name="shot" type="Area2D"]
+tracks/2/imported = false
+tracks/2/keys = {
+"times": FloatArray( 0 ),
+"transitions": FloatArray( 1 ),
+"update": 1,
+"values": [ false ]
+}
+
+[node name="shot" type="Area2D" groups=[
+"player_shot",
+]]
 
 input/pickable = true
 shapes/0/shape = SubResource( 1 )
@@ -45,25 +68,30 @@ script/script = ExtResource( 1 )
 
 [node name="visibility" type="VisibilityNotifier2D" parent="."]
 
-transform/pos = Vector2( 1.8353, -0.0742126 )
-transform/scale = Vector2( 1.54149, 0.770745 )
-rect = Rect2( -10, -10, 20, 20 )
+rect = Rect2( -10, -4, 20, 8 )
 
 [node name="sprite" type="Sprite" parent="."]
 
 texture = ExtResource( 2 )
+__meta__ = {
+"_edit_lock_": true
+}
 
 [node name="collision" type="CollisionShape2D" parent="."]
 
 shape = SubResource( 1 )
 trigger = false
 _update_shape_index = -1
+__meta__ = {
+"_edit_lock_": true
+}
 
 [node name="hit_splash" type="Particles2D" parent="."]
 
 config/amount = 32
 config/lifetime = 0.5
 config/emitting = false
+config/process_mode = 1
 config/explosiveness = 0.1
 params/direction = 0.0
 params/spread = 180.0
@@ -82,6 +110,9 @@ params/hue_variation = 0.0
 params/anim_speed_scale = 1.0
 params/anim_initial_pos = 0.0
 color/color_ramp = SubResource( 2 )
+__meta__ = {
+"_edit_lock_": true
+}
 
 [node name="anim" type="AnimationPlayer" parent="."]
 
@@ -94,10 +125,10 @@ playback/speed = 1.0
 blend_times = [  ]
 autoplay = ""
 
-[connection signal="body_enter" from="." to="." method="_on_shot_body_enter"]
-
 [connection signal="area_enter" from="." to="." method="_on_shot_area_enter"]
 
+[connection signal="body_enter" from="." to="." method="_on_shot_body_enter"]
+
 [connection signal="exit_screen" from="visibility" to="." method="_on_visibility_exit_screen"]
 
 

+ 0 - 19
2d/space_shooter/rail.gd

@@ -1,19 +0,0 @@
-
-extends Node2D
-
-# Member variables
-const SPEED = 200
-var offset = 0
-
-
-func stop():
-	set_fixed_process(false)
-
-
-func _fixed_process(delta):
-	offset += delta*SPEED
-	set_pos(Vector2(offset, 0))
-
-
-func _ready():
-	set_fixed_process(true)

+ 0 - 80
2d/space_shooter/ship.gd

@@ -1,80 +0,0 @@
-
-extends Area2D
-
-# Member variables
-const SPEED = 200
-
-var screen_size
-var prev_shooting = false
-var killed = false
-
-
-func _fixed_process(delta):
-	var motion = Vector2()
-	if Input.is_action_pressed("move_up"):
-		motion += Vector2(0, -1)
-	if Input.is_action_pressed("move_down"):
-		motion += Vector2(0, 1)
-	if Input.is_action_pressed("move_left"):
-		motion += Vector2(-1, 0)
-	if Input.is_action_pressed("move_right"):
-		motion += Vector2(1, 0)
-	var shooting = Input.is_action_pressed("shoot")
-	
-	var pos = get_pos()
-	
-	pos += motion*delta*SPEED
-	if (pos.x < 0):
-		pos.x = 0
-	if (pos.x > screen_size.x):
-		pos.x = screen_size.x
-	if (pos.y < 0):
-		pos.y = 0
-	if (pos.y > screen_size.y):
-		pos.y = screen_size.y
-	
-	set_pos(pos)
-	
-	if (shooting and not prev_shooting and not killed):
-		# Just pressed
-		var shot = preload("res://shot.tscn").instance()
-		# Use the Position2D as reference
-		shot.set_pos(get_node("shootfrom").get_global_pos())
-		# Put it two parents above, so it is not moved by us
-		get_node("../..").add_child(shot)
-		# Play sound
-		get_node("sfx").play("shoot")
-	
-	prev_shooting = shooting
-	
-	# Update points counter
-	get_node("../hud/score_points").set_text(str(get_node("/root/game_state").points))
-
-
-func _ready():
-	screen_size = get_viewport().get_rect().size
-	set_fixed_process(true)
-
-
-func _hit_something():
-	if (killed):
-		return
-	killed = true
-	get_node("anim").play("explode")
-	get_node("sfx").play("sound_explode")
-	get_node("../hud/game_over").show()
-	get_node("/root/game_state").game_over()
-	get_parent().stop()
-
-
-func _on_ship_body_enter(body):
-	_hit_something()
-
-
-func _on_ship_area_enter(area):
-	if (area.has_method("is_enemy") and area.is_enemy()):
-		_hit_something()
-
-
-func _on_back_to_menu_pressed():
-	get_tree().change_scene("res://main_menu.tscn")

+ 0 - 229
2d/space_shooter/ship.tscn

@@ -1,229 +0,0 @@
-[gd_scene load_steps=12 format=1]
-
-[ext_resource path="res://rail.gd" type="Script" id=1]
-[ext_resource path="res://ship.gd" type="Script" id=2]
-[ext_resource path="res://ship.png" type="Texture" id=3]
-[ext_resource path="res://fire.png" type="Texture" id=4]
-[ext_resource path="res://explosion.tscn" type="PackedScene" id=5]
-[ext_resource path="res://sound_shoot.wav" type="Sample" id=6]
-[ext_resource path="res://sound_explode.wav" type="Sample" id=7]
-
-[sub_resource type="ConvexPolygonShape2D" id=1]
-
-custom_solver_bias = 0.0
-points = Vector2Array( 25.9104, 1.3603, -20.5637, 14.8656, -20.5637, -15.3227 )
-
-[sub_resource type="ColorRamp" id=2]
-
-offsets = FloatArray( 0, 0.474062, 0.653631, 1 )
-colors = ColorArray( 0.154794, 0.413313, 0.991004, 1, 0.555474, 0.971578, 0, 1, 0.82934, 0.989088, 0.616085, 0.383915, 1, 1, 1, 0 )
-
-[sub_resource type="Animation" id=3]
-
-length = 1.0
-loop = false
-step = 0.1
-tracks/0/type = "value"
-tracks/0/path = NodePath("sprite:visibility/visible")
-tracks/0/interp = 1
-tracks/0/keys = { "cont":false, "times":FloatArray( 0, 0.1 ), "transitions":FloatArray( 1, 1 ), "values":[ true, false ] }
-tracks/1/type = "value"
-tracks/1/path = NodePath("thruster:config/emitting")
-tracks/1/interp = 1
-tracks/1/keys = { "cont":false, "times":FloatArray( 0 ), "transitions":FloatArray( 1 ), "values":[ false ] }
-tracks/2/type = "value"
-tracks/2/path = NodePath("explosion:config/emitting")
-tracks/2/interp = 1
-tracks/2/keys = { "cont":false, "times":FloatArray( 0, 0.1 ), "transitions":FloatArray( 1, 1 ), "values":[ true, false ] }
-
-[sub_resource type="SampleLibrary" id=4]
-
-samples/shoot = { "db":0.0, "pitch":1.0, "sample":ExtResource( 6 ) }
-samples/sound_explode = { "db":0.0, "pitch":1.0, "sample":ExtResource( 7 ) }
-
-[node name="rail" type="Node2D"]
-
-script/script = ExtResource( 1 )
-
-[node name="ship" type="Area2D" parent="."]
-
-transform/pos = Vector2( 253.607, 282.275 )
-input/pickable = true
-shapes/0/shape = SubResource( 1 )
-shapes/0/transform = Matrix32( 1, 0, 0, 1, 0, 0 )
-shapes/0/trigger = false
-gravity_vec = Vector2( 0, 1 )
-gravity = 98.0
-linear_damp = 0.1
-angular_damp = 1.0
-script/script = ExtResource( 2 )
-
-[node name="sprite" type="Sprite" parent="ship"]
-
-texture = ExtResource( 3 )
-
-[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="ship"]
-
-build_mode = 0
-polygon = Vector2Array( -20.5637, -15.3227, 25.9104, 1.3603, -20.5637, 14.8656 )
-shape_range = Vector2( -1, -1 )
-trigger = false
-
-[node name="thruster" type="Particles2D" parent="ship"]
-
-visibility/blend_mode = 1
-transform/pos = Vector2( -26.528, -0.358481 )
-transform/rot = -91.1436
-config/amount = 32
-config/lifetime = 2.0
-config/time_scale = 5.0
-config/emitting = false
-config/local_space = false
-config/texture = ExtResource( 4 )
-params/direction = 0.0
-params/spread = 10.0
-params/linear_velocity = 20.0
-params/spin_velocity = 0.0
-params/orbit_velocity = 0.0
-params/gravity_direction = 0.0
-params/gravity_strength = 0.0
-params/radial_accel = 0.0
-params/tangential_accel = 0.0
-params/damping = 0.0
-params/initial_angle = 0.0
-params/initial_size = 1.0
-params/final_size = 1.0
-params/hue_variation = 0.0
-params/anim_speed_scale = 1.0
-params/anim_initial_pos = 0.0
-color/color_ramp = SubResource( 2 )
-
-[node name="anim" type="AnimationPlayer" parent="ship"]
-
-playback/process_mode = 1
-playback/default_blend_time = 0.0
-root/root = NodePath("..")
-anims/explode = SubResource( 3 )
-playback/active = true
-playback/speed = 1.0
-blend_times = [  ]
-autoplay = ""
-
-[node name="shootfrom" type="Position2D" parent="ship"]
-
-transform/pos = Vector2( 35.3307, 0.875969 )
-
-[node name="explosion" parent="ship" instance=ExtResource( 5 )]
-
-transform/rot = -91.1436
-config/explosiveness = 0.1
-params/gravity_strength = 9.8
-
-[node name="sfx" type="SamplePlayer" parent="ship"]
-
-config/polyphony = 1
-config/samples = SubResource( 4 )
-default/volume_db = 0.0
-default/pitch_scale = 1.0
-default/pan = 0.0
-default/depth = 0.0
-default/height = 0.0
-default/filter/type = 0
-default/filter/cutoff = 0.0
-default/filter/resonance = 0.0
-default/filter/gain = 0.0
-default/reverb_room = 2
-default/reverb_send = 0.0
-default/chorus_send = 0.0
-
-[node name="camera" type="Camera2D" parent="."]
-
-anchor_mode = 0
-rotating = false
-current = true
-zoom = Vector2( 1, 1 )
-limit/left = -10000000
-limit/top = -10000000
-limit/right = 10000000
-limit/bottom = 10000000
-drag_margin/h_enabled = true
-drag_margin/v_enabled = true
-smoothing/enable = false
-smoothing/speed = 5.0
-drag_margin/left = 0.2
-drag_margin/top = 0.2
-drag_margin/right = 0.2
-drag_margin/bottom = 0.2
-
-[node name="hud" type="CanvasLayer" parent="."]
-
-layer = 1
-offset = Vector2( 0, 0 )
-rotation = 0.0
-scale = Vector2( 1, 1 )
-
-[node name="score" type="Label" parent="hud"]
-
-focus/ignore_mouse = true
-focus/stop_mouse = true
-size_flags/horizontal = 2
-margin/left = 15.0
-margin/top = 13.0
-margin/right = 66.0
-margin/bottom = 26.0
-text = "SCORE:"
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[node name="score_points" type="Label" parent="hud"]
-
-focus/ignore_mouse = true
-focus/stop_mouse = true
-size_flags/horizontal = 2
-margin/left = 70.0
-margin/top = 13.0
-margin/right = 121.0
-margin/bottom = 26.0
-text = "0"
-align = 1
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[node name="back_to_menu" type="Button" parent="hud"]
-
-focus/ignore_mouse = false
-focus/stop_mouse = true
-size_flags/horizontal = 2
-size_flags/vertical = 2
-margin/left = 911.0
-margin/top = 10.0
-margin/right = 1006.0
-margin/bottom = 31.0
-toggle_mode = false
-text = "Back to Menu"
-flat = false
-
-[node name="game_over" type="Label" parent="hud"]
-
-visibility/visible = false
-focus/ignore_mouse = true
-focus/stop_mouse = true
-size_flags/horizontal = 2
-margin/left = 482.0
-margin/top = 286.0
-margin/right = 564.0
-margin/bottom = 299.0
-text = "GAME_OVER"
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[connection signal="body_enter" from="ship" to="ship" method="_on_ship_body_enter"]
-
-[connection signal="area_enter" from="ship" to="ship" method="_on_ship_area_enter"]
-
-[connection signal="pressed" from="hud/back_to_menu" to="ship" method="_on_back_to_menu_pressed"]
-
-

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä