import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'maze.dart'; import 'player.dart'; class RaycasterPainter extends CustomPainter { final Maze maze; final Player player; static const double fov = pi / 3; static const int maxDepth = 20; RaycasterPainter({required this.maze, required this.player}); @override void paint(Canvas canvas, Size size) { _drawSky(canvas, size); _drawFloor(canvas, size); _drawWalls(canvas, size); _drawMinimap(canvas, size); } void _drawSky(Canvas canvas, Size size) { final paint = Paint() ..shader = const LinearGradient( begin: Alignment.topCenter, end: Alignment.center, colors: [Color(0xFF1a1a2e), Color(0xFF16213e)], ).createShader(Rect.fromLTWH(0, 0, size.width, size.height / 2)); canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height / 2), paint); } void _drawFloor(Canvas canvas, Size size) { final paint = Paint() ..shader = const LinearGradient( begin: Alignment.center, end: Alignment.bottomCenter, colors: [Color(0xFF2d4059), Color(0xFF1a1a2e)], ).createShader(Rect.fromLTWH(0, size.height / 2, size.width, size.height / 2)); canvas.drawRect(Rect.fromLTWH(0, size.height / 2, size.width, size.height / 2), paint); } void _drawWalls(Canvas canvas, Size size) { final numRays = size.width.toInt(); for (int i = 0; i < numRays; i++) { final rayAngle = player.angle - fov / 2 + (i / numRays) * fov; final result = _castRay(rayAngle); final distance = result.$1 * cos(rayAngle - player.angle); if (distance <= 0) continue; final wallHeight = size.height / distance; final wallTop = (size.height - wallHeight) / 2; final brightness = (1.0 - (distance / maxDepth)).clamp(0.1, 1.0); final isVertical = result.$2; final r = isVertical ? (40 * brightness).toInt() : (60 * brightness).toInt(); final g = isVertical ? (80 * brightness).toInt() : (100 * brightness).toInt(); final b = isVertical ? (120 * brightness).toInt() : (160 * brightness).toInt(); final paint = Paint() ..color = Color.fromARGB(255, r, g, b) ..strokeWidth = 1.5; canvas.drawLine( Offset(i.toDouble(), wallTop), Offset(i.toDouble(), wallTop + wallHeight), paint, ); } } (double, bool) _castRay(double angle) { final sinA = sin(angle); final cosA = cos(angle); double dist = 0; bool vertical = false; for (double d = 0.01; d < maxDepth; d += 0.02) { final testX = player.x + cosA * d; final testY = player.y + sinA * d; if (maze.isWall(testX.toInt(), testY.toInt())) { dist = d; final fracX = testX - testX.floorToDouble(); final fracY = testY - testY.floorToDouble(); vertical = fracX < 0.05 || fracX > 0.95; if (!vertical) vertical = !(fracY < 0.05 || fracY > 0.95); break; } } return (dist, vertical); } void _drawMinimap(Canvas canvas, Size size) { const cellSize = 6.0; const offsetX = 10.0; const offsetY = 10.0; final mapWidth = maze.width * cellSize; final mapHeight = maze.height * cellSize; // Background canvas.drawRect( Rect.fromLTWH(offsetX - 2, offsetY - 2, mapWidth + 4, mapHeight + 4), Paint()..color = const Color(0x88000000), ); // Walls final wallPaint = Paint()..color = const Color(0xFF4a6fa5); for (int y = 0; y < maze.height; y++) { for (int x = 0; x < maze.width; x++) { if (maze.grid[y][x] == 1) { canvas.drawRect( Rect.fromLTWH(offsetX + x * cellSize, offsetY + y * cellSize, cellSize, cellSize), wallPaint, ); } } } // Goal canvas.drawCircle( Offset(offsetX + (maze.width - 2) * cellSize + cellSize / 2, offsetY + (maze.height - 2) * cellSize + cellSize / 2), cellSize / 2, Paint()..color = const Color(0xFF00ff00), ); // Player canvas.drawCircle( Offset(offsetX + player.x * cellSize, offsetY + player.y * cellSize), cellSize / 2, Paint()..color = const Color(0xFFff4444), ); // Direction canvas.drawLine( Offset(offsetX + player.x * cellSize, offsetY + player.y * cellSize), Offset( offsetX + (player.x + cos(player.angle) * 2) * cellSize, offsetY + (player.y + sin(player.angle) * 2) * cellSize, ), Paint() ..color = const Color(0xFFff4444) ..strokeWidth = 2, ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; }