commit
c5e9e5aab1
38 changed files with 1849 additions and 0 deletions
@ -0,0 +1,9 @@ |
||||
root = true |
||||
|
||||
[*] |
||||
charset = utf-8 |
||||
end_of_line = lf |
||||
indent_size = 4 |
||||
indent_style = tab |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = true |
@ -0,0 +1,2 @@ |
||||
# Normalize EOL for all files that Git considers text files. |
||||
* text=auto eol=lf |
@ -0,0 +1,6 @@ |
||||
# Godot 4+ specific ignores |
||||
/.godot/ |
||||
/export_presets.cfg |
||||
|
||||
# Blender specifc ignores |
||||
*.blend? |
@ -0,0 +1,14 @@ |
||||
public partial class CameraController : Node3D |
||||
{ |
||||
private Vector3 Offset; |
||||
public override void _Ready() |
||||
{ |
||||
Offset = Position; |
||||
} |
||||
|
||||
public override void _Process(double delta) |
||||
{ |
||||
var target = GetParent<Node3D>().GlobalPosition + Offset; |
||||
GlobalPosition = GlobalPosition.Lerp(target, 1 - Pow(0.05f, (float)delta)); |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
global using System; |
||||
global using System.Collections.Generic; |
||||
global using System.Linq; |
||||
|
||||
global using Godot; |
||||
|
||||
global using static Godot.Mathf; |
@ -0,0 +1,80 @@ |
||||
public partial class MovementController : Node |
||||
{ |
||||
[ExportGroup("Movement")] |
||||
[Export] public float Acceleration { get; set; } = 2.5f; |
||||
[Export] public float MaxSpeed { get; set; } = 3.0f; |
||||
[Export] public float FrictionFloor { get; set; } = 3.5f; |
||||
[Export] public float FrictionAir { get; set; } = 1.0f; |
||||
[Export] public float SprintMultiplier { get; set; } = 2.0f; |
||||
|
||||
[ExportGroup("Jumping")] |
||||
[Export] public float JumpVelocity { get; set; } = 4.0f; |
||||
/// <summary> Time (in seconds) after pressing the jump button a jump may occur late. </summary> |
||||
[Export] public float JumpEarlyTime { get; set; } = 0.2f; |
||||
/// <summary> Time (in seconds) after leaving a jumpable surface when a jump may still occur. </summary> |
||||
[Export] public float JumpCoyoteTime { get; set; } = 0.2f; |
||||
|
||||
public float TimeSinceJumpPressed { get; private set; } = float.PositiveInfinity; |
||||
public float TimeSinceOnFloor { get; private set; } = float.PositiveInfinity; |
||||
|
||||
private CharacterBody3D Player; |
||||
public override void _Ready() |
||||
{ |
||||
Player = GetParent<CharacterBody3D>(); |
||||
} |
||||
|
||||
public override void _UnhandledInput(InputEvent ev) |
||||
{ |
||||
if (ev.IsActionPressed("move_jump")) { |
||||
TimeSinceJumpPressed = 0.0f; |
||||
GetViewport().SetInputAsHandled(); |
||||
} |
||||
} |
||||
|
||||
public override void _PhysicsProcess(double delta) |
||||
{ |
||||
var velocity = Player.Velocity; |
||||
var horVelocity = new Vector2(velocity.X, velocity.Z); |
||||
|
||||
var gravity = (float)ProjectSettings.GetSetting("physics/3d/default_gravity"); |
||||
velocity.Y -= gravity * (float)delta; |
||||
|
||||
var input = Input.GetVector("move_left", "move_right", "move_forward", "move_back"); |
||||
var camera = GetViewport().GetCamera3D(); |
||||
var target = input.Rotated(camera.GlobalRotation.Y - Tau / 4) * MaxSpeed; |
||||
|
||||
var isOnFloor = Player.IsOnFloor(); |
||||
var isMoving = target.Dot(horVelocity) > 0.0f; |
||||
var isSprinting = Input.IsActionPressed("move_sprint"); |
||||
|
||||
var accel = isMoving ? Acceleration |
||||
: isOnFloor ? FrictionFloor |
||||
: FrictionAir; |
||||
|
||||
if (isSprinting) { |
||||
target *= SprintMultiplier; |
||||
accel *= SprintMultiplier; |
||||
} |
||||
|
||||
horVelocity = horVelocity.Lerp(target, accel * (float)delta); |
||||
|
||||
velocity.X = horVelocity.X; |
||||
velocity.Z = horVelocity.Y; |
||||
|
||||
if (isOnFloor) TimeSinceOnFloor = 0.0f; |
||||
else TimeSinceOnFloor += (float)delta; |
||||
|
||||
if ((TimeSinceJumpPressed <= JumpEarlyTime) && (TimeSinceOnFloor <= JumpCoyoteTime)) { |
||||
TimeSinceJumpPressed = TimeSinceOnFloor = float.PositiveInfinity; |
||||
velocity.Y = JumpVelocity; |
||||
} else |
||||
TimeSinceJumpPressed += (float)delta; |
||||
|
||||
Player.Velocity = velocity; |
||||
Player.MoveAndSlide(); |
||||
} |
||||
|
||||
public override void _Process(double delta) |
||||
{ |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
public enum PhysicsLayer |
||||
{ |
||||
Terrain = 0b0000_0001, |
||||
Objects = 0b0000_0010, |
||||
} |
@ -0,0 +1,9 @@ |
||||
<Project Sdk="Godot.NET.Sdk/4.3.0"> |
||||
<PropertyGroup> |
||||
<LangVersion>12</LangVersion> |
||||
<TargetFramework>net6.0</TargetFramework> |
||||
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework> |
||||
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework> |
||||
<EnableDynamicLoading>true</EnableDynamicLoading> |
||||
</PropertyGroup> |
||||
</Project> |
@ -0,0 +1,65 @@ |
||||
#if TOOLS |
||||
|
||||
|
||||
using System.Security.Cryptography.X509Certificates; |
||||
|
||||
[Tool] |
||||
public partial class TerrainPlugin |
||||
: EditorPlugin |
||||
{ |
||||
public override bool _Handles(GodotObject @object) |
||||
=> @object is Terrain; |
||||
|
||||
public override void _EnterTree() |
||||
{ |
||||
// Initialization of the plugin goes here. |
||||
} |
||||
|
||||
public override void _ExitTree() |
||||
{ |
||||
// Clean-up of the plugin goes here. |
||||
} |
||||
|
||||
Terrain _previousTerrain = null; |
||||
public override int _Forward3DGuiInput(Camera3D viewportCamera, InputEvent @event) |
||||
{ |
||||
if (@event is not InputEventMouse) return (int)AfterGuiInput.Pass; |
||||
|
||||
if (!IsInstanceValid(_previousTerrain)) |
||||
_previousTerrain = null; |
||||
|
||||
if (RayCastTerrain(viewportCamera) is var (terrain, pos, normal, shape)) { |
||||
if (_previousTerrain != terrain) { |
||||
_previousTerrain?._MouseExit(); |
||||
terrain._MouseEnter(); |
||||
_previousTerrain = terrain; |
||||
} |
||||
terrain._InputEvent(viewportCamera, @event, pos, normal, shape); |
||||
return viewportCamera.GetViewport().IsInputHandled() |
||||
? (int)AfterGuiInput.Stop : (int)AfterGuiInput.Pass; |
||||
} else { |
||||
_previousTerrain?._MouseExit(); |
||||
_previousTerrain = null; |
||||
return (int)AfterGuiInput.Pass; |
||||
} |
||||
} |
||||
|
||||
(Terrain Terrain, Vector3 Position, Vector3 Normal, int Shape)? RayCastTerrain(Camera3D camera) |
||||
{ |
||||
var viewport = camera.GetViewport(); |
||||
var mouse = viewport.GetMousePosition(); |
||||
if (!viewport.GetVisibleRect().HasPoint(mouse)) return null; |
||||
|
||||
var from = camera.ProjectRayOrigin(mouse); |
||||
var to = from + camera.ProjectRayNormal(mouse) * camera.Far; |
||||
|
||||
var space = camera.GetWorld3D().DirectSpaceState; |
||||
var query = new PhysicsRayQueryParameters3D { From = from, To = to }; |
||||
var result = space.IntersectRay(query); |
||||
if (result.Count == 0) return null; |
||||
|
||||
if ((GodotObject)result["collider"] is not Terrain terrain) return null; |
||||
return (terrain, (Vector3)result["position"], (Vector3)result["normal"], (int)result["shape"]); |
||||
} |
||||
} |
||||
#endif |
@ -0,0 +1,7 @@ |
||||
[plugin] |
||||
|
||||
name="Terrain Plugin" |
||||
description="" |
||||
author="copygirl" |
||||
version="" |
||||
script="TerrainPlugin.cs" |
After Width: | Height: | Size: 949 B |
@ -0,0 +1,37 @@ |
||||
[remap] |
||||
|
||||
importer="texture" |
||||
type="CompressedTexture2D" |
||||
uid="uid://dmu6pvke0n7x3" |
||||
path="res://.godot/imported/icon.svg-56083ea2a1f1a4f1e49773bdc6d7826c.ctex" |
||||
metadata={ |
||||
"vram_texture": false |
||||
} |
||||
|
||||
[deps] |
||||
|
||||
source_file="res://assets/icon.svg" |
||||
dest_files=["res://.godot/imported/icon.svg-56083ea2a1f1a4f1e49773bdc6d7826c.ctex"] |
||||
|
||||
[params] |
||||
|
||||
compress/mode=0 |
||||
compress/high_quality=false |
||||
compress/lossy_quality=0.7 |
||||
compress/hdr_compression=1 |
||||
compress/normal_map=0 |
||||
compress/channel_pack=0 |
||||
mipmaps/generate=false |
||||
mipmaps/limit=-1 |
||||
roughness/mode=0 |
||||
roughness/src_normal="" |
||||
process/fix_alpha_border=true |
||||
process/premult_alpha=false |
||||
process/normal_map_invert_y=false |
||||
process/hdr_as_srgb=false |
||||
process/hdr_clamp_exposure=false |
||||
process/size_limit=0 |
||||
detect_3d/compress_to=1 |
||||
svg/scale=1.0 |
||||
editor/scale_with_editor_scale=false |
||||
editor/convert_colors_with_editor_theme=false |
Binary file not shown.
@ -0,0 +1,51 @@ |
||||
[remap] |
||||
|
||||
importer="scene" |
||||
importer_version=1 |
||||
type="PackedScene" |
||||
uid="uid://buj1g5fpiuqkd" |
||||
path="res://.godot/imported/terrain.blend-0343d6f0e6459ff0512c30a5803c2760.scn" |
||||
|
||||
[deps] |
||||
|
||||
source_file="res://assets/models/terrain.blend" |
||||
dest_files=["res://.godot/imported/terrain.blend-0343d6f0e6459ff0512c30a5803c2760.scn"] |
||||
|
||||
[params] |
||||
|
||||
nodes/root_type="" |
||||
nodes/root_name="" |
||||
nodes/apply_root_scale=true |
||||
nodes/root_scale=1.0 |
||||
nodes/import_as_skeleton_bones=false |
||||
meshes/ensure_tangents=true |
||||
meshes/generate_lods=true |
||||
meshes/create_shadow_meshes=true |
||||
meshes/light_baking=1 |
||||
meshes/lightmap_texel_size=0.2 |
||||
meshes/force_disable_compression=false |
||||
skins/use_named_skins=true |
||||
animation/import=true |
||||
animation/fps=30 |
||||
animation/trimming=false |
||||
animation/remove_immutable_tracks=true |
||||
animation/import_rest_as_RESET=false |
||||
import_script/path="" |
||||
_subresources={} |
||||
blender/nodes/visible=0 |
||||
blender/nodes/active_collection_only=false |
||||
blender/nodes/punctual_lights=true |
||||
blender/nodes/cameras=true |
||||
blender/nodes/custom_properties=true |
||||
blender/nodes/modifiers=1 |
||||
blender/meshes/colors=false |
||||
blender/meshes/uvs=true |
||||
blender/meshes/normals=true |
||||
blender/meshes/tangents=true |
||||
blender/meshes/skins=2 |
||||
blender/meshes/export_bones_deforming_mesh_only=false |
||||
blender/materials/unpack_enabled=true |
||||
blender/materials/export_materials=1 |
||||
blender/animation/limit_playback=true |
||||
blender/animation/always_sample=true |
||||
blender/animation/group_tracks=true |
Binary file not shown.
@ -0,0 +1,34 @@ |
||||
[remap] |
||||
|
||||
importer="scene" |
||||
importer_version=1 |
||||
type="PackedScene" |
||||
uid="uid://sul6hrbcaal3" |
||||
path="res://.godot/imported/tree_cone.glb-32c7aa66c7f7007efe8afbb55a64b8be.scn" |
||||
|
||||
[deps] |
||||
|
||||
source_file="res://assets/models/trees/tree_cone.glb" |
||||
dest_files=["res://.godot/imported/tree_cone.glb-32c7aa66c7f7007efe8afbb55a64b8be.scn"] |
||||
|
||||
[params] |
||||
|
||||
nodes/root_type="" |
||||
nodes/root_name="" |
||||
nodes/apply_root_scale=true |
||||
nodes/root_scale=1.0 |
||||
meshes/ensure_tangents=true |
||||
meshes/generate_lods=true |
||||
meshes/create_shadow_meshes=true |
||||
meshes/light_baking=1 |
||||
meshes/lightmap_texel_size=0.2 |
||||
meshes/force_disable_compression=false |
||||
skins/use_named_skins=true |
||||
animation/import=true |
||||
animation/fps=30 |
||||
animation/trimming=false |
||||
animation/remove_immutable_tracks=true |
||||
import_script/path="" |
||||
_subresources={} |
||||
gltf/naming_version=1 |
||||
gltf/embedded_image_handling=1 |
Binary file not shown.
@ -0,0 +1,34 @@ |
||||
[remap] |
||||
|
||||
importer="scene" |
||||
importer_version=1 |
||||
type="PackedScene" |
||||
uid="uid://c02oimc3fcl4v" |
||||
path="res://.godot/imported/tree_default.glb-e065ad2933f3574ace34d72aa07370d7.scn" |
||||
|
||||
[deps] |
||||
|
||||
source_file="res://assets/models/trees/tree_default.glb" |
||||
dest_files=["res://.godot/imported/tree_default.glb-e065ad2933f3574ace34d72aa07370d7.scn"] |
||||
|
||||
[params] |
||||
|
||||
nodes/root_type="" |
||||
nodes/root_name="" |
||||
nodes/apply_root_scale=true |
||||
nodes/root_scale=1.0 |
||||
meshes/ensure_tangents=true |
||||
meshes/generate_lods=true |
||||
meshes/create_shadow_meshes=true |
||||
meshes/light_baking=1 |
||||
meshes/lightmap_texel_size=0.2 |
||||
meshes/force_disable_compression=false |
||||
skins/use_named_skins=true |
||||
animation/import=true |
||||
animation/fps=30 |
||||
animation/trimming=false |
||||
animation/remove_immutable_tracks=true |
||||
import_script/path="" |
||||
_subresources={} |
||||
gltf/naming_version=1 |
||||
gltf/embedded_image_handling=1 |
Binary file not shown.
@ -0,0 +1,34 @@ |
||||
[remap] |
||||
|
||||
importer="scene" |
||||
importer_version=1 |
||||
type="PackedScene" |
||||
uid="uid://b0578fhc4t22h" |
||||
path="res://.godot/imported/tree_oak.glb-25d5c85394a71448f9e157e250f5713a.scn" |
||||
|
||||
[deps] |
||||
|
||||
source_file="res://assets/models/trees/tree_oak.glb" |
||||
dest_files=["res://.godot/imported/tree_oak.glb-25d5c85394a71448f9e157e250f5713a.scn"] |
||||
|
||||
[params] |
||||
|
||||
nodes/root_type="" |
||||
nodes/root_name="" |
||||
nodes/apply_root_scale=true |
||||
nodes/root_scale=1.0 |
||||
meshes/ensure_tangents=true |
||||
meshes/generate_lods=true |
||||
meshes/create_shadow_meshes=true |
||||
meshes/light_baking=1 |
||||
meshes/lightmap_texel_size=0.2 |
||||
meshes/force_disable_compression=false |
||||
skins/use_named_skins=true |
||||
animation/import=true |
||||
animation/fps=30 |
||||
animation/trimming=false |
||||
animation/remove_immutable_tracks=true |
||||
import_script/path="" |
||||
_subresources={} |
||||
gltf/naming_version=1 |
||||
gltf/embedded_image_handling=1 |
Binary file not shown.
After Width: | Height: | Size: 967 B |
@ -0,0 +1,35 @@ |
||||
[remap] |
||||
|
||||
importer="texture" |
||||
type="CompressedTexture2D" |
||||
uid="uid://bpo7mkr6sctqr" |
||||
path.s3tc="res://.godot/imported/dirt.png-67313fa97fa5b9369bec725f203ba6a9.s3tc.ctex" |
||||
metadata={ |
||||
"imported_formats": ["s3tc_bptc"], |
||||
"vram_texture": true |
||||
} |
||||
|
||||
[deps] |
||||
|
||||
source_file="res://assets/textures/terrain/dirt.png" |
||||
dest_files=["res://.godot/imported/dirt.png-67313fa97fa5b9369bec725f203ba6a9.s3tc.ctex"] |
||||
|
||||
[params] |
||||
|
||||
compress/mode=2 |
||||
compress/high_quality=false |
||||
compress/lossy_quality=0.7 |
||||
compress/hdr_compression=1 |
||||
compress/normal_map=0 |
||||
compress/channel_pack=0 |
||||
mipmaps/generate=true |
||||
mipmaps/limit=-1 |
||||
roughness/mode=0 |
||||
roughness/src_normal="" |
||||
process/fix_alpha_border=true |
||||
process/premult_alpha=false |
||||
process/normal_map_invert_y=false |
||||
process/hdr_as_srgb=false |
||||
process/hdr_clamp_exposure=false |
||||
process/size_limit=0 |
||||
detect_3d/compress_to=0 |
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,35 @@ |
||||
[remap] |
||||
|
||||
importer="texture" |
||||
type="CompressedTexture2D" |
||||
uid="uid://b0jp1dyxugbr7" |
||||
path.s3tc="res://.godot/imported/grass.png-aff98023336e7c812cce14c63596cfa6.s3tc.ctex" |
||||
metadata={ |
||||
"imported_formats": ["s3tc_bptc"], |
||||
"vram_texture": true |
||||
} |
||||
|
||||
[deps] |
||||
|
||||
source_file="res://assets/textures/terrain/grass.png" |
||||
dest_files=["res://.godot/imported/grass.png-aff98023336e7c812cce14c63596cfa6.s3tc.ctex"] |
||||
|
||||
[params] |
||||
|
||||
compress/mode=2 |
||||
compress/high_quality=false |
||||
compress/lossy_quality=0.7 |
||||
compress/hdr_compression=1 |
||||
compress/normal_map=0 |
||||
compress/channel_pack=0 |
||||
mipmaps/generate=true |
||||
mipmaps/limit=-1 |
||||
roughness/mode=0 |
||||
roughness/src_normal="" |
||||
process/fix_alpha_border=true |
||||
process/premult_alpha=false |
||||
process/normal_map_invert_y=false |
||||
process/hdr_as_srgb=false |
||||
process/hdr_clamp_exposure=false |
||||
process/size_limit=0 |
||||
detect_3d/compress_to=0 |
After Width: | Height: | Size: 888 B |
@ -0,0 +1,35 @@ |
||||
[remap] |
||||
|
||||
importer="texture" |
||||
type="CompressedTexture2D" |
||||
uid="uid://dqyqg6yt7yk3k" |
||||
path.s3tc="res://.godot/imported/rock.png-6fd2e985e4c17866aa31a92f6096a3ad.s3tc.ctex" |
||||
metadata={ |
||||
"imported_formats": ["s3tc_bptc"], |
||||
"vram_texture": true |
||||
} |
||||
|
||||
[deps] |
||||
|
||||
source_file="res://assets/textures/terrain/rock.png" |
||||
dest_files=["res://.godot/imported/rock.png-6fd2e985e4c17866aa31a92f6096a3ad.s3tc.ctex"] |
||||
|
||||
[params] |
||||
|
||||
compress/mode=2 |
||||
compress/high_quality=false |
||||
compress/lossy_quality=0.7 |
||||
compress/hdr_compression=1 |
||||
compress/normal_map=0 |
||||
compress/channel_pack=0 |
||||
mipmaps/generate=true |
||||
mipmaps/limit=-1 |
||||
roughness/mode=0 |
||||
roughness/src_normal="" |
||||
process/fix_alpha_border=true |
||||
process/premult_alpha=false |
||||
process/normal_map_invert_y=false |
||||
process/hdr_as_srgb=false |
||||
process/hdr_clamp_exposure=false |
||||
process/size_limit=0 |
||||
detect_3d/compress_to=0 |
After Width: | Height: | Size: 921 B |
@ -0,0 +1,35 @@ |
||||
[remap] |
||||
|
||||
importer="texture" |
||||
type="CompressedTexture2D" |
||||
uid="uid://bkwjxg6g2itag" |
||||
path.s3tc="res://.godot/imported/sand.png-451c9e51968306b5331529d9113b3e49.s3tc.ctex" |
||||
metadata={ |
||||
"imported_formats": ["s3tc_bptc"], |
||||
"vram_texture": true |
||||
} |
||||
|
||||
[deps] |
||||
|
||||
source_file="res://assets/textures/terrain/sand.png" |
||||
dest_files=["res://.godot/imported/sand.png-451c9e51968306b5331529d9113b3e49.s3tc.ctex"] |
||||
|
||||
[params] |
||||
|
||||
compress/mode=2 |
||||
compress/high_quality=false |
||||
compress/lossy_quality=0.7 |
||||
compress/hdr_compression=1 |
||||
compress/normal_map=0 |
||||
compress/channel_pack=0 |
||||
mipmaps/generate=true |
||||
mipmaps/limit=-1 |
||||
roughness/mode=0 |
||||
roughness/src_normal="" |
||||
process/fix_alpha_border=true |
||||
process/premult_alpha=false |
||||
process/normal_map_invert_y=false |
||||
process/hdr_as_srgb=false |
||||
process/hdr_clamp_exposure=false |
||||
process/size_limit=0 |
||||
detect_3d/compress_to=0 |
@ -0,0 +1,28 @@ |
||||
[gd_scene load_steps=5 format=3 uid="uid://daihc7acaxfns"] |
||||
|
||||
[ext_resource type="Script" path="res://MovementController.cs" id="1_akh08"] |
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_urpcu"] |
||||
albedo_color = Color(0.25098, 0.690196, 0.12549, 0.815686) |
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_7ljg8"] |
||||
material = SubResource("StandardMaterial3D_urpcu") |
||||
radius = 0.2 |
||||
height = 0.35 |
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_6qbb2"] |
||||
radius = 0.2 |
||||
|
||||
[node name="Character" type="CharacterBody3D"] |
||||
floor_max_angle = 0.698132 |
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."] |
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0) |
||||
mesh = SubResource("SphereMesh_7ljg8") |
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."] |
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.2, 0) |
||||
shape = SubResource("SphereShape3D_6qbb2") |
||||
|
||||
[node name="MovementController" type="Node" parent="."] |
||||
script = ExtResource("1_akh08") |
File diff suppressed because one or more lines are too long
@ -0,0 +1,19 @@ |
||||
[gd_scene load_steps=3 format=3 uid="uid://b65o2rhp8qx74"] |
||||
|
||||
[ext_resource type="PackedScene" uid="uid://sul6hrbcaal3" path="res://assets/models/trees/tree_cone.glb" id="1_8soi2"] |
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_82bei"] |
||||
radius = 0.25 |
||||
|
||||
[node name="TreeOakCone" type="Node3D"] |
||||
|
||||
[node name="tree_cone2" parent="." instance=ExtResource("1_8soi2")] |
||||
transform = Transform3D(4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0) |
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="."] |
||||
collision_layer = 2 |
||||
collision_mask = 0 |
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] |
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) |
||||
shape = SubResource("CylinderShape3D_82bei") |
@ -0,0 +1,19 @@ |
||||
[gd_scene load_steps=3 format=3 uid="uid://c732i0mrp6klk"] |
||||
|
||||
[ext_resource type="PackedScene" uid="uid://b0578fhc4t22h" path="res://assets/models/trees/tree_oak.glb" id="1_aojgv"] |
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_causx"] |
||||
radius = 0.35 |
||||
|
||||
[node name="TreeOakRound" type="Node3D"] |
||||
|
||||
[node name="tree_oak2" parent="." instance=ExtResource("1_aojgv")] |
||||
transform = Transform3D(4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0.171265, 0) |
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="."] |
||||
collision_layer = 2 |
||||
collision_mask = 0 |
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] |
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) |
||||
shape = SubResource("CylinderShape3D_causx") |
@ -0,0 +1,21 @@ |
||||
[gd_scene load_steps=3 format=3 uid="uid://2giwj61d3h66"] |
||||
|
||||
[ext_resource type="PackedScene" uid="uid://c02oimc3fcl4v" path="res://assets/models/trees/tree_default.glb" id="1_frbcr"] |
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_0l3l4"] |
||||
height = 4.0 |
||||
radius = 0.3 |
||||
|
||||
[node name="TreeOakTall" type="Node3D"] |
||||
|
||||
[node name="tree_default2" parent="." instance=ExtResource("1_frbcr")] |
||||
transform = Transform3D(4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0) |
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="."] |
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) |
||||
collision_layer = 2 |
||||
collision_mask = 0 |
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] |
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) |
||||
shape = SubResource("CylinderShape3D_0l3l4") |
@ -0,0 +1,66 @@ |
||||
; 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=5 |
||||
|
||||
[application] |
||||
|
||||
config/name="SlimeDream" |
||||
run/main_scene="res://level.tscn" |
||||
config/features=PackedStringArray("4.3", "C#", "GL Compatibility") |
||||
config/icon="res://assets/icon.svg" |
||||
|
||||
[dotnet] |
||||
|
||||
project/assembly_name="SlimeDream" |
||||
|
||||
[editor_plugins] |
||||
|
||||
enabled=PackedStringArray("res://addons/terrain/plugin.cfg") |
||||
|
||||
[input] |
||||
|
||||
move_left={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) |
||||
] |
||||
} |
||||
move_right={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) |
||||
] |
||||
} |
||||
move_forward={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) |
||||
] |
||||
} |
||||
move_back={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) |
||||
] |
||||
} |
||||
move_sprint={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) |
||||
] |
||||
} |
||||
move_jump={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) |
||||
] |
||||
} |
||||
|
||||
[layer_names] |
||||
|
||||
3d_physics/layer_1="Terrain" |
||||
3d_physics/layer_2="Objects" |
||||
|
||||
[rendering] |
||||
|
||||
renderer/rendering_method="mobile" |
@ -0,0 +1,226 @@ |
||||
[Tool] |
||||
public partial class Terrain |
||||
: StaticBody3D |
||||
{ |
||||
[Export] public Vector2I Size { get; set; } = new(64, 64); |
||||
[Export] public float TileSize { get; set; } = 2.0f; |
||||
|
||||
[Export] public Godot.Collections.Array<Texture2D> Textures { get; set; } |
||||
|
||||
public bool IsEditing { get; } = Engine.IsEditorHint(); |
||||
|
||||
Vector2I? _gridHover = null; |
||||
Vector2I? _gridSelectionStart = null; |
||||
Vector2I? _gridSelectionEnd = null; |
||||
bool _isSelecting = false; |
||||
|
||||
Material _editToolMaterial; |
||||
Material _terrainMaterial; |
||||
public override void _Ready() |
||||
{ |
||||
_editToolMaterial = new StandardMaterial3D { |
||||
VertexColorUseAsAlbedo = true, |
||||
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded, |
||||
DepthDrawMode = BaseMaterial3D.DepthDrawModeEnum.Disabled, |
||||
NoDepthTest = true, |
||||
}; |
||||
|
||||
_terrainMaterial = new StandardMaterial3D { |
||||
AlbedoTexture = Textures?.FirstOrDefault(), |
||||
}; |
||||
|
||||
UpdateMeshAndShape(); |
||||
} |
||||
|
||||
|
||||
Vector2I ToGridPos(Vector3 localPos) |
||||
=> new(RoundToInt(localPos.X / TileSize + Size.X / 2.0f), |
||||
RoundToInt(localPos.Z / TileSize + Size.Y / 2.0f)); |
||||
|
||||
float GetCornerHeight(Vector2I gridPos, Corner corner) |
||||
{ |
||||
return 0.0f; |
||||
} |
||||
|
||||
GridCorners GetGridCorners(Vector2I gridPos) |
||||
{ |
||||
var halfSize = TileSize / 2; |
||||
var vx = (gridPos.X - Size.X / 2.0f) * TileSize; |
||||
var vz = (gridPos.Y - Size.Y / 2.0f) * TileSize; |
||||
|
||||
return new() { |
||||
TopLeft = new(vx - halfSize, GetCornerHeight(gridPos, Corner.TopLeft ), vz - halfSize), |
||||
TopRight = new(vx + halfSize, GetCornerHeight(gridPos, Corner.TopRight ), vz - halfSize), |
||||
BottomLeft = new(vx - halfSize, GetCornerHeight(gridPos, Corner.BottomLeft ), vz + halfSize), |
||||
BottomRight = new(vx + halfSize, GetCornerHeight(gridPos, Corner.BottomRight), vz + halfSize), |
||||
}; |
||||
} |
||||
|
||||
|
||||
void UpdateMeshAndShape() |
||||
{ |
||||
var mesh = GetOrCreateMesh("MeshInstance"); |
||||
var shape = GetOrCreateShape("CollisionShape"); |
||||
|
||||
mesh.ClearSurfaces(); |
||||
mesh.SurfaceBegin(Mesh.PrimitiveType.Triangles); |
||||
var points = new List<Vector3>(); |
||||
|
||||
void AddPoint(Vector3 pos, Vector2 uv) { |
||||
mesh.SurfaceSetUV(uv); |
||||
mesh.SurfaceAddVertex(pos); |
||||
points.Add(pos); |
||||
} |
||||
|
||||
void AddTriangle(Vector3 v1, Vector2 uv1, |
||||
Vector3 v2, Vector2 uv2, |
||||
Vector3 v3, Vector2 uv3) { |
||||
var dir = (v3 - v1).Cross(v2 - v1); |
||||
mesh.SurfaceSetNormal(dir.Normalized()); |
||||
AddPoint(v1, uv1); |
||||
AddPoint(v2, uv2); |
||||
AddPoint(v3, uv3); |
||||
} |
||||
|
||||
for (var x = 0; x < Size.X; x++) { |
||||
for (var z = 0; z < Size.Y; z++) { |
||||
var corners = GetGridCorners(new(x, z)); |
||||
AddTriangle(corners.TopLeft , new(0.0f, 0.0f), |
||||
corners.TopRight , new(1.0f, 0.0f), |
||||
corners.BottomLeft , new(0.0f, 1.0f)); |
||||
AddTriangle(corners.TopRight , new(1.0f, 0.0f), |
||||
corners.BottomRight, new(1.0f, 1.0f), |
||||
corners.BottomLeft , new(0.0f, 1.0f)); |
||||
} |
||||
} |
||||
|
||||
mesh.SurfaceEnd(); |
||||
mesh.SurfaceSetMaterial(0, _terrainMaterial); |
||||
|
||||
shape.Data = [.. points]; |
||||
} |
||||
|
||||
|
||||
public override void _MouseEnter() |
||||
{ |
||||
} |
||||
|
||||
public override void _MouseExit() |
||||
{ |
||||
_gridHover = null; |
||||
} |
||||
|
||||
public override void _InputEvent(Camera3D camera, InputEvent @event, Vector3 position, Vector3 normal, int shapeIdx) |
||||
{ |
||||
if (!IsEditing) return; |
||||
|
||||
var localPos = ToLocal(position); |
||||
var gridPos = ToGridPos(localPos); |
||||
|
||||
if (@event is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: var pressed }) { |
||||
if (pressed) _gridSelectionStart = _gridSelectionEnd = gridPos; |
||||
_isSelecting = pressed; |
||||
camera.GetViewport().SetInputAsHandled(); |
||||
} |
||||
|
||||
if (@event is InputEventMouseMotion) { |
||||
_gridHover = gridPos; |
||||
if (_isSelecting) _gridSelectionEnd = gridPos; |
||||
} |
||||
} |
||||
|
||||
public override void _Process(double delta) |
||||
{ |
||||
if (!IsEditing) _gridHover = _gridSelectionStart = _gridSelectionEnd = null; |
||||
|
||||
if ((_gridHover != null) || (_gridSelectionStart != null)) { |
||||
var mesh = GetOrCreateMesh("EditToolMesh"); |
||||
mesh.ClearSurfaces(); |
||||
mesh.SurfaceBegin(Mesh.PrimitiveType.Lines); |
||||
|
||||
|
||||
void AddLine(Vector3 start, Vector3 end) { |
||||
mesh.SurfaceAddVertex(start); |
||||
mesh.SurfaceAddVertex(end); |
||||
} |
||||
|
||||
void AddQuad(Vector3 topLeft, Vector3 topRight, |
||||
Vector3 bottomLeft, Vector3 bottomRight) { |
||||
AddLine(topLeft , topRight ); |
||||
AddLine(topRight , bottomRight); |
||||
AddLine(bottomRight, bottomLeft ); |
||||
AddLine(bottomLeft , topLeft ); |
||||
} |
||||
|
||||
if (_gridHover is Vector2I hover) { |
||||
var corners = GetGridCorners(hover); |
||||
var margin = 0.1f; |
||||
mesh.SurfaceSetColor(Colors.Black); |
||||
AddQuad( |
||||
corners.TopLeft + new Vector3(-margin, 0, -margin), |
||||
corners.TopRight + new Vector3(+margin, 0, -margin), |
||||
corners.BottomLeft + new Vector3(-margin, 0, +margin), |
||||
corners.BottomRight + new Vector3(+margin, 0, +margin) |
||||
); |
||||
} |
||||
|
||||
if (_gridSelectionStart is Vector2I start) { |
||||
var end = _gridSelectionEnd ?? start; |
||||
(start, end) = (new(Min(start.X, end.X), Min(start.Y, end.Y)), |
||||
new(Max(start.X, end.X), Max(start.Y, end.Y))); |
||||
mesh.SurfaceSetColor(Colors.Blue); |
||||
for (var x = start.X; x <= end.X; x++) |
||||
for (var y = start.Y; y <= end.Y; y++) { |
||||
var corners = GetGridCorners(new(x, y)); |
||||
AddQuad(corners.TopLeft, corners.TopRight, corners.BottomLeft, corners.BottomRight); |
||||
} |
||||
} |
||||
|
||||
mesh.SurfaceEnd(); |
||||
mesh.SurfaceSetMaterial(0, _editToolMaterial); |
||||
} else { |
||||
var meshInstance = (MeshInstance3D)GetNodeOrNull("EditToolMesh"); |
||||
var mesh = (ImmediateMesh)meshInstance?.Mesh; |
||||
mesh?.ClearSurfaces(); |
||||
} |
||||
} |
||||
|
||||
|
||||
ImmediateMesh GetOrCreateMesh(string name) |
||||
{ |
||||
var meshInstance = (MeshInstance3D)GetNodeOrNull(name); |
||||
if (meshInstance == null) { |
||||
meshInstance = new() { Name = name, Mesh = new ImmediateMesh() }; |
||||
AddChild(meshInstance); |
||||
meshInstance.Owner = this; |
||||
} |
||||
return (ImmediateMesh)meshInstance.Mesh; |
||||
} |
||||
|
||||
ConcavePolygonShape3D GetOrCreateShape(string name) |
||||
{ |
||||
var collisionShape = (CollisionShape3D)GetNodeOrNull(name); |
||||
if (collisionShape == null) { |
||||
collisionShape = new() { Name = name, Shape = new ConcavePolygonShape3D() }; |
||||
AddChild(collisionShape); |
||||
collisionShape.Owner = this; |
||||
} |
||||
return (ConcavePolygonShape3D)collisionShape.Shape; |
||||
} |
||||
|
||||
enum Corner |
||||
{ |
||||
TopLeft, |
||||
TopRight, |
||||
BottomLeft, |
||||
BottomRight, |
||||
} |
||||
|
||||
readonly struct GridCorners |
||||
{ |
||||
public Vector3 TopLeft { get; init; } |
||||
public Vector3 TopRight { get; init; } |
||||
public Vector3 BottomLeft { get; init; } |
||||
public Vector3 BottomRight { get; init; } |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
[gd_scene load_steps=2 format=3 uid="uid://b16jht8mfqr21"] |
||||
|
||||
[ext_resource type="PackedScene" uid="uid://buj1g5fpiuqkd" path="res://assets/models/terrain.blend" id="1_ourjj"] |
||||
|
||||
[node name="TerrainSource" instance=ExtResource("1_ourjj")] |
Loading…
Reference in new issue