Color Mixer Game – Made with Flame Game Engine

Color Mixer is a minimalist game made with Flame engine. It is a simple game template developed to try some features of the under development game engine.

How to play:

  • Tap 2 color cards in the bottom to mix them into a new color (average of r,g, and b values).
  • Player becomes the same color of the mixed one.
  • If the player’s color is the same as a line, it flies toward the block with the same color.
  • The game is over if the player hits a block with different color or a block marked as DANGER.

Download source code

Folder Structure

Preview snippet of game.dart, background.dart, and player.dart below. The rest can be found on Github.

game.dart

import 'dart:ui';
import 'package:color_mixer/components/actors/card.dart';
import 'package:color_mixer/components/actors/obstacle.dart';
import 'package:color_mixer/components/actors/player.dart';
import 'package:color_mixer/components/background.dart';
import 'package:color_mixer/components/level_info.dart';
import 'package:color_mixer/utils/color_helper.dart';
import 'package:color_mixer/utils/sound_helper.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart' show Colors;
class GameManager extends BaseGame with HasTapableComponents {
Size screenSize;
double halfWidth;
double halfHeight;
List<ColorCard> cards = [];
double cardSize = 60;
int cardPerRow = 2;
double spacer = 20.0;
double marginX = 50.0; //left and right margin
double playerSize = 20;
double playerSpeed = 24;
List<Color> objectives = [];
//game logic variables: color card index in card list
int level = 1;
List<int> selectedIndexes = [];
//Player & enemies
Player player;
Obstacles obstacles;
bool gameOver = false;
@override
Future<void> onLoad() {
print('Game Start');
_calculateScreenSize();
SoundHelper.bgmStop();
SoundHelper.bgm();
//game background
add(Background());
add(PlayerBackground());
//add card list to a grid
_addColorCards();
calculateObjectives();
//player
var pX = (screenSize.width / 2 - playerSize).floorToDouble();
var pY = (screenSize.height / 2 - playerSize).floorToDouble() + 50;
player = Player(Vector2(pX, pY), Vector2.all(playerSize), Colors.blue[400]);
add(player);
player.addEffect(MoveEffect(
path: [
Vector2(player.x, player.y - 100),
],
speed: playerSpeed * 10,
onComplete: () {
}));
//enemies & helpers
obstacles = Obstacles();
add(obstacles);
//add level info
add(LevelInfo());
return super.onLoad();
}
void _calculateScreenSize() {
screenSize = Size(canvasSize.toOffset().dx, canvasSize.toOffset().dy);
halfWidth = (screenSize.width / 2).floorToDouble();
halfHeight = (screenSize.height / 2).floorToDouble();
}
@override
void onResize(Vector2 canvasSize) {
super.onResize(canvasSize);
}
@override
void render(Canvas canvas) {
super.render(canvas);
}
@override
void update(double dt) {
if (gameOver) {
overlays.add("ResetMenu");
}
super.update(dt);
}
void _addColorCards() {
double gridStartX = (screenSize.width -
(cardSize * cardPerRow + spacer * (cardPerRow - 1))) /
2;
double gridStartY = screenSize.height / 2;
Vector2 gridPosition = Vector2(gridStartX, gridStartY);
//generate colors first
List<Color> colors = ColorGenerator.generate(cardPerRow * cardPerRow);
for (var i = 0; i < cardPerRow * cardPerRow; i++) {
//increase position.y every cardPerRow rows
if (i % cardPerRow == 0) {
gridPosition = Vector2(gridStartX, gridPosition.y + spacer + cardSize);
}
//add color card to game
ColorCard card = ColorCard(i, colors[i], cardSize.floorToDouble());
card.position = gridPosition;
cards.add(card);
add(card);
//move position.x of grid placeholder for next card
gridPosition += Vector2(card.width + spacer, 0);
}
}
void selectColor(int index, bool toggle) {
if (toggle) {
selectedIndexes.add(index);
} else {
selectedIndexes.remove(index); //remove where index = value
}
//mix colors if there are 2 selected cards
if (selectedIndexes.length >= 2) {
//mix color
var newColorSource = ColorGenerator.randomColor();
var mixedColor = ColorGenerator.mix(
cards[selectedIndexes[0]].color, cards[selectedIndexes[1]].color);
cards[selectedIndexes[0]].updateColor(newColorSource, true);
cards[selectedIndexes[1]].updateColor(mixedColor, false);
//update color to player
player.updatePlayerColor(mixedColor);
var newPost = _posOfObstacleWithSameColor();
if (newPost != Vector2(0, 0)) {
player.moveTo(newPost);
}
}
}
void resetSelectedColors() {
//reset selected indexes after mixing
if (selectedIndexes.length >= 2) {
selectedIndexes = [];
}
}
void resetCardColors(){
//change card color positions
List<Color> colors = ColorGenerator.generate(cardPerRow * cardPerRow);
colors.shuffle();
for(var i = 0; i < cards.length; i++){
cards[i].color = colors[i];
}
}
Vector2 _posOfObstacleWithSameColor() {
for (var o in obstacles.items) {
if (player.color == o.color) {
return Vector2((o.x + o.width / 2) - playerSize / 2, player.y);
}
}
return Vector2(0, 0);
}
void calculateObjectives() {
objectives = [];
//get all available colors
List<Color> availableColors = [];
for (var c in cards) {
availableColors.add(c.color);
}
//calculate random mix
availableColors.shuffle();
for (var i = 0; i < availableColors.length; i++) {
for (var j = 0; j < availableColors.length; j++) {
var mix = ColorGenerator.mix(availableColors[i], availableColors[j]);
if (!objectives.contains(mix)) {
objectives.add(mix);
}
}
}
}
void killPlayer() {
gameOver = true;
player.isDead = true;
player.deadAnimation();
//disable card on die
for(var card in cards){
card.color = card.color.withOpacity(0.5);
card.selectable = false;
}
}
void passPlayer() {
obstacles.stop(true);
player.passAnimation();
}
}

