Add a Voxel demo project
58
3d/voxel/README.md
Normal 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
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 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
|
||||
8
3d/voxel/default_bus_layout.tres
Normal 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
@@ -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
|
After Width: | Height: | Size: 25 KiB |
34
3d/voxel/icon.png.import
Normal 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
|
After Width: | Height: | Size: 840 B |
34
3d/voxel/menu/button.png.import
Normal 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
@@ -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"
|
||||
38
3d/voxel/menu/ingame/pause_menu.gd
Normal 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()
|
||||
133
3d/voxel/menu/ingame/pause_menu.tscn
Normal 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"]
|
||||
BIN
3d/voxel/menu/main/dark_dirt.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
34
3d/voxel/menu/main/dark_dirt.png.import
Normal 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
|
||||
37
3d/voxel/menu/main/main_menu.gd
Normal 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
|
||||
236
3d/voxel/menu/main/main_menu.tscn
Normal 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"]
|
||||
8
3d/voxel/menu/main/splash_text.gd
Normal 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)
|
||||
BIN
3d/voxel/menu/main/title.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
34
3d/voxel/menu/main/title.png.import
Normal 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
|
||||
22
3d/voxel/menu/options/option_buttons.gd
Normal 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()
|
||||
8
3d/voxel/menu/options/options.gd
Normal file
@@ -0,0 +1,8 @@
|
||||
extends HBoxContainer
|
||||
|
||||
var prev_menu
|
||||
|
||||
|
||||
func _on_Back_pressed():
|
||||
prev_menu.visible = true
|
||||
visible = false
|
||||
112
3d/voxel/menu/options/options.tscn
Normal 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"]
|
||||
7
3d/voxel/menu/theme/TinyUnicode.tres
Normal 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 )
|
||||
BIN
3d/voxel/menu/theme/TinyUnicode.ttf
Normal file
6
3d/voxel/menu/theme/theme.tres
Normal 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 )
|
||||
1
3d/voxel/player/crosshair.svg
Normal 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 |
34
3d/voxel/player/crosshair.svg.import
Normal 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
@@ -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()
|
||||
48
3d/voxel/player/player.tscn
Normal 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
@@ -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"
|
||||
0
3d/voxel/screenshots/.gdignore
Normal file
BIN
3d/voxel/screenshots/blocks.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
3d/voxel/screenshots/title.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
40
3d/voxel/settings.gd
Normal 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
@@ -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)
|
||||
17
3d/voxel/world/environment.gd
Normal 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
|
||||
42
3d/voxel/world/terrain_generator.gd
Normal 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 {}
|
||||
8
3d/voxel/world/textures/material.tres
Normal 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 )
|
||||
BIN
3d/voxel/world/textures/texture_sheet.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
36
3d/voxel/world/textures/texture_sheet.png.import
Normal 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
|
||||
138
3d/voxel/world/voxel_world.gd
Normal 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
@@ -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 )
|
||||