Add a Voxel demo project

This commit is contained in:
Aaron Franke
2020-02-29 00:34:10 -05:00
parent ac5013f9ec
commit 050fda3a4c
40 changed files with 1745 additions and 0 deletions

58
3d/voxel/README.md Normal file
View File

@@ -0,0 +1,58 @@
# Voxel Game
This demo is a minimal first-person voxel game,
inspired by others such as Minecraft.
Language: GDScript
Renderer: GLES 2
## How does it work?
Each chunk is a
[`StaticBody`](https://docs.godotengine.org/en/latest/classes/class_staticbody.html)
with each block having its own
[`CollisionShape`](https://docs.godotengine.org/en/latest/classes/class_collisionshape.html)
for collisions. The meshes are created using
[`SurfaceTool`](https://docs.godotengine.org/en/latest/classes/class_surfacetool.html)
which allows specifying vertices, triangles, and UV coordinates
for constructing a mesh.
The chunks and chunk data are stored in
[`Dictionary`](https://docs.godotengine.org/en/latest/classes/class_dictionary.html)
objects. New chunks have their meshes drawn in separate
[`Thread`](https://docs.godotengine.org/en/latest/classes/class_thread.html)s,
but generating the collisions is done in the main thread, since Godot does
not support changing physics objects in a separate thread. There
are two terrain types, random blocks and flat grass. A more
complex terrain generator is out-of-scope for this demo project.
The player can place and break blocks using the
[`RayCast`](https://docs.godotengine.org/en/latest/classes/class_raycast.html)
node attached to the camera. It uses the collision information to
figure out the block position and change the block data. You can
switch the active block using the brackets or with the middle mouse button.
There is a settings menu for render distance and toggling the fog.
Settings are stored inside of an
[AutoLoad singleton](https://docs.godotengine.org/en/latest/getting_started/step_by_step/singletons_autoload.html)
called "Settings". This class will automatically save
settings, and load them when the game opens, by using the
[`File`](https://docs.godotengine.org/en/latest/classes/class_file.html) class.
Sticking to GDScript and the built-in Godot tools, as this demo does, is
quite limiting. If you are making your own voxel game, you should probably
use Zylann's voxel module instead: https://github.com/Zylann/godot_voxel
## Screenshots
![Screenshot](screenshots/blocks.png)
![Screenshot](screenshots/title.png)
## Licenses
Textures are from [Minetest](https://www.minetest.net/). Copyright © 2010-2018 Minetest contributors, CC BY-SA 3.0 Unported (Attribution-ShareAlike)
http://creativecommons.org/licenses/by-sa/3.0/
Font is "TinyUnicode" by DuffsDevice. Copyright © DuffsDevice, CC-BY (Attribution) http://www.pentacom.jp/pentacom/bitfontmaker2/gallery/?id=468

View File

@@ -0,0 +1,8 @@
[gd_resource type="AudioBusLayout" load_steps=2 format=2]
[sub_resource type="AudioEffectPitchShift" id=1]
resource_name = "PitchShift"
[resource]
bus/0/effect/0/effect = SubResource( 1 )
bus/0/effect/0/enabled = true

18
3d/voxel/default_env.tres Normal file
View File

@@ -0,0 +1,18 @@
[gd_resource type="Environment" load_steps=2 format=2]
[sub_resource type="ProceduralSky" id=1]
sun_longitude = 100.0
sun_angle_min = 2.0
sun_angle_max = 20.0
[resource]
background_mode = 2
background_sky = SubResource( 1 )
fog_enabled = true
fog_color = Color( 0.501961, 0.6, 0.701961, 1 )
fog_depth_begin = 32.0
fog_depth_end = 64.0
fog_transmit_enabled = true
dof_blur_far_enabled = true
dof_blur_far_transition = 32.0
dof_blur_far_amount = 0.05

BIN
3d/voxel/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

34
3d/voxel/icon.png.import Normal file
View File

@@ -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

BIN
3d/voxel/menu/button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/button.png-e6ddd405c0968c9fb68dca7b600a69a3.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://menu/button.png"
dest_files=[ "res://.import/button.png-e6ddd405c0968c9fb68dca7b600a69a3.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

45
3d/voxel/menu/debug.gd Normal file
View File

@@ -0,0 +1,45 @@
extends Label
# Displays some useful debug information in a Label.
onready var player = $"../Player"
onready var voxel_world = $"../VoxelWorld"
func _process(_delta):
if Input.is_action_just_pressed("debug"):
visible = !visible
text = "Position: " + _vector_to_string_appropriate_digits(player.transform.origin)
text += "\nEffective render distance: " + str(voxel_world.effective_render_distance)
text += "\nLooking: " + _cardinal_string_from_radians(player.transform.basis.get_euler().y)
text += "\nFPS: " + str(Engine.get_frames_per_second())
# Avoids the problem of showing more digits than needed or available.
func _vector_to_string_appropriate_digits(vector):
var factors = [1000, 1000, 1000]
for i in range(3):
if abs(vector[i]) > 4096:
factors[i] = factors[i] / 10
if abs(vector[i]) > 65536:
factors[i] = factors[i] / 10
if abs(vector[i]) > 524288:
factors[i] = factors[i] / 10
return "(" + \
str(round(vector.x * factors[0]) / factors[0]) + ", " + \
str(round(vector.y * factors[1]) / factors[1]) + ", " + \
str(round(vector.z * factors[2]) / factors[2]) + ")"
# Expects a rotation where 0 is North, on the range -PI to PI.
func _cardinal_string_from_radians(angle):
if angle > TAU * 3 / 8:
return "South"
if angle < -TAU * 3 / 8:
return "South"
if angle > TAU * 1 / 8:
return "West"
if angle < -TAU * 1 / 8:
return "East"
return "North"

View File

@@ -0,0 +1,38 @@
extends Control
onready var tree = get_tree()
onready var crosshair = $Crosshair
onready var pause = $Pause
onready var options = $Options
onready var voxel_world = $"../VoxelWorld"
func _process(_delta):
if Input.is_action_just_pressed("pause"):
pause.visible = crosshair.visible
crosshair.visible = !crosshair.visible
options.visible = false
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED if crosshair.visible else Input.MOUSE_MODE_VISIBLE)
func _on_Resume_pressed():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
crosshair.visible = true
pause.visible = false
func _on_Options_pressed():
options.prev_menu = pause
options.visible = true
pause.visible = false
func _on_MainMenu_pressed():
voxel_world.clean_up()
tree.change_scene("res://menu/main/main_menu.tscn")
func _on_Exit_pressed():
voxel_world.clean_up()
tree.quit()

View File

@@ -0,0 +1,133 @@
[gd_scene load_steps=6 format=2]
[ext_resource path="res://player/crosshair.svg" type="Texture" id=1]
[ext_resource path="res://menu/theme/theme.tres" type="Theme" id=2]
[ext_resource path="res://menu/options/options.tscn" type="PackedScene" id=3]
[ext_resource path="res://menu/ingame/pause_menu.gd" type="Script" id=4]
[ext_resource path="res://menu/button.png" type="Texture" id=5]
[node name="PauseMenu" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
theme = ExtResource( 2 )
script = ExtResource( 4 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Crosshair" type="CenterContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TextureRect" type="TextureRect" parent="Crosshair"]
margin_left = 784.0
margin_top = 434.0
margin_right = 816.0
margin_bottom = 466.0
texture = ExtResource( 1 )
[node name="Pause" type="VBoxContainer" parent="."]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ButtonHolder" type="HBoxContainer" parent="Pause"]
margin_right = 1600.0
margin_bottom = 900.0
size_flags_vertical = 3
alignment = 1
[node name="MainButtons" type="VBoxContainer" parent="Pause/ButtonHolder"]
margin_left = 608.0
margin_right = 992.0
margin_bottom = 900.0
custom_constants/separation = 20
alignment = 1
[node name="Resume" type="TextureButton" parent="Pause/ButtonHolder/MainButtons"]
margin_top = 292.0
margin_right = 384.0
margin_bottom = 356.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 5 )
[node name="Label" type="Label" parent="Pause/ButtonHolder/MainButtons/Resume"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Resume Game"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Options" type="TextureButton" parent="Pause/ButtonHolder/MainButtons"]
margin_top = 376.0
margin_right = 384.0
margin_bottom = 440.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 5 )
[node name="Label" type="Label" parent="Pause/ButtonHolder/MainButtons/Options"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Options"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="MainMenu" type="TextureButton" parent="Pause/ButtonHolder/MainButtons"]
margin_top = 460.0
margin_right = 384.0
margin_bottom = 524.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 5 )
[node name="Label" type="Label" parent="Pause/ButtonHolder/MainButtons/MainMenu"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Main Menu"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Exit" type="TextureButton" parent="Pause/ButtonHolder/MainButtons"]
margin_top = 544.0
margin_right = 384.0
margin_bottom = 608.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 5 )
[node name="Label" type="Label" parent="Pause/ButtonHolder/MainButtons/Exit"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Exit Game"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Options" parent="." instance=ExtResource( 3 )]
[connection signal="pressed" from="Pause/ButtonHolder/MainButtons/Resume" to="." method="_on_Resume_pressed"]
[connection signal="pressed" from="Pause/ButtonHolder/MainButtons/Options" to="." method="_on_Options_pressed"]
[connection signal="pressed" from="Pause/ButtonHolder/MainButtons/MainMenu" to="." method="_on_MainMenu_pressed"]
[connection signal="pressed" from="Pause/ButtonHolder/MainButtons/Exit" to="." method="_on_Exit_pressed"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/dark_dirt.png-8e8d84e3c30520a8995166be2b7ea97e.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://menu/main/dark_dirt.png"
dest_files=[ "res://.import/dark_dirt.png-8e8d84e3c30520a8995166be2b7ea97e.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

View File

@@ -0,0 +1,37 @@
extends Control
onready var tree = get_tree()
onready var title = $TitleScreen
onready var start = $StartGame
onready var options = $Options
func _on_Start_pressed():
start.visible = true
title.visible = false
func _on_Options_pressed():
options.prev_menu = title
options.visible = true
title.visible = false
func _on_Exit_pressed():
tree.quit()
func _on_RandomBlocks_pressed():
Settings.world_type = 0
tree.change_scene("res://world/world.tscn")
func _on_FlatGrass_pressed():
Settings.world_type = 1
tree.change_scene("res://world/world.tscn")
func _on_BackToTitle_pressed():
title.visible = true
start.visible = false

View File

@@ -0,0 +1,236 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://menu/main/title.png" type="Texture" id=1]
[ext_resource path="res://menu/main/splash_text.gd" type="Script" id=2]
[ext_resource path="res://menu/main/main_menu.gd" type="Script" id=3]
[ext_resource path="res://menu/main/dark_dirt.png" type="Texture" id=4]
[ext_resource path="res://menu/options/options.tscn" type="PackedScene" id=5]
[ext_resource path="res://menu/theme/theme.tres" type="Theme" id=6]
[ext_resource path="res://menu/button.png" type="Texture" id=7]
[node name="MainMenu" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
theme = ExtResource( 6 )
script = ExtResource( 3 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Background" type="TextureRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
texture = ExtResource( 4 )
stretch_mode = 2
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TitleScreen" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Logo" type="CenterContainer" parent="TitleScreen"]
margin_right = 1600.0
margin_bottom = 300.0
rect_min_size = Vector2( 0, 300 )
[node name="Logo" type="TextureRect" parent="TitleScreen/Logo"]
margin_left = 432.0
margin_top = 110.0
margin_right = 1168.0
margin_bottom = 190.0
texture = ExtResource( 1 )
[node name="SplashHolder" type="Control" parent="TitleScreen/Logo/Logo"]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="SplashText" type="Label" parent="TitleScreen/Logo/Logo/SplashHolder"]
modulate = Color( 1, 1, 0, 1 )
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -110.0
margin_top = 10.0
margin_right = -110.0
margin_bottom = 12.0
rect_rotation = -20.0
text = "Made in Godot!"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ButtonHolder" type="HBoxContainer" parent="TitleScreen"]
margin_top = 304.0
margin_right = 1600.0
margin_bottom = 680.0
size_flags_vertical = 3
alignment = 1
[node name="MainButtons" type="VBoxContainer" parent="TitleScreen/ButtonHolder"]
margin_left = 608.0
margin_right = 992.0
margin_bottom = 376.0
custom_constants/separation = 20
alignment = 1
[node name="Start" type="TextureButton" parent="TitleScreen/ButtonHolder/MainButtons"]
margin_top = 72.0
margin_right = 384.0
margin_bottom = 136.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 7 )
[node name="Label" type="Label" parent="TitleScreen/ButtonHolder/MainButtons/Start"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Start Game"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Options" type="TextureButton" parent="TitleScreen/ButtonHolder/MainButtons"]
margin_top = 156.0
margin_right = 384.0
margin_bottom = 220.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 7 )
[node name="Label" type="Label" parent="TitleScreen/ButtonHolder/MainButtons/Options"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Options"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Exit" type="TextureButton" parent="TitleScreen/ButtonHolder/MainButtons"]
margin_top = 240.0
margin_right = 384.0
margin_bottom = 304.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 7 )
[node name="Label" type="Label" parent="TitleScreen/ButtonHolder/MainButtons/Exit"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Exit Game"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Spacer" type="Control" parent="TitleScreen"]
margin_top = 684.0
margin_right = 1600.0
margin_bottom = 900.0
rect_min_size = Vector2( 0, 216 )
[node name="StartGame" type="HBoxContainer" parent="."]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
alignment = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="StartButtons" type="VBoxContainer" parent="StartGame"]
margin_left = 608.0
margin_right = 992.0
margin_bottom = 900.0
custom_constants/separation = 20
alignment = 1
[node name="Spacer" type="Control" parent="StartGame/StartButtons"]
margin_top = 292.0
margin_right = 384.0
margin_bottom = 356.0
rect_min_size = Vector2( 0, 64 )
[node name="RandomBlocks" type="TextureButton" parent="StartGame/StartButtons"]
margin_top = 376.0
margin_right = 384.0
margin_bottom = 440.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 7 )
[node name="Label" type="Label" parent="StartGame/StartButtons/RandomBlocks"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Random Blocks"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="FlatGrass" type="TextureButton" parent="StartGame/StartButtons"]
margin_top = 460.0
margin_right = 384.0
margin_bottom = 524.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 7 )
[node name="Label" type="Label" parent="StartGame/StartButtons/FlatGrass"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Flat Grass"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="BackToTitle" type="TextureButton" parent="StartGame/StartButtons"]
margin_top = 544.0
margin_right = 384.0
margin_bottom = 608.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 7 )
[node name="Label" type="Label" parent="StartGame/StartButtons/BackToTitle"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Back"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Options" parent="." instance=ExtResource( 5 )]
[connection signal="pressed" from="TitleScreen/ButtonHolder/MainButtons/Start" to="." method="_on_Start_pressed"]
[connection signal="pressed" from="TitleScreen/ButtonHolder/MainButtons/Options" to="." method="_on_Options_pressed"]
[connection signal="pressed" from="TitleScreen/ButtonHolder/MainButtons/Exit" to="." method="_on_Exit_pressed"]
[connection signal="pressed" from="StartGame/StartButtons/RandomBlocks" to="." method="_on_RandomBlocks_pressed"]
[connection signal="pressed" from="StartGame/StartButtons/FlatGrass" to="." method="_on_FlatGrass_pressed"]
[connection signal="pressed" from="StartGame/StartButtons/BackToTitle" to="." method="_on_BackToTitle_pressed"]

View File

@@ -0,0 +1,8 @@
extends Control
var time := 0.0
func _process(delta):
time += delta
rect_scale = Vector2.ONE * (1 - abs(sin(time * 4)) / 4)

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/title.png-73a3b55f70af530edac2d45668ba262c.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://menu/main/title.png"
dest_files=[ "res://.import/title.png-73a3b55f70af530edac2d45668ba262c.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=false
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=false
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

View File

@@ -0,0 +1,22 @@
extends Control
onready var render_distance_label = $RenderDistanceLabel
onready var render_distance_slider = $RenderDistanceSlider
onready var fog_checkbox = $FogCheckBox
func _ready():
render_distance_slider.value = Settings.render_distance
render_distance_label.text = "Render distance: " + str(Settings.render_distance)
fog_checkbox.pressed = Settings.fog_enabled
func _on_RenderDistanceSlider_value_changed(value):
Settings.render_distance = value
render_distance_label.text = "Render distance: " + str(value)
Settings.save_settings()
func _on_FogCheckBox_pressed():
Settings.fog_enabled = fog_checkbox.pressed
Settings.save_settings()

View File

@@ -0,0 +1,8 @@
extends HBoxContainer
var prev_menu
func _on_Back_pressed():
prev_menu.visible = true
visible = false

View File

@@ -0,0 +1,112 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://menu/options/options.gd" type="Script" id=1]
[ext_resource path="res://menu/options/option_buttons.gd" type="Script" id=2]
[ext_resource path="res://menu/button.png" type="Texture" id=3]
[node name="Options" type="HBoxContainer"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
alignment = 1
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="."]
margin_left = 384.0
margin_right = 1216.0
margin_bottom = 900.0
custom_constants/separation = 93
alignment = 1
[node name="OptionsBackground" type="TextureRect" parent="VBoxContainer"]
margin_top = 288.0
margin_right = 832.0
margin_bottom = 448.0
rect_min_size = Vector2( 832, 160 )
texture = ExtResource( 3 )
stretch_mode = 2
[node name="OptionButtons" type="GridContainer" parent="VBoxContainer/OptionsBackground"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -400.0
margin_top = -66.0
margin_right = 400.0
margin_bottom = 66.0
rect_min_size = Vector2( 800, 128 )
columns = 2
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="RenderDistanceLabel" type="Label" parent="VBoxContainer/OptionsBackground/OptionButtons"]
margin_right = 384.0
margin_bottom = 64.0
rect_min_size = Vector2( 384, 64 )
size_flags_vertical = 5
text = "Render distance: 3"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="RenderDistanceSlider" type="HSlider" parent="VBoxContainer/OptionsBackground/OptionButtons"]
margin_left = 388.0
margin_right = 800.0
margin_bottom = 64.0
rect_min_size = Vector2( 384, 64 )
size_flags_horizontal = 3
size_flags_vertical = 3
min_value = 3.0
max_value = 10.0
value = 7.0
[node name="FogLabel" type="Label" parent="VBoxContainer/OptionsBackground/OptionButtons"]
margin_top = 68.0
margin_right = 384.0
margin_bottom = 132.0
rect_min_size = Vector2( 384, 64 )
size_flags_vertical = 5
text = "Fog Enabled"
[node name="FogCheckBox" type="CheckBox" parent="VBoxContainer/OptionsBackground/OptionButtons"]
margin_left = 388.0
margin_top = 68.0
margin_right = 800.0
margin_bottom = 132.0
rect_min_size = Vector2( 384, 64 )
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 548.0
margin_right = 832.0
margin_bottom = 612.0
alignment = 1
[node name="Back" type="TextureButton" parent="VBoxContainer/HBoxContainer"]
margin_left = 224.0
margin_right = 608.0
margin_bottom = 64.0
rect_min_size = Vector2( 384, 64 )
texture_normal = ExtResource( 3 )
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/Back"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -1.0
margin_bottom = -18.0
text = "Back"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[connection signal="value_changed" from="VBoxContainer/OptionsBackground/OptionButtons/RenderDistanceSlider" to="VBoxContainer/OptionsBackground/OptionButtons" method="_on_RenderDistanceSlider_value_changed"]
[connection signal="pressed" from="VBoxContainer/OptionsBackground/OptionButtons/FogCheckBox" to="VBoxContainer/OptionsBackground/OptionButtons" method="_on_FogCheckBox_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Back" to="." method="_on_Back_pressed"]

View File

@@ -0,0 +1,7 @@
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://menu/theme/TinyUnicode.ttf" type="DynamicFontData" id=1]
[resource]
size = 64
font_data = ExtResource( 1 )

Binary file not shown.

View File

@@ -0,0 +1,6 @@
[gd_resource type="Theme" load_steps=2 format=2]
[ext_resource path="res://menu/theme/TinyUnicode.tres" type="DynamicFont" id=1]
[resource]
default_font = ExtResource( 1 )

View File

@@ -0,0 +1 @@
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g stroke-width="2"><path d="m12 2v10h-10v2 6h10v10h8v-10h10v-8h-10v-10z" fill-opacity=".627451"/><path d="m4 14v4l10.000161.000039-.000161 9.999961h4l-.000161-9.999961 10.000161-.000039v-4l-10.000161.000361.000161-10.000361h-4l.000161 10.000361z" fill="#fefefe" fill-opacity=".862745"/></g></svg>

After

Width:  |  Height:  |  Size: 381 B

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/crosshair.svg-c15896115a8fc4f09948d0fd31ee95e9.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://player/crosshair.svg"
dest_files=[ "res://.import/crosshair.svg-c15896115a8fc4f09948d0fd31ee95e9.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

94
3d/voxel/player/player.gd Normal file
View File

@@ -0,0 +1,94 @@
extends KinematicBody
var velocity = Vector3()
var _mouse_motion = Vector2()
var _selected_block = 6
onready var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
onready var head = $Head
onready var raycast = $Head/RayCast
onready var selected_block_texture = $SelectedBlock
onready var voxel_world = $"../VoxelWorld"
onready var crosshair = $"../PauseMenu/Crosshair"
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _process(_delta):
# Mouse movement.
_mouse_motion.y = clamp(_mouse_motion.y, -1550, 1550)
transform.basis = Basis(Vector3(0, _mouse_motion.x * -0.001, 0))
head.transform.basis = Basis(Vector3(_mouse_motion.y * -0.001, 0, 0))
# Block selection.
var position = raycast.get_collision_point()
var normal = raycast.get_collision_normal()
if Input.is_action_just_pressed("pick_block"):
# Block picking.
var block_global_position = (position - normal / 2).floor()
_selected_block = voxel_world.get_block_global_position(block_global_position)
else:
# Block prev/next keys.
if Input.is_action_just_pressed("prev_block"):
_selected_block -= 1
if Input.is_action_just_pressed("next_block"):
_selected_block += 1
_selected_block = wrapi(_selected_block, 1, 30)
# Set the appropriate texture.
var uv = Chunk.calculate_block_uvs(_selected_block)
selected_block_texture.texture.region = Rect2(uv[0] * 512, Vector2.ONE * 64)
# Block breaking/placing.
if crosshair.visible and raycast.is_colliding():
var breaking = Input.is_action_just_pressed("break")
var placing = Input.is_action_just_pressed("place")
# Either both buttons were pressed or neither are, so stop.
if breaking == placing:
return
if breaking:
var block_global_position = (position - normal / 2).floor()
voxel_world.set_block_global_position(block_global_position, 0)
elif placing:
var block_global_position = (position + normal / 2).floor()
voxel_world.set_block_global_position(block_global_position, _selected_block)
func _physics_process(delta):
# Crouching.
var crouching = Input.is_action_pressed("crouch")
if crouching:
head.transform.origin = Vector3(0, 1.2, 0)
else:
head.transform.origin = Vector3(0, 1.6, 0)
# Keyboard movement.
var movement = transform.basis.xform(Vector3(
Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
0,
Input.get_action_strength("move_back") - Input.get_action_strength("move_forward")
).normalized() * (1 if crouching else 5))
# Gravity.
velocity.y -= gravity * delta
#warning-ignore:return_value_discarded
velocity = move_and_slide(Vector3(movement.x, velocity.y, movement.z), Vector3.UP)
# Jumping, applied next frame.
if is_on_floor() and Input.is_action_pressed("jump"):
velocity.y = 5
func _input(event):
if event is InputEventMouseMotion:
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
_mouse_motion += event.relative
func chunk_pos():
return (transform.origin / Chunk.CHUNK_SIZE).floor()

View File

@@ -0,0 +1,48 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://player/player.gd" type="Script" id=1]
[ext_resource path="res://world/textures/texture_sheet.png" type="Texture" id=2]
[sub_resource type="CylinderShape" id=1]
radius = 0.4
height = 1.8
[sub_resource type="AtlasTexture" id=2]
flags = 3
atlas = ExtResource( 2 )
region = Rect2( 0, 0, 64, 64 )
[node name="Player" type="KinematicBody"]
script = ExtResource( 1 )
[node name="CollisionShape" type="CollisionShape" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0 )
shape = SubResource( 1 )
[node name="Head" type="Spatial" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0 )
[node name="Camera" type="Camera" parent="Head"]
fov = 75.0
near = 0.02
far = 1000.0
[node name="RayCast" type="RayCast" parent="Head"]
enabled = true
cast_to = Vector3( 0, 0, -5 )
[node name="SelectedBlock" type="TextureRect" parent="."]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -128.0
margin_top = -128.0
margin_right = -64.0
margin_bottom = -64.0
rect_min_size = Vector2( 64, 64 )
rect_scale = Vector2( 2, 2 )
texture = SubResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}

161
3d/voxel/project.godot Normal file
View File

@@ -0,0 +1,161 @@
; 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=[ {
"base": "StaticBody",
"class": "Chunk",
"language": "GDScript",
"path": "res://world/chunk.gd"
}, {
"base": "Resource",
"class": "TerrainGenerator",
"language": "GDScript",
"path": "res://world/terrain_generator.gd"
} ]
_global_script_class_icons={
"Chunk": "",
"TerrainGenerator": ""
}
[application]
config/name="Voxel Game"
config/description="This demo is a minimal voxel game, inspired by others such as Minecraft."
run/main_scene="res://menu/main/main_menu.tscn"
config/icon="res://icon.png"
[autoload]
Settings="*res://settings.gd"
[display]
window/size/width=1600
window/size/height=900
[input]
move_forward={
"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(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(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
]
}
move_back={
"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":83,"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":16777234,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
]
}
move_left={
"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(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(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
]
}
move_right={
"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(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(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
]
}
jump={
"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":32,"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)
]
}
crouch={
"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":16777237,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
]
}
pause={
"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":16777217,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
]
}
break={
"deadzone": 0.5,
"events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":7,"axis_value":1.0,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":7,"pressure":0.0,"pressed":false,"script":null)
]
}
place={
"deadzone": 0.5,
"events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":2,"pressed":false,"doubleclick":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":6,"axis_value":1.0,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
]
}
look_up={
"deadzone": 0.5,
"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":3,"axis_value":-1.0,"script":null)
]
}
look_down={
"deadzone": 0.5,
"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":3,"axis_value":1.0,"script":null)
]
}
look_left={
"deadzone": 0.5,
"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":2,"axis_value":-1.0,"script":null)
]
}
look_right={
"deadzone": 0.5,
"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":2,"axis_value":1.0,"script":null)
]
}
debug={
"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":16777246,"unicode":0,"echo":false,"script":null)
]
}
prev_block={
"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":123,"unicode":0,"echo":false,"script":null)
, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":4,"pressed":false,"doubleclick":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":91,"unicode":0,"echo":false,"script":null)
]
}
next_block={
"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":125,"unicode":0,"echo":false,"script":null)
, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":5,"pressed":false,"doubleclick":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":93,"unicode":0,"echo":false,"script":null)
]
}
pick_block={
"deadzone": 0.5,
"events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":3,"pressed":false,"doubleclick":false,"script":null)
]
}
[physics]
common/physics_fps=120
3d/physics_engine="Bullet"
[rendering]
quality/driver/driver_name="GLES2"
vram_compression/import_etc=true
vram_compression/import_etc2=false
gles2/debug/disable_half_float=true
environment/default_environment="res://default_env.tres"

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

