2019-08-09 03:27:07 (edited by amerikranian 2019-08-16 19:34:07)

Menus

A lot of questions evolve around the concept of menus. If you were used to BGT, you know that BGT has a module that is focused on menus and menus alone. When I started to learn Python, I kept looking for the non-existent module for creating menus. I couldn't find it. So, I had a great idea: Let's look at BGT's menu and converted the module to Python
What I didn't understand was that menus were glorified lists or arrays containing choices. Sure there are some variables that deal with music volume, holding down keys, and saving menu settings, but in a nutshell, a menu is a glorified list.

The skeleton of a menu

All of the code samples dealing with keyboard input will be using pygame

import pygame, pygame.locals as pl
class menu:
 def __init(self):
  self.menu_choices = [] #Our menu options

 def add_item(self, item):
  self.menu_choices.append(item)

 def reset(self):
  self.menu_choices = []
  #Other reset settings go here

 def run(self):
  choice = 0
  while 1: #Continuously check for user input
   for event in pygame.event.get():
     if event.type == pygame.KEYDOWN:
      if event.key == pl.K_UP and choice > 0: #Prevent negative menu options
       choice -= 1
       #To output an option, just do speak(self.menu_choices[choice])
       #No exact speech code is provided because I do not know what you will use for speech.
       #The same is true for sound
      if event.key == pl.K_DOWN and choice < len(self.menu_choices)-1: #Prevent negative menu options
       choice += 1
       #To output an option, just do speak(self.menu_choices[choice])
       #No exact speech code is provided because I do not know what you will use for speech.
       #The same is true for sound
      if event.key == pl.K_RETURN: #User pressed enter
       return choice #We can see what the user chose

#Save this code in the file called menu.py
#Now, create another file and populate it with the following code:
import pygame, menu
pygame.init()
pygame.display.set_mode((600, 400))
pygame.display.set_caption("example menu")
m = menu.menu()

def main():
 m.reset() #Make sure nothing is left in our menu 
 m.add_item("start game")
 m.add_item("quit")
 choice = m.run()
 print(m.menu_choices[choice])

main()

And there you go, a simple menu is created. Obviously you will want to expand the class at some point, but that should be enough to get you started.

But wait...

I have created a menu class that can support all of this and more. The only thing you'll have to do is add the sound and speaking logic to the class and you'll have a decent menu to work with. You can grab my version from here
No examples will be provided as I think I have commented the code rather well.

Timers

In a nutshell, a timer is a variable that gets updated with a new time every iteration of the loop that checks for time equality. Unfortunately, I was not smart enough to figure that out. Fortunately, I found someone that created a timer module and was kind enough to allow me to share it here. Stevo, or the Dwarfer, the dude who created Oh Shit, was kind enough to allow me to give out a public link to the timer script which I use in all of my projects. You can download the script here, and here is an example of the script being used.

import timer

tmr = timer.timer()
def main():
 while 1:
  if tmr.elapsed >= 2000: #Two seconds
   break #Same as time.sleep(2)

main()

As you can see, the timer module is a pretty simple one to use. I highly recommend you look through it despite me demonstrating a usage example, just because I didn't show you everything the module can do.

Sound

Sound is probably the most difficult one to get right. There is sound_lib which is relatively simple to setup and use do to the Carter Temm's sound class which basically ports BGT sound methods to Python. To put it into perspective, here is how a script that plays a sound could look like when using Carter's module.
Note: You must have the sound_lib folder and sound.py within the directory of your script.

import sound
s = sound.sound()
s.load("gun.ogg") #This assumes that you also have a file called gun.ogg within the directory of your script
s.play()
while s.handle.is_playing: pass #Do nothing

As you can see, this is quick and easy to setup and use. Carter also wrote the sound positioning functions that keep their names from BGT but still work in Python. You can find them here. Sadly, there isn't a successful sound pool port for Python. I created one which sort of works, sort of. If you wish to try and salvage it, you can grab it here.
However, the drawback with the sound_lib is that this option will not provide you with 3D sound support. I heard that the library can, in fact, do 3D positioning, but the effect is not that great.
On another hand, a user by the name of Magurp244 has created some Open Al examples which you can find here. Open Al does have 3D support, echo, recording, and reverb functions, but the drawback to using this particular library is the sound limit (about 255 at a time on windows), the need to use an external module to read any kind of audio files (The examples use the wave module which can read .wav files only), and the lack of controlling the sound's pan and volume. There are some preset formulas which you can try and use, Magurp was kind enough to include them in his wrapper, but I personally couldn't really find any large differences between them. That all being said, if you figure out a clean solution to cutting sounds off when the user moves, say, 30 units away, let me know.
There is also pysonic which requires FMOD to be installed on your system before you set it up, but I never used this module so cannot comment on it. Still, it may be something you wish to look into.
As if that wasn't enough for FMOD, there is pyfmodex, type
pip install pyfmodex
to try and install the package, but again, I don't know much about the module. It's API documentation can be found right here, and there are some examples of it in use here and here.
Thanks goes to Magurp244 again for providing the last resource.

Saving and loading data

When it comes to saving and loading game data, you have two options:
Pickle, what I use on a regular bases.
JSON, if you want your objects to be read in other languages like Javascript, JSON is the way to go. Unfortunately, I never have had such a need and thus am using pickle.

An example of using pickle
from pickle import dumps
x = 2
save_data = dumps(x) #Get the byte version of x.
f = open("save.sav", "wb")
f.write(save_data)
f.close()

Loading with Pickle is also simple

from pickle import loads
f = open("save.sav", "rb")
save_data = loads(f.read()) #Convert our byte data back to a variable
f.close()
print(save_data) #2
Attention!

