Compare commits
46 Commits
c8340efc95
...
master
Author | SHA1 | Date | |
---|---|---|---|
356897045f | |||
1e1f8cebe8
|
|||
b21ad67cfc
|
|||
4fffa8784a
|
|||
dc3f8c94f8 | |||
f06bf17757
|
|||
d74831df56
|
|||
2bd83504f2
|
|||
3f21aef4eb
|
|||
6fff2dd9a3
|
|||
0dada63709
|
|||
d862974747
|
|||
f53d91a9eb
|
|||
d57a59e9fe
|
|||
950d177936
|
|||
40d6162b95
|
|||
7311b52fad
|
|||
b2129d9aa6
|
|||
892b37572f
|
|||
7aedea8206
|
|||
7cff7b62e9
|
|||
0d8356957d
|
|||
9044338662
|
|||
0d8846fb83
|
|||
76cb23b5db
|
|||
45b3b1c53e
|
|||
1d75a850bf
|
|||
3e12c386c2
|
|||
9240413da7
|
|||
66822cbf9e
|
|||
f166f138a3
|
|||
699b758afb
|
|||
85d9486d2f
|
|||
294c88a830
|
|||
a2dfc2d65d
|
|||
a1cc8042ea
|
|||
5e8301a04c
|
|||
36dfcee36d
|
|||
0046865101
|
|||
f860ddd669
|
|||
80d00d0c26
|
|||
f0f717e00e
|
|||
83e2067fea
|
|||
a0d121fadc
|
|||
8f02850a73
|
|||
632643a130
|
16
.github/workflows/flake-check.yaml
vendored
Normal file
16
.github/workflows/flake-check.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: flake-check
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [ ubuntu-latest, homelab ]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- run: nix flake check
|
||||
|
34
README.md
34
README.md
@@ -2,20 +2,40 @@
|
||||
|
||||
Not AI-slop, but just regular slop survivors game.
|
||||
|
||||
# Build
|
||||
## Run
|
||||
|
||||
Build using nix:
|
||||
Can be run easily with either docker, or nix.
|
||||
|
||||
### Web-version using docker
|
||||
|
||||
Run the following command to run a web-server using docker.
|
||||
|
||||
In your browser, navigate to http://localhost:8080 to play.
|
||||
|
||||
```console
|
||||
$ nix build .#\slopvivors
|
||||
$ docker run --rm -it -p 8080:8080 git.t-juice.club/torjus/slopvivors:latest
|
||||
```
|
||||
|
||||
# Development
|
||||
### Linux version using nix
|
||||
|
||||
Desktop version can be run using nix flake.
|
||||
|
||||
```console
|
||||
$ nix run git+https://git.t-juice.club/torjus/slopvivors#slopvivors
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Use nix to setup development environment. Contains godot and other useful tools.
|
||||
|
||||
```console
|
||||
$ nix develop
|
||||
$ godot project.godot
|
||||
```shell
|
||||
nix develop
|
||||
godot project.godot
|
||||
|
||||
# slop-format runs gdformat on gdscript files, run before commit
|
||||
slop-format
|
||||
|
||||
# slop-push pushes image with latest-tag to git.t-juice.club
|
||||
slop-push
|
||||
```
|
||||
|
||||
|
Binary file not shown.
9
assets/shaders/base_color_tint.gdshader
Normal file
9
assets/shaders/base_color_tint.gdshader
Normal file
@@ -0,0 +1,9 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform vec4 base_color : source_color = vec4(1.0, 0.2, 0.2, 1.0); // tint color
|
||||
|
||||
void fragment() {
|
||||
vec4 tex = texture(TEXTURE, UV);
|
||||
|
||||
COLOR = vec4(base_color.rgb * tex.r, tex.a);
|
||||
}
|
1
assets/shaders/base_color_tint.gdshader.uid
Normal file
1
assets/shaders/base_color_tint.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cf48pgfl308o3
|
8
assets/shaders/hit_flash.gdshader
Normal file
8
assets/shaders/hit_flash.gdshader
Normal file
@@ -0,0 +1,8 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform float flash_amount : hint_range(0.0, 1.0) = 0.0; // 0 = normal, 1 = full white
|
||||
|
||||
void fragment() {
|
||||
vec4 tex_color = texture(TEXTURE, UV);
|
||||
COLOR = mix(tex_color, vec4(1.0, 1.0, 1.0, tex_color.a), flash_amount);
|
||||
}
|
1
assets/shaders/hit_flash.gdshader.uid
Normal file
1
assets/shaders/hit_flash.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://p7evv1wldgsa
|
39
assets/shaders/screen_low_hp.gdshader
Normal file
39
assets/shaders/screen_low_hp.gdshader
Normal file
@@ -0,0 +1,39 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform sampler2D screen_texture : hint_screen_texture;
|
||||
uniform float damage_amount : hint_range(0.0, 1.0) = 0.0;
|
||||
uniform float noise_strength : hint_range(0.0, 1.0) = 0.3;
|
||||
uniform float pulse_strength : hint_range(0.0, 1.0) = 0.2;
|
||||
uniform float time;
|
||||
|
||||
// TODO: uniform vec2 player_uv; // use this to calculate player offset to camera
|
||||
|
||||
float random(vec2 uv) {
|
||||
return fract(sin(dot(uv.xy ,vec2(12.9898,78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
// Distance from screen center
|
||||
vec2 center = vec2(0.5, 0.5);
|
||||
float dist = distance(SCREEN_UV, center);
|
||||
float edge_factor = smoothstep(0.4, 0.7, dist); // stronger at edges
|
||||
float edge_factor_gray = smoothstep(0.0, 1.0-damage_amount, dist);
|
||||
|
||||
// Noise flicker
|
||||
float n = random(SCREEN_UV * 200.0 + time * 2.0);
|
||||
float noise = (n - 0.5) * noise_strength;
|
||||
|
||||
// Pulsing intensity
|
||||
float pulse = sin(time * 5.0) * pulse_strength;
|
||||
|
||||
// Final intensity
|
||||
float final_amount = damage_amount * edge_factor + noise + pulse;
|
||||
final_amount = clamp(final_amount, 0.0, 1.0);
|
||||
|
||||
vec4 base = texture(screen_texture, SCREEN_UV);
|
||||
float gray = dot(base.rgb, vec3(0.299, 0.587, 0.114));
|
||||
base.rgb = mix(base.rgb, vec3(gray), edge_factor_gray * damage_amount);
|
||||
|
||||
vec4 red_tint = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
COLOR = mix(base, red_tint, final_amount);
|
||||
}
|
1
assets/shaders/screen_low_hp.gdshader.uid
Normal file
1
assets/shaders/screen_low_hp.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dnwiwj1ak1bu5
|
@@ -3,19 +3,28 @@ uniform vec4 water_color : source_color = vec4(0.0, 0.0, 1.0, 1.0);
|
||||
uniform sampler2D noise_tex; // noise texture
|
||||
uniform vec2 noise_speed = vec2(0.05, 0.01);
|
||||
uniform float noise_strength = 0.2; // how much to lighten/darken
|
||||
uniform float pixel_size = 0.005;
|
||||
|
||||
void fragment() {
|
||||
vec2 uv = UV;
|
||||
vec4 base_tex = texture(TEXTURE, uv);
|
||||
float is_water = step(0.8, 1.0 - distance(base_tex.rgb, water_color.rgb));
|
||||
|
||||
vec2 snapped_uv = floor(SCREEN_UV / pixel_size) * pixel_size;
|
||||
// Scroll UVs for noise animation
|
||||
vec2 noise_uv = SCREEN_UV + noise_speed * TIME;
|
||||
//vec2 noise_uv = SCREEN_UV + noise_speed * TIME;
|
||||
vec2 noise_uv = snapped_uv + noise_speed * TIME;
|
||||
vec4 noise_sample = texture(noise_tex, fract(noise_uv));
|
||||
|
||||
float n = noise_sample.r;
|
||||
n = pow(n, 3.0) * 4.0;
|
||||
n = clamp(n,0.0, 1.0);
|
||||
|
||||
vec4 noisy_color = water_color.rgba;
|
||||
noisy_color += vec4(0.1* (noise_sample.r - 0.5), 0.05 * (noise_sample.r - 0.5), 0.0, 1.0);
|
||||
// Use noise (0..1) to brighten/darken the water color
|
||||
float brightness = (noise_sample.r - 0.5) * 2.0 * noise_strength;
|
||||
vec4 animated_water = water_color + vec4(vec3(brightness), 0.0);
|
||||
float brightness = (n - 0.5) * 4.0 * noise_strength;
|
||||
vec4 animated_water = noisy_color + vec4(vec3(brightness), 0.0);
|
||||
|
||||
|
||||
COLOR = mix(base_tex, animated_water, is_water);
|
||||
|
24
assets/shaders/xp_orb.gdshader
Normal file
24
assets/shaders/xp_orb.gdshader
Normal file
@@ -0,0 +1,24 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform vec4 orb_color : source_color = vec4(0.2, 1.0, 0.5, 1.0); // base orb color
|
||||
uniform float glow_strength : hint_range(0.0, 2.0) = 1.2;
|
||||
uniform float pulse_speed : hint_range(0.0, 10.0) = 2.0;
|
||||
|
||||
void fragment() {
|
||||
// Centered UV (0,0 in middle, -1..1 range)
|
||||
vec2 uv = (UV - vec2(0.5)) * 2.0;
|
||||
float dist = length(uv);
|
||||
|
||||
// Soft circular mask
|
||||
float circle = smoothstep(0.8, 0.0, dist);
|
||||
|
||||
// Glow falloff
|
||||
float glow = smoothstep(0.5, 0.0, dist) * glow_strength;
|
||||
|
||||
// Pulsing
|
||||
float pulse = 0.5 + 0.5 * sin(TIME * pulse_speed);
|
||||
|
||||
vec3 color = orb_color.rgb * (circle + glow * pulse);
|
||||
|
||||
COLOR = vec4(color, circle); // alpha fades at edges
|
||||
}
|
1
assets/shaders/xp_orb.gdshader.uid
Normal file
1
assets/shaders/xp_orb.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ckjyt0mt0ngk
|
BIN
assets/sprites/puddle_1.aseprite
(Stored with Git LFS)
Normal file
BIN
assets/sprites/puddle_1.aseprite
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/sprites/puddle_1.png
(Stored with Git LFS)
Normal file
BIN
assets/sprites/puddle_1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
34
assets/sprites/puddle_1.png.import
Normal file
34
assets/sprites/puddle_1.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c5t4it4if0s6g"
|
||||
path="res://.godot/imported/puddle_1.png-28d62b8b3cc5647c5219303699bcce62.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/puddle_1.png"
|
||||
dest_files=["res://.godot/imported/puddle_1.png-28d62b8b3cc5647c5219303699bcce62.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/sprites/slime_small grayscale.aseprite
(Stored with Git LFS)
Normal file
BIN
assets/sprites/slime_small grayscale.aseprite
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/sprites/slime_small grayscale.png
(Stored with Git LFS)
Normal file
BIN
assets/sprites/slime_small grayscale.png
(Stored with Git LFS)
Normal file
Binary file not shown.
34
assets/sprites/slime_small grayscale.png.import
Normal file
34
assets/sprites/slime_small grayscale.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ctob5me7woid8"
|
||||
path="res://.godot/imported/slime_small grayscale.png-b5ea2536903e774dce2837ff19f6ad76.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/slime_small grayscale.png"
|
||||
dest_files=["res://.godot/imported/slime_small grayscale.png-b5ea2536903e774dce2837ff19f6ad76.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/sprites/slime_small.aseprite
(Stored with Git LFS)
Normal file
BIN
assets/sprites/slime_small.aseprite
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/sprites/slime_small.png
(Stored with Git LFS)
Normal file
BIN
assets/sprites/slime_small.png
(Stored with Git LFS)
Normal file
Binary file not shown.
34
assets/sprites/slime_small.png.import
Normal file
34
assets/sprites/slime_small.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bfj2h2jp1w60p"
|
||||
path="res://.godot/imported/slime_small.png-a01d407ba84c2dd70ce8b2ef5c142cae.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/slime_small.png"
|
||||
dest_files=["res://.godot/imported/slime_small.png-a01d407ba84c2dd70ce8b2ef5c142cae.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/sprites/small_bat.aseprite
(Stored with Git LFS)
Normal file
BIN
assets/sprites/small_bat.aseprite
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/sprites/small_bat.png
(Stored with Git LFS)
Normal file
BIN
assets/sprites/small_bat.png
(Stored with Git LFS)
Normal file
Binary file not shown.
34
assets/sprites/small_bat.png.import
Normal file
34
assets/sprites/small_bat.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://chtjfq3bmonhj"
|
||||
path="res://.godot/imported/small_bat.png-8cac10440ad9b69c99d6007471ff8d06.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/small_bat.png"
|
||||
dest_files=["res://.godot/imported/small_bat.png-8cac10440ad9b69c99d6007471ff8d06.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/sprites/small_bleed_icon.aseprite
(Stored with Git LFS)
Normal file
BIN
assets/sprites/small_bleed_icon.aseprite
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/sprites/small_bleed_icon.png
(Stored with Git LFS)
Normal file
BIN
assets/sprites/small_bleed_icon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
34
assets/sprites/small_bleed_icon.png.import
Normal file
34
assets/sprites/small_bleed_icon.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c856sh6vk5lqa"
|
||||
path="res://.godot/imported/small_bleed_icon.png-472f2c7cb608bb835a947b6c2d78acf7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/small_bleed_icon.png"
|
||||
dest_files=["res://.godot/imported/small_bleed_icon.png-472f2c7cb608bb835a947b6c2d78acf7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
@@ -3,7 +3,7 @@
|
||||
name="Linux"
|
||||
platform="Linux"
|
||||
runnable=true
|
||||
advanced_options=false
|
||||
advanced_options=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
@@ -39,3 +39,47 @@ unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
|
||||
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
|
||||
kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
|
||||
rm -rf \"{temp_dir}\""
|
||||
|
||||
[preset.1]
|
||||
|
||||
name="Web"
|
||||
platform="Web"
|
||||
runnable=true
|
||||
advanced_options=false
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path=""
|
||||
patches=PackedStringArray()
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.1.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
variant/extensions_support=false
|
||||
variant/thread_support=false
|
||||
vram_texture_compression/for_desktop=true
|
||||
vram_texture_compression/for_mobile=false
|
||||
html/export_icon=true
|
||||
html/custom_html_shell=""
|
||||
html/head_include=""
|
||||
html/canvas_resize_policy=2
|
||||
html/focus_canvas_on_start=true
|
||||
html/experimental_virtual_keyboard=false
|
||||
progressive_web_app/enabled=true
|
||||
progressive_web_app/ensure_cross_origin_isolation_headers=true
|
||||
progressive_web_app/offline_page=""
|
||||
progressive_web_app/display=1
|
||||
progressive_web_app/orientation=0
|
||||
progressive_web_app/icon_144x144=""
|
||||
progressive_web_app/icon_180x180=""
|
||||
progressive_web_app/icon_512x512=""
|
||||
progressive_web_app/background_color=Color(0, 0, 0, 1)
|
||||
|
6
flake.lock
generated
6
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1755186698,
|
||||
"narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=",
|
||||
"lastModified": 1755615617,
|
||||
"narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c",
|
||||
"rev": "20075955deac2583bb12f07151c2df830ef346b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
139
flake.nix
139
flake.nix
@@ -30,23 +30,56 @@
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
godot
|
||||
gdtoolkit_4
|
||||
krita
|
||||
gimp
|
||||
pixelorama
|
||||
aseprite
|
||||
gdtoolkit_4
|
||||
gimp
|
||||
godot
|
||||
krita
|
||||
pixelorama
|
||||
skopeo
|
||||
];
|
||||
shellHook = ''
|
||||
alias slop-format='find . -name "*.gd" | xargs gdformat'
|
||||
alias slop-push='nix build .#\slopvivors_docker && skopeo copy docker-archive:result docker://git.t-juice.club/torjus/slopvivors:latest'
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
checks = forAllSystems (
|
||||
{ pkgs }:
|
||||
{
|
||||
gdformat = pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "gdformat-check";
|
||||
src = pkgs.lib.sources.sourceFilesBySuffices (pkgs.lib.cleanSource ./.) [ ".gd" ];
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
gdtoolkit_4
|
||||
];
|
||||
|
||||
dontBuild = true;
|
||||
doCheck = true;
|
||||
|
||||
checkPhase = ''
|
||||
export HOME=$(mktemp -d)
|
||||
find . -name "*.gd" -print0 | xargs -0 gdformat --check
|
||||
echo "All .gd files are properly formatted"
|
||||
'';
|
||||
installPhase = "mkdir $out";
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
packages = forAllSystems (
|
||||
{ pkgs }:
|
||||
let
|
||||
project_file = builtins.readFile ./project.godot;
|
||||
version = builtins.head (builtins.match ".*version=\"([0-9.]+)\".*" project_file);
|
||||
in
|
||||
{
|
||||
slopvivors = pkgs.stdenv.mkDerivation {
|
||||
pname = "slopvivors";
|
||||
version = "0.1.0";
|
||||
version = version;
|
||||
src = ./.;
|
||||
|
||||
strictDeps = true;
|
||||
@@ -68,13 +101,105 @@
|
||||
runHook postBuild
|
||||
'';
|
||||
installPhase = ''
|
||||
find .
|
||||
install -D -m 755 -t $out/libexec ./build/slopvivors
|
||||
install -D -m 644 -t $out/libexec ./build/slopvivors.pck
|
||||
install -d -m 755 $out/bin
|
||||
ln -s $out/libexec/slopvivors $out/bin/slopvivors
|
||||
'';
|
||||
};
|
||||
slopvivors_web_files = pkgs.stdenv.mkDerivation {
|
||||
pname = "slopvivors-web-files";
|
||||
version = version;
|
||||
src = ./.;
|
||||
|
||||
strictDeps = true;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
godot
|
||||
godot-export-templates-bin
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
export HOME=$(mktemp -d)
|
||||
mkdir -p $HOME/.local/share/godot/
|
||||
ln -s ${pkgs.godot-export-templates-bin}/share/godot/export_templates "$HOME/.local/share/godot/"
|
||||
mkdir -p build
|
||||
|
||||
godot --headless --export-release Web ./build/slopvivors
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p "$out"
|
||||
cp ./build/* "$out"
|
||||
mv ./build/slopvivors "$out"/index.html
|
||||
'';
|
||||
};
|
||||
slopvivors_web = pkgs.buildGoModule {
|
||||
pname = "slopvivors-web";
|
||||
version = version;
|
||||
vendorHash = null;
|
||||
src = self.packages.${pkgs.system}.slopvivors_web_files;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
go
|
||||
];
|
||||
|
||||
prePatch = ''
|
||||
mkdir -p slopvivors_web_files
|
||||
cp "${self.packages.${pkgs.system}.slopvivors_web_files}"/* slopvivors_web_files
|
||||
cat > main.go <<EOF
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
//go:embed slopvivors_web_files/*
|
||||
var webFiles embed.FS
|
||||
|
||||
|
||||
func main() {
|
||||
rootDir, err := fs.Sub(webFiles, "slopvivors_web_files")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
http.Handle("/", http.FileServer(http.FS(rootDir)))
|
||||
fmt.Printf("Serving on :8080\n")
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
EOF
|
||||
'';
|
||||
postPatch = ''
|
||||
go mod init slopvivors-web
|
||||
'';
|
||||
};
|
||||
slopvivors_docker = pkgs.dockerTools.buildLayeredImage {
|
||||
name = "slopvivors-docker";
|
||||
tag = version;
|
||||
created = "now";
|
||||
contents = [
|
||||
pkgs.busybox
|
||||
pkgs.caddy
|
||||
self.packages.${pkgs.system}.slopvivors_web_files
|
||||
];
|
||||
config = {
|
||||
Cmd = [
|
||||
"caddy"
|
||||
"file-server"
|
||||
"-a"
|
||||
"--listen"
|
||||
":8080"
|
||||
];
|
||||
WorkingDir = "${self.packages.${pkgs.system}.slopvivors_web_files}/";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@@ -12,6 +12,10 @@ signal sig_debug_stats_set(key: String, value: String)
|
||||
signal sig_stop_spawning(value: bool)
|
||||
@warning_ignore("unused_signal")
|
||||
signal sig_set_spawn_rate(value: float)
|
||||
@warning_ignore("unused_signal")
|
||||
signal sig_on_player_hp_change(hp: float, max_hp: float)
|
||||
@warning_ignore("unused_signal")
|
||||
signal sig_toggle_low_hp_shader(toggled_on: float)
|
||||
|
||||
const GROUP_ENEMY = "enemy"
|
||||
const GROUP_DAMAGEABLE = "damagable"
|
||||
@@ -20,7 +24,7 @@ const GROUP_XP_ORB = "xp_orb"
|
||||
const GROUP_PICKUP = "pickup"
|
||||
const GROUP_PROJ_MANAGER = "proj_manager"
|
||||
|
||||
enum ModRarity { LEGENDARY, EPIC, RARE, NORMAL }
|
||||
enum Rarity { LEGENDARY, EPIC, RARE, NORMAL }
|
||||
|
||||
const placeholder_tex = preload("res://assets/sprites/64x64_placeholder.tres")
|
||||
|
||||
@@ -28,7 +32,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_health_small",
|
||||
"name": "+10 Health",
|
||||
"rarity": ModRarity.NORMAL,
|
||||
"rarity": Rarity.NORMAL,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Adds 10 flat health.",
|
||||
"weight": 100,
|
||||
@@ -39,7 +43,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_health_med",
|
||||
"name": "+25 Health",
|
||||
"rarity": ModRarity.RARE,
|
||||
"rarity": Rarity.RARE,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Adds 25 flat health.",
|
||||
"weight": 50,
|
||||
@@ -50,7 +54,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_health_large",
|
||||
"name": "+50 Health",
|
||||
"rarity": ModRarity.EPIC,
|
||||
"rarity": Rarity.EPIC,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Adds 50 flat health.",
|
||||
"weight": 10,
|
||||
@@ -61,7 +65,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_crit_small",
|
||||
"name": "2.5% More Critical Chance",
|
||||
"rarity": ModRarity.RARE,
|
||||
"rarity": Rarity.RARE,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Gives 2.5% more base critical hit chance",
|
||||
"weight": 50,
|
||||
@@ -72,7 +76,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_crit_large",
|
||||
"name": "5% More Critical Chance",
|
||||
"rarity": ModRarity.EPIC,
|
||||
"rarity": Rarity.EPIC,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Gives 5% more base critical hit chance",
|
||||
"weight": 10,
|
||||
@@ -83,6 +87,17 @@ var MOD_CHOICES = [
|
||||
]
|
||||
|
||||
|
||||
func rarity_to_color(rarity: Rarity) -> Color:
|
||||
match rarity:
|
||||
Rarity.RARE:
|
||||
return Color.YELLOW
|
||||
Rarity.EPIC:
|
||||
return Color.BLUE_VIOLET
|
||||
Rarity.LEGENDARY:
|
||||
return Color.DARK_ORANGE
|
||||
return Color.WHITE
|
||||
|
||||
|
||||
func _draw_random_choice(fortune: float = 1.0) -> Dictionary:
|
||||
var total_weight: int = 0
|
||||
for choice in MOD_CHOICES:
|
||||
|
@@ -11,6 +11,7 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="Slopvivors"
|
||||
config/version="0.2.0"
|
||||
run/main_scene="uid://bjg50n7aab3ng"
|
||||
config/features=PackedStringArray("4.4", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
@@ -67,6 +68,11 @@ click={
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(90, 11),"global_position":Vector2(99, 59),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
active={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[layer_names]
|
||||
|
||||
|
8
scenes/enemies/effects/enemy_effect_base.gd
Normal file
8
scenes/enemies/effects/enemy_effect_base.gd
Normal file
@@ -0,0 +1,8 @@
|
||||
class_name EnemyEffectBase
|
||||
extends Resource
|
||||
|
||||
var enemy: EnemyBase
|
||||
|
||||
|
||||
func apply(enemy: EnemyBase) -> void:
|
||||
push_error("%s does not implement apply" % self)
|
1
scenes/enemies/effects/enemy_effect_base.gd.uid
Normal file
1
scenes/enemies/effects/enemy_effect_base.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dg3yshxb4vdiu
|
33
scenes/enemies/effects/enemy_effect_bleed.gd
Normal file
33
scenes/enemies/effects/enemy_effect_bleed.gd
Normal file
@@ -0,0 +1,33 @@
|
||||
class_name EnemyEffectBleed
|
||||
extends EnemyEffectBase
|
||||
|
||||
var damage: float
|
||||
var duration: float
|
||||
var tick_rate: float = 1.0
|
||||
|
||||
var _timer: Timer
|
||||
var _remaining_ticks: int = 5
|
||||
var _enemy: EnemyBase
|
||||
|
||||
const PUDDLE = preload("res://scenes/puddle.tscn")
|
||||
|
||||
|
||||
func _init(enemy: EnemyBase, bleed_damage: float, duration: float):
|
||||
damage = bleed_damage
|
||||
_timer = Timer.new()
|
||||
|
||||
|
||||
func apply(enemy: EnemyBase) -> void:
|
||||
enemy.effects.append(self)
|
||||
while _remaining_ticks > 0:
|
||||
enemy.take_damage(damage, false)
|
||||
_remaining_ticks -= 1
|
||||
await enemy.get_tree().create_timer(1.0, false, true, false).timeout
|
||||
var p = PUDDLE.instantiate()
|
||||
enemy.get_parent().add_child(p)
|
||||
p.global_position = enemy.global_position
|
||||
enemy.effects.erase(self)
|
||||
|
||||
|
||||
static func _is_bleeding(enemy: EnemyBase) -> bool:
|
||||
return false
|
1
scenes/enemies/effects/enemy_effect_bleed.gd.uid
Normal file
1
scenes/enemies/effects/enemy_effect_bleed.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b2ptwltm211t3
|
@@ -9,9 +9,9 @@ extends CharacterBody2D
|
||||
@onready var contact_damage_cd: Timer = $ContactDamageCD
|
||||
@onready var animation_player: AnimationPlayer = $AnimationPlayer
|
||||
|
||||
var move_speed: float
|
||||
var health: float
|
||||
var max_health: float
|
||||
var move_speed: float = default_move_speed
|
||||
var health: float = default_max_health
|
||||
var max_health: float = default_max_health
|
||||
var god_mode: bool = false
|
||||
var is_dead: bool = false
|
||||
|
||||
|
@@ -6,6 +6,9 @@ extends CharacterBody2D
|
||||
@export var default_contact_damage: float = 0.0
|
||||
@export var target_distance: float = 6.0
|
||||
@export var path_update_interval: float = 1.5
|
||||
@export var xp_dropped: float = 5.0
|
||||
@export var enemy_rarity: GlobalConst.Rarity = GlobalConst.Rarity.NORMAL
|
||||
var modifiers: Array[EnemyMod] = []
|
||||
|
||||
@onready var target_cast: RayCast2D = $TargetCast
|
||||
@onready var animation_player: AnimationPlayer = $AnimationPlayer
|
||||
@@ -13,20 +16,45 @@ extends CharacterBody2D
|
||||
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
|
||||
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D
|
||||
@onready var shape_cast_2d: ShapeCast2D = $ShapeCast2D
|
||||
@onready var sprite_2d: Sprite2D = $Sprite2D
|
||||
@onready var label: Label = $HBoxContainer/Label
|
||||
@onready var effect_container: HBoxContainer = $HBoxContainer/EffectContainer
|
||||
|
||||
var player: Player
|
||||
var enemy_name: String
|
||||
var target: Node2D
|
||||
var god_mode: bool = false
|
||||
var is_dead: bool = false
|
||||
var health: float
|
||||
|
||||
var effects: Array[EnemyEffectBase]
|
||||
|
||||
var _path_update_timer: float = 0.0
|
||||
var _compute_cache: KeyedCache = KeyedCache.new()
|
||||
var _effects_visible = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
health = max_health
|
||||
enemy_name = _gen_name()
|
||||
match enemy_rarity:
|
||||
GlobalConst.Rarity.NORMAL:
|
||||
label.visible = false
|
||||
GlobalConst.Rarity.RARE:
|
||||
var mods = EnemyModPool.get_random_mods(2)
|
||||
label.visible = true
|
||||
modifiers = mods
|
||||
GlobalConst.Rarity.EPIC:
|
||||
var mods = EnemyModPool.get_random_mods(4)
|
||||
label.visible = true
|
||||
modifiers = mods
|
||||
if modifiers.size() > 0:
|
||||
enemy_name += " the %s" % modifiers.pick_random().adjective
|
||||
label.add_theme_color_override("font_color", GlobalConst.rarity_to_color(enemy_rarity))
|
||||
label.text = enemy_name
|
||||
health = get_calculated("max_health")
|
||||
shape_cast_2d.shape.radius = collision_shape_2d.shape.radius
|
||||
shape_cast_2d.enabled = false
|
||||
sprite_2d.material = sprite_2d.material.duplicate()
|
||||
_find_player()
|
||||
|
||||
|
||||
@@ -36,6 +64,20 @@ func _find_player():
|
||||
target = player
|
||||
|
||||
|
||||
func _gen_name() -> String:
|
||||
return "Unnamed enemy"
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
for effect in effects:
|
||||
if effect in _effects_visible:
|
||||
continue
|
||||
var effect_sprite = Sprite2D.new()
|
||||
effect_sprite.texture = preload("res://assets/sprites/small_bleed_icon.png")
|
||||
effect_container.add_child(effect_sprite)
|
||||
_effects_visible.append(effect)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not target:
|
||||
return
|
||||
@@ -54,6 +96,10 @@ func do_movement(delta: float) -> void:
|
||||
_do_simple_movement()
|
||||
else:
|
||||
_do_nav_agent_movement()
|
||||
if velocity.x < 0:
|
||||
sprite_2d.flip_h = true
|
||||
else:
|
||||
sprite_2d.flip_h = false
|
||||
|
||||
|
||||
func _has_direct_path():
|
||||
@@ -66,7 +112,7 @@ func _do_simple_movement():
|
||||
var direction = global_position.direction_to(target.global_position)
|
||||
var distance = global_position.distance_to(target.global_position)
|
||||
if distance > 4:
|
||||
velocity = direction * move_speed
|
||||
velocity = direction * get_calculated("move_speed")
|
||||
move_and_slide()
|
||||
|
||||
|
||||
@@ -84,14 +130,14 @@ func _do_nav_agent_movement():
|
||||
if shape_cast_2d.is_colliding():
|
||||
direction = direction.bounce(shape_cast_2d.get_collision_normal(0)).normalized()
|
||||
|
||||
velocity = direction * move_speed
|
||||
velocity = direction * get_calculated("move_speed")
|
||||
move_and_slide()
|
||||
|
||||
|
||||
func check_contact_damage():
|
||||
if default_contact_damage == 0.0:
|
||||
return
|
||||
if global_position.distance_to(target.global_position) > target_distance:
|
||||
if global_position.distance_to(target.global_position) > target_distance + 2:
|
||||
return
|
||||
deal_contact_damage()
|
||||
|
||||
@@ -134,11 +180,31 @@ func die():
|
||||
|
||||
func drop_xp_orb() -> void:
|
||||
var orb: XPOrb = preload("res://scenes/xp_orb.tscn").instantiate()
|
||||
orb.value = 5
|
||||
orb.value = xp_dropped * (1 + (modifiers.size() * 3))
|
||||
orb.position = position
|
||||
get_parent().add_child(orb)
|
||||
get_parent().call_deferred("add_child", orb)
|
||||
|
||||
|
||||
func _on_animation_player_animation_finished(anim_name: StringName) -> void:
|
||||
if is_dead:
|
||||
queue_free()
|
||||
|
||||
|
||||
func get_calculated(key: String) -> Variant:
|
||||
# set max move speed to players move speed
|
||||
var compute_func = func():
|
||||
if key == "move_speed":
|
||||
return clampf(
|
||||
EnemyMod.get_calculated(self, key),
|
||||
0,
|
||||
player.player_stats.get_final("move_speed", player.modifiers)
|
||||
)
|
||||
return EnemyMod.get_calculated(self, key)
|
||||
|
||||
return _compute_cache.get_or_compute(key, compute_func)
|
||||
|
||||
|
||||
func has_property(key: String) -> bool:
|
||||
var cache_key = "prop_%s" % key
|
||||
var compute_func = func(): return get(key) != null
|
||||
return _compute_cache.get_or_compute(key, compute_func)
|
||||
|
@@ -1,13 +1,18 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://b7vq8xspnlyeu"]
|
||||
[gd_scene load_steps=10 format=3 uid="uid://b7vq8xspnlyeu"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dxn17u7ltuibw" path="res://scenes/enemies/enemy_base.gd" id="1_qty17"]
|
||||
[ext_resource type="AnimationLibrary" uid="uid://dos4y853hq1gu" path="res://animation/generic_anims.res" id="2_pkqou"]
|
||||
[ext_resource type="Shader" uid="uid://p7evv1wldgsa" path="res://assets/shaders/hit_flash.gdshader" id="2_satqt"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_qa0nx"]
|
||||
shader = ExtResource("2_satqt")
|
||||
shader_parameter/flash_amount = 0.0
|
||||
|
||||
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_pkqou"]
|
||||
size = Vector2(32, 32)
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_satqt"]
|
||||
radius = 6.0
|
||||
radius = 4.0
|
||||
|
||||
[sub_resource type="Animation" id="Animation_satqt"]
|
||||
length = 0.001
|
||||
@@ -35,6 +40,18 @@ tracks/1/keys = {
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1)]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("Sprite2D:material:shader_parameter/flash_amount")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [0.0]
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_qa0nx"]
|
||||
_data = {
|
||||
@@ -43,18 +60,19 @@ _data = {
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_pkqou"]
|
||||
|
||||
[node name="EnemyBase" type="CharacterBody2D"]
|
||||
[node name="EnemyBase" type="CharacterBody2D" groups=["damagable", "enemy"]]
|
||||
collision_layer = 2
|
||||
collision_mask = 3
|
||||
script = ExtResource("1_qty17")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
material = SubResource("ShaderMaterial_qa0nx")
|
||||
texture = SubResource("PlaceholderTexture2D_pkqou")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("CircleShape2D_satqt")
|
||||
|
||||
[node name="TargetCast" type="RayCast2D" parent="." groups=["damagable", "enemy"]]
|
||||
[node name="TargetCast" type="RayCast2D" parent="."]
|
||||
enabled = false
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
@@ -72,4 +90,25 @@ wait_time = 0.5
|
||||
shape = SubResource("CircleShape2D_pkqou")
|
||||
max_results = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -53.0
|
||||
offset_right = 53.0
|
||||
offset_bottom = 40.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="EffectContainer" type="HBoxContainer" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
|
||||
[node name="Label" type="Label" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 9
|
||||
horizontal_alignment = 1
|
||||
|
||||
[connection signal="animation_finished" from="AnimationPlayer" to="." method="_on_animation_player_animation_finished"]
|
||||
|
32
scenes/enemies/enemy_bat.gd
Normal file
32
scenes/enemies/enemy_bat.gd
Normal file
@@ -0,0 +1,32 @@
|
||||
extends EnemyBase
|
||||
|
||||
const NAME_PREFIXES: Array[String] = [
|
||||
"Chi",
|
||||
"Sque",
|
||||
"Flit",
|
||||
"Bat",
|
||||
"Kla",
|
||||
]
|
||||
|
||||
const NAME_ROOTS: Array[String] = [
|
||||
"ter",
|
||||
"tch",
|
||||
"reek",
|
||||
"p",
|
||||
"nap",
|
||||
]
|
||||
|
||||
const NAME_SUFFIXES: Array[String] = [
|
||||
"y",
|
||||
"a",
|
||||
"et",
|
||||
"ik",
|
||||
"in",
|
||||
]
|
||||
|
||||
|
||||
func _gen_name() -> String:
|
||||
return (
|
||||
"%s%s%s"
|
||||
% [NAME_PREFIXES.pick_random(), NAME_ROOTS.pick_random(), NAME_SUFFIXES.pick_random()]
|
||||
)
|
1
scenes/enemies/enemy_bat.gd.uid
Normal file
1
scenes/enemies/enemy_bat.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bfhmjpjwdh25o
|
24
scenes/enemies/enemy_bat.tscn
Normal file
24
scenes/enemies/enemy_bat.tscn
Normal file
@@ -0,0 +1,24 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://du4t4vydra4rm"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://b7vq8xspnlyeu" path="res://scenes/enemies/enemy_base.tscn" id="1_4r40l"]
|
||||
[ext_resource type="Script" uid="uid://bfhmjpjwdh25o" path="res://scenes/enemies/enemy_bat.gd" id="2_1jj4k"]
|
||||
[ext_resource type="Texture2D" uid="uid://chtjfq3bmonhj" path="res://assets/sprites/small_bat.png" id="2_608ut"]
|
||||
|
||||
[node name="EnemyBat" instance=ExtResource("1_4r40l")]
|
||||
collision_mask = 2
|
||||
script = ExtResource("2_1jj4k")
|
||||
move_speed = 175.0
|
||||
max_health = 6.0
|
||||
default_contact_damage = 5.0
|
||||
target_distance = 2.0
|
||||
xp_dropped = 8.0
|
||||
|
||||
[node name="Sprite2D" parent="." index="0"]
|
||||
texture = ExtResource("2_608ut")
|
||||
|
||||
[node name="TargetCast" parent="." index="2"]
|
||||
collision_mask = 0
|
||||
collide_with_bodies = false
|
||||
|
||||
[node name="ShapeCast2D" parent="." index="6"]
|
||||
visible = false
|
34
scenes/enemies/enemy_mod.gd
Normal file
34
scenes/enemies/enemy_mod.gd
Normal file
@@ -0,0 +1,34 @@
|
||||
class_name EnemyMod
|
||||
extends Resource
|
||||
|
||||
enum ModType { ADDITIVE, MULTIPLICATIVE, ABSOLUTE, BOOL }
|
||||
|
||||
@export var mod_name: String
|
||||
@export var mod_property: String
|
||||
@export var mod_value: float
|
||||
@export var mod_value_bool: bool = false
|
||||
@export var mod_type: ModType = ModType.MULTIPLICATIVE
|
||||
|
||||
var adjective: String
|
||||
|
||||
|
||||
static func get_calculated(enemy: EnemyBase, key: String) -> Variant:
|
||||
assert(
|
||||
enemy.has_property(key),
|
||||
"tried to calculate property '%s' where base value does not exist on %s" % [key, enemy]
|
||||
)
|
||||
var base_value = enemy.get(key)
|
||||
var add = 0.0
|
||||
var mul = 1.0
|
||||
for mod in enemy.modifiers:
|
||||
if mod.mod_property == key:
|
||||
match mod.mod_type:
|
||||
ModType.ADDITIVE:
|
||||
add += mod.mod_value
|
||||
ModType.MULTIPLICATIVE:
|
||||
mul *= mod.mod_value
|
||||
ModType.ABSOLUTE:
|
||||
return mod.mod_value
|
||||
ModType.BOOL:
|
||||
return mod.mod_value_bool
|
||||
return (base_value + add) * mul
|
1
scenes/enemies/enemy_mod.gd.uid
Normal file
1
scenes/enemies/enemy_mod.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d2mvgwmsmcxp4
|
58
scenes/enemies/enemy_mod_pool.gd
Normal file
58
scenes/enemies/enemy_mod_pool.gd
Normal file
@@ -0,0 +1,58 @@
|
||||
class_name EnemyModPool
|
||||
extends Resource
|
||||
|
||||
const MAX_HEALTH_MOD: Dictionary = {
|
||||
"mod_name": "Extra health",
|
||||
"mod_property": "max_health",
|
||||
"mod_value": 1.0,
|
||||
"mod_value_bool": false,
|
||||
"mod_type": EnemyMod.ModType.MULTIPLICATIVE,
|
||||
"mod_adjectives":
|
||||
[
|
||||
"Sturdy",
|
||||
"Bulky",
|
||||
"Ironclad",
|
||||
"Unyielding",
|
||||
"Stonehide",
|
||||
"Thicc",
|
||||
"Chunky",
|
||||
"Pudgy",
|
||||
"Chonky",
|
||||
"Double-stuffed"
|
||||
],
|
||||
}
|
||||
|
||||
const MOD_POOL = [
|
||||
{
|
||||
"mod_name": "Extra movement speed",
|
||||
"mod_property": "move_speed",
|
||||
"mod_value": 1.25,
|
||||
"mod_value_bool": false,
|
||||
"mod_type": EnemyMod.ModType.MULTIPLICATIVE,
|
||||
"mod_adjectives": ["Swift", "Nimble", "Fleet", "Spry", "Skittering", "Zoomy", "Zippy"]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
static func get_random_mods(count: int) -> Array[EnemyMod]:
|
||||
var mods: Array[EnemyMod] = []
|
||||
# always include extra health mod
|
||||
var hp_mod = _dict_to_mod(MAX_HEALTH_MOD)
|
||||
hp_mod.mod_value *= 5 * count
|
||||
mods.append(hp_mod)
|
||||
for i in count:
|
||||
var mod_data = MOD_POOL.pick_random()
|
||||
var mod = _dict_to_mod(mod_data)
|
||||
mods.append(mod)
|
||||
return mods
|
||||
|
||||
|
||||
static func _dict_to_mod(d: Dictionary) -> EnemyMod:
|
||||
var mod: EnemyMod = EnemyMod.new()
|
||||
mod.mod_name = d["mod_name"]
|
||||
mod.mod_property = d["mod_property"]
|
||||
mod.mod_value = d["mod_value"]
|
||||
mod.mod_value_bool = d["mod_value_bool"]
|
||||
mod.mod_type = d["mod_type"]
|
||||
mod.adjective = d["mod_adjectives"].pick_random()
|
||||
return mod
|
1
scenes/enemies/enemy_mod_pool.gd.uid
Normal file
1
scenes/enemies/enemy_mod_pool.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bl11yfav37ecs
|
@@ -1 +1,29 @@
|
||||
extends EnemyBase
|
||||
|
||||
const NAME_PREFIXES: Array[String] = [
|
||||
"Sk",
|
||||
"Sn",
|
||||
"Gr",
|
||||
"R",
|
||||
"Kr",
|
||||
"V",
|
||||
"Vr",
|
||||
]
|
||||
|
||||
const NAME_ROOTS: Array[String] = [
|
||||
"itch",
|
||||
"abble",
|
||||
"itter",
|
||||
"usk",
|
||||
"arp",
|
||||
"uzzle",
|
||||
]
|
||||
|
||||
const NAME_SUFFIXES: Array[String] = ["y", "er", "o", "ok", "in"]
|
||||
|
||||
|
||||
func _gen_name() -> String:
|
||||
return (
|
||||
"%s%s%s"
|
||||
% [NAME_PREFIXES.pick_random(), NAME_ROOTS.pick_random(), NAME_SUFFIXES.pick_random()]
|
||||
)
|
||||
|
@@ -6,7 +6,6 @@
|
||||
|
||||
[node name="EnemyRat" instance=ExtResource("1_o1awd")]
|
||||
script = ExtResource("2_sari4")
|
||||
max_health = 50.0
|
||||
default_contact_damage = 5.0
|
||||
|
||||
[node name="Sprite2D" parent="." index="0"]
|
||||
|
60
scenes/enemies/enemy_slime_small.gd
Normal file
60
scenes/enemies/enemy_slime_small.gd
Normal file
@@ -0,0 +1,60 @@
|
||||
class_name EnemySlimeSmall
|
||||
extends EnemyBase
|
||||
|
||||
@export var color: Color = Color.CHARTREUSE
|
||||
@onready var disabled_sprite: Sprite2D = $Sprite2D
|
||||
@onready var base_sprite: Sprite2D = $Sprite/BaseSprite
|
||||
|
||||
var shader = preload("res://assets/shaders/base_color_tint.gdshader")
|
||||
var shader_material: ShaderMaterial
|
||||
|
||||
const NAME_PREFIXES: Array[String] = [
|
||||
"Gl",
|
||||
"Sl",
|
||||
"Bl",
|
||||
"Pl",
|
||||
"Dr",
|
||||
"Go",
|
||||
"Glo",
|
||||
"Blo",
|
||||
]
|
||||
|
||||
const NAME_ROOTS: Array[String] = [
|
||||
"op",
|
||||
"ub",
|
||||
"ooz",
|
||||
"ump",
|
||||
"ibble",
|
||||
"ug",
|
||||
"urp",
|
||||
"lop",
|
||||
]
|
||||
|
||||
const NAME_SUFFIXES: Array[String] = [
|
||||
"y",
|
||||
"o",
|
||||
"let",
|
||||
"kin",
|
||||
"ish",
|
||||
"oo",
|
||||
]
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
shader_material = ShaderMaterial.new()
|
||||
disabled_sprite.visible = false
|
||||
set_color(color)
|
||||
shader_material.shader = shader
|
||||
base_sprite.material = shader_material
|
||||
super._ready()
|
||||
|
||||
|
||||
func set_color(new_color: Color) -> void:
|
||||
shader_material.set_shader_parameter("base_color", new_color)
|
||||
|
||||
|
||||
func _gen_name() -> String:
|
||||
return (
|
||||
"%s%s%s"
|
||||
% [NAME_PREFIXES.pick_random(), NAME_ROOTS.pick_random(), NAME_SUFFIXES.pick_random()]
|
||||
)
|
1
scenes/enemies/enemy_slime_small.gd.uid
Normal file
1
scenes/enemies/enemy_slime_small.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c5wgwy5itcnkk
|
41
scenes/enemies/enemy_slime_small.tscn
Normal file
41
scenes/enemies/enemy_slime_small.tscn
Normal file
@@ -0,0 +1,41 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://cjikar11hbp3b"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://b7vq8xspnlyeu" path="res://scenes/enemies/enemy_base.tscn" id="1_o6v6p"]
|
||||
[ext_resource type="Script" uid="uid://c5wgwy5itcnkk" path="res://scenes/enemies/enemy_slime_small.gd" id="2_2bnvq"]
|
||||
[ext_resource type="Shader" uid="uid://cf48pgfl308o3" path="res://assets/shaders/base_color_tint.gdshader" id="3_2yvgl"]
|
||||
[ext_resource type="Texture2D" uid="uid://ctob5me7woid8" path="res://assets/sprites/slime_small grayscale.png" id="3_iaao1"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_daihb"]
|
||||
shader = ExtResource("3_2yvgl")
|
||||
shader_parameter/base_color = Color(3.46541e-06, 0.969973, 0.260278, 1)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_2yvgl"]
|
||||
atlas = ExtResource("3_iaao1")
|
||||
region = Rect2(0, 15, 16, 15)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_daihb"]
|
||||
atlas = ExtResource("3_iaao1")
|
||||
region = Rect2(0, 30, 16, 15)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_wu20c"]
|
||||
atlas = ExtResource("3_iaao1")
|
||||
region = Rect2(0, 45, 16, 15)
|
||||
|
||||
[node name="EnemySlimeSmall" instance=ExtResource("1_o6v6p")]
|
||||
script = ExtResource("2_2bnvq")
|
||||
color = Color(0.498039, 1, 0, 1)
|
||||
|
||||
[node name="Sprite" type="Node2D" parent="." index="0"]
|
||||
|
||||
[node name="BaseSprite" type="Sprite2D" parent="Sprite" index="0"]
|
||||
material = SubResource("ShaderMaterial_daihb")
|
||||
texture = SubResource("AtlasTexture_2yvgl")
|
||||
|
||||
[node name="Shading" type="Sprite2D" parent="Sprite" index="1"]
|
||||
texture = SubResource("AtlasTexture_daihb")
|
||||
|
||||
[node name="Highlights" type="Sprite2D" parent="Sprite" index="2"]
|
||||
texture = SubResource("AtlasTexture_wu20c")
|
||||
|
||||
[node name="Sprite2D" parent="." index="1"]
|
||||
visible = false
|
@@ -20,14 +20,16 @@ script = ExtResource("1_jyhfs")
|
||||
zoom = Vector2(2, 2)
|
||||
process_callback = 0
|
||||
|
||||
[node name="EnemyManager" parent="." node_paths=PackedStringArray("target", "camera") instance=ExtResource("5_tbgi4")]
|
||||
spawn_rate = 1.5
|
||||
target = NodePath("../Player")
|
||||
camera = NodePath("../MainCamera")
|
||||
|
||||
[node name="Player" parent="." node_paths=PackedStringArray("camera", "main_ui") instance=ExtResource("2_0wfyh")]
|
||||
position = Vector2(1057, 798)
|
||||
camera = NodePath("../MainCamera")
|
||||
main_ui = NodePath("../MainUI")
|
||||
|
||||
[node name="EnemyManager" parent="." node_paths=PackedStringArray("target") instance=ExtResource("5_tbgi4")]
|
||||
target = NodePath("../Player")
|
||||
|
||||
[node name="PickupMagnet" parent="." instance=ExtResource("6_tefeu")]
|
||||
position = Vector2(1697, 414)
|
||||
|
||||
|
19
scenes/main_menu.gd
Normal file
19
scenes/main_menu.gd
Normal file
@@ -0,0 +1,19 @@
|
||||
extends Control
|
||||
|
||||
const MAIN = preload("res://scenes/main.tscn")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _on_new_game_button_pressed() -> void:
|
||||
get_tree().change_scene_to_packed(MAIN)
|
||||
|
||||
|
||||
func _on_options_button_pressed() -> void:
|
||||
pass # Replace with function body.
|
||||
|
||||
|
||||
func _on_exit_game_button_pressed() -> void:
|
||||
get_tree().quit()
|
1
scenes/main_menu.gd.uid
Normal file
1
scenes/main_menu.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://sd158y3mdmkt
|
69
scenes/main_menu.tscn
Normal file
69
scenes/main_menu.tscn
Normal file
@@ -0,0 +1,69 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cynet50emve6c"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://sd158y3mdmkt" path="res://scenes/main_menu.gd" id="1_l6cm7"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_vue75"]
|
||||
colors = PackedColorArray(0.252028, 0.252028, 0.252028, 1, 0.25098, 0.25098, 0.25098, 1)
|
||||
|
||||
[sub_resource type="GradientTexture1D" id="GradientTexture1D_l6cm7"]
|
||||
gradient = SubResource("Gradient_vue75")
|
||||
|
||||
[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_l6cm7")
|
||||
|
||||
[node name="Background" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("GradientTexture1D_l6cm7")
|
||||
expand_mode = 2
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" 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 = -20.0
|
||||
offset_top = -20.0
|
||||
offset_right = 20.0
|
||||
offset_bottom = 20.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 20
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 20
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="NewGameButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "New game"
|
||||
|
||||
[node name="OptionsButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Options"
|
||||
|
||||
[node name="ExitGameButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Exit game"
|
||||
|
||||
[connection signal="pressed" from="PanelContainer/MarginContainer/VBoxContainer/NewGameButton" to="." method="_on_new_game_button_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/MarginContainer/VBoxContainer/OptionsButton" to="." method="_on_options_button_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/MarginContainer/VBoxContainer/ExitGameButton" to="." method="_on_exit_game_button_pressed"]
|
@@ -4,10 +4,16 @@ extends Node2D
|
||||
@export var max_enemies: int
|
||||
@export var spawn_rate: float
|
||||
@export var target: CollisionObject2D
|
||||
@export var camera: Camera2D
|
||||
|
||||
@onready var timer: Timer = $Timer
|
||||
|
||||
const ENEMY_RAT = preload("res://scenes/enemies/enemy_rat.tscn")
|
||||
const ENEMY_BAT = preload("res://scenes/enemies/enemy_bat.tscn")
|
||||
const ENEMY_SLIME_SMALL = preload("res://scenes/enemies/enemy_slime_small.tscn")
|
||||
const SLIME_COLOR_VARIATIONS: Array[Color] = [Color.CHARTREUSE, Color.FUCHSIA, Color.DARK_ORANGE]
|
||||
|
||||
var _elapsed_time: float = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -17,16 +23,87 @@ func _ready() -> void:
|
||||
GlobalConst.sig_set_spawn_rate.connect(_on_set_spawn_rate)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
_elapsed_time += delta
|
||||
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
_on_set_spawn_rate(1.0 + (_elapsed_time / 60.0) ** 2)
|
||||
var enemies = get_tree().get_nodes_in_group(GlobalConst.GROUP_ENEMY)
|
||||
GlobalConst.sig_debug_stats_set.emit("enemy_count", "%s" % len(enemies))
|
||||
var next_enemy: PackedScene
|
||||
match randi() % 3:
|
||||
0:
|
||||
next_enemy = ENEMY_BAT
|
||||
1:
|
||||
next_enemy = ENEMY_RAT
|
||||
2:
|
||||
next_enemy = ENEMY_SLIME_SMALL
|
||||
|
||||
if len(enemies) < max_enemies:
|
||||
var new_enemy = ENEMY_RAT.instantiate()
|
||||
new_enemy.position = target.position + Vector2(50, 50)
|
||||
var new_enemy = next_enemy.instantiate()
|
||||
new_enemy.position = _get_spawn_pos()
|
||||
new_enemy.target = target
|
||||
if randf() < 0.1:
|
||||
new_enemy.enemy_rarity = GlobalConst.Rarity.RARE
|
||||
if randf() < 0.1:
|
||||
new_enemy.enemy_rarity = GlobalConst.Rarity.EPIC
|
||||
if is_instance_of(new_enemy, EnemySlimeSmall):
|
||||
var slime_color: Color = SLIME_COLOR_VARIATIONS.pick_random()
|
||||
new_enemy.color = slime_color
|
||||
add_child(new_enemy)
|
||||
|
||||
|
||||
func _get_spawn_pos() -> Vector2:
|
||||
var rect = _get_camera_rect()
|
||||
var side = randi() % 4
|
||||
var margin = 50
|
||||
var pos: Vector2
|
||||
|
||||
match side:
|
||||
0: # Top
|
||||
pos = Vector2(
|
||||
randf_range(rect.position.x, rect.position.x + rect.size.x),
|
||||
rect.position.y - margin
|
||||
)
|
||||
1: # Bottom
|
||||
pos = Vector2(
|
||||
randf_range(rect.position.x, rect.position.x + rect.size.x),
|
||||
rect.position.y + rect.size.y + margin
|
||||
)
|
||||
2: # Left
|
||||
pos = Vector2(
|
||||
rect.position.x - margin,
|
||||
randf_range(rect.position.y, rect.position.y + rect.size.y)
|
||||
)
|
||||
3: # Right
|
||||
pos = Vector2(
|
||||
rect.position.x + rect.size.x + margin,
|
||||
randf_range(rect.position.y, rect.position.y + rect.size.y)
|
||||
)
|
||||
if !_is_pos_valid(pos):
|
||||
pos = _get_spawn_pos()
|
||||
return pos
|
||||
|
||||
|
||||
func _is_pos_valid(pos: Vector2) -> bool:
|
||||
var space = get_world_2d().direct_space_state
|
||||
var parameters = PhysicsPointQueryParameters2D.new()
|
||||
parameters.collide_with_areas = false
|
||||
parameters.collide_with_bodies = true
|
||||
parameters.collision_mask = 1
|
||||
parameters.position = pos
|
||||
var result = space.intersect_point(parameters, 1)
|
||||
return result.size() == 0
|
||||
|
||||
|
||||
func _get_camera_rect() -> Rect2:
|
||||
var viewport_size = camera.get_viewport_rect().size
|
||||
var half_size = viewport_size * 0.5
|
||||
var top_left = camera.global_position - half_size
|
||||
return Rect2(top_left, viewport_size)
|
||||
|
||||
|
||||
func _on_stop_spawning(val: bool):
|
||||
if val:
|
||||
timer.stop()
|
||||
@@ -35,4 +112,6 @@ func _on_stop_spawning(val: bool):
|
||||
|
||||
|
||||
func _on_set_spawn_rate(val: float):
|
||||
timer.wait_time = 1 / val
|
||||
timer.stop()
|
||||
timer.wait_time = 1.0 / val
|
||||
timer.start()
|
||||
|
@@ -13,13 +13,13 @@ signal lvlup_picked(mod: PlayerStatsModifier)
|
||||
|
||||
func _ready() -> void:
|
||||
match mod.rarity:
|
||||
GlobalConst.ModRarity.NORMAL:
|
||||
GlobalConst.Rarity.NORMAL:
|
||||
upgrade_name.add_theme_color_override("font_color", Color.WHITE)
|
||||
GlobalConst.ModRarity.RARE:
|
||||
GlobalConst.Rarity.RARE:
|
||||
upgrade_name.add_theme_color_override("font_color", Color.DODGER_BLUE)
|
||||
GlobalConst.ModRarity.EPIC:
|
||||
GlobalConst.Rarity.EPIC:
|
||||
upgrade_name.add_theme_color_override("font_color", Color.DARK_ORCHID)
|
||||
GlobalConst.ModRarity.LEGENDARY:
|
||||
GlobalConst.Rarity.LEGENDARY:
|
||||
upgrade_name.add_theme_color_override("font_color", Color.DARK_ORANGE)
|
||||
|
||||
upgrade_name.text = mod.title
|
||||
|
@@ -5,9 +5,34 @@ extends Control
|
||||
@onready var player_ui: PlayerUI = $CanvasLayer/PlayerUI
|
||||
@onready var debug_ui: DebugUI = $CanvasLayer/DebugUI
|
||||
@onready var level_up_ui: LevelUpUI = $CanvasLayer/LevelUpUI
|
||||
@onready var low_hp_indicator: ColorRect = $CanvasLayer/LowHPIndicator
|
||||
|
||||
var elapsed_time: float
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
pause_ui.visible = false
|
||||
player_ui.visible = true
|
||||
level_up_ui.visible = false
|
||||
low_hp_indicator.visible = false
|
||||
GlobalConst.sig_on_player_hp_change.connect(_on_player_hp_change)
|
||||
GlobalConst.sig_toggle_low_hp_shader.connect(_on_toggle_low_hp_shader)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
elapsed_time += delta
|
||||
if low_hp_indicator.visible:
|
||||
low_hp_indicator.material.set_shader_parameter("time", elapsed_time)
|
||||
|
||||
|
||||
func _on_player_hp_change(hp: float, max_hp: float):
|
||||
var percent_hp = hp / max_hp * 100
|
||||
if percent_hp < 40:
|
||||
low_hp_indicator.visible = true
|
||||
low_hp_indicator.material.set_shader_parameter("damage_amount", 1 - (percent_hp / 100))
|
||||
else:
|
||||
low_hp_indicator.visible = false
|
||||
|
||||
|
||||
func _on_toggle_low_hp_shader(toggled_on: bool):
|
||||
low_hp_indicator.visible = !low_hp_indicator.visible
|
||||
|
@@ -1,11 +1,19 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://b18uib08hvdpq"]
|
||||
[gd_scene load_steps=8 format=3 uid="uid://b18uib08hvdpq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dcxc70fvu7kl2" path="res://scenes/managers/ui/main_ui.gd" id="1_3a826"]
|
||||
[ext_resource type="Script" uid="uid://sjnxf0hj3egp" path="res://scenes/managers/ui/pause_ui.gd" id="1_lke1m"]
|
||||
[ext_resource type="Shader" uid="uid://dnwiwj1ak1bu5" path="res://assets/shaders/screen_low_hp.gdshader" id="2_lcbsd"]
|
||||
[ext_resource type="Script" uid="uid://dbq74tvxtpfjc" path="res://scenes/managers/ui/player_ui.gd" id="3_gaipe"]
|
||||
[ext_resource type="Script" uid="uid://d2o6tqnqg2o25" path="res://scenes/managers/ui/debug_ui.gd" id="4_217l8"]
|
||||
[ext_resource type="PackedScene" uid="uid://isg7vt4l7eem" path="res://scenes/managers/ui/level_up_ui.tscn" id="5_cfhdr"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_cg7ol"]
|
||||
shader = ExtResource("2_lcbsd")
|
||||
shader_parameter/damage_amount = 0.5
|
||||
shader_parameter/noise_strength = 0.2
|
||||
shader_parameter/pulse_strength = 0.05
|
||||
shader_parameter/time = 0.0
|
||||
|
||||
[node name="MainUI" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
@@ -17,6 +25,14 @@ script = ExtResource("1_3a826")
|
||||
|
||||
[node name="CanvasLayer" type="CanvasLayer" parent="."]
|
||||
|
||||
[node name="LowHPIndicator" type="ColorRect" parent="CanvasLayer"]
|
||||
material = SubResource("ShaderMaterial_cg7ol")
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="PauseUI" type="Control" parent="CanvasLayer"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
|
@@ -16,7 +16,7 @@ func pickup() -> void:
|
||||
mod.title = "Magnet"
|
||||
mod.tex = GlobalConst.placeholder_tex
|
||||
mod.internal_name = "magnet"
|
||||
mod.rarity = GlobalConst.ModRarity.RARE
|
||||
mod.rarity = GlobalConst.Rarity.RARE
|
||||
mod.stat_name = "pickup_radius"
|
||||
mod.value = 5000.0
|
||||
mod.type = PlayerStatsModifier.ModifierType.ABSOLUTE
|
||||
|
@@ -1,9 +1,13 @@
|
||||
class_name Player
|
||||
extends CharacterBody2D
|
||||
|
||||
const WEAPON_SWORD = preload("res://scenes/weapons/weapon_sword.tscn")
|
||||
|
||||
@export var camera: Camera2D
|
||||
@export var main_ui: MainUI
|
||||
|
||||
var weapon: WeaponBase
|
||||
|
||||
@onready var sprite_2d: Sprite2D = $Sprite2D
|
||||
|
||||
var player_stats: PlayerStats = PlayerStats.new()
|
||||
@@ -13,10 +17,18 @@ var death_anim_done: bool = false
|
||||
var god_mode: bool = false
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("active"):
|
||||
weapon.do_active()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
camera.position = global_position
|
||||
main_ui.player_ui.update_hp()
|
||||
GlobalConst.sig_debug_god_mode.connect(toggle_god_mode)
|
||||
if not weapon:
|
||||
weapon = WEAPON_SWORD.instantiate()
|
||||
add_child(weapon)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
@@ -51,6 +63,7 @@ func take_damage(value: float) -> void:
|
||||
dm.damage_taken = value
|
||||
dm.player_damage = false
|
||||
add_child(dm)
|
||||
GlobalConst.sig_on_player_hp_change.emit(current_hp, max_hp)
|
||||
main_ui.player_ui.update_hp()
|
||||
if player_stats.current_health <= 0:
|
||||
die()
|
||||
@@ -77,15 +90,12 @@ func death_animation(delta: float):
|
||||
func get_taunted():
|
||||
var taunting_enemies: Array[EnemyBase] = []
|
||||
for body in get_tree().get_nodes_in_group(GlobalConst.GROUP_ENEMY):
|
||||
print_debug("starting taunt: %s" % global_position.distance_to(body.global_position))
|
||||
if global_position.distance_to(body.global_position) < 1000.0:
|
||||
taunting_enemies.append(body)
|
||||
print_debug("getting taunted by %s enemies" % len(taunting_enemies))
|
||||
for i in range(taunting_enemies.size()):
|
||||
var angle = (TAU / taunting_enemies.size()) * i
|
||||
var target_pos = Vector2(cos(angle), sin(angle)) * 50
|
||||
var new_target = Marker2D.new()
|
||||
print_debug("getting taunted by %s" % taunting_enemies[i])
|
||||
new_target.position = target_pos
|
||||
add_child(new_target)
|
||||
taunting_enemies[i].target = new_target
|
||||
|
@@ -1,8 +1,7 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://ca2so8fm3q8fe"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://ca2so8fm3q8fe"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://5x5wimok8uw2" path="res://assets/sprites/roguelikeChar_transparent.png" id="1_3vyb7"]
|
||||
[ext_resource type="Script" uid="uid://cvqaxckx4num3" path="res://scenes/player.gd" id="1_g2els"]
|
||||
[ext_resource type="PackedScene" uid="uid://dfikvj27k01tu" path="res://scenes/weapons/weapon_sword.tscn" id="3_qhqgy"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_3vyb7"]
|
||||
radius = 8.0
|
||||
@@ -30,6 +29,4 @@ monitorable = false
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="PickupArea"]
|
||||
shape = SubResource("CircleShape2D_qhqgy")
|
||||
|
||||
[node name="WeaponSword" parent="." instance=ExtResource("3_qhqgy")]
|
||||
|
||||
[connection signal="area_entered" from="PickupArea" to="." method="_on_pickup_area_area_entered"]
|
||||
|
@@ -8,6 +8,6 @@ var value: Variant
|
||||
var type: ModifierType = ModifierType.ADDITIVE
|
||||
var internal_name: String
|
||||
var title: String
|
||||
var rarity: GlobalConst.ModRarity
|
||||
var rarity: GlobalConst.Rarity
|
||||
var tex: Texture2D
|
||||
var description: String
|
||||
|
22
scenes/puddle.gd
Normal file
22
scenes/puddle.gd
Normal file
@@ -0,0 +1,22 @@
|
||||
class_name Puddle
|
||||
extends Node2D
|
||||
|
||||
@export var color: Color = Color.CRIMSON
|
||||
@onready var base: Sprite2D = $Base
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var player: Player = get_tree().get_first_node_in_group(GlobalConst.GROUP_PLAYER)
|
||||
var shader = preload("res://assets/shaders/base_color_tint.gdshader")
|
||||
var shader_material: ShaderMaterial
|
||||
shader_material = ShaderMaterial.new()
|
||||
shader_material.set_shader_parameter("base_color", color)
|
||||
shader_material.shader = shader
|
||||
base.material = shader_material
|
||||
match randi() % 4:
|
||||
1:
|
||||
rotation_degrees = 90
|
||||
2:
|
||||
rotation_degrees = 180
|
||||
3:
|
||||
rotation_degrees = -90
|
1
scenes/puddle.gd.uid
Normal file
1
scenes/puddle.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cds5aqq2mqmpe
|
34
scenes/puddle.tscn
Normal file
34
scenes/puddle.tscn
Normal file
@@ -0,0 +1,34 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://dcka1mmlgeoj7"]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://cf48pgfl308o3" path="res://assets/shaders/base_color_tint.gdshader" id="1_qlq6d"]
|
||||
[ext_resource type="Script" uid="uid://cds5aqq2mqmpe" path="res://scenes/puddle.gd" id="1_s8xbh"]
|
||||
[ext_resource type="Texture2D" uid="uid://c5t4it4if0s6g" path="res://assets/sprites/puddle_1.png" id="2_s8xbh"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_i64cl"]
|
||||
shader = ExtResource("1_qlq6d")
|
||||
shader_parameter/base_color = Color(1, 0.2, 0.2, 1)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_h42jt"]
|
||||
atlas = ExtResource("2_s8xbh")
|
||||
region = Rect2(0, 0, 16, 13)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_16dvh"]
|
||||
atlas = ExtResource("2_s8xbh")
|
||||
region = Rect2(32, 0, 16, 13)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_5nuq4"]
|
||||
atlas = ExtResource("2_s8xbh")
|
||||
region = Rect2(16, 0, 16, 13)
|
||||
|
||||
[node name="Puddle" type="Node2D"]
|
||||
script = ExtResource("1_s8xbh")
|
||||
|
||||
[node name="Base" type="Sprite2D" parent="."]
|
||||
material = SubResource("ShaderMaterial_i64cl")
|
||||
texture = SubResource("AtlasTexture_h42jt")
|
||||
|
||||
[node name="Highlights" type="Sprite2D" parent="."]
|
||||
texture = SubResource("AtlasTexture_16dvh")
|
||||
|
||||
[node name="Shading" type="Sprite2D" parent="."]
|
||||
texture = SubResource("AtlasTexture_5nuq4")
|
17
scenes/utils/keyed_cache.gd
Normal file
17
scenes/utils/keyed_cache.gd
Normal file
@@ -0,0 +1,17 @@
|
||||
class_name KeyedCache
|
||||
extends Resource
|
||||
|
||||
var cache = {}
|
||||
|
||||
|
||||
func get_or_compute(key: String, compute_func: Callable):
|
||||
if key in cache:
|
||||
return cache["key"]
|
||||
|
||||
var value = compute_func.call()
|
||||
cache["key"] = value
|
||||
return value
|
||||
|
||||
|
||||
func invalidate_key(key: String):
|
||||
cache.erase(key)
|
1
scenes/utils/keyed_cache.gd.uid
Normal file
1
scenes/utils/keyed_cache.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bbshyok4m8nq3
|
@@ -1,11 +1,33 @@
|
||||
class_name WeaponBase
|
||||
extends Node2D
|
||||
|
||||
enum WeaponTag {
|
||||
CAN_RETURN,
|
||||
CAN_BLEED,
|
||||
CAN_CHAIN,
|
||||
CAN_FORK,
|
||||
}
|
||||
|
||||
@export var attack_cd: float
|
||||
@export var attack_damage: float
|
||||
@export var attack_aoe: float
|
||||
@export var attack_duration: float
|
||||
@export var attack_range: float
|
||||
@export var attack_crit_chance: float = 0.05
|
||||
@export var return_chance: float = 0.0
|
||||
@export var bleed_chance: float = 0.0
|
||||
@export var bleed_duration: float = 5.0
|
||||
@export var chain_chance: float = 0.0
|
||||
@export var tags: Array[String] = []
|
||||
@export var modifiers: Array[WeaponModBase] = []
|
||||
|
||||
@onready var active_cd_timer: Timer = $ActiveCDTimer
|
||||
|
||||
var _player: Player
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_player = get_tree().get_first_node_in_group(GlobalConst.GROUP_PLAYER)
|
||||
|
||||
|
||||
func _on_attack_cd_timer_timeout() -> void:
|
||||
@@ -16,6 +38,21 @@ func do_attack() -> void:
|
||||
push_error("%s does not implement do_attack" % self)
|
||||
|
||||
|
||||
func do_active() -> void:
|
||||
if not active_cd_timer.is_stopped():
|
||||
return
|
||||
active_cd_timer.start()
|
||||
_do_active()
|
||||
|
||||
|
||||
func add_mod(mod: WeaponModBase) -> void:
|
||||
modifiers.append(mod)
|
||||
|
||||
|
||||
func _do_active() -> void:
|
||||
push_error("%s does not implement do_active" % self)
|
||||
|
||||
|
||||
func find_target_in_radius() -> EnemyBase:
|
||||
var space_state: PhysicsDirectSpaceState2D = get_world_2d().direct_space_state
|
||||
var shape := CircleShape2D.new()
|
||||
@@ -40,3 +77,33 @@ func find_target_in_radius() -> EnemyBase:
|
||||
):
|
||||
closest = c
|
||||
return closest
|
||||
|
||||
|
||||
func get_calculated(key: String) -> Variant:
|
||||
return WeaponModBase.get_calculated(self, key)
|
||||
|
||||
|
||||
func has_property(key: String) -> bool:
|
||||
for prop in get_property_list():
|
||||
if prop.name == key:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func did_crit() -> bool:
|
||||
var weapon_crit = get_calculated("attack_crit_chance")
|
||||
var player_crit = _player.player_stats.get_final("crit_chance", _player.modifiers)
|
||||
|
||||
return randf() >= 1 - weapon_crit + player_crit
|
||||
|
||||
|
||||
func did_bleed() -> bool:
|
||||
return randf() >= 1 - bleed_chance
|
||||
|
||||
|
||||
func base_damage() -> Array[Variant]:
|
||||
var damage = get_calculated("attack_damage")
|
||||
var is_crit := did_crit()
|
||||
if is_crit:
|
||||
damage *= _player.player_stats.get_final("crit_multiplier", _player.modifiers)
|
||||
return [damage, is_crit]
|
||||
|
@@ -8,4 +8,7 @@ script = ExtResource("1_v4xn6")
|
||||
[node name="AttackCDTimer" type="Timer" parent="."]
|
||||
autostart = true
|
||||
|
||||
[node name="ActiveCDTimer" type="Timer" parent="."]
|
||||
one_shot = true
|
||||
|
||||
[connection signal="timeout" from="AttackCDTimer" to="." method="_on_attack_cd_timer_timeout"]
|
||||
|
36
scenes/weapons/weapon_mod_base.gd
Normal file
36
scenes/weapons/weapon_mod_base.gd
Normal file
@@ -0,0 +1,36 @@
|
||||
class_name WeaponModBase
|
||||
extends Resource
|
||||
|
||||
enum ModType { ADDITIVE, MULTIPLICATIVE, ABSOLUTE, BOOL }
|
||||
|
||||
@export var name: String = "Unnamed mod"
|
||||
@export var description: String = ""
|
||||
@export var icon: Texture2D
|
||||
@export var mod_property: String
|
||||
@export var mod_value: float
|
||||
@export var mod_value_bool: bool = false
|
||||
@export var mod_type: ModType = ModType.MULTIPLICATIVE
|
||||
@export var mod_extra_tags: Array[WeaponBase.WeaponTag] = []
|
||||
|
||||
|
||||
static func get_calculated(weapon: WeaponBase, key: String) -> Variant:
|
||||
assert(
|
||||
weapon.has_property(key),
|
||||
"tried calculate property '%s' where base value does not exist on %s" % [key, weapon]
|
||||
)
|
||||
var base_value = weapon.get(key)
|
||||
|
||||
var add = 0.0
|
||||
var mul = 1.0
|
||||
for mod in weapon.modifiers:
|
||||
if mod.mod_property == key:
|
||||
match mod.mod_type:
|
||||
ModType.ADDITIVE:
|
||||
add += mod.mod_value
|
||||
ModType.MULTIPLICATIVE:
|
||||
mul *= mod.mod_value
|
||||
ModType.ABSOLUTE:
|
||||
return mod.mod_value
|
||||
ModType.BOOL:
|
||||
return mod.mod_value_bool
|
||||
return (base_value + add) * mul
|
1
scenes/weapons/weapon_mod_base.gd.uid
Normal file
1
scenes/weapons/weapon_mod_base.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b1cg1bhnt6xk4
|
@@ -8,13 +8,12 @@ const WEAPON_SWORD_PROJECTILE = preload("res://scenes/weapons/weapon_sword_proje
|
||||
|
||||
signal projectile_hit(projectile: WeaponSwordProjectile, enemy: EnemyBase)
|
||||
|
||||
var _player: Player
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
bleed_chance = 0.2
|
||||
targeting_range_shape.shape.radius = attack_range
|
||||
projectile_hit.connect(_on_projectile_hit)
|
||||
_player = get_tree().get_first_node_in_group(GlobalConst.GROUP_PLAYER)
|
||||
super._ready()
|
||||
|
||||
|
||||
func do_attack() -> void:
|
||||
@@ -23,20 +22,35 @@ func do_attack() -> void:
|
||||
return
|
||||
|
||||
var projectile = WEAPON_SWORD_PROJECTILE.instantiate()
|
||||
projectile.damage = attack_damage
|
||||
projectile.target = target
|
||||
projectile.on_hit_sig = projectile_hit
|
||||
add_child(projectile)
|
||||
|
||||
|
||||
func deal_damage(enemy: EnemyBase):
|
||||
var crit_chance = _player.player_stats.get_final("crit_chance", _player.modifiers)
|
||||
var damage_dealt = attack_damage
|
||||
var is_crit = randf() >= 1 - crit_chance
|
||||
if is_crit:
|
||||
damage_dealt *= _player.player_stats.get_final("crit_multiplier", _player.modifiers)
|
||||
enemy.take_damage(damage_dealt, is_crit)
|
||||
func _do_active() -> void:
|
||||
var radius = targeting_range_shape.shape.radius
|
||||
var count = 15
|
||||
for i in count:
|
||||
var angle = TAU * float(i) / float(count)
|
||||
var target_pos = Vector2(cos(angle), sin(angle)) * radius
|
||||
var new_target := Marker2D.new()
|
||||
new_target.global_position = global_position + target_pos
|
||||
var projectile = WEAPON_SWORD_PROJECTILE.instantiate()
|
||||
projectile.damage_mult = 3.0
|
||||
projectile.target = new_target
|
||||
projectile.on_hit_sig = projectile_hit
|
||||
add_child(projectile)
|
||||
|
||||
|
||||
func _on_projectile_hit(projectile: WeaponSwordProjectile, enemy: EnemyBase):
|
||||
deal_damage(enemy)
|
||||
func deal_damage(enemy: EnemyBase, damage_mult: float):
|
||||
var damage_and_crit = base_damage()
|
||||
# TODO: Fix crit value
|
||||
enemy.take_damage(damage_and_crit[0], damage_and_crit[1])
|
||||
|
||||
if did_bleed():
|
||||
var bleed = EnemyEffectBleed.new(enemy, damage_and_crit[0], bleed_duration)
|
||||
bleed.apply(enemy)
|
||||
|
||||
|
||||
func _on_projectile_hit(projectile: WeaponSwordProjectile, enemy: EnemyBase, damage_mult: float):
|
||||
deal_damage(enemy, damage_mult)
|
||||
|
@@ -4,7 +4,8 @@ extends Node2D
|
||||
@export var speed: float = 500.0
|
||||
@export var range: float = 200.0
|
||||
@export var target: Node2D
|
||||
@export var damage: float
|
||||
@export var damage_mult: float = 1.0
|
||||
@export var bleed_chance: float = 0.2
|
||||
@export var on_hit_sig: Signal
|
||||
|
||||
var _direction: Vector2
|
||||
@@ -36,5 +37,5 @@ func _physics_process(delta: float) -> void:
|
||||
func _on_area_2d_body_entered(body: Node2D) -> void:
|
||||
if body in _already_hit:
|
||||
return
|
||||
on_hit_sig.emit(self, body)
|
||||
on_hit_sig.emit(self, body, damage_mult)
|
||||
_already_hit.append(body)
|
||||
|
@@ -1,17 +1,31 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://du4dqne3o0d5b"]
|
||||
[gd_scene load_steps=7 format=3 uid="uid://du4dqne3o0d5b"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bx43us4qd2vpf" path="res://scenes/xp_orb.gd" id="1_me6n8"]
|
||||
[ext_resource type="Texture2D" uid="uid://c0g255217x10l" path="res://assets/sprites/xp_orb_small.png" id="2_me6n8"]
|
||||
[ext_resource type="Shader" uid="uid://ckjyt0mt0ngk" path="res://assets/shaders/xp_orb.gdshader" id="2_me6n8"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_pkphv"]
|
||||
shader = ExtResource("2_me6n8")
|
||||
shader_parameter/orb_color = Color(0.2, 1, 0.5, 1)
|
||||
shader_parameter/glow_strength = 1.0
|
||||
shader_parameter/pulse_speed = 2.0
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_me6n8"]
|
||||
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 1)
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture2D_hluwh"]
|
||||
gradient = SubResource("Gradient_me6n8")
|
||||
width = 12
|
||||
height = 12
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_me6n8"]
|
||||
radius = 5.0
|
||||
radius = 4.0
|
||||
|
||||
[node name="XPOrb" type="Node2D" groups=["pickup", "xp_orb"]]
|
||||
script = ExtResource("1_me6n8")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
position = Vector2(-1, 1)
|
||||
texture = ExtResource("2_me6n8")
|
||||
material = SubResource("ShaderMaterial_pkphv")
|
||||
texture = SubResource("GradientTexture2D_hluwh")
|
||||
|
||||
[node name="Area2D" type="Area2D" parent="."]
|
||||
collision_layer = 9
|
||||
|
Reference in New Issue
Block a user