BGE-Python (Add Objects With Empties)

What I am trying to do is make a script that will get a list of empties (that are parented to a single empty) that will add a list of objects into the game in function to a Timer game property.

The advantage of this doing this is then I can use a single python script to control how many enemies are added into the game in function to the game property Timer.

Seems to me like you want the spawning empties to listen for a “spawn” message, and a single empty with a timer that sends the message at the required rate.

Hello there @agoose77 :slight_smile: ; Here is a far as I have got.

Example 1 :

Add_Cubes_Test_1.blend (490 KB)

Here is the script :


from bge import logic
from random import randint, shuffle
from time import time

# Get the current scene
scene = logic.getCurrentScene()

# Get the list of game objects
objects = scene.objects

# Define the source object "empty" and all the source children objects "empties" 
sources = objects['Add_Cubes_Parent']
cubes = [ob for ob in sources.children]
shuffle(cubes)

if not hasattr(logic, 'zero_t'):
    logic.zero_t = time()
    logic.done = 0
    logic.score = 0
    
t = time() - logic.zero_t

# Get the list of game objects to add
items = ['Cube_1','Cube_2','Cube_3']
num = round(t / 100) + 3

# Define the duration of the objects
if round(t) != logic.done:
    for e in range(num):
        scene.addObject(items[randint(0,len(items)-1)], cubes[e], 600)
    logic.done = round(t)

While creating new attributes in arbitrary modules like bge.logic may get you by, it’s not a very clean or “safe” way to design your code. Generally it’s preferable to create a Class to deal with these types of situations. While they may seem more complex at first, they result in cleaner, more organized code that can easily be modified, expanded, and built upon.

Here’s what I would do:

import bge
import time
import random

# difficulty defined by (time next level starts, spawn time) tuples
DIFFICULTY = [
    (10.0, 5.0),
    (20.0, 2.5),
    (30.0, 1.0),
    (0.0, 0.5)
]

class CubeSpawner:

    def __init__(self, spawnPoints, spawnObjects, startingDifficulty=0):
        self.spawnPoints = spawnPoints
        self.spawnObjects = spawnObjects
        self.startTime = time.time()
        self.currentDifficulty = startingDifficulty
        self.nextSpawnTime = self.startTime + DIFFICULTY[startingDifficulty][1]

    def spawn_objects(self):
        for spawnPoint in self.spawnPoints:
            spawnObj = random.choice(self.spawnObjects)
            scene.addObject(spawnObj, spawnPoint)

    def main(self):
        # check if we need to spawn cubes
        now = time.time()
        if now >= self.nextSpawnTime:
            self.spawn_objects()

            # check if we need to update the difficulty
            if now >= self.startTime + DIFFICULTY[self.currentDifficulty][0] and self.currentDifficulty < len(DIFFICULTY) - 1:
                self.currentDifficulty += 1
            self.nextSpawnTime = now + DIFFICULTY[self.currentDifficulty][1]


# initialize the CubeSpawner
scene = bge.logic.getCurrentScene()
cont = bge.logic.getCurrentController()
own = cont.owner
spawner = CubeSpawner(own.children, ['Cube_1', 'Cube_2', 'Cube_3'])

def main():
    spawner.main()

Just run the main function in module mode with pulse mode enabled, and it should work fine.

Thanks Monster and Mobious ; I will check out everything that you have just posted.
I will try and post back with some progress in the next few hours…

Mobious ! I cant seem to get the script working ? could you please make a simple example…
I don’t know what I should be doing here… Should the parent empty be called “CubeSpawner” and do I need a game property named “time”…

Make sure the Python controller is set to module mode and the module field is <script_name>.main, where <script_name> is whatever you decided to name the Python script. Also, scripts you want to execute in module mode need to end in .py, so the module needs to be called <script_name>.py

Okay, I managed to get that working.

Example 2 :

Add_Cubes_Test_2.blend (463 KB)

Better call the file main.py. While you doing this the class would surely benefit to be named Main too. I wonder why the second method in that class is not named main. This seams to violate the naming convention - must by a typo. The first one init unfortunately is forced by Python to name it like that. I can’t imagine why they do not want to call it main.

“main” seems to be the preferred name to hide what the code is doing :confused:.

I think I mained a bit off-topic :stuck_out_tongue:

Yeah ! It still beats me how python scripts can change a lot depending on who makes them :confused:
I still haven’t a clue about what some parts of that script do, I will play around with it today until something unexpected happens :eyebrowlift:

Analyzing the above code:

It is designed to run in script mode.
If it is supposed to run in module mode it has some serious design flaws and can only be used at a single object and without switching scene (not even the same scene).

“DIFFICULTY”
It allows you to define a set of ranges. Each range has a start and an end value.

Unfortunately you would need to edit the (third party) Python code to customize the configuration. The good thing is it is not hidden somewhere in the code.

Class
From the input of the constructor of the class, you can assume following:

  • it expects objects to spawn at (spawn points) - in the sample a list of game objects [the children]
  • it expects objects to spawn (spawn objects) - in the sample a list of names
  • optional: it expects a difficulty (starting difficulty) - in the sample not used

The class is expected to check the system time against a timeout.
If a timeout is detected it executes the add object operation and sets up the next timeout.

Thanks Monster for the explanations, it’s very helpful ; So even if these examples can be very useful, it still isn’t really what I am trying to achieve, It will help me to spawn some enemies for a game that I need anyway ! ; But the faults on me for not being specific enough :rolleyes:

Anyway ! thanks everyone for your help, I will close this thread and post on another…

While the code I provided is designed to run in module mode, Monster is correct that in it’s current form it will only worth with one CubeSpawner. I was just providing you with the easiest setup for you current blend file. However, it is trivial to make it work with as many spawners as you want.

Leaving the class as it, you can make the following changes to the end of the script:

scene = bge.logic.getCurrentScene()
spawnerList = []
objList = ['Cube_1', 'Cube_2', 'Cube_3']

def new_spawner(cont):
    obj = cont.owner
    spawnerList.append(CubeSpawner(obj.children, objList))

def main():
    for spawner in spawnerList:
        spawner.main()

Now all you need to do is have each spawner object call <script_name>.new_spawner once and have one object call <script_name>.main every frame. Note that if you want to have CubeSpawners on multiple scenes simultaneously, you’ll need to either pass the object’s scene to the CubeSpawner initilizer or just have the spawn_objects method use spawnPoint.scene as the Scene instead of the globally defined Scene.

If you want to make individual modifications to each CubeSpawner using game object properties, you can easily make simple modifications to the new_spawner() function to check for certain properties on the object, say “startingDiffculty” for example, and pass them to the initializer if they exist.

This still keeps the dependency to the current scene.
To solve that you need to remove getCurrentScene() from module level. There is no need to keep it there anyway as it is not used there. Better move it to function level or remove it at all - I currently see no users of the scene.

The best would be to place the created class instance in a property of the owning game object. This way you can be sure it lives as long as the game object lives (and not longer ;)). Additional “spamming objects” will be independent from each other.