Kirjoittaja: ZcMander
Kirjoitettu: 07.01.2008 – 07.01.2008
Tagit: kirjaston käyttö, koodi näytille, vinkki
PyODE on siitä "mielenkiintoinen" kirjasto, koska kun sillä tekee bodyn/jointin/geomin (ode.Body/ode.Joint/ode.GeomObject) niin se pitää aina säilyttää jossain muuttujassa, koska muuten pythonin roskienkeruu tuhoaa sen(luullakseni näin, koska ne ei vaan toimi, ellei ne ole jossain muuttujasa).
Tein yksinkertaisen luokan, jossa jokaisella kappaleella olisi oma ryhmä ja sitten törmäystarkistuksen kohdalla tarkistettaisiin missä ryhmässä törmäyksen kappaleet ovat ja tehtäisiin törmäys sen mukaan.
Tämä pienimuotoinen kirjasto sisätlää neljä luokkaa pyode2d, Physis2D, Handlers ja Handler.
pyode2d on todella yksinkertainen luokka joka luo geomin/bodyn tai jointin, mutta se myös käsittelee törmäyksen ja pitää törmäykseen tarvittavaa contactgrouppia sekä oden world ja spacet sisällään.
Physics2D on luokka joka kapseloi sisälleen kaikki jointit ja geomit (geomit sen takia, koska sillä on getBody, mutta ode.Body:stä ei saa geomia ulos). Jointelle annetaan nimi, jotta niitä voi helposti muistettavalla nimellä hakea. Geomeit on sen sijaan ryhmitelty sisäkkäisillä dictionaryillä.
Handlers hoitaa törmäysten käsittelyn, siihen voi lisätä käsittelijöitä ja handlers käy nämä käsittelijät läpi ja kutsuu niitä, jos ne ryhmään sopii.
Handler-luokka on itse törmäyksen käsitteliä, ja tästä luokasta voi helposti periä ja muokata oma käsittelijä jollekki ryhmälle.
Koodista tuli hieman pitkähkö, mutta help(pyode2d) pitäisi auttaa kirjaston käyttämisessä.
# -*- coding: utf-8 -*- #Tekijä: ZcMander #Luotu: 30.4.07 #Modifed: 2.1.08 import ode import re import logging class pyOde2d: _world = ode.World() _contactgroup = ode.JointGroup() _space = ode.Space() def set_collision_handler(self, collidecallback): """Asettaa törmäyksenkäsittelijän""" self._callback = collidecallback def set_gravity(self, gravity): """Asettaa gravitaatiovoiman""" self._world.setGravity([gravity[0], gravity[1], 0]) def create_body(self, pos): """Luo kappaleen""" body = ode.Body(self._world) body.setPosition((pos[0], pos[1], 0)) return body def create_sphere(self, radius): """Palauttaa pallon""" return ode.GeomSphere(self._space, float(radius)) def create_plane(self, normal, dist): """Luo loppumattoman maan""" return ode.GeomPlane(self._space, [normal[0], normal[1], 0], dist) def create_box(self, lenghts): """Luo laatikon""" l = lenghts[:] l.append(20) return ode.GeomBox(self._space, l) def create_polygon(self, pointlist): """ Luo monikulmion kolmiulotteisen mallin 0 1 /¨¨¨/| 3+---+2| | | | |___|/ Yksinkertainen kuutio, tästä luodaan oikeasti vain sivut, koska kahdessa ulottuviidessa ei tarvitse törmäystarkistusta syvyyssuuntaan """ #FIXME: Älä käytä Trimeshiä!!! #Trimeshi on kuulemma erittäin keskeneräinen tjsp. # ja ei välttämättä toimi hyvin esim. törmäystentarkistuksissa #Että ei vahinggossakaan jyrätä annettua pointlistiä pointlist = pointlist[:] #Pointlisti, mutta kolmiulotteisena real_pointlist = [] #Luodaan kolmiulotteinen malli, joten ensin verteksit, jotka on lähempänä for point in pointlist: point = point[:] #Ei haluta jyrätä alkuperäisiä tietoja point.append(float(-10)) real_pointlist.append(point) #Sen jälkeen lisätään malliin takana olevat verteksit for point in pointlist: point = point[:] point.append(float(10)) real_pointlist.append(point) #Luodaan kaksi kolmiota, koska trimesh ei nimensämukaan osaa neliöitä trindexes = [[0,1,2],[0,2,3]] #Ei välttämättä tarvitse, mutta luodaan toisinpäin kolmiot, joten #keskelle muodostuu risti trindexes.extend([[2,0,1], [3,1,2]]) #Luodaan facet faces = [] for i in range(len(pointlist)-1): #Vain helpotaakseen kirjoitusta, ettei tarvi pitkää muuttujan nimeä #kirjoittaa moneen kertaan b = pointlist #Neliö koostuu neljästä verteksistä (eli pisteestä), ensimmäiset kaksi #ovat edessä ja loput kaksi ovat takaa, vasemmalta oikealle kummatkin, #Katso ylhäällä olevan kuutio numerot, ne ovat tämän listan indexejä a = [float(i), float(len(b)+i), float(len(b)+i+1), float(i+1)] #Käydään kolmiot läpi for indexes in trindexes: #Ja lisätään listaan faces.append([ a[indexes[0]], a[indexes[1]], a[indexes[2]] ]) #Luodaan käsin vielä viimeinenkin sivu a = [len(b)-1, len(b)*2-1, len(b), 0] #Käydään kolmiot läpi for indexs in trindexes: #Ja lisätään listaan faces.append([ a[indexs[0]], a[indexs[1]], a[indexs[2]] ]) #Nyt voidaan luoda itse trimesh-data, #johon verteksien ja facet tallennetaan, meshdata = ode.TriMeshData() meshdata.build(real_pointlist, faces) #Luodaan geometrinen malli ja palautetaan se return ode.GeomTriMesh(meshdata, self._space) def create_joint(self, body1, body2, anchor_pos=None): """Palauttaa pallo-jointin""" j = ode.FixedJoint(self._world) j.attach(body1, body2) j.setFixed() #if anchor_pos != None: # j.setAnchor((anchor_pos[0],anchor_pos[1],0)) return j def step(self, fps): """Askeleen eteenpäin fysiikan simuloinnissa""" self._contactgroup.empty() self._space.collide((self._world, self._contactgroup), self._callback) self._world.step(0.4/fps) def get_world(self): """Palauttaa maailman""" return self._world def get_space(self): """Palauttaa maailman""" return self._space def get_contactgroup(self): """Palauttaa listan kaikista törmäyksistä""" return self._contactgroup def set_collision_handler(self, callback): """Asettaa törmäyksen käsittelijän""" self._callback = callback class Handler: def __init__(self, group1, group2): self._matching_res = [] groups = [group1, group2] for i in groups: for a in groups: if i != a: self._matching_res.append(i + "_" + a) if group1 == group2: self._matching_res.append(group1 + "_" + group2) def match(self, **kwargs): """Käy läpi omat säännölliset lausekkeet ja suorittaa törmäyksen käsittelyn""" matched = False for match_re in self._matching_res: if re.match(match_re, kwargs["rule"]): args = kwargs.copy() del args["rule"] self.handle(**args) return True return False def handle(self, contactgroup, world, body1, body2): """ contactgroup = ode.ContactGroup world = ode.World body1, body2 = dict Käsittelee törmäyksen """ geom1 = body1["geom"] geom2 = body2["geom"] contacts = ode.collide(geom1, geom2) for c in contacts: c.setMu(0) c.setBounce(0.5) j = ode.ContactJoint(world, contactgroup, c) j.attach(body1["body"], body2["body"]) class Handlers: """Huolehtii törmäyksen käsittelijöistä ja suorittaa käsittelijät kun törmäys tulee""" def __init__(self): self._log = logging.getLogger(self.__class__.__name__) self._handlers = {} # {handlername: Handler} def add(self, name, handler): """ name = string käsittelijän nimi handler = func käsittelijän funktio Lisää käsittelijän """ self._log.debug("Adding handler %s" % name) if self._handlers.has_key(name): raise NameError, "Handler's name exists" self._handlers[name] = handler def remove(self, name): """ name = string käsittelijän nimi Poistaa käsittelijän nimen perusteella """ self._log.debug("Removing handler %s" % name) if self._handlers.has_key(name): del self._handlers[name] else: raise NameError, "Handler's name not found" def collision(self, **kwargs): """ kwargs = dict Nämä välitetään itse käsittelijälle Käy käsittelijät läpi ja suorittaa ne """ rules = [] for group in kwargs["body1"]["groups"][::-1]: for group2 in kwargs["body2"]["groups"][::-1]: rules.append([group, group2]) rules.append(["all", "all"]) #Käydään käsittelijät läpi ja lisätään ohitettavien listaan, jotta #samaa käsittelijää ei ajettais kahta kertaa saman törmäyksen aikana skiphandler = [] for rule in rules: r = "_".join( [str(rule[0]), str(rule[1])] ) kwargs["rule"] = r for handler in self._handlers: if handler not in skiphandler: if self._handlers[handler].match(**kwargs): skiphandler.append(handler) class Physics2D: """ Huolehtii kappaleista ja niiden välisistä yhteyksistä (joints), lisäksi toimii pyOde2d-luokan frontendinä sekä huolentii törmäyksistä """ def __init__(self, gravity=None, custom_handler=None): """ gravity = list(2) painovoima kaksiulotteisesti custom_handler = func funktio törmäysten käsittelyyn Alustaa pyode2d:n ja asettaa törmäystenkäsittelijän, tyhjentää kappale ja jointtilistan. """ self._log = logging.getLogger(self.__class__.__name__) self._physics = pyOde2d() #Jos annettiin painovoima if gravity != None: self._physics.set_gravity(gravity) #Jos on annettu oma törmäysten käsittelijä,niin käytetään sitä self._physics.set_collision_handler(self._collision_handler) if custom_handler == None: self.handler = Handlers() self._collision_handler = self.handler.collision else: self._collision_handler = custom_handler self._joints = {} # {jointname: ode.Joint} self._geoms = {} # {category: # { # category2: # { # geomname: ode.Geom, # category3: # { # geomname: ode.Geom # } # }, # geomname: ode.Geom, # geomname: ode.Geom # } def _collision_handler(self, args, geom1, geom2): """ args = list Contactgroup ja world listassa geom1, geom2 = ode.GeomObject Kappaleet, joihin törmäys kohdistuu Esikäsittelee törmäykset """ #Tämä dicti annetaan varsinaiselle käsittelijälle kwargs = {} #Lisätään world ja contactgroup siihen world, contactgroup = args kwargs["world"] = world kwargs["contactgroup"] = contactgroup #Haetaan geomin body ja groupit ja lisätään dictiin geoms = [geom1, geom2] for i in range(len(geoms)): geom = geoms[i] body = geom.getBody() if geom != None: name = self.get_geomname(geom) else: name = "World" groups = self.get_geomgroups(geom) body_args = { "geom": geom, "groups": groups, "body": body, "name": name } kwargs["body" + str(i+1)] = body_args #for arg in kwargs: # if type(kwargs[arg]) == dict: # print arg, "{" # for index in kwargs[arg]: # print " ", index, "=", kwargs[arg][index] # print "}" # else: # print arg, "=", kwargs[arg] #print self._collision_handler(**kwargs) def _create_group_tree(self, groups, tree=None): """ groups = list luotavat ryhmöt tree = dictionary rekursiota varten Luo kategoriat jos niitä ei ole jo olemassa """ if tree == None: tree = self._geoms if not tree.has_key(groups[0]): tree[groups[0]] = {} if len(groups) > 1: self._create_group_tree(groups[1:], tree[groups[0]]) def _get_geomgroups(self, geom, geomname, tree=None, groups=None): """ geom = ode.GeomObject haettava kappale geomname = string haettavan kappaleen nimi tree = list haettava lista groups = list löydetyt ryhmät Etsii kappaleen ryhmät, jätä tree ja geom antamatta """ if tree == None: tree = self._geoms if groups == None: groups = [] for index in tree: if geom == tree[index] and geomname == index: break else: if type(tree[index]) == dict and self._get_geomname(geom, tree[index]): groups.append(index) self._get_geomgroups(geom, geomname, tree[index], groups) return groups def _get_geomname(self, geom, tree=None): """ name = string haettavan kappaleen nimi tree = dict rekursiota varten Palauttaa kappaleen kappaleen nimen perusteella """ if tree == None: tree = self._geoms # r niinkuin return r = None for index in tree: if tree[index] == geom: r = index if type(tree[index]) == dict: r = self._get_geomname(geom, tree[index]) if r != None: break return r def _remove_object(self, name, tree=None): """ name = string poistettavan kappaleen nimi tree = dict dictionary, josta poistettavaa kappaletta etsitään Poistaa kappaleen """ if tree == None: tree = self._geoms for index in tree: if type(tree[index]) != dict and index == name: del tree[index] break elif type(tree[index]) == dict: self._remove_object(name, tree[index]) def make_joint(self, name, joint, geom1=None, geom2=None, anchor=None): """ name = string jointin nimi joint = string jointin tyypin nimi (Katso pyOde:n API) geom1, geom2 = ode.GeomObject kappaleet joiden välille jointti tulee Luo jointin kahden kappaleen välille, jonka jälkeen lisää sen jointti-listaan. Joint on sama kuin pyOde:n API:ssa, mutta ilman Joint-loppua """ #Jos annettiin ode.Joint niin käytetään sitä suoraan if type(joint) == ode.Joint: body1 = joint.getBody(0) body2 = joint.getBody(1) geom1 = self.get_geom_by_body(body1) geom2 = self.get_geom_by_body(body2) else: jointtype, joint = joint, None jointtype += "Joint" #Ei tarvitse kirjoittaa Joint-loppua #Geomit listaan, jotta voidaan käydä läpi helposti geoms = [geom1, geom2] #Jos ei ode.Jointtia annettu if not joint: #Tarkistetaan että jointin tyyppi on olemassa if not hasattr(ode, jointtype): raise TypeError, "Joint type not found" #Ja luodaan se joint = getattr(ode, jointtype)(self._physics.get_world()) #ode.Joint.attach tarvii bodyt, eikä geomeja, joten haetaan #bodyt geomin perusteella bodies = [] for geom in geoms: #Jos geom on None, niin se tarkoittaa environment:ia if geom != None: bodies.append(geom.getBody()) else: bodies.append(None) #Ja liitetään ne jointtiin joint.attach(*bodies) geomnames = {} #Haetaan nimet for geom in geoms: if geom == None: geomnames[geom] = "Environment" else: geomnames[geom] = str(self.get_geomname(geom)) #Mudostetaan jointille nimi jointname = '_'.join(geomnames.values()) + "_" + name #Tarkistetaan, ettei jointin nimi ole käytössä ja #lisäätään dictionaryyn if not self._joints.has_key(jointname): self._joints[jointname] = joint else: raise NameError, "Joint name already in use" return jointname def add_object(self, name, geomtype, body=False, groups=None, bodymass=None, pos=[0,0], fixed=False, **kwargs): """ name = string kappale tunnistetaan nimellä geomtype = string, kappaleen tyyppi esim. box, sphere body = bool, None luodaanko body group = list lista sisäkkäisista ryhmistä bodymass = float, ode.Mass, None kappaleen massa pos = tuple(2), list(2) kappaleen paikka fixed = bool, laikkuuka kappale ikinä mihinkään **kwargs = dict, lisäparametreja geom:n luomiselle (kuten meshdata polygonille) Luo kappaleen annettujen tietojen perusteella ja lisää sen lopuksi omaan listaansa """ self._log.debug("Adding object called %s" % (name)) #Aloitetaan geom:n luomisella geom = getattr(self._physics, "create_" + geomtype)(**kwargs) #Jatketaan bodyn luomisella if body: if geomtype == "box": body = ode.Body(self._physics.get_world()) body.setPosition((pos[0], pos[1], -10)) else: body = self._physics.create_body(pos) geom.setBody(body) #Asetetaan massa suoraan, jos on ode.Mass if type(bodymass) == ode.Mass: body.setMass(bodymass) #Jos int, niin luodaan massa ja asetetaan kokonaispainoksi elif type(bodymass) == float: m = ode.Mass() #Muille tyypeille ei voi antaa kokonaispainoa kuin sphere ja box if geomtype == "sphere": m.setSphereTotal(bodymass, kwargs["radius"]) elif geomtype == "box": args = [bodymass] args.extend(*kwargs["lenghts"]) m.setBoxTotal(*args) elif geomtype != None: self._log.warning("Bodymass is set, but wasn't box or sphere") m = None #Jos massaa ei ole niin, sitä ei myöskään voida asettaa if m != None: body.setMass(m) #Luodaan kategoriat geomille, jos niitä ei ole self._create_group_tree(groups) #Haetaan kategoriaviidakon keskeltä haluttu kategoria curpos = self._geoms groups = groups[:] while len(groups) != 0: for group in curpos: if type(curpos[group]) == dict: if group == groups[0]: curpos = curpos[group] groups = groups[1:] break #Break sen takia, ettei for mene solmuun kun läpi- #käytävää listaa muokattiin #Vaikka muuttujan nimi on _bodies, voi seassa olla geomejakin #if body: # curpos[name] = body #else: curpos[name] = geom #Jointit lopuksi #Jos tuli parametrina jointteja if kwargs.has_key("joints"): for key in kwargs["joints"]: value = kwargs["joints"][key] self.make_joint(key, value) #Luodaan 2d-jointti, jottei kappale vajoa syvyyksiin self.make_joint("2djoint", "Plane2D", geom, None) #Jos haluttiin kappaleen pysyvän paikallaan if fixed: name = self.make_joint("fixed", "Fixed", geom, None) j = self.get_joint(name) j.setFixed() def remove_object(self, name, tree=None): self._log.debug("Removing object called %s" % (name)) self._remove_object(name) def step(self, size): """ size = float Ehm, timestep? Kiitokset PyOde:n dokumentaatiolle Menee muuttujan size verran eteenpäin simulaatiossa """ self._physics.step(size) def get_geomname(self, geom): """ geom = ode.GeomObject Etsii nimen kappaleen perusteella Palauttaa: string, None haetun geomin nimi tai None jos ei löytynyt """ name = self._get_geomname(geom) return name def get_geom(self, name, tree=None): """ name = string haettavan kappaleen nimi tree = dict Hakee kappaleen nimen perusteella Palauttaa: ode.GeomObject, None haettu kappale, None, jos ei löytynyt """ if tree == None: tree = self._geoms # r niinkuin return r = None for index in tree: if index == name and type(tree[index]) != dict: r = tree[index] if type(tree[index]) == dict: r = self.get_geom(name, tree[index]) if r != None: break return r def get_geom_by_body(self, body, tree=None): """ body = ode.Body haettava kappale tree = dict rekursiota varten Palauttaa kappaleen kappaleen nimen perusteella """ if tree == None: tree = self._geoms # r niinkuin return r = None for index in tree: if tree[index].getBody() == body: r = index if type(tree[index]) == dict: r = self._get_geomname(body, tree[index]) if r != None: break return r def get_geomgroups(self, geom): """ geom = ode.GeomObject haettava kappale Hakee kappaleen ryhmät """ name = self.get_geomname(geom) return self._get_geomgroups(geom, name) def get_joint(self, joint): """ joint = ode.Joint haettava jointti Palauttaa jointin jointin nimen perusteella """ for name in self._joints: if joint == name: return self._joints[name] return None def print_geoms(self, tree=None, depth=0): """Tulostaa geomi hierarkian""" if tree == None: tree = self._geoms if tree == {}: return for index in tree: t = type(tree[index]) print " "*depth, if t == dict: print index self.print_geoms(tree[index], depth+1) if t != dict: print index, ":", tree[index], print
# -*- coding: utf-8 -*- import sys import math #Tarkistetaan, jos halutaan piirrettävän ilman opengllää opengl = True if len(sys.argv) > 1 and sys.argv[1] == "--no-opengl": opengl = False import pyode2d import pygame if opengl: from OpenGL.GL import * from OpenGL.GLU import * class Renderer: def __init__(self, bgcolor=[128,64,255]): pygame.init() self._bgcolor = bgcolor def set_display(self, resolution, fullscreen=False, flags=0): """ resolution = list asetettava resoluutio fullscreen = bool onko kokoruudussa flags = int muita pygame:n flagejä Alustaa näytön lähimmälle sopivalle resoluutiolle """ flags = flags | pygame.DOUBLEBUF if fullscreen: flags = flags | pygame.FULLSCREEN pygame.display.set_mode(resolution, flags) def start(self): """Aloittaa piirtämisen""" pygame.display.get_surface().fill(self._bgcolor) def draw(self, shape, **kwargs): """ shape = string objectin muoto kwargs = dict muoto-kohtaiset asetukset, kuten sphere:llä radius Piirtää objectin muodon mukaan """ getattr(self, "_draw_" + shape)(**kwargs) def end(self): """Lopettaa piirtämisen""" pygame.display.flip() def _draw_sphere(self, pos, radius, color, **kwargs): pos = list(pos) pos[0] += radius/2 pos[1] += radius/2 pygame.draw.circle(pygame.display.get_surface(), color, pos, radius) def _draw_plane(self, **kwargs): pass def _draw_box(self, pos, color, lenghts, **kwargs): rect = pygame.Rect(pos, lenghts) pygame.draw.rect(pygame.display.get_surface(), color, rect) class GLRenderer(Renderer): def _convert_color(self, color): return color[0]/255.0, color[1]/255.0, color[2]/255.0 def set_display(self, resolution, fullscreen=False): Renderer.set_display(self, resolution, fullscreen, pygame.OPENGL) c = self._convert_color(self._bgcolor) glClearColor(c[0], c[1], c[2], 1.0) glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, resolution[0], 0, resolution[1]); glMatrixMode(GL_MODELVIEW); glDisable(GL_DEPTH_TEST) def start(self): glClear(GL_COLOR_BUFFER_BIT) def _draw_cross(self, pos): glLoadIdentity() glColorf((1,0,0)) glTranslatef(pos[0], pos[1], 0) glBegin(GL_LINES) glVertex2f(-10,0) glVertex2f( 10,0) glEnd() glBegin(GL_LINES) glVertex2f(0,-10) glVertex2f(0, 10) glEnd() def _start_draw(self, pos, color, type, rotation=None): glLoadIdentity() glColorf((1,0,0)) glBegin(GL_POINTS) glVertex2f(*pos) glEnd() glTranslatef(pos[0], pos[1], 0) if rotation: R = rotation rot = [R[0], R[3], R[6], 0, R[1], R[4], R[7], 0, R[2], R[5], R[8], 0, 0, 0, 0, 1] glMultMatrixf(rot) glColorf(self._convert_color(color)) glBegin(type) def _draw_box(self, pos, color, lenghts, rotation, **kwargs): self._start_draw(pos, color, GL_QUADS, rotation) w,h = lenghts glVertex2f(-w/2, -h/2) glVertex2f( w/2, -h/2) glVertex2f( w/2, h/2) glVertex2f(-w/2, h/2) glEnd() #self._draw_cross(pos) def _draw_sphere(self, pos, radius, color, **kwargs): self._start_draw(pos, color, GL_TRIANGLE_FAN) glVertex2f(0,0) for i in range(360): glVertex2f(math.cos(math.degrees(i))*radius, math.sin(math.degrees(i))*radius) glEnd() #self._draw_cross(pos) class Obj: """Luokka, joka yhdistää piirtämisen ja fysiikan""" def __init__(self, physics2d, renderer, color=[0,0,0], **kwargs): self._kwargs = kwargs self._renderer = renderer self._color = color self._physics2d = physics2d self._physics2d.add_object(**kwargs) def draw(self): """Piirtää kappaleen renderöijällä""" args = self._kwargs.copy() shape = self._kwargs["geomtype"] geom = self._physics2d.get_geom(self._kwargs["name"]) body = geom.getBody() if body != None: args["pos"] = body.getPosition()[:-1] args["rotation"] = body.getRotation() args["color"] = self._color self._renderer.draw(shape, **args) def add_force(self, f): geom = self._physics2d.get_geom(self._kwargs["name"]) body = geom.getBody() body.addForce((f[0], f[1], 0)) def print_debug(self): print self._kwargs["name"], print self._physics2d.get_geom(self._kwargs["name"]).getPosition() def main(): global opengl #Luodaan fysiikat physics = pyode2d.Physics2D([0, -0.01]) handler = pyode2d.Handler("all", "all") handler2 = pyode2d.Handler("box", "box") physics.handler.add("alltoall", handler) physics.handler.add("boxtobox", handler2) #Luodaan piirtäjä if opengl: render = GLRenderer() else: render = Renderer() render.set_display((800,600)) #Luodaan muutama objekti objects = [] obj = Obj(physics, render, name = "sphere1", geomtype = "sphere", body = True, groups = ["spheres"], bodymass = 0.0001, pos = [400,300], radius = 100, fixed = True, color = [255,255,255]) obj2 = Obj(physics, render, name = "box1", geomtype = "box", lenghts = [100,100], groups = ["controlable"], body = True, pos = [200, 200], fixed = False, color = [0,0,0]) obj3 = Obj(physics, render, name = "box2", geomtype = "box", lenghts = [100, 100], body = True, groups = ["box2"], pos = [400, 600], fixed = False, color = [128,128,128]) obj4 = Obj(physics, render, name = "sphere2", geomtype = "sphere", radius = 50, body = True, groups = ["spheres"], pos = [400, 800], fixed = False, color = [255,128,255]) obj6 = Obj(physics, render, name = "floor", geomtype = "plane", dist = 0, normal = [0, 1], body = False, groups = ["walls", "floor"], pos = [0, 600], fixed = True, color = [128,255,64]) objects.append(obj) objects.append(obj2) objects.append(obj3) objects.append(obj4) objects.append(obj6) done = False while not done: for e in pygame.event.get(): #Jos painettiin esciä tai suljetaan ikkuna if e.type == pygame.QUIT or \ ( e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE ): done = True elif e.type == pygame.KEYDOWN and e.key == pygame.K_SPACE: pass if e.type == pygame.KEYDOWN: if e.key == pygame.K_UP: objects[1].add_force((0,1)) if e.key == pygame.K_DOWN: objects[0].add_force((0,-1)) if e.key == pygame.K_LEFT: objects[1].add_force((-1,0)) if e.key == pygame.K_RIGHT: objects[1].add_force((1,0)) physics.step(1) #for obj in objects: # obj.print_debug() render.start() for obj in objects: obj.draw() render.end() if __name__ == '__main__': main()
Kannattaisi mielestäni mainita selvästi, mitkä ovat perus-Pythonin ulkopuoliset kirjastot, joita tämä koodivinkki vaatii. Sen voi kai mainita täällä kommentissakin.
PyODE: http://pyode.sourceforge.net/
PyGame: http://pygame.org/
Nämä on tietääkseni helppo asentaa, joten aloittelevakin Pythonisti pääsee asennuksen jälkeen nopsasti kokeilemaan koodia. (Toivottavasti tuossa oli kaikki vaatimukset.)
Juuh, ne unohtuikin mainita, eli yllä olevan listan lisäksi:
PyOpenGL: http://pyopengl.sourceforge.net/
Jos ei halua opengl:lää asentaa (tai onnistu asentamaan) niin esimerkin voi ajaa myös pelkällä pygamella:
python exmaple.py --no-opengl
Mutta pygamella piirto on himean vajaata, koska kappaleiden asentoja(rotation) ei oteta huomioon.