import 'package:flame/components.dart'; import 'package:flutter/material.dart'; import 'dart:math'; class VisionCone extends PositionComponent { double direction; // Angle in radians final double range; final double fov; // Field of view angle in radians final Color color; double opacity; List 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 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 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); } }