flutterdartflame

Flutter Flame Controller Joystick for mobile is not show at the front of layer


I try to make a mobile game by using Flutter and Flame, but the controller is not showing at the front of the layer even if I set the priority to 10 and the level of map priority to under 10.

Here's a screenshot :

Here this joystick code:

import 'dart:async';
import 'dart:io';

import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/painting.dart';
import 'package:pixel_adventure/components/player.dart';
import 'package:pixel_adventure/components/level.dart';

class PixelAdventure extends FlameGame
    with HasKeyboardHandlerComponents, DragCallbacks {
  final Player _player = Player();

  late final CameraComponent _cam;
  late final JoystickComponent _joystick;

  @override
  Color backgroundColor() => const Color(0xFF211F30);

  @override
  FutureOr<void> onLoad() async {
    await images.loadAllImages();

    final world = Level(levelName: 'level-01', player: _player);
    _cam = CameraComponent.withFixedResolution(
      world: world,
      width: 640,
      height: 360,
    );

    _cam.viewfinder.anchor = Anchor.topLeft;

    addAll([_cam, world]);

    if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) {
      _addJoystick();
    }

    return super.onLoad();
  }

  @override
  void update(double dt) {
    if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) {
      _updateJoystick();
    }
    super.update(dt);
  }

  void _addJoystick() {
    _joystick = JoystickComponent(
      priority: 10,
      knob: SpriteComponent(
        sprite: Sprite(
          images.fromCache('HUD/knob.png'),
        ),
      ),
      background: SpriteComponent(
        sprite: Sprite(
          images.fromCache('HUD/joystick.png'),
        ),
      ),
      margin: const EdgeInsets.only(left: 32, bottom: 32),
    );

    add(_joystick);
  }

  void _updateJoystick() {
    switch (_joystick.direction) {
      case JoystickDirection.left:
      case JoystickDirection.downLeft:
      case JoystickDirection.upLeft:
        _player.horizontalMovement = -1;
        break;
      case JoystickDirection.right:
      case JoystickDirection.downRight:
      case JoystickDirection.upRight:
        _player.horizontalMovement = 1;
        break;
      default:
        _player.horizontalMovement = 0;
        break;
    }
  }
}

and this level of map

import 'dart:async';

import 'package:flame/components.dart';
import 'package:flame_tiled/flame_tiled.dart';
import 'package:pixel_adventure/components/background_tile.dart';
import 'package:pixel_adventure/components/collision_block.dart';
import 'package:pixel_adventure/components/player.dart';
import 'package:pixel_adventure/pixel_adventure.dart';

class Level extends World with HasGameRef<PixelAdventure> {
  final String levelName;
  final Player player;

  Level({required this.player, required this.levelName});

  late TiledComponent level;
  List<CollisionBlock> collisionBlocks = [];

  @override
  FutureOr<void> onLoad() async {
    priority = -1;
    level = await TiledComponent.load(
      '$levelName.tmx',
      Vector2.all(16),
    );

    add(level);

    _scrollingObject();
    _spawningObject();
    _addCollisions();

    player.collisionBlocks = collisionBlocks;
    return super.onLoad();
  }

  void _scrollingObject() {
    final Layer? backgroundLayer = level.tileMap.getLayer('background');
    const int tileSize = 65;
    final int numTilesY = (game.size.y / tileSize).floor();
    final int numTilesX = (game.size.x / tileSize).floor();
    if (backgroundLayer != null) {
      final backgroundColor =
          backgroundLayer.properties.getValue('BackgroundColor');

      for (double y = 0; y < numTilesY; y++) {
        for (double x = 0; x < numTilesX; x++) {
          final backgroundTile = BackgroundTile(
              color: backgroundColor ?? 'Gray',
              position: Vector2(x * tileSize, y * tileSize - tileSize));
          add(backgroundTile);
        }
      }
    }
  }

  void _spawningObject() {
    final ObjectGroup? spawnPointsLayer =
        level.tileMap.getLayer<ObjectGroup>('Spawnpoints');
    if (spawnPointsLayer != null) {
      for (final TiledObject spawnPoint in spawnPointsLayer.objects) {
        switch (spawnPoint.class_) {
          case 'Player':
            player.position = Vector2(
              spawnPoint.x,
              spawnPoint.y,
            );
            add(player);
            break;
          default:
        }
      }
    }
  }

  void _addCollisions() {
    final ObjectGroup? collisionsLayer =
        level.tileMap.getLayer<ObjectGroup>('Collisions');
    if (collisionsLayer != null) {
      for (final TiledObject collisions in collisionsLayer.objects) {
        switch (collisions.class_) {
          case 'Platform':
            final platform = CollisionBlock(
              position: Vector2(collisions.x, collisions.y),
              size: Vector2(collisions.width, collisions.height),
              isPlatform: true,
            );
            collisionBlocks.add(platform);
            add(platform);
            break;
          default:
            final block = CollisionBlock(
              position: Vector2(collisions.x, collisions.y),
              size: Vector2(collisions.width, collisions.height),
            );
            collisionBlocks.add(block);
            add(block);
        }
      }
    }
  }
}

and this gray background

import 'dart:async';

import 'package:flame/components.dart';
import 'package:pixel_adventure/pixel_adventure.dart';

class BackgroundTile extends SpriteComponent with HasGameRef<PixelAdventure> {
  final String color;
  BackgroundTile({this.color = 'Gray', position}) : super(position: position);

  @override
  FutureOr<void> onLoad() {
    priority = -2;
    size = Vector2.all(65.6);
    sprite = Sprite(
      game.images.fromCache('Background/$color.png'),
    );
    super.onLoad();
  }
}

I tried to make a priority in the controller layer, but it's doesn't work.


Solution

  • Your joystick is added to the game, which is underneath the world. Setting the priority only makes a difference if you set it between sibling components. I would add the joystick to the viewport instead, to make sure that it is treated as a HUD and always visible.

    camera.viewport.add(joystick);
    

    You also have some other issues in your code, you have two cameras (there is already one built-in to FlameGame). Do this instead to use the built-in camera:

      @override
      FutureOr<void> onLoad() async {
        await images.loadAllImages();
    
        camera = CameraComponent.withFixedResolution(
          world: world,
          width: 640,
          height: 360,
        );
        world = Level(levelName: 'level-01', player: _player);
    
        camera.viewfinder.anchor = Anchor.topLeft;
    
        if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) {
          _addJoystick(); // Make sure this adds to `camera.viewport` instead.
        }
    
        return super.onLoad();
      }
    

    and remove all references to _cam and use camera intead.