40
3d/voxel/settings.gd Normal file
View File

@@ -0,0 +1,40 @@
extends Node
var render_distance = 7
var fog_enabled = true
var world_type = 0 # Not saved, only used during runtime.
var _save_path = "user://settings.json"
var _loaded = false
func _enter_tree():
if Settings._loaded:
printerr("Error: Settings is an AutoLoad singleton and it shouldn't be instanced elsewhere.")
printerr("Please delete the instance at: " + get_path())
else:
Settings._loaded = true
var file = File.new()
if file.file_exists(_save_path):
file.open(_save_path, File.READ)
while file.get_position() < file.get_len():
# Get the saved dictionary from the next line in the save file
var data = parse_json(file.get_line())
render_distance = data["render_distance"]
fog_enabled = data["fog_enabled"]
file.close()
else:
save_settings()
func save_settings():
var file = File.new()
file.open(_save_path, File.WRITE)
var data = {
"render_distance": render_distance,
"fog_enabled": fog_enabled,
}
file.store_line(to_json(data))
file.close()

216
3d/voxel/world/chunk.gd Normal file
View File

@@ -0,0 +1,216 @@
class_name Chunk
extends StaticBody
# These chunks are instanced and given data by VoxelWorld.
# After that, chunks finish setting themselves up in the _ready() function.
# If a chunk is changed, its "regenerate" method is called.
const CHUNK_SIZE = 16 # Keep in sync with TerrainGenerator.
const TEXTURE_SHEET_WIDTH = 8
const CHUNK_LAST_INDEX = CHUNK_SIZE - 1
const TEXTURE_TILE_SIZE = 1.0 / TEXTURE_SHEET_WIDTH
var data = {}
var chunk_position = Vector3() # TODO: Vector3i
var _thread
onready var voxel_world = get_parent()
func _ready():
transform.origin = chunk_position * CHUNK_SIZE
name = str(chunk_position)
if Settings.world_type == 0:
data = TerrainGenerator.random_blocks()
else:
data = TerrainGenerator.flat(chunk_position)
# We can only add colliders in the main thread due to physics limitations.
_generate_chunk_collider()
# However, we can use a thread for mesh generation.
_thread = Thread.new()
_thread.start(self, "_generate_chunk_mesh")
func regenerate():
# Clear out all old nodes first.
for c in get_children():
remove_child(c)
c.queue_free()
# Then generate new ones.
_generate_chunk_collider()
_generate_chunk_mesh(0)
func _generate_chunk_collider():
if data.empty():
# Avoid errors caused by StaticBody not having colliders.
_create_block_collider(Vector3.ZERO)
collision_layer = 0
collision_mask = 0
return
# For each block, generate a collider. Ensure collision layers are enabled.
collision_layer = 0xFFFFF
collision_mask = 0xFFFFF
for block_position in data.keys():
var block_id = data[block_position]
if block_id != 27 and block_id != 28:
_create_block_collider(block_position)
func _generate_chunk_mesh(_this_argument_exists_due_to_bug_9924):
if data.empty():
return
var surface_tool = SurfaceTool.new()
surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES)
# For each block, add data to the SurfaceTool and generate a collider.
for block_position in data.keys():
var block_id = data[block_position]
_draw_block_mesh(surface_tool, block_position, block_id)
# Create the chunk's mesh from the SurfaceTool data.
surface_tool.generate_normals()
surface_tool.generate_tangents()
surface_tool.index()
var array_mesh = surface_tool.commit()
var mi = MeshInstance.new()
mi.mesh = array_mesh
mi.material_override = preload("res://world/textures/material.tres")
add_child(mi)
func _draw_block_mesh(surface_tool, block_sub_position, block_id):
var verts = calculate_block_verts(block_sub_position)
var uvs = calculate_block_uvs(block_id)
var top_uvs = uvs
var bottom_uvs = uvs
# Bush blocks get drawn in their own special way.
if block_id == 27 or block_id == 28:
_draw_block_face(surface_tool, [verts[2], verts[0], verts[7], verts[5]], uvs)
_draw_block_face(surface_tool, [verts[7], verts[5], verts[2], verts[0]], uvs)
_draw_block_face(surface_tool, [verts[3], verts[1], verts[6], verts[4]], uvs)
_draw_block_face(surface_tool, [verts[6], verts[4], verts[3], verts[1]], uvs)
return
# Allow some blocks to have different top/bottom textures.
if block_id == 3: # Grass.
top_uvs = calculate_block_uvs(0)
bottom_uvs = calculate_block_uvs(2)
elif block_id == 5: # Furnace.
top_uvs = calculate_block_uvs(31)
bottom_uvs = top_uvs
elif block_id == 12: # Log.
top_uvs = calculate_block_uvs(30)
bottom_uvs = top_uvs
elif block_id == 19: # Bookshelf.
top_uvs = calculate_block_uvs(4)
bottom_uvs = top_uvs
# Main rendering code for normal blocks.
var other_block_position = block_sub_position + Vector3.LEFT
var other_block_id = 0
if other_block_position.x == -1:
other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
elif data.has(other_block_position):
other_block_id = data[other_block_position]
if block_id != other_block_id and is_block_transparent(other_block_id):
_draw_block_face(surface_tool, [verts[2], verts[0], verts[3], verts[1]], uvs)
other_block_position = block_sub_position + Vector3.RIGHT
other_block_id = 0
if other_block_position.x == CHUNK_SIZE:
other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
elif data.has(other_block_position):
other_block_id = data[other_block_position]
if block_id != other_block_id and is_block_transparent(other_block_id):
_draw_block_face(surface_tool, [verts[7], verts[5], verts[6], verts[4]], uvs)
other_block_position = block_sub_position + Vector3.FORWARD
other_block_id = 0
if other_block_position.z == -1:
other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
elif data.has(other_block_position):
other_block_id = data[other_block_position]
if block_id != other_block_id and is_block_transparent(other_block_id):
_draw_block_face(surface_tool, [verts[6], verts[4], verts[2], verts[0]], uvs)
other_block_position = block_sub_position + Vector3.BACK
other_block_id = 0
if other_block_position.z == CHUNK_SIZE:
other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
elif data.has(other_block_position):
other_block_id = data[other_block_position]
if block_id != other_block_id and is_block_transparent(other_block_id):
_draw_block_face(surface_tool, [verts[3], verts[1], verts[7], verts[5]], uvs)
other_block_position = block_sub_position + Vector3.DOWN
other_block_id = 0
if other_block_position.y == -1:
other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
elif data.has(other_block_position):
other_block_id = data[other_block_position]
if block_id != other_block_id and is_block_transparent(other_block_id):
_draw_block_face(surface_tool, [verts[4], verts[5], verts[0], verts[1]], bottom_uvs)
other_block_position = block_sub_position + Vector3.UP
other_block_id = 0
if other_block_position.y == CHUNK_SIZE:
other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
elif data.has(other_block_position):
other_block_id = data[other_block_position]
if block_id != other_block_id and is_block_transparent(other_block_id):
_draw_block_face(surface_tool, [verts[2], verts[3], verts[6], verts[7]], top_uvs)
func _draw_block_face(surface_tool, verts, uvs):
surface_tool.add_uv(uvs[1]); surface_tool.add_vertex(verts[1])
surface_tool.add_uv(uvs[2]); surface_tool.add_vertex(verts[2])
surface_tool.add_uv(uvs[3]); surface_tool.add_vertex(verts[3])
surface_tool.add_uv(uvs[2]); surface_tool.add_vertex(verts[2])
surface_tool.add_uv(uvs[1]); surface_tool.add_vertex(verts[1])
surface_tool.add_uv(uvs[0]); surface_tool.add_vertex(verts[0])
func _create_block_collider(block_sub_position):
var collider = CollisionShape.new()
collider.shape = BoxShape.new()
collider.shape.extents = Vector3.ONE / 2
collider.transform.origin = block_sub_position + Vector3.ONE / 2
add_child(collider)
static func calculate_block_uvs(block_id):
# This method only supports square texture sheets.
var row = block_id / TEXTURE_SHEET_WIDTH
var col = block_id % TEXTURE_SHEET_WIDTH
return [
TEXTURE_TILE_SIZE * Vector2(col, row),
TEXTURE_TILE_SIZE * Vector2(col, row + 1),
TEXTURE_TILE_SIZE * Vector2(col + 1, row),
TEXTURE_TILE_SIZE * Vector2(col + 1, row + 1),
]
static func calculate_block_verts(block_position):
return [
Vector3(block_position.x, block_position.y, block_position.z),
Vector3(block_position.x, block_position.y, block_position.z + 1),
Vector3(block_position.x, block_position.y + 1, block_position.z),
Vector3(block_position.x, block_position.y + 1, block_position.z + 1),
Vector3(block_position.x + 1, block_position.y, block_position.z),
Vector3(block_position.x + 1, block_position.y, block_position.z + 1),
Vector3(block_position.x + 1, block_position.y + 1, block_position.z),
Vector3(block_position.x + 1, block_position.y + 1, block_position.z + 1),
]
static func is_block_transparent(block_id):
return block_id == 0 or (block_id > 25 and block_id < 30)

