feat(faction): Implement faction combat system with attack mechanics, pathfinding, and damage handling
- Added speed and attackRate stats to faction configuration - Implemented faction class with health management, attack animations, and combat logic - Added sight detection to find and pursue enemies - Implemented attack mechanics with damage calculation and enemy destruction handling - Removed old collision detection system in favor of new combat system - Added timer-based attack and sight checks - Implemented melee attack animations and enemy targeting
This commit is contained in:
parent
425978dcb4
commit
75c2be852e
|
|
@ -6,8 +6,10 @@ export const FACTION_CONFIG = {
|
||||||
stats: {
|
stats: {
|
||||||
type: 'melee',
|
type: 'melee',
|
||||||
health: 5,
|
health: 5,
|
||||||
|
speed: 50,
|
||||||
attackMin: 1,
|
attackMin: 1,
|
||||||
attackMax: 4,
|
attackMax: 4,
|
||||||
|
attackRate: 2000,
|
||||||
meleeDefense: 1,
|
meleeDefense: 1,
|
||||||
rangeDefense: 2,
|
rangeDefense: 2,
|
||||||
magicDefense: 1,
|
magicDefense: 1,
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,20 @@ export class Faction extends Phaser.GameObjects.Sprite {
|
||||||
Object.entries(FACTION_CONFIG[faction][frame].stats).forEach(([key, value]) => {
|
Object.entries(FACTION_CONFIG[faction][frame].stats).forEach(([key, value]) => {
|
||||||
this.stats[key] = value;
|
this.stats[key] = value;
|
||||||
});
|
});
|
||||||
console.log(this);
|
this.stats.fullHealth = this.stats.health;
|
||||||
|
|
||||||
// Path following properties
|
// Path following properties
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.side = side;
|
this.side = side;
|
||||||
this.currentWaypoint = 0;
|
this.currentWaypoint = 0;
|
||||||
this.speed = 50;
|
|
||||||
this.arrived = false;
|
this.arrived = false;
|
||||||
this.isPathPaused = false;
|
this.isPathPaused = false;
|
||||||
|
this.movingToEnemy = false;
|
||||||
|
this.enemy = false;
|
||||||
|
|
||||||
|
// Timers
|
||||||
|
this.timerSight = 0;
|
||||||
|
this.timerAttack = 0;
|
||||||
|
|
||||||
this.animKey = `${texture}-${frame}`;
|
this.animKey = `${texture}-${frame}`;
|
||||||
this.createAnim();
|
this.createAnim();
|
||||||
|
|
@ -50,6 +55,15 @@ export class Faction extends Phaser.GameObjects.Sprite {
|
||||||
frameRate: 2,
|
frameRate: 2,
|
||||||
repeat: -1
|
repeat: -1
|
||||||
});
|
});
|
||||||
|
this.scene.anims.create({
|
||||||
|
key: `${this.animKey}-attack`,
|
||||||
|
frames: [
|
||||||
|
{ key: this.texture.key, frame: this.frame.name+2 },
|
||||||
|
{ key: this.texture.key, frame: this.frame.name+3 }
|
||||||
|
],
|
||||||
|
frameRate: 2,
|
||||||
|
repeat: -1
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,8 +108,8 @@ export class Faction extends Phaser.GameObjects.Sprite {
|
||||||
|
|
||||||
// Set velocity based on angle and speed
|
// Set velocity based on angle and speed
|
||||||
this.body.setVelocity(
|
this.body.setVelocity(
|
||||||
Math.cos(angle) * this.speed,
|
Math.cos(angle) * this.stats.speed,
|
||||||
Math.sin(angle) * this.speed
|
Math.sin(angle) * this.stats.speed
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,10 +134,104 @@ export class Faction extends Phaser.GameObjects.Sprite {
|
||||||
this.setFlipX(false); // Moving left, flipped
|
this.setFlipX(false); // Moving left, flipped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Timers
|
||||||
|
this.timerSight += delta;
|
||||||
|
this.timerAttack += delta;
|
||||||
|
|
||||||
// Move towards next waypoint if not arrived and path is not paused
|
// Move towards next waypoint if not arrived and path is not paused
|
||||||
if (!this.arrived && !this.isPathPaused) {
|
if (!this.arrived && !this.isPathPaused) {
|
||||||
this.moveToWaypoint(this.currentWaypoint);
|
this.moveToWaypoint(this.currentWaypoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check distance to closest unit of the other side every second
|
||||||
|
if (this.timerSight >= 500 && this.movingToEnemy === false && this.enemy === false) {
|
||||||
|
this.timerSight = 0;
|
||||||
|
this.checkSight();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle moving towards an enemy
|
||||||
|
if (this.movingToEnemy && this.movingToEnemy.active) {
|
||||||
|
const distance = Phaser.Math.Distance.Between(this.x, this.y, this.movingToEnemy.x, this.movingToEnemy.y);
|
||||||
|
if (distance <= this.stats.range) {
|
||||||
|
this.enemy = this.movingToEnemy;
|
||||||
|
this.movingToEnemy = false;
|
||||||
|
this.body.setVelocity(0, 0);
|
||||||
|
this.play(`${this.animKey}-attack`, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.movingToEnemy !== false) {
|
||||||
|
this.movingToEnemy = false;
|
||||||
|
this.resumePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attack
|
||||||
|
if (this.enemy && this.enemy.active && this.timerAttack >= 2000) {
|
||||||
|
this.timerAttack = 0;
|
||||||
|
console.log('attack');
|
||||||
|
this.attack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSight() {
|
||||||
|
let enemyGroup;
|
||||||
|
if (this.side === 'left') {
|
||||||
|
enemyGroup = this.scene.factionRight;
|
||||||
|
} else {
|
||||||
|
enemyGroup = this.scene.factionLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enemyGroup) return;
|
||||||
|
|
||||||
|
let closestDistance = Infinity;
|
||||||
|
let closestEnemy = null;
|
||||||
|
|
||||||
|
// Find the closest enemy unit
|
||||||
|
enemyGroup.children.iterate((enemy) => {
|
||||||
|
if (enemy && enemy !== this) {
|
||||||
|
const distance = Phaser.Math.Distance.Between(this.x, this.y, enemy.x, enemy.y);
|
||||||
|
if (distance < closestDistance) {
|
||||||
|
closestDistance = distance;
|
||||||
|
closestEnemy = enemy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (closestEnemy && closestDistance <= this.stats.sight) {
|
||||||
|
this.pausePath();
|
||||||
|
this.movingToEnemy = closestEnemy;
|
||||||
|
|
||||||
|
const angle = Phaser.Math.Angle.Between(this.x, this.y, closestEnemy.x, closestEnemy.y);
|
||||||
|
const speed = this.stats.speed;
|
||||||
|
|
||||||
|
this.body.setVelocity(
|
||||||
|
Math.cos(angle) * speed,
|
||||||
|
Math.sin(angle) * speed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attack() {
|
||||||
|
if (this.stats.type === 'melee' && this.enemy && this.enemy.active) {
|
||||||
|
const status = this.enemy.takeDamage('melee', Phaser.Math.Between(this.stats.attackMin, this.stats.attackMax));
|
||||||
|
if (status === 'dead') {
|
||||||
|
this.resumePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
takeDamage(type, amount) {
|
||||||
|
const defense = this.stats[`${type}Defense`];
|
||||||
|
const damage = Math.max(0, amount - defense);
|
||||||
|
this.stats.health -= damage;
|
||||||
|
|
||||||
|
// Check if unit is dead
|
||||||
|
if (this.stats.health <= 0) {
|
||||||
|
this.destroy();
|
||||||
|
return 'dead';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'alive';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -39,9 +39,6 @@ export class Level extends Phaser.Scene {
|
||||||
|
|
||||||
const test = new Faction(this, 4*64, 5*64, 'dark-ages', 0, 'left', 1, 'dark-ages').setOrigin(0.5);
|
const test = new Faction(this, 4*64, 5*64, 'dark-ages', 0, 'left', 1, 'dark-ages').setOrigin(0.5);
|
||||||
const test2 = new Faction(this, 27*64, 4*64, 'dark-ages', 0, 'right', 1, 'dark-ages').setOrigin(0.5);
|
const test2 = new Faction(this, 27*64, 4*64, 'dark-ages', 0, 'right', 1, 'dark-ages').setOrigin(0.5);
|
||||||
|
|
||||||
// Add collision detection
|
|
||||||
this.physics.add.overlap(this.factionLeft, this.factionRight, this.handleFactionCollision, null, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(time, delta) {
|
update(time, delta) {
|
||||||
|
|
@ -58,52 +55,6 @@ export class Level extends Phaser.Scene {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFactionCollision(faction1, faction2) {
|
|
||||||
// Check if factions are on opposite sides
|
|
||||||
if (faction1.side !== faction2.side) {
|
|
||||||
// Calculate distance between factions
|
|
||||||
const distance = Phaser.Math.Distance.Between(faction1.x, faction1.y, faction2.x, faction2.y);
|
|
||||||
|
|
||||||
// If within attack range
|
|
||||||
if (distance < 150) {
|
|
||||||
// Pause path following and move towards each other
|
|
||||||
faction1.pausePath();
|
|
||||||
faction2.pausePath();
|
|
||||||
|
|
||||||
// Move towards each other
|
|
||||||
const angle = Phaser.Math.Angle.Between(faction1.x, faction1.y, faction2.x, faction2.y);
|
|
||||||
const speed = 50;
|
|
||||||
|
|
||||||
faction1.body.setVelocity(
|
|
||||||
Math.cos(angle) * speed,
|
|
||||||
Math.sin(angle) * speed
|
|
||||||
);
|
|
||||||
|
|
||||||
faction2.body.setVelocity(
|
|
||||||
Math.cos(angle + Math.PI) * speed,
|
|
||||||
Math.sin(angle + Math.PI) * speed
|
|
||||||
);
|
|
||||||
|
|
||||||
// Flip sprites based on movement direction
|
|
||||||
if (faction1.body.velocity.x > 0) {
|
|
||||||
faction1.setFlipX(true);
|
|
||||||
} else {
|
|
||||||
faction1.setFlipX(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (faction2.body.velocity.x > 0) {
|
|
||||||
faction2.setFlipX(true);
|
|
||||||
} else {
|
|
||||||
faction2.setFlipX(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If they're not close enough, resume normal path following
|
|
||||||
faction1.resumePath();
|
|
||||||
faction2.resumePath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onPointerDown(pointer) {
|
onPointerDown(pointer) {
|
||||||
this.isDragging = true;
|
this.isDragging = true;
|
||||||
this.dragStartX = pointer.x;
|
this.dragStartX = pointer.x;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Start a simple HTTP server on port 8000
|
||||||
|
python3 -m http.server 8000
|
||||||
Loading…
Reference in New Issue