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 createState() => _WolfRendererState(); } class _WolfRendererState extends State 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), ], ), ), ); } }