View File

@@ -0,0 +1,17 @@
extends WorldEnvironment
# This script controls fog based on the VoxelWorld's effective render distance.
onready var voxel_world = $"../VoxelWorld"
func _process(delta):
environment.fog_enabled = Settings.fog_enabled
environment.dof_blur_far_enabled = Settings.fog_enabled
var target_distance = clamp(voxel_world.effective_render_distance, 2, voxel_world.render_distance - 1) * Chunk.CHUNK_SIZE
var rate = delta * 4
if environment.fog_depth_end > target_distance:
rate *= 2
environment.fog_depth_begin = move_toward(environment.fog_depth_begin, target_distance - Chunk.CHUNK_SIZE, rate)
environment.fog_depth_end = move_toward(environment.fog_depth_end, target_distance, rate)
environment.dof_blur_far_distance = environment.fog_depth_end

View File

@@ -0,0 +1,42 @@
class_name TerrainGenerator
extends Resource
# Can't be "Chunk.CHUNK_SIZE" due to cyclic dependency issues.
# https://github.com/godotengine/godot/issues/21461
const CHUNK_SIZE = 16
static func empty():
return {}
static func random_blocks():
var random_data = {}
for x in range(CHUNK_SIZE):
for y in range(CHUNK_SIZE):
for z in range(CHUNK_SIZE):
var vec = Vector3(x, y, z) # TODO: Vector3i
if randf() < 0.01:
random_data[vec] = randi() % 29 + 1
return random_data
static func flat(chunk_position):
var data = {}
if chunk_position.y != -1:
return data
for x in range(CHUNK_SIZE):
for z in range(CHUNK_SIZE):
data[Vector3(x, 0, z)] = 3
return data
# Used to create the project icon.
static func origin_grass(chunk_position):
if chunk_position == Vector3.ZERO:
return {Vector3.ZERO: 3}
return {}

