Line data Source code
1 : import 'dart:ui'; 2 : 3 : import 'package:meta/meta.dart'; 4 : 5 : import '../../game.dart'; 6 : import '../../input.dart'; 7 : import '../effects/effects.dart'; 8 : import '../effects/effects_handler.dart'; 9 : import '../extensions/vector2.dart'; 10 : import '../text.dart'; 11 : import 'component.dart'; 12 : import 'component_set.dart'; 13 : import 'mixins/has_game_ref.dart'; 14 : 15 : /// This can be extended to represent a basic Component for your game. 16 : /// 17 : /// The difference between this and [Component] is that the [BaseComponent] can 18 : /// have children, handle effects and can be used to see whether a position on 19 : /// the screen is on your component, which is useful for handling gestures. 20 : abstract class BaseComponent extends Component { 21 : final EffectsHandler _effectsHandler = EffectsHandler(); 22 : 23 48 : late final ComponentSet children = createComponentSet(); 24 : 25 : /// If the component has a parent it will be set here 26 : BaseComponent? _parent; 27 : 28 7 : @override 29 7 : BaseComponent? get parent => _parent; 30 : 31 : /// This is set by the BaseGame to tell this component to render additional 32 : /// debug information, like borders, coordinates, etc. 33 : /// This is very helpful while debugging. Set your BaseGame debugMode to true. 34 : /// You can also manually override this for certain components in order to 35 : /// identify issues. 36 : bool debugMode = false; 37 : 38 : Color debugColor = const Color(0xFFFF00FF); 39 : 40 0 : Paint get debugPaint => Paint() 41 0 : ..color = debugColor 42 0 : ..strokeWidth = 1 43 0 : ..style = PaintingStyle.stroke; 44 : 45 0 : TextPaint get debugTextPaint => TextPaint( 46 0 : config: TextPaintConfig( 47 0 : color: debugColor, 48 : fontSize: 12, 49 : ), 50 : ); 51 : 52 56 : BaseComponent({int? priority}) : super(priority: priority); 53 : 54 : /// This method is called periodically by the game engine to request that your 55 : /// component updates itself. 56 : /// 57 : /// The time [dt] in seconds (with microseconds precision provided by Flutter) 58 : /// since the last update cycle. 59 : /// This time can vary according to hardware capacity, so make sure to update 60 : /// your state considering this. 61 : /// All components on [BaseGame] are always updated by the same amount. The 62 : /// time each one takes to update adds up to the next update cycle. 63 20 : @mustCallSuper 64 : @override 65 : void update(double dt) { 66 40 : children.updateComponentList(); 67 40 : _effectsHandler.update(dt); 68 44 : children.forEach((c) => c.update(dt)); 69 : } 70 : 71 3 : @mustCallSuper 72 : @override 73 : void render(Canvas canvas) { 74 3 : preRender(canvas); 75 : } 76 : 77 3 : @mustCallSuper 78 : @override 79 : void renderTree(Canvas canvas) { 80 3 : render(canvas); 81 3 : postRender(canvas); 82 7 : children.forEach((c) { 83 1 : canvas.save(); 84 1 : c.renderTree(canvas); 85 1 : canvas.restore(); 86 : }); 87 : 88 : // Any debug rendering should be rendered on top of everything 89 3 : if (debugMode) { 90 0 : renderDebugMode(canvas); 91 : } 92 : } 93 : 94 : /// A render cycle callback that runs before the component and its children 95 : /// has been rendered. 96 0 : @protected 97 : void preRender(Canvas canvas) {} 98 : 99 : /// A render cycle callback that runs after the component has been 100 : /// rendered, but before any children has been rendered. 101 3 : void postRender(Canvas canvas) {} 102 : 103 0 : void renderDebugMode(Canvas canvas) {} 104 : 105 23 : @mustCallSuper 106 : @override 107 : void onGameResize(Vector2 gameSize) { 108 23 : super.onGameResize(gameSize); 109 46 : children.forEach((child) => child.onGameResize(gameSize)); 110 : } 111 : 112 20 : @mustCallSuper 113 : @override 114 : void onMount() { 115 20 : super.onMount(); 116 40 : children.forEach((child) => child.onMount()); 117 : } 118 : 119 5 : @mustCallSuper 120 : @override 121 : void onRemove() { 122 5 : super.onRemove(); 123 10 : children.forEach((child) => child.onRemove()); 124 : } 125 : 126 : /// Called to check whether the point is to be counted as within the component 127 : /// It needs to be overridden to have any effect, like it is in 128 : /// PositionComponent. 129 0 : bool containsPoint(Vector2 point) => false; 130 : 131 : /// Add an effect to the component 132 7 : void addEffect(ComponentEffect effect) { 133 14 : _effectsHandler.add(effect, this); 134 : } 135 : 136 : /// Mark an effect for removal on the component 137 0 : void removeEffect(ComponentEffect effect) { 138 0 : _effectsHandler.removeEffect(effect); 139 : } 140 : 141 : /// Remove all effects 142 0 : void clearEffects() { 143 0 : _effectsHandler.clearEffects(); 144 : } 145 : 146 : /// Get a list of non removed effects 147 18 : List<ComponentEffect> get effects => _effectsHandler.effects; 148 : 149 4 : void prepare(Component child, {Game? gameRef}) { 150 4 : if (this is HasGameRef) { 151 : final c = this as HasGameRef; 152 4 : gameRef ??= c.hasGameRef ? c.gameRef : null; 153 : } else if (gameRef == null) { 154 : assert( 155 3 : !isMounted, 156 : 'Parent was already added to Game and has no HasGameRef; in this case, gameRef is mandatory.', 157 : ); 158 : } 159 4 : if (gameRef is BaseGame) { 160 3 : gameRef.prepare(child); 161 : } 162 : 163 4 : if (child is BaseComponent) { 164 4 : child._parent = this; 165 8 : child.debugMode = debugMode; 166 : } 167 : } 168 : 169 : /// Uses the game passed in, or uses the game from [HasGameRef] otherwise, 170 : /// to prepare the child component before it is added to the list of children. 171 : /// Note that this component needs to be added to the game first if 172 : /// [this.gameRef] should be used to prepare the child. 173 : /// For children that don't need preparation from the game instance can 174 : /// disregard both the options given above. 175 4 : Future<void> addChild(Component child, {BaseGame? gameRef}) { 176 8 : return children.addChild(child, gameRef: gameRef); 177 : } 178 : 179 : /// Adds mutiple children. 180 : /// 181 : /// See [addChild] for details (or `children.addChildren()`). 182 0 : Future<void> addChildren(List<Component> cs, {BaseGame? gameRef}) { 183 0 : return children.addChildren(cs, gameRef: gameRef); 184 : } 185 : 186 : /// Removes a component from the component list, calling onRemove for it and 187 : /// its children. 188 0 : void removeChild(Component c) { 189 0 : children.remove(c); 190 : } 191 : 192 : /// Removes all the children in the list and calls onRemove for all of them 193 : /// and their children. 194 0 : void removeChildren(Iterable<Component> cs) { 195 0 : children.removeAll(cs); 196 : } 197 : 198 : /// Whether the children list contains the given component. 199 : /// 200 : /// This method uses reference equality. 201 3 : bool containsChild(Component c) => children.contains(c); 202 : 203 : /// Call this if any of this component's children priorities have changed 204 : /// at runtime. 205 : /// 206 : /// This will call `rebalanceAll` on the [children] ordered set. 207 3 : void reorderChildren() => children.rebalanceAll(); 208 : 209 : /// This method first calls the passed handler on the leaves in the tree, 210 : /// the children without any children of their own. 211 : /// Then it continues through all other children. The propagation continues 212 : /// until the handler returns false, which means "do not continue", or when 213 : /// the handler has been called with all children 214 : /// 215 : /// This method is important to be used by the engine to propagate actions 216 : /// like rendering, taps, etc, but you can call it yourself if you need to 217 : /// apply an action to the whole component chain. 218 : /// It will only consider components of type T in the hierarchy, 219 : /// so use T = Component to target everything. 220 4 : bool propagateToChildren<T extends Component>( 221 : bool Function(T) handler, 222 : ) { 223 : var shouldContinue = true; 224 5 : for (final child in children) { 225 1 : if (child is BaseComponent) { 226 1 : shouldContinue = child.propagateToChildren(handler); 227 : } 228 1 : if (shouldContinue && child is T) { 229 1 : shouldContinue = handler(child); 230 : } 231 : if (!shouldContinue) { 232 : break; 233 : } 234 : } 235 : return shouldContinue; 236 : } 237 : 238 4 : @protected 239 : Vector2 eventPosition(PositionInfo info) { 240 12 : return isHud ? info.eventPosition.widget : info.eventPosition.game; 241 : } 242 : 243 24 : ComponentSet createComponentSet() { 244 48 : final components = ComponentSet.createDefault(prepare); 245 24 : if (this is HasGameRef) { 246 5 : components.register<HasGameRef>(); 247 : } 248 : return components; 249 : } 250 : }