Skip to content

Commit

Permalink
feat: new sprite for monster that gives player a bullet
Browse files Browse the repository at this point in the history
  • Loading branch information
adil192 committed May 16, 2024
1 parent a07805a commit 8ebc6d0
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 43 deletions.
2 changes: 1 addition & 1 deletion assets/images/LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ https://creativecommons.org/publicdomain/zero/1.0/

Some of these sprites were modified by me, such as `character_subset.png`
being a modified subset of the original `character.png`,
and `log_subset.png` being modified from the original `log.png` to be more
and `log_normal.png` being modified from the original `log.png` to be more
rectangular.
These works are licensed under CC0 as well.

Expand Down
Binary file added assets/images/log_gold.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 assets/images/log_green.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 assets/images/log_normal.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 removed assets/images/log_subset.png
Binary file not shown.
Binary file added assets_raw/log_variants.xcf
Binary file not shown.
120 changes: 83 additions & 37 deletions lib/flame/components/monster.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'dart:ui' show lerpDouble;

import 'package:flame/components.dart';
import 'package:flame/image_composition.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' hide Image;
import 'package:ricochlime/flame/components/bullet.dart';
import 'package:ricochlime/flame/components/health_bar.dart';
import 'package:ricochlime/flame/ricochlime_game.dart';
Expand All @@ -20,47 +21,73 @@ enum MonsterState {
dead,
}

enum KillReward {
/// No reward for killing this monster
none,

/// Gives the player an extra bullet
bullet,

/// Gives the player a coin
coin,
}

/// A monster component.
class Monster extends BodyComponent with ContactCallbacks {
// ignore: public_member_api_docs
Monster({
required this.initialPosition,
required this.maxHp,
int? hp,
bool? givesPlayerABullet,
KillReward? killReward,
}) : hp = hp ?? maxHp,
super(
paint: Paint()..color = RicochlimePalette.monsterColor,
priority: getPriorityFromPosition(initialPosition),
renderBody: false,
) {
if (givesPlayerABullet != null) {
this.givesPlayerABullet = givesPlayerABullet;
}
if (killReward != null) this.killReward = killReward;

add(_animation);
add(_healthBar);
}

/// Creates a Monster from JSON data.
Monster.fromJson(Map<String, dynamic> json)
: this(
initialPosition: Vector2(
json['px'] as double,
json['py'] as double,
),
hp: json['hp'] as int,
maxHp: json['maxHp'] as int,
givesPlayerABullet: json['givesPlayerABullet'] as bool? ?? false,
);
factory Monster.fromJson(Map<String, dynamic> json) {
final initialPosition = Vector2(
json['px'] as double,
json['py'] as double,
);
final hp = json['hp'] as int;
final maxHp = json['maxHp'] as int;

final KillReward killReward;
if (json['killReward'] != null) {
print('killReward: ${json['killReward']}');
killReward = KillReward.values[json['killReward'] as int];
} else if (json['givesPlayerABullet'] as bool? ?? false) {
print('givesPlayerABullet: ${json['givesPlayerABullet']}');
killReward = KillReward.bullet;
} else {
print('killReward: none');
killReward = KillReward.none;
}

return Monster(
initialPosition: initialPosition,
maxHp: maxHp,
hp: hp,
killReward: killReward,
);
}

/// Converts the monster's data to a JSON map.
Map<String, dynamic> toJson() => {
'px': _movement?.targetPosition.x ?? position.x,
'py': _movement?.targetPosition.y ?? position.y,
'hp': hp,
'maxHp': maxHp,
'givesPlayerABullet': givesPlayerABullet,
'killReward': killReward.index,
};

/// How many monsters there are in each row.
Expand Down Expand Up @@ -117,13 +144,12 @@ class Monster extends BodyComponent with ContactCallbacks {
paint: _animation.paint,
);

bool _givesPlayerABullet = false;

/// Whether the monster gives the player a bullet when it dies.
bool get givesPlayerABullet => _givesPlayerABullet;
set givesPlayerABullet(bool value) {
_givesPlayerABullet = value;
_animation.givesPlayerABullet = value;
/// The reward for killing this monster
KillReward get killReward => _killReward;
KillReward _killReward = KillReward.none;
set killReward(KillReward killReward) {
_killReward = killReward;
_animation.setSpriteFromKillReward(killReward);
}

/// Whether the body has been created yet.
Expand Down Expand Up @@ -243,7 +269,15 @@ class Monster extends BodyComponent with ContactCallbacks {
_animation.current = MonsterState.dead;
remove(_healthBar);

if (givesPlayerABullet) (game as RicochlimeGame).numBullets += 1;
switch (killReward) {
case KillReward.none:
break;
case KillReward.bullet:
(game as RicochlimeGame).numBullets += 1;
case KillReward.coin:
// TODO(adil192): Implement coin reward
throw UnimplementedError();
}
}
}
}
Expand Down Expand Up @@ -304,17 +338,6 @@ class MonsterAnimation extends SpriteAnimationGroupComponent<MonsterState>
}
}

