convert game playing..

This commit is contained in:
2026-09-23 12:53:52 +09:00
parent 37c7942b71
commit 5d11288a6f
136 changed files with 15856 additions and 299 deletions
@@ -5,6 +5,7 @@ using BumpCombat.Combat;
using BumpCombat.Constants;
using BumpCombat.Core;
using BumpCombat.Enemies;
using BumpCombat.Progression;
using UnityEngine;
using UnityEngine.InputSystem;
@@ -339,6 +340,13 @@ namespace BumpCombat.Player
{
UpdateTemporaryPlayerModifiers();
if (playerHealth != null && playerHealth.CurrentHealth <= 0f)
{
CancelArtifactInput();
CancelExecution();
return;
}
if (playerHealth != null && playerHealth.IsHurtMovementLocked)
{
CancelArtifactInput();
@@ -450,6 +458,7 @@ namespace BumpCombat.Player
if (definition == null
|| IsExecutingArtifact
|| (dashController != null && dashController.IsDashing)
|| (playerHealth != null && playerHealth.CurrentHealth <= 0f)
|| (playerHealth != null && playerHealth.IsHurtMovementLocked)
|| (charged && !IsArtifactChargeUnlocked))
{
@@ -546,7 +555,7 @@ namespace BumpCombat.Player
return true;
}
private int GetOwnedArtifactColorCount(ArtifactColor color)
public int GetOwnedArtifactColorCount(ArtifactColor color)
{
int count = 0;
for (int i = 0; i < ownedArtifacts.Count; i++)
@@ -1168,6 +1177,7 @@ namespace BumpCombat.Player
float nextHitAt = 0f;
int hitIndex = 0;
int hitCount = GetScheduledHitCount(duration, hitInterval);
bool movementBlocked = false;
while (elapsed < duration)
{
visual.transform.position = body.position
@@ -1206,10 +1216,24 @@ namespace BumpCombat.Player
yield return new WaitForFixedUpdate();
elapsed += Time.fixedDeltaTime;
body.MovePosition(Vector2.Lerp(
start,
end,
Mathf.Clamp01(elapsed / duration)));
if (!movementBlocked
&& playerController != null
&& !playerController.IsTransitionLocked)
{
Vector2 intendedEnd = Vector2.Lerp(
start,
end,
Mathf.Clamp01(elapsed / duration));
Vector2 clampedEnd = playerController.ClampMovementToTerrain(
body.position,
intendedEnd);
movementBlocked = (clampedEnd - intendedEnd).sqrMagnitude > 0.0001f;
body.MovePosition(clampedEnd);
}
else if (playerController != null && playerController.IsTransitionLocked)
{
movementBlocked = true;
}
visual.transform.position = body.position
+ GetCycloneShadowOffsetWorld();
}
@@ -1236,7 +1260,6 @@ namespace BumpCombat.Player
hitIndex++;
}
body.position = end;
DestroyVisual(visual);
EndExecution();
}
@@ -2190,6 +2213,134 @@ namespace BumpCombat.Player
return false;
}
public bool TryEmitPassiveRelicEcho(
Vector2 origin,
Vector2 direction,
float damageMultiplier)
{
ActiveArtifactDefinition definition = CurrentArtifact;
if (definition == null
|| definition.NormalDamage <= 0f
|| damageMultiplier <= 0f
|| definition.Effect == ActiveArtifactEffect.Pulse)
{
return false;
}
if (direction.sqrMagnitude < MinimumDirectionSqrMagnitude)
{
direction = definition.Effect == ActiveArtifactEffect.ThunderCrash
? GetThunderCrashDirection()
: GetUseDirection();
}
direction.Normalize();
int castIdentity = ++nextArtifactCastIdentity;
float echoIgniteTickDamage = playerStats.CalculateDamage(
definition.NormalIgniteTickDamage * damageMultiplier,
definition.DamageTag);
if (definition.Effect == ActiveArtifactEffect.ChainLightning)
{
EnemyController first = FindInitialChainTarget(origin, definition.NormalRange);
if (first == null)
{
return false;
}
bool applied = false;
foreach (ChainStrike strike in PlanArcChain(definition, first, origin))
{
applied |= ApplyArtifactHit(
strike.Enemy,
definition,
strike.Damage * damageMultiplier,
(strike.End - strike.Start).normalized,
strike.Knockback,
true,
false,
echoIgniteTickDamage,
castIdentity,
true);
}
return applied;
}
Vector2 center = origin;
Vector2 radii = new Vector2(definition.NormalRange, definition.NormalRange);
float rayRange = definition.NormalRange;
float rayHalfWidth = Mathf.Max(0.05f, definition.NormalWidth * 0.5f);
switch (definition.Effect)
{
case ActiveArtifactEffect.Cyclone:
center += GetCycloneHitCenterOffset(definition.NormalRange, false);
radii = GetCycloneHitRadii(definition.NormalRange, false);
break;
case ActiveArtifactEffect.ThunderCrash:
center = origin + direction * 0.45f;
radii = GetThunderCrashHitRadii(definition.NormalRange, direction);
break;
}
List<EnemyController> enemies = FindNearestTargets(
definition.Effect == ActiveArtifactEffect.Dash
|| definition.Effect == ActiveArtifactEffect.Phoenix
? origin + direction * (rayRange * 0.5f)
: center,
Mathf.Max(radii.x, radii.y, rayRange),
null);
int hitCount = 0;
bool anyDamage = false;
foreach (EnemyController enemy in enemies)
{
if (hitCount >= definition.NormalMaxTargets)
{
break;
}
Vector2 position = enemy.GroundAnchorPosition;
Vector2 offset = position - origin;
bool inside = definition.Effect switch
{
ActiveArtifactEffect.Dash or ActiveArtifactEffect.Phoenix =>
Vector2.Dot(offset, direction) >= 0f
&& Vector2.Dot(offset, direction) <= rayRange
&& Mathf.Abs(Vector2.Dot(offset, new Vector2(-direction.y, direction.x))) <= rayHalfWidth,
_ => GetEllipseDistance(position - center, radii) <= 1.0001f,
};
if (!inside)
{
continue;
}
Vector2 hitDirection = definition.Effect == ActiveArtifactEffect.Dash
|| definition.Effect == ActiveArtifactEffect.Phoenix
? direction
: (position - center).normalized;
if (hitDirection.sqrMagnitude < MinimumDirectionSqrMagnitude)
{
hitDirection = direction;
}
bool damaged = ApplyArtifactHit(
enemy,
definition,
definition.NormalDamage * damageMultiplier,
hitDirection,
definition.NormalKnockback,
true,
false,
echoIgniteTickDamage,
castIdentity,
true);
anyDamage |= damaged;
if (damaged)
{
hitCount++;
}
}
return anyDamage;
}
private bool ApplyArtifactHit(
EnemyController enemy,
ActiveArtifactDefinition definition,
@@ -2199,7 +2350,8 @@ namespace BumpCombat.Player
bool showsHitFeedback = true,
bool charged = false,
float igniteTickDamage = 0f,
int castIdentity = 0)
int castIdentity = 0,
bool passiveRelicEcho = false)
{
if (enemy == null || enemy.IsDead)
{
@@ -2219,7 +2371,9 @@ namespace BumpCombat.Player
direction,
knockback,
definition.ArtifactColor,
castIdentity);
castIdentity,
true,
EnemyDamageSource.Player);
if (result == EnemyArtifactHitResult.Rejected)
{
return false;
@@ -2233,7 +2387,7 @@ namespace BumpCombat.Player
if (result == EnemyArtifactHitResult.Shielded)
{
if (enemy.LastArtifactContactCounted)
if (!passiveRelicEcho && enemy.LastArtifactContactCounted)
{
CombatEvents.RaiseArtifactEffectiveHit(
enemy.gameObject,
@@ -2262,7 +2416,7 @@ namespace BumpCombat.Player
igniteTickDamage);
}
if (damageWasApplied)
if (damageWasApplied && !passiveRelicEcho)
{
CombatEvents.RaiseArtifactEffectiveHit(
enemy.gameObject,
@@ -2283,7 +2437,7 @@ namespace BumpCombat.Player
false,
false,
definition.DamageTag,
definition.ArtifactId,
passiveRelicEcho ? PassiveRelicController.EchoSourceId : definition.ArtifactId,
false,
result == EnemyArtifactHitResult.DamageApplied
&& showsHitFeedback,
@@ -44,7 +44,8 @@ namespace BumpCombat.Player
|| IsDashing
|| playerController == null
|| playerController.IsDamageKnockbackActive
|| playerController.IsHurtMovementLocked)
|| playerController.IsHurtMovementLocked
|| playerController.IsTransitionLocked)
{
return false;
}
@@ -81,19 +82,29 @@ namespace BumpCombat.Player
: dashDuration;
Vector2 start = body.position;
Vector2 end = playerController.ClampToArena(start + direction * distance);
Vector2 finalPosition = start;
combatResolver.BeginDashPathContactSession();
float elapsed = 0f;
while (elapsed < duration)
{
yield return new WaitForFixedUpdate();
if (playerController.IsTransitionLocked)
{
break;
}
combatFeedback.EmitDashAfterimage(body.position, isStrongDash);
Vector2 segmentStart = body.position;
elapsed += Time.fixedDeltaTime;
Vector2 segmentEnd = Vector2.Lerp(
Vector2 intendedEnd = Vector2.Lerp(
start,
end,
Mathf.Clamp01(elapsed / duration));
Vector2 segmentEnd = playerController.ClampMovementToTerrain(
segmentStart,
intendedEnd);
bool blocked = (segmentEnd - intendedEnd).sqrMagnitude > 0.0001f;
Vector2 segment = segmentEnd - segmentStart;
if (segment.sqrMagnitude > 0.000001f)
{
@@ -116,9 +127,14 @@ namespace BumpCombat.Player
}
body.MovePosition(segmentEnd);
finalPosition = segmentEnd;
if (blocked)
{
break;
}
}
body.position = end;
body.position = finalPosition;
IsDashing = false;
IsStrongDashing = false;
animator.SetBool(IsDashingParameter, false);
@@ -16,6 +16,8 @@ namespace BumpCombat.Player
Animator.StringToHash("Base Layer.BackBumpAttack");
private static readonly int ArtifactUseState =
Animator.StringToHash("Base Layer.ArtifactUse");
private static readonly int DeathState =
Animator.StringToHash("Base Layer.Death");
public const string BumpAttackStateName = "BumpAttack";
public const string BackBumpAttackStateName = "BackBumpAttack";
@@ -45,6 +47,7 @@ namespace BumpCombat.Player
public bool IsDamageKnockbackActive { get; private set; }
public bool IsHurtMovementLocked =>
playerHealth != null && playerHealth.IsHurtMovementLocked;
public bool IsTransitionLocked { get; private set; }
public Vector2 PlayerClampHalfExtents
{
get
@@ -95,6 +98,23 @@ namespace BumpCombat.Player
return TryPlayCombatAnimation(ArtifactUseState, false);
}
public float PlayDeathAnimation()
{
if (animator == null
|| animator.runtimeAnimatorController == null
|| !animator.isActiveAndEnabled
|| !animator.HasState(0, DeathState))
{
return 0f;
}
SetTransitionLocked(true);
animator.SetBool(IsMovingParameter, false);
animator.Play(DeathState, 0, 0f);
animator.Update(0f);
return Mathf.Max(0f, animator.GetCurrentAnimatorStateInfo(0).length);
}
private bool TryPlayCombatAnimation(
int stateHash,
bool blockActiveArtifactUse)
@@ -148,7 +168,7 @@ namespace BumpCombat.Player
private void Update()
{
if (!RunManager.GameplayInputEnabled || IsHurtMovementLocked)
if (!RunManager.GameplayInputEnabled || IsHurtMovementLocked || IsTransitionLocked)
{
SetMoveInput(Vector2.zero);
return;
@@ -172,6 +192,13 @@ namespace BumpCombat.Player
private void FixedUpdate()
{
AttackIntentVelocity = Vector2.zero;
if (IsTransitionLocked)
{
if (body != null) body.linearVelocity = Vector2.zero;
CurrentVelocity = Vector2.zero;
return;
}
if (dashController != null && dashController.IsDashing)
{
pendingImpactRecoil = Vector2.zero;
@@ -244,6 +271,57 @@ namespace BumpCombat.Player
- GameplayConstants.Current.Player.ArenaPadding.y));
}
public Vector2 ClampMovementToTerrain(Vector2 start, Vector2 desiredPosition)
{
Vector2 desired = ClampToArena(desiredPosition);
Vector2 delta = desired - start;
float distance = delta.magnitude;
if (distance <= 0.0001f)
{
return desired;
}
Collider2D ownCollider = GetComponent<Collider2D>();
float radius = 0.2f;
if (ownCollider != null)
{
Vector2 extents = ownCollider.bounds.extents;
radius = Mathf.Max(0.05f, Mathf.Min(extents.x, extents.y) * 0.9f);
}
Vector2 direction = delta / distance;
RaycastHit2D[] hits = Physics2D.CircleCastAll(
start,
radius,
direction,
distance,
Physics2D.AllLayers);
float allowedDistance = distance;
for (int i = 0; i < hits.Length; i++)
{
Collider2D collider = hits[i].collider;
if (collider == null
|| collider.isTrigger
|| collider == ownCollider
|| collider.transform.IsChildOf(transform))
{
continue;
}
bool isTerrain = collider.GetComponentInParent<BumpCombat.Stage.StageObstacle>() != null
|| collider.name.StartsWith("Wall_")
|| collider.gameObject.name.Contains("Wall");
if (isTerrain)
{
allowedDistance = Mathf.Min(
allowedDistance,
Mathf.Max(0f, hits[i].distance - 0.02f));
}
}
return start + direction * allowedDistance;
}
public bool TryReposition(Vector2 position)
{
if (body == null)
@@ -338,5 +416,36 @@ namespace BumpCombat.Player
spriteRenderer.flipX = input.x < 0f;
}
}
public void SetTransitionLocked(bool locked)
{
IsTransitionLocked = locked;
if (locked)
{
moveInput = Vector2.zero;
CurrentVelocity = Vector2.zero;
AttackIntentVelocity = Vector2.zero;
if (body != null)
{
body.linearVelocity = Vector2.zero;
}
if (animator != null && animator.isActiveAndEnabled)
{
animator.SetBool(IsMovingParameter, false);
}
}
}
public void SetFacingDirection(Vector2 direction)
{
if (direction.sqrMagnitude > 0.0001f)
{
FacingDirection = direction.normalized;
if (spriteRenderer != null && !Mathf.Approximately(direction.x, 0f))
{
spriteRenderer.flipX = direction.x < 0f;
}
}
}
}
}
+43 -1
View File
@@ -2,6 +2,7 @@ using System;
using BumpCombat.Combat;
using BumpCombat.Core;
using BumpCombat.Constants;
using BumpCombat.Defense;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
@@ -235,8 +236,29 @@ namespace BumpCombat.Player
if (CurrentHealth <= 0f)
{
float deathAnimationDuration = playerController.PlayDeathAnimation();
OnDied?.Invoke();
RunManager.Instance?.EndRun();
RunManager runManager = RunManager.Instance;
if (runManager != null
&& runManager.IsDefensePrototypeRun
&& DefensePrototypeController.Instance != null
&& DefensePrototypeController.Instance.HandlePlayerDied())
{
return true;
}
if (runManager == null)
{
return true;
}
if (deathAnimationDuration > 0f)
{
runManager.BeginGameOverPresentation(deathAnimationDuration);
}
else
{
runManager.EndRun();
}
}
return true;
@@ -247,6 +269,26 @@ namespace BumpCombat.Player
return value >= 0f ? value : configuredValue;
}
public bool ReviveForDefense(float invulnerabilitySeconds)
{
if (CurrentHealth > 0f)
{
return false;
}
CurrentHealth = MaxHealth;
invulnerableUntil = Time.time + Mathf.Max(0f, invulnerabilitySeconds);
hurtMovementLockedUntil = 0f;
guardUntil = 0f;
guardCooldownUntil = 0f;
activeGuardHasBlockedAttack = false;
playerController.SetTransitionLocked(false);
animator.Rebind();
animator.Update(0f);
OnHealthChanged?.Invoke(CurrentHealth, MaxHealth);
return true;
}
public void Heal(float amount)
{
if (amount <= 0f || CurrentHealth <= 0f)