Pickle's loads and dumps functions are not the same as BGT serialize and desirialize methods. I can go open your file and get an idea of what you are saving. Furthermore, I can open your file in Python and use the example I provided above to load and modify your save.

Copying text to clipboard

To my knowledge, Python does not come with a module that can copy to your clipboard by default. Fortunately, there is a quick fix for that. Type this into your command line:
pip install clipboard
When the module finishes, go and type this into your Python console:

import clipboard
clipboard.copy("hi there!")
print(clipboard.paste())

There isn't much else to say on this subject.

Compiling code

There are again, a multitude of options available for code compilation, but I have only used one of them which is arguably the simplest one out there.
First things first, type this into your command line:
pip install --upgrade pywin32
The first time I tried to use pyinstaller (What we are going to use to compile our code) I had a large amount of issues concerning the pywin32 module. I am hoping that by typing this early on you can dodge most of my problems.
After pywin32 finishes upgrading/installing, type this:
pip install pyinstaller
You're done with installing packages.
Now, create a simple script. It's your choice as to what it should be, just make sure that it actually runs before you proceed with the next step.
After you have verified that your script is working, open up the command line and go into your script directory. Type this, sit back, and hold down your control key to prevent all the spam:
pyinstaller script_name.py --onefile
After a large block of text, you should have 2 new folders in your script directory, build and dist. Dist contains the actual exe, while build has all the information about the creation process. Ignore the warnings file located in the build folder, it just tells you everything you failed to import. Only be concerned when your script does not run as it should. If you use any external files in your script such as Tolk.dll or a sounds folder, you must copy them into the dist directory if you wish for your script to run correctly and not give you a fatal error upon launch.

A suggestion before recompiling

I would strongly urge you to remove your dist folder if you want to recompile the program. I had several times where for some unknown reason I was given the old version of the exe when the dist folder still existed. Alternatively, delete the exe itself if you are feeling more adventurous.

A quick tip

If you see the "building exe from tok completed successfully" in under 5 seconds, there is a good chance that something went wrong. This might be the time to delete both build and dist folders and run the compile command again.

The --window parameter

When compiling with pyinstaller, you have several options you can give when typing in the command. The --onefile creates a nice and clean exe, but it does not hide the console, meaning that you will see a traceback on the screen should something go wrong. However, if you type something like pyinstaller script_name.py --onefile --windowed the only thing you'd see should the program crash is a "fatal error" dialog. A good rule of thumb is to always use --onefile and only use --windowed when you are planning to release the program to an audience.

Compiling with Cython

Unfortunately, do to the open-source nature of Python, it is extremely easy to decompile Python-created content. To put it in perspective, I can decompile pyinstaller-created exe in about 5 minutes. So, what do you do to protect your code? The simplest option is Cython. It converts your code to C and makes it a lot harder for anybody to use it because you can't just open up the file and look at it anymore.

Note

This portion is more advanced, and thus will not have as much hand-holding as the rest of this guide.

Installing Cython

First, if you haven't done this already, download and install visual studio. When it's finished installing or if you already have it on your PC, launch the installer and choose to modify your installation. Check the checkbox that says "Desktop development with C++" and install it. Open up your command line and type
pip install Cython
and verify that it actually works by importing it in the python console. If everything works, you are now ready to cythonize your code.

Cythonizing your scripts

Whenever you wish to create a cythonized version of your code, open up the command line, go into your script directory, and type in
cythonize -3 -i script.py
and wait to see the result. If everything works as expected, you should have a .pyd file that begins with the same module name, script in my case. If you don't have that file, review the last couple of messages the cythonizing attempt produced, it's probably do to an error in your code. If you wish to cythonize all of your python scripts, type cythonize -3 -i *.py

Compiling Cython scripts

If you are using Pyinstaller, the steps needed to compile your cythonized scripts slightly change. While the interpreter can read .pyd files without an issue, Pyinstaller cannot do the same. This means that you must tell it what exactly you want in your executable. Here is my strategy for doing so:

  • Create a file called setup.py

  • Import all the modules that my program uses, remember, Pyinstaller cannot read .pyd files

  • Call the main function if necessary.

After my setup.py is complete, I compile the program like normal, that is, pyinstaller setup.py --onefile and optionally the --windowed parameter

note

The module name doesn't matter. I just call it setup.py for clarity sake

A tip when building Cython applications

Leave out the --windowed parameter until everything runs successfully. Python is nice, loading all the modules at the start of an application, so if your program runs you know you've included everything.

Sound or data encryption

Sadly, I have not had much luck in this area. I know of a user, pauliyobo, who has created a pack file which can be used to create .dat files, but his program has some interesting issues.
Paul's pack, which you can find here has a problem where if a file is too big the program will throw a memory error. He has attempted to fix that, but to my knowledge was unsuccessful. Furthermore, the .dat files his pack creates can be opened and their contents viewed, although that is a minor annoyance. The documentation for the pack can be found here

And that's it

I don't know what else needs to be covered here. I have given more than enough information to get anyone on their feet when it comes to building audiogames in Python, too much some would say, but that's better than the alternative. I hope that this guide is useful to anyone reading, and if you think that I need to cover something else or I have left out a crucial bit of information please, let me know so I can update this guide to be of utmost help.

Parts of this guide

Python guide part 2: Windows, keyboard input, and speech
Part 1, getting started with Python

Coding is not hard. No, not at all.
What is hard is making code that accepts different and sometimes unexpected types of input and still works.
This is what truly takes a large amount of effort on a developer's part.

Thumbs up +2