Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hamster Hurdle #87

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
3 changes: 2 additions & 1 deletion open_earable/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
Expand All @@ -8,7 +9,7 @@ if (localPropertiesFile.exists()) {

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
throw GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
Binary file added open_earable/assets/images/background_soil.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added open_earable/assets/images/hamster.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added open_earable/assets/images/nut_obstacle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added open_earable/assets/images/root_obstacle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions open_earable/lib/apps_tab/apps_tab.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:open_earable/apps_tab/hamster_hurdle/hamster_hurdle_app.dart';
import 'package:open_earable/apps_tab/posture_tracker/model/earable_attitude_tracker.dart';
import 'package:open_earable/apps_tab/posture_tracker/view/posture_tracker_view.dart';
import 'package:open_earable/apps_tab/neck_stretch/view/stretch_app_view.dart';
Expand Down Expand Up @@ -189,6 +190,25 @@ class AppsTab extends StatelessWidget {
);
},
),
AppInfo(
logoPath: "lib/apps_tab/hamster_hurdle/assets/logo.png",
//iconData: Icons.music_note,
title: "Hamster Hurdle",
description: "Your fun warmup",
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Material(
child: Theme(
data: materialTheme,
child: HamsterHurdleApp(openEarable),
),
),
),
);
},
),
// ... similarly for other apps
];
}
Expand Down
19 changes: 19 additions & 0 deletions open_earable/lib/apps_tab/hamster_hurdle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Hamster Hurdle


## About
This application is an endless runner game. By clicking on Start a game is loaded, where a hamster
is running through a tunnel. By making a small jump (in other words by quickly moving the
earable upwards) the player can jump over obstacles that are randomly generated and appear on
screen. To make the hamster duck the player has to do a squat (move the earable down). To get out
of the ducking position the player has to move upwards again. When the hamster collides with an
object the game ends. Throughout the game a score is calculated based on how long the game is
going on.


## Tests
I have a windows PC. Because of the Gradle error i was unable to test my App in the Android Emulator
or on a physical Android Phone. That's why Hamster Hurdle was only tested on Chrome(Web)
and Edge(Web). Please keep this in mind when testing the app.


Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:flame/components.dart';
import 'package:flame/parallax.dart';
import 'package:flutter/cupertino.dart';
import 'package:open_earable/apps_tab/hamster_hurdle/hamster_hurdles_game.dart';
import 'package:open_earable/apps_tab/hamster_hurdle/hamster_hurdles_world.dart';

///Class responsible for generating the moving background in the game.
class HurdleBackground extends ParallaxComponent<HamsterHurdle>
with HasWorldReference<HamsterHurdleWorld> {
@override
Future<void> onLoad() async {
anchor = Anchor.center;
parallax = await game.loadParallax(
[
ParallaxImageData('background_soil.png'),
],
baseVelocity: Vector2(world.gameSpeed, 0),
repeat: ImageRepeat.repeatX,
);
}
}
125 changes: 125 additions & 0 deletions open_earable/lib/apps_tab/hamster_hurdle/components/hamster.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import 'dart:ui';

import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:open_earable/apps_tab/hamster_hurdle/components/obstacle.dart';

import '../hamster_hurdles_game.dart';
import '../hamster_hurdles_world.dart';

///The player in the game.
class Hamster extends PositionComponent
with
HasGameRef<HamsterHurdle>,
HasWorldReference<HamsterHurdleWorld>,
CollisionCallbacks {
Hamster({required this.initialXPosition, required super.size})
: super(
anchor: Anchor.bottomCenter,
priority: 3,
);

///The x-coordinate the hamster appears on on the screen after loading the game.
final double initialXPosition;

///gravity used to calculate falling movement for jump.
final double _gravity = 2.3;

///the upwards movement used to calculate velocity during jump.
final double _jumpForce = -15;

///The maximum height the hamster can jump.
late double _maxJumpHeight;

///the size the hamster has at loading the game.
late double initialSize;

///The Sprite of the hamster used to render the character.
late Sprite _hamsterSprite;

///Used for calculating the jump movement.
double _velocity = 0;

@override
Future<void> onLoad() async {
await super.onLoad();
_hamsterSprite = await Sprite.load("hamster.png");
add(CircleHitbox(
radius: size.y * 0.35,
anchor: Anchor.center,
position: Vector2(position.x + size.x / 2, position.y + size.y / 2),)
..collisionType = CollisionType.active,);
position.y = world.groundLevel;
position.x = initialXPosition;
_maxJumpHeight = world.tunnelHeight - size.y;
initialSize = size.y;
}

///Updates velocity to match a jump movement and updates the posture of
///the hamster, if hamster was previously ducking.
void jump(GameAction lastAction) {
if (lastAction == GameAction.ducking) {
getUp();
}
_velocity = _jumpForce;
}

@override
void render(Canvas canvas) {
super.render(canvas);
_hamsterSprite.render(
canvas,
size: size,
);
}

///Updates the vertical size of the hamster to show a ducking motion.
void duck() {
size = Vector2(size.x, initialSize / 2);
}

///Sets the vertical size of the hamster back to the initial size.
void getUp() {
size = Vector2(size.x, initialSize);
}

///checks if the hamster is currently touching the ground.
bool isTouchingGround() {
return position.y >= world.groundLevel;
}

@override
void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollision(intersectionPoints, other);
if (other is Obstacle) {
game.playState = PlayState.gameOver;
world.stopGame();
}
}

