Rafael Almeida Barbosa

Mobile developer (Flutter, Android, IOS)

FlutterBR

Criando games RPG com

Flutter

O que é RPG?

Role-playing game, também conhecido como RPG, é um tipo de jogo em que os jogadores assumem papéis de personagens e criam narrativas colaborativamente.

Ferramentas de desenvolvimento de games

Como o Bonfire surgiu?

Meus jogos favoritos

  • Ragnarok
  • Word of Warcraft
  • MU Online

Ferramentas de criação de games

Flame engine

Flame engine

Será que consigo criar um RPG game com ele?

Criando jogo do estilo RPG

  • Map system
  • Decoration
  • Camera system
  • Collision
  • Player movement
  • Enemy movement
  • Attack system
  • Joystick
  • Etc

Meu primeiro jogo

(Darkness Dungeon)

Bonfire

(RPG maker) Crie jogos do estilo RPG ou similares com o Flutter.

Ele é ideal para criar games com as seguintes perspectivas:

Bonfire



return BonfireWidget(
    gameController: GameController(),
    joystick: MyJoystick()
    map: TiledWorldMap('tile/map.json', forceTileSize: tileSize), 
    player: Knight(), 
    interface: KnightInterface(),
    background: GameComponent(), 
    constructionMode: false, 
    showCollisionArea: false, 
    constructionModeColor: Colors.blue, 
    collisionAreaColor: Colors.blue, 
    lightingColorGame: Colors.black.withOpacity(0.4), 
    components: <GameComponent>[],
    cameraConfig: CameraConfig(
      sizeMovementWindow: Size(50,50),
      moveOnlyMapArea: false,
      zoom: 1.0, 
      target: GameComponent(),
    ),
    showFPS: false,
    progress: Widget(),
);

Como ele funciona?

Como ele funciona?

Mixin <3

Map


TiledWorldMap(
  'tiled/map.json',
  forceTileSize: Size(32,32),
  objectsBuilder: {
    'orc': (ObjectProperties properties){
	  return Orc(properties.position);
    },
  },
)

Mapa - Tiled

Mapa - Randômico



[
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 1, 2, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 2, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]

Decoration


GameDecoration.withSprite({
    required FutureOr<Sprite> sprite,
    required Vector2 position,
    required Vector2 size,
})
  
GameDecoration.withAnimation({
    required FutureOr<SpriteAnimation> animation,
    required Vector2 position,
    required Vector2 size,
  }
)

Decoration


class MyDecoration extends GameDecoration {
  MyDecoration(Position position)
      : super.withAnimation(
          animation: SpriteAnimation.load(
            "itens/torch_spritesheet.png",
            SpriteAnimationData.sequenced(
              amount: 6,
              stepTime: 0.1,
              textureSize: Vector2.all(16),
            ),
          ),
          size: Vector2.all(32),
          position: position,
        );

    @override
    void update(double dt) {
        // do anything
        super.update(dt);
    }

    @override
    void render(Canvas canvas) {
        // do anything
        super.render(canvas);
    }
}

Enemy

SimpleEnemy

RotationEnemy

SimpleEnemy


class Goblin extends SimpleEnemy {

    Goblin(Vector2 initPosition)
      : super(
          position:initPosition, //required
          size:Vector2.all(32), //required
          animation: SimpleDirectionAnimation(), //required
          life: 100,
          speed: 100,
      );
      
}

RotationEnemy


class Tank extends RotationEnemy {

    Tank(Position initPosition)
      : super(
          initPosition:initPosition, //required
          size: Vector2.all(32), 
          life: 100,
          speed: 100,
          animIdle: Future<SpriteAnimation>(), //required (TOP)
          animRun: Future<SpriteAnimation>(), //required  (TOP)
      );
      
    @override
    void update(double dt) {
        super.update(dt);
    }

    @override
    void render(Canvas canvas) {
        super.render(canvas);
    }

    @override
    void die() {
        super.die();
    }
      
}

Enemy movement


void seePlayer({
    Function(Player) observed,
    Function() notObserved,
    double radiusVision = 32,
    int interval = 500,
  })

void seeAndMoveToPlayer({
    Function(Player) closePlayer,
    double radiusVision = 32,
    double margin = 10,
  })

void seeAndMoveToAttackRange({
    Function(Player) positioned,
    double radiusVision = 32,
    double minDistanceCellsFromPlayer,
  })

void simpleAttackMelee({
    @required double damage,
    double height = 32,
    double width = 32,
    int id,
    int interval = 1000,
    bool withPush = false,
    double sizePush,
    Direction direction,
    Future<SpriteAnimation> attackEffectRightAnim,
    Future<SpriteAnimation> attackEffectBottomAnim,
    Future<SpriteAnimation> attackEffectLeftAnim,
    Future<SpriteAnimation> attackEffectTopAnim,
    VoidCallback execute,
  })
  
 void simpleAttackRange({...})

Movement example


  @override
  void update(double dt) {
    
    this.seeAndMoveToPlayer(
      closePlayer: (player) {
      	execAttack();
      },
      radiusVision: tileSize * 2,
    );

    super.update(dt);
  }

Movement example


  @override
  void update(double dt) {
    
    this.seeAndMoveToAttackRange(
      minDistanceFromPlayer: tileSize * 2,
      positioned: (p) {
      	execAttackRange();
      },
      radiusVision: tileSize * 5,
    );

    super.update(dt);
  }

Custom enemy


class MyCustomEnemy extends Enemy{}


void moveUp(double speed, {VoidCallback? onCollision});
void moveDown(double speed, {VoidCallback? onCollision});
void moveLeft(double speed, {VoidCallback? onCollision});
void moveRight(double speed, {VoidCallback? onCollision});
void moveUpRight(double speedX, double speedY, {VoidCallback? onCollision});
void moveUpLeft(double speedX, double speedY, {VoidCallback? onCollision});
void moveDownLeft(double speedX, double speedY, {VoidCallback? onCollision});
void moveDownRight(double speedX, double speedY, {VoidCallback? onCollision});
void moveFromAngleDodgeObstacles(double speed, double angle,{Function notMove});
void moveFromAngle(double speed, double angle);
void receiveDamage(double damage, int from);
void addLife(double life);
void die();

Player

SimplePlayer

RotationPlayer

SimplePlayer



class Kinght extends SimplePlayer {

    Kinght(Position initPosition)
      : super(
          position:initPosition, //required
          size: Vector2.all(32), //required
          life: 100,
          speed: 100,
          animation: SimpleDirectionAnimation(), //required
      );
}

RotationPlayer



class PlayerTank extends RotationPlayer {

    PlayerTank(Position initPosition)
      : super(
          position:initPosition, //required
          size: Vector2.all(32), 
          life: 100,
          speed: 100,
          animIdle: Future<SpriteAnimation>(), //required
          animRun: Future<SpriteAnimation> (), //required
      );
      
}

Player extensions



void simpleAttackMelee(...);

void simpleAttackRange(...);

void showDamage(...);

void seeEnemy(...);

Player joystick interaction



@override
void joystickChangeDirectional(JoystickDirectionalEvent event) {
  // do anything with event of the joystick
  super.joystickChangeDirectional(event);
}

@override
void joystickAction(JoystickActionEvent event) {
  // do anything with event of the joystick
  super.joystickAction(event);
}

Custom player



class MyCustomPlayer extends Player{}

void moveUp(double speed, {VoidCallback? onCollision});
void moveDown(double speed, {VoidCallback? onCollision});
void moveLeft(double speed, {VoidCallback? onCollision});
void moveRight(double speed, {VoidCallback? onCollision});
void moveUpRight(double speedX, double speedY, {VoidCallback? onCollision});
void moveUpLeft(double speedX, double speedY, {VoidCallback? onCollision});
void moveDownLeft(double speedX, double speedY, {VoidCallback? onCollision});
void moveDownRight(double speedX, double speedY, {VoidCallback? onCollision});
void moveFromAngleDodgeObstacles(double speed, double angle,{Function notMove});
void moveFromAngle(double speed, double angle);
void receiveDamage(double damage, int from);
void addLife(double life);
void die();

ObjectCollision


class MyDecoration extends GameDecoration with ObjectCollision {

  MyCustomDecoration(Position position)
      : super.withAnimation(
          animation: Future<SpriteAnimation>(),
          size: Vector2.all(32),
          height: 32,
          position: position,
        ){
            setupCollision(
                CollisionConfig(
                    enable: true,
                    collisions: [ //required
                        CollisionArea.rectangle(
                            size: Vector2(32,32),
                            align: Vector2(0,0),
                        ),
                    ],
                ),
            );
        }

}

ObjectCollision


return BonfireWidget(
  showCollisionArea: true,
);


return BonfireWidget(
  lightingColorGame: Colors.black.withOpacity(0.4),
);

class Torch extends Gamedecoration with Lighting{

  Torch(Position position)
      : super.withAnimation(
          animation: Future<SpriteAnimation>(),
          width: 32,
          height: 32,
          position: position,
        ){

    setupLighting(
    	LightingConfig(
        color: Colors.yellow.withOpacity(0.1),
        radius: 40,
        blurBorder: 20,
        withPulse: true,
        pulseVariation: 0.1,
        pulseCurve: Curves.decelerate,
        pulseSpeed: 1,
      ),
    );

  }
}

class MyInterface extends GameInterface {

  @override
  void onGameResize(Vector2 size) {
    add(BarLifeComponent());
    add(
      InterfaceComponent(
        sprite: Sprite('blue_button1.png'),
        spriteSelected: Sprite('blue_button2.png'),
        height: 40,
        width: 40,
        id: 5,
        position: Position(150, 20),
        onTapComponent: () {
          print('Test button');
        },
      ),
    );
    super.resize(size);
  }
}

Custom InterfaceComponent


class BarLifeComponent extends InterfaceComponent {

  BarLifeComponent(int id, Position position)
  : super(
    id: id,
    position: position,
    width: 120,
    height: 40,
  );
        
  @override
  void update(double t) {
    super.update(t);
  }

  @override
  void render(Canvas c) {
    super.render(c);
  }
  
  @override
  void onTap() {
    super.onTap();
  }
  
}

Joystick


Joystick(
  keyboardEnable: false,
  directional: JoystickDirectional(
    spriteBackgroundDirectional: Sprite.load('joystick_background.png'),
    spriteKnobDirectional: Sprite.load('joystick_knob.png'), 
    color: Colors.black,
    size: 100, 
    isFixed: false,
  ),
  actions: [
    JoystickAction(
      actionId: 1, //(required)
      sprite: Sprite.load('joystick_atack_range.png'), 
      spritePressed: Sprite.load('joystick_atack_range.png'), 
      spriteBackgroundDirection: Sprite('joystick_background.png'), 
      enableDirection: true, 
      align: JoystickActionAlign.BOTTOM_RIGHT,
      color: Colors.blue,
      size: 50,
      margin: EdgeInsets.only(bottom: 50, right: 160),
    )
  ],
)

Joystick (Directional)


Joystick(
  directional: JoystickDirectional(),
)

Joystick (Actions)


Joystick(
  actions: [
    JoystickAction(
      actionId: 1,
      size: 80,
      margin: EdgeInsets.only(
      	bottom: 50, 
      	right: 50,
      ),
    )
    JoystickAction(
      actionId: 2,
      enableDirection: true,
      size: 50,
      margin: EdgeInsets.only(
        bottom: 50, 
        right: 160,
      ),
    )
  ],
)

Hands On

Games Examples

Games Examples

Multi Scenes

SunnyPlace

Próximos passos

Bonfire 3.0

- Remover implementações próprias

- Suporte raycast

Isometric Maps

Onde aprender?

Onde aprender?

PRs are Welcome

Rafael Almeida Barbosa

Thanks!!!

Criando games RPG com Bonfire

By Rafael Almeida Barbosa

Criando games RPG com Bonfire

  • 1,236