This commit is contained in:
Redsandy
2026-01-18 20:56:24 +03:00
commit 7980e0add8
68 changed files with 1605294 additions and 0 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

3
docs/Action Rog/.obsidian/app.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"alwaysUpdateLinks": true
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,3 @@
[
"table-editor-obsidian"
]

View File

@@ -0,0 +1,33 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"footnotes": false,
"properties": true,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": true,
"bases": true,
"webviewer": false
}

22
docs/Action Rog/.obsidian/graph.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"collapse-filter": false,
"search": "",
"showTags": true,
"showAttachments": true,
"hideUnresolved": false,
"showOrphans": true,
"collapse-color-groups": false,
"colorGroups": [],
"collapse-display": false,
"showArrow": false,
"textFadeMultiplier": 0,
"nodeSizeMultiplier": 1,
"lineSizeMultiplier": 1,
"collapse-forces": false,
"centerStrength": 0.518713248970312,
"repelStrength": 10,
"linkStrength": 1,
"linkDistance": 250,
"scale": 1,
"close": true
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
{
"id": "obsidian-tasks-plugin",
"name": "Tasks",
"version": "7.22.0",
"minAppVersion": "1.4.0",
"description": "Track tasks across your vault. Supports due dates, recurring tasks, done dates, sub-set of checklist items, and filtering.",
"helpUrl": "https://publish.obsidian.md/tasks/",
"author": "Clare Macrae and Ilyas Landikov (created by Martin Schenck)",
"authorUrl": "https://github.com/obsidian-tasks-group",
"fundingUrl": "https://github.com/sponsors/claremacrae",
"isDesktopOnly": false
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
{
"formatType": "normal",
"showRibbonIcon": true,
"bindEnter": true,
"bindTab": true
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,17 @@
{
"id": "table-editor-obsidian",
"name": "Advanced Tables",
"author": "Tony Grosinger",
"authorUrl": "https://grosinger.net",
"description": "Improved table navigation, formatting, manipulation, and formulas",
"isDesktopOnly": false,
"minAppVersion": "1.0.0",
"version": "0.22.1",
"js": "main.js",
"fundingUrl": {
"Github Sponsor": "https://github.com/sponsors/tgrosinger",
"Buy me a Coffee": "https://buymeacoffee.com/tgrosinger",
"Paypal": "https://paypal.me/tgrosinger"
},
"donation": "https://buymeacoffee.com/tgrosinger"
}

View File

@@ -0,0 +1,78 @@
:root {
--advanced-tables-helper-size: 28px;
}
.HyperMD-table-row span.cm-inline-code {
font-size: 100%;
padding: 0px;
}
.advanced-tables-buttons>div>.title {
font-weight: var(--font-medium);
font-size: var(--nav-item-size);
color: var(--nav-item-color);
text-decoration: underline;
}
[data-type="advanced-tables-toolbar"] .nav-buttons-container {
column-gap: 0.2rem;
margin: 0.2rem 0 0.2rem 0;
justify-content: start;
}
[data-type="advanced-tables-toolbar"] .nav-buttons-container::before {
min-width: 2.6rem;
line-height: var(--advanced-tables-helper-size);
font-size: var(--nav-item-size);
font-weight: var(--nav-item-weight);
color: var(--nav-item-color);
}
[data-type="advanced-tables-toolbar"] .nav-buttons-container>* {
height: var(--advanced-tables-helper-size);
line-height: var(--advanced-tables-helper-size);
}
[data-type="advanced-tables-toolbar"] .nav-buttons-container .nav-action-button {
width: var(--advanced-tables-helper-size);
height: var(--advanced-tables-helper-size);
display: flex;
justify-content: center;
align-items: center;
border-radius: var(--radius-s);
}
[data-type="advanced-tables-toolbar"] .nav-buttons-container .nav-action-button:hover {
background-color: var(--nav-item-background-hover);
color: var(--nav-item-color-hover);
font-weight: var(--nav-item-weight-hover);
}
.advanced-tables-row-label {
width: 50px;
}
.widget-icon {
width: 20px;
height: 20px;
fill: var(--text-muted);
}
.widget-icon:hover {
fill: var(--text-normal);
}
.advanced-tables-csv-export textarea {
height: 200px;
width: 100%;
}
.advanced-tables-donation {
width: 70%;
margin: 0 auto;
text-align: center;
}
.advanced-tables-donate-button {
margin: 10px;
}

224
docs/Action Rog/.obsidian/workspace.json vendored Normal file
View File

@@ -0,0 +1,224 @@
{
"main": {
"id": "9d84761fdc521e48",
"type": "split",
"children": [
{
"id": "56870b1a5abc8bc3",
"type": "tabs",
"dimension": 52.76162790697675,
"children": [
{
"id": "fae3dd60da6746c9",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Спелы/Начинания/Начинания.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Начинания"
}
}
]
},
{
"id": "693c8197b2393835",
"type": "tabs",
"dimension": 47.23837209302326,
"children": [
{
"id": "41a43fa3fff1d73f",
"type": "leaf",
"state": {
"type": "graph",
"state": {},
"icon": "lucide-git-fork",
"title": "Граф"
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "c0e70ec8604f67da",
"type": "split",
"children": [
{
"id": "b11845e247b44591",
"type": "tabs",
"children": [
{
"id": "9a81516dc6c65404",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
"autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "Файловый менеджер"
}
},
{
"id": "6f4075d7a5920333",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
},
"icon": "lucide-search",
"title": "Поиск"
}
},
{
"id": "f0a6202fc12fc4af",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {},
"icon": "lucide-bookmark",
"title": "Закладки"
}
}
]
}
],
"direction": "horizontal",
"width": 300
},
"right": {
"id": "feabda6c3d6801d3",
"type": "split",
"children": [
{
"id": "cd27a52bffd0262b",
"type": "tabs",
"children": [
{
"id": "b0383d0fcbd61458",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"file": "Спелы/Начинания/Начинания.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "Обратные ссылки для Начинания"
}
},
{
"id": "609b4502709daf5e",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"file": "Спелы/Начинания/Начинания.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "Исходящие ссылки из Начинания"
}
},
{
"id": "2d8db340c9660aae",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-tags",
"title": "Теги"
}
},
{
"id": "dff9a1d15096e8ab",
"type": "leaf",
"state": {
"type": "all-properties",
"state": {
"sortOrder": "frequency",
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-archive",
"title": "Все свойства"
}
},
{
"id": "1591bf739b0cab13",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"file": "Спелы/Начинания/Начинания.md",
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "Структура Начинания"
}
}
]
}
],
"direction": "horizontal",
"width": 300,
"collapsed": true
},
"left-ribbon": {
"hiddenItems": {
"switcher:Меню быстрого перехода": false,
"graph:Граф": false,
"canvas:Создать новый холст": false,
"daily-notes:Сегодняшняя заметка": false,
"templates:Вставить шаблон": false,
"command-palette:Открыть палитру команд": false,
"bases:Создать новую базу": false,
"table-editor-obsidian:Advanced Tables Toolbar": false
}
},
"active": "fae3dd60da6746c9",
"lastOpenFiles": [
"Спелы/Начинания/Первичные/Вода.md",
"Вода.md",
"Спелы/Начинания/Начинания.md",
"Спелы/Начинания/Первичные/Огонь.md",
"Спелы/Начинания/Вторичные",
"Спелы/Начинания/Первичные",
"Спелы/Лужа.md",
"Спелы/Начинания/Первичные/Смерть.md",
"Спелы/Начинания/Первичные/Жизнь.md",
"Спелы/Начинания/Первичные/Тьма.md",
"Спелы/Начинания/Первичные/Свет.md",
"Спелы/Начинания/Первичные/Воздух.md",
"Спелы/Начинания/Первичные/Земля.md",
"Спелы/Начинания",
"Добро пожаловать.md",
"Спелы"
]
}

