@@ -11,6 +11,7 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
final LinearCoordinates player;
|
final LinearCoordinates player;
|
||||||
final double playerAngle;
|
final double playerAngle;
|
||||||
final double fov;
|
final double fov;
|
||||||
|
final Map<String, double> doorOffsets;
|
||||||
|
|
||||||
RaycasterPainter({
|
RaycasterPainter({
|
||||||
required this.map,
|
required this.map,
|
||||||
@@ -18,6 +19,7 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
required this.player,
|
required this.player,
|
||||||
required this.playerAngle,
|
required this.playerAngle,
|
||||||
required this.fov,
|
required this.fov,
|
||||||
|
required this.doorOffsets,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -61,6 +63,11 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
bool hitOutOfBounds = false;
|
bool hitOutOfBounds = false;
|
||||||
int side = 0;
|
int side = 0;
|
||||||
int hitWallId = 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) {
|
if (rayDirX < 0) {
|
||||||
stepX = -1;
|
stepX = -1;
|
||||||
@@ -94,8 +101,33 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
mapX < 0 ||
|
mapX < 0 ||
|
||||||
mapX >= map[0].length) {
|
mapX >= map[0].length) {
|
||||||
hit = true;
|
hit = true;
|
||||||
hitOutOfBounds = true; // Safely abort if we clip out of bounds
|
hitOutOfBounds = true;
|
||||||
} else if (map[mapY][mapX] > 0) {
|
} 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;
|
hit = true;
|
||||||
hitWallId = map[mapY][mapX];
|
hitWallId = map[mapY][mapX];
|
||||||
}
|
}
|
||||||
@@ -127,6 +159,7 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
size,
|
size,
|
||||||
hitWallId,
|
hitWallId,
|
||||||
textures,
|
textures,
|
||||||
|
doorOffset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,28 +173,29 @@ class RaycasterPainter extends CustomPainter {
|
|||||||
Size size,
|
Size size,
|
||||||
int hitWallId,
|
int hitWallId,
|
||||||
List<Matrix<int>> textures,
|
List<Matrix<int>> textures,
|
||||||
|
double doorOffset,
|
||||||
) {
|
) {
|
||||||
if (distance <= 0.01) distance = 0.01;
|
if (distance <= 0.01) distance = 0.01;
|
||||||
|
|
||||||
double wallHeight = size.height / distance;
|
double wallHeight = size.height / distance;
|
||||||
int drawStart = ((size.height / 2) - (wallHeight / 2)).toInt();
|
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).
|
// Wolf3D stores textures in pairs. Even = N/S (Light), Odd = E/W (Dark).
|
||||||
int texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
|
int texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
|
||||||
|
int texX = (wallX * 64).toInt().clamp(0, 63);
|
||||||
|
|
||||||
// INTERCEPT DOORS
|
// INTERCEPT DOORS
|
||||||
if (hitWallId >= 90) {
|
if (hitWallId >= 90) {
|
||||||
// Safely clamp the door texture index so it never crashes the paint loop!
|
// Safely clamp the door texture index so it never crashes the paint loop!
|
||||||
texNum = 98.clamp(0, textures.length - 1);
|
texNum = 98.clamp(0, textures.length - 1);
|
||||||
|
texX = ((wallX - doorOffset) * 64).toInt().clamp(0, 63);
|
||||||
} else {
|
} else {
|
||||||
// Standard wall texture pairing
|
// Standard wall texture pairing
|
||||||
texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
|
texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
|
||||||
if (side == 1) texNum += 1;
|
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 == 0 && math.cos(playerAngle) > 0) texX = 63 - texX;
|
||||||
if (side == 1 && math.sin(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 _isLoading = true;
|
||||||
bool _spaceWasPressed = false;
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.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) {
|
void _tick(Duration elapsed) {
|
||||||
const double moveSpeed = 0.12;
|
const double moveSpeed = 0.12;
|
||||||
const double turnSpeed = 0.08;
|
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 moveStepX = 0;
|
||||||
double moveStepY = 0;
|
double moveStepY = 0;
|
||||||
|
|
||||||
final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed;
|
final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed;
|
||||||
|
|
||||||
// 1. Calculate intended movement amounts
|
|
||||||
if (pressedKeys.contains(LogicalKeyboardKey.keyW)) {
|
if (pressedKeys.contains(LogicalKeyboardKey.keyW)) {
|
||||||
moveStepX += math.cos(playerAngle) * moveSpeed;
|
moveStepX += math.cos(playerAngle) * moveSpeed;
|
||||||
moveStepY += math.sin(playerAngle) * moveSpeed;
|
moveStepY += math.sin(playerAngle) * moveSpeed;
|
||||||
@@ -149,8 +176,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
moveStepX -= math.cos(playerAngle) * moveSpeed;
|
moveStepX -= math.cos(playerAngle) * moveSpeed;
|
||||||
moveStepY -= math.sin(playerAngle) * moveSpeed;
|
moveStepY -= math.sin(playerAngle) * moveSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Handle Turning
|
|
||||||
if (pressedKeys.contains(LogicalKeyboardKey.keyA)) {
|
if (pressedKeys.contains(LogicalKeyboardKey.keyA)) {
|
||||||
playerAngle -= turnSpeed;
|
playerAngle -= turnSpeed;
|
||||||
}
|
}
|
||||||
@@ -160,18 +185,14 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
if (playerAngle < 0) playerAngle += 2 * math.pi;
|
if (playerAngle < 0) playerAngle += 2 * math.pi;
|
||||||
if (playerAngle > 2 * math.pi) playerAngle -= 2 * math.pi;
|
if (playerAngle > 2 * math.pi) playerAngle -= 2 * math.pi;
|
||||||
|
|
||||||
// 3. Wall Sliding Collision (with Hitbox Margin!)
|
// 2. UPDATED WALL COLLISION
|
||||||
// A 0.3 margin keeps the camera plane safely out of the walls
|
|
||||||
const double margin = 0.3;
|
const double margin = 0.3;
|
||||||
|
|
||||||
double newX = player.x + moveStepX;
|
double newX = player.x + moveStepX;
|
||||||
// Check the edge of our hitbox, not just the center point
|
|
||||||
int checkX = (moveStepX > 0)
|
int checkX = (moveStepX > 0)
|
||||||
? (newX + margin).toInt()
|
? (newX + margin).toInt()
|
||||||
: (newX - margin).toInt();
|
: (newX - margin).toInt();
|
||||||
|
|
||||||
// Try to move along the X axis
|
if (_isWalkable(checkX, player.y.toInt())) {
|
||||||
if (currentLevel[player.y.toInt()][checkX] == 0) {
|
|
||||||
player = (x: newX, y: player.y);
|
player = (x: newX, y: player.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,12 +201,11 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
? (newY + margin).toInt()
|
? (newY + margin).toInt()
|
||||||
: (newY - margin).toInt();
|
: (newY - margin).toInt();
|
||||||
|
|
||||||
// Try to move along the Y axis
|
if (_isWalkable(player.x.toInt(), checkY)) {
|
||||||
if (currentLevel[checkY][player.x.toInt()] == 0) {
|
|
||||||
player = (x: player.x, y: newY);
|
player = (x: player.x, y: newY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. DOOR INTERACTION LOGIC
|
// 3. UPDATED DOOR INTERACTION
|
||||||
bool isSpacePressed = pressedKeys.contains(LogicalKeyboardKey.space);
|
bool isSpacePressed = pressedKeys.contains(LogicalKeyboardKey.space);
|
||||||
|
|
||||||
if (isSpacePressed && !_spaceWasPressed) {
|
if (isSpacePressed && !_spaceWasPressed) {
|
||||||
@@ -196,10 +216,12 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
targetY < currentLevel.length &&
|
targetY < currentLevel.length &&
|
||||||
targetX > 0 &&
|
targetX > 0 &&
|
||||||
targetX < currentLevel[0].length) {
|
targetX < currentLevel[0].length) {
|
||||||
int targetBlock = currentLevel[targetY][targetX];
|
if (currentLevel[targetY][targetX] >= 90) {
|
||||||
|
String key = '$targetX,$targetY';
|
||||||
if (targetBlock >= 90) {
|
// Start the animation if it isn't already opening!
|
||||||
currentLevel[targetY][targetX] = 0;
|
if (!doorStates.containsKey(key) || doorStates[key] == 0) {
|
||||||
|
doorStates[key] = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,6 +252,7 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
player: player,
|
player: player,
|
||||||
playerAngle: playerAngle,
|
playerAngle: playerAngle,
|
||||||
fov: fov,
|
fov: fov,
|
||||||
|
doorOffsets: doorOffsets,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user