Initial commit: labirynt 3D pseudo-raycasting game
This commit is contained in:
155
lib/raycaster.dart
Normal file
155
lib/raycaster.dart
Normal file
@@ -0,0 +1,155 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user