background.dart

import 'dart:math';
import 'dart:ui';
import 'package:color_mixer/screens/game.dart';
import 'package:color_mixer/utils/color_helper.dart';
import 'package:flame/components.dart';
import 'package:flutter/material.dart' show Colors;
class Background extends PositionComponent {
final Color color = ColorGenerator.backgroundColor;
@override
void render(Canvas canvas) {
Rect bgRect = Rect.largest;
Paint bgPaint = Paint();
bgPaint.color = color;
canvas.drawRect(bgRect, bgPaint);
super.render(canvas);
}
}
class PlayerBackground extends PositionComponent with HasGameRef<GameManager> {
final Color color = ColorGenerator.backgroundColor;
@override
Future<void> onLoad() {
var deadPoint = gameRef.halfHeight - 30;
for(var i = 0; i < 10; i ++){
var posX = Random().nextDouble() * gameRef.screenSize.width;
var posY = Random().nextDouble() * (gameRef.halfHeight - 60);
var circle = CircleBackground(Vector2(posX, posY), deadPoint);
addChild(circle);
}
return super.onLoad();
}
@override
void render(Canvas canvas) {
//background
Rect bgRect = Rect.fromLTWH(0, 0, gameRef.screenSize.width,
(gameRef.screenSize.height / 2).floorToDouble());
canvas.drawRect(bgRect, Paint()..color = color);
//ground
Rect ground = Rect.fromLTWH(
0,
(gameRef.screenSize.height / 2).floorToDouble(),
gameRef.screenSize.width,
1);
canvas.drawRect(ground, Paint()..color = Colors.white.withOpacity(0.15));
super.render(canvas);
}
}
class CircleBackground extends PositionComponent {
final Color color = Colors.white;
double deadPoint = 0;
List<double> sizeList = [20, 25, 30, 35, 40];
CircleBackground(Vector2 position, double deadPoint) {
this.size =
Vector2(sizeList[Random().nextInt(4)], sizeList[Random().nextInt(4)]);
this.deadPoint = deadPoint;
this.position = position;
}
@override
void render(Canvas canvas) {
canvas.drawCircle(
position.toOffset(), 12, Paint()..color = color.withOpacity(0.15));
super.render(canvas);
}
@override
void update(double dt) {
if (y < deadPoint) {
//create random size after reaching dead point
size =
Vector2(sizeList[Random().nextInt(4)], sizeList[Random().nextInt(4)]);
y += 10 * dt;
} else {
y = -50;
}
super.update(dt);
}
}