View File

@@ -0,0 +1,8 @@
[gd_resource type="SpatialMaterial" load_steps=2 format=2]
[ext_resource path="res://world/textures/texture_sheet.png" type="Texture" id=1]
[resource]
flags_transparent = true
params_depth_draw_mode = 3
albedo_texture = ExtResource( 1 )

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,36 @@
[remap]
importer="texture"
type="StreamTexture"
path.s3tc="res://.import/texture_sheet.png-52286655fc64f0acb01bdaefe1f1bd3e.s3tc.stex"
path.etc="res://.import/texture_sheet.png-52286655fc64f0acb01bdaefe1f1bd3e.etc.stex"
metadata={
"imported_formats": [ "s3tc", "etc" ],
"vram_texture": true
}
[deps]
source_file="res://world/textures/texture_sheet.png"
dest_files=[ "res://.import/texture_sheet.png-52286655fc64f0acb01bdaefe1f1bd3e.s3tc.stex", "res://.import/texture_sheet.png-52286655fc64f0acb01bdaefe1f1bd3e.etc.stex" ]
[params]
compress/mode=2
compress/lossy_quality=1.0
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=true
flags/filter=false
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=false
svg/scale=1.0

View File

@@ -0,0 +1,138 @@
extends Node
# This file manages the creation and deletion of Chunks.
const CHUNK_MIDPOINT = Vector3(0.5, 0.5, 0.5) * Chunk.CHUNK_SIZE
const CHUNK_END_SIZE = Chunk.CHUNK_SIZE - 1
var render_distance setget _set_render_distance
var _delete_distance = 0
var effective_render_distance = 0
var _old_player_chunk = Vector3() # TODO: Vector3i
var _generating = true
var _deleting = false
var _chunks = {}
onready var player = $"../Player"
func _process(_delta):
_set_render_distance(Settings.render_distance)
var player_chunk = (player.transform.origin / Chunk.CHUNK_SIZE).round()
if _deleting or player_chunk != _old_player_chunk:
_delete_far_away_chunks(player_chunk)
_generating = true
if not _generating:
return
# Try to generate chunks ahead of time based on where the player is moving.
player_chunk.y += round(clamp(player.velocity.y, -render_distance / 4, render_distance / 4))
# Check existing chunks within range. If it doesn't exist, create it.
for x in range(player_chunk.x - effective_render_distance, player_chunk.x + effective_render_distance):
for y in range(player_chunk.y - effective_render_distance, player_chunk.y + effective_render_distance):
for z in range(player_chunk.z - effective_render_distance, player_chunk.z + effective_render_distance):
var chunk_position = Vector3(x, y, z)
if player_chunk.distance_to(chunk_position) > render_distance:
continue
if _chunks.has(chunk_position):
continue
var chunk = Chunk.new()
chunk.chunk_position = chunk_position
_chunks[chunk_position] = chunk
add_child(chunk)
return
# If we didn't generate any chunks (and therefore didn't return), what next?
if effective_render_distance < render_distance:
# We can move on to the next stage by increasing the effective distance.
effective_render_distance += 1
else:
# Effective render distance is maxed out, done generating.
_generating = false
func get_block_global_position(block_global_position):
var chunk_position = (block_global_position / Chunk.CHUNK_SIZE).floor()
if _chunks.has(chunk_position):
var chunk = _chunks[chunk_position]
var sub_position = block_global_position.posmod(Chunk.CHUNK_SIZE)
if chunk.data.has(sub_position):
return chunk.data[sub_position]
return 0
func set_block_global_position(block_global_position, block_id):
var chunk_position = (block_global_position / Chunk.CHUNK_SIZE).floor()
var chunk = _chunks[chunk_position]
var sub_position = block_global_position.posmod(Chunk.CHUNK_SIZE)
if block_id == 0:
chunk.data.erase(sub_position)
else:
chunk.data[sub_position] = block_id
chunk.regenerate()
# We also might need to regenerate some neighboring chunks.
if Chunk.is_block_transparent(block_id):
if sub_position.x == 0:
_chunks[chunk_position + Vector3.LEFT].regenerate()
elif sub_position.x == CHUNK_END_SIZE:
_chunks[chunk_position + Vector3.RIGHT].regenerate()
if sub_position.z == 0:
_chunks[chunk_position + Vector3.FORWARD].regenerate()
elif sub_position.z == CHUNK_END_SIZE:
_chunks[chunk_position + Vector3.BACK].regenerate()
if sub_position.y == 0:
_chunks[chunk_position + Vector3.DOWN].regenerate()
elif sub_position.y == CHUNK_END_SIZE:
_chunks[chunk_position + Vector3.UP].regenerate()
func clean_up():
for chunk_position_key in _chunks.keys():
var thread = _chunks[chunk_position_key]._thread
if thread:
thread.wait_to_finish()
_chunks = {}
set_process(false)
for c in get_children():
c.free()
func _delete_far_away_chunks(player_chunk):
_old_player_chunk = player_chunk
# If we need to delete chunks, give the new chunk system a chance to catch up.
effective_render_distance = max(1, effective_render_distance - 1)
var deleted_this_frame = 0
# We should delete old chunks more aggressively if moving fast.
# An easy way to calculate this is by using the effective render distance.
# The specific values in this formula are arbitrary and from experimentation.
var max_deletions = clamp(2 * (render_distance - effective_render_distance), 2, 8)
# Also take the opportunity to delete far away chunks.
for chunk_position_key in _chunks.keys():
if player_chunk.distance_to(chunk_position_key) > _delete_distance:
var thread = _chunks[chunk_position_key]._thread
if thread:
thread.wait_to_finish()
_chunks[chunk_position_key].queue_free()
_chunks.erase(chunk_position_key)
deleted_this_frame += 1
# Limit the amount of deletions per frame to avoid lag spikes.
if deleted_this_frame > max_deletions:
# Continue deleting next frame.
_deleting = true
return
# We're done deleting.
_deleting = false
func _set_render_distance(value):
render_distance = value
_delete_distance = value + 2

