Browse Source

Port the Bullet Shower demo from Godot 2.1

This demo showcases how to use low-level Servers to achieve better
CPU performance when drawing large amounts of objects.

The code has been updated for Godot 3.2, cleaned up and has received
additional comments.
Hugo Locurcio 4 years ago
parent
commit
ca0f74ee44

+ 18 - 0
2d/bullet_shower/README.md

@@ -0,0 +1,18 @@
+# Bullet Shower
+
+This demonstrates how to manage large amounts of objects efficiently using
+low-level Servers.
+
+See
+[Optimization using Servers](https://docs.godotengine.org/en/latest/tutorials/performance/using_servers.html)
+in the documentation for more information.
+
+Language: GDScript
+
+Renderer: GLES 2
+
+## Screenshots
+
+![No collision](screenshots/no_collision.png)
+
+![Collision](screenshots/collision.png)

BIN
2d/bullet_shower/bullet.png


+ 34 - 0
2d/bullet_shower/bullet.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/bullet.png-ff1424653e10246c11e3724e402c519e.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://bullet.png"
+dest_files=[ "res://.import/bullet.png-ff1424653e10246c11e3724e402c519e.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

+ 86 - 0
2d/bullet_shower/bullets.gd

@@ -0,0 +1,86 @@
+extends Node2D
+# This demo is an example of controling a high number of 2D objects with logic
+# and collision without using nodes in the scene. This technique is a lot more
+# efficient than using instancing and nodes, but requires more programming and
+# is less visual. Bullets are managed together in the `bullets.gd` script.
+
+const BULLET_COUNT = 500
+const SPEED_MIN = 20
+const SPEED_MAX = 80
+
+const bullet_image = preload("res://bullet.png")
+
+var bullets = []
+var shape
+
+
+class Bullet:
+	var position = Vector2()
+	var speed = 1.0
+	# The body is stored as a RID, which is an "opaque" way to access resources.
+	# With large amounts of objects (thousands or more), it can be significantly
+	# faster to use RIDs compared to a high-level approach.
+	var body = RID()
+
+
+func _ready():
+	randomize()
+
+	shape = Physics2DServer.circle_shape_create()
+	# Set the collision shape's radius for each bullet in pixels.
+	Physics2DServer.shape_set_data(shape, 8)
+
+	for _i in BULLET_COUNT:
+		var bullet = Bullet.new()
+		# Give each bullet its own speed.
+		bullet.speed = rand_range(SPEED_MIN, SPEED_MAX)
+		bullet.body = Physics2DServer.body_create()
+
+		Physics2DServer.body_set_space(bullet.body, get_world_2d().get_space())
+		Physics2DServer.body_add_shape(bullet.body, shape)
+
+		# Place bullets randomly on the viewport and move bullets outside the
+		# play area so that they fade in nicely.
+		bullet.position = Vector2(
+			rand_range(0, get_viewport_rect().size.x) + get_viewport_rect().size.x,
+			rand_range(0, get_viewport_rect().size.y)
+		)
+		var transform2d = Transform2D()
+		transform2d.origin = bullet.position
+		Physics2DServer.body_set_state(bullet.body, Physics2DServer.BODY_STATE_TRANSFORM, transform2d)
+
+		bullets.push_back(bullet)
+
+
+func _process(delta):
+	var transform2d = Transform2D()
+	for bullet in bullets:
+		bullet.position.x -= bullet.speed * delta
+
+		if bullet.position.x < -16:
+			# The bullet has left the screen; move it back to the right.
+			bullet.position.x = get_viewport_rect().size.x + 16
+
+		transform2d.origin = bullet.position
+
+		Physics2DServer.body_set_state(bullet.body, Physics2DServer.BODY_STATE_TRANSFORM, transform2d)
+
+	# Order the CanvasItem to update since bullets are moving every frame.
+	update()
+
+
+# Instead of drawing each bullet individually in a script attached to each bullet,
+# we are drawing *all* the bullets at once here.
+func _draw():
+	var offset = -bullet_image.get_size() * 0.5
+	for bullet in bullets:
+		draw_texture(bullet_image, bullet.position + offset)
+
+
+# Perform cleanup operations (required to exit without error messages in the console).
+func _exit_tree():
+	for bullet in bullets:
+		Physics2DServer.free_rid(bullet.body)
+
+	Physics2DServer.free_rid(shape)
+	bullets.clear()

BIN
2d/bullet_shower/face_happy.png


+ 34 - 0
2d/bullet_shower/face_happy.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/face_happy.png-38d387d31ec13459f749c93ce3d75d80.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://face_happy.png"
+dest_files=[ "res://.import/face_happy.png-38d387d31ec13459f749c93ce3d75d80.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

BIN
2d/bullet_shower/face_sad.png


+ 34 - 0
2d/bullet_shower/face_sad.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/face_sad.png-0ac7165eab24f595aba17a746a66c550.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://face_sad.png"
+dest_files=[ "res://.import/face_sad.png-0ac7165eab24f595aba17a746a66c550.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

BIN
2d/bullet_shower/icon.png


+ 34 - 0
2d/bullet_shower/icon.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.png"
+dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

+ 33 - 0
2d/bullet_shower/player.gd

@@ -0,0 +1,33 @@
+extends Node2D
+# This demo is an example of controling a high number of 2D objects with logic
+# and collision without using nodes in the scene. This technique is a lot more
+# efficient than using instancing and nodes, but requires more programming and
+# is less visual. Bullets are managed together in the `bullets.gd` script.
+
+# The number of bullets currently touched by the player.
+var touching = 0
+
+onready var sprite = $AnimatedSprite
+
+
+func _ready():
+	# The player follows the mouse cursor automatically, so there's no point
+	# in displaying the mouse cursor.
+	Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
+
+
+func _input(event):
+	if event is InputEventMouseMotion:
+		position = event.position - Vector2(0, 16)
+
+
+func _on_body_shape_entered(_body_id, _body, _body_shape, _local_shape):
+	touching += 1
+	if touching >= 1:
+		sprite.frame = 1
+
+
+func _on_body_shape_exited(_body_id, _body, _body_shape, _local_shape):
+	touching -= 1
+	if touching == 0:
+		sprite.frame = 0

+ 41 - 0
2d/bullet_shower/project.godot

@@ -0,0 +1,41 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+;   [section] ; section goes between []
+;   param=value ; assign values to parameters
+
+config_version=4
+
+_global_script_classes=[  ]
+_global_script_class_icons={
+
+}
+
+[application]
+
+config/name="Bullet Shower"
+config/description="Demonstrates how to manage large amounts of objects efficiently using low-level Servers."
+run/main_scene="res://shower.tscn"
+config/icon="res://icon.png"
+
+[display]
+
+window/dpi/allow_hidpi=true
+window/stretch/mode="2d"
+window/stretch/aspect="expand"
+
+[physics]
+
+2d/cell_size=64
+common/enable_pause_aware_picking=true
+
+[rendering]
+
+quality/driver/driver_name="GLES2"
+quality/intended_usage/framebuffer_allocation=0
+quality/intended_usage/framebuffer_allocation.mobile=0
+vram_compression/import_etc=true
+vram_compression/import_etc2=false
+environment/default_clear_color=Color( 0.133333, 0.133333, 0.2, 1 )

+ 0 - 0
2d/bullet_shower/screenshots/.gdignore


BIN
2d/bullet_shower/screenshots/collision.png


BIN
2d/bullet_shower/screenshots/no_collision.png


+ 33 - 0
2d/bullet_shower/shower.tscn

@@ -0,0 +1,33 @@
+[gd_scene load_steps=7 format=2]
+
+[ext_resource path="res://bullets.gd" type="Script" id=2]
+[ext_resource path="res://face_happy.png" type="Texture" id=3]
+[ext_resource path="res://face_sad.png" type="Texture" id=4]
+[ext_resource path="res://player.gd" type="Script" id=5]
+
+[sub_resource type="SpriteFrames" id=1]
+animations = [ {
+"frames": [ ExtResource( 3 ), ExtResource( 4 ) ],
+"loop": true,
+"name": "default",
+"speed": 5.0
+} ]
+
+[sub_resource type="CircleShape2D" id=2]
+radius = 27.0
+
+[node name="Shower" type="Node2D"]
+
+[node name="Bullets" type="Node2D" parent="."]
+script = ExtResource( 2 )
+
+[node name="Player" type="Area2D" parent="."]
+script = ExtResource( 5 )
+
+[node name="AnimatedSprite" type="AnimatedSprite" parent="Player"]
+frames = SubResource( 1 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="Player"]
+shape = SubResource( 2 )
+[connection signal="body_shape_entered" from="Player" to="Player" method="_on_body_shape_entered"]
+[connection signal="body_shape_exited" from="Player" to="Player" method="_on_body_shape_exited"]