player.dart

import 'dart:math';
import 'dart:ui';
import 'package:color_mixer/screens/game.dart';
import 'package:color_mixer/utils/sound_helper.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/extensions.dart';
import 'package:flame/particles.dart';
import 'package:flutter/material.dart';
class Player extends PositionComponent with HasGameRef<GameManager> {
Color color;
bool isDead = false;
List<TrailComponent> trails = [];
List<double> trailScales = [2/3, 1/2 , 1/3];
double get widthInScreen => width + x;
Player(Vector2 position, Vector2 size, Color color) {
this.position = position;
this.color = color;
this.size = size;
this.isDead = false;
}
@override
Future<void> onLoad() {
initTrail();
return super.onLoad();
}
void initTrail() {
var cPosY = height + 2;
for( var i = 0; i < 3; i++){
var scale = trailScales[i];
var c = TrailComponent(Size(width * scale, height * trailScales[i]), color.withOpacity(scale) ,
(width - width * scale) / 2, cPosY);
trails.add(c);
addChild(ParticleComponent(
particle: ComponentParticle(lifespan: 10000, component: c)));
cPosY += height * scale + 2;
}
}
@override
void render(Canvas canvas) {
var shadow = Rect.fromLTWH(x - 1, y - 1, width + 2, height + 2);
canvas.drawRect(shadow, Paint()..color = Colors.blue[50]);
var rect = Rect.fromLTWH(x, y, width, height);
canvas.drawRect(rect, paint());
var innerSize = (size.x / 2).floorToDouble();
var shadow2 = Rect.fromLTWH(x + innerSize / 2 - 1, y + innerSize / 2 - 1,
innerSize + 2, innerSize + 2);
canvas.drawRect(shadow2, Paint()..color = Colors.blue[50]);
var rectInner = Rect.fromLTWH(
x + innerSize / 2, y + innerSize / 2, innerSize, innerSize);
canvas.drawRect(rectInner, paint());
super.render(canvas);
}
Paint paint() {
return Paint()..color = this.color;
}
@override
void update(double dt) {
super.update(dt);
}
void updatePlayerColor(Color mixedColor){
color = mixedColor;
for(var i = 0; i < trails.length; i++){
trails[i].color = mixedColor.withOpacity(trailScales[i]);
}
}
void moveTo(Vector2 newPos) {
addEffect(MoveEffect(
path: [
newPos,
],
speed: gameRef.playerSpeed * 10,
));
}
void deadAnimation() {
SoundHelper.dead();
if(isDead){
isDead = false;
hitAnimation();
addEffect(MoveEffect(
path: [
Vector2(x, y + 50),
],
speed: gameRef.playerSpeed * 20,
));
}
}
void passAnimation() {
SoundHelper.shoot();
addEffect(MoveEffect(
path: [
Vector2(x, y - 30),
],
curve: Curves.ease,
isAlternating: true,
speed: gameRef.playerSpeed * 20,
));
}
void hitAnimation() {
//remove trail
removeTrail();
//play hit animation
Random rnd = Random();
Function randomOffset = () => Offset(
rnd.nextDouble() * 300 - 100,
rnd.nextDouble() * 200 - 100,
);
addChild(ParticleComponent(
particle: Particle.generate(
count: 10,
generator: (i) => AcceleratedParticle(
position: Offset(width / 2, 0),
acceleration: randomOffset(),
child: CircleParticle(paint: Paint()..color = color)))));
}
void removeTrail(){
if(trails.isNotEmpty){
for(var trail in trails){
trail.size = Size.zero;
}
}
}
}
class TrailComponent extends Component {
Size size;
double left;
double top;
Color color;
TrailComponent(Size size, Color color, double left, double top) {
this.size = size;
this.left = left;
this.top = top;
this.color = color;
}
@override
Future<void> onLoad() {
return super.onLoad();
}
void render(Canvas c) {
c.drawRect(Rect.fromLTWH(left, top, size.width, size.height),
Paint()..color = color);
}
void update(double dt) {
}
}

Leave a Comment

Your email address will not be published. Required fields are marked *

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close