Compare commits
8 commits
developmen
...
archive/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7e07797c8 | ||
|
|
062633c742 | ||
|
|
049ac16541 | ||
|
|
80a948686f | ||
|
|
8d07ee03fa | ||
|
|
1828ebbe21 | ||
|
|
f6e6cf9e55 | ||
|
|
37b3a63ae6 |
|
|
@ -1,6 +0,0 @@
|
|||
((c++-mode . ((mode . clang-format-on-save)))
|
||||
(c-mode . ((mode . c++)))
|
||||
(nil . ((projectile-project-compilation-cmd . "just build")
|
||||
(projectile-project-run-cmd . "engine/bin/godot.linuxbsd.editor.dev.x86_64.llvm --path project")
|
||||
(projectile-project-configure-cmd . "engine/bin/godot.linuxbsd.editor.dev.x86_64.llvm --path project -e")
|
||||
(projectile-project-test-cmd . "engine/bin/godot.linuxbsd.editor.dev.x86.llvm --path project"))))
|
||||
10
.gitignore
vendored
|
|
@ -5,18 +5,16 @@
|
|||
# When configure fails, SCons outputs these
|
||||
config.log
|
||||
.sconf_temp
|
||||
.config
|
||||
|
||||
# build artifacts
|
||||
# build artefacts
|
||||
.cache/
|
||||
*.o
|
||||
compile_commands.json
|
||||
|
||||
engine/.github
|
||||
project/.godot
|
||||
build/authority.pck
|
||||
build/authority.x86_64
|
||||
build/authority.exe
|
||||
build.zip
|
||||
|
||||
# general-purpose cache folder (used by e.g clangd)
|
||||
.cache
|
||||
|
||||
__pycache__
|
||||
|
|
|
|||
183
design/monarchist_camp.svg
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="297mm"
|
||||
height="210mm"
|
||||
viewBox="0 0 297 210"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
sodipodi:docname="monarchist_camp.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#999999"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.640567"
|
||||
inkscape:cx="483.67425"
|
||||
inkscape:cy="468.43562"
|
||||
inkscape:window-width="2540"
|
||||
inkscape:window-height="1367"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
style="display:inline">
|
||||
<circle
|
||||
style="fill:none;stroke:none;stroke-width:2"
|
||||
id="path1"
|
||||
r="26.849329"
|
||||
cy="-22.617027"
|
||||
cx="144.71928"
|
||||
transform="rotate(30.90333)" />
|
||||
<circle
|
||||
style="fill:none;stroke:#ff3a00;stroke-width:2;stroke-opacity:1"
|
||||
id="path3"
|
||||
cx="197.15057"
|
||||
cy="12.570523"
|
||||
r="59.961803"
|
||||
transform="rotate(30.90333)" />
|
||||
<circle
|
||||
style="fill:none;stroke:#ff3a00;stroke-width:2;stroke-opacity:1"
|
||||
id="path4"
|
||||
cx="170.88332"
|
||||
cy="42.191242"
|
||||
r="20.471628"
|
||||
transform="rotate(30.90333)" />
|
||||
<circle
|
||||
style="fill:none;stroke:#ff3a00;stroke-width:2;stroke-opacity:1"
|
||||
id="path5"
|
||||
cx="157.94936"
|
||||
cy="3.7319982"
|
||||
r="19.529419"
|
||||
transform="rotate(30.90333)" />
|
||||
<circle
|
||||
style="fill:none;stroke:#ff3a00;stroke-width:2;stroke-opacity:1"
|
||||
id="path6"
|
||||
cx="223.90398"
|
||||
cy="-0.37947115"
|
||||
r="30.321995"
|
||||
transform="rotate(30.90333)" />
|
||||
<ellipse
|
||||
style="fill:none;stroke:#ff3a00;stroke-width:2;stroke-opacity:1"
|
||||
id="path7"
|
||||
cx="174.47501"
|
||||
cy="77.630127"
|
||||
rx="6.205318"
|
||||
ry="59.151665"
|
||||
transform="rotate(11.27939)" />
|
||||
<circle
|
||||
style="fill:none;stroke:#ff3a00;stroke-width:2;stroke-opacity:1"
|
||||
id="path8"
|
||||
cx="193.49632"
|
||||
cy="-33.014153"
|
||||
r="13.619201"
|
||||
transform="rotate(30.90333)" />
|
||||
<circle
|
||||
style="fill:none;stroke:#ff3a00;stroke-width:2;stroke-opacity:1"
|
||||
id="path9"
|
||||
cx="224.58922"
|
||||
cy="46.388348"
|
||||
r="16.617136"
|
||||
transform="rotate(30.90333)" />
|
||||
<path
|
||||
style="fill:none;stroke:#ff3a00;stroke-width:2;stroke-opacity:1"
|
||||
d="m 144.06991,169.24883 -7.19465,31.03241"
|
||||
id="path10" />
|
||||
<path
|
||||
style="fill:none;stroke:#ff3a00;stroke-width:2;stroke-opacity:1"
|
||||
d="m 166.80343,51.882914 5.05932,-22.12813"
|
||||
id="path11" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2">
|
||||
<rect
|
||||
style="fill:#000000;stroke:none;stroke-width:2;stroke-opacity:1"
|
||||
id="rect11"
|
||||
width="17.086163"
|
||||
height="26.649414"
|
||||
x="202.4292"
|
||||
y="92.027176"
|
||||
transform="rotate(4.15663)" />
|
||||
<rect
|
||||
style="fill:#000000;stroke:none;stroke-width:2;stroke-opacity:1"
|
||||
id="rect11-3"
|
||||
width="16.747086"
|
||||
height="23.300817"
|
||||
x="211.42197"
|
||||
y="-116.61307"
|
||||
transform="rotate(67.03562)" />
|
||||
<rect
|
||||
style="fill:#000000;stroke:none;stroke-width:2;stroke-opacity:1"
|
||||
id="rect11-3-1"
|
||||
width="16.747086"
|
||||
height="23.300817"
|
||||
x="153.35806"
|
||||
y="52.250866"
|
||||
transform="rotate(25.13231)" />
|
||||
<rect
|
||||
style="fill:#000000;stroke:none;stroke-width:2;stroke-opacity:1"
|
||||
id="rect11-3-1-3"
|
||||
width="16.747086"
|
||||
height="23.300817"
|
||||
x="139.8774"
|
||||
y="30.531561"
|
||||
transform="rotate(16.66083)" />
|
||||
<rect
|
||||
style="fill:#000000;stroke:none;stroke-width:2;stroke-opacity:1"
|
||||
id="rect11-3-1-3-6"
|
||||
width="13.885768"
|
||||
height="15.52168"
|
||||
x="-177.2769"
|
||||
y="-109.64846"
|
||||
transform="rotate(170.10507)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-opacity:1"
|
||||
d="m 168.51515,87.048444 49.7405,-10.74439 -11.59163,-23.78924 -34.05085,-6.36336 z"
|
||||
id="path12"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-opacity:1"
|
||||
d="m 167.90348,92.072994 -6.81162,32.060266 3.42606,9.03285 50.86591,9.20609 4.78459,-60.134606 z"
|
||||
id="path13"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-opacity:1"
|
||||
d="m 161.76062,139.13159 -9.82191,20.20953 4.20318,13.74019 19.43214,4.89741 29.11417,-13.30994 8.46487,-16.33574 z"
|
||||
id="path14"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-opacity:1"
|
||||
d="m 131.20919,159.8118 8.99249,-4.41574 7.79771,-39.28515 -43.81905,-9.86931 -3.00506,27.21896 17.64521,22.70473 z"
|
||||
id="path15"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-opacity:1"
|
||||
d="m 107.52909,93.556844 c 0.58795,0.35194 40.17069,12.855976 40.17069,12.855976 l 11.52783,-43.543596 -33.50469,-1.2183 z"
|
||||
id="path16"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#000000;stroke:none;stroke-width:2;stroke-opacity:1"
|
||||
d="M -10.027839,166.80916 -0.34598048,166.54137 7.8424327,122.34249 25.872585,93.782258 34.797274,63.284639 65.815884,30.44139 108.46648,12.423235 l 8.387,-18.0823962 -127.760166,0.1592669 z"
|
||||
id="path17"
|
||||
sodipodi:nodetypes="cccccccccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6 KiB |
2
engine
|
|
@ -1 +1 @@
|
|||
Subproject commit 6b76a5a8dc011723033cc8ad2ba3da345daab039
|
||||
Subproject commit e531f3eb7b13a9adaaf5b15ac9b3aebc4c2030cd
|
||||
3
justfile
|
|
@ -37,8 +37,9 @@ release-windows: build
|
|||
initialize-template projectname:
|
||||
# Initializing Template {{projectname}}
|
||||
sed -i -e "s/PROJECT/{{projectname}}/g" ./modules/PROJECT/register_types.h ./modules/PROJECT/register_types.cpp ./project/project.godot ./project/export_presets.cfg .gitignore
|
||||
sed -i -e "s/authority/{{projectname}}/" ./justfile
|
||||
sed "s/change_me/{{projectname}}/" ./justfile
|
||||
mv ./modules/PROJECT ./modules/{{projectname}}
|
||||
# Done Initializing, you will still have to update BUILD_NAME in your justfile
|
||||
|
||||
format:
|
||||
# Formatting Custom Modules
|
||||
|
|
|
|||
BIN
modules/authority/__pycache__/config.cpython-313.pyc
Normal file
81
modules/authority/actor_body.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#include "actor_body.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "core/object/object.h"
|
||||
#include "macros.h"
|
||||
|
||||
void ActorBody::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::FLOAT, movement_speed, PROPERTY_HINT_RANGE, "0.0,20.0");
|
||||
}
|
||||
|
||||
void ActorBody::physics_process(double delta) {
|
||||
if (this->teleport_on_process) {
|
||||
set_global_position(this->teleport_target);
|
||||
this->teleport_on_process = false;
|
||||
force_update_transform();
|
||||
}
|
||||
set_velocity(get_movement_direction() * this->movement_speed);
|
||||
move_and_slide();
|
||||
}
|
||||
|
||||
void ActorBody::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
set_physics_process(true);
|
||||
set_as_top_level(true);
|
||||
case NOTIFICATION_PHYSICS_PROCESS:
|
||||
physics_process(get_physics_process_delta_time());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ActorBody::set_movement_direction(Vector3 direction) {
|
||||
this->mode = Direction;
|
||||
this->movement_vector = Vector3(direction.x, 0.f, direction.z).normalized();
|
||||
}
|
||||
|
||||
Vector3 ActorBody::get_movement_direction() const {
|
||||
switch (this->mode) {
|
||||
case Position: {
|
||||
Vector3 const direction_3d{ get_global_position().direction_to(get_movement_target()) };
|
||||
return Vector3{ direction_3d.x, 0.f, direction_3d.z };
|
||||
} break;
|
||||
case Direction:
|
||||
return this->movement_vector;
|
||||
}
|
||||
}
|
||||
|
||||
void ActorBody::set_movement_target(Vector3 location) {
|
||||
this->mode = Position;
|
||||
this->movement_vector = { location.x, 0.f, location.z };
|
||||
}
|
||||
|
||||
Vector3 ActorBody::get_movement_target() const {
|
||||
switch (this->mode) {
|
||||
case Position:
|
||||
return this->movement_vector;
|
||||
case Direction:
|
||||
return get_global_position() + this->movement_vector;
|
||||
}
|
||||
}
|
||||
|
||||
void ActorBody::teleport(Vector3 target) {
|
||||
this->teleport_target = target;
|
||||
this->teleport_on_process = true;
|
||||
}
|
||||
|
||||
void ActorBody::set_movement_speed(float speed) {
|
||||
this->movement_speed = speed;
|
||||
}
|
||||
|
||||
float ActorBody::get_movement_speed() const {
|
||||
return this->movement_speed;
|
||||
}
|
||||
|
||||
ActorBody::MovementMode ActorBody::get_movement_mode() const {
|
||||
return this->mode;
|
||||
}
|
||||
37
modules/authority/actor_body.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef ACTOR_BODY_H
|
||||
#define ACTOR_BODY_H
|
||||
|
||||
#include "scene/3d/physics/character_body_3d.h"
|
||||
|
||||
class ActorBody : public CharacterBody3D {
|
||||
GDCLASS(ActorBody, CharacterBody3D);
|
||||
static void _bind_methods();
|
||||
void physics_process(double delta);
|
||||
|
||||
public:
|
||||
// support both directional and positional movement modes
|
||||
enum MovementMode {
|
||||
Direction,
|
||||
Position
|
||||
};
|
||||
void _notification(int what);
|
||||
|
||||
void set_movement_direction(Vector3 direction);
|
||||
Vector3 get_movement_direction() const;
|
||||
void set_movement_target(Vector3 location);
|
||||
Vector3 get_movement_target() const;
|
||||
void teleport(Vector3 target);
|
||||
|
||||
void set_movement_speed(float speed);
|
||||
float get_movement_speed() const;
|
||||
MovementMode get_movement_mode() const;
|
||||
|
||||
private:
|
||||
float movement_speed{ 3.f };
|
||||
Vector3 movement_vector{ 0.f, 0.f, 0.f };
|
||||
MovementMode mode{ Direction };
|
||||
bool teleport_on_process{ false };
|
||||
Vector3 teleport_target{};
|
||||
};
|
||||
|
||||
#endif //! ACTOR_BODY_H
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
#include "character.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "macros.h"
|
||||
|
||||
void CharacterData::_bind_methods() {
|
||||
BIND_PROPERTY(Variant::FLOAT, speed);
|
||||
}
|
||||
|
||||
void Character::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::OBJECT, data, PROPERTY_HINT_RESOURCE_TYPE, "CharacterData");
|
||||
}
|
||||
|
||||
void Character::physics_process(double delta) {
|
||||
Vector3 const velocity{ get_velocity() };
|
||||
Vector3 new_velocity{ velocity };
|
||||
new_velocity.x = this->world_movement_direction.x;
|
||||
new_velocity.z = this->world_movement_direction.y;
|
||||
set_velocity(new_velocity);
|
||||
if (!velocity.is_zero_approx()) {
|
||||
move_and_slide();
|
||||
}
|
||||
}
|
||||
|
||||
void Character::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint() || !this->data.is_valid()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
set_physics_process(true);
|
||||
return;
|
||||
case NOTIFICATION_PHYSICS_PROCESS:
|
||||
physics_process(get_physics_process_delta_time());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PackedStringArray Character::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (this->data.is_null()) {
|
||||
warnings.push_back("Character requires 'data' to be initialised. To avoid crashes consider adding a placeholder if you intend to programmatically initialise it.");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void Character::set_movement(Vector2 movement) {
|
||||
this->world_movement_direction = movement;
|
||||
}
|
||||
|
||||
bool Character::is_moving() const {
|
||||
return !this->world_movement_direction.is_zero_approx();
|
||||
}
|
||||
|
||||
void CharacterState::_bind_methods() {
|
||||
BIND_PROPERTY(Variant::BOOL, start_active);
|
||||
}
|
||||
|
||||
void CharacterState::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
this->character = cast_to<Character>(get_parent());
|
||||
ERR_FAIL_COND_EDMSG(this->character == nullptr, "CharacterState requires parent to be of type Character");
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
if (start_active) {
|
||||
callable_mp(this, &self_type::set_state_active).call_deferred(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PackedStringArray CharacterState::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (cast_to<Character>(get_parent()) == nullptr) {
|
||||
warnings.push_back("CharacterState requires direct Character parent");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void CharacterState::switch_to_state(String value) {
|
||||
if (!this->state_active) {
|
||||
print_error(vformat("Attempt to switch from inactive state %s to new state %s", get_path(), value));
|
||||
return;
|
||||
}
|
||||
set_state_active(false);
|
||||
stack_state_independent(value);
|
||||
}
|
||||
|
||||
void CharacterState::stack_state_dependent(String value) {
|
||||
if (!this->state_active) {
|
||||
print_error(vformat("Attempt to stack dependent state %s from inactive state %s", value, get_path()));
|
||||
return;
|
||||
}
|
||||
Node *node{ get_parent()->get_node(value) };
|
||||
if (CharacterState * state{ cast_to<CharacterState>(node) }) {
|
||||
state->depending_state = this;
|
||||
this->dependent_states.insert(state);
|
||||
state->set_state_active(true);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterState::notify_dependent_inactive(CharacterState *state) {
|
||||
if (!this->state_active) {
|
||||
print_error(vformat("Received notification that dependent state %s became inactive, while depending state %s was inactive", state->get_path(), get_path()));
|
||||
return;
|
||||
}
|
||||
this->dependent_states.erase(state);
|
||||
}
|
||||
|
||||
void CharacterState::stack_state_independent(String value) {
|
||||
if (!this->state_active) {
|
||||
print_error(vformat("Attempt to stack state %s from inactive state %s", value, get_path()));
|
||||
return;
|
||||
}
|
||||
Node *node{ get_parent()->get_node(value) };
|
||||
if (CharacterState * state{ cast_to<CharacterState>(node) }) {
|
||||
state->set_state_active(true);
|
||||
} else {
|
||||
print_error(vformat("Attempt to stack nonexistent state %s from %s", value, get_path()));
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterState::set_state_active(bool active) {
|
||||
if (this->state_active != active) {
|
||||
this->state_active = active;
|
||||
if (active) {
|
||||
state_entered();
|
||||
} else {
|
||||
state_exited();
|
||||
if (this->depending_state && this->depending_state->state_active) {
|
||||
this->depending_state->notify_dependent_inactive(this);
|
||||
}
|
||||
this->depending_state = nullptr;
|
||||
for (CharacterState *state : this->dependent_states) {
|
||||
state->set_state_active(false);
|
||||
}
|
||||
this->dependent_states.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CharacterState::get_state_active() const {
|
||||
return this->state_active;
|
||||
}
|
||||
|
||||
Character *CharacterState::get_character() const {
|
||||
return this->character;
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "macros.h"
|
||||
#include "scene/3d/physics/character_body_3d.h"
|
||||
|
||||
class CharacterData : public Resource {
|
||||
GDCLASS(CharacterData, Resource);
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
float speed{};
|
||||
|
||||
public:
|
||||
GET_SET_FNS(float, speed);
|
||||
};
|
||||
|
||||
class Character : public CharacterBody3D {
|
||||
GDCLASS(Character, CharacterBody3D);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void physics_process(double delta);
|
||||
void _notification(int what);
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
public:
|
||||
void set_movement(Vector2 movement);
|
||||
bool is_moving() const;
|
||||
|
||||
private:
|
||||
Ref<CharacterData> data{};
|
||||
Vector2 world_movement_direction{};
|
||||
|
||||
public:
|
||||
GET_SET_FNS(Ref<CharacterData>, data);
|
||||
};
|
||||
|
||||
class CharacterState : public Node {
|
||||
GDCLASS(CharacterState, Node);
|
||||
static void _bind_methods();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
void switch_to_state(String state);
|
||||
void stack_state_dependent(String state);
|
||||
void notify_dependent_inactive(CharacterState *dependent);
|
||||
void stack_state_independent(String state);
|
||||
virtual void state_entered() {}
|
||||
virtual void state_exited() {}
|
||||
|
||||
public:
|
||||
void set_state_active(bool active);
|
||||
bool get_state_active() const;
|
||||
Character *get_character() const;
|
||||
|
||||
private:
|
||||
bool start_active{ false };
|
||||
bool state_active{ false };
|
||||
|
||||
Character *character{ nullptr };
|
||||
HashSet<CharacterState *> dependent_states{};
|
||||
CharacterState *depending_state{ nullptr };
|
||||
|
||||
public:
|
||||
GET_SET_FNS(bool, start_active);
|
||||
};
|
||||
18
modules/authority/game_state.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include "game_state.h"
|
||||
#include "macros.h"
|
||||
|
||||
GameState *GameState::singleton_instance{ nullptr };
|
||||
|
||||
void GameState::_bind_methods() {}
|
||||
|
||||
GameState::GameState() {
|
||||
self_type::singleton_instance = this;
|
||||
}
|
||||
|
||||
GameState::~GameState() {
|
||||
self_type::singleton_instance = nullptr;
|
||||
}
|
||||
|
||||
GameState *GameState::get_singleton() {
|
||||
return self_type::singleton_instance;
|
||||
}
|
||||
19
modules/authority/game_state.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef GAME_STATE_H
|
||||
#define GAME_STATE_H
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/templates/vector.h"
|
||||
|
||||
class GameState : public Object {
|
||||
GDCLASS(GameState, Object);
|
||||
static void _bind_methods();
|
||||
static GameState *singleton_instance;
|
||||
|
||||
public:
|
||||
GameState();
|
||||
virtual ~GameState();
|
||||
static GameState *get_singleton();
|
||||
};
|
||||
|
||||
#endif // !GAME_STATE_H
|
||||
20
modules/authority/macros.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef GODOT_EXTRA_MACROS_H
|
||||
#define GODOT_EXTRA_MACROS_H
|
||||
|
||||
#define BIND_GET_SET(m_property) \
|
||||
ClassDB::bind_method(D_METHOD("set_" #m_property, #m_property), \
|
||||
&self_type::set_##m_property); \
|
||||
ClassDB::bind_method(D_METHOD("get_" #m_property), \
|
||||
&self_type::get_##m_property)
|
||||
|
||||
#define BIND_HPROPERTY(m_type, m_property, ...) \
|
||||
BIND_GET_SET(m_property); \
|
||||
ADD_PROPERTY(PropertyInfo(m_type, #m_property, __VA_ARGS__), \
|
||||
"set_" #m_property, "get_" #m_property)
|
||||
|
||||
#define BIND_PROPERTY(m_type, m_property) \
|
||||
BIND_GET_SET(m_property); \
|
||||
ADD_PROPERTY(PropertyInfo(m_type, #m_property), "set_" #m_property, \
|
||||
"get_" #m_property)
|
||||
|
||||
#endif // !GODOT_EXTRA_MACROS_H
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
#include "nav_marker.h"
|
||||
|
||||
void NavMarker::_bind_methods() {}
|
||||
|
||||
void NavMarker::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
this->set_gizmo_extents(3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "macros.h"
|
||||
#include "scene/3d/marker_3d.h"
|
||||
class Character;
|
||||
|
||||
class NavMarker : public Marker3D {
|
||||
GDCLASS(NavMarker, Marker3D);
|
||||
static void _bind_methods();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
private:
|
||||
Character *claimed{ nullptr };
|
||||
|
||||
public:
|
||||
GET_SET_FNS(Character *, claimed);
|
||||
};
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
#include "party_member_states.h"
|
||||
#include "authority/nav_marker.h"
|
||||
#include "authority/player_character.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "core/error/error_macros.h"
|
||||
#include "core/templates/vector.h"
|
||||
|
||||
void PartyMemberFollow::_bind_methods() {}
|
||||
|
||||
void PartyMemberFollow::process_position_target() {
|
||||
Vector3 const marker_position{ this->claimed_marker->get_global_position() };
|
||||
Vector3 const nav_target{ this->nav->get_target_position() };
|
||||
Vector3 const global_position{ get_character()->get_global_position() };
|
||||
if (global_position.distance_squared_to(marker_position) < 0.5) {
|
||||
return;
|
||||
}
|
||||
if (nav_target.distance_squared_to(marker_position) > 0.25) {
|
||||
this->nav->set_target_position(marker_position);
|
||||
}
|
||||
if (this->nav->is_navigation_finished()) {
|
||||
return;
|
||||
}
|
||||
Vector3 velocity{ global_position.direction_to(this->nav->get_next_path_position()) };
|
||||
velocity.y = 0;
|
||||
if (this->nav->get_avoidance_enabled()) {
|
||||
this->nav->set_velocity(velocity * get_character()->get_data()->get_speed());
|
||||
} else {
|
||||
push_movement_direction(velocity * get_character()->get_data()->get_speed());
|
||||
}
|
||||
}
|
||||
|
||||
void PartyMemberFollow::push_movement_direction(Vector3 velocity) {
|
||||
get_character()->set_movement(Vector2{ velocity.x, velocity.z });
|
||||
}
|
||||
|
||||
void PartyMemberFollow::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
this->nav = cast_to<NavigationAgent3D>(get_parent()->get_node(NodePath("%NavigationAgent3D")));
|
||||
ERR_FAIL_COND_EDMSG(this->nav == nullptr, "PartyMemberFollow cannot initialise without a navigation agent");
|
||||
return;
|
||||
case NOTIFICATION_PROCESS:
|
||||
process_position_target();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PackedStringArray PartyMemberFollow::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (!get_parent()->has_node(NodePath("%NavigationAgent3D")) || !cast_to<NavigationAgent3D>(get_parent()->get_node(NodePath("%NavigationAgent3D")))) {
|
||||
warnings.push_back("PartyMemberFollow expects a scene sibling of type NavigationAgent3D named with unique name '%NavigationAgent3D'");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void PartyMemberFollow::state_entered() {
|
||||
Vector<NavMarker *> const &markers{ PlayerCharacter::get_singleton()->get_party_follow_markers() };
|
||||
for (NavMarker *marker : markers) {
|
||||
if (marker->get_claimed() == nullptr) {
|
||||
marker->set_claimed(get_character());
|
||||
this->claimed_marker = marker;
|
||||
if (this->nav->get_avoidance_enabled()) {
|
||||
this->nav->connect("velocity_computed", callable_mp(this, &self_type::push_movement_direction));
|
||||
}
|
||||
set_process(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ERR_FAIL_EDMSG("PartyMemberFollow could not find an unclaimed player follow marker");
|
||||
set_state_active(false);
|
||||
}
|
||||
|
||||
void PartyMemberFollow::state_exited() {
|
||||
if (this->claimed_marker) {
|
||||
this->claimed_marker->set_claimed(nullptr);
|
||||
this->nav->disconnect("velocity_computed", callable_mp(this, &self_type::push_movement_direction));
|
||||
set_process(false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "authority/character.h"
|
||||
#include "authority/nav_marker.h"
|
||||
#include "scene/3d/navigation/navigation_agent_3d.h"
|
||||
|
||||
class PartyMemberFollow : public CharacterState {
|
||||
GDCLASS(PartyMemberFollow, CharacterState);
|
||||
static void _bind_methods();
|
||||
void process_position_target();
|
||||
void push_movement_direction(Vector3 velocity);
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
void state_entered() override;
|
||||
void state_exited() override;
|
||||
|
||||
private:
|
||||
NavigationAgent3D *nav{ nullptr };
|
||||
NavMarker *claimed_marker{ nullptr };
|
||||
};
|
||||
42
modules/authority/player_actor.cpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#include "player_actor.h"
|
||||
#include "authority/game_state.h"
|
||||
#include "authority/player_input.h"
|
||||
#include "scene/main/viewport.h"
|
||||
|
||||
void PlayerActor::_bind_methods() {
|
||||
}
|
||||
|
||||
void PlayerActor::ready() {
|
||||
PlayerInput *input{ cast_to<PlayerInput>(get_node(NodePath("%PlayerInput"))) };
|
||||
input->connect(PlayerInput::signal_movement, callable_mp(this, &self_type::input_move));
|
||||
set_process(true);
|
||||
}
|
||||
|
||||
void PlayerActor::process(double delta) {
|
||||
Basis const basis{ this->get_viewport()->get_camera_3d()->get_global_basis() };
|
||||
Vector3 x{ basis.get_column(0) };
|
||||
x = Vector3{ x.x, 0.f, x.z }.normalized();
|
||||
Vector3 z{ basis.get_column(2) };
|
||||
z = Vector3{ z.x, 0.f, z.z }.normalized();
|
||||
set_movement_direction(x * this->move_input.x + z * this->move_input.y);
|
||||
}
|
||||
|
||||
void PlayerActor::input_move(Vector2 movement) {
|
||||
this->move_input = movement;
|
||||
}
|
||||
|
||||
void PlayerActor::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
ready();
|
||||
return;
|
||||
case NOTIFICATION_PROCESS:
|
||||
process(get_process_delta_time());
|
||||
return;
|
||||
}
|
||||
}
|
||||
20
modules/authority/player_actor.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef PLAYER_ACTOR_H
|
||||
#define PLAYER_ACTOR_H
|
||||
|
||||
#include "actor_body.h"
|
||||
|
||||
class PlayerActor : public ActorBody {
|
||||
GDCLASS(PlayerActor, ActorBody);
|
||||
static void _bind_methods();
|
||||
void ready();
|
||||
void process(double delta);
|
||||
void input_move(Vector2 movement);
|
||||
|
||||
public:
|
||||
void _notification(int what);
|
||||
|
||||
private:
|
||||
Vector2 move_input{ 0.f, 0.f };
|
||||
};
|
||||
|
||||
#endif // !PLAYER_ACTOR_H
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "scene/3d/camera_3d.h"
|
||||
|
||||
class PlayerCamera : public Camera3D {
|
||||
GDCLASS(PlayerCamera, Camera3D);
|
||||
static void _bind_methods();
|
||||
};
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
#include "player_character.h"
|
||||
#include "authority/nav_marker.h"
|
||||
|
||||
void PlayerCharacter::_bind_methods() {}
|
||||
|
||||
void PlayerCharacter::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
instance = this;
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
for (Variant var : find_children("*", "NavMarker")) {
|
||||
if (NavMarker * marker{ cast_to<NavMarker>(var) }) {
|
||||
this->party_follow_markers.push_back(marker);
|
||||
}
|
||||
}
|
||||
ERR_FAIL_COND_EDMSG(this->party_follow_markers.size() < 4, "PlayerCharacter should have at least 4 follow NavMarkers for party members");
|
||||
return;
|
||||
case NOTIFICATION_EXIT_TREE:
|
||||
if (instance == this) {
|
||||
instance = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerCharacter *PlayerCharacter::instance{ nullptr };
|
||||
|
||||
PlayerCharacter *PlayerCharacter::get_singleton() {
|
||||
return instance;
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "authority/character.h"
|
||||
#include "authority/nav_marker.h"
|
||||
#include "macros.h"
|
||||
|
||||
class PlayerCharacter : public Character {
|
||||
GDCLASS(PlayerCharacter, Character);
|
||||
static void _bind_methods();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
private:
|
||||
Vector<NavMarker *> party_follow_markers{};
|
||||
static PlayerCharacter *instance;
|
||||
|
||||
public:
|
||||
static PlayerCharacter *get_singleton();
|
||||
GET_SET_FNS(Vector<NavMarker *> const &, party_follow_markers);
|
||||
};
|
||||
146
modules/authority/player_input.cpp
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#include "player_input.h"
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
#include "macros.h"
|
||||
|
||||
String const PlayerInput::signal_movement{ "movement" };
|
||||
String const PlayerInput::signal_look{ "look" };
|
||||
String const PlayerInput::signal_mouselook{ "mouselook" };
|
||||
|
||||
void PlayerInput::_bind_methods() {
|
||||
BIND_PROPERTY(Variant::STRING, action_move_left);
|
||||
BIND_PROPERTY(Variant::STRING, action_move_right);
|
||||
BIND_PROPERTY(Variant::STRING, action_move_forward);
|
||||
BIND_PROPERTY(Variant::STRING, action_move_back);
|
||||
BIND_PROPERTY(Variant::STRING, action_look_left);
|
||||
BIND_PROPERTY(Variant::STRING, action_look_right);
|
||||
BIND_PROPERTY(Variant::STRING, action_look_up);
|
||||
BIND_PROPERTY(Variant::STRING, action_look_down);
|
||||
ADD_SIGNAL(MethodInfo(self_type::signal_movement, PropertyInfo(Variant::VECTOR2, "movement_directions")));
|
||||
ADD_SIGNAL(MethodInfo(self_type::signal_look, PropertyInfo(Variant::VECTOR2, "look_delta")));
|
||||
ADD_SIGNAL(MethodInfo(self_type::signal_mouselook, PropertyInfo(Variant::VECTOR2, "mouse_delta")));
|
||||
}
|
||||
|
||||
void PlayerInput::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
set_process_unhandled_input(true);
|
||||
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerInput::unhandled_input(Ref<InputEvent> const &event) {
|
||||
Input *input{ Input::get_singleton() };
|
||||
|
||||
bool is_movement_action{ event->is_action(self_type::action_move_left) };
|
||||
is_movement_action |= event->is_action(self_type::action_move_right);
|
||||
is_movement_action |= event->is_action(self_type::action_move_forward);
|
||||
is_movement_action |= event->is_action(self_type::action_move_back);
|
||||
if (is_movement_action) {
|
||||
float const h_axis{ input->get_axis(self_type::action_move_left, self_type::action_move_right) };
|
||||
float const v_axis{ input->get_axis(self_type::action_move_forward, self_type::action_move_back) };
|
||||
emit_signal(self_type::signal_movement, Vector2{ h_axis, v_axis });
|
||||
}
|
||||
|
||||
bool is_look_action{ event->is_action(self_type::action_look_left) };
|
||||
is_look_action |= event->is_action(self_type::action_look_right);
|
||||
is_look_action |= event->is_action(self_type::action_look_up);
|
||||
is_look_action |= event->is_action(self_type::action_look_down);
|
||||
if (is_look_action) {
|
||||
float const h_axis{ input->get_axis(self_type::action_look_right, self_type::action_look_left) };
|
||||
float const v_axis{ input->get_axis(self_type::action_look_down, self_type::action_look_up) };
|
||||
emit_signal(self_type::signal_look, Vector2{ h_axis, v_axis } * this->look_speed);
|
||||
}
|
||||
|
||||
Ref<InputEventMouseMotion> mouse_motion_action{ event };
|
||||
if (mouse_motion_action.is_valid()) {
|
||||
Vector2 const velocity{ mouse_motion_action->get_relative() };
|
||||
emit_signal(self_type::signal_mouselook, velocity * this->mouselook_speed);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerInput::set_mouselook_speed(Vector2 speed) {
|
||||
this->mouselook_speed = speed;
|
||||
}
|
||||
|
||||
Vector2 PlayerInput::get_mouselook_speed() const {
|
||||
return this->mouselook_speed;
|
||||
}
|
||||
|
||||
void PlayerInput::set_look_speed(Vector2 speed) {
|
||||
this->look_speed = speed;
|
||||
}
|
||||
|
||||
Vector2 PlayerInput::get_look_speed() const {
|
||||
return this->look_speed;
|
||||
}
|
||||
|
||||
void PlayerInput::set_action_move_left(String action) {
|
||||
this->action_move_left = action;
|
||||
}
|
||||
|
||||
String PlayerInput::get_action_move_left() const {
|
||||
return this->action_move_left;
|
||||
}
|
||||
|
||||
void PlayerInput::set_action_move_right(String action) {
|
||||
this->action_move_right = action;
|
||||
}
|
||||
|
||||
String PlayerInput::get_action_move_right() const {
|
||||
return this->action_move_right;
|
||||
}
|
||||
|
||||
void PlayerInput::set_action_move_forward(String action) {
|
||||
this->action_move_forward = action;
|
||||
}
|
||||
|
||||
String PlayerInput::get_action_move_forward() const {
|
||||
return this->action_move_forward;
|
||||
}
|
||||
|
||||
void PlayerInput::set_action_move_back(String action) {
|
||||
this->action_move_back = action;
|
||||
}
|
||||
|
||||
String PlayerInput::get_action_move_back() const {
|
||||
return this->action_move_back;
|
||||
}
|
||||
|
||||
void PlayerInput::set_action_look_left(String action) {
|
||||
this->action_look_left = action;
|
||||
}
|
||||
|
||||
String PlayerInput::get_action_look_left() const {
|
||||
return this->action_look_left;
|
||||
}
|
||||
|
||||
void PlayerInput::set_action_look_right(String action) {
|
||||
this->action_look_right = action;
|
||||
}
|
||||
|
||||
String PlayerInput::get_action_look_right() const {
|
||||
return this->action_look_right;
|
||||
}
|
||||
|
||||
void PlayerInput::set_action_look_up(String action) {
|
||||
this->action_look_up = action;
|
||||
}
|
||||
|
||||
String PlayerInput::get_action_look_up() const {
|
||||
return this->action_look_up;
|
||||
}
|
||||
|
||||
void PlayerInput::set_action_look_down(String action) {
|
||||
this->action_look_down = action;
|
||||
}
|
||||
|
||||
String PlayerInput::get_action_look_down() const {
|
||||
return this->action_look_down;
|
||||
}
|
||||
58
modules/authority/player_input.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#ifndef PLAYER_INPUT_H
|
||||
#define PLAYER_INPUT_H
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class PlayerInput : public Node {
|
||||
GDCLASS(PlayerInput, Node);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void _notification(int what);
|
||||
virtual void unhandled_input(Ref<InputEvent> const &event) override;
|
||||
|
||||
void set_mouselook_speed(Vector2 speed);
|
||||
Vector2 get_mouselook_speed() const;
|
||||
void set_look_speed(Vector2 speed);
|
||||
Vector2 get_look_speed() const;
|
||||
|
||||
void set_action_move_left(String action);
|
||||
String get_action_move_left() const;
|
||||
void set_action_move_right(String action);
|
||||
String get_action_move_right() const;
|
||||
void set_action_move_forward(String action);
|
||||
String get_action_move_forward() const;
|
||||
void set_action_move_back(String action);
|
||||
String get_action_move_back() const;
|
||||
|
||||
void set_action_look_left(String action);
|
||||
String get_action_look_left() const;
|
||||
void set_action_look_right(String action);
|
||||
String get_action_look_right() const;
|
||||
void set_action_look_up(String action);
|
||||
String get_action_look_up() const;
|
||||
void set_action_look_down(String action);
|
||||
String get_action_look_down() const;
|
||||
|
||||
public:
|
||||
static String const signal_movement;
|
||||
static String const signal_look;
|
||||
static String const signal_mouselook;
|
||||
|
||||
private:
|
||||
Vector2 mouselook_speed{ 0.001f, 0.001f };
|
||||
Vector2 look_speed{ 1.75f, 1.75f };
|
||||
|
||||
String action_move_left{ "move_left" };
|
||||
String action_move_right{ "move_right" };
|
||||
String action_move_forward{ "move_forward" };
|
||||
String action_move_back{ "move_back" };
|
||||
|
||||
String action_look_left{ "look_left" };
|
||||
String action_look_right{ "look_right" };
|
||||
String action_look_up{ "look_up" };
|
||||
String action_look_down{ "look_down" };
|
||||
};
|
||||
|
||||
#endif // !PLAYER_INPUT_H
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
#include "player_states.h"
|
||||
#include "core/input/input.h"
|
||||
#include "core/math/basis.h"
|
||||
#include "scene/3d/camera_3d.h"
|
||||
#include "scene/main/viewport.h"
|
||||
|
||||
void PlayerInputState::_bind_methods() {}
|
||||
|
||||
void PlayerInputState::unhandled_input(Ref<InputEvent> const &what) {
|
||||
bool const is_move_vertical{ what->is_action("move_forward") || what->is_action("move_backward") };
|
||||
bool const is_move_horizontal{ what->is_action("move_right") || what->is_action("move_left") };
|
||||
if (is_move_vertical || is_move_horizontal) {
|
||||
stack_state_dependent("PlayerMovementState");
|
||||
get_viewport()->set_input_as_handled();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerInputState::state_entered() {
|
||||
set_process_unhandled_input(true);
|
||||
}
|
||||
|
||||
void PlayerInputState::state_exited() {
|
||||
set_process_unhandled_input(false);
|
||||
}
|
||||
|
||||
void PlayerMovementState::_bind_methods() {}
|
||||
|
||||
void PlayerMovementState::process(double delta) {
|
||||
Basis const basis{ get_viewport()->get_camera_3d()->get_global_basis() };
|
||||
Vector2 backward{ basis.get_column(0).x, basis.get_column(0).z };
|
||||
if (backward.is_zero_approx()) {
|
||||
backward = Vector2{ basis.get_column(1).x, basis.get_column(1).z };
|
||||
}
|
||||
Vector2 const right{ basis.get_column(2).x, basis.get_column(2).z };
|
||||
get_character()->set_movement((backward.normalized() * this->movement.x + right.normalized() * this->movement.y) * get_character()->get_data()->get_speed());
|
||||
}
|
||||
|
||||
void PlayerMovementState::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
set_process_input(true);
|
||||
return;
|
||||
case NOTIFICATION_PROCESS:
|
||||
process(get_process_delta_time());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerMovementState::input(Ref<InputEvent> const &what) {
|
||||
bool const is_move_vertical{ what->is_action("move_forward") || what->is_action("move_backward") };
|
||||
bool const is_move_horizontal{ what->is_action("move_right") || what->is_action("move_left") };
|
||||
if (is_move_vertical || is_move_horizontal) {
|
||||
this->movement = {
|
||||
Input::get_singleton()->get_axis("move_left", "move_right"),
|
||||
Input::get_singleton()->get_axis("move_forward", "move_backward")
|
||||
};
|
||||
this->movement.normalize();
|
||||
}
|
||||
if (this->get_state_active() && this->movement.is_zero_approx()) {
|
||||
set_state_active(false);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerMovementState::state_entered() {
|
||||
set_process(true);
|
||||
}
|
||||
|
||||
void PlayerMovementState::state_exited() {
|
||||
set_process(false);
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "authority/character.h"
|
||||
|
||||
class PlayerInputState : public CharacterState {
|
||||
GDCLASS(PlayerInputState, CharacterState);
|
||||
static void _bind_methods();
|
||||
|
||||
protected:
|
||||
void unhandled_input(Ref<InputEvent> const &event) override;
|
||||
void state_entered() override;
|
||||
void state_exited() override;
|
||||
};
|
||||
|
||||
class PlayerMovementState : public CharacterState {
|
||||
GDCLASS(PlayerMovementState, CharacterState);
|
||||
static void _bind_methods();
|
||||
void ready();
|
||||
void process(double delta);
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
void input(Ref<InputEvent> const &event) override;
|
||||
void state_entered() override;
|
||||
void state_exited() override;
|
||||
|
||||
private:
|
||||
Vector2 movement{};
|
||||
};
|
||||
|
|
@ -1,28 +1,34 @@
|
|||
#include "register_types.h"
|
||||
|
||||
#include "authority/character.h"
|
||||
#include "authority/nav_marker.h"
|
||||
#include "authority/party_member_states.h"
|
||||
#include "authority/player_character.h"
|
||||
#include "authority/player_states.h"
|
||||
#include "authority/actor_body.h"
|
||||
#include "authority/game_state.h"
|
||||
#include "authority/player_actor.h"
|
||||
#include "authority/player_input.h"
|
||||
#include "authority/third_person_camera.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "core/object/class_db.h"
|
||||
|
||||
GameState *game_state{ nullptr };
|
||||
|
||||
void initialize_authority_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
ClassDB::register_class<CharacterData>();
|
||||
ClassDB::register_class<Character>();
|
||||
ClassDB::register_class<CharacterState>();
|
||||
ClassDB::register_class<PlayerCharacter>();
|
||||
ClassDB::register_class<PlayerInputState>();
|
||||
ClassDB::register_class<PlayerMovementState>();
|
||||
ClassDB::register_class<NavMarker>();
|
||||
ClassDB::register_class<PartyMemberFollow>();
|
||||
GDREGISTER_CLASS(GameState);
|
||||
GDREGISTER_CLASS(ActorBody);
|
||||
GDREGISTER_CLASS(PlayerActor);
|
||||
GDREGISTER_CLASS(PlayerInput);
|
||||
GDREGISTER_CLASS(ThirdPersonCamera);
|
||||
|
||||
game_state = memnew(GameState);
|
||||
Engine::get_singleton()->add_singleton(Engine::Singleton("GameState", GameState::get_singleton()));
|
||||
}
|
||||
|
||||
void uninitialize_authority_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
if (game_state) {
|
||||
memdelete(game_state);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
54
modules/authority/third_person_camera.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#include "third_person_camera.h"
|
||||
#include "player_input.h"
|
||||
|
||||
void ThirdPersonCamera::_bind_methods() {
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::ready() {
|
||||
set_process(true);
|
||||
if (PlayerInput * player_input{ cast_to<PlayerInput>(get_node(NodePath("%PlayerInput"))) }) {
|
||||
player_input->connect(PlayerInput::signal_look, callable_mp(this, &self_type::on_look_input));
|
||||
player_input->connect(PlayerInput::signal_mouselook, callable_mp(this, &self_type::on_mouselook_input));
|
||||
}
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::process(double delta) {
|
||||
this->look_rotation += look_rotation_motion * delta;
|
||||
this->look_rotation.y = CLAMP(this->look_rotation.y, -Math_PI / 2.5, Math_PI / 2.5);
|
||||
Vector3 pivot{ get_parent_node_3d()->get_global_position() };
|
||||
pivot.y += this->height;
|
||||
Vector3 offset{ 0.f, 0.f, this->distance };
|
||||
offset.rotate({ 1.f, 0.f, 0.f }, this->look_rotation.y);
|
||||
offset.rotate({ 0.f, 1.f, 0.f }, this->look_rotation.x);
|
||||
look_at_from_position(pivot + offset, pivot);
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::on_look_input(Vector2 input) {
|
||||
this->look_rotation_motion = input;
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::on_mouselook_input(Vector2 input) {
|
||||
this->look_rotation -= input;
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
case NOTIFICATION_READY:
|
||||
ready();
|
||||
break;
|
||||
case NOTIFICATION_PROCESS:
|
||||
process(get_process_delta_time());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::set_base_rotation_speed(Vector2 value) {
|
||||
this->base_rotation_speed = value;
|
||||
}
|
||||
|
||||
Vector2 ThirdPersonCamera::get_base_rotation_speed() const {
|
||||
return this->base_rotation_speed;
|
||||
}
|
||||
29
modules/authority/third_person_camera.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef THIRD_PERSON_CAMERA_H
|
||||
#define THIRD_PERSON_CAMERA_H
|
||||
|
||||
#include "scene/3d/camera_3d.h"
|
||||
|
||||
class ThirdPersonCamera : public Camera3D {
|
||||
GDCLASS(ThirdPersonCamera, Camera3D);
|
||||
static void _bind_methods();
|
||||
void ready();
|
||||
void process(double delta);
|
||||
void on_look_input(Vector2 input);
|
||||
void on_mouselook_input(Vector2 input);
|
||||
|
||||
public:
|
||||
void _notification(int what);
|
||||
|
||||
void set_base_rotation_speed(Vector2 value);
|
||||
Vector2 get_base_rotation_speed() const;
|
||||
|
||||
private:
|
||||
Vector2 look_rotation_motion{ 0.f, 0.f };
|
||||
Vector2 look_rotation{ 0.f, 0.f };
|
||||
|
||||
Vector2 base_rotation_speed{ 0.1f, 0.1f };
|
||||
float distance{ 3.f };
|
||||
float height{ 2.2f };
|
||||
};
|
||||
|
||||
#endif // !THIRD_PERSON_CAMERA_H
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
Import('env')
|
||||
|
||||
env.add_source_files(env.modules_sources, "*.cpp")
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#include "behaviour_action.h"
|
||||
#include "core/object/object.h"
|
||||
|
||||
void BehaviourAction::_bind_methods() {
|
||||
ClassDB::add_virtual_method(get_class_static(), _gdvirtual__execute_get_method_info());
|
||||
ClassDB::add_virtual_method(get_class_static(), _gdvirtual__exit_get_method_info());
|
||||
ClassDB::add_virtual_method(get_class_static(), _gdvirtual__enter_get_method_info());
|
||||
}
|
||||
|
||||
void BehaviourAction::enter() {
|
||||
GDVIRTUAL_CALL(_enter);
|
||||
}
|
||||
|
||||
void BehaviourAction::execute() {
|
||||
Status out_status{ Fail };
|
||||
GDVIRTUAL_CALL(_execute, out_status);
|
||||
set_status(out_status);
|
||||
}
|
||||
|
||||
void BehaviourAction::exit() {
|
||||
GDVIRTUAL_CALL(_exit);
|
||||
}
|
||||
|
||||
BehaviourNode *BehaviourAction::get_next() {
|
||||
switch (get_status()) {
|
||||
case Running:
|
||||
return this;
|
||||
default:
|
||||
return cast_to<BehaviourNode>(get_parent());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "behaviour_nodes/behaviour_node.h"
|
||||
|
||||
class BehaviourAction : public BehaviourNode {
|
||||
GDCLASS(BehaviourAction, BehaviourNode);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void enter() override;
|
||||
void execute() override;
|
||||
void exit() override;
|
||||
BehaviourNode *get_next() override;
|
||||
GDVIRTUAL0R_REQUIRED(Status, _execute);
|
||||
GDVIRTUAL0(_enter);
|
||||
GDVIRTUAL0(_exit);
|
||||
};
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#include "behaviour_composite.h"
|
||||
|
||||
void BehaviourComposite::_bind_methods() {}
|
||||
|
||||
void BehaviourComposite::child_order_changed() {
|
||||
this->child_behaviours.clear();
|
||||
for (Variant var : get_children()) {
|
||||
if (BehaviourNode * node{ cast_to<BehaviourNode>(var) }) {
|
||||
this->child_behaviours.push_back(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviourComposite::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
child_order_changed();
|
||||
set_leaf(get_child_behaviours().is_empty());
|
||||
return;
|
||||
case NOTIFICATION_CHILD_ORDER_CHANGED:
|
||||
if (is_ready()) {
|
||||
child_order_changed();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#pragma once
|
||||
#include "behaviour_nodes/behaviour_node.h"
|
||||
|
||||
class BehaviourComposite : public BehaviourNode {
|
||||
GDCLASS(BehaviourComposite, BehaviourNode);
|
||||
static void _bind_methods();
|
||||
void child_order_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
private:
|
||||
Vector<BehaviourNode *> child_behaviours{};
|
||||
|
||||
public:
|
||||
Vector<BehaviourNode *> const &get_child_behaviours() const { return this->child_behaviours; }
|
||||
GET_SET_REF_FNS(Vector<BehaviourNode *>, child_behaviours);
|
||||
};
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#include "behaviour_node.h"
|
||||
#include "behaviour_nodes/behaviour_tree.h"
|
||||
#include "core/config/engine.h"
|
||||
|
||||
void BehaviourNode::_bind_methods() {
|
||||
BIND_ENUM_CONSTANT(Fail);
|
||||
BIND_ENUM_CONSTANT(Running);
|
||||
BIND_ENUM_CONSTANT(Success);
|
||||
}
|
||||
|
||||
void BehaviourNode::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
Node *parent{ get_parent() };
|
||||
this->parent = cast_to<BehaviourNode>(parent);
|
||||
while (this->behaviour_tree == nullptr && parent != nullptr) {
|
||||
if ((this->behaviour_tree = cast_to<BehaviourTree>(parent))) {
|
||||
break;
|
||||
}
|
||||
parent = parent->get_parent();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "macros.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class BehaviourNode : public Node {
|
||||
GDCLASS(BehaviourNode, Node);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
GDENUM(Status, Fail, Running, Success);
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
virtual void enter() {}
|
||||
virtual void execute() {}
|
||||
virtual void exit() {}
|
||||
virtual BehaviourNode *get_next() { return this; }
|
||||
|
||||
private:
|
||||
class BehaviourTree *behaviour_tree{ nullptr };
|
||||
BehaviourNode *parent{ nullptr };
|
||||
Status status{};
|
||||
bool leaf{ false };
|
||||
|
||||
public:
|
||||
GET_SET_FNS(class BehaviourTree *, behaviour_tree);
|
||||
GET_SET_FNS(Status, status);
|
||||
GET_SET_FNS(bool, leaf);
|
||||
};
|
||||
|
||||
MAKE_TYPE_INFO(BehaviourNode::Status, Variant::INT);
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
#include "behaviour_tree.h"
|
||||
#include "behaviour_nodes/behaviour_node.h"
|
||||
#include "behaviour_nodes/control_nodes.h"
|
||||
#include "core/config/engine.h"
|
||||
|
||||
void BehaviourTree::_bind_methods() {}
|
||||
|
||||
void BehaviourTree::process() {
|
||||
this->current->execute();
|
||||
while (execute_next()) {
|
||||
this->current->execute();
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviourTree::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
for (Variant var : get_children()) {
|
||||
if ((this->current = cast_to<BehaviourNode>(var))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ERR_FAIL_COND_EDMSG(this->current == nullptr, "No valid BehaviourNode in BehaviourTree");
|
||||
set_process(true);
|
||||
return;
|
||||
case NOTIFICATION_PROCESS:
|
||||
process();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool BehaviourTree::execute_next() {
|
||||
BehaviourNode *next{ this->current->get_next() };
|
||||
if (next == this->current) {
|
||||
return false;
|
||||
} else {
|
||||
ERR_FAIL_COND_V_EDMSG(next == nullptr, false, vformat("%s::get_next returned a nullptr, repeating last node", this->current->get_class()));
|
||||
if (this->current != next->get_parent()) {
|
||||
this->current->exit();
|
||||
}
|
||||
if (this->current->get_parent() != next) {
|
||||
next->enter();
|
||||
}
|
||||
this->current = next;
|
||||
return cast_to<BehaviourComposite>(this->current);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class BehaviourTree : public Node {
|
||||
GDCLASS(BehaviourTree, Node);
|
||||
static void _bind_methods();
|
||||
void process();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
bool execute_next();
|
||||
|
||||
private:
|
||||
class BehaviourNode *current{ nullptr };
|
||||
};
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
def can_build(env, platform):
|
||||
return True;
|
||||
|
||||
def configure(env):
|
||||
pass;
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
#include "control_nodes.h"
|
||||
#include "behaviour_nodes/behaviour_node.h"
|
||||
|
||||
PackedStringArray BehaviourSequence::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (get_child_behaviours().is_empty()) {
|
||||
warnings.push_back("Sequence cannot have zero children");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void BehaviourSequence::execute() {
|
||||
if (get_child_behaviours().is_empty()) {
|
||||
set_status(Fail);
|
||||
ERR_FAIL_EDMSG("BehaviourSequence executed with no children.");
|
||||
} else if (get_status() == Running && this->current >= 0 && this->current < get_child_behaviours().size()) {
|
||||
switch (get_child_behaviours().get(this->current)->get_status()) {
|
||||
case Running:
|
||||
set_status(Running);
|
||||
break;
|
||||
case Fail:
|
||||
set_status(Fail);
|
||||
break;
|
||||
case Success:
|
||||
++this->current;
|
||||
set_status(this->current >= get_child_behaviours().size()
|
||||
? Success
|
||||
: Running);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
set_status(Running);
|
||||
this->current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
BehaviourNode *BehaviourSequence::get_next() {
|
||||
return get_status() == Running
|
||||
? get_child_behaviours().get(this->current)
|
||||
: cast_to<BehaviourNode>(get_parent());
|
||||
}
|
||||
|
||||
PackedStringArray BehaviourSelector::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (get_child_behaviours().is_empty()) {
|
||||
warnings.push_back("Selector cannot have zero children");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void BehaviourSelector::execute() {
|
||||
if (get_child_behaviours().is_empty()) {
|
||||
set_status(Fail);
|
||||
ERR_FAIL_EDMSG("BehaviourSelector execution with no children.");
|
||||
} else if (get_status() == Running && this->current >= 0 && this->current < get_child_behaviours().size()) {
|
||||
switch (get_child_behaviours().get(this->current)->get_status()) {
|
||||
case Running:
|
||||
set_status(Running);
|
||||
break;
|
||||
case Fail:
|
||||
++this->current;
|
||||
set_status(this->current >= get_child_behaviours().size()
|
||||
? Fail
|
||||
: Running);
|
||||
break;
|
||||
case Success:
|
||||
set_status(Success);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
set_status(Running);
|
||||
this->current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
BehaviourNode *BehaviourSelector::get_next() {
|
||||
return get_status() == Running
|
||||
? get_child_behaviours().get(this->current)
|
||||
: cast_to<BehaviourNode>(get_parent());
|
||||
}
|
||||
|
||||
PackedStringArray BehaviourRepeater::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (get_child_behaviours().size() != 1) {
|
||||
warnings.push_back(vformat("Repeater should have exactly one BehaviourNode child, has %d", get_child_behaviours().size()));
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void BehaviourRepeater::execute() {
|
||||
set_status(Running);
|
||||
}
|
||||
|
||||
BehaviourNode *BehaviourRepeater::get_next() {
|
||||
return get_child_behaviours().get(0);
|
||||
}
|
||||
|
||||
PackedStringArray BehaviourRepeatUntilFail::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (get_child_behaviours().size() != 1) {
|
||||
warnings.push_back("RepeatUntilFailure should have exactly one BehaviourNode child");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void BehaviourRepeatUntilFail::execute() {
|
||||
if (get_child_behaviours().is_empty()) {
|
||||
set_status(Fail);
|
||||
ERR_FAIL_EDMSG("BehaviourRepeatUntilFail execution with no child");
|
||||
} else {
|
||||
set_status(get_child_behaviours().get(0)->get_status() == Fail
|
||||
? Success
|
||||
: Running);
|
||||
}
|
||||
}
|
||||
|
||||
BehaviourNode *BehaviourRepeatUntilFail::get_next() {
|
||||
return get_status() == Running
|
||||
? get_child_behaviours().get(0)
|
||||
: cast_to<BehaviourNode>(get_parent());
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
#pragma once
|
||||
#include "behaviour_nodes/behaviour_composite.h"
|
||||
#include "behaviour_nodes/behaviour_node.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
class BehaviourSequence : public BehaviourComposite {
|
||||
GDCLASS(BehaviourSequence, BehaviourComposite);
|
||||
static void _bind_methods() {}
|
||||
|
||||
public:
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
void execute() override;
|
||||
BehaviourNode *get_next() override;
|
||||
|
||||
private:
|
||||
int current{ -1 };
|
||||
};
|
||||
|
||||
class BehaviourSelector : public BehaviourComposite {
|
||||
GDCLASS(BehaviourSelector, BehaviourComposite);
|
||||
static void _bind_methods() {}
|
||||
|
||||
public:
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
void execute() override;
|
||||
BehaviourNode *get_next() override;
|
||||
|
||||
private:
|
||||
int current{ -1 };
|
||||
};
|
||||
|
||||
class BehaviourRepeater : public BehaviourComposite {
|
||||
GDCLASS(BehaviourRepeater, BehaviourComposite);
|
||||
static void _bind_methods() {}
|
||||
|
||||
public:
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
void execute() override;
|
||||
BehaviourNode *get_next() override;
|
||||
};
|
||||
|
||||
class BehaviourRepeatUntilFail : public BehaviourComposite {
|
||||
GDCLASS(BehaviourRepeatUntilFail, BehaviourComposite);
|
||||
static void _bind_methods() {}
|
||||
|
||||
public:
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
void execute() override;
|
||||
BehaviourNode *get_next() override;
|
||||
};
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
#include "decorator_nodes.h"
|
||||
#include "behaviour_nodes/behaviour_node.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
void BehaviourAlwaysSuccess::_bind_methods() {}
|
||||
|
||||
PackedStringArray BehaviourAlwaysSuccess::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (get_child_behaviours().size() != 1) {
|
||||
warnings.push_back("BehaviourAlwaysSuccess should have exactly one child");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void BehaviourAlwaysSuccess::execute() {
|
||||
if (get_child_behaviours().is_empty()) {
|
||||
set_status(Fail);
|
||||
ERR_FAIL_EDMSG("BehaviourSequence executed with no children.");
|
||||
} else if (get_status() == Running) {
|
||||
set_status(get_child_behaviours().get(0)->get_status() == Running ? Running : Success);
|
||||
} else {
|
||||
set_status(Running);
|
||||
}
|
||||
}
|
||||
|
||||
BehaviourNode *BehaviourAlwaysSuccess::get_next() {
|
||||
return get_status() == Running
|
||||
? get_child_behaviours().get(0)
|
||||
: cast_to<BehaviourNode>(get_parent());
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "behaviour_nodes/behaviour_composite.h"
|
||||
#include "behaviour_nodes/behaviour_node.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
class BehaviourAlwaysSuccess : public BehaviourComposite {
|
||||
GDCLASS(BehaviourAlwaysSuccess, BehaviourComposite);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
void execute() override;
|
||||
BehaviourNode *get_next() override;
|
||||
};
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
#include "behaviour_nodes/register_types.h"
|
||||
#include "behaviour_nodes/behaviour_action.h"
|
||||
#include "behaviour_nodes/behaviour_node.h"
|
||||
#include "behaviour_nodes/behaviour_tree.h"
|
||||
#include "behaviour_nodes/control_nodes.h"
|
||||
#include "behaviour_nodes/decorator_nodes.h"
|
||||
#include "core/object/class_db.h"
|
||||
|
||||
void initialize_behaviour_nodes_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
ClassDB::register_class<BehaviourTree>();
|
||||
ClassDB::register_abstract_class<BehaviourNode>();
|
||||
ClassDB::register_class<BehaviourSequence>();
|
||||
ClassDB::register_class<BehaviourRepeater>();
|
||||
ClassDB::register_class<BehaviourSelector>();
|
||||
ClassDB::register_class<BehaviourAction>();
|
||||
ClassDB::register_class<BehaviourRepeatUntilFail>();
|
||||
ClassDB::register_class<BehaviourAlwaysSuccess>();
|
||||
}
|
||||
|
||||
void uninitialize_behaviour_nodes_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#ifndef BEHAVIOUR_NODES_REGISTER_TYPES_H
|
||||
#define BEHAVIOUR_NODES_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_behaviour_nodes_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_behaviour_nodes_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // !BEHAVIOUR_NODES_REGISTER_TYPES_H
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
#ifndef GODOT_EXTRA_MACROS_H
|
||||
#define GODOT_EXTRA_MACROS_H
|
||||
|
||||
#define BIND_GET_SET(m_property) \
|
||||
ClassDB::bind_method(D_METHOD("set_" #m_property, #m_property), \
|
||||
&self_type::set_##m_property); \
|
||||
ClassDB::bind_method(D_METHOD("get_" #m_property), \
|
||||
&self_type::get_##m_property)
|
||||
|
||||
#define BIND_HPROPERTY(m_type, m_property, ...) \
|
||||
BIND_GET_SET(m_property); \
|
||||
ADD_PROPERTY(PropertyInfo(m_type, #m_property, __VA_ARGS__), \
|
||||
"set_" #m_property, "get_" #m_property)
|
||||
|
||||
#define BIND_PROPERTY(m_type, m_property) \
|
||||
BIND_GET_SET(m_property); \
|
||||
ADD_PROPERTY(PropertyInfo(m_type, #m_property), "set_" #m_property, \
|
||||
"get_" #m_property)
|
||||
|
||||
#define GET_SET_FNS(m_type, m_property) \
|
||||
m_type get_##m_property() const { \
|
||||
return this->m_property; \
|
||||
} \
|
||||
void set_##m_property(m_type value) { \
|
||||
this->m_property = value; \
|
||||
}
|
||||
#define GET_SET_REF_FNS(m_type, m_property) \
|
||||
m_type &get_##m_property() { \
|
||||
return this->m_property; \
|
||||
} \
|
||||
void set_##m_property(m_type &value) { \
|
||||
this->m_property = value; \
|
||||
}
|
||||
|
||||
#define GET_SET_FNS_EX(m_type, m_property, m_ex) \
|
||||
m_type get_##m_property() const { \
|
||||
return this->m_property; \
|
||||
} \
|
||||
void set_##m_property(m_type value) { \
|
||||
m_ex; \
|
||||
this->m_property = value; \
|
||||
}
|
||||
|
||||
#define __VA_ARGS__STRING(...) String(#__VA_ARGS__)
|
||||
|
||||
#define GDENUM(M_Name, ...) \
|
||||
enum M_Name { __VA_ARGS__ }; \
|
||||
static String M_Name##_hint() { \
|
||||
return __VA_ARGS__STRING(__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#endif // !GODOT_EXTRA_MACROS_H
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
Import('env')
|
||||
|
||||
env.add_source_files(env.modules_sources, "*.cpp")
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
- [x] Cache chunk meshes
|
||||
- [ ] Noise modifier
|
||||
- [ ] Only load defined chunks
|
||||
- [ ] More accurate modifier bounds
|
||||
- [ ] Separate terrain module into it's own repository
|
||||
- [ ] Texturing step
|
||||
- [ ] Use Semaphores in mesh gen multi-threading
|
||||
- [x] Separate source files for terrain modifiers
|
||||
- [x] Stop threads when generation finishes
|
||||
- [ ] Regenerate chunks when deleting/reordering modifiers
|
||||
- [x] Stop processing terrain after generation is complete
|
||||
- [x] Don't start generation threads if all meshes are cached
|
||||
- [ ] Scaling modifiers
|
||||
- [x] Rewrite line modifier
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
def can_build(env, platform):
|
||||
return True;
|
||||
|
||||
def configure(env):
|
||||
pass;
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#include "register_types.h"
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "terrain/terrain.h"
|
||||
#include "terrain/terrain_chunk.h"
|
||||
#include "terrain/terrain_modifier.h"
|
||||
#include "terrain/terrain_modifier_composite.h"
|
||||
#include "terrain/terrain_modifier_distance.h"
|
||||
#include "terrain/terrain_modifier_noise.h"
|
||||
#include "terrain/terrain_modifier_path.h"
|
||||
|
||||
void initialize_terrain_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
ClassDB::register_class<Terrain>();
|
||||
ClassDB::register_abstract_class<TerrainModifier>();
|
||||
ClassDB::register_class<TerrainModifierDistance>();
|
||||
ClassDB::register_class<TerrainModifierPath>();
|
||||
ClassDB::register_class<TerrainModifierComposite>();
|
||||
ClassDB::register_class<TerrainModifierNoise>();
|
||||
ClassDB::register_class<TerrainChunkMesh>();
|
||||
}
|
||||
|
||||
void uninitialize_terrain_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#ifndef TERRAIN_REGISTER_TYPES_H
|
||||
#define TERRAIN_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_terrain_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_terrain_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // !TERRAIN_REGISTER_TYPES_H
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
#include "shared_mutex.h"
|
||||
#include "core/error/error_macros.h"
|
||||
|
||||
void SharedMutex::lock_shared() {
|
||||
this->lock.lock();
|
||||
this->shared_count++;
|
||||
this->lock.unlock();
|
||||
}
|
||||
|
||||
void SharedMutex::unlock_shared() {
|
||||
this->lock.lock();
|
||||
this->shared_count--;
|
||||
ERR_FAIL_COND_EDMSG(this->shared_count < 0, "SharedMutex::unlock_shared, shared_count < 0");
|
||||
this->lock.unlock();
|
||||
}
|
||||
|
||||
void SharedMutex::lock_exclusive() {
|
||||
while (true) {
|
||||
this->lock.lock();
|
||||
if (this->shared_count == 0) {
|
||||
return;
|
||||
}
|
||||
this->lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void SharedMutex::unlock_exclusive() {
|
||||
this->lock.unlock();
|
||||
}
|
||||
|
||||
bool SharedMutex::is_locked() {
|
||||
if (this->lock.try_lock()) {
|
||||
bool locked{ this->shared_count != 0 };
|
||||
this->lock.unlock();
|
||||
return locked;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SharedMutex::LockExclusive::LockExclusive(SharedMutex &mutex) : mtx{ mutex } {
|
||||
this->mtx.lock_exclusive();
|
||||
}
|
||||
|
||||
SharedMutex::LockExclusive::~LockExclusive() {
|
||||
this->mtx.unlock_exclusive();
|
||||
}
|
||||
|
||||
SharedMutex::LockShared::LockShared(SharedMutex &mutex) : mtx{ mutex } {
|
||||
this->mtx.lock_shared();
|
||||
}
|
||||
|
||||
SharedMutex::LockShared::~LockShared() {
|
||||
this->mtx.unlock_shared();
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "core/os/mutex.h"
|
||||
|
||||
struct SharedMutex {
|
||||
void lock_shared();
|
||||
void unlock_shared();
|
||||
void lock_exclusive();
|
||||
void unlock_exclusive();
|
||||
bool is_locked();
|
||||
|
||||
private:
|
||||
BinaryMutex lock{};
|
||||
unsigned shared_count{};
|
||||
|
||||
public:
|
||||
class LockExclusive {
|
||||
SharedMutex &mtx;
|
||||
|
||||
public:
|
||||
explicit LockExclusive(SharedMutex &mtx);
|
||||
~LockExclusive();
|
||||
LockExclusive() = delete;
|
||||
LockExclusive(LockExclusive &) = delete;
|
||||
LockExclusive(LockExclusive &&) = delete;
|
||||
LockExclusive &operator=(LockExclusive &) = delete;
|
||||
};
|
||||
class LockShared {
|
||||
SharedMutex &mtx;
|
||||
|
||||
public:
|
||||
explicit LockShared(SharedMutex &mtx);
|
||||
~LockShared();
|
||||
LockShared() = delete;
|
||||
LockShared(LockShared &) = delete;
|
||||
LockShared(LockShared &&) = delete;
|
||||
LockShared &operator=(LockShared &) = delete;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,343 +0,0 @@
|
|||
#include "terrain.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "terrain/terrain_chunk.h"
|
||||
#include "terrain/terrain_modifier.h"
|
||||
|
||||
void Terrain::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::OBJECT, mesh_material, PROPERTY_HINT_RESOURCE_TYPE, "Material");
|
||||
BIND_PROPERTY(Variant::INT, side_length);
|
||||
BIND_PROPERTY(Variant::INT, chunk_size);
|
||||
BIND_PROPERTY(Variant::INT, thread_count);
|
||||
BIND_HPROPERTY(Variant::ARRAY, terrain_meshes, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:TerrainMeshChunk", Variant::OBJECT, PROPERTY_HINT_NODE_TYPE), PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY);
|
||||
}
|
||||
|
||||
void Terrain::stop_threads() {
|
||||
this->workload_lock.lock();
|
||||
this->threads_stop = true;
|
||||
this->workload_lock.unlock();
|
||||
for (Thread &thread : this->threads) {
|
||||
if (thread.is_started()) {
|
||||
thread.wait_to_finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::start_threads() {
|
||||
this->workload_lock.lock();
|
||||
print_line(vformat("Starting threads; workload: %d", this->workload.size()));
|
||||
this->threads_stop = false;
|
||||
for (Thread &thread : this->threads) {
|
||||
if (!thread.is_started()) {
|
||||
thread.start(Terrain::generate_meshes_thread, this);
|
||||
}
|
||||
}
|
||||
this->workload_lock.unlock(); // don't let the threads proceed until all are started
|
||||
}
|
||||
|
||||
void Terrain::child_order_changed() {
|
||||
this->modifiers.clear();
|
||||
for (Variant var : get_children()) {
|
||||
if (TerrainModifier * mod{ cast_to<TerrainModifier>(var) }) {
|
||||
mod->set_terrain(this);
|
||||
this->modifiers.push_back(mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::update_meshes() {
|
||||
size_t num{ max_mesh_assignments_per_frame };
|
||||
this->dirty_meshes_lock.lock();
|
||||
num = num > this->dirty_meshes.size() ? this->dirty_meshes.size() : num;
|
||||
this->dirty_meshes_lock.unlock();
|
||||
for (size_t i{ 0 }; i < num; i++) {
|
||||
this->dirty_meshes_lock.lock();
|
||||
TerrainChunkMesh *mesh{ this->dirty_meshes[0] };
|
||||
this->dirty_meshes.remove_at(0);
|
||||
this->dirty_meshes_lock.unlock();
|
||||
mesh->apply_new_mesh();
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::update_threads() {
|
||||
if (this->workload.is_empty()) {
|
||||
stop_threads();
|
||||
} else {
|
||||
start_threads();
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::update_process() {
|
||||
// check if there is any tasks going on that would require processing each frame
|
||||
// any running threads?
|
||||
for (Thread &thread : this->threads) {
|
||||
if (thread.is_started()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// dirty meshes?
|
||||
this->dirty_meshes_lock.lock();
|
||||
bool workload_empty{ this->dirty_meshes.is_empty() };
|
||||
this->dirty_meshes_lock.unlock();
|
||||
if (!workload_empty) {
|
||||
return;
|
||||
}
|
||||
// queued mesh generation tasks?
|
||||
this->workload_lock.lock();
|
||||
workload_empty = this->workload.is_empty();
|
||||
this->workload_lock.unlock();
|
||||
if (!workload_empty) {
|
||||
return;
|
||||
}
|
||||
// stop processing each frame
|
||||
print_line("Terrain processing stopped");
|
||||
set_process(false);
|
||||
}
|
||||
|
||||
void Terrain::synchronous_generate_terrain() {
|
||||
print_line("Blocking regenerate terrain");
|
||||
this->workload_lock.lock();
|
||||
this->threads_stop = false;
|
||||
// queue all meshes
|
||||
this->workload.clear();
|
||||
this->workload.append_array(this->meshes);
|
||||
start_threads();
|
||||
// wait for workload to empty out
|
||||
do {
|
||||
this->workload_lock.unlock();
|
||||
Thread::yield();
|
||||
this->workload_lock.lock();
|
||||
} while (!this->workload.is_empty());
|
||||
this->threads_stop = true;
|
||||
this->workload_lock.unlock();
|
||||
stop_threads();
|
||||
for (TerrainChunkMesh *mesh : this->dirty_meshes) {
|
||||
mesh->apply_new_mesh();
|
||||
}
|
||||
this->dirty_meshes.clear();
|
||||
}
|
||||
|
||||
void Terrain::_notification(int what) {
|
||||
if (!Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_CHILD_ORDER_CHANGED:
|
||||
if (is_ready()) {
|
||||
this->child_order_changed();
|
||||
}
|
||||
return;
|
||||
case NOTIFICATION_READY: {
|
||||
this->meshes.clear();
|
||||
this->modifiers.clear();
|
||||
for (Variant var : get_children()) {
|
||||
if (TerrainChunkMesh * mesh{ cast_to<TerrainChunkMesh>(var) }) {
|
||||
this->meshes.push_back(mesh);
|
||||
mesh->set_terrain(this);
|
||||
}
|
||||
if (TerrainModifier * mod{ cast_to<TerrainModifier>(var) }) {
|
||||
this->modifiers.push_back(mod);
|
||||
mod->set_terrain(this);
|
||||
}
|
||||
}
|
||||
size_t expected_size{ this->side_length / this->chunk_size };
|
||||
if (this->meshes.size() != expected_size * expected_size) {
|
||||
construct_chunk_grid();
|
||||
synchronous_generate_terrain();
|
||||
}
|
||||
return;
|
||||
}
|
||||
case NOTIFICATION_PROCESS:
|
||||
update_meshes();
|
||||
update_threads();
|
||||
update_process();
|
||||
return;
|
||||
case NOTIFICATION_EXIT_TREE:
|
||||
this->workload_lock.lock();
|
||||
this->threads_stop = true;
|
||||
this->workload_lock.unlock();
|
||||
for (Thread &thread : this->threads) {
|
||||
if (thread.is_started()) {
|
||||
thread.wait_to_finish();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::generate_meshes_thread(void *terrain) {
|
||||
Terrain *self{ static_cast<Terrain *>(terrain) };
|
||||
print_line("thread", Thread::get_caller_id(), "start");
|
||||
for (;;) {
|
||||
self->workload_lock.lock();
|
||||
if (self->threads_stop) {
|
||||
self->workload_lock.unlock();
|
||||
print_line(Thread::get_caller_id(), "exiting");
|
||||
break;
|
||||
}
|
||||
if (self->workload.is_empty()) {
|
||||
self->workload_lock.unlock();
|
||||
Thread::yield();
|
||||
continue;
|
||||
}
|
||||
TerrainChunkMesh *mesh{ self->workload[0] };
|
||||
self->workload.remove_at(0);
|
||||
self->workload_lock.unlock();
|
||||
if (!mesh->is_inside_tree()) {
|
||||
print_line(Thread::get_caller_id(), "mesh is outside tree, exiting");
|
||||
break;
|
||||
}
|
||||
mesh->update_mesh();
|
||||
Thread::yield();
|
||||
}
|
||||
print_line(Thread::get_caller_id(), "done");
|
||||
return;
|
||||
}
|
||||
|
||||
void Terrain::construct_chunk_grid() {
|
||||
print_line("Constructing chunk grid");
|
||||
this->workload_lock.lock();
|
||||
this->threads_stop = true;
|
||||
this->workload_lock.unlock();
|
||||
for (Thread &thread : this->threads) {
|
||||
if (thread.is_started()) {
|
||||
thread.wait_to_finish();
|
||||
}
|
||||
}
|
||||
this->workload_lock.lock();
|
||||
for (TerrainChunkMesh *mesh : this->meshes) {
|
||||
remove_child(mesh);
|
||||
mesh->queue_free();
|
||||
}
|
||||
this->meshes.clear();
|
||||
this->workload.clear();
|
||||
size_t const chunks_per_side{ this->side_length / this->chunk_size };
|
||||
Vector3 const origin{ (float)this->chunk_size / 2.f, 0.f, (float)this->chunk_size / 2.f };
|
||||
for (size_t y{ 0 }; y < chunks_per_side; ++y) {
|
||||
for (size_t x{ 0 }; x < chunks_per_side; ++x) {
|
||||
TerrainChunkMesh *chunk{ memnew(TerrainChunkMesh) };
|
||||
chunk->set_size(this->chunk_size);
|
||||
chunk->set_detail(this->detail);
|
||||
chunk->set_terrain(this);
|
||||
chunk->set_material_override(this->mesh_material);
|
||||
chunk->set_position(origin + Vector3{ (float)this->chunk_size * (float)x, 0.f, (float)this->chunk_size * (float)y });
|
||||
chunk->set_name(vformat("Chunk%dx%d", x, y));
|
||||
add_child(chunk);
|
||||
chunk->set_owner(get_owner());
|
||||
this->meshes.push_back(chunk);
|
||||
this->workload.push_back(chunk);
|
||||
}
|
||||
}
|
||||
this->threads_stop = false;
|
||||
this->dirty_meshes.clear();
|
||||
this->workload_lock.unlock();
|
||||
set_process(true);
|
||||
}
|
||||
|
||||
float Terrain::height_at(Vector2 world_coordinate) {
|
||||
float height{ 0 };
|
||||
for (TerrainModifier *mod : this->modifiers) {
|
||||
if (!mod->is_inside_tree()) {
|
||||
return height;
|
||||
}
|
||||
height = mod->evaluate_at(world_coordinate, height);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
void Terrain::push_changed(Rect2 area) {
|
||||
for (TerrainChunkMesh *mesh : this->meshes) {
|
||||
this->workload_lock.lock();
|
||||
if (area.intersects(mesh->get_bounds()) && !this->workload.has(mesh)) {
|
||||
workload.push_back(mesh);
|
||||
}
|
||||
this->workload_lock.unlock();
|
||||
}
|
||||
set_process(true);
|
||||
}
|
||||
|
||||
void Terrain::mesh_dirty(TerrainChunkMesh *mesh) {
|
||||
this->dirty_meshes_lock.lock();
|
||||
this->dirty_meshes.push_back(mesh);
|
||||
callable_mp(cast_to<Node>(this), &self_type::set_process).call_deferred(true);
|
||||
this->dirty_meshes_lock.unlock();
|
||||
}
|
||||
|
||||
void Terrain::set_mesh_material(Ref<Material> material) {
|
||||
this->mesh_material = material;
|
||||
for (TerrainChunkMesh *mesh : this->meshes) {
|
||||
mesh->set_material_override(material);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Material> Terrain::get_mesh_material() const {
|
||||
return this->mesh_material;
|
||||
}
|
||||
|
||||
void Terrain::set_side_length(size_t length) {
|
||||
this->side_length = length;
|
||||
if (is_inside_tree()) {
|
||||
construct_chunk_grid();
|
||||
}
|
||||
}
|
||||
|
||||
size_t Terrain::get_side_length() const {
|
||||
return this->side_length;
|
||||
}
|
||||
|
||||
void Terrain::set_chunk_size(size_t size) {
|
||||
this->chunk_size = size;
|
||||
if (is_inside_tree()) {
|
||||
construct_chunk_grid();
|
||||
}
|
||||
}
|
||||
|
||||
size_t Terrain::get_chunk_size() const {
|
||||
return this->chunk_size;
|
||||
}
|
||||
|
||||
void Terrain::set_detail(size_t detail) {
|
||||
this->detail = detail;
|
||||
if (is_inside_tree()) {
|
||||
construct_chunk_grid();
|
||||
}
|
||||
}
|
||||
|
||||
size_t Terrain::get_detail() const {
|
||||
return this->detail;
|
||||
}
|
||||
|
||||
void Terrain::set_thread_count(size_t num) {
|
||||
this->workload_lock.lock();
|
||||
this->threads_stop = true;
|
||||
this->workload_lock.unlock();
|
||||
for (Thread &thread : this->threads) {
|
||||
thread.wait_to_finish();
|
||||
}
|
||||
this->threads_stop = false;
|
||||
this->threads.resize_initialized(num);
|
||||
}
|
||||
|
||||
size_t Terrain::get_thread_count() const {
|
||||
return this->threads.size();
|
||||
}
|
||||
|
||||
void Terrain::set_terrain_meshes(Array array) {
|
||||
return;
|
||||
this->meshes.clear();
|
||||
for (Variant var : array) {
|
||||
if (TerrainChunkMesh * mesh{ cast_to<TerrainChunkMesh>(var) }) {
|
||||
mesh->set_terrain(this);
|
||||
this->meshes.push_back(mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array Terrain::get_terrain_meshes() const {
|
||||
Array a{};
|
||||
for (TerrainChunkMesh *mesh : this->meshes) {
|
||||
a.push_back(mesh);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "core/math/rect2.h"
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/templates/vector.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "scene/resources/material.h"
|
||||
class TerrainChunkMesh;
|
||||
class TerrainModifier;
|
||||
|
||||
class Terrain : public Node {
|
||||
GDCLASS(Terrain, Node);
|
||||
static void _bind_methods();
|
||||
void stop_threads();
|
||||
void start_threads();
|
||||
void child_order_changed();
|
||||
void update_meshes();
|
||||
void update_threads();
|
||||
void update_process();
|
||||
void synchronous_generate_terrain();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
static void generate_meshes_thread(void *terrain);
|
||||
|
||||
public:
|
||||
void construct_chunk_grid();
|
||||
float height_at(Vector2 world_coordinate);
|
||||
void push_changed(Rect2 area);
|
||||
void mesh_dirty(TerrainChunkMesh *mesh);
|
||||
|
||||
private:
|
||||
Ref<Material> mesh_material{};
|
||||
Vector<TerrainChunkMesh *> workload{};
|
||||
bool threads_stop{ false };
|
||||
Mutex workload_lock;
|
||||
|
||||
Vector<TerrainChunkMesh *> dirty_meshes{};
|
||||
Mutex dirty_meshes_lock{};
|
||||
|
||||
Vector<TerrainChunkMesh *> meshes{};
|
||||
Vector<TerrainModifier *> modifiers{};
|
||||
LocalVector<Thread> threads{};
|
||||
|
||||
size_t side_length{ 200 };
|
||||
size_t chunk_size{ 50 };
|
||||
size_t detail{ 1 };
|
||||
size_t max_mesh_assignments_per_frame{ 1 };
|
||||
|
||||
public:
|
||||
void set_mesh_material(Ref<Material> material);
|
||||
Ref<Material> get_mesh_material() const;
|
||||
void set_side_length(size_t length);
|
||||
size_t get_side_length() const;
|
||||
void set_chunk_size(size_t size);
|
||||
size_t get_chunk_size() const;
|
||||
void set_detail(size_t detail);
|
||||
size_t get_detail() const;
|
||||
void set_thread_count(size_t num);
|
||||
size_t get_thread_count() const;
|
||||
void set_terrain_meshes(Array array);
|
||||
Array get_terrain_meshes() const;
|
||||
};
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
#include "terrain_chunk.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "scene/3d/physics/collision_shape_3d.h"
|
||||
#include "scene/3d/physics/static_body_3d.h"
|
||||
#include "scene/resources/3d/height_map_shape_3d.h"
|
||||
#include "scene/resources/surface_tool.h"
|
||||
#include "terrain/terrain.h"
|
||||
|
||||
void TerrainChunkMesh::_bind_methods() {
|
||||
// bind properties so they will be saved to the scene file, don't allow direct modification
|
||||
BIND_HPROPERTY(Variant::OBJECT, shape, PROPERTY_HINT_RESOURCE_TYPE, "HeightMapShape3D", PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE);
|
||||
BIND_HPROPERTY(Variant::INT, size, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE);
|
||||
}
|
||||
|
||||
void TerrainChunkMesh::ready() {
|
||||
// initialise thread-safe position buffer,
|
||||
// we're assuming that chunks only get generated on the main thread
|
||||
this->position_buffer = get_global_position();
|
||||
float const sizef{ (float)get_size() };
|
||||
this->bounds.position = { this->position_buffer.x - sizef / 2.f, this->position_buffer.z - sizef / 2.f };
|
||||
this->bounds.size = { sizef, sizef };
|
||||
// add static body
|
||||
add_child(this->body = memnew(StaticBody3D));
|
||||
ERR_FAIL_COND_EDMSG(this->body == nullptr, "Failed to instantiate StaticBody3D");
|
||||
// initialise collision shape
|
||||
this->body->add_child(this->collider = memnew(CollisionShape3D));
|
||||
this->body->set_owner(this);
|
||||
ERR_FAIL_COND_EDMSG(this->collider == nullptr, "Failed to instantiate CollisionShape3D");
|
||||
this->collider->set_owner(this);
|
||||
// either instantiate the shape as cached, or initialise it
|
||||
if (this->shape.is_null()) {
|
||||
this->shape = memnew(HeightMapShape3D);
|
||||
this->shape->set_map_depth(heightmap_side_length());
|
||||
this->shape->set_map_width(heightmap_side_length());
|
||||
} else {
|
||||
// if cached, initialise thread-safe buffer
|
||||
this->heightmap.append_array(this->shape->get_map_data());
|
||||
}
|
||||
ERR_FAIL_COND_EDMSG(!this->shape.is_valid(), "Failed to instantiate HeightMapShape3D");
|
||||
this->collider->set_shape(this->shape);
|
||||
}
|
||||
|
||||
void TerrainChunkMesh::generate_vertices() {
|
||||
ERR_FAIL_COND_EDMSG(this->terrain == nullptr, "TerrainChunkMesh::generate_vertices: no terrain assigned");
|
||||
ERR_FAIL_COND_EDMSG(this->size <= 0.f, "TerrainChunkMesh::generate_vertices: size <= 0");
|
||||
ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate_vertices: points per side <= 0");
|
||||
// generate mesh surface vertices
|
||||
float const half_extent{ (float)this->size / 2.f };
|
||||
float const point_distance{ (float)this->size / ((float)points_per_side() - 1) };
|
||||
Vector3 origin{ this->position_buffer - Vector3{ half_extent, 0, half_extent } };
|
||||
for (size_t x{ 0 }; x < points_per_side(); ++x) {
|
||||
for (size_t y{ 0 }; y < points_per_side(); ++y) {
|
||||
Vector2 const coordinate{ origin.x + point_distance * x, origin.z + point_distance * y };
|
||||
this->surface->set_uv({ (float)x / (float)points_per_side(), (float)y / (float)points_per_side() });
|
||||
float height{ this->terrain->height_at(coordinate) };
|
||||
this->surface->add_vertex({ coordinate.x - this->position_buffer.x, height, coordinate.y - this->position_buffer.z });
|
||||
}
|
||||
}
|
||||
// generate heightmap surface points
|
||||
this->heightmap.resize_initialized(heightmap_side_length() * heightmap_side_length());
|
||||
for (size_t x{ 0 }; x < heightmap_side_length(); x++) {
|
||||
for (size_t y{ 0 }; y < heightmap_side_length(); y++) {
|
||||
Vector2 coordinate{ origin.x + x, origin.z + y };
|
||||
this->heightmap.set(x + y * heightmap_side_length(), this->terrain->height_at(coordinate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainChunkMesh::generate_faces() {
|
||||
LocalVector<SurfaceTool::Vertex> &verts{ this->surface->get_vertex_array() };
|
||||
ERR_FAIL_COND_EDMSG(verts.size() == 0, "TerrainChunkMesh::generate_faces: no vertices in surface, call generate_vertices first");
|
||||
size_t const faces_per_side{ points_per_side() - 1 };
|
||||
for (size_t x{ 0 }; x < faces_per_side; ++x) {
|
||||
for (size_t y{ 0 }; y < faces_per_side; ++y) {
|
||||
// generate shortest diagonal edge
|
||||
size_t const tl{ x + y * points_per_side() };
|
||||
float tl_br{ verts[tl].vertex.distance_to(verts[tl + points_per_side() + 1].vertex) };
|
||||
float tr_bl{ verts[tl + 1].vertex.distance_to(verts[tl + points_per_side()].vertex) };
|
||||
if (tl_br < tr_bl) {
|
||||
surface->add_index(tl);
|
||||
surface->add_index(tl + points_per_side() + 1);
|
||||
surface->add_index(tl + 1);
|
||||
surface->add_index(tl);
|
||||
surface->add_index(tl + points_per_side());
|
||||
surface->add_index(tl + points_per_side() + 1);
|
||||
} else {
|
||||
surface->add_index(tl + points_per_side());
|
||||
surface->add_index(tl + points_per_side() + 1);
|
||||
surface->add_index(tl + 1);
|
||||
surface->add_index(tl + 1);
|
||||
surface->add_index(tl);
|
||||
surface->add_index(tl + points_per_side());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainChunkMesh::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
if (!is_ready()) {
|
||||
ready();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainChunkMesh::apply_new_mesh() {
|
||||
this->lock.lock();
|
||||
set_mesh(this->new_mesh);
|
||||
if (this->shape->get_map_depth() != heightmap_side_length() || this->shape->get_map_width() != heightmap_side_length()) {
|
||||
this->shape->set_map_depth(heightmap_side_length());
|
||||
this->shape->set_map_width(heightmap_side_length());
|
||||
}
|
||||
this->shape->set_map_data(heightmap);
|
||||
this->lock.unlock();
|
||||
}
|
||||
|
||||
// NOTE: this _will not_ be called on the main thread.
|
||||
// - Don't modify scene tree
|
||||
// - Don't rely on scene tree data
|
||||
void TerrainChunkMesh::update_mesh() {
|
||||
ERR_FAIL_COND_EDMSG(this->size <= 0.f, "TerrainChunkMesh::generate: size <= 0");
|
||||
ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate: points per side <= 0");
|
||||
this->lock.lock();
|
||||
this->surface = memnew(SurfaceTool);
|
||||
this->surface->begin(Mesh::PRIMITIVE_TRIANGLES);
|
||||
generate_vertices();
|
||||
generate_faces();
|
||||
this->surface->generate_normals();
|
||||
this->surface->generate_tangents();
|
||||
this->new_mesh = memnew(ArrayMesh);
|
||||
this->surface->commit(this->new_mesh);
|
||||
this->lock.unlock();
|
||||
this->terrain->mesh_dirty(this);
|
||||
}
|
||||
|
||||
size_t TerrainChunkMesh::points_per_side() const {
|
||||
return this->size * this->detail;
|
||||
}
|
||||
|
||||
int TerrainChunkMesh::heightmap_side_length() const {
|
||||
return get_size() + 1;
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "core/math/rect2.h"
|
||||
#include "macros.h"
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/3d/physics/collision_shape_3d.h"
|
||||
#include "scene/3d/physics/static_body_3d.h"
|
||||
#include "scene/resources/3d/height_map_shape_3d.h"
|
||||
#include "scene/resources/mesh.h"
|
||||
#include "scene/resources/surface_tool.h"
|
||||
class Terrain;
|
||||
|
||||
class TerrainChunkMesh : public MeshInstance3D {
|
||||
GDCLASS(TerrainChunkMesh, MeshInstance3D);
|
||||
static void _bind_methods();
|
||||
void ready();
|
||||
void generate_vertices();
|
||||
void generate_faces();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
void apply_new_mesh();
|
||||
void update_mesh();
|
||||
size_t points_per_side() const;
|
||||
int heightmap_side_length() const;
|
||||
|
||||
private:
|
||||
Mutex lock{};
|
||||
Vector3 position_buffer{};
|
||||
Ref<SurfaceTool> surface{};
|
||||
Ref<ArrayMesh> new_mesh{};
|
||||
Terrain *terrain{ nullptr };
|
||||
size_t detail{ 1 };
|
||||
size_t size{ 1 };
|
||||
Rect2 bounds{};
|
||||
|
||||
Ref<HeightMapShape3D> shape{};
|
||||
StaticBody3D *body{ nullptr };
|
||||
CollisionShape3D *collider{ nullptr };
|
||||
Vector<real_t> heightmap{};
|
||||
|
||||
public:
|
||||
GET_SET_FNS(Rect2, bounds);
|
||||
GET_SET_FNS(Terrain *, terrain);
|
||||
GET_SET_FNS(Ref<HeightMapShape3D>, shape);
|
||||
GET_SET_FNS(size_t, detail);
|
||||
GET_SET_FNS(size_t, size);
|
||||
};
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
#include "terrain_modifier.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "terrain/terrain.h"
|
||||
|
||||
String TerrainModifier::sig_terrain_changed{ "terrain_changed" };
|
||||
|
||||
void TerrainModifier::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo(sig_terrain_changed, PropertyInfo(Variant::OBJECT, "terrain", PROPERTY_HINT_NODE_TYPE, "Terrain")));
|
||||
}
|
||||
|
||||
void TerrainModifier::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
set_notify_transform(true);
|
||||
}
|
||||
this->thread_safe_global_position = get_global_position();
|
||||
return;
|
||||
case NOTIFICATION_TRANSFORM_CHANGED:
|
||||
this->thread_safe_global_position = get_global_position();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainModifier::push_changed(Rect2 area) {
|
||||
if (this->terrain && is_inside_tree() && is_ready()) {
|
||||
this->terrain->push_changed(area);
|
||||
}
|
||||
}
|
||||
|
||||
float TerrainModifier::evaluate_at(Vector2 world_coordinate, float before) {
|
||||
Vector3 const global_position{ get_thread_safe_global_position() };
|
||||
return global_position.y;
|
||||
}
|
||||
|
||||
void TerrainModifier::set_bounds(Rect2 bounds) {
|
||||
if (this->bounds != bounds) {
|
||||
push_changed(bounds);
|
||||
push_changed(this->bounds);
|
||||
this->bounds = bounds;
|
||||
}
|
||||
}
|
||||
|
||||
Rect2 TerrainModifier::get_bounds() const {
|
||||
return this->bounds;
|
||||
}
|
||||
|
||||
Vector3 TerrainModifier::get_thread_safe_global_position() const {
|
||||
return this->thread_safe_global_position;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "core/object/object.h"
|
||||
#include "scene/3d/marker_3d.h"
|
||||
#include <cmath>
|
||||
class Terrain;
|
||||
|
||||
class TerrainModifier : public Marker3D {
|
||||
GDCLASS(TerrainModifier, Marker3D);
|
||||
static void _bind_methods();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
void push_changed(Rect2 bounds);
|
||||
virtual float evaluate_at(Vector2 world_coordinate, float before);
|
||||
|
||||
private:
|
||||
Vector3 thread_safe_global_position{};
|
||||
Terrain *terrain{ nullptr };
|
||||
Rect2 bounds{ { -INFINITY, -INFINITY }, { INFINITY, INFINITY } };
|
||||
|
||||
protected:
|
||||
void set_bounds(Rect2 bounds);
|
||||
|
||||
public:
|
||||
static String sig_terrain_changed;
|
||||
Rect2 get_bounds() const;
|
||||
Vector3 get_thread_safe_global_position() const;
|
||||
Terrain *get_terrain() const {
|
||||
return this->terrain;
|
||||
}
|
||||
void set_terrain(Terrain *terrain) {
|
||||
this->terrain = terrain;
|
||||
emit_signal(sig_terrain_changed, terrain);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
#include "terrain_modifier_composite.h"
|
||||
#include "core/math/math_funcs.h"
|
||||
#include "terrain/terrain.h"
|
||||
#include <cmath>
|
||||
|
||||
void TerrainModifierComposite::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::INT, mode, PROPERTY_HINT_ENUM, CompositeMode_hint());
|
||||
}
|
||||
|
||||
void TerrainModifierComposite::push_all_changes() {
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
push_changed(mod->get_bounds());
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainModifierComposite::update_sub_modifiers() {
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
push_changed(mod->get_bounds());
|
||||
}
|
||||
this->sub_modifiers.clear();
|
||||
for (Variant var : get_children()) {
|
||||
if (TerrainModifier * mod{ cast_to<TerrainModifier>(var) }) {
|
||||
this->sub_modifiers.push_back(mod);
|
||||
mod->set_terrain(get_terrain());
|
||||
push_changed(mod->get_bounds());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainModifierComposite::terrain_changed(Terrain *terrain) {
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
mod->set_terrain(terrain);
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainModifierComposite::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
set_notify_transform(true);
|
||||
if (!is_ready()) {
|
||||
connect(sig_terrain_changed, callable_mp(this, &self_type::terrain_changed));
|
||||
}
|
||||
return;
|
||||
case NOTIFICATION_CHILD_ORDER_CHANGED:
|
||||
if (!is_ready()) {
|
||||
return;
|
||||
}
|
||||
// fall through
|
||||
case NOTIFICATION_READY:
|
||||
update_sub_modifiers();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float TerrainModifierComposite::evaluate_at(Vector2 world_coordinate, float before) {
|
||||
if (this->sub_modifiers.is_empty()) {
|
||||
return before;
|
||||
}
|
||||
switch (this->mode) {
|
||||
case Normal: { // evaluate as if the sub-modifiers are part of the parent directly, with no modification
|
||||
float height{ before };
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
height = mod->evaluate_at(world_coordinate, height);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
case Multiply: { // evaluate independently, multiplying the /change/ each modifier makes together
|
||||
float result_delta{ 1.f };
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
result_delta *= mod->evaluate_at(world_coordinate, before) - before;
|
||||
}
|
||||
return result_delta + before;
|
||||
}
|
||||
case Add: { // evaluate independently, adding together the /change/ each modifier makes
|
||||
float result{ 0.f };
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
result += mod->evaluate_at(world_coordinate, before) - before;
|
||||
}
|
||||
return before + result;
|
||||
}
|
||||
case Max: { // always select the largest change
|
||||
float result_delta{ 0.f };
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
float delta{ mod->evaluate_at(world_coordinate, before) - before };
|
||||
if (Math::abs(delta) > Math::abs(result_delta)) {
|
||||
result_delta = delta;
|
||||
}
|
||||
}
|
||||
return before + result_delta;
|
||||
}
|
||||
case Min: { // always select the smallest change
|
||||
float result_delta{ INFINITY };
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
float delta{ mod->evaluate_at(world_coordinate, before) - before };
|
||||
if (Math::abs(delta) < Math::abs(result_delta)) {
|
||||
result_delta = delta;
|
||||
}
|
||||
}
|
||||
return before + result_delta;
|
||||
}
|
||||
case Average: {
|
||||
float total_delta{ 0.f };
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
total_delta += mod->evaluate_at(world_coordinate, before) - before;
|
||||
}
|
||||
return before + total_delta / (float)this->sub_modifiers.size();
|
||||
}
|
||||
case NormalMultiply: {
|
||||
float add_result{ 0.f };
|
||||
float multiply_result{ 1.f };
|
||||
for (TerrainModifier *mod : this->sub_modifiers) {
|
||||
float evaluated{ mod->evaluate_at(world_coordinate, before) - before };
|
||||
add_result += evaluated;
|
||||
multiply_result *= evaluated;
|
||||
}
|
||||
return before + add_result + multiply_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "macros.h"
|
||||
#include "terrain/terrain_modifier.h"
|
||||
|
||||
class TerrainModifierComposite : public TerrainModifier {
|
||||
GDCLASS(TerrainModifierComposite, TerrainModifier);
|
||||
static void _bind_methods();
|
||||
void push_all_changes();
|
||||
void update_sub_modifiers();
|
||||
void terrain_changed(Terrain *terrain);
|
||||
|
||||
public:
|
||||
GDENUM(CompositeMode, Normal, Add, Multiply, Max, Min, Average, NormalMultiply);
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
float evaluate_at(Vector2 world_coordinate, float before) override;
|
||||
|
||||
private:
|
||||
Vector<TerrainModifier *> sub_modifiers{};
|
||||
CompositeMode mode{ Normal };
|
||||
|
||||
public:
|
||||
GET_SET_FNS_EX(CompositeMode, mode, push_all_changes());
|
||||
};
|
||||
|
||||
MAKE_TYPE_INFO(TerrainModifierComposite::CompositeMode, Variant::INT);
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
#include "terrain_modifier_distance.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "macros.h"
|
||||
|
||||
void TerrainModifierDistance::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::OBJECT, distance_weight_curve, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
}
|
||||
|
||||
void TerrainModifierDistance::curves_changed() {
|
||||
if (!update_bounds()) {
|
||||
push_changed(get_bounds());
|
||||
}
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
this->distance_weight_curve_buffer = this->distance_weight_curve.is_valid() ? this->distance_weight_curve->duplicate(true) : nullptr;
|
||||
}
|
||||
|
||||
bool TerrainModifierDistance::update_bounds() {
|
||||
bool changed{ false };
|
||||
Rect2 bounds{};
|
||||
{
|
||||
SharedMutex::LockShared shared{ this->lock };
|
||||
Rect2 const before{ get_bounds() };
|
||||
Vector3 position{ get_thread_safe_global_position() };
|
||||
bounds.position = { position.x, position.z };
|
||||
bounds.size = { 0, 0 };
|
||||
if (this->distance_weight_curve.is_valid()) {
|
||||
float const max_radius{ this->distance_weight_curve->get_max_domain() };
|
||||
float const max_diameter{ 2.f * max_radius };
|
||||
bounds.size = { max_diameter, max_diameter };
|
||||
bounds.position -= { max_radius, max_radius };
|
||||
}
|
||||
changed = before != bounds;
|
||||
}
|
||||
{
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
set_bounds(bounds);
|
||||
set_gizmo_extents(this->distance_weight_curve.is_valid() ? this->distance_weight_curve->get_max_domain() : 2.f);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void TerrainModifierDistance::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
update_bounds();
|
||||
set_notify_transform(true);
|
||||
return;
|
||||
case NOTIFICATION_TRANSFORM_CHANGED:
|
||||
if (is_inside_tree()) {
|
||||
if (!update_bounds()) {
|
||||
push_changed(get_bounds());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float TerrainModifierDistance::distance_at(Vector2 const &world_coordinate) {
|
||||
Vector3 const global_position{ get_thread_safe_global_position() };
|
||||
return world_coordinate.distance_to({ global_position.x, global_position.z });
|
||||
}
|
||||
|
||||
float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float before) {
|
||||
SharedMutex::LockShared shared{ this->lock };
|
||||
if (this->distance_weight_curve_buffer.is_null()) {
|
||||
return before;
|
||||
}
|
||||
float const distance{ distance_at(world_coordinate) };
|
||||
if (distance >= this->distance_weight_curve_buffer->get_max_domain()) {
|
||||
return before;
|
||||
}
|
||||
float const weight_offset{ CLAMP(distance, this->distance_weight_curve_buffer->get_min_domain(), this->distance_weight_curve_buffer->get_max_domain()) };
|
||||
float const weight{ this->distance_weight_curve_buffer->sample(weight_offset) };
|
||||
float out{ weight <= 0.f ? before : Math::lerp(before, get_thread_safe_global_position().y, weight) };
|
||||
return out;
|
||||
}
|
||||
|
||||
PackedStringArray TerrainModifierDistance::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (this->distance_weight_curve.is_null()) {
|
||||
warnings.push_back("distance_weight_curve is invalid, add a valid distance_weight_curve");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void TerrainModifierDistance::set_distance_weight_curve(Ref<Curve> curve) {
|
||||
{
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
if (this->distance_weight_curve.is_valid()) {
|
||||
this->distance_weight_curve->disconnect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
if (curve.is_valid()) {
|
||||
curve->connect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
}
|
||||
this->distance_weight_curve = curve;
|
||||
}
|
||||
curves_changed();
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
Ref<Curve> TerrainModifierDistance::get_distance_weight_curve() const {
|
||||
return this->distance_weight_curve;
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "terrain/shared_mutex.h"
|
||||
#include "terrain/terrain_modifier.h"
|
||||
|
||||
class TerrainModifierDistance : public TerrainModifier {
|
||||
GDCLASS(TerrainModifierDistance, TerrainModifier);
|
||||
static void _bind_methods();
|
||||
void curves_changed();
|
||||
bool update_bounds();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
float distance_at(Vector2 const &world_coordinate);
|
||||
|
||||
public:
|
||||
float evaluate_at(Vector2 world_coordinate, float before) override;
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
private:
|
||||
SharedMutex lock{};
|
||||
Ref<Curve> distance_weight_curve{};
|
||||
Ref<Curve> distance_weight_curve_buffer{};
|
||||
|
||||
public:
|
||||
void set_distance_weight_curve(Ref<Curve> curve);
|
||||
Ref<Curve> get_distance_weight_curve() const;
|
||||
};
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
#include "terrain_modifier_noise.h"
|
||||
#include "core/math/math_defs.h"
|
||||
#include "macros.h"
|
||||
|
||||
void TerrainModifierNoise::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::OBJECT, noise, PROPERTY_HINT_RESOURCE_TYPE, "Noise");
|
||||
BIND_PROPERTY(Variant::FLOAT, noise_amplitude);
|
||||
}
|
||||
|
||||
void TerrainModifierNoise::push_changed_all() {
|
||||
push_changed(get_bounds());
|
||||
}
|
||||
|
||||
void TerrainModifierNoise::noise_changed() {
|
||||
{
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
this->noise_buffer = this->noise->duplicate_deep();
|
||||
}
|
||||
push_changed_all();
|
||||
}
|
||||
|
||||
float TerrainModifierNoise::evaluate_at(Vector2 world_coordinates, float before) {
|
||||
SharedMutex::LockShared shared{ this->lock };
|
||||
if (this->noise.is_null()) {
|
||||
return before;
|
||||
}
|
||||
return 0.5f * this->noise_amplitude * (this->noise_buffer->get_noise_2d(world_coordinates.x, world_coordinates.y) + 1.f) + before;
|
||||
}
|
||||
|
||||
void TerrainModifierNoise::set_noise(Ref<Noise> noise) {
|
||||
{
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
if (noise == this->noise) {
|
||||
return;
|
||||
}
|
||||
if (this->noise.is_valid()) {
|
||||
this->noise->disconnect_changed(callable_mp(this, &self_type::noise_changed));
|
||||
}
|
||||
if (noise.is_valid()) {
|
||||
noise->connect_changed(callable_mp(this, &self_type::noise_changed));
|
||||
set_bounds({ { -Math::INF, -Math::INF }, { Math::INF, Math::INF } });
|
||||
} else {
|
||||
set_bounds({ { 0, 0 }, { 0, 0 } });
|
||||
}
|
||||
this->noise = noise;
|
||||
}
|
||||
noise_changed();
|
||||
}
|
||||
|
||||
Ref<Noise> TerrainModifierNoise::get_noise() const {
|
||||
return this->noise;
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "macros.h"
|
||||
#include "modules/noise/noise.h"
|
||||
#include "terrain/shared_mutex.h"
|
||||
#include "terrain/terrain_modifier.h"
|
||||
|
||||
class TerrainModifierNoise : public TerrainModifier {
|
||||
GDCLASS(TerrainModifierNoise, TerrainModifier);
|
||||
static void _bind_methods();
|
||||
void push_changed_all();
|
||||
void noise_changed();
|
||||
|
||||
public:
|
||||
float evaluate_at(Vector2 world_coordinates, float before) override;
|
||||
|
||||
private:
|
||||
SharedMutex lock{};
|
||||
Ref<Noise> noise{};
|
||||
Ref<Noise> noise_buffer{};
|
||||
float noise_amplitude{ 1.f };
|
||||
|
||||
public:
|
||||
void set_noise(Ref<Noise> noise);
|
||||
Ref<Noise> get_noise() const;
|
||||
GET_SET_FNS_EX(float, noise_amplitude, push_changed_all());
|
||||
};
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
#include "terrain_modifier_path.h"
|
||||
#include "macros.h"
|
||||
|
||||
void TerrainModifierPath::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::OBJECT, curve_left, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
BIND_HPROPERTY(Variant::OBJECT, curve_right, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
}
|
||||
|
||||
void TerrainModifierPath::curves_changed() {
|
||||
{
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
this->curve_left_buffer = this->curve_left.is_valid() ? this->curve_left->duplicate(true) : nullptr;
|
||||
this->curve_right_buffer = this->curve_right.is_valid() ? this->curve_right->duplicate(true) : nullptr;
|
||||
}
|
||||
if (!update_bounds()) {
|
||||
push_changed(get_bounds());
|
||||
}
|
||||
}
|
||||
|
||||
bool TerrainModifierPath::update_bounds() {
|
||||
bool changed{ false };
|
||||
float margin{ 0.f };
|
||||
Rect2 bounds{};
|
||||
{
|
||||
// calculate the bounds, no need to make an exclusive lock if we can avoid it
|
||||
SharedMutex::LockShared shared{ this->lock };
|
||||
if (this->path == nullptr || this->path->get_curve().is_null() || this->path->get_curve()->get_point_count() <= 1) {
|
||||
return false;
|
||||
}
|
||||
// which of the two curves is the furthest reaching
|
||||
if (this->curve_left.is_valid()) {
|
||||
float const domain{ this->curve_left->get_max_domain() };
|
||||
margin = domain > margin ? domain : margin;
|
||||
}
|
||||
if (this->curve_right.is_valid()) {
|
||||
float const domain{ this->curve_right->get_max_domain() };
|
||||
margin = domain > margin ? domain : margin;
|
||||
}
|
||||
// alias some known data
|
||||
Transform3D curve_transform{ this->path->get_global_transform() };
|
||||
PackedVector3Array const &baked_points{ this->path->get_curve()->get_baked_points() };
|
||||
// find the highest and lowest x and z values
|
||||
Vector2 min{}, max{};
|
||||
for (int i{ 0 }; i < baked_points.size(); i += baked_points.size() / 10) {
|
||||
Vector3 point{ baked_points[i] };
|
||||
point = {
|
||||
curve_transform.basis.get_column(0) * point.x +
|
||||
curve_transform.basis.get_column(1) * point.y +
|
||||
curve_transform.basis.get_column(2) * point.z +
|
||||
curve_transform.origin
|
||||
};
|
||||
min = min.min({ point.x, point.z });
|
||||
max = max.max({ point.x, point.z });
|
||||
}
|
||||
// extend found min and max with margins
|
||||
min -= { margin, margin };
|
||||
max += Vector2{ margin, margin };
|
||||
// calculate bounds and check if any change is made
|
||||
bounds = { min, max - min };
|
||||
changed = bounds != get_bounds();
|
||||
}
|
||||
if (changed) {
|
||||
// ensure we have an exclusive lock before writing thread-shared data
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
set_bounds(bounds);
|
||||
set_gizmo_extents(margin > 2.f ? margin : 2.f);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
void TerrainModifierPath::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
children_changed();
|
||||
set_notify_transform(true);
|
||||
return;
|
||||
case NOTIFICATION_TRANSFORM_CHANGED:
|
||||
if (is_inside_tree() && is_ready()) {
|
||||
path_changed();
|
||||
}
|
||||
return;
|
||||
case NOTIFICATION_CHILD_ORDER_CHANGED:
|
||||
if (is_ready()) {
|
||||
children_changed();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) {
|
||||
SharedMutex::LockShared shared{ this->lock };
|
||||
if (this->path == nullptr) {
|
||||
return before;
|
||||
}
|
||||
if (this->curve_left_buffer.is_null()) {
|
||||
return before;
|
||||
}
|
||||
if (this->path_buffer.is_null()) {
|
||||
return before;
|
||||
}
|
||||
if (this->path_buffer->get_point_count() <= 1) {
|
||||
return before;
|
||||
}
|
||||
Ref<Curve> right_curve{ this->curve_right_buffer };
|
||||
if (right_curve.is_null()) {
|
||||
right_curve = this->curve_left_buffer;
|
||||
}
|
||||
Transform3D const inv_global_transform{ this->global_path_transform.inverse() };
|
||||
// convert world coordinate from 2d world to 3d path-local space
|
||||
Vector3 relative_position{
|
||||
world_coordinate.x - this->global_path_transform.origin.x, 0.f, world_coordinate.y - this->global_path_transform.origin.z
|
||||
};
|
||||
relative_position = {
|
||||
inv_global_transform.basis.get_column(0) * relative_position.x +
|
||||
inv_global_transform.basis.get_column(1) * relative_position.y +
|
||||
inv_global_transform.basis.get_column(2) * relative_position.z
|
||||
};
|
||||
// find the offset of the point closest to the world coordinate ...
|
||||
real_t const offset{ this->path_buffer->get_closest_offset(relative_position) };
|
||||
// ... and fetch the corresponding transform
|
||||
Transform3D const curve_point{ this->path_buffer->sample_baked_with_rotation(offset) };
|
||||
Vector3 global_curve_position{
|
||||
curve_point.origin.x * this->global_path_transform.basis.get_column(0) +
|
||||
curve_point.origin.y * this->global_path_transform.basis.get_column(1) +
|
||||
curve_point.origin.z * this->global_path_transform.basis.get_column(2) +
|
||||
this->global_path_transform.origin
|
||||
};
|
||||
// extract and xz position from sampled transform
|
||||
Vector2 const world_curve_point{ Vector2{ global_curve_position.x, global_curve_position.z } };
|
||||
// calculate the xz distance from the curve
|
||||
float const distance{ world_curve_point.distance_to(world_coordinate) };
|
||||
// exit early if we know for sure this point should not be affected by the path
|
||||
if (distance > this->curve_left_buffer->get_max_domain() && distance > right_curve->get_max_domain()) {
|
||||
return before;
|
||||
}
|
||||
// extract right direction and extract xz coordinates
|
||||
Vector3 right_direction{ curve_point.basis.get_column(0) };
|
||||
right_direction = {
|
||||
right_direction.x * this->global_path_transform.basis.get_column(0) +
|
||||
right_direction.y * this->global_path_transform.basis.get_column(1) +
|
||||
right_direction.z * this->global_path_transform.basis.get_column(2)
|
||||
};
|
||||
Vector2 const right2d{ Vector2{ right_direction.x, right_direction.z }.normalized() };
|
||||
// fetch the left and right curve weights according to the distance
|
||||
float const left_weight{ this->curve_left_buffer->sample(distance) };
|
||||
float const right_weight{ right_curve->sample(distance) };
|
||||
// calculate xz dot product, normalized to the distance
|
||||
float const dot{ right2d.dot(world_coordinate - world_curve_point) / distance };
|
||||
// use the dot product to calculate the ratio between the left and right weights ...
|
||||
float const right_left_ratio{ (dot + 1.f) / 2.f };
|
||||
// ... and use that as the t-value in a lerp between left and right weights
|
||||
float const weight{ Math::lerp(left_weight, right_weight, right_left_ratio) };
|
||||
// which then is the t-value of the final lerp from the unchanged height to the curve's height at this point
|
||||
return Math::lerp(before, global_curve_position.y, weight);
|
||||
}
|
||||
|
||||
void TerrainModifierPath::path_changed() {
|
||||
print_line("Path changed");
|
||||
{
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
if (this->path) {
|
||||
this->path_buffer = this->path->get_curve()->duplicate_deep();
|
||||
this->path_buffer->sample_baked(0.0).hash();
|
||||
this->global_path_transform = this->path->get_global_transform();
|
||||
print_line("path len:", this->path_buffer->get_point_count());
|
||||
}
|
||||
}
|
||||
update_configuration_warnings();
|
||||
if (!is_ready()) {
|
||||
return;
|
||||
}
|
||||
if (!update_bounds()) {
|
||||
push_changed(get_bounds());
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainModifierPath::children_changed() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
if (this->path) {
|
||||
this->path->disconnect("curve_changed", callable_mp(this, &self_type::path_changed));
|
||||
}
|
||||
for (Variant var : get_children()) {
|
||||
if (Path3D * path{ cast_to<Path3D>(var) }) {
|
||||
print_line("path found");
|
||||
this->path = path;
|
||||
this->path->connect("curve_changed", callable_mp(this, &self_type::path_changed));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
path_changed();
|
||||
}
|
||||
|
||||
PackedStringArray TerrainModifierPath::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (this->curve_left.is_null()) {
|
||||
warnings.push_back("curve_left is invalid, add a valid curve_left");
|
||||
}
|
||||
if (this->path == nullptr) {
|
||||
warnings.push_back("path is unassigned");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void TerrainModifierPath::set_curve_left(Ref<Curve> curve) {
|
||||
this->lock.lock_exclusive();
|
||||
if (curve.is_valid() && curve == this->curve_right) {
|
||||
curve = curve->duplicate();
|
||||
}
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
if (this->curve_left.is_valid()) {
|
||||
this->curve_left->disconnect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
if (curve.is_valid()) {
|
||||
curve->connect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
}
|
||||
this->curve_left = curve;
|
||||
if (!curve.is_valid() && this->curve_right.is_valid()) {
|
||||
this->curve_left = this->curve_right;
|
||||
this->lock.unlock_exclusive();
|
||||
set_curve_right(nullptr);
|
||||
} else {
|
||||
this->lock.unlock_exclusive();
|
||||
curves_changed();
|
||||
update_configuration_warnings();
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Curve> TerrainModifierPath::get_curve_left() const {
|
||||
return this->curve_left;
|
||||
}
|
||||
|
||||
void TerrainModifierPath::set_curve_right(Ref<Curve> curve) {
|
||||
{
|
||||
SharedMutex::LockExclusive exclusive{ this->lock };
|
||||
if (curve.is_valid() && curve == this->curve_left) {
|
||||
curve = curve->duplicate();
|
||||
}
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
if (this->curve_right.is_valid()) {
|
||||
this->curve_right->disconnect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
if (curve.is_valid()) {
|
||||
curve->connect_changed(callable_mp(this, &self_type::curves_changed));
|
||||
}
|
||||
}
|
||||
this->curve_right = curve;
|
||||
}
|
||||
curves_changed();
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
Ref<Curve> TerrainModifierPath::get_curve_right() const {
|
||||
return this->curve_right;
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "scene/3d/path_3d.h"
|
||||
#include "terrain/shared_mutex.h"
|
||||
#include "terrain/terrain_modifier.h"
|
||||
|
||||
class TerrainModifierPath : public TerrainModifier {
|
||||
GDCLASS(TerrainModifierPath, TerrainModifier);
|
||||
static void _bind_methods();
|
||||
void curves_changed();
|
||||
bool update_bounds();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
float evaluate_at(Vector2 world_coordinate, float before) override;
|
||||
void path_changed();
|
||||
void children_changed();
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
private:
|
||||
SharedMutex lock{};
|
||||
Path3D *path{ nullptr };
|
||||
Transform3D global_path_transform{};
|
||||
Ref<Curve3D> path_buffer{};
|
||||
Ref<Curve> curve_left_buffer{};
|
||||
Ref<Curve> curve_left{};
|
||||
Ref<Curve> curve_right_buffer{};
|
||||
Ref<Curve> curve_right{};
|
||||
|
||||
public:
|
||||
void set_curve_left(Ref<Curve> curve);
|
||||
Ref<Curve> get_curve_left() const;
|
||||
void set_curve_right(Ref<Curve> curve);
|
||||
Ref<Curve> get_curve_right() const;
|
||||
};
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://grb3q5nd2uds"
|
||||
path="res://.godot/imported/character_fem.blend-e169cb46816e89cf00aa8e7f988a0574.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/characters/player_fem/character_fem.blend"
|
||||
dest_files=["res://.godot/imported/character_fem.blend-e169cb46816e89cf00aa8e7f988a0574.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
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="uid://ba7qlhj5ylm3d"
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_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/export_geometry_nodes_instances=false
|
||||
blender/meshes/gpu_instances=false
|
||||
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
|
||||
gltf/naming_version=2
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://biqq268lccpng"
|
||||
path.s3tc="res://.godot/imported/Face.png-08c0111f3b71fa077c35243a4c740f6e.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/characters/player_fem/textures/Face.png"
|
||||
dest_files=["res://.godot/imported/Face.png-08c0111f3b71fa077c35243a4c740f6e.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 96 KiB |
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cwqc2g4616eun"
|
||||
path.s3tc="res://.godot/imported/Texture.png-98af1a158e1830cbcd0a13178176c442.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/characters/player_fem/textures/Texture.png"
|
||||
dest_files=["res://.godot/imported/Texture.png-98af1a158e1830cbcd0a13178176c442.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bsdvnyn6nhiaa"
|
||||
path="res://.godot/imported/character_masc.blend-0037c94467e2e85d7ec685a838d7e95d.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/characters/player_masc/character_masc.blend"
|
||||
dest_files=["res://.godot/imported/character_masc.blend-0037c94467e2e85d7ec685a838d7e95d.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
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=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_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/export_geometry_nodes_instances=false
|
||||
blender/meshes/gpu_instances=false
|
||||
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
|
||||
gltf/naming_version=2
|
||||
|
Before Width: | Height: | Size: 31 KiB |
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b7n0rgtlub4r7"
|
||||
path.s3tc="res://.godot/imported/Face.png-f5f833f7c71137a9e4aac5fe268e4dd4.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/characters/player_masc/textures/Face.png"
|
||||
dest_files=["res://.godot/imported/Face.png-f5f833f7c71137a9e4aac5fe268e4dd4.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 96 KiB |
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://lea3dgv2585y"
|
||||
path.s3tc="res://.godot/imported/Texture.png-51f4b86d2d244a13f05f399cc30a0d6b.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/characters/player_masc/textures/Texture.png"
|
||||
dest_files=["res://.godot/imported/Texture.png-51f4b86d2d244a13f05f399cc30a0d6b.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://dw4p3s74f1pdg"
|
||||
path="res://.godot/imported/cliffs_blockout.blend-f86a374f2c48645fd5614df18445a45a.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/environments/blockouts/cliffs_blockout.blend"
|
||||
dest_files=["res://.godot/imported/cliffs_blockout.blend-f86a374f2c48645fd5614df18445a45a.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
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="uid://ba7qlhj5ylm3d"
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_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/export_geometry_nodes_instances=false
|
||||
blender/meshes/gpu_instances=false
|
||||
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
|
||||
gltf/naming_version=2
|
||||
|
Before Width: | Height: | Size: 6.5 KiB |
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dwmur2qflotv6"
|
||||
path.bptc="res://.godot/imported/terrain.png-52ea7eaf6b989ed8d9fc2c0c5a398fdf.bptc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/environments/blockouts/terrain.png"
|
||||
dest_files=["res://.godot/imported/terrain.png-52ea7eaf6b989ed8d9fc2c0c5a398fdf.bptc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=true
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://dyt1mwbep2012"
|
||||
path="res://.godot/imported/flower.blend-a54f993c4db9e1bce792f272b6b1a837.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/environments/props/flower.blend"
|
||||
dest_files=["res://.godot/imported/flower.blend-a54f993c4db9e1bce792f272b6b1a837.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
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="uid://ba7qlhj5ylm3d"
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_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/export_geometry_nodes_instances=false
|
||||
blender/meshes/gpu_instances=false
|
||||
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
|
||||
gltf/naming_version=2
|
||||
|
Before Width: | Height: | Size: 39 KiB |
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://uujfed6yrp8p"
|
||||
path.s3tc="res://.godot/imported/grass_a.png-df3280112d606c2f3fb6a8ca84baa85d.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/environments/props/grass_a.png"
|
||||
dest_files=["res://.godot/imported/grass_a.png-df3280112d606c2f3fb6a8ca84baa85d.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 89 KiB |
|
|
@ -1,60 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://db6ddpj53gl5w"
|
||||
path="res://.godot/imported/rock_a.blend-b8d8f34d9e140f1b8adb493b605dd07e.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/environments/props/rock_a.blend"
|
||||
dest_files=["res://.godot/imported/rock_a.blend-b8d8f34d9e140f1b8adb493b605dd07e.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
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="uid://ba7qlhj5ylm3d"
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_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/export_geometry_nodes_instances=false
|
||||
blender/meshes/gpu_instances=false
|
||||
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
|
||||
gltf/naming_version=2
|
||||
|
Before Width: | Height: | Size: 37 KiB |
|
|
@ -1,60 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://517yqaw110pf"
|
||||
path="res://.godot/imported/rock_b.blend-d76791249d4c14d1c951b842f0208090.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/environments/props/rock_b.blend"
|
||||
dest_files=["res://.godot/imported/rock_b.blend-d76791249d4c14d1c951b842f0208090.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
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="uid://ba7qlhj5ylm3d"
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_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/export_geometry_nodes_instances=false
|
||||
blender/meshes/gpu_instances=false
|
||||
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
|
||||
gltf/naming_version=2
|
||||