View File

View File

@@ -0,0 +1 @@
#мокрый

View File

@@ -0,0 +1,31 @@
# Общее
Есть первичные начинания и вторичные.
Первичные доступны сразу, до вторичных нужно дойти.
Начинания - кирпичики из которых складываются спелы.
# Первичные начинания
1. [[Огонь]]
2. [[Вода]]
3. [[Земля]]
4. [[Воздух]]
5. [[Свет]]
6. [[Тьма]]
7. [[Жизнь]]
8. [[Смерть]]
Первичные начинания доступны сразу и могут
# Вторичные
Доступны как комбинации первичных
1. Паладин ([[Огонь]] + [[Свет]])
2. Бладмаг ([[Огонь]] + [[Жизнь]])
3. Растения ([[Жизнь]] + [[Земля]])
4. Сталь ([[Земля]] + [[Тьма]])
5. Лед ([[Тьма]] + [[Спелы/Начинания/Первичные/Вода]])
6. Яд ([[Спелы/Начинания/Первичные/Вода]] + [[Смерть]])
7. Чума ([[Смерть]] + [[Воздух]])
8. Молния ([[Воздух]] + [[Свет]])

View File

@@ -0,0 +1,5 @@
#мокрый

1
icon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 995 B

43
icon.svg.import Normal file
View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dn3m6ec8rak21"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
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=false
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=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,12 @@
# Blender 4.4.0 MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.png

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
[remap]
importer="wavefront_obj"
importer_version=1
type="Mesh"
uid="uid://c82de86x4ho25"
path="res://.godot/imported/Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.obj-272c3e26cc8a388ddaff3599002a085b.mesh"
[deps]
files=["res://.godot/imported/Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.obj-272c3e26cc8a388ddaff3599002a085b.mesh"]
source_file="res://models/Crystal Golem Behemot/Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.obj"
dest_files=["res://.godot/imported/Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.obj-272c3e26cc8a388ddaff3599002a085b.mesh", "res://.godot/imported/Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.obj-272c3e26cc8a388ddaff3599002a085b.mesh"]
[params]
generate_tangents=true
generate_lods=true
generate_shadow_mesh=true
generate_lightmap_uv2=false
generate_lightmap_uv2_texel_size=0.2
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
force_disable_mesh_compression=false

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 MiB

View File

