mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-08 23:56:07 +08:00
141 lines
5.4 KiB
Dart
141 lines
5.4 KiB
Dart
import 'dart:ui';
|
|
|
|
import 'package:flutter/painting.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:ordered_set/comparing.dart';
|
|
import 'package:ordered_set/ordered_set.dart';
|
|
|
|
import '../effects/effects.dart';
|
|
import '../effects/effects_handler.dart';
|
|
import '../extensions/vector2.dart';
|
|
import '../game.dart';
|
|
|
|
/// This represents a Component for your game.
|
|
///
|
|
/// Components can be bullets flying on the screen, a spaceship or your player's fighter.
|
|
/// Anything that either renders or updates can be added to the list on [BaseGame]. It will deal with calling those methods for you.
|
|
/// Components also have other methods that can help you out if you want to overwrite them.
|
|
abstract class Component {
|
|
final EffectsHandler _effectsHandler = EffectsHandler();
|
|
|
|
final OrderedSet<Component> children =
|
|
OrderedSet(Comparing.on((c) => c.priority));
|
|
|
|
/// Whether this component has been loaded yet. If not loaded, [BaseGame] will not try to render it.
|
|
///
|
|
/// Sprite based components can use this to let [BaseGame] know not to try to render when the [Sprite] has not been loaded yet.
|
|
/// Note that for a more consistent experience, you can pre-load all your assets beforehand with Flame.images.loadAll.
|
|
bool loaded = true;
|
|
|
|
/// Whether this component is HUD object or not.
|
|
///
|
|
/// HUD objects ignore the [BaseGame.camera] when rendered (so their position coordinates are considered relative to the device screen).
|
|
bool isHud = false;
|
|
|
|
/// Render priority of this component. This allows you to control the order in which your components are rendered.
|
|
///
|
|
/// Components are always updated and rendered in the order defined by what this number is when the component is added to the game.
|
|
/// The smaller the priority, the sooner your component will be updated/rendered.
|
|
/// It can be any integer (negative, zero, or positive).
|
|
/// If two components share the same priority, they will probably be drawn in the order they were added.
|
|
final int priority;
|
|
|
|
/// Whether this component should be removed or not.
|
|
///
|
|
/// It will be checked once per component per tick, and if it is true, [BaseGame] will remove it.
|
|
bool shouldRemove = false;
|
|
|
|
Component({this.priority = 0});
|
|
|
|
/// This method is called periodically by the game engine to request that your component updates itself.
|
|
///
|
|
/// The time [t] in seconds (with microseconds precision provided by Flutter) since the last update cycle.
|
|
/// This time can vary according to hardware capacity, so make sure to update your state considering this.
|
|
/// All components on [BaseGame] are always updated by the same amount. The time each one takes to update adds up to the next update cycle.
|
|
@mustCallSuper
|
|
void update(double dt) {
|
|
_effectsHandler.update(dt);
|
|
}
|
|
|
|
/// Renders this component on the provided Canvas [c].
|
|
void render(Canvas c);
|
|
|
|
/// It receives the new game size.
|
|
/// Executed right after the component is attached to a game and right before [onMount] is called
|
|
///
|
|
/// Use [Resizable] to save the gameSize in a component.
|
|
void onGameResize(Vector2 gameSize) {}
|
|
|
|
/// Remove the component from the game it is added to in the next tick
|
|
void remove() => shouldRemove = true;
|
|
|
|
/// Called when the component has been added and prepared by the game instance.
|
|
///
|
|
/// This can be used to make initializations on your component as, when this method is called,
|
|
/// things like [onGameResize] are already set and usable.
|
|
void onMount() {}
|
|
|
|
/// Called right before the component is removed from the game
|
|
void onRemove() {}
|
|
|
|
/// Called to check whether the point should be counted as a tap on the component
|
|
bool checkOverlap(Vector2 point) => false;
|
|
|
|
/// Add an effect to the component
|
|
void addEffect(ComponentEffect effect) {
|
|
_effectsHandler.add(effect, this);
|
|
}
|
|
|
|
/// Mark an effect for removal on the component
|
|
void removeEffect(ComponentEffect effect) {
|
|
_effectsHandler.removeEffect(effect);
|
|
}
|
|
|
|
/// Remove all effects
|
|
void clearEffects() {
|
|
_effectsHandler.clearEffects();
|
|
}
|
|
|
|
/// Get a list of non removed effects
|
|
List<ComponentEffect> get effects => _effectsHandler.effects;
|
|
|
|
/// Uses the game passed in to prepare the child component before it is added
|
|
/// to the list of children
|
|
void addChild(Game gameRef, Component c) {
|
|
if (gameRef is BaseGame) {
|
|
gameRef.prepare(c);
|
|
}
|
|
children.add(c);
|
|
}
|
|
|
|
/// This method first calls the passed function on itself and then recursively propagates it to every children
|
|
/// and grandchildren (and so on) of this component, either until it has propagated through the whole tree or
|
|
/// if the handler at any point returns false.
|
|
///
|
|
/// This method is important to be used by the engine to propagate actions like rendering, taps, etc,
|
|
/// but you can call it yourself if you need to apply an action to the whole component chain.
|
|
/// It will only consider components of type T in the hierarchy, so use T = Component to target everything.
|
|
bool propagateToChildren<T extends Component>(
|
|
bool Function(T) handler,
|
|
) {
|
|
bool shouldContinue = true;
|
|
if (this is T) {
|
|
shouldContinue = handler(this as T);
|
|
if (!shouldContinue) {
|
|
return false;
|
|
}
|
|
}
|
|
for (Component child in children) {
|
|
if (child is T) {
|
|
shouldContinue = handler(child);
|
|
if (shouldContinue) {
|
|
shouldContinue = child.propagateToChildren(handler);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return shouldContinue;
|
|
}
|
|
}
|