@override
void update(double dt) {
super.update(dt);
if (_velocity < 0 &&
position.y > world.groundLevel - world.tunnelHeight * 0.3) {
_velocity += (_gravity * 0.7) * dt; // Reduced gravity when rising and
// hamster position in y direction is lower than height of ground obstacles.
// This is for a good playing experience, the player rises relatively
// quickly over the obstacle but then stays in the air long enough to get
// over the obstacle
} else {
_velocity += _gravity * dt;
}
position.y += _velocity;
bool belowGround = position.y > world.groundLevel;
if (belowGround) {
position.y = world.groundLevel;
_velocity = 0;
}
//Prevents hamster from jumping outside the tunnel
if (position.y < world.groundLevel - _maxJumpHeight) {
position.y = world.groundLevel - _maxJumpHeight;
_velocity = 0; // Stop upward velocity when max height is reached
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flutter/cupertino.dart';

import '../hamster_hurdles_game.dart';
import '../hamster_hurdles_world.dart';


///Class representing the Tunnel the hamster runs through.
class HamsterTunnel extends RectangleComponent
with HasWorldReference<HamsterHurdleWorld>, HasGameRef<HamsterHurdle> {
HamsterTunnel({
required this.tunnelHeight,
}) : super(
paint: Paint()
..color = const Color(0xffad784c)
..style = PaintingStyle.fill,
priority: 1,
anchor: Anchor.bottomCenter,);

final double tunnelHeight;

@override
Future<void> onLoad() async {
super.onLoad();
position.y = world.groundLevel;
size = Vector2(game.size.x, tunnelHeight);
}

@override
void update(double dt) {
size = Vector2(game.size.x, tunnelHeight);
super.update(dt);
}
}
77 changes: 77 additions & 0 deletions open_earable/lib/apps_tab/hamster_hurdle/components/obstacle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/extensions.dart';
import 'package:open_earable/apps_tab/hamster_hurdle/hamster_hurdles_world.dart';


///Class representing an obstacle in the game.
class Obstacle extends PositionComponent
with HasWorldReference<HamsterHurdleWorld> {

///The image rendered to visually represent the obstacle.
late Sprite _obstacleSprite;

///The type of the obstacle.
final ObstacleType obstacleType;

///The initial x position that the obstacle is placed on on the screen.
final double initialXPosition;

///The speed at which the obstacles move along the x-axis.
final double gameSpeed;

Obstacle(
{required this.gameSpeed,
required this.initialXPosition,
required this.obstacleType,})
: super(priority: 2);

@override
Future<void> onLoad() async {
super.onLoad();
double height;
String imageSource;
switch (obstacleType) {
case ObstacleType.root:
imageSource = "root_obstacle.png";
anchor = Anchor.topLeft;
height = world.rootHeight;
position.y = world.groundLevel - world.tunnelHeight;
add(RectangleHitbox()..collisionType = CollisionType.passive);
break;
case ObstacleType.nuts:
imageSource = "nut_obstacle.png";
anchor = Anchor.bottomLeft;
height = world.nutHeight;
position.y = world.groundLevel;
add(CircleHitbox()..collisionType = CollisionType.passive);
break;
}
position.x = initialXPosition;
_obstacleSprite = await Sprite.load(imageSource);
final ratio = _obstacleSprite.srcSize.x / _obstacleSprite.srcSize.y;
size = Vector2(height * ratio, height);
}

@override
void render(Canvas canvas) {
super.render(canvas);
_obstacleSprite.render(
canvas,
size: size,
);
}

///Moves the obstacles along the x-axis according to gameSpeed.
@override
void update(double dt) {
super.update(dt);
position.x -= dt * gameSpeed;
}
}

///Encapsulates the types an obstacle can be.
enum ObstacleType {
root,
nuts,
}
Binary file not shown.
42 changes: 42 additions & 0 deletions open_earable/lib/apps_tab/hamster_hurdle/gameOverOverlay.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:flutter/cupertino.dart';

import 'hamster_hurdles_game.dart';

///A Widget representing what is being shown when the player loses.
class GameOverOverlay extends StatelessWidget {

///The final score achieved by the player during the game.
final int finalScore;

const GameOverOverlay({
required this.finalScore,
super.key,
});


/// A widget that displays a "Game Over" screen with the final score
/// and a prompt to play again.
@override
Widget build(BuildContext context) {
double screenHeight = MediaQuery.of(context).size.height;
return Column(children: [
Container(
height: screenHeight / 2,
alignment: const Alignment(0, -0.5),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GameText(text: "Game Over", fontSize: 48,),
const SizedBox(height: 16),
GameText(text: "Tap to play again", fontSize: 36,),
],
),
),
Container(
alignment: const Alignment(0, 0.5),
height: screenHeight / 2,
child: GameText(text: "Final Score: $finalScore", fontSize: 36,),
)
]);
}
}
Loading