174 lines
4.8 KiB
Dart
174 lines
4.8 KiB
Dart
import 'package:flame/components.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'dart:math';
|
|
|
|
import 'package:shitman/game/components/base.dart';
|
|
|
|
class VisionCone extends ShitComponent {
|
|
double direction; // Angle in radians
|
|
final double range;
|
|
final double fov; // Field of view angle in radians
|
|
final Color color;
|
|
double opacity;
|
|
|
|
List<Vector2> visionVertices = [];
|
|
late Paint fillPaint;
|
|
late Paint strokePaint;
|
|
|
|
VisionCone({
|
|
required Vector2 origin,
|
|
required this.direction,
|
|
this.range = 150.0,
|
|
this.fov = pi / 3, // 60 degrees
|
|
this.color = Colors.red,
|
|
this.opacity = 0.3,
|
|
}) : super(position: origin);
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
await super.onLoad();
|
|
|
|
fillPaint =
|
|
Paint()
|
|
..color = color.withValues(alpha: opacity)
|
|
..style = PaintingStyle.fill;
|
|
|
|
strokePaint =
|
|
Paint()
|
|
..color = color.withValues(alpha: opacity + 0.2)
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 2.0;
|
|
|
|
_calculateVisionCone();
|
|
}
|
|
|
|
void updatePosition(Vector2 newOrigin, double newDirection) {
|
|
position.setFrom(newOrigin);
|
|
direction = newDirection;
|
|
_calculateVisionCone();
|
|
}
|
|
|
|
void _calculateVisionCone() {
|
|
visionVertices.clear();
|
|
|
|
// Start from the origin (0,0 relative to this component)
|
|
visionVertices.add(Vector2.zero());
|
|
|
|
// Calculate the two edge rays of the vision cone
|
|
final leftAngle = direction - fov / 2;
|
|
final rightAngle = direction + fov / 2;
|
|
|
|
// Create points along the vision cone arc
|
|
const int arcPoints = 10;
|
|
for (int i = 0; i <= arcPoints; i++) {
|
|
final angle = leftAngle + (rightAngle - leftAngle) * (i / arcPoints);
|
|
final point = Vector2(cos(angle), sin(angle)) * range;
|
|
visionVertices.add(point);
|
|
}
|
|
|
|
// Close the cone back to origin
|
|
visionVertices.add(Vector2.zero());
|
|
}
|
|
|
|
/// Check if a point is within the vision cone
|
|
bool canSee(Vector2 point) {
|
|
// Get the world position of this vision cone by adding parent position
|
|
final parentPosition =
|
|
(parent as PositionComponent?)?.position ?? Vector2.zero();
|
|
final worldPosition = parentPosition + position;
|
|
final toPoint = point - worldPosition;
|
|
final distance = toPoint.length;
|
|
|
|
// Check if within range
|
|
if (distance > range) return false;
|
|
|
|
// Check if within angle
|
|
final angleToPoint = atan2(toPoint.y, toPoint.x);
|
|
final angleDiff = _normalizeAngle(angleToPoint - direction);
|
|
|
|
return angleDiff.abs() <= fov / 2;
|
|
}
|
|
|
|
/// Normalize angle to [-pi, pi]
|
|
double _normalizeAngle(double angle) {
|
|
while (angle > pi) {
|
|
angle -= 2 * pi;
|
|
}
|
|
while (angle < -pi) {
|
|
angle += 2 * pi;
|
|
}
|
|
return angle;
|
|
}
|
|
|
|
/// Raycast from origin to target, checking for obstructions
|
|
bool hasLineOfSight(Vector2 target, List<Component> obstacles) {
|
|
// Simple line-of-sight check - can be enhanced with proper raycasting
|
|
final parentPosition =
|
|
(parent as PositionComponent?)?.position ?? Vector2.zero();
|
|
final worldPosition = parentPosition + position;
|
|
final rayDirection = target - worldPosition;
|
|
final distance = rayDirection.length;
|
|
final normalizedDir = rayDirection.normalized();
|
|
|
|
// Check points along the ray for obstacles
|
|
const double stepSize = 5.0;
|
|
for (double step = stepSize; step < distance; step += stepSize) {
|
|
final checkPoint = worldPosition + normalizedDir * step;
|
|
|
|
// Check if this point intersects with any obstacles
|
|
for (final obstacle in obstacles) {
|
|
if (obstacle is RectangleComponent) {
|
|
if (obstacle.containsPoint(checkPoint)) {
|
|
return false; // Line of sight blocked
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true; // Clear line of sight
|
|
}
|
|
|
|
void updateOpacity(double newOpacity) {
|
|
opacity = newOpacity;
|
|
fillPaint =
|
|
Paint()
|
|
..color = color.withValues(alpha: opacity)
|
|
..style = PaintingStyle.fill;
|
|
|
|
strokePaint =
|
|
Paint()
|
|
..color = color.withValues(alpha: opacity + 0.2)
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 2.0;
|
|
}
|
|
|
|
@override
|
|
void render(Canvas canvas) {
|
|
if (visionVertices.length < 3 || opacity <= 0.0) return;
|
|
|
|
// Create path for vision cone
|
|
final path = Path();
|
|
path.moveTo(visionVertices[0].x, visionVertices[0].y);
|
|
|
|
for (int i = 1; i < visionVertices.length; i++) {
|
|
path.lineTo(visionVertices[i].x, visionVertices[i].y);
|
|
}
|
|
path.close();
|
|
|
|
// Draw filled vision cone
|
|
canvas.drawPath(path, fillPaint);
|
|
|
|
// Draw vision cone outline
|
|
canvas.drawPath(path, strokePaint);
|
|
}
|
|
|
|
@override
|
|
Future<void> reset() async {
|
|
// Reset vision cone state
|
|
direction = 0.0;
|
|
opacity = 0.3;
|
|
visionVertices.clear();
|
|
_calculateVisionCone();
|
|
appLog.finest('Vision cone reset');
|
|
}
|
|
}
|