diff --git a/mpf/config_spec.yaml b/mpf/config_spec.yaml
index 937b708c5..39c05c8eb 100644
--- a/mpf/config_spec.yaml
+++ b/mpf/config_spec.yaml
@@ -544,7 +544,9 @@ game:
     balls_per_game: single|template_int|3
     max_players: single|template_int|4
     start_game_switch_tag: single|str|start
+    start_game_event: single|str|None
     add_player_switch_tag: single|str|start
+    add_player_event: single|str|None
     allow_start_with_loose_balls: single|bool|False
     allow_start_with_ball_in_drain: single|bool|False
 hardware:
diff --git a/mpf/modes/attract/code/attract.py b/mpf/modes/attract/code/attract.py
index 365d7e1c7..1a933ac27 100644
--- a/mpf/modes/attract/code/attract.py
+++ b/mpf/modes/attract/code/attract.py
@@ -38,6 +38,9 @@ class Attract(Mode):
                 self.machine.switch_controller.add_switch_handler(
                     switch.name, self.start_button_released, 0))
 
+        if self.machine.config['game']['start_game_event']:
+            self.add_mode_event_handler(self.machine.config['game']['start_game_event'], self.start_button_released)
+
         if hasattr(self.machine, 'ball_devices'):
             self.machine.ball_controller.collect_balls()
 
@@ -61,7 +64,7 @@ class Attract(Mode):
         """
         self.start_button_pressed_time = self.machine.clock.get_time()
 
-    def start_button_released(self):
+    def start_button_released(self, **kwargs):
         """Handle start button release.
 
         Called when the a switch tagged with *start* is deactivated.
@@ -70,6 +73,7 @@ class Attract(Mode):
         called *request_to_start_game*. If that event comes back True, this
         method calls :meth:`result_of_start_request`.
         """
+        del kwargs
         self.start_hold_time = self.machine.clock.get_time() - self.start_button_pressed_time
         self.start_buttons_held = list()
 
diff --git a/mpf/modes/game/code/game.py b/mpf/modes/game/code/game.py
index 663774e91..a4b548115 100644
--- a/mpf/modes/game/code/game.py
+++ b/mpf/modes/game/code/game.py
@@ -73,6 +73,9 @@ class Game(AsyncMode):
                     '%', self.machine.config['game']['add_player_switch_tag']),
                 self.request_player_add)
 
+        if self.machine.config['game']['add_player_event']:
+            self.add_mode_event_handler(self.machine.config['game']['add_player_event'], self.request_player_add)
+
         self.max_players = self.machine.config['game']['max_players'].evaluate({})
 
         yield from self._start_game()

Support game start/player add with an event in attract and game mode
Currently we only support switches to start a game. Also allow event only starts (e.g. with combo_switches). See: https://groups.google.com/forum/#!topic/mpf-users/vuUJMdSI2_A
diff --git a/mpf/tests/machine_files/game/config/config.yaml b/mpf/tests/machine_files/game/config/config.yaml
index c43ae23eb..d8aa231d6 100644
--- a/mpf/tests/machine_files/game/config/config.yaml
+++ b/mpf/tests/machine_files/game/config/config.yaml
@@ -2,6 +2,8 @@
 
 game:
     balls_per_game: 3
+    start_game_event: start_my_game
+    add_player_event: add_my_player
 
 coils:
     eject_coil1:
diff --git a/mpf/tests/test_Game.py b/mpf/tests/test_Game.py
index 65064903f..9b6483c7d 100644
--- a/mpf/tests/test_Game.py
+++ b/mpf/tests/test_Game.py
@@ -388,6 +388,29 @@ class TestGame(MpfGameTestCase):
         self.assertEqual('game_ending', self._events.call_args_list[7][1]['event_name'])
         self.assertEqual('game_ended', self._events.call_args_list[8][1]['event_name'])
 
+    def testGameEvents(self):
+        self.machine.switch_controller.process_switch('s_ball_switch1', 1)
+        self.machine.switch_controller.process_switch('s_ball_switch2', 1)
+        self.advance_time_and_run(10)
+        self.assertEqual(2, self.machine.ball_controller.num_balls_known)
+        self.assertEqual(2, self.machine.ball_devices.bd_trough.balls)
+
+        self.post_event("start_my_game")
+        self.assertGameIsRunning()
+        self.advance_time_and_run()
+        self.assertPlayerCount(1)
+        self.post_event("start_my_game")
+        self.assertPlayerCount(1)
+
+        self.post_event("add_my_player")
+        self.assertPlayerCount(2)
+        self.post_event("add_my_player")
+        self.assertPlayerCount(3)
+        self.post_event("add_my_player")
+        self.assertPlayerCount(4)
+        self.post_event("add_my_player")
+        self.assertPlayerCount(4)
+
 
 class TestGameLogic(MpfFakeGameTestCase):
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio pytest
apt-get update apt-get install -y gcc
3.6
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
asciimatics==1.14.0
attrs==22.2.0
certifi==2021.5.30
coverage==6.2
execnet==1.9.0
future==1.0.0
importlib-metadata==4.8.3
iniconfig==1.1.1
-e git+https://github.com/missionpinball/mpf.git@ea714f2719e28818d07afbc52d774c79e148a4b3#egg=mpf
packaging==21.3
Pillow==8.4.0
pluggy==1.0.0
psutil==7.0.0
py==1.11.0
pyfiglet==0.8.post1
pyparsing==3.1.4
pyserial==3.5
pyserial-asyncio==0.6
pytest==7.0.1
pytest-asyncio==0.16.0
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-xdist==3.0.2
ruamel.base==1.0.0
ruamel.yaml==0.10.23
terminaltables==3.1.10
tomli==1.2.3
typing==3.7.4.3
typing_extensions==4.1.1
wcwidth==0.2.13
zipp==3.6.0

name: mpf
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - asciimatics==1.14.0
      - attrs==22.2.0
      - coverage==6.2
      - execnet==1.9.0
      - future==1.0.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - packaging==21.3
      - pillow==8.4.0
      - pluggy==1.0.0
      - psutil==7.0.0
      - py==1.11.0
      - pyfiglet==0.8.post1
      - pyparsing==3.1.4
      - pyserial==3.5
      - pyserial-asyncio==0.6
      - pytest==7.0.1
      - pytest-asyncio==0.16.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-xdist==3.0.2
      - ruamel-base==1.0.0
      - ruamel-yaml==0.10.23
      - terminaltables==3.1.10
      - tomli==1.2.3
      - typing==3.7.4.3
      - typing-extensions==4.1.1
      - wcwidth==0.2.13
      - zipp==3.6.0
prefix: /opt/conda/envs/mpf

mpf/tests/test_Game.py::TestGame::testGameEvents mpf/tests/test_Game.py::TestGame::testMultiplePlayerGame mpf/tests/test_Game.py::TestGame::testSinglePlayerGame
mpf/tests/test_Game.py::TestGameLogic::testLastGameScore
mpf/modes/attract/code/attract.py mpf/modes/game/code/game.py mpf/config_spec.yaml
mpf/modes/attract/code/attract.py mpf/modes/game/code/game.py mpf/config_spec.yaml