@@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://jyix4ilr67uk"
path.s3tc="res://.godot/imported/Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.png-68db2fcac524e74c59fbe9360f7bc6f0.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://models/Crystal Golem Behemot/Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.png"
dest_files=["res://.godot/imported/Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.png-68db2fcac524e74c59fbe9360f7bc6f0.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 MiB

View File

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://x4734mds7b74"
path.s3tc="res://.godot/imported/Meshy_AI_Meshy_Merged_Animations_0.png-afda3fedb942079159108d0b652b7e94.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "7bd9bbed46ac9e0d5bf50f774da896f3"
}
[deps]
source_file="res://models/Crystal Golem Behemot/Meshy_AI_Meshy_Merged_Animations_0.png"
dest_files=["res://.godot/imported/Meshy_AI_Meshy_Merged_Animations_0.png-afda3fedb942079159108d0b652b7e94.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

Binary file not shown.

View File

@@ -0,0 +1,50 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://fruqpocnluu4"
path="res://.godot/imported/Mutant Walking.fbx-044ace83349eaaf9d031cd571d61e9b3.scn"
[deps]
source_file="res://models/Crystal Golem Behemot/Mutant Walking.fbx"
dest_files=["res://.godot/imported/Mutant Walking.fbx-044ace83349eaaf9d031cd571d61e9b3.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=true
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={
"nodes": {
"PATH:Skeleton3D": {
"rest_pose/selected_animation": "mixamo_com"
}
}
}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

View File

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cgxy3uivyomg"
path.s3tc="res://.godot/imported/Mutant Walking_0.png-5ddbf6c8fdd4c9c6cc7d81ed05a731ad.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "9f199a31179af6f4a0995373751c7d0f"
}
[deps]
source_file="res://models/Crystal Golem Behemot/Mutant Walking_0.png"
dest_files=["res://.godot/imported/Mutant Walking_0.png-5ddbf6c8fdd4c9c6cc7d81ed05a731ad.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

44
project.godot Normal file
View File

@@ -0,0 +1,44 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="ActionRog"
run/main_scene="res://scenes/main_menu.tscn"
config/features=PackedStringArray("4.5", "Forward Plus")
config/icon="res://icon.svg"
[input]
move_forward={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
move_back={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
]
}
move_left={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
move_right={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
dash={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
]
}

61
scenes/enemy.tscn Normal file
View File

@@ -0,0 +1,61 @@
[gd_scene load_steps=6 format=3 uid="uid://dcyxfspptvuqn"]
[ext_resource type="Script" uid="uid://8vuywuv7ebsl" path="res://scripts/enemy.gd" id="1_enemy"]
[ext_resource type="Script" path="res://scripts/enemy_health_bar.gd" id="2_health_bar"]
[ext_resource type="ArrayMesh" uid="uid://c82de86x4ho25" path="res://models/Crystal Golem Behemot/Meshy_AI_Crystal_Golem_Behemot_0110152107_texture.obj" id="3_enemy"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
radius = 1.5
height = 5.0
[sub_resource type="BoxMesh" id="BoxMesh_1"]
size = Vector3(3, 0.2, 0.05)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.5, 0, 0, 1)
emission_enabled = true
emission = Color(0.8, 0, 0, 1)
emission_energy_multiplier = 2.0
shading_mode = 0
flags_unshaded = true
flags_transparent = false
depth_draw_mode = 2
render_priority = 1
[sub_resource type="BoxMesh" id="BoxMesh_2"]
size = Vector3(3, 0.2, 0.05)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2"]
albedo_color = Color(0, 1, 0, 1)
emission_enabled = true
emission = Color(0, 1, 0, 1)
emission_energy_multiplier = 2.0
shading_mode = 0
flags_unshaded = true
flags_transparent = false
depth_draw_mode = 2
render_priority = 2
[node name="Enemy" type="CharacterBody3D"]
script = ExtResource("1_enemy")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(2.5, 0, 0, 0, 2.5, 0, 0, 0, 2.5, 0, 2.6208835, 0)
mesh = ExtResource("3_enemy")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
shape = SubResource("CapsuleShape3D_1")
[node name="HealthBarContainer" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.5, 0)
script = ExtResource("2_health_bar")
[node name="BackgroundBar" type="MeshInstance3D" parent="HealthBarContainer"]
mesh = SubResource("BoxMesh_1")
material_override = SubResource("StandardMaterial3D_1")
[node name="HealthBar" type="MeshInstance3D" parent="HealthBarContainer"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.01)
mesh = SubResource("BoxMesh_2")
material_override = SubResource("StandardMaterial3D_2")

104
scenes/hud.tscn Normal file
View File

@@ -0,0 +1,104 @@
[gd_scene load_steps=6 format=3 uid="uid://hud"]
[ext_resource type="Script" path="res://scripts/hud.gd" id="1_hud"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1"]
bg_color = Color(0.2, 0, 0, 0.8)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2"]
bg_color = Color(0, 0.8, 0, 0.8)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3"]
bg_color = Color(0.2, 0.2, 0.2, 0.8)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4"]
bg_color = Color(0.2, 0.6, 1, 0.8)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[node name="HUD" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("1_hud")
[node name="HealthBar" type="ProgressBar" parent="."]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 20.0
offset_top = -60.0
offset_right = 320.0
offset_bottom = -20.0
grow_vertical = 0
max_value = 100.0
value = 100.0
show_percentage = false
theme_override_styles/background = SubResource("StyleBoxFlat_1")
theme_override_styles/fill = SubResource("StyleBoxFlat_2")
[node name="HealthLabel" type="Label" parent="HealthBar"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
theme_override_constants/shadow_offset_x = 2
theme_override_constants/shadow_offset_y = 2
text = "HP: 100 / 100"
horizontal_alignment = 1
vertical_alignment = 1
[node name="DashCooldownBar" type="ProgressBar" parent="."]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 20.0
offset_top = -120.0
offset_right = 320.0
offset_bottom = -80.0
grow_vertical = 0
max_value = 100.0
value = 100.0
show_percentage = false
theme_override_styles/background = SubResource("StyleBoxFlat_3")
theme_override_styles/fill = SubResource("StyleBoxFlat_4")
[node name="DashCooldownLabel" type="Label" parent="DashCooldownBar"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
theme_override_constants/shadow_offset_x = 2
theme_override_constants/shadow_offset_y = 2
text = "Рывок: готов (Пробел)"
horizontal_alignment = 1
vertical_alignment = 1

60
scenes/main.tscn Normal file
View File

@@ -0,0 +1,60 @@
[gd_scene load_steps=8 format=3 uid="uid://c64bxmqeaaaku"]
[ext_resource type="PackedScene" uid="uid://player" path="res://scenes/player.tscn" id="1_main"]
[ext_resource type="PackedScene" path="res://scenes/enemy.tscn" id="2_main"]
[ext_resource type="Script" uid="uid://28onm6d36qbo" path="res://scripts/camera_controller.gd" id="3_main"]
[ext_resource type="PackedScene" path="res://scenes/hud.tscn" id="4_main"]
[sub_resource type="PlaneMesh" id="PlaneMesh_1"]
size = Vector2(50, 50)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.2, 0.2, 0.2, 1)
[sub_resource type="BoxShape3D" id="BoxShape3D_1"]
size = Vector3(50, 0.1, 50)
[node name="Main" type="Node3D"]
[node name="Ground" type="StaticBody3D" parent="."]
[node name="MeshInstance3D" type="MeshInstance3D" parent="Ground"]
mesh = SubResource("PlaneMesh_1")
surface_material_override/0 = SubResource("StandardMaterial3D_1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Ground"]
shape = SubResource("BoxShape3D_1")
[node name="Player" parent="." instance=ExtResource("1_main")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.2521453, 1, 12.966181)
[node name="Enemies" type="Node3D" parent="."]
[node name="Enemy1" parent="Enemies" instance=ExtResource("2_main")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13.720226, 1, 5)
[node name="Enemy2" parent="Enemies" instance=ExtResource("2_main")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 1, 5)
[node name="Enemy3" parent="Enemies" instance=ExtResource("2_main")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 1, -5)
[node name="Enemy4" parent="Enemies" instance=ExtResource("2_main")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 1, -5)
[node name="Enemy5" parent="Enemies" instance=ExtResource("2_main")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 8)
[node name="CameraController" type="Node3D" parent="."]
script = ExtResource("3_main")
[node name="Camera3D" type="Camera3D" parent="CameraController"]
transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 0, 15, 15)
current = true
fov = 60.0
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.866025, -0.433013, 0.25, 0, 0.5, 0.866025, -0.5, -0.75, 0.433013, 0, 10, 0)
shadow_enabled = true
[node name="HUD" parent="." instance=ExtResource("4_main")]

99
scenes/main_menu.tscn Normal file
View File

@@ -0,0 +1,99 @@
[gd_scene load_steps=5 format=3 uid="uid://main_menu"]
[ext_resource type="Script" path="res://scripts/main_menu.gd" id="1_menu"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1"]
bg_color = Color(0.2, 0.2, 0.2, 0.8)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2"]
bg_color = Color(0.3, 0.3, 0.4, 0.9)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3"]
bg_color = Color(0.15, 0.15, 0.25, 0.9)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[node name="MainMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_menu")
[node name="Background" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.1, 0.1, 0.15, 1)
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -150.0
offset_top = -120.0
offset_right = 150.0
offset_bottom = 120.0
grow_horizontal = 2
grow_vertical = 2
[node name="Title" type="Label" parent="VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 48
text = "Action Rog"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
layout_mode = 2
[node name="StartButton" type="Button" parent="VBoxContainer"]
layout_mode = 2
custom_minimum_size = Vector2(200, 40)
theme_override_styles/normal = SubResource("StyleBoxFlat_1")
theme_override_styles/hover = SubResource("StyleBoxFlat_2")
theme_override_styles/pressed = SubResource("StyleBoxFlat_3")
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_color = Color(0.9, 0.9, 1, 1)
theme_override_colors/font_pressed_color = Color(0.7, 0.7, 0.9, 1)
text = "Начать игру"
[node name="SettingsButton" type="Button" parent="VBoxContainer"]
layout_mode = 2
custom_minimum_size = Vector2(200, 40)
theme_override_styles/normal = SubResource("StyleBoxFlat_1")
theme_override_styles/hover = SubResource("StyleBoxFlat_2")
theme_override_styles/pressed = SubResource("StyleBoxFlat_3")
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_color = Color(0.9, 0.9, 1, 1)
theme_override_colors/font_pressed_color = Color(0.7, 0.7, 0.9, 1)
text = "Настройки"
[node name="QuitButton" type="Button" parent="VBoxContainer"]
layout_mode = 2
custom_minimum_size = Vector2(200, 40)
theme_override_styles/normal = SubResource("StyleBoxFlat_1")
theme_override_styles/hover = SubResource("StyleBoxFlat_2")
theme_override_styles/pressed = SubResource("StyleBoxFlat_3")
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_color = Color(0.9, 0.9, 1, 1)
theme_override_colors/font_pressed_color = Color(0.7, 0.7, 0.9, 1)
text = "Выход"

View File

@@ -0,0 +1,113 @@
[gd_scene load_steps=10 format=3 uid="uid://cwte6nc4bxak0"]
[ext_resource type="Script" uid="uid://d3c5g160kh3vv" path="res://scripts/parabolic_projectile.gd" id="1_parabolic"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(1, 0.4, 0.1, 1)
emission_enabled = true
emission = Color(1, 0.3, 0, 1)
emission_energy_multiplier = 4.0
[sub_resource type="SphereMesh" id="SphereMesh_1"]
radius = 0.3
height = 0.6
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
radius = 0.3
height = 0.6
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_1"]
direction = Vector3(0, 0, -1)
initial_velocity_min = 2.0
initial_velocity_max = 5.0
gravity = Vector3(0, -2, 0)
scale_min = 0.15
scale_max = 0.25
color = Color(1, 0.5, 0, 1)
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_2"]
direction = Vector3(0, 0, -1)
initial_velocity_min = 1.0
initial_velocity_max = 3.0
gravity = Vector3(0, -1, 0)
scale_min = 0.08
scale_max = 0.18
color = Color(1, 0.3, 0, 0.8)
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_3"]
direction = Vector3(0, 1, 0)
spread = 180.0
initial_velocity_min = 5.0
initial_velocity_max = 10.0
gravity = Vector3(0, -5, 0)
scale_min = 0.1
scale_max = 0.25
scale_over_velocity_min = 0.05
scale_over_velocity_max = 0.15
color = Color(1, 0.6, 0.1, 1)
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_4"]
direction = Vector3(0, 1, 0)
initial_velocity_min = 1.5
initial_velocity_max = 3.0
gravity = Vector3(0, -2, 0)
scale_min = 0.15
scale_max = 0.3
color = Color(1, 0.8, 0.2, 1)
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_5"]
direction = Vector3(0, 1, 0)
spread = 90.0
initial_velocity_min = 2.0
initial_velocity_max = 4.0
gravity = Vector3(0, -1, 0)
scale_min = 0.2
scale_max = 0.4
color = Color(0.3, 0.3, 0.3, 0.6)
[node name="ParabolicProjectile" type="Area3D"]
script = ExtResource("1_parabolic")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
material_override = SubResource("StandardMaterial3D_1")
mesh = SubResource("SphereMesh_1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CapsuleShape3D_1")
[node name="FireParticles" type="GPUParticles3D" parent="."]
amount = 40
lifetime = 0.4
process_material = SubResource("ParticleProcessMaterial_1")
draw_pass_1 = SubResource("SphereMesh_1")
[node name="TrailParticles" type="GPUParticles3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.4)
amount = 60
lifetime = 0.6
process_material = SubResource("ParticleProcessMaterial_2")
draw_pass_1 = SubResource("SphereMesh_1")
[node name="ExplosionParticles" type="GPUParticles3D" parent="."]
emitting = false
amount = 80
lifetime = 0.5
one_shot = true
process_material = SubResource("ParticleProcessMaterial_3")
draw_pass_1 = SubResource("SphereMesh_1")
[node name="ExplosionCore" type="GPUParticles3D" parent="."]
emitting = false
amount = 30
lifetime = 0.4
one_shot = true
process_material = SubResource("ParticleProcessMaterial_4")
draw_pass_1 = SubResource("SphereMesh_1")
[node name="ExplosionSmoke" type="GPUParticles3D" parent="."]
emitting = false
amount = 40
lifetime = 0.5
one_shot = true
process_material = SubResource("ParticleProcessMaterial_5")
draw_pass_1 = SubResource("SphereMesh_1")

23
scenes/player.tscn Normal file
View File

@@ -0,0 +1,23 @@
[gd_scene load_steps=6 format=3 uid="uid://player"]
[ext_resource type="Script" uid="uid://bt4nfjnngh21i" path="res://scripts/player.gd" id="1_player"]
[ext_resource type="PackedScene" uid="uid://q46fjntx43e2" path="res://scenes/straight_projectile.tscn" id="2_player"]
[ext_resource type="PackedScene" uid="uid://cwte6nc4bxak0" path="res://scenes/parabolic_projectile.tscn" id="3_player"]
[sub_resource type="BoxMesh" id="BoxMesh_1"]
[sub_resource type="BoxShape3D" id="BoxShape3D_1"]
[node name="Player" type="CharacterBody3D" groups=["player"]]
script = ExtResource("1_player")
straight_projectile_scene = ExtResource("2_player")
parabolic_projectile_scene = ExtResource("3_player")
speed = 15.0
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
mesh = SubResource("BoxMesh_1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
shape = SubResource("BoxShape3D_1")

View File

@@ -0,0 +1,57 @@
[gd_scene load_steps=7 format=3 uid="uid://q46fjntx43e2"]
[ext_resource type="Script" uid="uid://crsltf0vxq1xd" path="res://scripts/straight_projectile.gd" id="1_straight"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(1, 0.6, 0.2, 1)
emission_enabled = true
emission = Color(1, 0.4, 0, 1)
emission_energy_multiplier = 3.0
[sub_resource type="SphereMesh" id="SphereMesh_1"]
radius = 0.2
height = 0.6
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
radius = 0.2
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_1"]
direction = Vector3(0, 0, -1)
initial_velocity_min = 2.0
initial_velocity_max = 4.0
gravity = Vector3(0, -2, 0)
scale_min = 0.1
scale_max = 0.2
color = Color(1, 0.5, 0, 1)
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_2"]
direction = Vector3(0, 0, -1)
initial_velocity_min = 1.0
initial_velocity_max = 2.0
gravity = Vector3(0, -1, 0)
scale_min = 0.05
scale_max = 0.15
color = Color(1, 0.3, 0, 0.8)
[node name="StraightProjectile" type="Area3D"]
script = ExtResource("1_straight")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
material_override = SubResource("StandardMaterial3D_1")
mesh = SubResource("SphereMesh_1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("SphereShape3D_1")
[node name="FireParticles" type="GPUParticles3D" parent="."]
amount = 30
lifetime = 0.3
process_material = SubResource("ParticleProcessMaterial_1")
draw_pass_1 = SubResource("SphereMesh_1")
[node name="TrailParticles" type="GPUParticles3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.3)
amount = 50
lifetime = 0.5
process_material = SubResource("ParticleProcessMaterial_2")
draw_pass_1 = SubResource("SphereMesh_1")

View File

@@ -0,0 +1,46 @@
extends Node3D
class_name CameraController
# Контроллер камеры в стиле Path of Exile 2
# Изометрическая камера, которая следует за игроком
@export var target: Node3D = null
@export var camera_distance: float = 20.0
@export var camera_height: float = 15.0
@export var camera_angle: float = 45.0 # Угол наклона камеры
@export var follow_speed: float = 5.0
var camera: Camera3D
func _ready():
camera = get_node_or_null("Camera3D")
if not camera:
camera = Camera3D.new()
add_child(camera)
# Находим игрока если цель не установлена
if not target:
var players = get_tree().get_nodes_in_group("player")
if players.size() > 0:
target = players[0]
func _process(delta):
if not target or not is_instance_valid(target):
return
# Вычисляем позицию камеры
var target_pos = target.global_position
var angle_rad = deg_to_rad(camera_angle)
# Изометрическая позиция камеры (диагональный вид сверху, как в POE 2)
# Камера находится под углом 45 градусов по диагонали
var offset_x = camera_distance * 0.707 # cos(45) = 0.707
var offset_z = camera_distance * 0.707 # sin(45) = 0.707
var offset_y = camera_height
# Плавное следование за игроком
var desired_pos = target_pos + Vector3(offset_x, offset_y, offset_z)
global_position = global_position.lerp(desired_pos, follow_speed * delta)
# Камера всегда смотрит на игрока
camera.look_at(target_pos, Vector3.UP)

View File

@@ -0,0 +1 @@
uid://28onm6d36qbo

77
scripts/enemy.gd Normal file
View File

@@ -0,0 +1,77 @@
extends Unit
class_name Enemy
# Класс противника
@export var detection_range: float = 10.0
@export var attack_range: float = 2.0
@export var attack_damage: float = 10.0
@export var attack_cooldown: float = 1.0
var target: Node3D = null
var attack_timer: float = 0.0
func _ready():
super._ready()
func _physics_process(delta):
attack_timer -= delta
# Поиск цели (игрока)
if target == null or not is_instance_valid(target):
find_target()
# Движение к цели
if target and is_instance_valid(target):
var distance = global_position.distance_to(target.global_position)
if distance <= attack_range:
# Атака - поворачиваемся к цели
var look_direction = (target.global_position - global_position)
look_direction.y = 0
if look_direction.length() > 0.1:
var angle = atan2(look_direction.x, look_direction.z)
rotation.y = angle
if attack_timer <= 0:
attack(target)
attack_timer = attack_cooldown
elif distance <= detection_range:
# Преследование - поворачиваемся и бежим к цели
var direction = (target.global_position - global_position).normalized()
velocity.x = direction.x * speed
velocity.z = direction.z * speed
# Поворачиваем врага в сторону цели
var look_direction = (target.global_position - global_position)
look_direction.y = 0
if look_direction.length() > 0.1:
var angle = atan2(look_direction.x, look_direction.z)
rotation.y = angle
else:
# Остановка - не поворачиваемся
velocity.x = move_toward(velocity.x, 0, speed)
velocity.z = move_toward(velocity.z, 0, speed)
else:
# Остановка если нет цели
velocity.x = move_toward(velocity.x, 0, speed)
velocity.z = move_toward(velocity.z, 0, speed)
# Применяем гравитацию
if not is_on_floor():
velocity.y -= 9.8 * delta
else:
velocity.y = 0
super._physics_process(delta)
func find_target():
# Ищем игрока в сцене
var players = get_tree().get_nodes_in_group("player")
if players.size() > 0:
target = players[0]
func attack(target_unit: Node3D):
# Атака цели
if target_unit.has_method("take_damage"):
target_unit.take_damage(attack_damage)

1
scripts/enemy.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://8vuywuv7ebsl

View File

@@ -0,0 +1,87 @@
extends Node3D
class_name EnemyHealthBar
# Скрипт для отображения полоски здоровья над врагом
@onready var background_bar: MeshInstance3D = $BackgroundBar
@onready var health_bar: MeshInstance3D = $HealthBar
var unit: Unit = null
var bar_width: float = 3.0
var bar_height: float = 0.2
func _ready():
# Находим родительский Unit (враг)
unit = get_parent() as Unit
if unit:
# Подключаемся к сигналу изменения здоровья
unit.health_changed.connect(_on_health_changed)
# Изначально скрываем полоску (если здоровье 100%)
update_visibility()
update_health_bar()
else:
print("EnemyHealthBar: Не найден Unit!")
# Убеждаемся, что узлы найдены
if not background_bar:
print("EnemyHealthBar: Не найден BackgroundBar!")
if not health_bar:
print("EnemyHealthBar: Не найден HealthBar!")
func _process(_delta):
# Поворачиваем полоску к камере
var camera = get_viewport().get_camera_3d()
if camera and visible:
# BoxMesh по умолчанию ориентирован правильно
# Поворачиваем полоску так, чтобы она была обращена к камере
# Используем только горизонтальный поворот (по оси Y), чтобы полоска оставалась горизонтальной
var direction_to_camera = (camera.global_position - global_position).normalized()
if direction_to_camera.length() > 0.01:
var horizontal_direction = direction_to_camera
horizontal_direction.y = 0
horizontal_direction = horizontal_direction.normalized()
if horizontal_direction.length() > 0.01:
# Поворачиваем только по оси Y
var angle = atan2(horizontal_direction.x, horizontal_direction.z)
rotation.y = angle
# Обновляем полоску после поворота (чтобы позиция была правильной)
if unit:
update_health_bar()
func _on_health_changed(new_health: float):
update_health_bar()
update_visibility()
func update_health_bar():
if not unit or not health_bar:
return
var health_percent = clamp(unit.health / unit.max_health, 0.0, 1.0)
# Масштабируем полоску здоровья в зависимости от процента
# Убеждаемся, что масштаб всегда положительный и достаточно большой для видимости
var scale_x = max(health_percent, 0.05) # Минимум 0.05 для лучшей видимости
health_bar.scale.x = scale_x
# Смещаем полоску влево, чтобы она уменьшалась справа налево
# Используем локальные координаты относительно центра
# Центр полоски должен быть в центре, поэтому смещаем на половину разницы
var offset = -(bar_width * (1.0 - health_percent)) / 2.0
health_bar.position.x = offset
# Убеждаемся, что зеленая полоска немного впереди красной для правильного отображения
health_bar.position.z = 0.01
func update_visibility():
if not unit:
visible = false
return
# Показываем полоску только если здоровье меньше 100%
var should_show = unit.health < unit.max_health
visible = should_show
if background_bar:
background_bar.visible = should_show
if health_bar:
health_bar.visible = should_show

View File

@@ -0,0 +1 @@
uid://c7bnw4xa03juj

53
scripts/hud.gd Normal file
View File

@@ -0,0 +1,53 @@
extends Control
class_name HUD
# HUD для отображения информации об игроке
@onready var health_bar: ProgressBar = $HealthBar
@onready var health_label: Label = $HealthBar/HealthLabel
@onready var dash_cooldown_bar: ProgressBar = $DashCooldownBar
@onready var dash_cooldown_label: Label = $DashCooldownBar/DashCooldownLabel
var player: Player = null
func _ready():
# Находим игрока в сцене
call_deferred("find_player")
func find_player():
var players = get_tree().get_nodes_in_group("player")
if players.size() > 0:
player = players[0] as Player
if player:
# Подключаемся к сигналам
player.health_changed.connect(_on_player_health_changed)
player.dash_cooldown_changed.connect(_on_dash_cooldown_changed)
# Обновляем отображение
update_health_display()
update_dash_cooldown_display()
func _on_player_health_changed(new_health: float):
update_health_display()
func _on_dash_cooldown_changed(remaining_time: float):
update_dash_cooldown_display()
func update_health_display():
if not player:
return
var health_percent = (player.health / player.max_health) * 100.0
health_bar.value = health_percent
health_label.text = "HP: %d / %d" % [int(player.health), int(player.max_health)]
func update_dash_cooldown_display():
if not player:
return
var cooldown_percent = (player.dash_cooldown_timer / player.dash_cooldown) * 100.0
dash_cooldown_bar.value = cooldown_percent
if player.dash_cooldown_timer > 0.0:
dash_cooldown_label.text = "Рывок: %.1f" % player.dash_cooldown_timer
else:
dash_cooldown_label.text = "Рывок: готов (Пробел)"

1
scripts/hud.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://buc8nflhbd2kx

29
scripts/main_menu.gd Normal file
View File

@@ -0,0 +1,29 @@
extends Control
# Скрипт главного меню
func _ready():
# Подключаем сигналы кнопок
var start_button = get_node_or_null("VBoxContainer/StartButton")
var settings_button = get_node_or_null("VBoxContainer/SettingsButton")
var quit_button = get_node_or_null("VBoxContainer/QuitButton")
if start_button:
start_button.pressed.connect(_on_start_button_pressed)
if settings_button:
settings_button.pressed.connect(_on_settings_button_pressed)
if quit_button:
quit_button.pressed.connect(_on_quit_button_pressed)
func _on_start_button_pressed():
# Переход к игре
get_tree().change_scene_to_file("res://scenes/main.tscn")
func _on_settings_button_pressed():
# TODO: Открыть меню настроек
print("Настройки (пока не реализовано)")
func _on_quit_button_pressed():
# Выход из игры
get_tree().quit()

1
scripts/main_menu.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://dpfj2k2b4353w

View File

@@ -0,0 +1,124 @@
extends Projectile
class_name ParabolicProjectile
# Параболический снаряд для броска
@export var projectile_gravity: float = 200
@export var arc_height: float = 15.0 # Увеличена высота параболы
var target_position: Vector3 = Vector3.ZERO
var start_position: Vector3 = Vector3.ZERO
var travel_time: float = 0.0
var total_time: float = 0.0
func _ready():
start_position = global_position
# Проверяем, что target_position установлен
if target_position == Vector3.ZERO:
# Если не установлен, используем направление вперед
target_position = start_position + Vector3(0, 0, -10.0)
target_position.y = start_position.y
# Вычисляем горизонтальное расстояние
var horizontal_distance = Vector3(start_position.x, 0, start_position.z).distance_to(Vector3(target_position.x, 0, target_position.z))
# Вычисляем правильную траекторию для попадания в цель
var horizontal_dir = (target_position - start_position)
horizontal_dir.y = 0
# Если цель слишком близко (включая клик на самого игрока), устанавливаем минимальное расстояние и направление
if horizontal_distance < 1.0:
# Если направление нулевое или очень маленькое, используем направление вперед от игрока
if horizontal_dir.length() < 0.1:
# Используем направление камеры или направление вперед
var camera = get_viewport().get_camera_3d()
if camera:
var forward = -camera.global_transform.basis.z
forward.y = 0
horizontal_dir = forward.normalized()
else:
horizontal_dir = Vector3(0, 0, -1) # Направление по умолчанию
else:
horizontal_dir = horizontal_dir.normalized()
# Устанавливаем минимальное расстояние
horizontal_distance = 1.0
# Обновляем target_position на минимальном расстоянии от стартовой позиции
target_position = start_position + horizontal_dir * horizontal_distance
target_position.y = 0.0
else:
horizontal_dir = horizontal_dir.normalized()
# Время подъема до максимальной высоты
var time_to_peak = sqrt(2 * arc_height / projectile_gravity)
# Время полета вниз (примерно такое же)
var time_to_fall = time_to_peak
# Общее время полета по вертикали
var vertical_time = time_to_peak + time_to_fall
# Горизонтальная скорость должна обеспечить попадание в цель за время полета
var horizontal_speed = horizontal_distance / vertical_time
# Начальная вертикальная скорость для достижения нужной высоты
var vertical_speed = sqrt(2 * projectile_gravity * arc_height)
# Общее время полета
total_time = vertical_time
# Устанавливаем начальную скорость
velocity = horizontal_dir * horizontal_speed + Vector3.UP * vertical_speed
# Вызываем super._ready() после установки velocity
super._ready()
# Отключаем мониторинг коллизий при полете
monitoring = false
func _physics_process(delta):
if has_hit:
return
# Если velocity не установлена, не двигаемся
if velocity == Vector3.ZERO:
return
# Проверяем время жизни
lifetime_timer -= delta
if lifetime_timer <= 0:
if is_explosive:
explode(global_position)
else:
queue_free()
return
travel_time += delta
# Сохраняем предыдущую позицию для raycast (если понадобится)
previous_position = global_position
# Применяем гравитацию
velocity.y -= projectile_gravity * delta
# Движение снаряда
global_position += velocity * delta
# Проверяем, достигли ли цели
# Проверяем по Y координате (приземление) и по времени полета
# НЕ проверяем горизонтальное расстояние, так как это может сработать сразу при близких целях
if (global_position.y <= target_position.y + 0.3) and (travel_time >= total_time * 0.5):
# Включаем коллизию только при приземлении
monitoring = true
# Устанавливаем позицию точно в целевую точку
global_position = target_position
on_hit(target_position)
return
# Дополнительная проверка по времени полета (на случай, если снаряд не достиг земли)
if travel_time >= total_time * 1.2:
monitoring = true
global_position = target_position
on_hit(target_position)
return
# Не проверяем столкновения при полете - только движение

View File

@@ -0,0 +1 @@
uid://d3c5g160kh3vv

249
scripts/player.gd Normal file
View File

@@ -0,0 +1,249 @@
extends Unit
class_name Player
# Класс игрока с управлением WASD
@export var mouse_sensitivity: float = 0.003
@export var dash_speed: float = 25.0 # Скорость рывка
@export var dash_duration: float = 0.2 # Длительность рывка
@export var dash_cooldown: float = 1.0 # Перезарядка рывка
@export var straight_projectile_scene: PackedScene = null # Сцена прямого снаряда
@export var parabolic_projectile_scene: PackedScene = null # Сцена параболического снаряда
@export var projectile_damage: float = 20.0
@export var projectile_speed: float = 20.0
@export var explosion_radius: float = 3.0
var camera_angle: float = 0.0
var is_dashing: bool = false
var dash_timer: float = 0.0
var dash_cooldown_timer: float = 0.0
var dash_direction: Vector3 = Vector3.ZERO
signal dash_started
signal dash_ended
signal dash_cooldown_changed(remaining_time: float)
func _ready():
super._ready()
# Игрок белый
var mesh_instance = get_node_or_null("MeshInstance3D")
if mesh_instance:
var material = StandardMaterial3D.new()
material.albedo_color = Color.WHITE
mesh_instance.material_override = material
func _input(event):
# Обработка рывка
if event.is_action_pressed("dash") and dash_cooldown_timer <= 0.0 and not is_dashing:
perform_dash()
# Обработка стрельбы (левая кнопка мыши)
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
shoot_straight_projectile()
# Обработка броска (правая кнопка мыши)
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
throw_parabolic_projectile()
func _physics_process(delta):
# Обновляем таймеры
var previous_cooldown = dash_cooldown_timer
if dash_cooldown_timer > 0.0:
dash_cooldown_timer -= delta
dash_cooldown_timer = max(0.0, dash_cooldown_timer)
# Эмитим сигнал только если значение изменилось
if abs(previous_cooldown - dash_cooldown_timer) > 0.01:
dash_cooldown_changed.emit(dash_cooldown_timer)
# Поворачиваем игрока в сторону курсора мыши
update_rotation_to_mouse()
# Получаем направление движения от клавиатуры
var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
var direction = Vector3(input_dir.x, 0, input_dir.y)
# Обработка рывка
if is_dashing:
dash_timer -= delta
if dash_timer <= 0.0:
end_dash()
else:
# Во время рывка двигаемся с увеличенной скоростью
velocity.x = dash_direction.x * dash_speed
velocity.z = dash_direction.z * dash_speed
else:
# Обычное движение
if direction.length() > 0:
velocity.x = direction.x * speed
velocity.z = direction.z * speed
else:
velocity.x = move_toward(velocity.x, 0, speed)
velocity.z = move_toward(velocity.z, 0, speed)
# Применяем гравитацию
if not is_on_floor():
velocity.y -= 9.8 * delta
else:
velocity.y = 0
super._physics_process(delta)
func update_rotation_to_mouse():
# Получаем камеру из сцены
var camera = get_viewport().get_camera_3d()
if not camera:
return
# Получаем позицию курсора мыши на экране
var mouse_pos = get_viewport().get_mouse_position()
# Создаем луч от камеры через позицию курсора
var from = camera.project_ray_origin(mouse_pos)
var to = from + camera.project_ray_normal(mouse_pos) * 1000.0
# Создаем запрос для raycast
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(from, to)
query.collision_mask = 1 # Слой земли
# Выполняем raycast
var result = space_state.intersect_ray(query)
if result:
# Получаем точку пересечения
var target_point = result.position
# Поворачиваем игрока в сторону курсора (только по оси Y)
var look_direction = (target_point - global_position)
look_direction.y = 0 # Игнорируем вертикальную составляющую
if look_direction.length() > 0.1:
look_at(global_position + look_direction, Vector3.UP)
func perform_dash():
# Получаем направление движения
var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
var direction = Vector3(input_dir.x, 0, input_dir.y)
# Если игрок не двигается, рывок вперед (по направлению взгляда или вверх)
if direction.length() < 0.1:
direction = Vector3(0, 0, -1) # Вперед по умолчанию
# Нормализуем направление
dash_direction = direction.normalized()
# Запускаем рывок
is_dashing = true
dash_timer = dash_duration
dash_cooldown_timer = dash_cooldown
dash_started.emit()
func end_dash():
is_dashing = false
dash_timer = 0.0
dash_ended.emit()
func shoot_straight_projectile():
if not straight_projectile_scene:
return
# Получаем точку прицеливания через raycast
var target_point = get_mouse_target_point()
if not target_point:
return
# Создаем снаряд
var projectile = straight_projectile_scene.instantiate() as StraightProjectile
if not projectile:
return
# Устанавливаем параметры
projectile.owner_unit = self
projectile.damage = projectile_damage
projectile.speed = projectile_speed
projectile.is_explosive = false
# Направление полета
var shoot_position = global_position + Vector3.UP * 1.0 # Немного выше игрока
var direction = (target_point - shoot_position).normalized()
projectile.direction = direction
# Позиция и поворот
projectile.global_position = shoot_position
projectile.look_at(shoot_position + direction, Vector3.UP)
# Добавляем в сцену
var scene_root = get_tree().current_scene
if not scene_root:
scene_root = get_tree().root.get_child(get_tree().root.get_child_count() - 1)
scene_root.add_child(projectile)
func throw_parabolic_projectile():
if not parabolic_projectile_scene:
# Пробуем загрузить вручную
parabolic_projectile_scene = load("res://scenes/parabolic_projectile.tscn") as PackedScene
if not parabolic_projectile_scene:
return
# Получаем точку прицеливания через raycast (на плоскости)
var target_point = get_mouse_target_point()
# Убеждаемся, что целевая точка на уровне земли (Y = 0)
# Если кликнули на противника или другой объект, используем его X и Z, но Y ставим на землю
target_point.y = 0.0
# Позиция запуска
var throw_position = global_position + Vector3.UP * 1.0
# Создаем снаряд
var projectile = parabolic_projectile_scene.instantiate()
if not projectile:
return
# Устанавливаем параметры ДО добавления в сцену
projectile.owner_unit = self
projectile.damage = projectile_damage
projectile.speed = projectile_speed
projectile.is_explosive = true
projectile.explosion_radius = explosion_radius
# Устанавливаем позицию
projectile.global_position = throw_position
# Целевая позиция (на уровне земли) - устанавливаем ДО _ready()
projectile.target_position = target_point
# Добавляем в сцену (после установки всех параметров)
var scene_root = get_tree().current_scene
if not scene_root:
scene_root = get_tree().root.get_child(get_tree().root.get_child_count() - 1)
scene_root.add_child(projectile)
func get_mouse_target_point() -> Vector3:
# Получаем камеру
var camera = get_viewport().get_camera_3d()
if not camera:
return global_position + transform.basis.z * -10.0
# Получаем позицию курсора мыши
var mouse_pos = get_viewport().get_mouse_position()
# Создаем луч от камеры
var from = camera.project_ray_origin(mouse_pos)
var ray_dir = camera.project_ray_normal(mouse_pos)
var to = from + ray_dir * 1000.0
# Raycast
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(from, to)
query.collision_mask = 1
var result = space_state.intersect_ray(query)
if result:
return result.position
# Если не попали ни во что, вычисляем точку на плоскости земли
if ray_dir.y < -0.01: # Луч направлен вниз
var t = -from.y / ray_dir.y
return from + ray_dir * t
# Если луч не направлен вниз, используем точку перед игроком
return global_position + transform.basis.z * -10.0

1
scripts/player.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bt4nfjnngh21i

182
scripts/projectile.gd Normal file
View File

@@ -0,0 +1,182 @@
extends Area3D
class_name Projectile
# Базовый класс для снарядов
@export var speed: float = 120.0
@export var damage: float = 20.0
@export var lifetime: float = 5.0
@export var is_explosive: bool = false
@export var explosion_radius: float = 3.0
var direction: Vector3 = Vector3.ZERO
var velocity: Vector3 = Vector3.ZERO
var owner_unit: Node3D = null
var lifetime_timer: float = 0.0
var has_hit: bool = false
var previous_position: Vector3 = Vector3.ZERO
signal hit(target: Node3D, position: Vector3)
signal exploded(position: Vector3)
func _ready():
lifetime_timer = lifetime
previous_position = global_position
# Настраиваем Area3D для обнаружения целей
body_entered.connect(_on_body_entered)
# Отключаем мониторинг по умолчанию для параболических
monitoring = true
monitorable = false
func _physics_process(delta):
if has_hit:
return
lifetime_timer -= delta
if lifetime_timer <= 0:
if is_explosive:
explode(global_position)
else:
queue_free()
return
# Сохраняем предыдущую позицию для raycast
previous_position = global_position
# Движение снаряда
global_position += velocity * delta
# Проверка столкновений через raycast
check_collisions_raycast()
func check_collisions_raycast():
# Используем raycast для проверки столкновений
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(previous_position, global_position)
query.exclude = [self, owner_unit] if owner_unit else [self]
query.collision_mask = 1 # Только слой земли и объектов
var result = space_state.intersect_ray(query)
if result:
var collider = result.collider
var hit_position = result.position
# Если это враг, наносим урон
if collider.has_method("take_damage"):
collider.take_damage(damage)
hit.emit(collider, hit_position)
on_hit(hit_position)
func _on_body_entered(body: Node3D):
# Дополнительная проверка через Area3D
if has_hit:
return
if body == owner_unit:
return
if body is Projectile:
return
if body.has_method("take_damage"):
body.take_damage(damage)
hit.emit(body, global_position)
on_hit(global_position)
func on_hit(position: Vector3):
if has_hit:
return
has_hit = true
velocity = Vector3.ZERO
# Останавливаем частицы
var fire_particles = get_node_or_null("FireParticles")
var trail_particles = get_node_or_null("TrailParticles")
if fire_particles:
fire_particles.emitting = false
if trail_particles:
trail_particles.emitting = false
if is_explosive:
explode(position)
else:
# Простое попадание
hit.emit(null, position)
# Воспроизводим анимацию попадания
play_hit_animation()
# Удаляем через небольшую задержку
await get_tree().create_timer(0.5).timeout
queue_free()
func explode(position: Vector3):
# Находим все цели в радиусе взрыва
var space_state = get_world_3d().direct_space_state
var query = PhysicsShapeQueryParameters3D.new()
var sphere_shape = SphereShape3D.new()
sphere_shape.radius = explosion_radius
query.shape = sphere_shape
query.transform.origin = position
query.collision_mask = 1
var results = space_state.intersect_shape(query)
# Наносим урон всем в радиусе
for result in results:
var collider = result.collider
if collider != owner_unit and collider.has_method("take_damage"):
var distance = position.distance_to(collider.global_position)
var damage_multiplier = 1.0 - (distance / explosion_radius)
collider.take_damage(damage * damage_multiplier)
exploded.emit(position)
play_explosion_animation()
# Ждем, пока частицы взрыва отыграют (0.5 секунды)
await get_tree().create_timer(0.5).timeout
queue_free()
func play_hit_animation():
# Создаем простой визуальный эффект попадания
var mesh = get_node_or_null("MeshInstance3D")
if mesh:
# Увеличиваем размер и скрываем
var tween = create_tween()
tween.tween_property(mesh, "scale", Vector3(2.0, 2.0, 2.0), 0.2)
tween.tween_callback(func(): mesh.visible = false)
func play_explosion_animation():
# Скрываем основной меш
var mesh = get_node_or_null("MeshInstance3D")
if mesh:
mesh.visible = false
# Останавливаем частицы полета
var fire_particles = get_node_or_null("FireParticles")
var trail_particles = get_node_or_null("TrailParticles")
if fire_particles:
fire_particles.emitting = false
if trail_particles:
trail_particles.emitting = false
# Воспроизводим частицы взрыва
var explosion_particles = get_node_or_null("ExplosionParticles")
var explosion_core = get_node_or_null("ExplosionCore")
var explosion_smoke = get_node_or_null("ExplosionSmoke")
# Масштабируем узлы частиц в зависимости от радиуса взрыва
# Это создаст визуальный эффект взрыва нужного размера
var scale_factor = explosion_radius / 3.0 # 3.0 - базовый радиус взрыва
if explosion_particles:
explosion_particles.scale = Vector3.ONE * scale_factor
explosion_particles.restart()
explosion_particles.emitting = true
if explosion_core:
explosion_core.scale = Vector3.ONE * scale_factor * 0.6
explosion_core.restart()
explosion_core.emitting = true
if explosion_smoke:
explosion_smoke.scale = Vector3.ONE * scale_factor * 1.2
explosion_smoke.restart()
explosion_smoke.emitting = true

View File

@@ -0,0 +1 @@
uid://bg1bpdny7eta6

View File

@@ -0,0 +1,13 @@
extends Projectile
class_name StraightProjectile
# Прямой снаряд для стрельбы
func _ready():
super._ready()
# Устанавливаем скорость только горизонтально (параллельно земле)
var horizontal_dir = direction
horizontal_dir.y = 0
horizontal_dir = horizontal_dir.normalized()
velocity = horizontal_dir * speed

View File

@@ -0,0 +1 @@
uid://crsltf0vxq1xd

30
scripts/unit.gd Normal file
View File

@@ -0,0 +1,30 @@
extends CharacterBody3D
class_name Unit
# Базовый класс для всех юнитов (игрок и противники)
@export var speed: float = 5.0
@export var health: float = 100.0
@export var max_health: float = 100.0
signal health_changed(new_health: float)
signal died
func _ready():
health = max_health
func take_damage(amount: float):
health -= amount
health = max(0, health)
health_changed.emit(health)
if health <= 0:
die()
func die():
died.emit()
queue_free()
func _physics_process(delta):
# Базовая физика движения
move_and_slide()

1
scripts/unit.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://cok6ep4d2e6mf