added fixed dash targets and can dash towards enemies to hit them, get a knockback or dash through if killed
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 1m51s

This commit is contained in:
2026-01-21 16:46:20 +01:00
parent fb78add739
commit db49703326
19 changed files with 370 additions and 60 deletions

View File

@@ -1,9 +1,12 @@
[gd_scene load_steps=58 format=3 uid="uid://bei4nhkf8lwdo"]
[gd_scene load_steps=64 format=3 uid="uid://bei4nhkf8lwdo"]
[ext_resource type="Script" uid="uid://bbbrf5ckydfna" path="res://player_controller/Scripts/PlayerController.cs" id="1_poq2x"]
[ext_resource type="PackedScene" uid="uid://cf3rrgr1imvv4" path="res://scenes/path/path.tscn" id="2_6lejt"]
[ext_resource type="Script" uid="uid://jitubgv6judn" path="res://components/damage/RDamage.cs" id="2_x835q"]
[ext_resource type="Script" uid="uid://b44cse62qru7j" path="res://components/knockback/RKnockback.cs" id="3_cb2lu"]
[ext_resource type="Resource" uid="uid://bl5crtu1gkrtr" path="res://systems/inputs/base_mode/base_mode.tres" id="3_cresl"]
[ext_resource type="PackedScene" uid="uid://c4ikbhojckpnc" path="res://components/health/CHealth.tscn" id="3_q7bng"]
[ext_resource type="Script" uid="uid://baiapod3csndf" path="res://components/health/RHealth.cs" id="4_abfq8"]
[ext_resource type="Resource" uid="uid://bjyd801wvverk" path="res://player_controller/resources/player_health.tres" id="4_m8gvy"]
[ext_resource type="Resource" uid="uid://cpdaw41ah5gic" path="res://systems/inputs/base_mode/rotate_y.tres" id="4_rxwoh"]
[ext_resource type="Resource" uid="uid://ccrb5xsnphc8" path="res://systems/inputs/base_mode/rotate_floorplane.tres" id="5_4u7i3"]
@@ -49,6 +52,21 @@
[ext_resource type="Script" uid="uid://b4dwolbvt8our" path="res://addons/godot_state_charts/history_state.gd" id="41_ruloh"]
[ext_resource type="Texture2D" uid="uid://buu21kg4kkhiw" path="res://guide_examples/shared/fireball/fireball.svg" id="42_cmijs"]
[sub_resource type="Resource" id="Resource_cb2lu"]
script = ExtResource("2_x835q")
DamageDealt = 3.0
metadata/_custom_type_script = "uid://jitubgv6judn"
[sub_resource type="Resource" id="Resource_abfq8"]
script = ExtResource("3_cb2lu")
Modifier = 10.0
metadata/_custom_type_script = "uid://b44cse62qru7j"
[sub_resource type="Resource" id="Resource_ue7xq"]
script = ExtResource("4_abfq8")
StartingHealth = 10.0
metadata/_custom_type_script = "uid://baiapod3csndf"
[sub_resource type="CapsuleMesh" id="CapsuleMesh_xc2g5"]
height = 1.7
@@ -58,9 +76,6 @@ radius = 0.45
[sub_resource type="SphereShape3D" id="SphereShape3D_q14ux"]
radius = 1.0
[sub_resource type="SphereShape3D" id="SphereShape3D_cmijs"]
radius = 1.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_nodcl"]
transparency = 1
albedo_color = Color(0, 0.627451, 0.6313726, 0.49019608)
@@ -71,6 +86,9 @@ top_radius = 0.2
bottom_radius = 0.2
height = 1.0
[sub_resource type="SphereShape3D" id="SphereShape3D_cmijs"]
radius = 1.0
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_6lejt"]
radius = 1.0
height = 3.5
@@ -84,6 +102,9 @@ blend_mode = 1
[node name="Player" type="CharacterBody3D"]
collision_mask = 272
script = ExtResource("1_poq2x")
RDamage = SubResource("Resource_cb2lu")
RKnockback = SubResource("Resource_abfq8")
RHealth = SubResource("Resource_ue7xq")
WalkSpeed = 7.5
AccelerationFloor = 4.0
DecelerationFloor = 3.0
@@ -206,13 +227,6 @@ monitorable = false
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -1.5)
shape = SubResource("SphereShape3D_q14ux")
[node name="CloseEnemyDetector" type="ShapeCast3D" parent="."]
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0)
shape = SubResource("SphereShape3D_cmijs")
target_position = Vector3(0, 0, -5)
collision_mask = 16
[node name="StairsSystem" type="Node3D" parent="."]
script = ExtResource("7_bmt5a")
@@ -307,6 +321,13 @@ visible = false
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 0, -1)
mesh = SubResource("CylinderMesh_nodcl")
[node name="CloseEnemyDetector" type="ShapeCast3D" parent="."]
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0)
shape = SubResource("SphereShape3D_cmijs")
target_position = Vector3(0, 0, -5)
collision_mask = 48
[node name="GroundDetector" type="ShapeCast3D" parent="."]
shape = SubResource("CapsuleShape3D_6lejt")
collision_mask = 256
@@ -446,7 +467,7 @@ layout_mode = 2
[node name="EnemyTarget" type="TextureRect" parent="UI"]
unique_name_in_owner = true
modulate = Color(0.9882353, 0, 0.10980392, 1)
modulate = Color(0, 0.61278194, 0.56044877, 1)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
@@ -537,6 +558,49 @@ to = NodePath("../../AtLeastOneCharge")
event = &"power_used"
delay_in_seconds = "0.0"
[node name="Attack" type="Node" parent="StateChart/Root"]
script = ExtResource("26_infe6")
initial_state = NodePath("Ready")
[node name="Ready" type="Node" parent="StateChart/Root/Attack"]
script = ExtResource("27_34snm")
[node name="ToStandardAttack" type="Node" parent="StateChart/Root/Attack/Ready"]
script = ExtResource("28_n7qhm")
to = NodePath("../../StandardAttack")
event = &"standard_attack"
delay_in_seconds = "0.0"
[node name="ToDashAttack" type="Node" parent="StateChart/Root/Attack/Ready"]
script = ExtResource("28_n7qhm")
to = NodePath("../../DashAttack")
event = &"dash_attack"
delay_in_seconds = "0.0"
[node name="StandardAttack" type="Node" parent="StateChart/Root/Attack"]
script = ExtResource("27_34snm")
[node name="ToReady" type="Node" parent="StateChart/Root/Attack/StandardAttack"]
script = ExtResource("28_n7qhm")
to = NodePath("../../Ready")
event = &"attack_finished"
delay_in_seconds = "0.0"
[node name="ToDashAttack" type="Node" parent="StateChart/Root/Attack/StandardAttack"]
script = ExtResource("28_n7qhm")
to = NodePath("../../DashAttack")
event = &"dash_attack"
delay_in_seconds = "0.0"
[node name="DashAttack" type="Node" parent="StateChart/Root/Attack"]
script = ExtResource("27_34snm")
[node name="ToReady" type="Node" parent="StateChart/Root/Attack/DashAttack"]
script = ExtResource("28_n7qhm")
to = NodePath("../../Ready")
event = &"attack_finished"
delay_in_seconds = "0.0"
[node name="Movement" type="Node" parent="StateChart/Root"]
script = ExtResource("26_infe6")
initial_state = NodePath("Grounded")

