Files
wolf_dart/packages/wolf_3d_renderer/lib/wolf_3d_renderer.dart

183 lines
5.8 KiB
Dart

import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
import 'package:wolf_3d_input/wolf_3d_input.dart';
import 'package:wolf_3d_renderer/hud.dart';
import 'package:wolf_3d_renderer/raycast_painter.dart';
import 'package:wolf_3d_renderer/weapon_painter.dart';
class WolfRenderer extends StatefulWidget {
const WolfRenderer(
this.data, {
required this.difficulty,
required this.startingEpisode,
required this.audio,
super.key,
});
final WolfensteinData data;
final Difficulty difficulty;
final int startingEpisode;
final EngineAudio audio;
@override
State<WolfRenderer> createState() => _WolfRendererState();
}
class _WolfRendererState extends State<WolfRenderer>
with SingleTickerProviderStateMixin {
// 1. The input reader
final WolfInput inputManager = WolfInput();
// 2. The central brain of the game
late final WolfEngine engine;
late Ticker _gameLoop;
final FocusNode _focusNode = FocusNode();
final double fov = math.pi / 3;
@override
void initState() {
super.initState();
// Initialize the engine and hand over all the data and dependencies
engine = WolfEngine(
data: widget.data,
difficulty: widget.difficulty,
startingEpisode: widget.startingEpisode,
audio: widget.audio,
onGameWon: () {
Navigator.of(context).pop();
},
);
engine.init();
// Start the loop
_gameLoop = createTicker(_tick)..start();
_focusNode.requestFocus();
}
// --- ORCHESTRATOR ---
void _tick(Duration elapsed) {
// 1. Read the keyboard state
inputManager.update();
// 2. Let the engine do all the math, physics, collision, and logic!
engine.tick(elapsed, inputManager.currentInput);
// 3. Force a UI repaint using the newly updated engine state
setState(() {});
}
@override
void dispose() {
_gameLoop.dispose();
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Wait for the engine to finish parsing the level
if (!engine.isInitialized) {
return const Center(child: CircularProgressIndicator(color: Colors.teal));
}
return Scaffold(
backgroundColor: Colors.black,
body: KeyboardListener(
focusNode: _focusNode,
autofocus: true,
onKeyEvent: (_) {},
child: Column(
children: [
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return Center(
child: AspectRatio(
aspectRatio: 16 / 10,
child: Stack(
children: [
// --- 3D WORLD ---
CustomPaint(
size: Size(
constraints.maxWidth,
constraints.maxHeight,
),
painter: RaycasterPainter(
// Read state directly from the engine
map: engine.currentLevel,
textures: widget.data.walls,
player: engine.player,
fov: fov,
doorOffsets: engine.doorManager
.getOffsetsForRenderer(),
entities: engine.entities,
sprites: widget.data.sprites,
activePushwall:
engine.pushwallManager.activePushwall,
),
),
// --- FIRST PERSON WEAPON ---
Positioned(
bottom: -20,
left: 0,
right: 0,
child: Center(
child: Transform.translate(
offset: Offset(
0,
engine.player.weaponAnimOffset,
),
child: SizedBox(
width: 500,
height: 500,
child: CustomPaint(
painter: WeaponPainter(
sprite:
widget.data.sprites[engine
.player
.currentWeapon
.getCurrentSpriteIndex(
widget.data.sprites.length,
)],
),
),
),
),
),
),
// --- DAMAGE FLASH ---
if (engine.damageFlashOpacity > 0)
Positioned.fill(
child: Container(
color: Colors.red.withValues(
alpha: engine.damageFlashOpacity,
),
),
),
],
),
),
);
},
),
),
// --- UI ---
Hud(player: engine.player),
],
),
),
);
}
}