@@ -11,6 +11,7 @@ class RaycasterPainter extends CustomPainter {
|
||||
final LinearCoordinates player;
|
||||
final double playerAngle;
|
||||
final double fov;
|
||||
final Map<String, double> doorOffsets;
|
||||
|
||||
RaycasterPainter({
|
||||
required this.map,
|
||||
@@ -18,6 +19,7 @@ class RaycasterPainter extends CustomPainter {
|
||||
required this.player,
|
||||
required this.playerAngle,
|
||||
required this.fov,
|
||||
required this.doorOffsets,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -61,6 +63,11 @@ class RaycasterPainter extends CustomPainter {
|
||||
bool hitOutOfBounds = false;
|
||||
int side = 0;
|
||||
int hitWallId = 0;
|
||||
double doorOffset =
|
||||
0.0; // Track the slide amount to pass to the texture drawer
|
||||
|
||||
Set<String> ignoredDoors =
|
||||
{}; // Prevent the ray from checking the same door gap twice
|
||||
|
||||
if (rayDirX < 0) {
|
||||
stepX = -1;
|
||||
@@ -94,8 +101,33 @@ class RaycasterPainter extends CustomPainter {
|
||||
mapX < 0 ||
|
||||
mapX >= map[0].length) {
|
||||
hit = true;
|
||||
hitOutOfBounds = true; // Safely abort if we clip out of bounds
|
||||
hitOutOfBounds = true;
|
||||
} else if (map[mapY][mapX] > 0) {
|
||||
// NEW: DOOR PASS-THROUGH LOGIC
|
||||
String doorKey = '$mapX,$mapY';
|
||||
if (map[mapY][mapX] >= 90 && !ignoredDoors.contains(doorKey)) {
|
||||
double currentOffset = doorOffsets[doorKey] ?? 0.0;
|
||||
|
||||
if (currentOffset > 0.0) {
|
||||
// Calculate exactly where the ray hit the block
|
||||
double perpWallDistTemp = (side == 0)
|
||||
? (sideDistX - deltaDistX)
|
||||
: (sideDistY - deltaDistY);
|
||||
double wallXTemp = (side == 0)
|
||||
? player.y + perpWallDistTemp * rayDirY
|
||||
: player.x + perpWallDistTemp * rayDirX;
|
||||
wallXTemp -= wallXTemp.floor();
|
||||
|
||||
// If the hit coordinate is less than the open amount, the ray passes through!
|
||||
if (wallXTemp < currentOffset) {
|
||||
ignoredDoors.add(doorKey);
|
||||
continue; // Skip the rest of this loop iteration and keep raycasting!
|
||||
}
|
||||
}
|
||||
doorOffset =
|
||||
currentOffset; // Save the offset so we can slide the texture
|
||||
}
|
||||
|
||||
hit = true;
|
||||
hitWallId = map[mapY][mapX];
|
||||
}
|
||||
@@ -127,6 +159,7 @@ class RaycasterPainter extends CustomPainter {
|
||||
size,
|
||||
hitWallId,
|
||||
textures,
|
||||
doorOffset,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -140,28 +173,29 @@ class RaycasterPainter extends CustomPainter {
|
||||
Size size,
|
||||
int hitWallId,
|
||||
List<Matrix<int>> textures,
|
||||
double doorOffset,
|
||||
) {
|
||||
if (distance <= 0.01) distance = 0.01;
|
||||
|
||||
double wallHeight = size.height / distance;
|
||||
int drawStart = ((size.height / 2) - (wallHeight / 2)).toInt();
|
||||
|
||||
// 1. PERFECT TEXTURE MAPPING
|
||||
// TEXTURE MAPPING
|
||||
// Wolf3D stores textures in pairs. Even = N/S (Light), Odd = E/W (Dark).
|
||||
int texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
|
||||
int texX = (wallX * 64).toInt().clamp(0, 63);
|
||||
|
||||
// INTERCEPT DOORS
|
||||
if (hitWallId >= 90) {
|
||||
// Safely clamp the door texture index so it never crashes the paint loop!
|
||||
texNum = 98.clamp(0, textures.length - 1);
|
||||
texX = ((wallX - doorOffset) * 64).toInt().clamp(0, 63);
|
||||
} else {
|
||||
// Standard wall texture pairing
|
||||
texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
|
||||
if (side == 1) texNum += 1;
|
||||
}
|
||||
|
||||
int texX = (wallX * 64).toInt().clamp(0, 63);
|
||||
|
||||
if (side == 0 && math.cos(playerAngle) > 0) texX = 63 - texX;
|
||||
if (side == 1 && math.sin(playerAngle) < 0) texX = 63 - texX;
|
||||
|
||||
|
||||
@@ -30,6 +30,11 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
bool _isLoading = true;
|
||||
bool _spaceWasPressed = false;
|
||||
|
||||
// Track door animations
|
||||
// Key is "X,Y" coordinate. Value is how far open it is (0.0 to 1.0)
|
||||
Map<String, double> doorOffsets = {};
|
||||
Map<String, int> doorStates = {}; // 1 = opening, 2 = fully open
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -131,16 +136,38 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
}
|
||||
}
|
||||
|
||||
bool _isWalkable(int x, int y) {
|
||||
if (currentLevel[y][x] == 0) return true; // Empty space
|
||||
if (currentLevel[y][x] >= 90) {
|
||||
String key = '$x,$y';
|
||||
// Allow the player to walk through if the door is > 70% open
|
||||
if (doorOffsets[key] != null && doorOffsets[key]! > 0.7) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _tick(Duration elapsed) {
|
||||
const double moveSpeed = 0.12;
|
||||
const double turnSpeed = 0.08;
|
||||
|
||||
// 1. ANIMATE DOORS
|
||||
doorStates.forEach((key, state) {
|
||||
if (state == 1) {
|
||||
// If opening
|
||||
doorOffsets[key] = (doorOffsets[key] ?? 0.0) + 0.02; // Slide speed
|
||||
if (doorOffsets[key]! >= 1.0) {
|
||||
doorOffsets[key] = 1.0;
|
||||
doorStates[key] = 2; // Mark as fully open
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
double moveStepX = 0;
|
||||
double moveStepY = 0;
|
||||
|
||||
final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed;
|
||||
|
||||
// 1. Calculate intended movement amounts
|
||||
if (pressedKeys.contains(LogicalKeyboardKey.keyW)) {
|
||||
moveStepX += math.cos(playerAngle) * moveSpeed;
|
||||
moveStepY += math.sin(playerAngle) * moveSpeed;
|
||||
@@ -149,8 +176,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
moveStepX -= math.cos(playerAngle) * moveSpeed;
|
||||
moveStepY -= math.sin(playerAngle) * moveSpeed;
|
||||
}
|
||||
|
||||
// 2. Handle Turning
|
||||
if (pressedKeys.contains(LogicalKeyboardKey.keyA)) {
|
||||
playerAngle -= turnSpeed;
|
||||
}
|
||||
@@ -160,18 +185,14 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
if (playerAngle < 0) playerAngle += 2 * math.pi;
|
||||
if (playerAngle > 2 * math.pi) playerAngle -= 2 * math.pi;
|
||||
|
||||
// 3. Wall Sliding Collision (with Hitbox Margin!)
|
||||
// A 0.3 margin keeps the camera plane safely out of the walls
|
||||
// 2. UPDATED WALL COLLISION
|
||||
const double margin = 0.3;
|
||||
|
||||
double newX = player.x + moveStepX;
|
||||
// Check the edge of our hitbox, not just the center point
|
||||
int checkX = (moveStepX > 0)
|
||||
? (newX + margin).toInt()
|
||||
: (newX - margin).toInt();
|
||||
|
||||
// Try to move along the X axis
|
||||
if (currentLevel[player.y.toInt()][checkX] == 0) {
|
||||
if (_isWalkable(checkX, player.y.toInt())) {
|
||||
player = (x: newX, y: player.y);
|
||||
}
|
||||
|
||||
@@ -180,12 +201,11 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
? (newY + margin).toInt()
|
||||
: (newY - margin).toInt();
|
||||
|
||||
// Try to move along the Y axis
|
||||
if (currentLevel[checkY][player.x.toInt()] == 0) {
|
||||
if (_isWalkable(player.x.toInt(), checkY)) {
|
||||
player = (x: player.x, y: newY);
|
||||
}
|
||||
|
||||
// 4. DOOR INTERACTION LOGIC
|
||||
// 3. UPDATED DOOR INTERACTION
|
||||
bool isSpacePressed = pressedKeys.contains(LogicalKeyboardKey.space);
|
||||
|
||||
if (isSpacePressed && !_spaceWasPressed) {
|
||||
@@ -196,10 +216,12 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
targetY < currentLevel.length &&
|
||||
targetX > 0 &&
|
||||
targetX < currentLevel[0].length) {
|
||||
int targetBlock = currentLevel[targetY][targetX];
|
||||
|
||||
if (targetBlock >= 90) {
|
||||
currentLevel[targetY][targetX] = 0;
|
||||
if (currentLevel[targetY][targetX] >= 90) {
|
||||
String key = '$targetX,$targetY';
|
||||
// Start the animation if it isn't already opening!
|
||||
if (!doorStates.containsKey(key) || doorStates[key] == 0) {
|
||||
doorStates[key] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,6 +252,7 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
player: player,
|
||||
playerAngle: playerAngle,
|
||||
fov: fov,
|
||||
doorOffsets: doorOffsets,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user