View File

@@ -11,10 +11,18 @@ public partial class PlayerUi : Control
NoTarget,
TargetTooFar,
TargetInRange,
TargetDashThrough
}
public record TargetProperties(TargetState State, Vector2 Position);
[Export]
public Color DashThroughColor { get; set; } = new Color("009c8f");
[Export]
public Color DashBlockedColor { get; set; } = new Color("fc001c");
[Export]
public Color DashOutOfRangeColor { get; set; } = new Color("ffffff");
public override void _Ready()
{
_dashIcons[0] = GetNode<TextureRect>("%Dash1");
@@ -29,7 +37,14 @@ public partial class PlayerUi : Control
var (state, position) = targetProperties;
var visible = state != TargetState.NoTarget;
var modulation = state == TargetState.TargetInRange ? new Color("ffffff") : new Color("fc001c");
var modulation = state switch
{
TargetState.TargetTooFar => DashOutOfRangeColor,
TargetState.TargetInRange => DashBlockedColor,
TargetState.TargetDashThrough => DashThroughColor,
_ => DashOutOfRangeColor
};
_enemyTarget.SetVisible(visible);
_enemyTarget.SetPosition(position - _enemyTarget.Size / 2);
_enemyTarget.SetModulate(modulation);

View File

@@ -7,6 +7,7 @@ using Movementtests.addons.godot_state_charts.csharp;
using Movementtests.interfaces;
using Movementtests.systems;
using Movementtests.player_controller.Scripts;
using Movementtests.systems.damage;
using RustyOptions;
public partial class PlayerController : CharacterBody3D,
@@ -323,6 +324,11 @@ public partial class PlayerController : CharacterBody3D,
private StateChartState _onWallHanging;
private StateChartState _onWallRunning;
private StateChartState _attack;
private StateChartState _attackReady;
private StateChartState _attackStandard;
private StateChartState _attackDash;
private Transition _onDashEnded;
private Transition _onJumpFromWall;
@@ -340,7 +346,6 @@ public partial class PlayerController : CharacterBody3D,
public float CurrentHealth { get; set; }
private bool _isInvincible;
private bool _canAttack = true;
private readonly List<IDamageable> _hitEnemies = new List<IDamageable>();
private ShapeCast3D _closeEnemyDetector;
@@ -464,6 +469,12 @@ public partial class PlayerController : CharacterBody3D,
_onLeaveWallFromRun = Transition.Of(GetNode("StateChart/Root/Movement/OnWall/Running/OnLeaveWall"));
_onAirborneToGrounded = Transition.Of(GetNode("StateChart/Root/Movement/Airborne/OnGrounded"));
// Attack states
_attack = StateChartState.Of(GetNode("StateChart/Root/Attack"));
_attackReady = StateChartState.Of(GetNode("StateChart/Root/Attack/Ready"));
_attackStandard = StateChartState.Of(GetNode("StateChart/Root/Attack/StandardAttack"));
_attackDash = StateChartState.Of(GetNode("StateChart/Root/Attack/DashAttack"));
// State timers
_powerCooldownTimer = GetNode<Timer>("PowerCooldown");
_timeScaleAimInAirTimer = GetNode<Timer>("TimeScaleAimInAir");
@@ -570,7 +581,11 @@ public partial class PlayerController : CharacterBody3D,
_onJumpFromWall.Taken += OnJumpFromWall;
_onJumpFromWallFalling.Taken += OnJumpFromWall;
_onLeaveWallFromRun.Taken += OnLeaveWallFromRun;
_onAirborneToGrounded.Taken += OnAirborneToGrounded;
_onAirborneToGrounded.Taken += OnAirborneToGrounded;
// Attack states
_attackStandard.StateEntered += OnStandardAttackStarted;
_attackDash.StateEntered += OnDashAttackStarted;
}
///////////////////////////
@@ -1705,9 +1720,13 @@ public partial class PlayerController : CharacterBody3D,
}
public void OnAimedDashFinished()
{
var postDashVelocity = _preDashVelocity.Length() > PostDashSpeed ? PostDashSpeed : _preDashVelocity.Length();
if (_customMantle)
{
_playerState.SendEvent("mantle");
return;
}
var postDashVelocity = _preDashVelocity.Length() > PostDashSpeed ? _preDashVelocity.Length() : PostDashSpeed;
Velocity = _dashDirection * postDashVelocity;
if (_customMantle) _playerState.SendEvent("mantle");
}
// Weapon dashing
@@ -1823,8 +1842,18 @@ public partial class PlayerController : CharacterBody3D,
DashIndicatorNode.LookAt(WeaponSystem.GlobalPosition);
}
}
///////////////////////////
// Hit Management ///////
///////////////////////////
private bool _isEnemyInDashAttackRange;
private Vector3 _targetHitLocation;
private Vector3 _targetLocation;
private Object _targetObject;
public void HandleEnemyTargeting()
{
_isEnemyInDashAttackRange = false;
_closeEnemyDetector.SetRotation(HeadSystem.GetGlobalLookRotation());
var enemyTargetState = PlayerUi.TargetState.NoTarget;
@@ -1834,24 +1863,35 @@ public partial class PlayerController : CharacterBody3D,
PlayerUi.SetEnemyTargetProperties(new PlayerUi.TargetProperties(enemyTargetState, positionOnScreen));
return;
}
var collidedObject = _closeEnemyDetector.GetCollider(0);
if (collidedObject is not ITargetable target)
_targetHitLocation = _closeEnemyDetector.GetCollisionPoint(0);
_targetObject = _closeEnemyDetector.GetCollider(0);
if (_targetObject is not ITargetable target)
{
PlayerUi.SetEnemyTargetProperties(new PlayerUi.TargetProperties(enemyTargetState, positionOnScreen));
return;
}
var targetPos = target.GetTargetGlobalPosition();
var targetDistance = targetPos.DistanceTo(GlobalPosition);
enemyTargetState = targetDistance > TargetInRangeDistance ? PlayerUi.TargetState.TargetInRange : PlayerUi.TargetState.TargetTooFar;
positionOnScreen = _camera.UnprojectPosition(targetPos);
_targetLocation = target.GetTargetGlobalPosition();
var targetDistance = _targetLocation.DistanceTo(GlobalPosition);
positionOnScreen = _camera.UnprojectPosition(_targetLocation);
_isEnemyInDashAttackRange = targetDistance < TargetInRangeDistance;
if (_isEnemyInDashAttackRange)
{
enemyTargetState = PlayerUi.TargetState.TargetDashThrough;
if (_targetObject is IDamageable damageable && _targetObject is IHealthable healthable)
{
var wouldBeDamage = damageable.ComputeDamage(new DamageRecord(this, RDamage));
if (wouldBeDamage.Damage.DamageDealt < healthable.CurrentHealth)
enemyTargetState = PlayerUi.TargetState.TargetInRange;
}
}
else enemyTargetState = PlayerUi.TargetState.TargetTooFar;
PlayerUi.SetEnemyTargetProperties(new PlayerUi.TargetProperties(enemyTargetState, positionOnScreen));
}
///////////////////////////
// Hit Management ///////
///////////////////////////
public DamageRecord TakeDamage(DamageRecord damageRecord)
{
if (_isInvincible)
@@ -1868,11 +1908,71 @@ public partial class PlayerController : CharacterBody3D,
return finalDamage;
}
public DamageRecord ComputeDamage(DamageRecord damageRecord)
{
return CDamageable.ComputeDamage(damageRecord);
}
public void OnHitInvincibility()
{
_isInvincible = true;
_invincibilityTimer.Start();
}
public void OnStandardAttackStarted()
{
_attackCooldown.Start();
HeadSystem.OnHit();
_audioStream!.SwitchToClipByName("attacks");
}
public void OnDashAttackStarted()
{
_audioStream!.SwitchToClipByName("attacks");
_isInvincible = true;
var actualDashLocation = _targetLocation + Vector3.Down*HeadSystem.Position.Y;
var travel = actualDashLocation - GlobalPosition;
_preDashVelocity = Velocity;
_dashDirection = travel.Normalized();
var dashTween = CreatePositionTween(actualDashLocation, AimedDashTime);
dashTween.Finished += OnDashAttackEnded;
}
public void OnDashAttackEnded()
{
if (_targetObject is IDamageable damageable)
{
_hitEnemies.Add(damageable);
TriggerDamage();
}
if (_targetObject is IStunnable stunnable)
{
stunnable.Stun();
}
var shouldKnockback = false;
if (_targetObject is IHealthable healthable)
{
if (healthable.CurrentHealth > 0) shouldKnockback = true;
}
if (shouldKnockback)
{
Velocity = -_dashDirection*RKnockback.Modifier;
}
else
{
var locationOtherSide = _targetLocation + (_targetLocation - _targetHitLocation);
GlobalPosition = locationOtherSide;
var postDashVelocity = _preDashVelocity.Length() > PostDashSpeed ? _preDashVelocity.Length() : PostDashSpeed;
Velocity = _dashDirection * postDashVelocity;
}
_isInvincible = false;
_playerState.SendEvent("attack_finished");
}
public void OnInputHitPressed()
{
@@ -1881,24 +1981,15 @@ public partial class PlayerController : CharacterBody3D,
ThrowWeapon();
}
if (!WeaponSystem.InHandState.Active) return;
if (!_canAttack) return;
_canAttack = false;
_attackCooldown.Start();
PerformHit();
var attackToDo = _isEnemyInDashAttackRange ? "dash_attack" : "standard_attack";
_playerState.SendEvent(attackToDo);
}
public void ResetAttackCooldown()
{
_canAttack = true;
_playerState.SendEvent("attack_finished");
}
public void PerformHit()
{
HeadSystem.OnHit();
_audioStream!.SwitchToClipByName("attacks");
}
public void OnHitboxActivated()
{
@@ -1942,6 +2033,7 @@ public partial class PlayerController : CharacterBody3D,
}
public void ReduceHealth(IDamageable source, DamageRecord damageRecord)
{
GD.Print("That's NOT fine");
CHealth.ReduceHealth(source, damageRecord);
HealthChanged?.Invoke(this, CHealth.CurrentHealth);
}
@@ -1952,7 +2044,8 @@ public partial class PlayerController : CharacterBody3D,
public Vector3 ComputeKnockback()
{
return CKnockback.ComputeKnockback();
var kb = CKnockback.ComputeKnockback();
return kb;
}
public void Kill(IHealthable source)