Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0a854c4
add vampire-hunter boss encounters with telegraphed area-denial attacks
partouf Jul 13, 2026
e5692ba
confine boss to its arena instead of chasing across the level
partouf Jul 14, 2026
20d2d63
append boss arena on top of the full city instead of replacing a band
partouf Jul 14, 2026
789bfb6
boss plants and faces the player before shooting
partouf Jul 14, 2026
bda242f
clamp boss to the top of the arena so it can't escape through the exit
partouf Jul 14, 2026
ace6429
give the boss a random wander instead of fleeing to keep distance
partouf Jul 14, 2026
304118d
make the boss hunt the player and hold spots longer
partouf Jul 14, 2026
d743eff
fix melee whiffing on moving humans/boss
partouf Jul 14, 2026
76d2600
replace deprecated FindObjectOfType/FindObjectsOfType calls
partouf Jul 14, 2026
e98e2c5
lower boss arrow damage from 6 to 3
partouf Jul 14, 2026
07aa442
melee targets the tile a mover is closest to, not the one it left
partouf Jul 14, 2026
5d02721
add boss intro cutscene: pan, walk-in, dialogue, then fight
partouf Jul 14, 2026
2e13398
make the boss arrow attack purely ranged with a melee blind spot
partouf Jul 14, 2026
75a385f
add Unity .meta files for the new boss scripts
partouf Jul 14, 2026
14ae30b
boss walks in to the 'B' marker tile from the template
partouf Jul 14, 2026
bf4f800
make boss encounters data-driven with a random roster
partouf Jul 14, 2026
8f4c41a
make the player invulnerable while frozen by a cutscene
partouf Jul 14, 2026
4b37e7a
add Unity .meta files for BossDefinition and BossRoster
partouf Jul 14, 2026
0b1c117
give each boss a fixed body prefab via the roster
partouf Jul 14, 2026
40efad0
add a separate BossPrefabs array for boss body art
partouf Jul 14, 2026
daa8c96
point level-1 bosses at the Human5 boss prefab
partouf Jul 14, 2026
3d44a8b
.
partouf Jul 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 27 additions & 32 deletions Assets/Scenes/MainScene.unity
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ MonoBehaviour:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 20140502}
m_Enabled: 0
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
Expand All @@ -292,7 +292,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 6065d1c1eb6981444bb481613c4cd11c, type: 3}
m_Sprite: {fileID: 21300000, guid: 627f05383f6b58640ac9b1ee9dddbbdc, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
Expand Down Expand Up @@ -1136,7 +1136,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 32d65bdbcbb545d468277d0fb39d3c3f, type: 3}
m_Sprite: {fileID: 21300000, guid: fedb7ced60395164f8d062e2b67c1270, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
Expand Down Expand Up @@ -1456,6 +1456,21 @@ Canvas:
m_SortingLayerID: 2066495187
m_SortingOrder: 0
m_TargetDisplay: 0
--- !u!114 &900200000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 931804142}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a07b7ea3dd0eddb18928dbc7b6a75f84, type: 3}
m_Name:
m_EditorClassIdentifier:
Icons:
- AbilityName: Glamour
Icon: {fileID: 3093879478420113611, guid: 34805f69b368dbc3c97b5a7e82e46bd5, type: 3}
--- !u!1 &931804142
GameObject:
m_ObjectHideFlags: 0
Expand All @@ -1468,8 +1483,7 @@ GameObject:
- component: {fileID: 931804143}
- component: {fileID: 931804145}
- component: {fileID: 931804144}
- component:
fileID: 900200000
- component: {fileID: 900200000}
m_Layer: 0
m_Name: GameManager
m_TagString: Untagged
Expand Down Expand Up @@ -1550,14 +1564,15 @@ MonoBehaviour:
- {fileID: 1053176256474194, guid: 22b65ec108270314abb7a703065d9fec, type: 3}
- {fileID: 1413321323189438, guid: 96c834e23d6b0cc46b4b05cabcf89b2e, type: 3}
- {fileID: 1565667635693750, guid: ee4f02036f9c2554e9e9c3a84eb7a806, type: 3}
- {fileID: 1389379551073354, guid: 134b637b0dd9d2247b45c7340e74828a, type: 3}
BridgeBottomH:
- {fileID: 1517841448877270, guid: eec8b44ff369b324a9761878dfe42824, type: 3}
ItemPrefabs:
- {fileID: 5477247290407607681, guid: 6b3e04845ce0195479e3849fee8976d2, type: 3}
- {fileID: 5477247290407607681, guid: f183d5d53b8ad3c4492f606db0653bc2, type: 3}
- {fileID: 5477247290407607681, guid: 1e2e2c97a1aac214c9d3146f36032a6c, type: 3}
- {fileID: 5477247290407607681, guid: 4e536bffa16801d45937752cf461f46b, type: 3}
BossPrefabs:
- {fileID: 1389379551073354, guid: 134b637b0dd9d2247b45c7340e74828a, type: 3}
XPText: {fileID: 688187632}
BloodfillText: {fileID: 1762789916}
TimeOfDayText: {fileID: 1890321003}
Expand Down Expand Up @@ -2186,6 +2201,11 @@ PrefabInstance:
propertyPath: MausoleumH.Array.size
value: 1
objectReference: {fileID: 0}
- target: {fileID: 114616681878518094, guid: f3bbbc2a9aa494c40ba34e989909e805,
type: 2}
propertyPath: BossPrefabs.Array.size
value: 1
objectReference: {fileID: 0}
- target: {fileID: 114616681878518094, guid: f3bbbc2a9aa494c40ba34e989909e805,
type: 2}
propertyPath: ItemPrefabs.Array.size
Expand All @@ -2194,7 +2214,7 @@ PrefabInstance:
- target: {fileID: 114616681878518094, guid: f3bbbc2a9aa494c40ba34e989909e805,
type: 2}
propertyPath: BloodPrefabs.Array.size
value: 5
value: 4
objectReference: {fileID: 0}
- target: {fileID: 114616681878518094, guid: f3bbbc2a9aa494c40ba34e989909e805,
type: 2}
Expand Down Expand Up @@ -2773,28 +2793,3 @@ SceneRoots:
- {fileID: 1338412496}
- {fileID: 72271895}
- {fileID: 1212246474}
--- !u!114 &900200000
MonoBehaviour:
Icons:
- AbilityName: Glamour
Icon:
fileID: 3093879478420113611
guid: 34805f69b368dbc3c97b5a7e82e46bd5
type: 3
m_CorrespondingSourceObject:
fileID: 0
m_EditorClassIdentifier: ''
m_EditorHideFlags: !!int '0'
m_Enabled: !!int '1'
m_GameObject:
fileID: 931804142
m_Name: ''
m_ObjectHideFlags: !!int '0'
m_PrefabAsset:
fileID: 0
m_PrefabInstance:
fileID: 0
m_Script:
fileID: 11500000
guid: a07b7ea3dd0eddb18928dbc7b6a75f84
type: 3
228 changes: 228 additions & 0 deletions Assets/Scripts/Blood/Boss.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
namespace VampireDrama
{
using UnityEngine;

// A vampire-hunter boss. Reuses the Human body (collider, rigidbody,
// animations) but replaces the wandering brain with an aggressive,
// keep-distance ranged fighter that self-heals and rains telegraphed
// volleys of arrows on the player (see BossAttackController).
//
// The boss is defeated by draining it: GetResistance always keeps the
// player on the melee-drain path (never the instant-kill feed path) so its
// blood pool is chipped down over several hits. When drained it reports its
// defeat to the level, which opens the sealed exit and grants a reward.
public class Boss : Human
{
public float HealInterval = 1.5f;
public float HealAmount = 1f;

// Movement: the boss holds a spot for a randomized dwell time, then
// takes a step. Most steps hunt the player (it is trying to kill them);
// the rest are random so it is not perfectly predictable. It never flees.
public float HoldMin = 0.8f;
public float HoldMax = 2.0f;
public float HuntChance = 0.7f;

// The arena band the boss is confined to (inclusive y range). It stays
// put until the player enters, and never leaves the band — including up
// through the exit gap at the top.
public float ArenaMinY;
public float ArenaMaxY;

// True once the player has entered the arena and the fight has begun.
public bool Engaged { get; private set; }

// Set by BossAttackController while it is aiming/firing: the boss plants
// and holds position (and its facing) so it never shoots mid-step.
public bool IsAttacking;

// Intro cutscene: the boss walks straight down to IntroTargetY without
// hunting or shooting, then holds until BeginFight() starts the fight.
public bool InIntro;
public int IntroTargetY;
public float IntroStepInterval = 0.22f;
public bool IntroArrived { get; private set; }

private const float DeathThreshold = 1f;

// Data-driven tuning (arena, dialogue, attack) for this encounter. Set
// before Start; the attack controller reads it from here too.
public BossDefinition Definition;

private float lastMove;
private float moveDelay = 1f;
private float lastHealTime;
private bool defeated;

protected override void Start()
{
base.Start();

if (Definition != null)
{
MaxBlood = Definition.MaxBlood;
HealAmount = Definition.HealAmount;
HealInterval = Definition.HealInterval;
HuntChance = Definition.HuntChance;
}
else
{
MaxBlood = 40f;
}

LitresOfBlood = MaxBlood;
Suspicion = 100; // always aware of the vampire
Intoxication = 0;
Darkness = 0;

lastMove = Time.time;
lastHealTime = Time.time;
defeated = false;

if (GetComponent<BossAttackController>() == null)
{
gameObject.AddComponent<BossAttackController>();
}
}

public override float GetResistance()
{
return defeated ? 0f : 1f;
}

public override void LoseBlood(float hitStrength, Vector3 attackerPosition)
{
base.LoseBlood(hitStrength, attackerPosition);

if (!defeated && LitresOfBlood <= DeathThreshold)
{
defeated = true;

var level = GameManager.GetCurrentLevel();
if (level != null)
{
level.BossDefeated(this);
}
}
}

// Ends the intro and switches the boss into its normal combat brain.
public void BeginFight()
{
InIntro = false;
IntroArrived = true;
Engaged = true;
}

protected override void Brain()
{
if (defeated) return;

if (InIntro)
{
IntroWalk();
return;
}

var level = GameManager.GetCurrentLevel();
if (level == null) return;

Vector3 me = transform.position;
Vector3 playerPos = level.GetPlayerPosition();

if (!Engaged)
{
// Guard the arena; only wake up once the player steps into it.
if (playerPos.y >= ArenaMinY - 0.5f)
{
Engaged = true;
}
else
{
return;
}
}

float now = Time.time;

SelfHeal(now);

// hold still (and keep facing the player) while aiming/firing
if (IsAttacking) return;

// hold the current spot for a randomized dwell, then take one step
if (now - lastMove < moveDelay) return;
lastMove = now;
moveDelay = HoldMin + Random.value * (HoldMax - HoldMin);

Vector3 diff = playerPos - me;

int dx = 0, dy = 0;

// Mostly hunt the player (step toward them on the dominant axis);
// occasionally take a random step so it isn't perfectly predictable.
if (Random.value < HuntChance)
{
if (System.Math.Abs(diff.x) >= System.Math.Abs(diff.y))
{
dx = diff.x >= 0 ? 1 : -1;
}
else
{
dy = diff.y >= 0 ? 1 : -1;
}
}
else
{
if (Random.value < 0.5f)
{
dx = Random.value < 0.5f ? -1 : 1;
}
else
{
dy = Random.value < 0.5f ? -1 : 1;
}
}

// never leave the arena band: don't wander down into the city, and
// don't slip up through the exit gap off the top of the map
if (dy < 0 && me.y + dy < ArenaMinY) dy = 0;
if (dy > 0 && me.y + dy > ArenaMaxY) dy = 0;

if (dx == 0 && dy == 0) return;

RaycastHit2D hit;
Move(dx, dy, out hit);
}

private void IntroWalk()
{
if (IntroArrived) return;

if (Mathf.RoundToInt(transform.position.y) <= IntroTargetY)
{
IntroArrived = true;
return;
}

float now = Time.time;
if (now - lastMove < IntroStepInterval) return;
lastMove = now;

RaycastHit2D hit;
Move(0, -1, out hit);
}

private void SelfHeal(float now)
{
if (defeated) return;
if (now - lastHealTime < HealInterval) return;
lastHealTime = now;

if (LitresOfBlood < MaxBlood)
{
LitresOfBlood = System.Math.Min(MaxBlood, LitresOfBlood + HealAmount);
}
}
}
}
2 changes: 2 additions & 0 deletions Assets/Scripts/Blood/Boss.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading