You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter构建玩家大厅时触发StackOverflowError问题求助

问题

我希望在游戏启动前构建玩家大厅,为此定义了8个包含slot_number和player属性的PlayerSlot对象,预期游戏创建者会显示在1号槽位,但运行时触发了StackOverflowError。相关代码如下:

class WaitingPlayersPage extends Stateful Widget {

  final Player creatorOfLobby;

  WaitingPlayersPage(this.creatorOfLobby);

  @override
  State<WaitingPlayersPage> createState() => WaitingPlayersPageState();
}

class WaitingPlayersPageState extends State<WaitingPlayersPage> {

  double rangeSlider = 1;
  
  PlayerSlot _playerSlot1 = PlayerSlot(1 ,false, null);
  PlayerSlot _playerSlot2 = PlayerSlot(2 ,false, null);
  PlayerSlot _playerSlot3 = PlayerSlot(3 ,false, null);
  PlayerSlot _playerSlot4 = PlayerSlot(4 ,false, null);
  PlayerSlot _playerSlot5 = PlayerSlot(5 ,false, null);
  PlayerSlot _playerSlot6 = PlayerSlot(6 ,false, null);
  PlayerSlot _playerSlot7 = PlayerSlot(7 ,false, null);
  PlayerSlot _playerSlot8 = PlayerSlot(8 ,false, null);

  @override
  void initState() {
    _playerSlot1.fillSlotWithPlayer(widget.creatorOfLobby);
  }
  
  @override
  Widget build(BuildContext context) {
    List<PlayerSlot> multiPlayerSlot = [
      _playerSlot1, _playerSlot2,
      _playerSlot3,_playerSlot4,
      _playerSlot5, _playerSlot6,
      _playerSlot7, _playerSlot8
    ];
    return Scaffold(
      body: Container( // WHOLE SCREEN
        alignment: Alignment.center,
        color: Color.fromARGB(255, 236, 131, 166),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            SizedBox(height: 30,),
            Container( // BOX FOR PLAYERS
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10.0),
                color: Colors.pink,
              ),
              
              width: 340,
              height: 500,
              child: Column(
                children: [
                  const Padding(padding: EdgeInsets.symmetric(vertical: 12.0)),
                  _playerSlot1,
                  const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
                  _playerSlot2,
                  const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
                  _playerSlot3,
                  const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
                  _playerSlot4,
                  const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
                  _playerSlot5,
                  const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
                  _playerSlot6,
                  const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
                  _playerSlot7,
                  const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
                  _playerSlot8,
                ],
              ),
            ),
            Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
            
            Container(
              width: 1000,
              child: Slider(
                label: 'Number of round(s) : $rangeSlider',

                value: rangeSlider,
                onChanged: (double newRangeSlider) {
                  setState(() {
                    rangeSlider = newRangeSlider;
                  });
                },
                min: 1,
                max: 10,
                divisions: 9,
              ),
            ),
            Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
             Row( 
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                onPressed: () {},
                child: Container(
                  padding: EdgeInsets.all(16.0),
                  width: 320,
                  alignment: Alignment.center,
                  child: Text('CANCEL', style: TextStyle(color: Color.fromARGB(255, 236, 131, 166), decoration: TextDecoration.none),),
                ),
              
            ),
            Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
            ElevatedButton(
                onPressed: () {
                  GlobalKey gameGlobalkey = GlobalKey();
                  //initGame(gameGlobalkey, ,rangeSlider);
                },
                child: Container(
                  alignment: Alignment.center,
                  width: 320,
                  padding: EdgeInsets.all(16.0),
                  child: Text('START', style: TextStyle(color: Color.fromARGB(255, 236, 131, 166), decoration: TextDecoration.none),),
                ),
              ),])
          ],
        ),
      ),
    );
  }
}


class PlayerSlot extends StatelessWidget {
  int slot_number;
  bool filled = false; 

  PlayerSlot(this.slot_number, this.filled,Player? p);
  
  Player get p => Player('Player $slot_number');
  set p(Player newPlayer) => p = newPlayer;

  void fillSlotWithPlayer(Player player) {
    filled = true;
    p = player;
  }

  @override
  Widget build(BuildContext context) {
    String writeInSlot = filled == true ? p.name : '';
    return Container( // ONE PLAYER BOX
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(10.0),
        color: Colors.pinkAccent,
      ),
        width: 300,
        height: 40,
        child: Row(
          children: [
            Container(
              width: 160,
              height: 30,
              child: Text(writeInSlot, style: TextStyle(color: Color.fromARGB(255, 236, 131, 166), decoration: TextDecoration.none, fontSize: 18, fontWeight: FontWeight.bold))),
            Padding(padding: EdgeInsets.symmetric(horizontal: 40.0)),
            IconButton(
              onPressed: () {},
              iconSize: 24,
              icon: Icon(Icons.cancel),
              color: Color.fromARGB(255, 236, 131, 166),
            ),
          ],
        ),
      
    );
  }
}

Player类代码:

class Player {
  String name;

  Player(this.name);

  set playerName(String newName) => name = newName;

  void bindPlayerToCharacter(List<Character> c) {

  }
}
问题根源

StackOverflowError的直接原因是PlayerSlot类中对p的setter实现错误:

set p(Player newPlayer) => p = newPlayer;

这里的p = newPlayer会再次调用p的setter,形成无限递归,最终耗尽栈内存抛出异常。

此外还有两个隐性问题:

  1. p的getter每次调用都会新建一个Player实例,没有存储传入的玩家对象,导致无法正确显示创建者信息
  2. PlayerSlot是StatelessWidget,调用fillSlotWithPlayer修改属性后不会触发UI重建,槽位状态更新无法在界面体现
修复方案

1. 重构PlayerSlot为StatefulWidget并修正属性逻辑

class PlayerSlot extends StatefulWidget {
  final int slotNumber;
  final Player? initialPlayer;

  const PlayerSlot({
    Key? key,
    required this.slotNumber,
    this.initialPlayer,
  }) : super(key: key);

  @override
  State<PlayerSlot> createState() => _PlayerSlotState();
}

class _PlayerSlotState extends State<PlayerSlot> {
  bool _filled = false;
  late Player _player;

  @override
  void initState() {
    super.initState();
    // 初始化玩家:优先使用传入的初始玩家,否则创建默认玩家
    if (widget.initialPlayer != null) {
      _filled = true;
      _player = widget.initialPlayer!;
    } else {
      _player = Player('Player ${widget.slotNumber}');
    }
  }

  void fillSlotWithPlayer(Player player) {
    setState(() {
      _filled = true;
      _player = player;
    });
  }

  @override
  Widget build(BuildContext context) {
    String writeInSlot = _filled ? _player.name : '';
    return Container( // ONE PLAYER BOX
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(10.0),
        color: Colors.pinkAccent,
      ),
      width: 300,
      height: 40,
      child: Row(
        children: [
          Container(
            width: 160,
            height: 30,
            child: Text(
              writeInSlot,
              style: const TextStyle(
                color: Color.fromARGB(255, 236, 131, 166),
                decoration: TextDecoration.none,
                fontSize: 18,
                fontWeight: FontWeight.bold
              )
            ),
          ),
          const Padding(padding: EdgeInsets.symmetric(horizontal: 40.0)),
          IconButton(
            onPressed: () {},
            iconSize: 24,
            icon: const Icon(Icons.cancel),
            color: const Color.fromARGB(255, 236, 131, 166),
          ),
        ],
      ),
    );
  }
}

2. 修正WaitingPlayersPage的初始化逻辑

class WaitingPlayersPageState extends State<WaitingPlayersPage> {
  double rangeSlider = 1;
  late List<PlayerSlot> playerSlots;

  @override
  void initState() {
    super.initState();
    // 批量生成8个槽位,1号槽直接传入创建者
    playerSlots = List.generate(8, (index) {
      final slotNumber = index + 1;
      return PlayerSlot(
        slotNumber: slotNumber,
        initialPlayer: slotNumber == 1 ? widget.creatorOfLobby : null,
      );
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container( // WHOLE SCREEN
        alignment: Alignment.center,
        color: const Color.fromARGB(255, 236, 131, 166),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            const SizedBox(height: 30,),
            Container( // BOX FOR PLAYERS
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10.0),
                color: Colors.pink,
              ),
              width: 340,
              height: 500,
              child: Column(
                children: [
                  const Padding(padding: EdgeInsets.symmetric(vertical: 12.0)),
                  // 遍历槽位生成UI,减少重复代码
                  ...playerSlots.map((slot) {
                    return Column(
                      children: [
                        slot,
                        const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
                      ],
                    );
                  }),
                ],
              ),
            ),
            // 以下Slider、按钮部分代码保持不变
            const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
            Container(
              width: 1000,
              child: Slider(
                label: 'Number of round(s) : $rangeSlider',
                value: rangeSlider,
                onChanged: (double newRangeSlider) {
                  setState(() {
                    rangeSlider = newRangeSlider;
                  });
                },
                min: 1,
                max: 10,
                divisions: 9,
              ),
            ),
            const Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
            Row( 
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {},
                  child: Container(
                    padding: const EdgeInsets.all(16.0),
                    width: 320,
                    alignment: Alignment.center,
                    child: const Text('CANCEL', style: TextStyle(color: Color.fromARGB(255, 236, 131, 166), decoration: TextDecoration.none),),
                  ),
                ),
                const Padding(padding: EdgeInsets.symmetric(horizontal: 8.0)),
                ElevatedButton(
                  onPressed: () {
                    GlobalKey gameGlobalkey = GlobalKey();
                    //initGame(gameGlobalkey, ,rangeSlider);
                  },
                  child: Container(
                    alignment: Alignment.center,
                    width: 320,
                    padding: const EdgeInsets.all(16.0),
                    child: const Text('START', style: TextStyle(color: Color.fromARGB(255, 236, 131, 166), decoration: TextDecoration.none),),
                  ),
                ),
              ]
            )
          ],
        ),
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Manewow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 15:32:34