On my first order I had them send me the CDs blank. My thinking was I would just burn them as needed. I didn't think I would sell very many CDs, and surly not a 100 of them before I had the next version ready.
Feb 7, 2010
Number Drill CDs
On my first order I had them send me the CDs blank. My thinking was I would just burn them as needed. I didn't think I would sell very many CDs, and surly not a 100 of them before I had the next version ready.
Jul 26, 2009
Pure Python to deb and rpm
I recently started supporting Number Drill, my math drill software, for Linux a few days ago. Number Drill is a pure python program with the external dependencies of pygame, rabbyt, PyOpenGL and a few others. All of which were in the Debian and Fedora repositories.
I originally tried using cx_Freeze to make a binary to distribute. But I kept on running into major problems with this method. And as it ended up I would have to include a plethora of .so files (such as all of pygame and everything that it depends on, libssl and everything it depends on etc) just to avoid version conflicts that resulted in segfaults. Not to mention the resulting package ending up far larger than it should be.
That was about the time Matthew suggested just distributing the pyc files (gasp! I know, it's not open source) and letting package systems like deb and rpm handle dependencies. This method has ended up working almost perfectly. Both Ubuntu 9.04 and Fedora 11 have all the dependencies I require available. The resulting packages are very small (no binaries included, just the python byte code).
The only disadvantage is that it requires the latest versions of Ubuntu or Fedora (as of today) as they are the only releases that have new enough versions of my dependencies.
We wrote a bash script that automatically packages up a pure python program into both a deb and rpm. If you are interested in here it is: http://arcticpaint.com/static/blog/python_to_deb_rpm.sh
I believe that this method of packaging is even more effective in the open source world. It essentially just makes getting your program to run from source as easy as possible.
May 16, 2009
PyOpenGL 3.0 with py2exe
One of the major hindrances to using PyOpenGL before 3.0 was released (not python 3.0) was that packaging it with py2exe was next to impossible. Thankfully with the release of PyOpenGL 3.0 it is easy. Just include these two imports in one of your project files:
from ctypes import util
try:
from OpenGL.platform import win32
except AttributeError:
pass
That will tell py2exe which part of PyOpenGL to include. Otherwise you have to copy them over manually. Be sure to have the try and except around all win32 imports so your program remains cross platform.
May 7, 2009
Better pygame and rabbyt texture loading
from __future__ import division
import pygame, rabbyt, sys, os
import rabbyt
from rabbyt._rabbyt import load_texture
def next_pow2( n ):
"""
Find the next power of two.
"""
n -= 1
n = n | (n >> 1)
n = n | (n >> 2)
n = n | (n >> 4)
n = n | (n >> 8)
n = n | (n >> 16)
n += 1
return n
class Tex:
def __init__(self):
self.id = 0
self.width = 0
self.height = 0
self.tex_coords = (0,0,0,0)
_texture_cache = {}
def load_and_size(filename, filter=True, mipmap=True):
if filename not in _texture_cache:
pygame = __import__("pygame", {},{},[])
if os.path.exists(filename):
img = pygame.image.load(filename)
else:
img = pygame.image.load(os.path.join(data_directory, filename))
t = Tex()
t.width,t.height = size = list(img.get_size())
size[0] = next_pow2(size[0])
size[1] = next_pow2(size[1])
t.tex_coords = (0,t.height/size[1],t.width/size[0],0)
n = pygame.Surface(size, pygame.SRCALPHA|pygame.HWSURFACE, img)
n.blit(img, (0,size[1]-t.height))
data = pygame.image.tostring(n, 'RGBA', True)
t.id = load_texture(data, size, "RGBA", filter, mipmap)
_texture_cache[filename] = t
return _texture_cache[filename]
rabbyt.set_load_texture_file_hook(load_and_size)
Mar 31, 2009
Gondola and 4 types of players
1. Those that wanted to optimize their networks. A player from this group can spend hours playing on just one map, trying to make things run better. The ideal group to be in to enjoy Gondola.
2. Those that wanted to solve a puzzle. They lay out their networks and then they're done. Either they don't realize it or it just doesn't appeal to them to go back and optimize their routs. Keeping these players going takes a lot of large and complex maps. Maps also require some sort of goal; something to work towards and be able to fail at doing. Which is something Gondola's current selection of maps does not provide.
3. Those that just want explosions. Gondola is similar to simcity in the sense that it is a simulation and a toy. There is no real goal other than to do better that you have before. But one thing that Gondola doesn't have that simcity does is explosions. There are no futureistic robots that come and wipe out your infrustructor. No fires, tordadoes or anything else destructive.
4. Those that just didn't get it. I guess the game, for some people, isn't explained well enough. I don't blame them at all because the tutorial really isn't that comprehensive. I wish I knew in what way they didn't get it though, so I could address the confusing aspects. A game, idealy, should not require any explination or even a tutorial in order to start playing; Something I am still trying to figure out how to do.
Mar 11, 2009
Officially moved
Mar 7, 2009
Zooming and performance
I started working on making it fade into a paper-looking map when zooming out; So I could strip out the trees and terrain. But on the fly cartography that looks even half way decent is a lot of work. That's when Matthew suggested rendering the entire map to a texture. Bingo.
I created a function that set the viewport to cover the entire map and rendered just the terrain, trees and other decor to an off-screen buffer; Which I then saved to a texture. Now when the player zooms out to a certain point it switches to drawing that one texture which, compared to a 80,000+ vertex terrain and a couple hundred trees, is much faster.
The one drawback of this method is that it isn't perfectly seamless. But you wouldn't notice the difference unless you were looking for it. I rendered to a 1024x1024 texture so it is a little blurry when you use it too close up. This could be solved by rendering it to a larger texture but my concern comes when you have a map that is bigger than my 150x150 tiles (odd number, I know).
If there is any interest in seeing how I did it here it is:
(slightly modified for clarity out of context, my thanks to Richard Jones for showing how to do this on the pyglet mailing list)
# create our frame bufferUsing this method requires a certain OpenGL extension that older hardware might not have. You are also limited to a max texture resolution. Which could be an issue when it comes to large maps, even on newer hardware.
fbo = GLuint()
glGenFramebuffersEXT(1, ctypes.byref(fbo))
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo)
# allocate a texture and add to the frame buffer
tex = image.Texture.create_for_size(GL_TEXTURE_2D, 1024, 1024, GL_RGB)
glBindTexture(GL_TEXTURE_2D, tex.id)
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, tex.id, 0)
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)
assert status == GL_FRAMEBUFFER_COMPLETE_EXT
# now render
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo)
# Snipped. This is where I render my map. See lib/map/display.py for full code.
# clean up
glDeleteFramebuffersEXT(1, ctypes.byref(fbo))
self.offscreen_map = rabbyt.Sprite(tex, shape=(0,map_w,map_h,0),
tex_shape=(0,1,1,0))
A method that avoids both of these problems is to split up your prerendering of the terrain into sections of 512x512 textures. That avoids the sharpness problem. As long as your section resolutions are smaller than your screen size you can render them to the backbuffer and copy that to a texture, which avoids GL extention problems.
This is how I'm doing it:
chunk_res = 2048
chunk_tex_res = 512
num_chunks = (map_w//chunk_res+1, map_h//chunk_res+1)
glClearColor(0,0,0,0)
for x in range(num_chunks[0]):
for y in range(num_chunks[1]):
tex = image.Texture.create_for_size(GL_TEXTURE_2D,
chunk_tex_res, chunk_tex_res, GL_RGB)
l = x*chunk_res
t = y*chunk_res+chunk_res
r = x*chunk_res+chunk_res
b = y*chunk_res
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glViewport(0,0,chunk_tex_res,chunk_tex_res)
glOrtho(l, r, b, t, -1000, 1000)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glClear(GL_COLOR_BUFFER_BIT)
# (Snipped) Render your map here.
glBindTexture(GL_TEXTURE_2D, tex.id)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0,
chunk_tex_res, chunk_tex_res, 0)
self.offscreen_chunks[(x,y)] = rabbyt.Sprite(tex,
x=x*chunk_res, y=y*chunk_res,
shape=(0,chunk_res, chunk_res, 0))
glViewport(0,0,self.scene.view.width,self.scene.view.height)