Skip to content

Commit

Permalink
add all game mechaniscs
Browse files Browse the repository at this point in the history
  • Loading branch information
renancaraujo committed Oct 19, 2023
1 parent aacef5a commit 2c639f3
Show file tree
Hide file tree
Showing 15 changed files with 601 additions and 94 deletions.
41 changes: 29 additions & 12 deletions lib/game/components/camera_target.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:crystal_ball/game/constants.dart';
import 'package:crystal_ball/game/crystal_ball.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
Expand All @@ -7,34 +6,42 @@ import 'package:flutter/animation.dart';
class CameraTarget extends PositionComponent with HasGameRef<CrystalBallGame> {
CameraTarget()
: super(
position: Vector2(0.0, -kCameraSize.height / 4),
size: Vector2.all(1),
position: Vector2(0, 0),
size: Vector2.all(0),
anchor: Anchor.center,
priority: 0x7fffffff,
);

final effectController = CurvedEffectController(
final effectController = GoodCurvedEffectController(
0.1,
Curves.easeInOut,
)..setToEnd();

late final moveEffect = MoveCameraTarget(position, effectController);

@override
Color get debugColor => const Color(0xFFFFFF00);

@override
bool get debugMode => true;

@override
Future<void> onLoad() async {
await add(moveEffect);
}

void go({required Vector2 to, bool calm = false}) {
effectController.duration = calm ? 10 : 0.5;
void go({
required Vector2 to,
Curve curve = Curves.easeInOut,
double duration = 0.25,
double scale = 1,
}) {
effectController
..duration = duration * 4
..curve = curve;

moveEffect.go(to: to);
// add(ScaleEffect.to(Vector2.all(scale), effectController));
}

@override
void update(double dt) {
super.update(dt);
game.camera.viewfinder.zoom = scale.x;
}
}

Expand Down Expand Up @@ -66,3 +73,13 @@ class MoveCameraTarget extends Effect with EffectTarget<CameraTarget> {
_from = target.position;
}
}

class GoodCurvedEffectController extends DurationEffectController {
GoodCurvedEffectController(super.duration, this.curve)
: assert(duration > 0, 'Duration must be positive: $duration');

Curve curve;

@override
double get progress => curve.transform(timer / duration);
}
3 changes: 3 additions & 0 deletions lib/game/components/components.dart
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export 'camera_target.dart';
export 'game_state_controller.dart';
export 'keyboard_handler.dart';
export 'platform_spawner.dart';
56 changes: 56 additions & 0 deletions lib/game/components/game_state_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'package:crystal_ball/game/game.dart';
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/animation.dart';

class GameStateController extends Component
with
HasGameRef<CrystalBallGame>,
FlameBlocListenable<GameCubit, GameState> {
Timer? timer;

@override
void onNewState(GameState state) {
super.onNewState(state);
timer?.stop();
timer = null;
switch (state) {
case GameState.initial:
break;
case GameState.starting:
game.world.cameraTarget.go(
to: Vector2(0, -kCameraSize.height / 4),
curve: Curves.easeInOutCubic,
duration: kOpeningDuration,
);
timer = Timer(
kOpeningDuration,
onTick: bloc.gameStarted,
);
case GameState.playing:
break;
case GameState.gameOver:
game.world.cameraTarget.go(
to: Vector2(0, 0),
curve: Curves.easeInOutCubic,
duration: 0.3,
);
game.world.theBall.position = Vector2.zero();
for (final platform in game.world.flameMultiBlocProvider
.descendants()
.whereType<Platform>()) {
platform.removeFromParent();
}
timer = Timer(
1,
onTick: bloc.setInitial,
);
}
}

@override
void update(double dt) {
super.update(dt);
timer?.update(dt);
}
}
81 changes: 81 additions & 0 deletions lib/game/components/keyboard_handler.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import 'package:crystal_ball/game/game.dart';
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/services.dart';

class KeyboardHandlerSync extends Component
with FlameBlocReader<GameCubit, GameState> {
KeyboardHandlerSync();

@override
Future<void> onLoad() async {
await add(KeyboardListenerComponent(
keyDown: {
LogicalKeyboardKey.space: onSpace,
},
));

return super.onLoad();
}

bool onSpace(Set<LogicalKeyboardKey> logicalKeys) {
if (bloc.state == GameState.initial) {
bloc.startGame();
return false;
}
return true;
}
}

class DirectionalController extends Component
with FlameBlocReader<GameCubit, GameState> {
DirectionalController();

double _directionalCoefficient = 0;

double get directionalCoefficient => _directionalCoefficient;

@override
Future<void> onLoad() async {
await add(KeyboardListenerComponent(
keyDown: {
LogicalKeyboardKey.arrowLeft: onLeftStart,
LogicalKeyboardKey.arrowRight: onRightStart,
},
keyUp: {
LogicalKeyboardKey.arrowLeft: onLeftEnd,
LogicalKeyboardKey.arrowRight: onRightEnd,
},
));

return super.onLoad();
}

bool onLeftStart(Set<LogicalKeyboardKey> logicalKeys) {
if (!bloc.isPlaying) return true;
_directionalCoefficient = -1;
return false;
}

bool onRightStart(Set<LogicalKeyboardKey> logicalKeys) {
if (!bloc.isPlaying) return true;
_directionalCoefficient = 1;
return false;
}

bool onLeftEnd(Set<LogicalKeyboardKey> logicalKeys) {
if (!bloc.isPlaying) return true;
if (_directionalCoefficient < 0) {
_directionalCoefficient = 0;
}
return false;
}

bool onRightEnd(Set<LogicalKeyboardKey> logicalKeys) {
if (!bloc.isPlaying) return true;
if (_directionalCoefficient > 0) {
_directionalCoefficient = 0;
}
return false;
}
}
91 changes: 84 additions & 7 deletions lib/game/components/platform_spawner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,101 @@ import 'dart:math';

import 'package:crystal_ball/game/game.dart';
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';

class PlatformSpawner extends Component with HasGameRef<CrystalBallGame> {
class PlatformSpawner extends Component
with
HasGameRef<CrystalBallGame>,
FlameBlocListenable<GameCubit, GameState> {
PlatformSpawner({
required this.random,
});

final Random random;

static const interval = 2.0;
double currentMinY = kStartPlatformHeight;

bool needsPreloadCheck = false;

@override
void onLoad() {
game.world.add(
void onLoad() {}

Future<void> spawnPlatform() async {
final y = currentMinY;
final padedHalfWidth = (kCameraSize.width - 100) / 2;
final x = random.nextDoubleInBetween(-padedHalfWidth, padedHalfWidth);

final color = PlatformColor.random(random);

final width = kPlatformMinWidth +
random.nextDoubleAntiSmooth() * kPlatformWidthVariation;

final size = Vector2(width, kPlatformHeight);

await game.world.flameMultiBlocProvider.add(
Platform(
position: Vector2(0, -200),
size: Vector2(200, 35),
color: PlatformColor.green,
position: Vector2(x, -y),
size: size,
color: color,
),
);

final interval = kMeanPlatformInterval +
random.nextVariation() * kPlatformIntervalVariation;
currentMinY += interval;
}

void preloadPlatforms() async {
needsPreloadCheck = false;
int count = 0;
while (distanceToCameraTop < kPlatformPreloadArea && count < 10) {
await spawnPlatform();
count++;
}
needsPreloadCheck = true;
}

void spawnIntitialPlatforms() async {
print("spwn init");
await Future<void>.delayed(Duration(milliseconds: 1200));
int count = 0;
while (distanceToCameraTop < kPlatformPreloadArea && count < 10) {
final delayed = Future<void>.delayed(
Duration(
milliseconds: (kPlatformSpawnDuration * 1000).floor(),
),
);
await Future.wait<void>([spawnPlatform(), delayed]);
count++;
}
needsPreloadCheck = true;
}

@override
void onNewState(GameState state) {
super.onNewState(state);
switch (state) {
case GameState.initial:
needsPreloadCheck = false;
currentMinY = kStartPlatformHeight;
case GameState.starting:
spawnIntitialPlatforms();
case GameState.playing:
case GameState.gameOver:
}
}

double get cameraTop => game.world.cameraTarget.y - kCameraSize.height / 2;

double get distanceToCameraTop => currentMinY - (-cameraTop);

@override
void update(double dt) {
super.update(dt);

if (needsPreloadCheck && distanceToCameraTop < kPlatformPreloadArea) {
needsPreloadCheck = false;
preloadPlatforms();
}
}
}
42 changes: 41 additions & 1 deletion lib/game/constants.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,53 @@
import 'dart:math';

import 'package:flame/components.dart';

const (double, double) kCameraSize = (900, 1600);
const double kPlayerRadius = 50;
const double kPlayerRadius = 20;
const (double, double) kPlayerSize = (kPlayerRadius * 2, kPlayerRadius * 2);

const double kOpeningDuration = 4;

const double kPlatformSpawnDuration = 0.4;
const double kPlatformVerticalInterval = 1;
const double kStartPlatformHeight = 400;
const double kMeanPlatformInterval = 370;
const double kPlatformIntervalVariation = 100;
const double kPlatformMinWidth = 120;
const double kPlatformWidthVariation = 100;
const double kPlatformHeight = 40;
const double kPlatformPreloadArea = 1600;

const double kGravity = 100;
const double kJumpVelocity = 3000;

const double kReaperTolerance = 800;

extension TransformRec on (double, double) {
Vector2 get asVector2 => Vector2($1, $2);

double get width => $1;

double get height => $2;
}

extension RandomX on Random {
double nextDoubleAntiSmooth() {
final normal = nextDouble();
return _invSmoothstep(normal);
}

double nextVariation() {
return nextDoubleAntiSmooth() * 2 - 1;
}

double nextDoubleInBetween(double min, double max) {
return nextDoubleAntiSmooth() * (max - min) + min;
}
}

double _invSmoothstep(double normal) {
if (normal <= 0) return 0;
if (normal >= 1) return 1;
return 0.5 - sin(asin(1 - 2 * normal) / 3);
}
Loading

0 comments on commit 2c639f3

Please sign in to comment.