/// Whether the monster gives the player a bullet when it dies.
bool get givesPlayerABullet => getPaint().colorFilter != null;
set givesPlayerABullet(bool value) {
getPaint().colorFilter = value
? ColorFilter.mode(
const HSLColor.fromAHSL(1, 0, 0.5, 0.7).toColor(),
BlendMode.modulate,
)
: null;
}

@override
Future<void> onLoad() async {
animations = getAnimations();
Expand All @@ -330,12 +353,35 @@ class MonsterAnimation extends SpriteAnimationGroupComponent<MonsterState>
static Future<void> preloadSprites({
required RicochlimeGame gameRef,
}) {
return gameRef.images.load('log_subset.png');
return Future.wait([
gameRef.images.load('log_normal.png'),
gameRef.images.load('log_green.png'),
gameRef.images.load('log_gold.png'),
]);
}

String monsterImagePath = 'log_normal.png';
void setSpriteFromKillReward(KillReward killReward) {
monsterImagePath = switch (killReward) {
KillReward.none => 'log_normal.png',
KillReward.bullet => 'log_green.png',
KillReward.coin => 'log_gold.png',
};

final animations = this.animations;
if (animations == null) return;

final monsterImage = gameRef.images.fromCache(monsterImagePath);
for (final animation in animations.values) {
for (final frame in animation.frames) {
frame.sprite.image = monsterImage;
}
}
}

/// The list of animations for the monster.
Map<MonsterState, SpriteAnimation> getAnimations() {
final monsterImage = gameRef.images.fromCache('log_subset.png');
final monsterImage = gameRef.images.fromCache(monsterImagePath);
return {
MonsterState.idle: SpriteAnimation.fromFrameData(
monsterImage,
Expand Down
10 changes: 5 additions & 5 deletions lib/flame/ricochlime_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ class RicochlimeGame extends Forge2DGame
monsters.add(monster);
add(monster);

if (monster.givesPlayerABullet) numMonstersThatGiveBullets++;
if (monster.killReward == KillReward.bullet) numMonstersThatGiveBullets++;
if (monster.position.y <= 0) topGapNeedsAdjusting = true;
}

Expand Down Expand Up @@ -575,11 +575,11 @@ class RicochlimeGame extends Forge2DGame
];

// one of the monsters should give the user a bullet when it dies
int chosenIndex = random.nextInt(row.length);
while (row[chosenIndex] == null) {
chosenIndex = random.nextInt(row.length);
int bulletRewardIndex = random.nextInt(row.length);
while (row[bulletRewardIndex] == null) {
bulletRewardIndex = random.nextInt(row.length);
}
row[chosenIndex]!.givesPlayerABullet = true;
row[bulletRewardIndex]!.killReward = KillReward.bullet;

return row;
}
Expand Down

0 comments on commit 8ebc6d0

Please sign in to comment.