Fixed clipping through walls; map is now an overlay
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
+346
-287
@@ -3,23 +3,6 @@ import "dart:math" as math;
|
|||||||
import "package:flutter/services.dart";
|
import "package:flutter/services.dart";
|
||||||
import "package:flutter/widgets.dart";
|
import "package:flutter/widgets.dart";
|
||||||
|
|
||||||
// dart format off
|
|
||||||
const List<int> map = [
|
|
||||||
1,1,1,1,1,1,1,1,
|
|
||||||
1,0,0,0,0,0,0,1,
|
|
||||||
1,0,1,0,0,0,0,1,
|
|
||||||
1,0,1,0,0,0,0,1,
|
|
||||||
1,0,0,0,0,0,0,1,
|
|
||||||
1,0,0,0,0,2,0,1,
|
|
||||||
1,0,0,0,0,0,0,1,
|
|
||||||
1,1,1,1,1,1,1,1,
|
|
||||||
];
|
|
||||||
// dart format on
|
|
||||||
|
|
||||||
const int mapX = 8;
|
|
||||||
const int mapY = 8;
|
|
||||||
const int mapSize = 32;
|
|
||||||
|
|
||||||
const double gameWidth = 320;
|
const double gameWidth = 320;
|
||||||
const double gameHeight = 256;
|
const double gameHeight = 256;
|
||||||
|
|
||||||
@@ -67,13 +50,15 @@ extension GameValues on double {
|
|||||||
extension WorldValues on Offset {
|
extension WorldValues on Offset {
|
||||||
// The current "tile" we're in on the map
|
// The current "tile" we're in on the map
|
||||||
Offset get currentTile => Offset(
|
Offset get currentTile => Offset(
|
||||||
(dx / mapSize).floorToDouble(),
|
(dx / GameMap.size).floorToDouble(),
|
||||||
(dy / mapSize).floorToDouble(),
|
(dy / GameMap.size).floorToDouble(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// The index of the current "tile" we're in
|
// The index of the current "tile" we're in
|
||||||
// index = row * stride + column
|
// index = row * stride + column
|
||||||
int get index => (currentTile.dy.toInt() * mapX) + currentTile.dx.toInt();
|
int get index =>
|
||||||
|
(currentTile.dy.toInt() * GameMap.dimensions.width.toInt()) +
|
||||||
|
currentTile.dx.toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -90,7 +75,7 @@ class MainApp extends StatefulWidget {
|
|||||||
class _MainAppState extends State<MainApp> {
|
class _MainAppState extends State<MainApp> {
|
||||||
RayData rayData = const RayData(
|
RayData rayData = const RayData(
|
||||||
playerAngle: 0,
|
playerAngle: 0,
|
||||||
playerPosition: Offset(1.5 * mapSize, 1.5 * mapSize),
|
playerPosition: Offset(1.5 * GameMap.size, 1.5 * GameMap.size),
|
||||||
rays: [],
|
rays: [],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -99,57 +84,49 @@ class _MainAppState extends State<MainApp> {
|
|||||||
return Directionality(
|
return Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) => Row(
|
builder: (context, constraints) => Stack(
|
||||||
spacing: mapSize / 1,
|
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Stack(
|
||||||
child: Transform.translate(
|
children: [
|
||||||
offset: const Offset(16, 0),
|
const Background(),
|
||||||
child: Center(
|
Walls(rayData),
|
||||||
child: FittedBox(
|
],
|
||||||
fit: BoxFit.contain,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Map(rayData),
|
|
||||||
Player(
|
|
||||||
rayData: rayData,
|
|
||||||
onAngleUpdated: (radians) {
|
|
||||||
// degrees = radians × (180 / π)
|
|
||||||
setState(() {
|
|
||||||
rayData = rayData.copyWith(
|
|
||||||
playerAngle: radians,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onPositionUpdated: (position) {
|
|
||||||
setState(() {
|
|
||||||
rayData = rayData.copyWith(
|
|
||||||
playerPosition: position,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onRaysUpdated: (rays) {
|
|
||||||
setState(() {
|
|
||||||
rayData = rayData.copyWith(
|
|
||||||
rays: rays,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Flexible(
|
Opacity(
|
||||||
flex: 3,
|
opacity: 0.5,
|
||||||
child: Stack(
|
child: SizedBox.square(
|
||||||
children: [
|
dimension: GameMap.size * GameMap.dimensions.width / 2,
|
||||||
const Background(),
|
child: Stack(
|
||||||
Walls(rayData),
|
children: [
|
||||||
],
|
Map(rayData),
|
||||||
|
Player(
|
||||||
|
rayData: rayData,
|
||||||
|
onAngleUpdated: (radians) {
|
||||||
|
// degrees = radians × (180 / π)
|
||||||
|
setState(() {
|
||||||
|
rayData = rayData.copyWith(
|
||||||
|
playerAngle: radians,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onPositionUpdated: (position) {
|
||||||
|
setState(() {
|
||||||
|
rayData = rayData.copyWith(
|
||||||
|
playerPosition: position,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onRaysUpdated: (rays) {
|
||||||
|
setState(() {
|
||||||
|
rayData = rayData.copyWith(
|
||||||
|
rays: rays,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -214,25 +191,38 @@ class _PlayerState extends State<Player> {
|
|||||||
|
|
||||||
/// Get the map tile at a given pixel coordinate
|
/// Get the map tile at a given pixel coordinate
|
||||||
int tileAt(Offset position) {
|
int tileAt(Offset position) {
|
||||||
final int col = (position.dx / mapSize).floor();
|
final int col = (position.dx / GameMap.size).floor();
|
||||||
final int row = (position.dy / mapSize).floor();
|
final int row = (position.dy / GameMap.size).floor();
|
||||||
|
|
||||||
if (col < 0 || col >= mapX || row < 0 || row >= mapY) return 1; // solid
|
if (col < 0 ||
|
||||||
|
col >= GameMap.dimensions.width ||
|
||||||
|
row < 0 ||
|
||||||
|
row >= GameMap.dimensions.height)
|
||||||
|
return 1; // solid
|
||||||
|
|
||||||
return map[row * mapX + col];
|
return GameMap.map[row * GameMap.dimensions.width.toInt() + col];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How far until we hit a wall in the movement direction?
|
/// How far until we hit a wall in the movement direction?
|
||||||
double safeDistanceToWall(Offset position, Offset direction) {
|
double safeDistanceToWall(Offset position, Offset direction) {
|
||||||
const int maxCheck = 64;
|
final List<double> choices = [
|
||||||
const int stepSize = 8;
|
RayHit.castRay(
|
||||||
|
position,
|
||||||
|
direction.dx,
|
||||||
|
).distance,
|
||||||
|
RayHit.castRay(
|
||||||
|
position,
|
||||||
|
direction.direction.normalizeAngle - 0.05,
|
||||||
|
).distance,
|
||||||
|
RayHit.castRay(
|
||||||
|
position,
|
||||||
|
direction.direction.normalizeAngle + 0.05,
|
||||||
|
).distance,
|
||||||
|
];
|
||||||
|
|
||||||
for (double d = 0; d < maxCheck; d += stepSize) {
|
choices.sort();
|
||||||
final Offset p = position + Offset(direction.dx, direction.dy) * d;
|
|
||||||
if (tileAt(p) > 0) return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
return double.infinity;
|
return choices.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keyPressHandler(KeyEvent event) {
|
bool keyPressHandler(KeyEvent event) {
|
||||||
@@ -243,6 +233,9 @@ class _PlayerState extends State<Player> {
|
|||||||
|
|
||||||
// Where is the player now?
|
// Where is the player now?
|
||||||
Offset playerPosition = widget.rayData.playerPosition;
|
Offset playerPosition = widget.rayData.playerPosition;
|
||||||
|
final Offset playerCenter =
|
||||||
|
playerPosition +
|
||||||
|
Offset(GameMap.dimensions.width / 2, GameMap.dimensions.height / 2);
|
||||||
double playerAngle = widget.rayData.playerAngle;
|
double playerAngle = widget.rayData.playerAngle;
|
||||||
|
|
||||||
// How many pixels away from a wall to stay
|
// How many pixels away from a wall to stay
|
||||||
@@ -262,14 +255,13 @@ class _PlayerState extends State<Player> {
|
|||||||
final Offset direction = playerAngle.direction;
|
final Offset direction = playerAngle.direction;
|
||||||
|
|
||||||
if (keys.contains(LogicalKeyboardKey.keyW)) {
|
if (keys.contains(LogicalKeyboardKey.keyW)) {
|
||||||
if (safeDistanceToWall(playerPosition, direction) >
|
if (safeDistanceToWall(playerCenter, direction) > Player.speed + buffer) {
|
||||||
Player.speed + buffer) {
|
|
||||||
// Move forward
|
// Move forward
|
||||||
playerPosition += playerAngle.playerDelta;
|
playerPosition += playerAngle.playerDelta;
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
} else if (keys.contains(LogicalKeyboardKey.keyS)) {
|
} else if (keys.contains(LogicalKeyboardKey.keyS)) {
|
||||||
if (safeDistanceToWall(playerPosition, -direction) >
|
if (safeDistanceToWall(playerCenter, -direction) >
|
||||||
Player.speed + buffer) {
|
Player.speed + buffer) {
|
||||||
// Move backwwards
|
// Move backwwards
|
||||||
playerPosition -= playerAngle.playerDelta;
|
playerPosition -= playerAngle.playerDelta;
|
||||||
@@ -291,198 +283,29 @@ class _PlayerState extends State<Player> {
|
|||||||
|
|
||||||
List<Ray> calculateRays(Offset offset, double playerAngle) {
|
List<Ray> calculateRays(Offset offset, double playerAngle) {
|
||||||
const double dr = math.pi / 180;
|
const double dr = math.pi / 180;
|
||||||
|
final double baseAngle = (playerAngle - dr * 30).normalizeAngle;
|
||||||
|
|
||||||
final List<Ray> rays = [];
|
final List<Ray> rays = [];
|
||||||
|
|
||||||
int dof;
|
final Offset playerCenter =
|
||||||
int mx;
|
offset +
|
||||||
int my;
|
Offset(
|
||||||
int mapPoint;
|
GameMap.dimensions.width / 2,
|
||||||
|
GameMap.dimensions.height / 2,
|
||||||
double yo = 1;
|
);
|
||||||
double xo = 1;
|
|
||||||
|
|
||||||
final Offset playerCenter = offset + const Offset(4, 4);
|
|
||||||
|
|
||||||
double rayX = 0;
|
|
||||||
double rayY = 0;
|
|
||||||
double disT = 0;
|
|
||||||
|
|
||||||
double rayAngle = (playerAngle - (dr * 30)).normalizeAngle;
|
|
||||||
|
|
||||||
int r;
|
int r;
|
||||||
for (r = 0; r < 60; r++) {
|
for (r = 0; r < 60; r++) {
|
||||||
// * --- Check Horizontal Lines ---
|
final double angle = (baseAngle + r * dr).normalizeAngle;
|
||||||
dof = 0;
|
final RayHit hit = RayHit.castRay(playerCenter, angle);
|
||||||
double distanceHor = 1000000;
|
|
||||||
double hx = playerCenter.dx;
|
|
||||||
double hy = playerCenter.dy;
|
|
||||||
|
|
||||||
// Negative inverse of tangent
|
rays.add(
|
||||||
final double aTan = -1.0 / math.tan(rayAngle);
|
Ray(
|
||||||
|
hit.hitPosition,
|
||||||
// ? Looking up (ray angle > π)
|
hit.distance,
|
||||||
if (rayAngle > math.pi) {
|
hit.colorFor(hit.tileValue, hit.axis),
|
||||||
rayY = ((playerCenter.dy / mapSize).floorToDouble() * mapSize) - 0.0001;
|
),
|
||||||
|
);
|
||||||
rayX = (playerCenter.dy - rayY) * aTan + playerCenter.dx;
|
|
||||||
|
|
||||||
yo = -mapSize / 1;
|
|
||||||
xo = -yo * aTan;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ? Looking down
|
|
||||||
if (rayAngle < math.pi) {
|
|
||||||
rayY =
|
|
||||||
((playerCenter.dy / mapSize).floorToDouble() * mapSize) + mapSize;
|
|
||||||
|
|
||||||
rayX = (playerCenter.dy - rayY) * aTan + playerCenter.dx;
|
|
||||||
|
|
||||||
yo = mapSize / 1;
|
|
||||||
xo = -yo * aTan;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ? Looking straight left or right
|
|
||||||
if (rayAngle == 0.0 || rayAngle == math.pi) {
|
|
||||||
// It's impossible to ever hit a horizontal line
|
|
||||||
rayX = playerCenter.dx;
|
|
||||||
rayY = playerCenter.dy;
|
|
||||||
dof = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
int mv = 0;
|
|
||||||
int mh = 0;
|
|
||||||
|
|
||||||
while (dof < 8) {
|
|
||||||
mx = rayX ~/ mapSize;
|
|
||||||
my = rayY ~/ mapSize;
|
|
||||||
|
|
||||||
mapPoint = my * mapX + mx;
|
|
||||||
|
|
||||||
if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) {
|
|
||||||
// We hit a wall!
|
|
||||||
|
|
||||||
mh = map[mapPoint];
|
|
||||||
|
|
||||||
hx = rayX;
|
|
||||||
hy = rayY;
|
|
||||||
distanceHor = dist(
|
|
||||||
playerCenter.dx,
|
|
||||||
playerCenter.dy,
|
|
||||||
hx,
|
|
||||||
hy,
|
|
||||||
rayAngle,
|
|
||||||
);
|
|
||||||
|
|
||||||
dof = 8;
|
|
||||||
} else {
|
|
||||||
rayX += xo;
|
|
||||||
rayY += yo;
|
|
||||||
dof += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// * --- Check Vertical Lines ---
|
|
||||||
dof = 0;
|
|
||||||
double distanceVert = 1000000;
|
|
||||||
double vx = playerCenter.dx;
|
|
||||||
double vy = playerCenter.dy;
|
|
||||||
|
|
||||||
// Negative of tangent
|
|
||||||
final double nTan = -math.tan(rayAngle);
|
|
||||||
|
|
||||||
const double pi2 = math.pi / 2;
|
|
||||||
const double pi3 = 3 * math.pi / 2;
|
|
||||||
|
|
||||||
// ? Looking left
|
|
||||||
if (rayAngle > pi2 && rayAngle < pi3) {
|
|
||||||
rayX = ((playerCenter.dx / mapSize).floorToDouble() * mapSize) - 0.0001;
|
|
||||||
rayY = (playerCenter.dx - rayX) * nTan + playerCenter.dy;
|
|
||||||
xo = -mapSize / 1;
|
|
||||||
yo = -xo * nTan;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ? Looking right
|
|
||||||
if (rayAngle < pi2 || rayAngle > pi3) {
|
|
||||||
rayX =
|
|
||||||
((playerCenter.dx / mapSize).floorToDouble() * mapSize) + mapSize;
|
|
||||||
rayY = (playerCenter.dx - rayX) * nTan + playerCenter.dy;
|
|
||||||
xo = mapSize / 1;
|
|
||||||
yo = -xo * nTan;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ? Looking straight up or down
|
|
||||||
if (rayAngle == pi2 || rayAngle == pi3) {
|
|
||||||
rayX = playerCenter.dx;
|
|
||||||
rayY = playerCenter.dy;
|
|
||||||
dof = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (dof < 8) {
|
|
||||||
mx = rayX ~/ mapSize;
|
|
||||||
my = rayY ~/ mapSize;
|
|
||||||
|
|
||||||
mapPoint = my * mapX + mx;
|
|
||||||
|
|
||||||
if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) {
|
|
||||||
// We hit a wall!
|
|
||||||
|
|
||||||
mv = map[mapPoint];
|
|
||||||
|
|
||||||
// Save the values and calculate the distance from the player
|
|
||||||
vx = rayX;
|
|
||||||
vy = rayY;
|
|
||||||
distanceVert = dist(
|
|
||||||
playerCenter.dx,
|
|
||||||
playerCenter.dy,
|
|
||||||
vx,
|
|
||||||
vy,
|
|
||||||
rayAngle,
|
|
||||||
);
|
|
||||||
|
|
||||||
dof = 8; // Break the loop - no need to calculate more.
|
|
||||||
} else {
|
|
||||||
// If we didn't hit a wall, we check the next horizontal line by adding
|
|
||||||
// the x and y offset.
|
|
||||||
rayX += xo;
|
|
||||||
rayY += yo;
|
|
||||||
dof += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Color wallColor = const Color.fromRGBO(0, 0, 0, 0);
|
|
||||||
|
|
||||||
if (distanceVert < distanceHor) {
|
|
||||||
rayX = vx;
|
|
||||||
rayY = vy;
|
|
||||||
disT = distanceVert;
|
|
||||||
// Vertical walls are drawn a little brighter than horizontal walls
|
|
||||||
wallColor = const Color.fromRGBO(230, 0, 0, 1);
|
|
||||||
|
|
||||||
if (mv == 2) {
|
|
||||||
wallColor = const Color.fromRGBO(0, 179, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
rays.add(Ray(Offset(rayX, rayY), disT, wallColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (distanceHor < distanceVert) {
|
|
||||||
rayX = hx;
|
|
||||||
rayY = hy;
|
|
||||||
disT = distanceHor;
|
|
||||||
// Horizontal walls are drawn a little darker than vertical walls
|
|
||||||
wallColor = const Color.fromRGBO(179, 0, 0, 1);
|
|
||||||
|
|
||||||
if (mh == 2) {
|
|
||||||
wallColor = const Color.fromRGBO(0, 102, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
rays.add(Ray(Offset(rayX, rayY), disT, wallColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
// * Increase the ray angle to draw the next ray
|
|
||||||
rayAngle =
|
|
||||||
(playerAngle.normalizeAngle - (dr * 30) + r * dr).normalizeAngle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rays;
|
return rays;
|
||||||
@@ -595,7 +418,7 @@ class WallPainter extends CustomPainter {
|
|||||||
|
|
||||||
final double disT = ray.distance * math.cos(correctedAngle);
|
final double disT = ray.distance * math.cos(correctedAngle);
|
||||||
|
|
||||||
double lineHeight = (mapSize * gameHeight) / disT;
|
double lineHeight = (GameMap.size * gameHeight) / disT;
|
||||||
if (lineHeight > gameHeight) {
|
if (lineHeight > gameHeight) {
|
||||||
lineHeight = gameHeight;
|
lineHeight = gameHeight;
|
||||||
}
|
}
|
||||||
@@ -621,9 +444,9 @@ class WallPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant oldDelegate) => false;
|
bool shouldRepaint(covariant oldDelegate) => true;
|
||||||
@override
|
@override
|
||||||
bool shouldRebuildSemantics(covariant oldDelegate) => false;
|
bool shouldRebuildSemantics(covariant oldDelegate) => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// * Map
|
// * Map
|
||||||
@@ -637,18 +460,15 @@ class Map extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const double width = mapX * mapSize / 1;
|
|
||||||
const double height = mapY * mapSize / 1;
|
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
const CustomPaint(
|
CustomPaint(
|
||||||
size: Size(width, height),
|
size: GameMap.dimensions,
|
||||||
painter: MapPainter(
|
painter: MapPainter(
|
||||||
mapData: map,
|
mapData: GameMap.map,
|
||||||
mapX: mapX,
|
mapX: GameMap.dimensions.width,
|
||||||
mapY: mapY,
|
mapY: GameMap.dimensions.height,
|
||||||
tileSize: mapSize / 1,
|
tileSize: GameMap.size / 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -664,8 +484,8 @@ class Map extends StatelessWidget {
|
|||||||
|
|
||||||
class MapPainter extends CustomPainter {
|
class MapPainter extends CustomPainter {
|
||||||
final List<int> mapData;
|
final List<int> mapData;
|
||||||
final int mapX;
|
final double mapX;
|
||||||
final int mapY;
|
final double mapY;
|
||||||
final double tileSize;
|
final double tileSize;
|
||||||
|
|
||||||
const new({
|
const new({
|
||||||
@@ -698,9 +518,9 @@ class MapPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant oldDelegate) => false;
|
bool shouldRepaint(covariant oldDelegate) => true;
|
||||||
@override
|
@override
|
||||||
bool shouldRebuildSemantics(covariant oldDelegate) => false;
|
bool shouldRebuildSemantics(covariant oldDelegate) => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapRayPainter extends CustomPainter {
|
class MapRayPainter extends CustomPainter {
|
||||||
@@ -726,9 +546,9 @@ class MapRayPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant oldDelegate) => false;
|
bool shouldRepaint(covariant oldDelegate) => true;
|
||||||
@override
|
@override
|
||||||
bool shouldRebuildSemantics(covariant oldDelegate) => false;
|
bool shouldRebuildSemantics(covariant oldDelegate) => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// * Background
|
// * Background
|
||||||
@@ -823,7 +643,7 @@ class Floor extends CustomPainter {
|
|||||||
bool shouldRebuildSemantics(covariant oldDelegate) => false;
|
bool shouldRebuildSemantics(covariant oldDelegate) => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// * Ray Data
|
// * Rays
|
||||||
class Ray(
|
class Ray(
|
||||||
/// Where it hit on the 2D map
|
/// Where it hit on the 2D map
|
||||||
var Offset hitPosition,
|
var Offset hitPosition,
|
||||||
@@ -863,3 +683,242 @@ class RayData {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RayHit(
|
||||||
|
/// Exact boundary crossing where the ray stopped
|
||||||
|
var Offset hitPosition,
|
||||||
|
|
||||||
|
/// Euclidean, from ray origin
|
||||||
|
var double distance,
|
||||||
|
|
||||||
|
/// 0 means "no hit within map"
|
||||||
|
var int tileValue,
|
||||||
|
|
||||||
|
/// [Axis] of the corresponding ray hit
|
||||||
|
var Axis axis,
|
||||||
|
) {
|
||||||
|
factory castRay(Offset origin, double angle) {
|
||||||
|
// 1. run horizontal-axis DDA → hit position + distance (or none)
|
||||||
|
// 2. run vertical-axis DDA → hit position + distance (or none)
|
||||||
|
// 3. return the nearer one
|
||||||
|
|
||||||
|
final RayHit hitHorizontal = RayHit._marchHorizontal(origin, angle);
|
||||||
|
final RayHit hitVertical = RayHit._marchVertical(origin, angle);
|
||||||
|
|
||||||
|
if (hitVertical.distance < hitHorizontal.distance) {
|
||||||
|
return hitVertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hitHorizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory _marchHorizontal(Offset origin, double angle) {
|
||||||
|
// * --- Check Horizontal Lines ---
|
||||||
|
int dof = 0;
|
||||||
|
double distanceHor = double.infinity;
|
||||||
|
Offset hit = origin;
|
||||||
|
|
||||||
|
// Negative inverse of tangent
|
||||||
|
final double aTan = -1.0 / math.tan(angle);
|
||||||
|
|
||||||
|
double rayY = 0;
|
||||||
|
double rayX = 0;
|
||||||
|
double yOffset = 0;
|
||||||
|
double xOffset = 0;
|
||||||
|
|
||||||
|
// ? Looking up (ray angle > π)
|
||||||
|
if (angle > math.pi) {
|
||||||
|
rayY =
|
||||||
|
((origin.dy / GameMap.size).floorToDouble() * GameMap.size) - 0.0001;
|
||||||
|
|
||||||
|
rayX = (origin.dy - rayY) * aTan + origin.dx;
|
||||||
|
|
||||||
|
yOffset = -GameMap.size / 1;
|
||||||
|
xOffset = -yOffset * aTan;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ? Looking down
|
||||||
|
if (angle < math.pi) {
|
||||||
|
rayY =
|
||||||
|
((origin.dy / GameMap.size).floorToDouble() * GameMap.size) +
|
||||||
|
GameMap.size;
|
||||||
|
|
||||||
|
rayX = (origin.dy - rayY) * aTan + origin.dx;
|
||||||
|
|
||||||
|
yOffset = GameMap.size / 1;
|
||||||
|
xOffset = -yOffset * aTan;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mapPointX = 0;
|
||||||
|
int mapPointY = 0;
|
||||||
|
int mapPoint = 0;
|
||||||
|
int mapHit = 0;
|
||||||
|
|
||||||
|
while (dof < 8) {
|
||||||
|
mapPointX = rayX ~/ GameMap.size;
|
||||||
|
mapPointY = rayY ~/ GameMap.size;
|
||||||
|
|
||||||
|
mapPoint = mapPointY * GameMap.dimensions.width.toInt() + mapPointX;
|
||||||
|
|
||||||
|
if (mapPoint > 0 &&
|
||||||
|
mapPoint <
|
||||||
|
GameMap.dimensions.width.toInt() *
|
||||||
|
GameMap.dimensions.height.toInt() &&
|
||||||
|
GameMap.map[mapPoint] > 0) {
|
||||||
|
// We hit a wall!
|
||||||
|
mapHit = GameMap.map[mapPoint];
|
||||||
|
|
||||||
|
hit = Offset(rayX, rayY);
|
||||||
|
|
||||||
|
distanceHor = dist(
|
||||||
|
origin,
|
||||||
|
hit,
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
|
||||||
|
dof = 8;
|
||||||
|
} else {
|
||||||
|
rayX += xOffset;
|
||||||
|
rayY += yOffset;
|
||||||
|
dof += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RayHit(hit, distanceHor, mapHit, Axis.horizontal);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory _marchVertical(Offset origin, double angle) {
|
||||||
|
// * --- Check Vertical Lines ---
|
||||||
|
int dof = 0;
|
||||||
|
double distanceVert = double.infinity;
|
||||||
|
Offset hit = origin;
|
||||||
|
|
||||||
|
// Negative of tangent
|
||||||
|
final double nTan = -math.tan(angle);
|
||||||
|
|
||||||
|
const double pi2 = math.pi / 2;
|
||||||
|
const double pi3 = 3 * math.pi / 2;
|
||||||
|
|
||||||
|
double rayX = 0;
|
||||||
|
double rayY = 0;
|
||||||
|
double xOffset = 0;
|
||||||
|
double yOffset = 0;
|
||||||
|
|
||||||
|
// ? Looking left
|
||||||
|
if (angle > pi2 && angle < pi3) {
|
||||||
|
rayX =
|
||||||
|
((origin.dx / GameMap.size).floorToDouble() * GameMap.size) - 0.0001;
|
||||||
|
rayY = (origin.dx - rayX) * nTan + origin.dy;
|
||||||
|
xOffset = -GameMap.size / 1;
|
||||||
|
yOffset = -xOffset * nTan;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ? Looking right
|
||||||
|
if (angle < pi2 || angle > pi3) {
|
||||||
|
rayX =
|
||||||
|
((origin.dx / GameMap.size).floorToDouble() * GameMap.size) +
|
||||||
|
GameMap.size;
|
||||||
|
rayY = (origin.dx - rayX) * nTan + origin.dy;
|
||||||
|
xOffset = GameMap.size / 1;
|
||||||
|
yOffset = -xOffset * nTan;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ? Looking straight up or down
|
||||||
|
if (angle == pi2 || angle == pi3) {
|
||||||
|
rayX = origin.dx;
|
||||||
|
rayY = origin.dy;
|
||||||
|
dof = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mapPointX = 0;
|
||||||
|
int mapPointY = 0;
|
||||||
|
int mapPoint = 0;
|
||||||
|
int mapHit = 0;
|
||||||
|
|
||||||
|
while (dof < 8) {
|
||||||
|
mapPointX = rayX ~/ GameMap.size;
|
||||||
|
mapPointY = rayY ~/ GameMap.size;
|
||||||
|
|
||||||
|
mapPoint = mapPointY * GameMap.dimensions.width.toInt() + mapPointX;
|
||||||
|
|
||||||
|
if (mapPoint > 0 &&
|
||||||
|
mapPoint <
|
||||||
|
GameMap.dimensions.width.toInt() *
|
||||||
|
GameMap.dimensions.height.toInt() &&
|
||||||
|
GameMap.map[mapPoint] > 0) {
|
||||||
|
// We hit a wall!
|
||||||
|
mapHit = GameMap.map[mapPoint];
|
||||||
|
|
||||||
|
// Save the values and calculate the distance from the player
|
||||||
|
hit = Offset(rayX, rayY);
|
||||||
|
|
||||||
|
distanceVert = dist(
|
||||||
|
origin,
|
||||||
|
hit,
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Break the loop - no need to calculate more.
|
||||||
|
dof = 8;
|
||||||
|
} else {
|
||||||
|
// If we didn't hit a wall, we check the next horizontal line by adding
|
||||||
|
// the x and y offset.
|
||||||
|
rayX += xOffset;
|
||||||
|
rayY += yOffset;
|
||||||
|
dof += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RayHit(hit, distanceVert, mapHit, Axis.vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double dist(Offset a, Offset b, double ang) {
|
||||||
|
return math.sqrt(
|
||||||
|
(b.dx - a.dx) * (b.dx - a.dx) + (b.dy - a.dy) * (b.dy - a.dy),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color colorFor(int tileValue, Axis axis) {
|
||||||
|
Color wallColor = const Color.fromRGBO(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (axis == Axis.vertical) {
|
||||||
|
// Vertical walls are drawn a little brighter than horizontal walls
|
||||||
|
wallColor = const Color.fromRGBO(230, 0, 0, 1);
|
||||||
|
|
||||||
|
if (tileValue == 2) {
|
||||||
|
wallColor = const Color.fromRGBO(0, 179, 0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (axis == Axis.horizontal) {
|
||||||
|
// Horizontal walls are drawn a little darker than vertical walls
|
||||||
|
wallColor = const Color.fromRGBO(179, 0, 0, 1);
|
||||||
|
|
||||||
|
if (tileValue == 2) {
|
||||||
|
wallColor = const Color.fromRGBO(0, 102, 0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// * Map
|
||||||
|
|
||||||
|
abstract class GameMap {
|
||||||
|
// dart format off
|
||||||
|
static const List<int> map = [
|
||||||
|
1,1,1,1,1,1,1,1,
|
||||||
|
1,0,0,0,0,0,0,1,
|
||||||
|
1,0,1,0,0,0,0,1,
|
||||||
|
1,0,1,0,0,0,0,1,
|
||||||
|
1,0,0,0,0,0,0,1,
|
||||||
|
1,0,0,0,0,2,0,1,
|
||||||
|
1,0,0,0,0,0,0,1,
|
||||||
|
1,1,1,1,1,1,1,1,
|
||||||
|
];
|
||||||
|
// dart format on
|
||||||
|
|
||||||
|
static const Size dimensions = Size(8, 8);
|
||||||
|
static const int size = 32;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user