38
3d/voxel/world/world.tscn Normal file
View File

@@ -0,0 +1,38 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://player/player.tscn" type="PackedScene" id=1]
[ext_resource path="res://world/voxel_world.gd" type="Script" id=2]
[ext_resource path="res://default_env.tres" type="Environment" id=3]
[ext_resource path="res://world/environment.gd" type="Script" id=4]
[ext_resource path="res://menu/ingame/pause_menu.tscn" type="PackedScene" id=5]
[ext_resource path="res://menu/debug.gd" type="Script" id=6]
[ext_resource path="res://menu/theme/theme.tres" type="Theme" id=7]
[node name="World" type="Spatial"]
[node name="Player" parent="." instance=ExtResource( 1 )]
[node name="Debug" type="Label" parent="."]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 20.0
margin_right = -20.0
margin_bottom = -20.0
theme = ExtResource( 7 )
script = ExtResource( 6 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PauseMenu" parent="." instance=ExtResource( 5 )]
[node name="VoxelWorld" type="Node" parent="."]
script = ExtResource( 2 )
[node name="Environment" type="WorldEnvironment" parent="."]
environment = ExtResource( 3 )
script = ExtResource( 4 )
[node name="Sun" type="DirectionalLight" parent="Environment"]
transform = Transform( 0.173648, -0.564863, 0.806707, 0, 0.819152, 0.573576, -0.984808, -0.0996005, 0.142244, 0, 0, 0 )