feat: initial Star Taxi APK test prototype
This commit is contained in:
304
lib/main.dart
Normal file
304
lib/main.dart
Normal file
@@ -0,0 +1,304 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const StarTaxiApp());
|
||||
}
|
||||
|
||||
class StarTaxiApp extends StatelessWidget {
|
||||
const StarTaxiApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Star Taxi – APK Test',
|
||||
theme: ThemeData.dark(useMaterial3: true),
|
||||
home: const StarTaxiDemo(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StarTaxiDemo extends StatefulWidget {
|
||||
const StarTaxiDemo({super.key});
|
||||
|
||||
@override
|
||||
State<StarTaxiDemo> createState() => _StarTaxiDemoState();
|
||||
}
|
||||
|
||||
class _StarTaxiDemoState extends State<StarTaxiDemo> {
|
||||
static const int lanes = 3;
|
||||
static const double playerY = 0.86;
|
||||
|
||||
final _rng = math.Random();
|
||||
final List<_Obstacle> _obstacles = [];
|
||||
|
||||
Timer? _loop;
|
||||
double _lane = 1;
|
||||
double _speed = 0.010;
|
||||
int _score = 0;
|
||||
int _best = 0;
|
||||
bool _running = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_spawnInitial();
|
||||
_loop = Timer.periodic(const Duration(milliseconds: 16), (_) => _tick());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_loop?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _spawnInitial() {
|
||||
_obstacles.clear();
|
||||
for (var i = 0; i < 5; i++) {
|
||||
_obstacles.add(_Obstacle(
|
||||
lane: _rng.nextInt(lanes).toDouble(),
|
||||
y: -0.8 - i * 0.45,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _tick() {
|
||||
if (!_running) return;
|
||||
|
||||
setState(() {
|
||||
_speed = (_speed + 0.00001).clamp(0.010, 0.03);
|
||||
_score += 1;
|
||||
|
||||
for (final o in _obstacles) {
|
||||
o.y += _speed;
|
||||
if (o.y > 1.15) {
|
||||
o.y = -0.9 - _rng.nextDouble() * 0.8;
|
||||
o.lane = _rng.nextInt(lanes).toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
for (final o in _obstacles) {
|
||||
if ((o.y - playerY).abs() < 0.085 && (o.lane - _lane).abs() < 0.1) {
|
||||
_running = false;
|
||||
_best = math.max(_best, _score);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _move(int dir) {
|
||||
if (!_running) return;
|
||||
setState(() {
|
||||
_lane = (_lane + dir).clamp(0, lanes - 1).toDouble();
|
||||
});
|
||||
}
|
||||
|
||||
void _restart() {
|
||||
setState(() {
|
||||
_score = 0;
|
||||
_speed = 0.010;
|
||||
_lane = 1;
|
||||
_running = true;
|
||||
_spawnInitial();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Score: $_score', style: const TextStyle(fontSize: 20)),
|
||||
Text('Best: $_best', style: const TextStyle(fontSize: 18)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 9 / 16,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF0B1021), Color(0xFF1B2348)],
|
||||
),
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: _RoadPainter(
|
||||
lane: _lane,
|
||||
obstacles: _obstacles,
|
||||
),
|
||||
child: !_running
|
||||
? Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.65),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Crash! 💥',
|
||||
style: TextStyle(fontSize: 26)),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: _restart,
|
||||
child: const Text('Nochmal'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => _move(-1),
|
||||
icon: const Icon(Icons.arrow_left),
|
||||
label: const Text('Links'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => _move(1),
|
||||
icon: const Icon(Icons.arrow_right),
|
||||
label: const Text('Rechts'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 10),
|
||||
child: Text('Star Taxi – kleines 3D-Arcade APK Testbuild'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoadPainter extends CustomPainter {
|
||||
final double lane;
|
||||
final List<_Obstacle> obstacles;
|
||||
|
||||
const _RoadPainter({required this.lane, required this.obstacles});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final road = Paint()..color = const Color(0xFF2A2F40);
|
||||
final line = Paint()
|
||||
..color = const Color(0xAAFFFFFF)
|
||||
..strokeWidth = 2;
|
||||
|
||||
final topWidth = size.width * 0.35;
|
||||
final bottomWidth = size.width * 0.95;
|
||||
|
||||
final roadPath = Path()
|
||||
..moveTo((size.width - topWidth) / 2, 0)
|
||||
..lineTo((size.width + topWidth) / 2, 0)
|
||||
..lineTo((size.width + bottomWidth) / 2, size.height)
|
||||
..lineTo((size.width - bottomWidth) / 2, size.height)
|
||||
..close();
|
||||
|
||||
canvas.drawPath(roadPath, road);
|
||||
|
||||
for (int i = 1; i < 3; i++) {
|
||||
final t = i / 3;
|
||||
final xTop = size.width / 2 + (t - 0.5) * topWidth;
|
||||
final xBottom = size.width / 2 + (t - 0.5) * bottomWidth;
|
||||
canvas.drawLine(Offset(xTop, 0), Offset(xBottom, size.height), line);
|
||||
}
|
||||
|
||||
for (double y = 0; y < 1.1; y += 0.12) {
|
||||
final py = y * size.height;
|
||||
final w = _roadWidthAtY(size, py, topWidth, bottomWidth);
|
||||
canvas.drawLine(
|
||||
Offset(size.width / 2 - w * 0.04, py),
|
||||
Offset(size.width / 2 + w * 0.04, py),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.35)
|
||||
..strokeWidth = 2,
|
||||
);
|
||||
}
|
||||
|
||||
for (final o in obstacles) {
|
||||
_drawCar(
|
||||
canvas,
|
||||
size,
|
||||
lane: o.lane,
|
||||
yNorm: o.y,
|
||||
color: const Color(0xFFE26D5A),
|
||||
);
|
||||
}
|
||||
|
||||
_drawCar(
|
||||
canvas,
|
||||
size,
|
||||
lane: lane,
|
||||
yNorm: _StarTaxiDemoState.playerY,
|
||||
color: const Color(0xFF3DDC97),
|
||||
);
|
||||
}
|
||||
|
||||
void _drawCar(Canvas canvas, Size size,
|
||||
{required double lane, required double yNorm, required Color color}) {
|
||||
final y = yNorm * size.height;
|
||||
final wRoad = _roadWidthAtY(size, y, size.width * 0.35, size.width * 0.95);
|
||||
final laneWidth = wRoad / 3;
|
||||
final left = size.width / 2 - wRoad / 2 + lane * laneWidth + laneWidth * 0.15;
|
||||
final width = laneWidth * 0.7;
|
||||
final height = width * 0.9;
|
||||
|
||||
final rect = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(left, y - height / 2, width, height),
|
||||
const Radius.circular(8),
|
||||
);
|
||||
canvas.drawRRect(rect, Paint()..color = color);
|
||||
canvas.drawRRect(
|
||||
rect,
|
||||
Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2
|
||||
..color = Colors.white.withValues(alpha: 0.65),
|
||||
);
|
||||
}
|
||||
|
||||
double _roadWidthAtY(Size size, double y, double topWidth, double bottomWidth) {
|
||||
final t = (y / size.height).clamp(0.0, 1.0);
|
||||
return topWidth + (bottomWidth - topWidth) * t;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _RoadPainter oldDelegate) => true;
|
||||
}
|
||||
|
||||
class _Obstacle {
|
||||
_Obstacle({required this.lane, required this.y});
|
||||
|
||||
double lane;
|
||||
double y;
|
||||
}
|
||||
Reference in New Issue
Block a user